Skip to content

Commit 8bc3c32

Browse files
go/worker/storage: Move availability nudger to separate worker
Same issues as with checkpointer. Possibly we would want to accept WatchFinalizedRounds as part of an interface. Co-authored-by: Peter Nose <peter.nose@gmail.com>
1 parent 9d17682 commit 8bc3c32

4 files changed

Lines changed: 179 additions & 70 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Package availabilitynudger defines logic for updating the role providers.
2+
package availabilitynudger
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"math"
8+
9+
"github.com/eapache/channels"
10+
11+
"github.com/oasisprotocol/oasis-core/go/common"
12+
"github.com/oasisprotocol/oasis-core/go/common/logging"
13+
"github.com/oasisprotocol/oasis-core/go/common/node"
14+
"github.com/oasisprotocol/oasis-core/go/roothash/api/block"
15+
"github.com/oasisprotocol/oasis-core/go/worker/registration"
16+
"github.com/oasisprotocol/oasis-core/go/worker/storage/statesync"
17+
)
18+
19+
const (
20+
// The maximum number of rounds the worker can be behind the chain before it's sensible for
21+
// it to register as available.
22+
maximumRoundDelayForAvailability = uint64(10)
23+
24+
// The minimum number of rounds the worker can be behind the chain before it's sensible for
25+
// it to stop advertising availability.
26+
minimumRoundDelayForUnavailability = uint64(15)
27+
)
28+
29+
// Worker tracks the progress of last and last synced rounds
30+
// and “nudges” role providers to mark themselves available or unavailable
31+
// based on how closely the node is keeping up with consensus.
32+
type Worker struct {
33+
roleProvider registration.RoleProvider
34+
rpcRoleProvider registration.RoleProvider
35+
roleAvailable bool
36+
37+
lastRound uint64
38+
lastSyncedRound uint64
39+
40+
blockCh *channels.InfiniteChannel
41+
stateSync *statesync.Worker
42+
43+
logger *logging.Logger
44+
}
45+
46+
// New creates a new worker that updates the availability to role providers.
47+
func New(localProvider, rpcProvider registration.RoleProvider, blockCh *channels.InfiniteChannel, stateSync *statesync.Worker, runtimeID common.Namespace) *Worker {
48+
return &Worker{
49+
roleProvider: localProvider,
50+
rpcRoleProvider: rpcProvider,
51+
lastRound: math.MaxUint64,
52+
lastSyncedRound: math.MaxUint64,
53+
blockCh: blockCh,
54+
stateSync: stateSync,
55+
logger: logging.GetLogger("worker/storage/availabilitynudger").With("runtime_id", runtimeID),
56+
}
57+
}
58+
59+
// Serve starts the worker.
60+
func (w *Worker) Serve(ctx context.Context) error {
61+
w.logger.Info("started")
62+
defer w.logger.Info("stopped")
63+
64+
finalizeCh, sub, err := w.stateSync.WatchFinalizedRounds()
65+
if err != nil {
66+
return fmt.Errorf("failed to watch finalized rounds: %w", err)
67+
}
68+
defer sub.Close()
69+
70+
for {
71+
select {
72+
case <-ctx.Done():
73+
return ctx.Err()
74+
case inBlk := <-w.blockCh.Out():
75+
blk := inBlk.(*block.Block)
76+
w.setLastRound(blk.Header.Round)
77+
case round := <-finalizeCh:
78+
w.setLastSyncedRound(round)
79+
}
80+
w.updateAvailability()
81+
}
82+
}
83+
84+
// setLastRound updates the last round number.
85+
func (w *Worker) setLastRound(round uint64) {
86+
w.lastRound = round
87+
}
88+
89+
// setLastSyncedRound updates the most recently synced round number.
90+
func (w *Worker) setLastSyncedRound(round uint64) {
91+
w.lastSyncedRound = round
92+
}
93+
94+
// updateAvailability updates the role's availability based on the gap
95+
// between the last round and the last synced round.
96+
func (w *Worker) updateAvailability() {
97+
if w.lastRound == math.MaxUint64 || w.lastSyncedRound == math.MaxUint64 {
98+
return
99+
}
100+
// if w.lastRound > w.lastSyncedRound {
101+
// return
102+
// } not sure what was intent here given this we are looking for the gap.
103+
104+
switch roundLag := w.lastRound - w.lastSyncedRound; {
105+
case roundLag < maximumRoundDelayForAvailability:
106+
w.markAvailable()
107+
case roundLag > minimumRoundDelayForUnavailability:
108+
w.markUnavailable()
109+
}
110+
}
111+
112+
// markAvailable sets the role as available if it is not already.
113+
func (w *Worker) markAvailable() {
114+
if w.roleAvailable {
115+
return
116+
}
117+
w.roleAvailable = true
118+
119+
w.logger.Info("marking as available")
120+
121+
if w.roleProvider != nil {
122+
w.roleProvider.SetAvailable(func(*node.Node) error { return nil })
123+
}
124+
if w.rpcRoleProvider != nil {
125+
w.rpcRoleProvider.SetAvailable(func(*node.Node) error { return nil })
126+
}
127+
}
128+
129+
// markUnavailable sets the role as unavailable if it is currently available.
130+
func (w *Worker) markUnavailable() {
131+
if !w.roleAvailable {
132+
return
133+
}
134+
w.roleAvailable = false
135+
136+
w.logger.Info("marking as unavailable")
137+
138+
if w.roleProvider != nil {
139+
w.roleProvider.SetUnavailable()
140+
}
141+
if w.rpcRoleProvider != nil {
142+
w.rpcRoleProvider.SetUnavailable()
143+
}
144+
}

go/worker/storage/runtime_worker.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,20 @@ import (
1515
committeeCommon "github.com/oasisprotocol/oasis-core/go/worker/common/committee"
1616
"github.com/oasisprotocol/oasis-core/go/worker/registration"
1717
storageAPI "github.com/oasisprotocol/oasis-core/go/worker/storage/api"
18+
"github.com/oasisprotocol/oasis-core/go/worker/storage/availabilitynudger"
1819
"github.com/oasisprotocol/oasis-core/go/worker/storage/checkpointer"
1920
"github.com/oasisprotocol/oasis-core/go/worker/storage/statesync"
2021
)
2122

2223
// Worker is handling storage operations for a single runtime.
2324
type worker struct {
24-
logger *logging.Logger
25-
stateSync *statesync.Worker
26-
checkpointer *checkpointer.Worker
27-
stateSyncBlkCh *channels.InfiniteChannel
25+
commonNode *committeeCommon.Node
26+
logger *logging.Logger
27+
stateSync *statesync.Worker
28+
checkpointer *checkpointer.Worker
29+
availabilityNudger *availabilitynudger.Worker
30+
stateSyncBlkCh *channels.InfiniteChannel
31+
availabilityBlkCh *channels.InfiniteChannel
2832
}
2933

3034
func newRuntimeWorker(
@@ -36,14 +40,14 @@ func newRuntimeWorker(
3640
checkpointerEnabled bool,
3741
) (*worker, error) {
3842
worker := &worker{
39-
logger: logging.GetLogger("worker/storage").With("runtimeID", commonNode.Runtime.ID()),
40-
stateSyncBlkCh: channels.NewInfiniteChannel(),
43+
commonNode: commonNode,
44+
logger: logging.GetLogger("worker/storage").With("runtimeID", commonNode.Runtime.ID()),
45+
stateSyncBlkCh: channels.NewInfiniteChannel(),
46+
availabilityBlkCh: channels.NewInfiniteChannel(),
4147
}
4248

4349
stateSync, err := statesync.New(
4450
commonNode,
45-
rp,
46-
rpRPC,
4751
localStorage,
4852
worker.stateSyncBlkCh,
4953
checkpointSyncCfg,
@@ -64,6 +68,8 @@ func newRuntimeWorker(
6468
}
6569
worker.checkpointer = checkpointer
6670

71+
worker.availabilityNudger = availabilitynudger.New(rp, rpRPC, worker.availabilityBlkCh, stateSync, commonNode.Runtime.ID())
72+
6773
return worker, nil
6874
}
6975

@@ -76,8 +82,9 @@ func (w *worker) HandleNewBlockEarlyLocked(*runtimeAPI.BlockInfo) {
7682

7783
// HandleNewBlockLocked is guarded by CrossNode.
7884
func (w *worker) HandleNewBlockLocked(bi *runtimeAPI.BlockInfo) {
79-
// Notify the state syncer that there is a new block.
85+
// Notify the state syncer and availability nudger that there is a new block.
8086
w.stateSyncBlkCh.In() <- bi.RuntimeBlock
87+
w.availabilityBlkCh.In() <- bi.RuntimeBlock
8188
}
8289

8390
// HandleRuntimeHostEventLocked is guarded by CrossNode.
@@ -117,7 +124,16 @@ func (w *worker) serve(ctx context.Context) error {
117124

118125
g, ctx := errgroup.WithContext(ctx)
119126
g.Go(func() error {
120-
return w.stateSync.Serve(ctx)
127+
if err := w.stateSync.Serve(ctx); err != nil {
128+
return fmt.Errorf("state sync worker failed: %w", err)
129+
}
130+
return nil
131+
})
132+
g.Go(func() error {
133+
if err := w.availabilityNudger.Serve(ctx); err != nil {
134+
return fmt.Errorf("availability nudger failed: %w", err)
135+
}
136+
return nil
121137
})
122138
return g.Wait()
123139
}

go/worker/storage/statesync/state_sync.go

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/eapache/channels"
1515

1616
"github.com/oasisprotocol/oasis-core/go/common/logging"
17-
"github.com/oasisprotocol/oasis-core/go/common/node"
1817
"github.com/oasisprotocol/oasis-core/go/common/pubsub"
1918
"github.com/oasisprotocol/oasis-core/go/common/workerpool"
2019
"github.com/oasisprotocol/oasis-core/go/config"
@@ -26,11 +25,9 @@ import (
2625
storageApi "github.com/oasisprotocol/oasis-core/go/storage/api"
2726
dbApi "github.com/oasisprotocol/oasis-core/go/storage/mkvs/db/api"
2827
"github.com/oasisprotocol/oasis-core/go/worker/common/committee"
29-
"github.com/oasisprotocol/oasis-core/go/worker/registration"
3028
"github.com/oasisprotocol/oasis-core/go/worker/storage/api"
3129
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/checkpointsync"
3230
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/diffsync"
33-
storagePub "github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/pub"
3431
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/synclegacy"
3532
)
3633

@@ -45,14 +42,6 @@ const (
4542

4643
checkpointSyncRetryDelay = 10 * time.Second
4744

48-
// The maximum number of rounds the worker can be behind the chain before it's sensible for
49-
// it to register as available.
50-
maximumRoundDelayForAvailability = uint64(10)
51-
52-
// The minimum number of rounds the worker can be behind the chain before it's sensible for
53-
// it to stop advertising availability.
54-
minimumRoundDelayForUnavailability = uint64(15)
55-
5645
// maxInFlightRounds is the maximum number of rounds that should be fetched before waiting
5746
// for them to be applied.
5847
maxInFlightRounds = 100
@@ -114,17 +103,10 @@ type finalizeResult struct {
114103
// In addition this worker is responsible for:
115104
// 1. Initializing the runtime state, possibly using checkpoints (if configured).
116105
// 2. Pruning the state as specified by the configuration.
117-
// 3. Optionally creating runtime state checkpoints (used by other nodes) for the state sync.
118-
// 4. Creating (and optionally advertising) statesync p2p protocol clients and servers.
119-
// 5. Registering node availability when it has synced sufficiently close to
120-
// the latest known block header.
106+
// 3. Creating (and optionally advertising) statesync p2p protocol clients and servers.
121107
type Worker struct {
122108
commonNode *committee.Node
123109

124-
roleProvider registration.RoleProvider
125-
rpcRoleProvider registration.RoleProvider
126-
roleAvailable bool
127-
128110
logger *logging.Logger
129111

130112
localStorage storageApi.LocalBackend
@@ -156,8 +138,6 @@ type Worker struct {
156138
// New creates a new state sync worker.
157139
func New(
158140
commonNode *committee.Node,
159-
roleProvider registration.RoleProvider,
160-
rpcRoleProvider registration.RoleProvider,
161141
localStorage storageApi.LocalBackend,
162142
blockCh *channels.InfiniteChannel,
163143
checkpointSyncCfg *CheckpointSyncConfig,
@@ -167,9 +147,6 @@ func New(
167147
w := &Worker{
168148
commonNode: commonNode,
169149

170-
roleProvider: roleProvider,
171-
rpcRoleProvider: rpcRoleProvider,
172-
173150
logger: logging.GetLogger("worker/storage/statesync").With("runtime_id", commonNode.Runtime.ID()),
174151

175152
localStorage: localStorage,
@@ -202,9 +179,6 @@ func New(
202179
if config.GlobalConfig.Storage.Checkpointer.Enabled {
203180
commonNode.P2P.RegisterProtocolServer(checkpointsync.NewServer(commonNode.ChainContext, commonNode.Runtime.ID(), localStorage))
204181
}
205-
if rpcRoleProvider != nil {
206-
commonNode.P2P.RegisterProtocolServer(storagePub.NewServer(commonNode.ChainContext, commonNode.Runtime.ID(), localStorage))
207-
}
208182

209183
// Create p2p protocol clients.
210184
w.legacyStorageSync = synclegacy.NewClient(commonNode.P2P, commonNode.ChainContext, commonNode.Runtime.ID())
@@ -493,31 +467,6 @@ func (w *Worker) flushSyncedState(summary *blockSummary) (uint64, error) {
493467
return w.syncedState.Round, nil
494468
}
495469

496-
// This is only called from the main worker goroutine, so no locking should be necessary.
497-
func (w *Worker) nudgeAvailability(lastSynced, latest uint64) {
498-
if lastSynced == w.undefinedRound || latest == w.undefinedRound {
499-
return
500-
}
501-
if latest-lastSynced < maximumRoundDelayForAvailability && !w.roleAvailable {
502-
w.roleProvider.SetAvailable(func(_ *node.Node) error {
503-
return nil
504-
})
505-
if w.rpcRoleProvider != nil {
506-
w.rpcRoleProvider.SetAvailable(func(_ *node.Node) error {
507-
return nil
508-
})
509-
}
510-
w.roleAvailable = true
511-
}
512-
if latest-lastSynced > minimumRoundDelayForUnavailability && w.roleAvailable {
513-
w.roleProvider.SetUnavailable()
514-
if w.rpcRoleProvider != nil {
515-
w.rpcRoleProvider.SetUnavailable()
516-
}
517-
w.roleAvailable = false
518-
}
519-
}
520-
521470
// Serve runs the state sync worker.
522471
func (w *Worker) Serve(ctx context.Context) error { // nolint: gocyclo
523472
defer close(w.diffCh)
@@ -950,7 +899,6 @@ mainLoop:
950899

951900
// Check if we're far enough to reasonably register as available.
952901
latestBlockRound = blk.Header.Round
953-
w.nudgeAvailability(lastFinalizedRound, latestBlockRound)
954902

955903
if _, ok := summaryCache[lastFullyAppliedRound]; !ok && lastFullyAppliedRound == w.undefinedRound {
956904
dummy := blockSummary{
@@ -1043,9 +991,6 @@ mainLoop:
1043991
}
1044992
storageWorkerLastFinalizedRound.With(w.getMetricLabels()).Set(float64(finalized.summary.Round))
1045993

1046-
// Check if we're far enough to reasonably register as available.
1047-
w.nudgeAvailability(lastFinalizedRound, latestBlockRound)
1048-
1049994
case <-ctx.Done():
1050995
err = ctx.Err()
1051996
break mainLoop

go/worker/storage/worker.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
committeeCommon "github.com/oasisprotocol/oasis-core/go/worker/common/committee"
1616
"github.com/oasisprotocol/oasis-core/go/worker/registration"
1717
storageWorkerAPI "github.com/oasisprotocol/oasis-core/go/worker/storage/api"
18+
"github.com/oasisprotocol/oasis-core/go/worker/storage/p2p/pub"
1819
"github.com/oasisprotocol/oasis-core/go/worker/storage/statesync"
1920
)
2021

@@ -79,6 +80,11 @@ func (w *Worker) registerRuntime(commonNode *committeeCommon.Node) error {
7980
"runtime_id", id,
8081
)
8182

83+
localStorage, err := NewLocalBackend(commonNode.Runtime.DataDir(), id)
84+
if err != nil {
85+
return fmt.Errorf("can't create local storage backend: %w", err)
86+
}
87+
8288
// Since the storage node is always coupled with another role, make sure to not add any
8389
// particular role here. Instead this only serves to prevent registration until the storage node
8490
// is synced by making the role provider unavailable.
@@ -93,10 +99,8 @@ func (w *Worker) registerRuntime(commonNode *committeeCommon.Node) error {
9399
return fmt.Errorf("failed to create rpc role provider: %w", err)
94100
}
95101
}
96-
97-
localStorage, err := NewLocalBackend(commonNode.Runtime.DataDir(), id)
98-
if err != nil {
99-
return fmt.Errorf("can't create local storage backend: %w", err)
102+
if rpRPC != nil {
103+
commonNode.P2P.RegisterProtocolServer(pub.NewServer(commonNode.ChainContext, commonNode.Runtime.ID(), localStorage))
100104
}
101105

102106
worker, err := newRuntimeWorker(

0 commit comments

Comments
 (0)