Skip to content

Commit badc2b3

Browse files
committed
Widen the EC/CC static provider
1 parent 516613a commit badc2b3

10 files changed

Lines changed: 731 additions & 12 deletions

File tree

rocketpool/node/node.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,18 @@ func run(c *cli.Command) error {
145145
errorLog := log.NewColorLogger(ErrorColor)
146146
updateLog := log.NewColorLogger(UpdateColor)
147147

148-
// Create the state manager
149-
m := state.NewNetworkStateManager(rp, cfg.Smartnode.GetStateManagerContracts(), bc, &updateLog)
148+
// Create the state provider. In live mode this is a NetworkStateManager
149+
// backed by the real EC/BC; in --network-state mode it is a
150+
// StaticNetworkStateProvider that serves from the pre-loaded snapshot.
151+
var m state.NetworkStateProvider
152+
if services.IsStaticStateMode(c) {
153+
m, err = services.GetNetworkStateProvider(c)
154+
if err != nil {
155+
return fmt.Errorf("error getting network state provider: %w", err)
156+
}
157+
} else {
158+
m = state.NewNetworkStateManager(rp, cfg.Smartnode.GetStateManagerContracts(), bc, &updateLog)
159+
}
150160
stateLocker := collectors.NewStateLocker()
151161

152162
// Initialize tasks
@@ -497,7 +507,7 @@ func removeLegacyFeeRecipientFiles(c *cli.Command) error {
497507
}
498508

499509
// Update the latest network state at each cycle
500-
func updateNetworkState(m *state.NetworkStateManager, log *log.ColorLogger, nodeAddress common.Address) (*state.NetworkState, error) {
510+
func updateNetworkState(m state.NetworkStateProvider, log *log.ColorLogger, nodeAddress common.Address) (*state.NetworkState, error) {
501511
// Get the state of the network
502512
state, err := m.GetHeadStateForNode(nodeAddress)
503513
if err != nil {

rocketpool/rocketpool.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ func main() {
7474
Name: "use-protected-api",
7575
Usage: "Set this to true to use the Flashbots Protect RPC instead of your local Execution Client. Useful to ensure your transactions aren't front-run.",
7676
},
77+
&cli.StringFlag{
78+
Name: "network-state",
79+
Usage: "Absolute path to a saved NetworkState JSON (optionally gzipped) snapshot. When set, the daemon answers requests from the snapshot instead of dialling the execution / consensus clients. Intended for offline inspection and tests.",
80+
},
7781
}
7882

7983
// Register commands

shared/services/bc-manager.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,23 @@ type BeaconClientManager struct {
2626
primaryReady bool
2727
fallbackReady bool
2828
ignoreSyncCheck bool
29+
30+
// static, when non-nil, satisfies every public method of this manager
31+
// directly from the provided client instead of dialling a live beacon
32+
// node. It is set by NewStaticBeaconClientManager and used when the
33+
// daemon is running in --network-state mode.
34+
static beacon.Client
35+
}
36+
37+
// NewStaticBeaconClientManager returns a BeaconClientManager whose public
38+
// methods all delegate to the provided beacon.Client. No network
39+
// connections are established.
40+
func NewStaticBeaconClientManager(static beacon.Client) *BeaconClientManager {
41+
return &BeaconClientManager{
42+
static: static,
43+
primaryReady: true,
44+
fallbackReady: false,
45+
}
2946
}
3047

3148
// This is a signature for a wrapped Beacon client function that only returns an error
@@ -369,6 +386,19 @@ func (m *BeaconClientManager) GetValidatorBalances(indices []string, opts *beaco
369386

370387
func (m *BeaconClientManager) CheckStatus() *api.ClientManagerStatus {
371388

389+
// In static mode we have no real beacon clients to probe; report the
390+
// synthetic "primary is working and synced" status.
391+
if m.static != nil {
392+
return &api.ClientManagerStatus{
393+
FallbackEnabled: false,
394+
PrimaryClientStatus: api.ClientStatus{
395+
IsWorking: true,
396+
IsSynced: true,
397+
SyncProgress: 1,
398+
},
399+
}
400+
}
401+
372402
status := &api.ClientManagerStatus{
373403
FallbackEnabled: m.fallbackBc != nil,
374404
}
@@ -431,6 +461,13 @@ func checkBcStatus(client beacon.Client) api.ClientStatus {
431461
// Attempts to run a function progressively through each client until one succeeds or they all fail.
432462
func (m *BeaconClientManager) runFunction0(function bcFunction0) error {
433463

464+
// Delegate directly to the static backend when the manager is running in
465+
// --network-state mode; there are no primary/fallback clients to route
466+
// through.
467+
if m.static != nil {
468+
return function(m.static)
469+
}
470+
434471
// Check if we can use the primary
435472
if m.primaryReady {
436473
// Try to run the function on the primary
@@ -473,6 +510,10 @@ func (m *BeaconClientManager) runFunction0(function bcFunction0) error {
473510
// Attempts to run a function progressively through each client until one succeeds or they all fail.
474511
func (m *BeaconClientManager) runFunction1(function bcFunction1) (interface{}, error) {
475512

513+
if m.static != nil {
514+
return function(m.static)
515+
}
516+
476517
// Check if we can use the primary
477518
if m.primaryReady {
478519
// Try to run the function on the primary
@@ -515,6 +556,10 @@ func (m *BeaconClientManager) runFunction1(function bcFunction1) (interface{}, e
515556
// Attempts to run a function progressively through each client until one succeeds or they all fail.
516557
func (m *BeaconClientManager) runFunction2(function bcFunction2) (interface{}, interface{}, error) {
517558

559+
if m.static != nil {
560+
return function(m.static)
561+
}
562+
518563
// Check if we can use the primary
519564
if m.primaryReady {
520565
// Try to run the function on the primary

shared/services/ec-manager.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/ethereum/go-ethereum/core/types"
1414
"github.com/ethereum/go-ethereum/ethclient"
1515
"github.com/fatih/color"
16+
"github.com/rocket-pool/smartnode/bindings/rocketpool"
1617
"github.com/rocket-pool/smartnode/shared/services/config"
1718
"github.com/rocket-pool/smartnode/shared/types/api"
1819
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config"
@@ -30,6 +31,23 @@ type ExecutionClientManager struct {
3031
primaryReady bool
3132
fallbackReady bool
3233
ignoreSyncCheck bool
34+
35+
// static, when non-nil, satisfies every public method of this manager
36+
// directly from the provided client instead of dialling a live EC.
37+
// It is set by NewStaticExecutionClientManager and used when the daemon
38+
// is running in --network-state mode.
39+
static rocketpool.ExecutionClient
40+
}
41+
42+
// NewStaticExecutionClientManager returns an ExecutionClientManager whose
43+
// public methods all delegate to the provided ExecutionClient. No network
44+
// connections are established.
45+
func NewStaticExecutionClientManager(static rocketpool.ExecutionClient) *ExecutionClientManager {
46+
return &ExecutionClientManager{
47+
static: static,
48+
primaryReady: true,
49+
fallbackReady: false,
50+
}
3351
}
3452

3553
// This is a signature for a wrapped ethclient.Client function
@@ -100,6 +118,9 @@ func NewExecutionClientManager(cfg *config.RocketPoolConfig) (*ExecutionClientMa
100118
// CodeAt returns the code of the given account. This is needed to differentiate
101119
// between contract internal errors and the local chain being out of sync.
102120
func (p *ExecutionClientManager) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
121+
if p.static != nil {
122+
return p.static.CodeAt(ctx, contract, blockNumber)
123+
}
103124
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
104125
return client.CodeAt(ctx, contract, blockNumber)
105126
})
@@ -112,6 +133,9 @@ func (p *ExecutionClientManager) CodeAt(ctx context.Context, contract common.Add
112133
// CallContract executes an Ethereum contract call with the specified data as the
113134
// input.
114135
func (p *ExecutionClientManager) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
136+
if p.static != nil {
137+
return p.static.CallContract(ctx, call, blockNumber)
138+
}
115139
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
116140
return client.CallContract(ctx, call, blockNumber)
117141
})
@@ -127,6 +151,9 @@ func (p *ExecutionClientManager) CallContract(ctx context.Context, call ethereum
127151

128152
// HeaderByHash returns the block header with the given hash.
129153
func (p *ExecutionClientManager) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
154+
if p.static != nil {
155+
return p.static.HeaderByHash(ctx, hash)
156+
}
130157
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
131158
return client.HeaderByHash(ctx, hash)
132159
})
@@ -139,6 +166,9 @@ func (p *ExecutionClientManager) HeaderByHash(ctx context.Context, hash common.H
139166
// HeaderByNumber returns a block header from the current canonical chain. If number is
140167
// nil, the latest known header is returned.
141168
func (p *ExecutionClientManager) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
169+
if p.static != nil {
170+
return p.static.HeaderByNumber(ctx, number)
171+
}
142172
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
143173
return client.HeaderByNumber(ctx, number)
144174
})
@@ -150,6 +180,9 @@ func (p *ExecutionClientManager) HeaderByNumber(ctx context.Context, number *big
150180

151181
// PendingCodeAt returns the code of the given account in the pending state.
152182
func (p *ExecutionClientManager) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
183+
if p.static != nil {
184+
return p.static.PendingCodeAt(ctx, account)
185+
}
153186
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
154187
return client.PendingCodeAt(ctx, account)
155188
})
@@ -161,6 +194,9 @@ func (p *ExecutionClientManager) PendingCodeAt(ctx context.Context, account comm
161194

162195
// PendingNonceAt retrieves the current pending nonce associated with an account.
163196
func (p *ExecutionClientManager) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
197+
if p.static != nil {
198+
return p.static.PendingNonceAt(ctx, account)
199+
}
164200
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
165201
return client.PendingNonceAt(ctx, account)
166202
})
@@ -173,6 +209,9 @@ func (p *ExecutionClientManager) PendingNonceAt(ctx context.Context, account com
173209
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
174210
// execution of a transaction.
175211
func (p *ExecutionClientManager) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
212+
if p.static != nil {
213+
return p.static.SuggestGasPrice(ctx)
214+
}
176215
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
177216
return client.SuggestGasPrice(ctx)
178217
})
@@ -185,6 +224,9 @@ func (p *ExecutionClientManager) SuggestGasPrice(ctx context.Context) (*big.Int,
185224
// SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow
186225
// a timely execution of a transaction.
187226
func (p *ExecutionClientManager) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
227+
if p.static != nil {
228+
return p.static.SuggestGasTipCap(ctx)
229+
}
188230
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
189231
return client.SuggestGasTipCap(ctx)
190232
})
@@ -200,6 +242,9 @@ func (p *ExecutionClientManager) SuggestGasTipCap(ctx context.Context) (*big.Int
200242
// transactions may be added or removed by miners, but it should provide a basis
201243
// for setting a reasonable default.
202244
func (p *ExecutionClientManager) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) {
245+
if p.static != nil {
246+
return p.static.EstimateGas(ctx, call)
247+
}
203248
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
204249
return client.EstimateGas(ctx, call)
205250
})
@@ -211,6 +256,9 @@ func (p *ExecutionClientManager) EstimateGas(ctx context.Context, call ethereum.
211256

212257
// SendTransaction injects the transaction into the pending pool for execution.
213258
func (p *ExecutionClientManager) SendTransaction(ctx context.Context, tx *types.Transaction) error {
259+
if p.static != nil {
260+
return p.static.SendTransaction(ctx, tx)
261+
}
214262
_, err := p.runFunction(func(client *ethClient) (interface{}, error) {
215263
return nil, client.SendTransaction(ctx, tx)
216264
})
@@ -226,6 +274,9 @@ func (p *ExecutionClientManager) SendTransaction(ctx context.Context, tx *types.
226274
//
227275
// TODO(karalabe): Deprecate when the subscription one can return past data too.
228276
func (p *ExecutionClientManager) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
277+
if p.static != nil {
278+
return p.static.FilterLogs(ctx, query)
279+
}
229280
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
230281
return client.FilterLogs(ctx, query)
231282
})
@@ -238,6 +289,9 @@ func (p *ExecutionClientManager) FilterLogs(ctx context.Context, query ethereum.
238289
// SubscribeFilterLogs creates a background log filtering operation, returning
239290
// a subscription immediately, which can be used to stream the found events.
240291
func (p *ExecutionClientManager) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
292+
if p.static != nil {
293+
return p.static.SubscribeFilterLogs(ctx, query, ch)
294+
}
241295
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
242296
return client.SubscribeFilterLogs(ctx, query, ch)
243297
})
@@ -254,6 +308,9 @@ func (p *ExecutionClientManager) SubscribeFilterLogs(ctx context.Context, query
254308
// TransactionReceipt returns the receipt of a transaction by transaction hash.
255309
// Note that the receipt is not available for pending transactions.
256310
func (p *ExecutionClientManager) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
311+
if p.static != nil {
312+
return p.static.TransactionReceipt(ctx, txHash)
313+
}
257314
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
258315
return client.TransactionReceipt(ctx, txHash)
259316
})
@@ -269,6 +326,9 @@ func (p *ExecutionClientManager) TransactionReceipt(ctx context.Context, txHash
269326

270327
// BlockNumber returns the most recent block number
271328
func (p *ExecutionClientManager) BlockNumber(ctx context.Context) (uint64, error) {
329+
if p.static != nil {
330+
return p.static.BlockNumber(ctx)
331+
}
272332
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
273333
return client.BlockNumber(ctx)
274334
})
@@ -281,6 +341,9 @@ func (p *ExecutionClientManager) BlockNumber(ctx context.Context) (uint64, error
281341
// BalanceAt returns the wei balance of the given account.
282342
// The block number can be nil, in which case the balance is taken from the latest known block.
283343
func (p *ExecutionClientManager) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
344+
if p.static != nil {
345+
return p.static.BalanceAt(ctx, account, blockNumber)
346+
}
284347
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
285348
return client.BalanceAt(ctx, account, blockNumber)
286349
})
@@ -292,6 +355,9 @@ func (p *ExecutionClientManager) BalanceAt(ctx context.Context, account common.A
292355

293356
// TransactionByHash returns the transaction with the given hash.
294357
func (p *ExecutionClientManager) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
358+
if p.static != nil {
359+
return p.static.TransactionByHash(ctx, hash)
360+
}
295361
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
296362
tx, isPending, err := client.TransactionByHash(ctx, hash)
297363
result := []interface{}{tx, isPending}
@@ -311,6 +377,9 @@ func (p *ExecutionClientManager) TransactionByHash(ctx context.Context, hash com
311377
// NonceAt returns the account nonce of the given account.
312378
// The block number can be nil, in which case the nonce is taken from the latest known block.
313379
func (p *ExecutionClientManager) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
380+
if p.static != nil {
381+
return p.static.NonceAt(ctx, account, blockNumber)
382+
}
314383
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
315384
return client.NonceAt(ctx, account, blockNumber)
316385
})
@@ -323,6 +392,9 @@ func (p *ExecutionClientManager) NonceAt(ctx context.Context, account common.Add
323392
// SyncProgress retrieves the current progress of the sync algorithm. If there's
324393
// no sync currently running, it returns nil.
325394
func (p *ExecutionClientManager) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
395+
if p.static != nil {
396+
return p.static.SyncProgress(ctx)
397+
}
326398
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
327399
return client.SyncProgress(ctx)
328400
})
@@ -333,6 +405,9 @@ func (p *ExecutionClientManager) SyncProgress(ctx context.Context) (*ethereum.Sy
333405
}
334406

335407
func (p *ExecutionClientManager) LatestBlockTime(ctx context.Context) (time.Time, error) {
408+
if p.static != nil {
409+
return p.static.LatestBlockTime(ctx)
410+
}
336411
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
337412
return client.LatestBlockTime(ctx)
338413
})
@@ -344,6 +419,9 @@ func (p *ExecutionClientManager) LatestBlockTime(ctx context.Context) (time.Time
344419

345420
// BlockNumber returns the most recent block number
346421
func (p *ExecutionClientManager) ChainID(ctx context.Context) (*big.Int, error) {
422+
if p.static != nil {
423+
return p.static.ChainID(ctx)
424+
}
347425
result, err := p.runFunction(func(client *ethClient) (interface{}, error) {
348426
return client.ChainID(ctx)
349427
})
@@ -359,6 +437,19 @@ func (p *ExecutionClientManager) ChainID(ctx context.Context) (*big.Int, error)
359437

360438
func (p *ExecutionClientManager) CheckStatus(cfg *config.RocketPoolConfig) *api.ClientManagerStatus {
361439

440+
// In static mode we have no real clients to probe; report the synthetic
441+
// "primary is working and synced" status.
442+
if p.static != nil {
443+
return &api.ClientManagerStatus{
444+
FallbackEnabled: false,
445+
PrimaryClientStatus: api.ClientStatus{
446+
IsWorking: true,
447+
IsSynced: true,
448+
SyncProgress: 1,
449+
},
450+
}
451+
}
452+
362453
status := &api.ClientManagerStatus{
363454
FallbackEnabled: p.fallbackEc != nil,
364455
}

0 commit comments

Comments
 (0)