@@ -14,6 +14,7 @@ import (
1414 "github.com/ethersphere/bee/v2/pkg/bzz"
1515 "github.com/ethersphere/bee/v2/pkg/crypto"
1616 "github.com/ethersphere/bee/v2/pkg/statestore/mock"
17+ "github.com/ethersphere/bee/v2/pkg/storage"
1718 "github.com/ethersphere/bee/v2/pkg/swarm"
1819 ma "github.com/multiformats/go-multiaddr"
1920)
@@ -136,21 +137,134 @@ func TestUpdateLastSeen(t *testing.T) {
136137 t .Fatal (err )
137138 }
138139
139- // advance the clock and bump last-seen; the entry must survive a prune at
140- // the original time.
141- now = time .Unix (5000 , 0 )
140+ // advance the clock past the update interval and bump last-seen; the entry
141+ // must survive a prune at the original time.
142+ seenAt := now .Add (2 * 24 * time .Hour )
143+ now = seenAt
142144 if err := store .UpdateLastSeen (overlay ); err != nil {
143145 t .Fatal (err )
144146 }
145147
146- if err := store .Prune (time . Unix ( 4000 , 0 )); err != nil {
148+ if err := store .Prune (seenAt . Add ( - time . Hour )); err != nil {
147149 t .Fatal (err )
148150 }
149151 if _ , _ , err := store .Get (overlay ); err != nil {
150152 t .Fatalf ("entry pruned despite recent last-seen: %v" , err )
151153 }
152154}
153155
156+ // TestUpdateLastSeenThrottled asserts that a bump within lastSeenUpdateInterval
157+ // does not write. Hive calls UpdateLastSeen on every gossip sighting, so this
158+ // is what keeps the write rate bounded to roughly one per peer per day.
159+ func TestUpdateLastSeenThrottled (t * testing.T ) {
160+ t .Parallel ()
161+
162+ base := time .Unix (1_000_000 , 0 )
163+ now := base
164+ mockStore := mock .NewStateStore ()
165+ store := addressbook .NewWithClock (mockStore , func () time.Time { return now })
166+
167+ overlay := swarm .NewAddress ([]byte {0 , 1 , 2 , 3 })
168+ if err := store .Put (overlay , newTestAddr (t , overlay ), true ); err != nil {
169+ t .Fatal (err )
170+ }
171+
172+ // well inside the interval: must not touch the record.
173+ now = base .Add (time .Hour )
174+ if err := store .UpdateLastSeen (overlay ); err != nil {
175+ t .Fatal (err )
176+ }
177+ if got := lastSeenOf (t , mockStore , overlay ); got != base .Unix () {
178+ t .Fatalf ("throttled update wrote: last_seen = %d, want %d" , got , base .Unix ())
179+ }
180+
181+ // past the interval: must write.
182+ now = base .Add (addressbook .LastSeenUpdateInterval + time .Second )
183+ if err := store .UpdateLastSeen (overlay ); err != nil {
184+ t .Fatal (err )
185+ }
186+ if got := lastSeenOf (t , mockStore , overlay ); got != now .Unix () {
187+ t .Fatalf ("update past interval did not write: last_seen = %d, want %d" , got , now .Unix ())
188+ }
189+ }
190+
191+ // TestUpdateLastSeenKeepsConcurrentPut pins the read-modify-write in
192+ // UpdateLastSeen against a Put that lands between its read and its write.
193+ // Without serialization the Put's Verified flag is rolled back, which would
194+ // also desync the addressbook from hive's chequebook registry.
195+ func TestUpdateLastSeenKeepsConcurrentPut (t * testing.T ) {
196+ t .Parallel ()
197+
198+ base := time .Unix (1_000_000 , 0 )
199+ now := base
200+ hooked := & hookStore {StateStorer : mock .NewStateStore ()}
201+ book := addressbook .NewWithClock (hooked , func () time.Time { return now })
202+
203+ overlay := swarm .NewAddress ([]byte {0 , 1 , 2 , 3 })
204+ addr := newTestAddr (t , overlay )
205+
206+ // a known, not-yet-verified peer.
207+ if err := book .Put (overlay , addr , false ); err != nil {
208+ t .Fatal (err )
209+ }
210+ // move past the throttle so UpdateLastSeen really writes.
211+ now = base .Add (addressbook .LastSeenUpdateInterval + time .Second )
212+
213+ // While UpdateLastSeen holds the entry it has just read, hive verifies the
214+ // same peer and stores it with Verified=true.
215+ started , finished := make (chan struct {}), make (chan struct {})
216+ hooked .onGet = func () {
217+ go func () {
218+ defer close (finished )
219+ close (started )
220+ if err := book .Put (overlay , addr , true ); err != nil {
221+ t .Error (err )
222+ }
223+ }()
224+ <- started
225+ // Give the writer time to land. Serialized, it blocks on the
226+ // addressbook lock until UpdateLastSeen returns; unsynchronized, its
227+ // write completes here and is then overwritten below.
228+ time .Sleep (100 * time .Millisecond )
229+ }
230+
231+ if err := book .UpdateLastSeen (overlay ); err != nil {
232+ t .Fatal (err )
233+ }
234+ <- finished
235+
236+ if _ , verified , err := book .Get (overlay ); err != nil || ! verified {
237+ t .Fatalf ("concurrent Put(verified=true) was rolled back: verified=%v err=%v" , verified , err )
238+ }
239+ }
240+
241+ // hookStore fires onGet once, immediately after a Get returns, to interleave a
242+ // concurrent writer inside UpdateLastSeen's read-modify-write.
243+ type hookStore struct {
244+ storage.StateStorer
245+ onGet func ()
246+ }
247+
248+ func (h * hookStore ) Get (key string , i any ) error {
249+ err := h .StateStorer .Get (key , i )
250+ if h .onGet != nil {
251+ f := h .onGet
252+ h .onGet = nil
253+ f ()
254+ }
255+ return err
256+ }
257+
258+ func lastSeenOf (t * testing.T , store storage.StateStorer , overlay swarm.Address ) int64 {
259+ t .Helper ()
260+
261+ v := & addressbook.VerifiedAddress {}
262+ if err := store .Get ("addressbook_entry_" + overlay .String (), v ); err != nil {
263+ t .Fatalf ("get entry: %v" , err )
264+ }
265+ return v .LastSeen
266+ }
267+
154268func TestPrune (t * testing.T ) {
155269 t .Parallel ()
156270
0 commit comments