gossmap: change local API.

Now we create a separate set of local mods, and apply and unapply it.
This is more efficient than the previous approach, since we can do
some work up-front.  It's also more graceful (and well-defined) when a
local modification overlaps an existing one.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2021-02-02 15:35:05 +10:30
parent 1490ae4cf6
commit c5bd518d2f
3 changed files with 285 additions and 207 deletions

View File

@ -81,8 +81,8 @@ struct gossmap {
/* Linked list of freed ones, if any. */ /* Linked list of freed ones, if any. */
u32 freed_nodes, freed_chans; u32 freed_nodes, freed_chans;
/* local messages (tal array) */ /* local messages, if any. */
u8 *local; const u8 *local;
}; };
/* Accessors for the gossmap */ /* Accessors for the gossmap */
@ -395,7 +395,8 @@ void gossmap_remove_node(struct gossmap *map, struct gossmap_node *node)
* * [`point`:`node_id_1`] * * [`point`:`node_id_1`]
* * [`point`:`node_id_2`] * * [`point`:`node_id_2`]
*/ */
static void add_channel(struct gossmap *map, size_t cannounce_off) static struct gossmap_chan *add_channel(struct gossmap *map,
size_t cannounce_off)
{ {
/* Note that first two bytes are message type */ /* Note that first two bytes are message type */
const size_t feature_len_off = 2 + (64 + 64 + 64 + 64); const size_t feature_len_off = 2 + (64 + 64 + 64 + 64);
@ -403,6 +404,7 @@ static void add_channel(struct gossmap *map, size_t cannounce_off)
size_t scid_off; size_t scid_off;
struct node_id node_id[2]; struct node_id node_id[2];
struct gossmap_node *n[2]; struct gossmap_node *n[2];
struct gossmap_chan *chan;
u32 nidx[2]; u32 nidx[2];
feature_len = map_be16(map, cannounce_off + feature_len_off); feature_len = map_be16(map, cannounce_off + feature_len_off);
@ -424,7 +426,7 @@ static void add_channel(struct gossmap *map, size_t cannounce_off)
else else
nidx[1] = new_node(map); nidx[1] = new_node(map);
new_channel(map, cannounce_off, scid_off, nidx[0], nidx[1]); chan = new_channel(map, cannounce_off, scid_off, nidx[0], nidx[1]);
/* Now we have a channel, we can add nodes to htable */ /* Now we have a channel, we can add nodes to htable */
if (!n[0]) if (!n[0])
@ -433,6 +435,8 @@ static void add_channel(struct gossmap *map, size_t cannounce_off)
if (!n[1]) if (!n[1])
nodeidx_htable_add(&map->nodes, nodeidx_htable_add(&map->nodes,
node2ptrint(map->node_arr + nidx[1])); node2ptrint(map->node_arr + nidx[1]));
return chan;
} }
/* BOLT #7: /* BOLT #7:
@ -612,13 +616,11 @@ static bool load_gossip_store(struct gossmap *map)
if (map->fd < 0) if (map->fd < 0)
return false; return false;
/* Start with empty local map */
map->local = tal_arr(map, u8, 0);
fstat(map->fd, &st); fstat(map->fd, &st);
map->st_dev = st.st_dev; map->st_dev = st.st_dev;
map->st_ino = st.st_ino; map->st_ino = st.st_ino;
map->map_size = st.st_size; map->map_size = st.st_size;
map->local = NULL;
/* If this fails, we fall back to read */ /* If this fails, we fall back to read */
map->mmap = mmap(NULL, map->map_size, PROT_READ, MAP_SHARED, map->fd, 0); map->mmap = mmap(NULL, map->map_size, PROT_READ, MAP_SHARED, map->fd, 0);
if (map->mmap == MAP_FAILED) if (map->mmap == MAP_FAILED)
@ -661,69 +663,235 @@ static void destroy_map(struct gossmap *map)
free(map->node_arr[i].chan_idxs); free(map->node_arr[i].chan_idxs);
} }
void gossmap_local_cleanup(struct gossmap *map) /* Local modifications. We only expect a few, so we use a simple
* array. */
struct localmod {
struct short_channel_id scid;
/* If this is an entirely-local channel, here's its offset.
* Otherwise, 0xFFFFFFFF. */
u32 local_off;
/* Are updates in either direction set? */
bool updates_set[2];
/* hc[n] defined if updates_set[n]. */
struct half_chan hc[2];
/* orig[n] defined if updates_set[n] and local_off == 0xFFFFFFFF */
struct half_chan orig[2];
};
struct gossmap_localmods {
struct localmod *mods;
/* This is the local array to be used by the gossmap */
u8 *local;
};
struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx)
{ {
size_t off, msglen; struct gossmap_localmods *localmods;
/* We need to undo all the local additions and updates. localmods = tal(ctx, struct gossmap_localmods);
* FIXME: local updates may have overriden previous ones, but localmods->mods = tal_arr(localmods, struct localmod, 0);
* we simply mark them disabled (they're usually used to localmods->local = tal_arr(localmods, u8, 0);
* update local-only channels anyway). */
for (off = 0;
off < tal_bytelen(map->local);
off += sizeof(msglen) + msglen) {
struct short_channel_id scid;
struct gossmap_chan *chan;
be64 bescid;
be16 type;
/* Local cursor */ return localmods;
u8 *p = map->local + off; }
memcpy(&msglen, p, sizeof(msglen));
p += sizeof(msglen);
memcpy(&type, p, sizeof(type));
p += sizeof(type);
if (type == CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT)) { /* Create space at end of local map, return offset it was added at. */
/* Get scid from inside announcement. */ static size_t insert_local_space(struct gossmap_localmods *localmods,
be16 flen; size_t msglen)
p += 64 * 4; {
memcpy(&flen, p, sizeof(flen)); size_t oldlen = tal_bytelen(localmods->local);
p += sizeof(flen) + be16_to_cpu(flen) + 32;
memcpy(&bescid, p, sizeof(bescid));
scid.u64 = be64_to_cpu(bescid);
chan = gossmap_find_chan(map, &scid); tal_resize(&localmods->local, oldlen + msglen);
if (chan) return oldlen;
gossmap_remove_chan(map, chan); }
} else {
u8 channel_flags;
assert(type == CPU_TO_BE16(WIRE_CHANNEL_UPDATE));
p += 64 + 32;
memcpy(&bescid, p, sizeof(bescid));
p += sizeof(bescid);
scid.u64 = be64_to_cpu(bescid);
p += 4 + 1;
channel_flags = *p;
chan = gossmap_find_chan(map, &scid); static struct localmod *find_localmod(struct gossmap_localmods *localmods,
/* May have removed it when we processed const struct short_channel_id *scid)
* announce above */ {
if (chan) for (size_t i = 0; i < tal_count(localmods->mods); i++)
chan->half[channel_flags & 1].enabled = false; if (short_channel_id_eq(&localmods->mods[i].scid, scid))
} return &localmods->mods[i];
return NULL;
}
bool gossmap_local_addchan(struct gossmap_localmods *localmods,
const struct node_id *n1,
const struct node_id *n2,
const struct short_channel_id *scid,
const u8 *features)
{
be16 be16;
be64 be64;
size_t off;
struct localmod mod;
/* Don't create duplicate channels. */
if (find_localmod(localmods, scid))
return false;
mod.scid = *scid;
mod.updates_set[0] = mod.updates_set[1] = false;
/* We create fake local channel_announcement. */
off = insert_local_space(localmods,
2 + 64 * 4 + 2 + tal_bytelen(features)
+ 32 + 8 + 33 + 33);
mod.local_off = off;
/* Set type to be kosher. */
be16 = CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT);
memcpy(localmods->local + off, &be16, sizeof(be16));
off += sizeof(be16);
/* Skip sigs */
off += 64 * 4;
/* Set length and features */
be16 = cpu_to_be16(tal_bytelen(features));
memcpy(localmods->local + off, &be16, sizeof(be16));
off += sizeof(be16);
memcpy(localmods->local + off, features, tal_bytelen(features));
off += tal_bytelen(features);
/* Skip chain_hash */
off += 32;
/* Set scid */
be64 = be64_to_cpu(scid->u64);
memcpy(localmods->local + off, &be64, sizeof(be64));
off += sizeof(be64);
/* set node_ids */
memcpy(localmods->local + off, n1->k, sizeof(n1->k));
off += sizeof(n1->k);
memcpy(localmods->local + off, n2->k, sizeof(n2->k));
off += sizeof(n2->k);
assert(off == tal_bytelen(localmods->local));
tal_arr_expand(&localmods->mods, mod);
return true;
};
/* Insert a local-only channel_update. */
bool gossmap_local_updatechan(struct gossmap_localmods *localmods,
const struct short_channel_id *scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
u32 base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir)
{
struct localmod *mod;
mod = find_localmod(localmods, scid);
if (!mod) {
/* Create new reference to (presumably) existing channel. */
size_t nmods = tal_count(localmods->mods);
tal_resize(&localmods->mods, nmods + 1);
mod = &localmods->mods[nmods];
mod->scid = *scid;
mod->updates_set[0] = mod->updates_set[1] = false;
mod->local_off = 0xFFFFFFFF;
} }
/* Now zero out map */ assert(dir == 0 || dir == 1);
tal_resize(&map->local, 0); mod->updates_set[dir] = true;
mod->hc[dir].enabled = enabled;
/* node_idx needs to be set once we're in the gossmap. */
mod->hc[dir].htlc_min
= u64_to_fp16(htlc_min.millisatoshis, /* Raw: to fp16 */
false);
mod->hc[dir].htlc_max
= u64_to_fp16(htlc_max.millisatoshis, /* Raw: to fp16 */
true);
mod->hc[dir].base_fee = base_fee;
mod->hc[dir].proportional_fee = proportional_fee;
mod->hc[dir].delay = delay;
/* Check they fit */
if (mod->hc[dir].base_fee != base_fee
|| mod->hc[dir].proportional_fee != proportional_fee
|| mod->hc[dir].delay != delay)
return false;
return true;
}
/* Apply localmods to this map */
void gossmap_apply_localmods(struct gossmap *map,
struct gossmap_localmods *localmods)
{
size_t n = tal_count(localmods->mods);
assert(!map->local);
map->local = localmods->local;
for (size_t i = 0; i < n; i++) {
struct localmod *mod = &localmods->mods[i];
struct gossmap_chan *chan;
/* Find gossmap entry which this applies to. */
chan = gossmap_find_chan(map, &mod->scid);
/* If it doesn't exist, are we supposed to create a local one? */
if (!chan) {
if (mod->local_off == 0xFFFFFFFF)
continue;
/* Create new channel, pointing into local. */
chan = add_channel(map, map->map_size + mod->local_off);
}
/* Save old, overwrite (keep nodeidx) */
for (size_t h = 0; h < 2; h++) {
if (!mod->updates_set[h])
continue;
mod->orig[h] = chan->half[h];
chan->half[h] = mod->hc[h];
chan->half[h].nodeidx = mod->orig[h].nodeidx;
}
}
}
void gossmap_remove_localmods(struct gossmap *map,
const struct gossmap_localmods *localmods)
{
size_t n = tal_count(localmods->mods);
assert(map->local == localmods->local);
for (size_t i = 0; i < n; i++) {
const struct localmod *mod = &localmods->mods[i];
struct gossmap_chan *chan = gossmap_find_chan(map, &mod->scid);
/* If that's a local channel, remove it now. */
if (chan->cann_off >= map->map_size) {
gossmap_remove_chan(map, chan);
} else {
/* Restore (keep nodeidx). */
for (size_t h = 0; h < 2; h++) {
u32 nodeidx;
if (!mod->updates_set[h])
continue;
nodeidx = chan->half[h].nodeidx;
chan->half[h] = mod->orig[h];
chan->half[h].nodeidx = nodeidx;
}
}
}
map->local = NULL;
} }
bool gossmap_refresh(struct gossmap *map) bool gossmap_refresh(struct gossmap *map)
{ {
struct stat st; struct stat st;
/* You must clean local updates before this. */ /* You must remoe local updates before this. */
assert(tal_bytelen(map->local) == 0); assert(!map->local);
/* If file has changed, move to it. */ /* If file has changed, move to it. */
if (stat(map->fname, &st) != 0) if (stat(map->fname, &st) != 0)
@ -762,141 +930,6 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename)
return map; return map;
} }
/* Add something to the local map, return offset it was added at. */
static size_t insert_local_goss(struct gossmap *map, const u8 *msg TAKES)
{
size_t oldlen = tal_bytelen(map->local);
size_t msglen = tal_bytelen(msg);
/* We store length, then the msg. */
tal_resize(&map->local, oldlen + sizeof(msglen) + msglen);
memcpy(map->local + oldlen, &msglen, sizeof(msglen));
memcpy(map->local + oldlen + sizeof(msglen), msg, msglen);
if (taken(msg))
tal_free(msg);
return map->map_size + oldlen + sizeof(msglen);
}
void gossmap_local_addchan(struct gossmap *map,
const struct node_id *n1,
const struct node_id *n2,
const struct short_channel_id *scid,
const u8 *features)
{
be16 be16;
be64 be64;
size_t off;
u8 *fake_ann = tal_arr(NULL, u8,
2 + 64 * 4 + 2 + tal_bytelen(features)
+ 32 + 8 + 33 + 33);
off = 0;
/* Set type to be kosher. */
be16 = CPU_TO_BE16(WIRE_CHANNEL_ANNOUNCEMENT);
memcpy(fake_ann + off, &be16, sizeof(be16));
off += sizeof(be16);
/* Skip sigs */
off += 64 * 4;
/* Set length and features */
be16 = cpu_to_be16(tal_bytelen(features));
memcpy(fake_ann + off, &be16, sizeof(be16));
off += sizeof(be16);
memcpy(fake_ann + off, features, tal_bytelen(features));
off += tal_bytelen(features);
/* Skip chain_hash */
off += 32;
/* Set scid */
be64 = be64_to_cpu(scid->u64);
memcpy(fake_ann + off, &be64, sizeof(be64));
off += sizeof(be64);
/* set node_ids */
memcpy(fake_ann + off, n1->k, sizeof(n1->k));
off += sizeof(n1->k);
memcpy(fake_ann + off, n2->k, sizeof(n2->k));
off += sizeof(n2->k);
assert(off == tal_bytelen(fake_ann));
add_channel(map, insert_local_goss(map, take(fake_ann)));
}
/* Insert a local-only channel_update (not in the mmap'ed gossmap,
* cleared on refresh). Must exist! */
void gossmap_local_updatechan(struct gossmap *map,
const struct short_channel_id *scid,
struct amount_msat htlc_min,
struct amount_msat htlc_max,
u32 base_fee,
u32 proportional_fee,
u16 delay,
bool enabled,
int dir)
{
be16 be16;
be32 be32;
be64 be64;
size_t off;
u8 *fake_upd = tal_arr(NULL, u8,
2 + 64 + 32 + 8 + 4 + 1 + 1 + 2 + 8 + 4 + 4 + 8);
off = 0;
/* Set type to be kosher. */
be16 = CPU_TO_BE16(WIRE_CHANNEL_UPDATE);
memcpy(fake_upd + off, &be16, sizeof(be16));
off += sizeof(be16);
/* Skip signature and chainhash */
off += 64 + 32;
/* Set scid */
be64 = be64_to_cpu(scid->u64);
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
/* Skip timestamp. */
off += 4;
/* We support htlc_maximum_msat. */
fake_upd[off] = 1;
off += 1;
/* Bottom bit is direction, second is disable. */
fake_upd[off] = dir;
if (!enabled)
fake_upd[off] |= 2;
off += 1;
be16 = cpu_to_be16(delay);
memcpy(fake_upd + off, &be16, sizeof(be16));
off += sizeof(be16);
be64 = cpu_to_be64(htlc_min.millisatoshis); /* Raw: endian */
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
be32 = cpu_to_be32(base_fee);
memcpy(fake_upd + off, &be32, sizeof(be32));
off += sizeof(be32);
be32 = cpu_to_be32(proportional_fee);
memcpy(fake_upd + off, &be32, sizeof(be32));
off += sizeof(be32);
be64 = cpu_to_be64(htlc_max.millisatoshis); /* Raw: endian */
memcpy(fake_upd + off, &be64, sizeof(be64));
off += sizeof(be64);
assert(off == tal_bytelen(fake_upd));
update_channel(map, insert_local_goss(map, take(fake_upd)));
}
void gossmap_node_get_id(const struct gossmap *map, void gossmap_node_get_id(const struct gossmap *map,
const struct gossmap_node *node, const struct gossmap_node *node,
struct node_id *id) struct node_id *id)

View File

@ -43,20 +43,24 @@ struct gossmap *gossmap_load(const tal_t *ctx, const char *filename);
* was updated. Note: this can scramble node and chan indexes! */ * was updated. Note: this can scramble node and chan indexes! */
bool gossmap_refresh(struct gossmap *map); bool gossmap_refresh(struct gossmap *map);
/* Insert a local-only channel (not in the mmap'ed gossmap, cleared on /* Local modifications. */
* refresh). */ struct gossmap_localmods *gossmap_localmods_new(const tal_t *ctx);
void gossmap_local_addchan(struct gossmap *map,
/* Create a local-only channel; if this conflicts with a real channel when added,
* that will be used instead.
* Returns false (and does nothing) if scid was already in localmods.
*/
bool gossmap_local_addchan(struct gossmap_localmods *localmods,
const struct node_id *n1, const struct node_id *n1,
const struct node_id *n2, const struct node_id *n2,
const struct short_channel_id *scid, const struct short_channel_id *scid,
const u8 *features) const u8 *features)
NON_NULL_ARGS(1,2,3,4); NON_NULL_ARGS(1,2,3,4);
/* Insert a local-only channel_update (not in the mmap'ed gossmap, /* Create a local-only channel_update: can apply to lcoal-only or
* cleared on refresh). Must exist, and usually should be a local * normal channels. Returns false if amounts don't fit in our
* channel (otherwise channel will be disabled on * internal representation (implies channel unusable anyway). */
* gossmap_local_addchan!) */ bool gossmap_local_updatechan(struct gossmap_localmods *localmods,
void gossmap_local_updatechan(struct gossmap *map,
const struct short_channel_id *scid, const struct short_channel_id *scid,
struct amount_msat htlc_min, struct amount_msat htlc_min,
struct amount_msat htlc_max, struct amount_msat htlc_max,
@ -67,9 +71,13 @@ void gossmap_local_updatechan(struct gossmap *map,
int dir) int dir)
NO_NULL_ARGS; NO_NULL_ARGS;
/* Remove all local-only changes. Must be done before calling /* Apply localmods to this map */
* gossmap_refresh! */ void gossmap_apply_localmods(struct gossmap *map,
void gossmap_local_cleanup(struct gossmap *map); struct gossmap_localmods *localmods);
/* Remove localmods from this map */
void gossmap_remove_localmods(struct gossmap *map,
const struct gossmap_localmods *localmods);
/* Each channel has a unique (low) index. */ /* Each channel has a unique (low) index. */
u32 gossmap_node_idx(const struct gossmap *map, const struct gossmap_node *node); u32 gossmap_node_idx(const struct gossmap *map, const struct gossmap_node *node);

View File

@ -334,6 +334,7 @@ int main(int argc, char *argv[])
struct node_id l1, l2, l3, l4; struct node_id l1, l2, l3, l4;
struct short_channel_id scid23, scid12, scid_local; struct short_channel_id scid23, scid12, scid_local;
struct gossmap_chan *chan; struct gossmap_chan *chan;
struct gossmap_localmods *mods;
common_setup(argv[0]); common_setup(argv[0]);
@ -358,11 +359,14 @@ int main(int argc, char *argv[])
assert(gossmap_find_chan(map, &scid12)); assert(gossmap_find_chan(map, &scid12));
/* Now, let's add a new channel l1 -> l4. */ /* Now, let's add a new channel l1 -> l4. */
mods = gossmap_localmods_new(tmpctx);
assert(node_id_from_hexstr("0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", 66, &l4)); assert(node_id_from_hexstr("0382ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199", 66, &l4));
assert(short_channel_id_from_str("111x1x1", 7, &scid_local)); assert(short_channel_id_from_str("111x1x1", 7, &scid_local));
gossmap_local_addchan(map, &l1, &l4, &scid_local, NULL);
assert(gossmap_local_addchan(mods, &l1, &l4, &scid_local, NULL));
/* Apply changes, check they work. */
gossmap_apply_localmods(map, mods);
assert(gossmap_find_node(map, &l4)); assert(gossmap_find_node(map, &l4));
chan = gossmap_find_chan(map, &scid_local); chan = gossmap_find_chan(map, &scid_local);
@ -370,11 +374,27 @@ int main(int argc, char *argv[])
assert(!gossmap_chan_set(chan, 0)); assert(!gossmap_chan_set(chan, 0));
assert(!gossmap_chan_set(chan, 1)); assert(!gossmap_chan_set(chan, 1));
/* Now update it. */ /* Remove, no longer can find. */
gossmap_local_updatechan(map, &scid_local, gossmap_remove_localmods(map, mods);
assert(!gossmap_find_chan(map, &scid_local));
assert(!gossmap_find_node(map, &l4));
/* Now update it both local, and an existing one. */
gossmap_local_updatechan(mods, &scid_local,
AMOUNT_MSAT(1), AMOUNT_MSAT(1),
AMOUNT_MSAT(100000), AMOUNT_MSAT(100000),
2, 3, 4, true, 0); 2, 3, 4, true, 0);
/* Adding an existing channel is a noop. */
assert(gossmap_local_addchan(mods, &l2, &l3, &scid23, NULL));
gossmap_local_updatechan(mods, &scid23,
AMOUNT_MSAT(99),
AMOUNT_MSAT(100),
101, 102, 103, true, 0);
gossmap_apply_localmods(map, mods);
chan = gossmap_find_chan(map, &scid_local); chan = gossmap_find_chan(map, &scid_local);
assert(gossmap_chan_set(chan, 0)); assert(gossmap_chan_set(chan, 0));
assert(!gossmap_chan_set(chan, 1)); assert(!gossmap_chan_set(chan, 1));
@ -386,8 +406,17 @@ int main(int argc, char *argv[])
assert(chan->half[0].proportional_fee == 3); assert(chan->half[0].proportional_fee == 3);
assert(chan->half[0].delay == 4); assert(chan->half[0].delay == 4);
chan = gossmap_find_chan(map, &scid23);
assert(chan->half[0].enabled);
assert(chan->half[0].htlc_min == u64_to_fp16(99, false));
assert(chan->half[0].htlc_max == u64_to_fp16(100, true));
assert(chan->half[0].base_fee == 101);
assert(chan->half[0].proportional_fee == 102);
assert(chan->half[0].delay == 103);
/* Cleanup leaves everything previous intact */ /* Cleanup leaves everything previous intact */
gossmap_local_cleanup(map); gossmap_remove_localmods(map, mods);
assert(!gossmap_find_node(map, &l4)); assert(!gossmap_find_node(map, &l4));
assert(!gossmap_find_chan(map, &scid_local)); assert(!gossmap_find_chan(map, &scid_local));
assert(gossmap_find_node(map, &l1)); assert(gossmap_find_node(map, &l1));
@ -396,6 +425,14 @@ int main(int argc, char *argv[])
assert(gossmap_find_chan(map, &scid23)); assert(gossmap_find_chan(map, &scid23));
assert(gossmap_find_chan(map, &scid12)); assert(gossmap_find_chan(map, &scid12));
chan = gossmap_find_chan(map, &scid23);
assert(chan->half[0].enabled);
assert(chan->half[0].htlc_min == u64_to_fp16(0, false));
assert(chan->half[0].htlc_max == u64_to_fp16(990380000, true));
assert(chan->half[0].base_fee == 20);
assert(chan->half[0].proportional_fee == 1000);
assert(chan->half[0].delay == 6);
/* Now we can refresh. */ /* Now we can refresh. */
assert(write(fd, "", 1) == 1); assert(write(fd, "", 1) == 1);
gossmap_refresh(map); gossmap_refresh(map);