Skip to content

Commit e867557

Browse files
author
Alok Nerurkar
committed
feat: eligibility for incentives during reserve-expanding pullsync
1 parent ae1ad4e commit e867557

8 files changed

Lines changed: 199 additions & 38 deletions

File tree

pkg/api/api_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import (
2222
"time"
2323

2424
"github.com/ethereum/go-ethereum/common"
25+
"github.com/gorilla/websocket"
26+
"resenje.org/web"
27+
2528
"github.com/ethersphere/bee/v2/pkg/accesscontrol"
2629
mockac "github.com/ethersphere/bee/v2/pkg/accesscontrol/mock"
2730
accountingmock "github.com/ethersphere/bee/v2/pkg/accounting/mock"
@@ -70,8 +73,6 @@ import (
7073
"github.com/ethersphere/bee/v2/pkg/transaction/backendmock"
7174
transactionmock "github.com/ethersphere/bee/v2/pkg/transaction/mock"
7275
"github.com/ethersphere/bee/v2/pkg/util/testutil"
73-
"github.com/gorilla/websocket"
74-
"resenje.org/web"
7576
)
7677

7778
var (
@@ -702,7 +703,7 @@ func createRedistributionAgentService(
702703
postageContract,
703704
stakingContract,
704705
mockstorer.NewReserve(),
705-
func() bool { return true },
706+
func(uint8) bool { return true },
706707
time.Millisecond*10,
707708
blocksPerRound,
708709
blocksPerPhase,

pkg/node/node.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ import (
2626
"time"
2727

2828
"github.com/ethereum/go-ethereum/common"
29+
"github.com/hashicorp/go-multierror"
30+
ma "github.com/multiformats/go-multiaddr"
31+
"github.com/prometheus/client_golang/prometheus"
32+
"golang.org/x/crypto/sha3"
33+
"golang.org/x/net/idna"
34+
"golang.org/x/sync/errgroup"
35+
2936
bee "github.com/ethersphere/bee/v2"
3037
"github.com/ethersphere/bee/v2/pkg/accesscontrol"
3138
"github.com/ethersphere/bee/v2/pkg/accounting"
@@ -81,12 +88,6 @@ import (
8188
"github.com/ethersphere/bee/v2/pkg/util/ioutil"
8289
"github.com/ethersphere/bee/v2/pkg/util/nbhdutil"
8390
"github.com/ethersphere/bee/v2/pkg/util/syncutil"
84-
"github.com/hashicorp/go-multierror"
85-
ma "github.com/multiformats/go-multiaddr"
86-
"github.com/prometheus/client_golang/prometheus"
87-
"golang.org/x/crypto/sha3"
88-
"golang.org/x/net/idna"
89-
"golang.org/x/sync/errgroup"
9091
)
9192

9293
// LoggerName is the tree path name of the logger for this package.
@@ -1323,10 +1324,10 @@ func NewBee(
13231324

13241325
redistributionContract := redistribution.New(swarmAddress, overlayEthAddress, logger, transactionService, redistributionContractAddress, abiutil.MustParseABI(chainCfg.RedistributionABI), contractGasLimit)
13251326

1326-
isFullySynced := func() bool {
1327+
isReserveSynced := func(depth uint8) bool {
13271328
reserveThreshold := reserveCapacity * 5 / 10
13281329
logger.Debug("Sync status check evaluated", "stabilized", detector.IsStabilized())
1329-
return localStore.ReserveSize() >= reserveThreshold && pullerService.SyncRate() == 0 && detector.IsStabilized()
1330+
return localStore.ReserveSize() >= reserveThreshold && pullerService.IsReserveSynced(depth) && detector.IsStabilized()
13301331
}
13311332

13321333
agent, err = storageincentives.New(
@@ -1337,7 +1338,7 @@ func NewBee(
13371338
postageStampContractService,
13381339
stakingContract,
13391340
localStore,
1340-
isFullySynced,
1341+
isReserveSynced,
13411342
o.BlockTime,
13421343
storageincentives.DefaultBlocksPerRound,
13431344
storageincentives.DefaultBlocksPerPhase,

pkg/puller/export_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func (p *Puller) IsSyncing(addr swarm.Address) bool {
1515
return ok
1616
}
1717

18-
func (p *Puller) IsBinSyncing(addr swarm.Address, bin uint8) bool {
18+
func (p *Puller) IsPeerBinSyncing(addr swarm.Address, bin uint8) bool {
1919
p.syncPeersMtx.Lock()
2020
defer p.syncPeersMtx.Unlock()
2121
if peer, ok := p.syncPeers[addr.ByteString()]; ok {

pkg/puller/mock/puller.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,48 @@ package mock
66

77
import "context"
88

9-
type mockSyncer struct{ rate float64 }
9+
type Option func(*mockSyncer)
1010

11-
func NewMockRateReporter(r float64) *mockSyncer { return &mockSyncer{r} }
12-
func (m *mockSyncer) SyncRate() float64 { return m.rate }
13-
func (m *mockSyncer) Start(context.Context) {}
11+
type mockSyncer struct {
12+
rate float64
13+
isReserveSyncedFunc func(depth uint8) bool
14+
isBinSyncingFunc func(bin uint8) bool
15+
}
16+
17+
func WithReserveSynced(f func(depth uint8) bool) Option {
18+
return func(m *mockSyncer) {
19+
m.isReserveSyncedFunc = f
20+
}
21+
}
22+
23+
func WithBinSyncing(f func(bin uint8) bool) Option {
24+
return func(m *mockSyncer) {
25+
m.isBinSyncingFunc = f
26+
}
27+
}
28+
29+
func NewMockRateReporter(r float64, opts ...Option) *mockSyncer {
30+
m := &mockSyncer{rate: r}
31+
for _, opt := range opts {
32+
opt(m)
33+
}
34+
return m
35+
}
36+
37+
func (m *mockSyncer) SyncRate() float64 { return m.rate }
38+
39+
func (m *mockSyncer) IsReserveSynced(depth uint8) bool {
40+
if m.isReserveSyncedFunc != nil {
41+
return m.isReserveSyncedFunc(depth)
42+
}
43+
return true
44+
}
45+
46+
func (m *mockSyncer) IsBinSyncing(bin uint8) bool {
47+
if m.isBinSyncingFunc != nil {
48+
return m.isBinSyncingFunc(bin)
49+
}
50+
return false
51+
}
52+
53+
func (m *mockSyncer) Start(context.Context) {}

pkg/puller/puller.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"sync"
1717
"time"
1818

19+
ratelimit "golang.org/x/time/rate"
20+
1921
"github.com/ethersphere/bee/v2/pkg/log"
2022
"github.com/ethersphere/bee/v2/pkg/p2p"
2123
"github.com/ethersphere/bee/v2/pkg/puller/intervalstore"
@@ -26,7 +28,6 @@ import (
2628
"github.com/ethersphere/bee/v2/pkg/storer"
2729
"github.com/ethersphere/bee/v2/pkg/swarm"
2830
"github.com/ethersphere/bee/v2/pkg/topology"
29-
ratelimit "golang.org/x/time/rate"
3031
)
3132

3233
// loggerName is the tree path name of the logger for this package.
@@ -106,6 +107,9 @@ type Puller struct {
106107

107108
rate *rate.Rate // rate of historical syncing
108109

110+
activeHistSyncs [swarm.MaxBins]int
111+
activeHistSyncsMu sync.RWMutex
112+
109113
start sync.Once
110114

111115
limiter *ratelimit.Limiter
@@ -159,6 +163,28 @@ func (p *Puller) SyncRate() float64 {
159163
return p.rate.Rate()
160164
}
161165

166+
// IsBinSyncing returns true if any peer is actively running historical sync for the given bin.
167+
func (p *Puller) IsBinSyncing(bin uint8) bool {
168+
if bin >= p.bins {
169+
return false
170+
}
171+
p.activeHistSyncsMu.RLock()
172+
defer p.activeHistSyncsMu.RUnlock()
173+
return p.activeHistSyncs[bin] > 0
174+
}
175+
176+
// IsReserveSynced returns true if no bins at or above the given depth are actively syncing.
177+
func (p *Puller) IsReserveSynced(depth uint8) bool {
178+
p.activeHistSyncsMu.RLock()
179+
defer p.activeHistSyncsMu.RUnlock()
180+
for bin := depth; bin < p.bins; bin++ {
181+
if p.activeHistSyncs[bin] > 0 {
182+
return false
183+
}
184+
}
185+
return true
186+
}
187+
162188
func (p *Puller) manage(ctx context.Context) {
163189
defer p.wg.Done()
164190

@@ -409,9 +435,18 @@ func (p *Puller) syncPeerBin(parentCtx context.Context, peer *syncPeer, bin uint
409435
}
410436

411437
if cursor > 0 {
438+
p.activeHistSyncsMu.Lock()
439+
p.activeHistSyncs[bin]++
440+
p.activeHistSyncsMu.Unlock()
441+
412442
peer.wg.Add(1)
413443
p.wg.Add(1)
414444
safe.Go(p.logger, "puller-sync-historical", func() {
445+
defer func() {
446+
p.activeHistSyncsMu.Lock()
447+
p.activeHistSyncs[bin]--
448+
p.activeHistSyncsMu.Unlock()
449+
}()
415450
sync(true, peer.address, cursor)
416451
})
417452
}

pkg/puller/puller_test.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"testing"
1212
"time"
1313

14+
"github.com/google/go-cmp/cmp"
15+
1416
"github.com/ethersphere/bee/v2/pkg/log"
1517
"github.com/ethersphere/bee/v2/pkg/puller"
1618
"github.com/ethersphere/bee/v2/pkg/puller/intervalstore"
@@ -23,7 +25,6 @@ import (
2325
"github.com/ethersphere/bee/v2/pkg/swarm"
2426
kadMock "github.com/ethersphere/bee/v2/pkg/topology/kademlia/mock"
2527
"github.com/ethersphere/bee/v2/pkg/util/testutil"
26-
"github.com/google/go-cmp/cmp"
2728
)
2829

2930
// test that adding one peer starts syncing
@@ -462,10 +463,10 @@ func TestRadiusIncrease(t *testing.T) {
462463
rs.SetStorageRadius(2)
463464
kad.Trigger()
464465
time.Sleep(100 * time.Millisecond)
465-
if !p.IsBinSyncing(addr, 1) {
466+
if !p.IsPeerBinSyncing(addr, 1) {
466467
t.Fatalf("peer is not syncing but should")
467468
}
468-
if p.IsBinSyncing(addr, 2) {
469+
if p.IsPeerBinSyncing(addr, 2) {
469470
t.Fatalf("peer is syncing but shouldn't")
470471
}
471472
}
@@ -642,6 +643,54 @@ type opts struct {
642643
syncSleepDur time.Duration
643644
}
644645

646+
func TestIsReserveSynced(t *testing.T) {
647+
t.Parallel()
648+
649+
var (
650+
addr = swarm.RandAddress(t)
651+
cursors = []uint64{1000, 1000, 1000, 1000}
652+
replies = []mockps.SyncReply{
653+
{Bin: 1, Start: 1, Topmost: 500, Peer: addr}, // partial sync, historical stays active
654+
}
655+
)
656+
657+
p, _, kad, pullsync := newPuller(t, opts{
658+
kad: []kadMock.Option{
659+
kadMock.WithEachPeerRevCalls(
660+
kadMock.AddrTuple{Addr: addr, PO: 1},
661+
),
662+
},
663+
pullSync: []mockps.Option{
664+
mockps.WithCursors(cursors, 0),
665+
mockps.WithReplies(replies...),
666+
},
667+
bins: 4,
668+
rs: resMock.NewReserve(resMock.WithRadius(2)),
669+
})
670+
671+
kad.Trigger()
672+
waitCursorsCalled(t, pullsync, addr)
673+
waitSyncCalledBins(t, pullsync, addr, 1)
674+
675+
// While bin 1 has an active historical sync and radius is 2:
676+
err := spinlock.Wait(time.Second, func() bool {
677+
return p.IsBinSyncing(1)
678+
})
679+
if err != nil {
680+
t.Fatal("expected bin 1 to be syncing")
681+
}
682+
683+
if p.IsBinSyncing(2) {
684+
t.Fatal("expected bin 2 not to be syncing")
685+
}
686+
if !p.IsReserveSynced(2) {
687+
t.Fatal("expected reserve to be synced for depth 2 when only bin 1 is syncing")
688+
}
689+
if p.IsReserveSynced(1) {
690+
t.Fatal("expected reserve NOT to be synced for depth 1 when bin 1 is syncing")
691+
}
692+
}
693+
645694
func newPuller(t *testing.T, ops opts) (*puller.Puller, storage.StateStorer, *kadMock.Mock, *mockps.PullSyncMock) {
646695
t.Helper()
647696

pkg/storageincentives/agent.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616

1717
"github.com/ethereum/go-ethereum/common"
1818
"github.com/ethereum/go-ethereum/core/types"
19+
"resenje.org/singleflight"
20+
1921
"github.com/ethersphere/bee/v2/pkg/crypto"
2022
"github.com/ethersphere/bee/v2/pkg/log"
2123
"github.com/ethersphere/bee/v2/pkg/postage"
@@ -28,7 +30,6 @@ import (
2830
"github.com/ethersphere/bee/v2/pkg/storer"
2931
"github.com/ethersphere/bee/v2/pkg/swarm"
3032
"github.com/ethersphere/bee/v2/pkg/transaction"
31-
"resenje.org/singleflight"
3233
)
3334

3435
const loggerName = "storageincentives"
@@ -64,7 +65,7 @@ type Agent struct {
6465
batchExpirer postagecontract.PostageBatchExpirer
6566
redistributionStatuser staking.RedistributionStatuser
6667
store storer.Reserve
67-
fullSyncedFunc func() bool
68+
reserveSyncedFunc func(depth uint8) bool
6869
overlay swarm.Address
6970
quit chan struct{}
7071
wg sync.WaitGroup
@@ -82,7 +83,7 @@ func New(overlay swarm.Address,
8283
batchExpirer postagecontract.PostageBatchExpirer,
8384
redistributionStatuser staking.RedistributionStatuser,
8485
store storer.Reserve,
85-
fullSyncedFunc func() bool,
86+
reserveSyncedFunc func(depth uint8) bool,
8687
blockTime time.Duration,
8788
blocksPerRound,
8889
blocksPerPhase uint64,
@@ -101,7 +102,7 @@ func New(overlay swarm.Address,
101102
contract: contract,
102103
batchExpirer: batchExpirer,
103104
store: store,
104-
fullSyncedFunc: fullSyncedFunc,
105+
reserveSyncedFunc: reserveSyncedFunc,
105106
blocksPerRound: blocksPerRound,
106107
quit: make(chan struct{}),
107108
redistributionStatuser: redistributionStatuser,
@@ -221,7 +222,7 @@ func (a *Agent) start(blockTime time.Duration, blocksPerRound, blocksPerPhase ui
221222
a.logger.Info("entered new phase", "phase", currentPhase.String(), "round", round, "block", block)
222223

223224
a.state.SetCurrentEvent(currentPhase, round)
224-
a.state.SetFullySynced(a.fullSyncedFunc())
225+
a.state.SetFullySynced(a.reserveSyncedFunc(a.store.StorageRadius()))
225226
a.state.SetHealthy(a.health.IsHealthy())
226227
safe.Go(a.logger, "storageincentives-purge-stale-round-data", func() {
227228
a.state.purgeStaleRoundData()
@@ -416,8 +417,8 @@ func (a *Agent) handleSample(ctx context.Context, round uint64) (bool, error) {
416417
a.metrics.NeighborhoodSelected.Inc()
417418
a.logger.Info("neighbourhood chosen", "round", round)
418419

419-
if !a.state.IsFullySynced() {
420-
a.logger.Info("skipping round because node is not fully synced")
420+
if !a.reserveSyncedFunc(committedDepth) {
421+
a.logger.Info("skipping round because reserve is not synced", "depth", committedDepth, "round", round)
421422
return false, nil
422423
}
423424

0 commit comments

Comments
 (0)