@@ -14,6 +14,8 @@ import (
14
14
"github.com/ethereum/go-ethereum/eth"
15
15
"github.com/ethereum/go-ethereum/eth/tracers"
16
16
"github.com/ethereum/go-ethereum/log"
17
+ "github.com/ethereum/go-ethereum/metrics"
18
+ "github.com/ethereum/go-ethereum/trie"
17
19
18
20
"github.com/ethereum/go-ethereum/accounts"
19
21
"github.com/ethereum/go-ethereum/common"
@@ -22,6 +24,7 @@ import (
22
24
"github.com/ethereum/go-ethereum/core/bloombits"
23
25
"github.com/ethereum/go-ethereum/core/rawdb"
24
26
"github.com/ethereum/go-ethereum/core/state"
27
+ "github.com/ethereum/go-ethereum/core/state/snapshot"
25
28
"github.com/ethereum/go-ethereum/core/types"
26
29
"github.com/ethereum/go-ethereum/core/vm"
27
30
"github.com/ethereum/go-ethereum/eth/filters"
@@ -32,6 +35,13 @@ import (
32
35
"github.com/ethereum/go-ethereum/rpc"
33
36
)
34
37
38
+ var (
39
+ liveStatesReferencedCounter = metrics .NewRegisteredCounter ("arb/apibackend/states/live/referenced" , nil )
40
+ liveStatesDereferencedCounter = metrics .NewRegisteredCounter ("arb/apibackend/states/live/dereferenced" , nil )
41
+ recreatedStatesReferencedCounter = metrics .NewRegisteredCounter ("arb/apibackend/states/recreated/referenced" , nil )
42
+ recreatedStatesDereferencedCounter = metrics .NewRegisteredCounter ("arb/apibackend/states/recreated/dereferenced" , nil )
43
+ )
44
+
35
45
type APIBackend struct {
36
46
b * Backend
37
47
@@ -444,21 +454,75 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types
444
454
return nil , header , types .ErrUseFallback
445
455
}
446
456
bc := a .BlockChain ()
447
- stateFor := func (header * types.Header ) (* state.StateDB , error ) {
448
- return bc .StateAt (header .Root )
457
+ stateFor := func (db state.Database , snapshots * snapshot.Tree ) func (header * types.Header ) (* state.StateDB , StateReleaseFunc , error ) {
458
+ return func (header * types.Header ) (* state.StateDB , StateReleaseFunc , error ) {
459
+ if header .Root != (common.Hash {}) {
460
+ // Try referencing the root, if it isn't in dirties cache then Reference will have no effect
461
+ db .TrieDB ().Reference (header .Root , common.Hash {})
462
+ }
463
+ statedb , err := state .New (header .Root , db , snapshots )
464
+ if err != nil {
465
+ return nil , nil , err
466
+ }
467
+ if header .Root != (common.Hash {}) {
468
+ headerRoot := header .Root
469
+ return statedb , func () { db .TrieDB ().Dereference (headerRoot ) }, nil
470
+ }
471
+ return statedb , NoopStateRelease , nil
472
+ }
449
473
}
450
- state , lastHeader , err := FindLastAvailableState (ctx , bc , stateFor , header , nil , a .b .config .MaxRecreateStateDepth )
474
+ liveState , liveStateRelease , err := stateFor (bc .StateCache (), bc .Snapshots ())(header )
475
+ if err == nil {
476
+ liveStatesReferencedCounter .Inc (1 )
477
+ liveState .SetArbFinalizer (func (* state.ArbitrumExtraData ) {
478
+ liveStateRelease ()
479
+ liveStatesDereferencedCounter .Inc (1 )
480
+ })
481
+ return liveState , header , nil
482
+ }
483
+ // else err != nil => we don't need to call liveStateRelease
484
+
485
+ // Create an ephemeral trie.Database for isolating the live one
486
+ // note: triedb cleans cache is disabled in trie.HashDefaults
487
+ // note: only states committed to diskdb can be found as we're creating new triedb
488
+ // note: snapshots are not used here
489
+ ephemeral := state .NewDatabaseWithConfig (a .ChainDb (), trie .HashDefaults )
490
+ lastState , lastHeader , lastStateRelease , err := FindLastAvailableState (ctx , bc , stateFor (ephemeral , nil ), header , nil , a .b .config .MaxRecreateStateDepth )
451
491
if err != nil {
452
492
return nil , nil , err
453
493
}
494
+ // make sure that we haven't found the state in diskdb
454
495
if lastHeader == header {
455
- return state , header , nil
456
- }
457
- state , err = AdvanceStateUpToBlock (ctx , bc , state , header , lastHeader , nil )
496
+ liveStatesReferencedCounter .Inc (1 )
497
+ lastState .SetArbFinalizer (func (* state.ArbitrumExtraData ) {
498
+ lastStateRelease ()
499
+ liveStatesDereferencedCounter .Inc (1 )
500
+ })
501
+ return lastState , header , nil
502
+ }
503
+ defer lastStateRelease ()
504
+ targetBlock := bc .GetBlockByNumber (header .Number .Uint64 ())
505
+ if targetBlock == nil {
506
+ return nil , nil , errors .New ("target block not found" )
507
+ }
508
+ lastBlock := bc .GetBlockByNumber (lastHeader .Number .Uint64 ())
509
+ if lastBlock == nil {
510
+ return nil , nil , errors .New ("last block not found" )
511
+ }
512
+ reexec := uint64 (0 )
513
+ checkLive := false
514
+ preferDisk := false // preferDisk is ignored in this case
515
+ statedb , release , err := eth .NewArbEthereum (a .b .arb .BlockChain (), a .ChainDb ()).StateAtBlock (ctx , targetBlock , reexec , lastState , lastBlock , checkLive , preferDisk )
458
516
if err != nil {
459
- return nil , nil , err
517
+ return nil , nil , fmt . Errorf ( "failed to recreate state: %w" , err )
460
518
}
461
- return state , header , err
519
+ // we are setting finalizer instead of returning a StateReleaseFunc to avoid changing ethapi.Backend interface to minimize diff to upstream
520
+ recreatedStatesReferencedCounter .Inc (1 )
521
+ statedb .SetArbFinalizer (func (* state.ArbitrumExtraData ) {
522
+ release ()
523
+ recreatedStatesDereferencedCounter .Inc (1 )
524
+ })
525
+ return statedb , header , err
462
526
}
463
527
464
528
func (a * APIBackend ) StateAndHeaderByNumber (ctx context.Context , number rpc.BlockNumber ) (* state.StateDB , * types.Header , error ) {
@@ -468,6 +532,12 @@ func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.Bloc
468
532
469
533
func (a * APIBackend ) StateAndHeaderByNumberOrHash (ctx context.Context , blockNrOrHash rpc.BlockNumberOrHash ) (* state.StateDB , * types.Header , error ) {
470
534
header , err := a .HeaderByNumberOrHash (ctx , blockNrOrHash )
535
+ hash , ishash := blockNrOrHash .Hash ()
536
+ bc := a .BlockChain ()
537
+ // check if we are not trying to get recent state that is not yet triedb referenced or committed in Blockchain.writeBlockWithState
538
+ if ishash && header .Number .Cmp (bc .CurrentBlock ().Number ) > 0 && bc .GetCanonicalHash (header .Number .Uint64 ()) != hash {
539
+ return nil , nil , errors .New ("requested block ahead of current block and the hash is not currently canonical" )
540
+ }
471
541
return a .stateAndHeaderFromHeader (ctx , header , err )
472
542
}
473
543
@@ -476,7 +546,7 @@ func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexe
476
546
return nil , nil , types .ErrUseFallback
477
547
}
478
548
// DEV: This assumes that `StateAtBlock` only accesses the blockchain and chainDb fields
479
- return eth .NewArbEthereum (a .b .arb .BlockChain (), a .ChainDb ()).StateAtBlock (ctx , block , reexec , base , checkLive , preferDisk )
549
+ return eth .NewArbEthereum (a .b .arb .BlockChain (), a .ChainDb ()).StateAtBlock (ctx , block , reexec , base , nil , checkLive , preferDisk )
480
550
}
481
551
482
552
func (a * APIBackend ) StateAtTransaction (ctx context.Context , block * types.Block , txIndex int , reexec uint64 ) (* core.Message , vm.BlockContext , * state.StateDB , tracers.StateReleaseFunc , error ) {
0 commit comments