From 7c29647dd79fc17474c3f4a75f7f1925a25609bb Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 16 Jan 2023 21:24:32 +0100 Subject: [PATCH 1/9] add max recreate state depth config --- arbitrum/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arbitrum/config.go b/arbitrum/config.go index 61d456f84e..5718508527 100644 --- a/arbitrum/config.go +++ b/arbitrum/config.go @@ -34,6 +34,7 @@ type Config struct { ClassicRedirect string `koanf:"classic-redirect"` ClassicRedirectTimeout time.Duration `koanf:"classic-redirect-timeout"` + MaxRecreateStateDepth uint64 `koanf:"max-recreate-state-depth"` } type ArbDebugConfig struct { @@ -51,7 +52,7 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".classic-redirect-timeout", DefaultConfig.ClassicRedirectTimeout, "timeout for forwarded classic requests, where 0 = no timeout") f.Int(prefix+".filter-log-cache-size", DefaultConfig.FilterLogCacheSize, "log filter system maximum number of cached blocks") f.Duration(prefix+".filter-timeout", DefaultConfig.FilterTimeout, "log filter system maximum time filters stay active") - + f.Uint64(prefix+".max-recreate-state-depth", DefaultConfig.MaxRecreateStateDepth, "maximum depth for recreating state, measured in l2 gas (0=infinite)") arbDebug := DefaultConfig.ArbDebug f.Uint64(prefix+".arbdebug.block-range-bound", arbDebug.BlockRangeBound, "bounds the number of blocks arbdebug calls may return") f.Uint64(prefix+".arbdebug.timeout-queue-bound", arbDebug.TimeoutQueueBound, "bounds the length of timeout queues arbdebug calls may return") @@ -67,6 +68,7 @@ var DefaultConfig = Config{ FilterTimeout: 5 * time.Minute, FeeHistoryMaxBlockCount: 1024, ClassicRedirect: "", + MaxRecreateStateDepth: 0, // TODO ArbDebug: ArbDebugConfig{ BlockRangeBound: 256, TimeoutQueueBound: 512, From d8d840aac0a0abdda803abdc7710d3f5f07081dc Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 16 Jan 2023 18:02:31 +0100 Subject: [PATCH 2/9] add recreate state helper functions, use the helpers in RecordingDatabase --- arbitrum/recordingdb.go | 44 ++++------------- arbitrum/recreatestate.go | 100 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 arbitrum/recreatestate.go diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index a6f9818896..2c91fa4e85 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -219,7 +218,7 @@ func (r *RecordingDatabase) addStateVerify(statedb *state.StateDB, expected comm return nil } -type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) +//type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) func (r *RecordingDatabase) PrepareRecording(ctx context.Context, lastBlockHeader *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, core.ChainContext, *RecordingKV, error) { _, err := r.GetOrRecreateState(ctx, lastBlockHeader, logFunc) @@ -274,27 +273,11 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type if err == nil { return stateDb, nil } - returnedBlockNumber := header.Number.Uint64() - genesis := r.bc.Config().ArbitrumChainParams.GenesisBlockNum - currentHeader := header - var lastRoot common.Hash - for ctx.Err() == nil { - if logFunc != nil { - logFunc(header, currentHeader, false) - } - if currentHeader.Number.Uint64() <= genesis { - return nil, fmt.Errorf("moved beyond genesis looking for state looking for %d, genesis %d, err %w", returnedBlockNumber, genesis, err) - } - currentHeader = r.bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) - if currentHeader == nil { - return nil, fmt.Errorf("chain doesn't contain parent of block %d hash %v", currentHeader.Number, currentHeader.Hash()) - } - stateDb, err = r.StateFor(currentHeader) - if err == nil { - lastRoot = currentHeader.Root - break - } + stateDb, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, 0) + if err != nil { + return nil, err } + lastRoot := currentHeader.Root defer func() { if (lastRoot != common.Hash{}) { r.dereferenceRoot(lastRoot) @@ -302,22 +285,13 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type }() blockToRecreate := currentHeader.Number.Uint64() + 1 prevHash := currentHeader.Hash() + returnedBlockNumber := header.Number.Uint64() for ctx.Err() == nil { - block := r.bc.GetBlockByNumber(blockToRecreate) - if block == nil { - return nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate) - } - if block.ParentHash() != prevHash { - return nil, fmt.Errorf("reorg detected: number %d expectedPrev: %v foundPrev: %v", blockToRecreate, prevHash, block.ParentHash()) - } - prevHash = block.Hash() - if logFunc != nil { - logFunc(header, block.Header(), true) - } - _, _, _, err := r.bc.Processor().Process(block, stateDb, vm.Config{}) + block, err := RecreateBlock(ctx, r.bc, header, stateDb, blockToRecreate, prevHash, logFunc) if err != nil { - return nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err) + return nil, err } + prevHash = block.Hash() err = r.addStateVerify(stateDb, block.Root()) if err != nil { return nil, fmt.Errorf("failed commiting state for block %d : %w", blockToRecreate, err) diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go new file mode 100644 index 0000000000..f8dc42e6ef --- /dev/null +++ b/arbitrum/recreatestate.go @@ -0,0 +1,100 @@ +package arbitrum + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/pkg/errors" +) + +var ( + ErrInvalidBlockHash = errors.New("invalid block hash") + ErrBlockNotFound = errors.New("block not found") + ErrDepthLimitExceeded = errors.New("state recreation l2 gas depth limit exceeded") +) + +type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) +type StateForHeaderFunction func(header *types.Header) (*state.StateDB, error) + +func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, header *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas uint64) (*state.StateDB, *types.Header, error) { + genesis := bc.Config().ArbitrumChainParams.GenesisBlockNum + currentHeader := header + var stateDb *state.StateDB + var err error + var previousHeader *types.Header + var l2GasUsed uint64 + for ctx.Err() == nil { + if maxDepthInL2Gas > 0 { + receipts := bc.GetReceiptsByHash(currentHeader.Hash()) + if receipts == nil { + return nil, previousHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("failed to get receipts for hash %v", currentHeader.Hash())) + } + for _, receipt := range receipts { + l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1 + } + if l2GasUsed > maxDepthInL2Gas { + return nil, previousHeader, ErrDepthLimitExceeded + } + } + if logFunc != nil { + logFunc(header, currentHeader, false) + } + if currentHeader.Number.Uint64() <= genesis { + return nil, previousHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d, err %w", header.Number.Uint64(), genesis, err)) + } + currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) + if currentHeader == nil { + return nil, previousHeader, errors.Wrap(ErrBlockNotFound, fmt.Sprintf("chain doesn't contain parent of block %d hash %v", currentHeader.Number, currentHeader.Hash())) + } + stateDb, err = stateFor(currentHeader) + if err == nil { + break + } + previousHeader = currentHeader + } + return stateDb, currentHeader, nil +} + +func RecreateBlock(ctx context.Context, bc *core.BlockChain, header *types.Header, stateDb *state.StateDB, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*types.Block, error) { + block := bc.GetBlockByNumber(blockToRecreate) + if block == nil { + return nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate) + } + if block.ParentHash() != prevBlockHash { + return nil, fmt.Errorf("reorg detected: number %d expectedPrev: %v foundPrev: %v", blockToRecreate, prevBlockHash, block.ParentHash()) + } + if logFunc != nil { + logFunc(header, block.Header(), true) + } + _, _, _, err := bc.Processor().Process(block, stateDb, vm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err) + } + return block, nil +} + +func RecreateBlocks(ctx context.Context, bc *core.BlockChain, header *types.Header, lastAvailableHeader *types.Header, stateDb *state.StateDB, logFunc StateBuildingLogFunction) (*state.StateDB, error) { + returnedBlockNumber := header.Number.Uint64() + blockToRecreate := lastAvailableHeader.Number.Uint64() + 1 + prevHash := lastAvailableHeader.Hash() + for ctx.Err() == nil { + block, err := RecreateBlock(ctx, bc, header, stateDb, blockToRecreate, prevHash, logFunc) + if err != nil { + return nil, err + } + prevHash = block.Hash() + if blockToRecreate >= returnedBlockNumber { + if block.Hash() != header.Hash() { + return nil, fmt.Errorf("blockHash doesn't match when recreating number: %d expected: %v got: %v", blockToRecreate, header.Hash(), block.Hash()) + } + return stateDb, nil + } + blockToRecreate++ + } + return nil, ctx.Err() +} From 25142b70e5fde262a7ae08df339b3a29cb7246e8 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 16 Jan 2023 21:31:20 +0100 Subject: [PATCH 3/9] recreate state if needed in arbitrum.APIBackend stateAndHeaderFromHeader --- arbitrum/apibackend.go | 27 ++++++++++++++++++++++----- arbitrum/recordingdb.go | 2 -- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 6cd971009b..2e5581b0d8 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -397,7 +397,7 @@ func (a *APIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc. return nil, errors.New("invalid arguments; neither block nor hash specified") } -func (a *APIBackend) stateAndHeaderFromHeader(header *types.Header, err error) (*state.StateDB, *types.Header, error) { +func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types.Header, err error) (*state.StateDB, *types.Header, error) { if err != nil { return nil, header, err } @@ -407,16 +407,33 @@ func (a *APIBackend) stateAndHeaderFromHeader(header *types.Header, err error) ( if !a.blockChain().Config().IsArbitrumNitro(header.Number) { return nil, header, types.ErrUseFallback } - state, err := a.blockChain().StateAt(header.Root) - return state, header, err + bc := a.blockChain() + stateDB, err := bc.StateAt(header.Root) + if err == nil { + return stateDB, header, nil + } + stateFor := func(header *types.Header) (*state.StateDB, error) { + return bc.StateAt(header.Root) + } + stateDB, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth) + if err != nil { + return nil, nil, err + } + stateDB, err = RecreateBlocks(ctx, bc, header, lastHeader, stateDB, nil) + if err != nil { + return nil, nil, err + } + return stateDB, header, err } func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { - return a.stateAndHeaderFromHeader(a.HeaderByNumber(ctx, number)) + header, err := a.HeaderByNumber(ctx, number) + return a.stateAndHeaderFromHeader(ctx, header, err) } func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { - return a.stateAndHeaderFromHeader(a.HeaderByNumberOrHash(ctx, blockNrOrHash)) + header, err := a.HeaderByNumberOrHash(ctx, blockNrOrHash) + return a.stateAndHeaderFromHeader(ctx, header, err) } func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 2c91fa4e85..49f9e466e9 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -218,8 +218,6 @@ func (r *RecordingDatabase) addStateVerify(statedb *state.StateDB, expected comm return nil } -//type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) - func (r *RecordingDatabase) PrepareRecording(ctx context.Context, lastBlockHeader *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, core.ChainContext, *RecordingKV, error) { _, err := r.GetOrRecreateState(ctx, lastBlockHeader, logFunc) if err != nil { From 6447cde42c7ccf5fc823a88bc6ba20696e4516fd Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 18 Jan 2023 18:28:37 +0100 Subject: [PATCH 4/9] fix returned error --- arbitrum/recreatestate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index f8dc42e6ef..d9fa38a1cb 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -45,7 +45,7 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S logFunc(header, currentHeader, false) } if currentHeader.Number.Uint64() <= genesis { - return nil, previousHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d, err %w", header.Number.Uint64(), genesis, err)) + return nil, previousHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", header.Number.Uint64(), genesis)) } currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) if currentHeader == nil { From 642b242161761e04cb530b4c7e961865c5628df2 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 24 Jan 2023 18:39:32 +0100 Subject: [PATCH 5/9] refactor recreate state errors --- arbitrum/recreatestate.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index cd919c0ce4..0fea7ae94b 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -13,8 +13,6 @@ import ( ) var ( - ErrInvalidBlockHash = errors.New("invalid block hash") - ErrBlockNotFound = errors.New("block not found") ErrDepthLimitExceeded = errors.New("state recreation l2 gas depth limit exceeded") ) @@ -32,7 +30,7 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S if maxDepthInL2Gas > 0 { receipts := bc.GetReceiptsByHash(currentHeader.Hash()) if receipts == nil { - return nil, lastHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("failed to get receipts for hash %v", currentHeader.Hash())) + return nil, lastHeader, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash()) } for _, receipt := range receipts { l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1 @@ -45,11 +43,11 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S logFunc(header, currentHeader, false) } if currentHeader.Number.Uint64() <= genesis { - return nil, lastHeader, errors.Wrap(ErrInvalidBlockHash, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", header.Number.Uint64(), genesis)) + return nil, lastHeader, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", header.Number.Uint64(), genesis)) } currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) if currentHeader == nil { - return nil, lastHeader, errors.Wrap(ErrBlockNotFound, fmt.Sprintf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash())) + return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash()) } stateDb, err = stateFor(currentHeader) if err == nil { From 42327e748d1f50d55895cd01cb05917dc6c4d5d3 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 15 Mar 2023 00:50:44 +0000 Subject: [PATCH 6/9] check if current state is available before going back in FindLastAvailableState --- arbitrum/apibackend.go | 7 +++---- arbitrum/recordingdb.go | 7 +++---- arbitrum/recreatestate.go | 10 +++++----- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 2e5581b0d8..7d6701bc3f 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -408,10 +408,6 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types return nil, header, types.ErrUseFallback } bc := a.blockChain() - stateDB, err := bc.StateAt(header.Root) - if err == nil { - return stateDB, header, nil - } stateFor := func(header *types.Header) (*state.StateDB, error) { return bc.StateAt(header.Root) } @@ -419,6 +415,9 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types if err != nil { return nil, nil, err } + if lastHeader == header { + return stateDB, header, nil + } stateDB, err = RecreateBlocks(ctx, bc, header, lastHeader, stateDB, nil) if err != nil { return nil, nil, err diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 49f9e466e9..dbee385c68 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -267,14 +267,13 @@ func (r *RecordingDatabase) PreimagesFromRecording(chainContextIf core.ChainCont } func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) { - stateDb, err := r.StateFor(header) - if err == nil { - return stateDb, nil - } stateDb, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, 0) if err != nil { return nil, err } + if currentHeader == header { + return stateDb, nil + } lastRoot := currentHeader.Root defer func() { if (lastRoot != common.Hash{}) { diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index 0fea7ae94b..be13ffb9df 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -27,6 +27,10 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S var l2GasUsed uint64 for ctx.Err() == nil { lastHeader := currentHeader + stateDb, err = stateFor(currentHeader) + if err == nil { + break + } if maxDepthInL2Gas > 0 { receipts := bc.GetReceiptsByHash(currentHeader.Hash()) if receipts == nil { @@ -49,12 +53,8 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S if currentHeader == nil { return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash()) } - stateDb, err = stateFor(currentHeader) - if err == nil { - break - } } - return stateDb, currentHeader, nil + return stateDb, currentHeader, ctx.Err() } func RecreateBlock(ctx context.Context, bc *core.BlockChain, header *types.Header, stateDb *state.StateDB, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*types.Block, error) { From 1b98615c4be15fe92ee2907f36f6a3aa8c8d1d96 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 15 Mar 2023 00:58:00 +0000 Subject: [PATCH 7/9] rename RecreateBlock func to AdvanceStateByBlock --- arbitrum/recordingdb.go | 2 +- arbitrum/recreatestate.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index dbee385c68..ee6ec335ff 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -284,7 +284,7 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type prevHash := currentHeader.Hash() returnedBlockNumber := header.Number.Uint64() for ctx.Err() == nil { - block, err := RecreateBlock(ctx, r.bc, header, stateDb, blockToRecreate, prevHash, logFunc) + block, err := AdvanceStateByBlock(ctx, r.bc, header, stateDb, blockToRecreate, prevHash, logFunc) if err != nil { return nil, err } diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index be13ffb9df..97c115be97 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -57,7 +57,7 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S return stateDb, currentHeader, ctx.Err() } -func RecreateBlock(ctx context.Context, bc *core.BlockChain, header *types.Header, stateDb *state.StateDB, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*types.Block, error) { +func AdvanceStateByBlock(ctx context.Context, bc *core.BlockChain, header *types.Header, stateDb *state.StateDB, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*types.Block, error) { block := bc.GetBlockByNumber(blockToRecreate) if block == nil { return nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate) @@ -80,7 +80,7 @@ func RecreateBlocks(ctx context.Context, bc *core.BlockChain, header *types.Head blockToRecreate := lastAvailableHeader.Number.Uint64() + 1 prevHash := lastAvailableHeader.Hash() for ctx.Err() == nil { - block, err := RecreateBlock(ctx, bc, header, stateDb, blockToRecreate, prevHash, logFunc) + block, err := AdvanceStateByBlock(ctx, bc, header, stateDb, blockToRecreate, prevHash, logFunc) if err != nil { return nil, err } From f113a6e354e8b53893168af1beba5ab16d3f3b82 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 15 Mar 2023 17:46:29 +0000 Subject: [PATCH 8/9] further refactor naming in state reacreation helpers --- arbitrum/apibackend.go | 8 ++++---- arbitrum/recordingdb.go | 10 +++++----- arbitrum/recreatestate.go | 40 +++++++++++++++++++-------------------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 7d6701bc3f..75fc6077c3 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -411,18 +411,18 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types stateFor := func(header *types.Header) (*state.StateDB, error) { return bc.StateAt(header.Root) } - stateDB, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth) + state, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth) if err != nil { return nil, nil, err } if lastHeader == header { - return stateDB, header, nil + return state, header, nil } - stateDB, err = RecreateBlocks(ctx, bc, header, lastHeader, stateDB, nil) + state, err = AdvanceStateUpToBlock(ctx, bc, state, header, lastHeader, nil) if err != nil { return nil, nil, err } - return stateDB, header, err + return state, header, err } func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index ee6ec335ff..5ba93b910d 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -267,12 +267,12 @@ func (r *RecordingDatabase) PreimagesFromRecording(chainContextIf core.ChainCont } func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) { - stateDb, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, 0) + state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, 0) if err != nil { return nil, err } if currentHeader == header { - return stateDb, nil + return state, nil } lastRoot := currentHeader.Root defer func() { @@ -284,12 +284,12 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type prevHash := currentHeader.Hash() returnedBlockNumber := header.Number.Uint64() for ctx.Err() == nil { - block, err := AdvanceStateByBlock(ctx, r.bc, header, stateDb, blockToRecreate, prevHash, logFunc) + state, block, err := AdvanceStateByBlock(ctx, r.bc, state, header, blockToRecreate, prevHash, logFunc) if err != nil { return nil, err } prevHash = block.Hash() - err = r.addStateVerify(stateDb, block.Root()) + err = r.addStateVerify(state, block.Root()) if err != nil { return nil, fmt.Errorf("failed commiting state for block %d : %w", blockToRecreate, err) } @@ -301,7 +301,7 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type } // don't dereference this one lastRoot = common.Hash{} - return stateDb, nil + return state, nil } blockToRecreate++ } diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index 97c115be97..5ce4e6b92d 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -19,15 +19,15 @@ var ( type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) type StateForHeaderFunction func(header *types.Header) (*state.StateDB, error) -func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, header *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas uint64) (*state.StateDB, *types.Header, error) { +func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas uint64) (*state.StateDB, *types.Header, error) { genesis := bc.Config().ArbitrumChainParams.GenesisBlockNum - currentHeader := header - var stateDb *state.StateDB + currentHeader := targetHeader + var state *state.StateDB var err error var l2GasUsed uint64 for ctx.Err() == nil { lastHeader := currentHeader - stateDb, err = stateFor(currentHeader) + state, err = stateFor(currentHeader) if err == nil { break } @@ -44,52 +44,52 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S } } if logFunc != nil { - logFunc(header, currentHeader, false) + logFunc(targetHeader, currentHeader, false) } if currentHeader.Number.Uint64() <= genesis { - return nil, lastHeader, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", header.Number.Uint64(), genesis)) + return nil, lastHeader, errors.Wrap(err, fmt.Sprintf("moved beyond genesis looking for state %d, genesis %d", targetHeader.Number.Uint64(), genesis)) } currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1) if currentHeader == nil { return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash()) } } - return stateDb, currentHeader, ctx.Err() + return state, currentHeader, ctx.Err() } -func AdvanceStateByBlock(ctx context.Context, bc *core.BlockChain, header *types.Header, stateDb *state.StateDB, blockToRecreate uint64, prevBlockHash common.Hash, logFunc StateBuildingLogFunction) (*types.Block, error) { +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) { block := bc.GetBlockByNumber(blockToRecreate) if block == nil { - return nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate) + return nil, nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate) } if block.ParentHash() != prevBlockHash { - return nil, fmt.Errorf("reorg detected: number %d expectedPrev: %v foundPrev: %v", blockToRecreate, prevBlockHash, block.ParentHash()) + return nil, nil, fmt.Errorf("reorg detected: number %d expectedPrev: %v foundPrev: %v", blockToRecreate, prevBlockHash, block.ParentHash()) } if logFunc != nil { - logFunc(header, block.Header(), true) + logFunc(targetHeader, block.Header(), true) } - _, _, _, err := bc.Processor().Process(block, stateDb, vm.Config{}) + _, _, _, err := bc.Processor().Process(block, state, vm.Config{}) if err != nil { - return nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err) + return nil, nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err) } - return block, nil + return state, block, nil } -func RecreateBlocks(ctx context.Context, bc *core.BlockChain, header *types.Header, lastAvailableHeader *types.Header, stateDb *state.StateDB, logFunc StateBuildingLogFunction) (*state.StateDB, error) { - returnedBlockNumber := header.Number.Uint64() +func AdvanceStateUpToBlock(ctx context.Context, bc *core.BlockChain, state *state.StateDB, targetHeader *types.Header, lastAvailableHeader *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) { + returnedBlockNumber := targetHeader.Number.Uint64() blockToRecreate := lastAvailableHeader.Number.Uint64() + 1 prevHash := lastAvailableHeader.Hash() for ctx.Err() == nil { - block, err := AdvanceStateByBlock(ctx, bc, header, stateDb, blockToRecreate, prevHash, logFunc) + state, block, err := AdvanceStateByBlock(ctx, bc, state, targetHeader, blockToRecreate, prevHash, logFunc) if err != nil { return nil, err } prevHash = block.Hash() if blockToRecreate >= returnedBlockNumber { - if block.Hash() != header.Hash() { - return nil, fmt.Errorf("blockHash doesn't match when recreating number: %d expected: %v got: %v", blockToRecreate, header.Hash(), block.Hash()) + if block.Hash() != targetHeader.Hash() { + return nil, fmt.Errorf("blockHash doesn't match when recreating number: %d expected: %v got: %v", blockToRecreate, targetHeader.Hash(), block.Hash()) } - return stateDb, nil + return state, nil } blockToRecreate++ } From f260155e26e54e8aa2add60d7c2628b39e2526b9 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 28 Mar 2023 17:51:20 +0000 Subject: [PATCH 9/9] add special negative values of MaxRecreateStateDepth config --- arbitrum/config.go | 13 ++++++++++--- arbitrum/recordingdb.go | 2 +- arbitrum/recreatestate.go | 10 ++++++++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/arbitrum/config.go b/arbitrum/config.go index 5718508527..e97c17c92c 100644 --- a/arbitrum/config.go +++ b/arbitrum/config.go @@ -34,7 +34,7 @@ type Config struct { ClassicRedirect string `koanf:"classic-redirect"` ClassicRedirectTimeout time.Duration `koanf:"classic-redirect-timeout"` - MaxRecreateStateDepth uint64 `koanf:"max-recreate-state-depth"` + MaxRecreateStateDepth int64 `koanf:"max-recreate-state-depth"` } type ArbDebugConfig struct { @@ -52,12 +52,19 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".classic-redirect-timeout", DefaultConfig.ClassicRedirectTimeout, "timeout for forwarded classic requests, where 0 = no timeout") f.Int(prefix+".filter-log-cache-size", DefaultConfig.FilterLogCacheSize, "log filter system maximum number of cached blocks") f.Duration(prefix+".filter-timeout", DefaultConfig.FilterTimeout, "log filter system maximum time filters stay active") - f.Uint64(prefix+".max-recreate-state-depth", DefaultConfig.MaxRecreateStateDepth, "maximum depth for recreating state, measured in l2 gas (0=infinite)") + f.Int64(prefix+".max-recreate-state-depth", DefaultConfig.MaxRecreateStateDepth, "maximum depth for recreating state, measured in l2 gas (0=don't recreate state, -1=infinite, -2=use default value for archive or non-archive node (whichever is configured))") arbDebug := DefaultConfig.ArbDebug f.Uint64(prefix+".arbdebug.block-range-bound", arbDebug.BlockRangeBound, "bounds the number of blocks arbdebug calls may return") f.Uint64(prefix+".arbdebug.timeout-queue-bound", arbDebug.TimeoutQueueBound, "bounds the length of timeout queues arbdebug calls may return") } +const ( + DefaultArchiveNodeMaxRecreateStateDepth = 30 * 1000 * 1000 + DefaultNonArchiveNodeMaxRecreateStateDepth = 0 // don't recreate state + UninitializedMaxRecreateStateDepth = -2 + InfiniteMaxRecreateStateDepth = -1 +) + var DefaultConfig = Config{ RPCGasCap: ethconfig.Defaults.RPCGasCap, // 50,000,000 RPCTxFeeCap: ethconfig.Defaults.RPCTxFeeCap, // 1 ether @@ -68,7 +75,7 @@ var DefaultConfig = Config{ FilterTimeout: 5 * time.Minute, FeeHistoryMaxBlockCount: 1024, ClassicRedirect: "", - MaxRecreateStateDepth: 0, // TODO + MaxRecreateStateDepth: UninitializedMaxRecreateStateDepth, // default value should be set for depending on node type (archive / non-archive) ArbDebug: ArbDebugConfig{ BlockRangeBound: 256, TimeoutQueueBound: 512, diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 5ba93b910d..d740579540 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -267,7 +267,7 @@ func (r *RecordingDatabase) PreimagesFromRecording(chainContextIf core.ChainCont } func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, error) { - state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, 0) + state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, -1) if err != nil { return nil, err } diff --git a/arbitrum/recreatestate.go b/arbitrum/recreatestate.go index 5ce4e6b92d..1b2dbb1a18 100644 --- a/arbitrum/recreatestate.go +++ b/arbitrum/recreatestate.go @@ -19,7 +19,11 @@ var ( type StateBuildingLogFunction func(targetHeader, header *types.Header, hasState bool) type StateForHeaderFunction func(header *types.Header) (*state.StateDB, error) -func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas uint64) (*state.StateDB, *types.Header, error) { +// finds last available state and header checking it first for targetHeader then looking backwards +// if maxDepthInL2Gas is positive, it constitutes a limit for cumulative l2 gas used of the traversed blocks +// else if maxDepthInL2Gas is -1, the traversal depth is not limited +// otherwise only targetHeader state is checked and no search is performed +func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor StateForHeaderFunction, targetHeader *types.Header, logFunc StateBuildingLogFunction, maxDepthInL2Gas int64) (*state.StateDB, *types.Header, error) { genesis := bc.Config().ArbitrumChainParams.GenesisBlockNum currentHeader := targetHeader var state *state.StateDB @@ -39,9 +43,11 @@ func FindLastAvailableState(ctx context.Context, bc *core.BlockChain, stateFor S for _, receipt := range receipts { l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1 } - if l2GasUsed > maxDepthInL2Gas { + if l2GasUsed > uint64(maxDepthInL2Gas) { return nil, lastHeader, ErrDepthLimitExceeded } + } else if maxDepthInL2Gas != InfiniteMaxRecreateStateDepth { + return nil, lastHeader, err } if logFunc != nil { logFunc(targetHeader, currentHeader, false)