Skip to content

Commit e5d8587

Browse files
authored
Merge pull request #277 from OffchainLabs/better-recreate-state-for-rpc
use StateAtBlock and reference states when recreating missing state
2 parents a104baf + 088149d commit e5d8587

File tree

8 files changed

+158
-48
lines changed

8 files changed

+158
-48
lines changed

arbitrum/apibackend.go

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"github.com/ethereum/go-ethereum/eth"
1515
"github.com/ethereum/go-ethereum/eth/tracers"
1616
"github.com/ethereum/go-ethereum/log"
17+
"github.com/ethereum/go-ethereum/metrics"
18+
"github.com/ethereum/go-ethereum/trie"
1719

1820
"github.com/ethereum/go-ethereum/accounts"
1921
"github.com/ethereum/go-ethereum/common"
@@ -22,6 +24,7 @@ import (
2224
"github.com/ethereum/go-ethereum/core/bloombits"
2325
"github.com/ethereum/go-ethereum/core/rawdb"
2426
"github.com/ethereum/go-ethereum/core/state"
27+
"github.com/ethereum/go-ethereum/core/state/snapshot"
2528
"github.com/ethereum/go-ethereum/core/types"
2629
"github.com/ethereum/go-ethereum/core/vm"
2730
"github.com/ethereum/go-ethereum/eth/filters"
@@ -32,6 +35,13 @@ import (
3235
"github.com/ethereum/go-ethereum/rpc"
3336
)
3437

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+
3545
type APIBackend struct {
3646
b *Backend
3747

@@ -444,21 +454,75 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types
444454
return nil, header, types.ErrUseFallback
445455
}
446456
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+
}
449473
}
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)
451491
if err != nil {
452492
return nil, nil, err
453493
}
494+
// make sure that we haven't found the state in diskdb
454495
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)
458516
if err != nil {
459-
return nil, nil, err
517+
return nil, nil, fmt.Errorf("failed to recreate state: %w", err)
460518
}
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
462526
}
463527

464528
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
468532

469533
func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
470534
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+
}
471541
return a.stateAndHeaderFromHeader(ctx, header, err)
472542
}
473543

@@ -476,7 +546,7 @@ func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexe
476546
return nil, nil, types.ErrUseFallback
477547
}
478548
// 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)
480550
}
481551

482552
func (a *APIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {

arbitrum/recordingdb.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,12 @@ func (r *RecordingDatabase) PreimagesFromRecording(chainContextIf core.ChainCont
309309
}
310310

311311
func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) {
312-
state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, -1)
312+
stateFor := func(header *types.Header) (*state.StateDB, StateReleaseFunc, error) {
313+
state, err := r.StateFor(header)
314+
// we don't use the release functor pattern here yet
315+
return state, NoopStateRelease, err
316+
}
317+
state, currentHeader, _, err := FindLastAvailableState(ctx, r.bc, stateFor, header, logFunc, -1)
313318
if err != nil {
314319
return nil, err
315320
}

arbitrum/recreatestate.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,64 @@ import (
99
"github.com/ethereum/go-ethereum/core/state"
1010
"github.com/ethereum/go-ethereum/core/types"
1111
"github.com/ethereum/go-ethereum/core/vm"
12+
"github.com/ethereum/go-ethereum/eth/tracers"
1213
"github.com/pkg/errors"
1314
)
1415

1516
var (
1617
ErrDepthLimitExceeded = errors.New("state recreation l2 gas depth limit exceeded")
1718
)
1819

20+
type StateReleaseFunc tracers.StateReleaseFunc
21+
22+
var NoopStateRelease StateReleaseFunc = func() {}
23+
1924
type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool)
20-
type StateForHeaderFunction func(header *types.Header) (*state.StateDB, error)
25+
type StateForHeaderFunction func(header *types.Header) (*state.StateDB, StateReleaseFunc, error)
2126

2227
// finds last available state and header checking it first for targetHeader then looking backwards
2328
// if maxDepthInL2Gas is positive, it constitutes a limit for cumulative l2 gas used of the traversed blocks
2429
// else if maxDepthInL2Gas is -1, the traversal depth is not limited
2530
// otherwise only targetHeader state is checked and no search is performed
26-
func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas int64) (*state.StateDB, *types.Header, error) {
31+
func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas int64) (*state.StateDB, *types.Header, StateReleaseFunc, error) {
2732
genesis := bc.Config().ArbitrumChainParams.GenesisBlockNum
2833
currentHeader := targetHeader
2934
var state *state.StateDB
3035
var err error
3136
var l2GasUsed uint64
37+
release := NoopStateRelease
3238
for ctx.Err() == nil {
3339
lastHeader := currentHeader
34-
state, err = stateFor(currentHeader)
40+
state, release, err = stateFor(currentHeader)
3541
if err == nil {
3642
break
3743
}
3844
if maxDepthInL2Gas > 0 {
3945
receipts := bc.GetReceiptsByHash(currentHeader.Hash())
4046
if receipts == nil {
41-
return nil, lastHeader, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash())
47+
return nil, lastHeader, nil, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash())
4248
}
4349
for _, receipt := range receipts {
4450
l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1
4551
}
4652
if l2GasUsed > uint64(maxDepthInL2Gas) {
47-
return nil, lastHeader, ErrDepthLimitExceeded
53+
return nil, lastHeader, nil, ErrDepthLimitExceeded
4854
}
4955
} else if maxDepthInL2Gas != InfiniteMaxRecreateStateDepth {
50-
return nil, lastHeader, err
56+
return nil, lastHeader, nil, err
5157
}
5258
if logFunc != nil {
5359
logFunc(targetHeader, currentHeader, false)
5460
}
5561
if currentHeader.Number.Uint64() <= genesis {
56-
return nil, lastHeader, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", targetHeader.Number.Uint64(), genesis))
62+
return nil, lastHeader, nil, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", targetHeader.Number.Uint64(), genesis))
5763
}
5864
currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1)
5965
if currentHeader == nil {
60-
return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash())
66+
return nil, lastHeader, nil, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash())
6167
}
6268
}
63-
return state, currentHeader, ctx.Err()
69+
return state, currentHeader, release, ctx.Err()
6470
}
6571

6672
func AdvanceStateByBlock(ctx context.Context, bc *core.BlockChain, state *state.StateDB, targetHeader *types.Header, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*state.StateDB, *types.Block, error) {

core/blockchain.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,8 @@ func (bc *BlockChain) Stop() {
10711071
// - HEAD: So we don't need to reprocess any blocks in the general case
10721072
// - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle
10731073
// - HEAD-127: So we have a hard limit on the number of blocks reexecuted
1074-
if !bc.cacheConfig.TrieDirtyDisabled {
1074+
// It applies for both full node and sparse archive node
1075+
if !bc.cacheConfig.TrieDirtyDisabled || bc.cacheConfig.MaxNumberOfBlocksToSkipStateSaving > 0 || bc.cacheConfig.MaxAmountOfGasToSkipStateSaving > 0 {
10751076
triedb := bc.triedb
10761077

10771078
for _, offset := range []uint64{0, 1, bc.cacheConfig.TriesInMemory - 1, math.MaxUint64} {
@@ -1496,7 +1497,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
14961497
return nil
14971498
}
14981499
// If we're running an archive node, flush
1499-
// If MaxNumberOfBlocksToSkipStateSaving or MaxAmountOfGasToSkipStateSaving is not zero, then flushing of some blocks will be skipped:
1500+
// Sparse archive: if MaxNumberOfBlocksToSkipStateSaving or MaxAmountOfGasToSkipStateSaving is not zero, then flushing of some blocks will be skipped:
15001501
// * at most MaxNumberOfBlocksToSkipStateSaving block state commits will be skipped
15011502
// * sum of gas used in skipped blocks will be at most MaxAmountOfGasToSkipStateSaving
15021503
archiveNode := bc.cacheConfig.TrieDirtyDisabled
@@ -1526,7 +1527,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
15261527
// we are skipping saving the trie to diskdb, so we need to keep the trie in memory and garbage collect it later
15271528
}
15281529

1529-
// Full node or archive node that's not keeping all states, do proper garbage collection
1530+
// Full node or sparse archive node that's not keeping all states, do proper garbage collection
15301531
bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
15311532
bc.triegc.Push(trieGcEntry{root, block.Header().Time}, -int64(block.NumberU64()))
15321533

core/state/statedb.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ type revision struct {
6262
// must be created with new root and updated database for accessing post-
6363
// commit states.
6464
type StateDB struct {
65-
// Arbitrum: track the total balance change across all accounts
66-
unexpectedBalanceDelta *big.Int
65+
arbExtraData *ArbitrumExtraData // must be a pointer - can't be a part of StateDB allocation, otherwise its finalizer might not get called
6766

6867
db Database
6968
prefetcher *triePrefetcher
@@ -155,7 +154,9 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
155154
return nil, err
156155
}
157156
sdb := &StateDB{
158-
unexpectedBalanceDelta: new(big.Int),
157+
arbExtraData: &ArbitrumExtraData{
158+
unexpectedBalanceDelta: new(big.Int),
159+
},
159160

160161
db: db,
161162
trie: tr,
@@ -395,7 +396,7 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool {
395396
func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
396397
stateObject := s.GetOrNewStateObject(addr)
397398
if stateObject != nil {
398-
s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount)
399+
s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount)
399400
stateObject.AddBalance(amount)
400401
}
401402
}
@@ -404,7 +405,7 @@ func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
404405
func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
405406
stateObject := s.GetOrNewStateObject(addr)
406407
if stateObject != nil {
407-
s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, amount)
408+
s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, amount)
408409
stateObject.SubBalance(amount)
409410
}
410411
}
@@ -416,8 +417,8 @@ func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
416417
amount = big.NewInt(0)
417418
}
418419
prevBalance := stateObject.Balance()
419-
s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount)
420-
s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, prevBalance)
420+
s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount)
421+
s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, prevBalance)
421422
stateObject.SetBalance(amount)
422423
}
423424
}
@@ -426,7 +427,7 @@ func (s *StateDB) ExpectBalanceBurn(amount *big.Int) {
426427
if amount.Sign() < 0 {
427428
panic(fmt.Sprintf("ExpectBalanceBurn called with negative amount %v", amount))
428429
}
429-
s.unexpectedBalanceDelta.Add(s.unexpectedBalanceDelta, amount)
430+
s.arbExtraData.unexpectedBalanceDelta.Add(s.arbExtraData.unexpectedBalanceDelta, amount)
430431
}
431432

432433
func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
@@ -488,7 +489,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) {
488489
})
489490

490491
stateObject.markSelfdestructed()
491-
s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, stateObject.data.Balance)
492+
s.arbExtraData.unexpectedBalanceDelta.Sub(s.arbExtraData.unexpectedBalanceDelta, stateObject.data.Balance)
492493

493494
stateObject.data.Balance = new(big.Int)
494495
}
@@ -726,7 +727,9 @@ func (s *StateDB) CreateAccount(addr common.Address) {
726727
func (s *StateDB) Copy() *StateDB {
727728
// Copy all the basic fields, initialize the memory ones
728729
state := &StateDB{
729-
unexpectedBalanceDelta: new(big.Int).Set(s.unexpectedBalanceDelta),
730+
arbExtraData: &ArbitrumExtraData{
731+
unexpectedBalanceDelta: new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta),
732+
},
730733

731734
db: s.db,
732735
trie: s.db.CopyTrie(s.trie),
@@ -831,7 +834,7 @@ func (s *StateDB) Copy() *StateDB {
831834
func (s *StateDB) Snapshot() int {
832835
id := s.nextRevisionId
833836
s.nextRevisionId++
834-
s.validRevisions = append(s.validRevisions, revision{id, s.journal.length(), new(big.Int).Set(s.unexpectedBalanceDelta)})
837+
s.validRevisions = append(s.validRevisions, revision{id, s.journal.length(), new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta)})
835838
return id
836839
}
837840

@@ -846,7 +849,7 @@ func (s *StateDB) RevertToSnapshot(revid int) {
846849
}
847850
revision := s.validRevisions[idx]
848851
snapshot := revision.journalIndex
849-
s.unexpectedBalanceDelta = new(big.Int).Set(revision.unexpectedBalanceDelta)
852+
s.arbExtraData.unexpectedBalanceDelta = new(big.Int).Set(revision.unexpectedBalanceDelta)
850853

851854
// Replay the journal to undo changes and remove invalidated snapshots
852855
s.journal.revert(s, snapshot)
@@ -1322,7 +1325,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
13221325
s.snap = nil
13231326
}
13241327

1325-
s.unexpectedBalanceDelta.Set(new(big.Int))
1328+
s.arbExtraData.unexpectedBalanceDelta.Set(new(big.Int))
13261329

13271330
if root == (common.Hash{}) {
13281331
root = types.EmptyRootHash

core/state/statedb_arbitrum.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,30 @@ package state
1919

2020
import (
2121
"math/big"
22+
"runtime"
2223

2324
"github.com/ethereum/go-ethereum/common"
2425
"github.com/ethereum/go-ethereum/core/types"
2526
"github.com/ethereum/go-ethereum/rlp"
2627
"github.com/ethereum/go-ethereum/trie"
2728
)
2829

30+
type ArbitrumExtraData struct {
31+
// track the total balance change across all accounts
32+
unexpectedBalanceDelta *big.Int
33+
}
34+
35+
func (s *StateDB) SetArbFinalizer(f func(*ArbitrumExtraData)) {
36+
runtime.SetFinalizer(s.arbExtraData, f)
37+
}
38+
2939
func (s *StateDB) GetCurrentTxLogs() []*types.Log {
3040
return s.logs[s.thash]
3141
}
3242

3343
// GetUnexpectedBalanceDelta returns the total unexpected change in balances since the last commit to the database.
3444
func (s *StateDB) GetUnexpectedBalanceDelta() *big.Int {
35-
return new(big.Int).Set(s.unexpectedBalanceDelta)
45+
return new(big.Int).Set(s.arbExtraData.unexpectedBalanceDelta)
3646
}
3747

3848
func (s *StateDB) GetSelfDestructs() []common.Address {

eth/api_backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ func (b *EthAPIBackend) StartMining() error {
414414
}
415415

416416
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
417-
return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
417+
return b.eth.stateAtBlock(ctx, block, reexec, base, nil, readOnly, preferDisk)
418418
}
419419

420420
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {

0 commit comments

Comments
 (0)