Skip to content

fix(handshake): reuse session-stable signed BzzAddress - #5493

Merged
gacevicljubisa merged 7 commits into
masterfrom
fix/session-stable-bzzaddress
Jun 8, 2026
Merged

fix(handshake): reuse session-stable signed BzzAddress#5493
gacevicljubisa merged 7 commits into
masterfrom
fix/session-stable-bzzaddress

Conversation

@gacevicljubisa

@gacevicljubisa gacevicljubisa commented Jun 5, 2026

Copy link
Copy Markdown
Member

Checklist

  • I have read the coding guide.
  • My change requires a documentation update, and I have done it.
  • I have added tests to cover my changes.
  • I have filled out the description and linked the related issues.

Description

Every handshake created and signed a new BzzAddress with a fresh timestamp, so each record the node advertised differed from the previous one even when nothing changed.
Receiving nodes therefore treated every record as an update: each reconnect caused an addressbook write on the peer, and re-gossiped records with advancing timestamps caused periodic addressbook writes across the whole network.

  • The handshake service now mints the signed BzzAddress once and reuses it while the advertised underlay set and the local chequebook stay unchanged, so reconnects and re-gossip present a byte-identical record that peers skip without writing.
  • A new record is minted only when the advertised underlays or the local chequebook actually change, with a monotonically increasing timestamp that stays valid across same-second mints and wall-clock regressions.
  • The cache keeps only the most recently minted record: since peers reject handshake records older than the one they hold (bzz.CheckTimestamp), only the latest record is ever valid to present, so a single entry is sufficient — and bounded by design, regardless of what observed underlays peers report.
  • putHandshakeAddress now skips the addressbook write when the stored record is identical and already verified, so a reconnecting peer costs one cached read and zero disk writes; the chequebook registry mapping is still re-registered and unverified records are still upgraded.

Open API Spec Version Changes (if applicable)

Motivation and Context (Optional)

Related Issue (Optional)

Closes https://github.com/ethersphere/bee-private/issues/23

Screenshots (if appropriate):

AI Disclosure

  • This PR contains code that has been generated by an LLM.
  • I have reviewed the AI generated code thoroughly.
  • I possess the technical expertise to responsibly review the code generated in this PR.

@gacevicljubisa
gacevicljubisa requested review from acud, akrem-chabchoub, janos, martinconic and sbackend123 and removed request for janos June 5, 2026 12:43
@gacevicljubisa gacevicljubisa self-assigned this Jun 5, 2026
@gacevicljubisa
gacevicljubisa force-pushed the fix/session-stable-bzzaddress branch from de0507d to 3ac8098 Compare June 5, 2026 13:10
@gacevicljubisa

Copy link
Copy Markdown
Member Author

// misses the cache and re-signs with the new chequebook.
func (s *Service) SetChequebookAddress(addr common.Address) {
if (addr == common.Address{}) {
s.chequebookAddr.Store(nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder why we can't just save &addr here... why not save the default value?

mfw78 added a commit to nxm-rs/vertex that referenced this pull request Jun 8, 2026
`prepare_local_peer` signed our self `SwarmPeer` record with
`Timestamp::now()` on every handshake (one ECDSA sign per connection),
so an unchanged advertised address set produced a different signed
record each time. Receivers store and re-gossip that record, so an
advancing timestamp on otherwise-identical content churns the network
for no semantic gain. This is the producer-side complement to #161
(which only rejects such churn on the receive side), and the root-cause
version of the fix discussed in ethersphere/bee#5493.

## Changes
- `HandshakeBehaviour` caches the signed self-record keyed by a
fingerprint of the (deterministically ordered) advertised address set.
While the set is unchanged it reuses a byte-identical record (same
timestamp, same signature) across handshakes, so we re-sign only when
the set changes. Computed at connection establishment, threaded to the
handler/protocol. `LocalAddressManager` and the `AddressProvider` trait
are untouched.
- The timestamp now means "when the advertised set last changed or was
last refreshed", not "now at each handshake". A
`SELF_RECORD_REFRESH_INTERVAL` (1h) re-signs a stable node periodically
so its record never ages out of a receiver's clock-skew window
(`SwarmPeer::parse` rejects timestamps outside ~6h as
`TimestampOutsideSkewWindow`); 1h is well under that window and above
#161's 300s `too_soon` floor, so a refresh reads as a normal update.
- The observed-address append is now last-resort only (empty set) for
both inbound and outbound, so the common-case record is peer-independent
and cacheable. The NAT-discovery role the inbound observed-append served
is covered by AutoNAT v2 (#156) and advertising confirmed-external
addresses (#160).

## Composition with #161
An unchanged record presents an equal timestamp, which #161 treats as
`Stale` (a silent no-op), not `too_soon`. So this is what actually
eliminates the churn #161 would otherwise just reject; the hourly
refresh advances the timestamp by more than the 300s interval and is
accepted as a normal update.

## Compatibility
No wire-format change and no conformance vectors changed: the peer
sign/parse interop vectors pass unchanged (the signed bytes are
identical for fixed inputs aside from the now-stable timestamp). The
residual hourly refresh is inherent to wall-clock-plus-skew-window
records; eliminating it entirely (seq-style ordering without a freshness
window) is a future SWIP/hardfork, not this PR.

## Testing
Pure `needs_resign` unit tests (no-cache,
same-fingerprint-within-interval, fingerprint-change, past-interval) and
fingerprint stability/order-sensitivity; a behaviour-level test
asserting two consecutive cached signs of an unchanged set return a
byte-identical record (same timestamp and signature);
`select_local_addrs` tests updated for last-resort-only semantics.
handshake (28), peer + interop, and topology suites pass; clippy/fmt
clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants