A consensus client that embeds discovery (ethlambda) has to republish its
`eth2` entry at every fork-digest boundary, but it could only read the ENR.
Discovery served a private copy of the record and overwrote the shared
mirror on every IP or fork-id update, so an edit made through the mirror
never reached the wire and was lost on the next update.
The shared slot is now the only copy. It is an ArcSwap: readers take a
snapshot without locking, and every writer (IP voting, the fork-id
refresh, the new `SharedLocalNode::edit_record`) swaps a new identity in
by compare-and-swap, re-applying its edit on top of whatever got in first
instead of overwriting it.
`DiscoveryServer::spawn` takes the slot in place of the node, record and
mirror it took before. `start_network` seeds the slot with the record it
already served, so what peers receive is unchanged; RPC and shutdown now
read that same record instead of the one built at startup.
arc-swap is pinned to 1.9.2, since 1.9.3 is still inside ethlambda's
14-day crates.io publish-age cooldown.
Motivation
A consensus client that embeds ethrex discovery (ethlambda, lambdaclass/ethlambda#732) has to republish its
eth2ENR entry at every fork-digest boundary. Onfeat/discv5-predicate-queriesit can read the live ENR but not write it:DiscoveryServerserves its ownlocal_node_recordfield and only copies it intoshared_local_nodeafter an IP or fork-id update. An edit written into the shared mirror never reaches the wire, and the next update overwrites it.ethentry.Stacked on
feat/discv5-predicate-queries, which has no PR of its own yet. Retarget tomainonce it does or merges.Description
SharedLocalNodeis now the only copy of the local identity, and it has no locks.Arc<RwLock<LocalNode>>Arc<ArcSwap<LocalNode>>local_node_recordcopySharedLocalNode::edit_record(&signer, FnMut(&mut NodeRecordPairs))NodeRecord::edit(oneseqbump, re-signed), and swaps it in only if the slot still holds what it loaded. Otherwise it re-applies its edit on top of the newer record, so a consumer edit and an IP update racing each other both land.edit_recordtakesFnMut, notFnOnce, because a retry runs the edit again on the newer record. The closure must give the same result every time.edit_recordrefuses a key that doesn't match the record'ssecp256k1entry, and keepsidandsecp256k1as they were.ip,ip6,tcp,udp) stay with discovery. IP voting rewrites them, andLocalNode::nodedoesn't follow a consumer's change to them.DiscoveryServer::spawnAPI change: it now takeslocal_node: SharedLocalNodeinstead oflocal_node: Node,local_node_record: NodeRecordandshared_local_node. Thetoo_many_argumentsexpectation is gone with them. The server keeps the node id outside the slot, since packet decoding needs it on every packet and it never changes.start_networkseeds the slot with the record it already served. RPC (admin_nodeInfo) and shutdown now read that same record instead of the one built at startup fromnode_config.json. Unchanged: the served record still starts atseq2 on every boot, regardless of the persistedseq.arc-swapis pinned to 1.9.2 (June 28) in all four lockfiles. 1.9.3 (Oct 18) is still inside ethlambda's 14-day crates.io publish-age cooldown.Tests:
consumer_edit_racing_an_ip_update_keeps_both: an IP update swaps in between a consumer edit's read and its swap. The edit runs twice and the final record carries both changes atseq + 2.a_consumer_edit_is_what_peers_fetch: two real discv5 servers. A's consumer edits the record after A starts, B asks A for its ENR, and B ends up with the edited record.apply_predicted_ip_updates_shared_arcand theupdate_local_ip_*tests pass. The first now checks one slot instead of a copy and a mirror.Checklist
STORE_SCHEMA_VERSION(crates/storage/lib.rs) if the PR includes breaking changes to theStorerequiring a re-sync.