Skip to content

feat: reserve-expanding sync eligibility for redistribution game (SWIP-44) - #5568

Open
aloknerurkar wants to merge 2 commits into
masterfrom
swip-44.0
Open

aloknerurkar wants to merge 2 commits into
masterfrom
swip-44.0

Conversation

@aloknerurkar

@aloknerurkar aloknerurkar commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

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

Implements SWIP-44: Reserve-Expanding Sync Eligibility.

Currently, Bee excludes a node from participating in the redistribution game whenever any historical pullsync is active (SyncRate() > 0). When a node is expanding its reserve into a lower bin ($d-1$) in preparation for a storage radius decrease, this sync can take hours, causing the node to miss rounds despite maintaining a complete and valid reserve at the current storage depth $d$.

Because the reserve digest $H(R_N(d))$ is computed strictly over PO bins $p \geq d$, chunks arriving in bins $p < d$ are disjoint from the reserve and do not influence the digest. This PR amends the eligibility check to allow participation during sub-depth sync:

  1. pkg/puller:
    • Added bin-level active historical sync worker tracking (activeHistSyncs [swarm.MaxBins]int, activeHistSyncsMu sync.RWMutex).
    • Exposed IsBinSyncing(bin uint8) bool and depth-aware IsReserveSynced(depth uint8) bool.
    • Updated mock syncer and added TestIsReserveSynced.
  2. pkg/storageincentives:
    • Updated Agent constructor and sampling handler to evaluate reserveSyncedFunc(committedDepth) instead of blanket global sync state.
    • Added unit tests covering game participation during sub-depth sync vs. reserve-depth sync.
  3. pkg/node:
    • Wired pullerService.IsReserveSynced(depth) into storageincentives.New.

Open API Spec Version Changes (if applicable)
N/A

Motivation and Context (Optional)
Addresses SWIP-44 (ethersphere/SWIPs#92).

Related Issue (Optional)

Screenshots (if appropriate):
N/A

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.

@aloknerurkar aloknerurkar changed the title feat: eligibility for incentives during reserve-expanding pullsync feat: reserve-expanding sync eligibility for redistribution game (SWIP-44) Aug 21, 2026

@sbackend123 sbackend123 left a comment

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.

Nice job!

I would just clean up it a little bit - removing "formatting" changes, so PR looks bigger than it is.

Comment thread pkg/storageincentives/staking/contract.go Outdated
Comment thread pkg/storageincentives/staking/contract_test.go Outdated
Comment thread pkg/storageincentives/main_test.go Outdated
Comment thread pkg/storageincentives/agent.go Outdated
@aloknerurkar

Copy link
Copy Markdown
Contributor Author

Nice job!

I would just clean up it a little bit - removing "formatting" changes, so PR looks bigger than it is.

So this formatter is actually from our coding style doc. We used to follow it but I see now we dont.
https://github.com/ethersphere/bee/blob/master/CODINGSTYLE.md#code-formatting
https://github.com/ethersphere/bee/blob/master/Makefile#L74

I had my editor setup to run these formatters as soon as I open any file. I will revert the changes for now but we should update the coding style and makefile for others in future. I personally prefer this formatting as it breaks down std library, external and internal imports.

Comment thread pkg/node/node.go Outdated
reserveThreshold := reserveCapacity * 5 / 10
logger.Debug("Sync status check evaluated", "stabilized", detector.IsStabilized())
return localStore.ReserveSize() >= reserveThreshold && pullerService.SyncRate() == 0 && detector.IsStabilized()
return localStore.ReserveSize() >= reserveThreshold && pullerService.IsReserveSynced(depth) && detector.IsStabilized()

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.

When a node uses Capacity Doubling, it stores chunks locally at radius 8, but commits on the blockchain at depth 9. It is responsible for 2 adjacent "sister neighborhoods". The PR only checks if bins >= 9 are synced, completely ignoring bin 8. Is this right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Did you check the SWIP?
I believe this is intentional and consistent with how the reserve digest itself is calculated.

CommittedDepth() (doublingFactor + StorageRadius()) is what the redistribution game and the sample are actually keyed on — see sample.go's own doc comment[L56-60]. With a physical radius of 8 and a doubling factor of 1, committed depth is 9, and ReserveSample only ever reads chunks at proximity ≥ 9 to the anchor — bin 8 chunks (which straddle both sister neighborhoods) simply aren't part of what gets hashed into the digest.

Comment thread pkg/node/node.go Outdated
Comment thread pkg/puller/puller.go
Comment thread pkg/storageincentives/agent.go
@acud

acud commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

@aloknerurkar please rebase

Comment thread pkg/puller/puller.go
@@ -106,6 +107,9 @@ type Puller struct {

rate *rate.Rate // rate of historical syncing

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.

since you now count the active historical syncing using the number of active goroutines in radius bins, this thing is no longer used if i get it right. there are a couple of changes here - one is the fact we're no longer using these sliding window rate which is probably a good thing, but the problem is that the goroutine approach would be a bit tricky because it would basically flip on/off all the time. maybe a bit more thought needs to do be done here so we can get a smoothed reading that could be a bit more informative (but also that doesn't take half an hour to reach below the reading we need it to get to in order to conclude "not syncing")

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.

5 participants