Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recreate state for rpcs #191

Merged
merged 16 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions arbitrum/apibackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,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
}
Expand All @@ -425,16 +425,32 @@ 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)
bc := a.blockChain()
stateFor := func(header *types.Header) (*state.StateDB, error) {
return bc.StateAt(header.Root)
}
state, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth)
if err != nil {
return nil, nil, err
}
if lastHeader == header {
return state, header, nil
}
state, err = AdvanceStateUpToBlock(ctx, bc, state, header, lastHeader, nil)
if err != nil {
return nil, nil, err
}
return state, 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, release tracers.StateReleaseFunc, err error) {
Expand Down
11 changes: 10 additions & 1 deletion arbitrum/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Config struct {

ClassicRedirect string `koanf:"classic-redirect"`
ClassicRedirectTimeout time.Duration `koanf:"classic-redirect-timeout"`
MaxRecreateStateDepth int64 `koanf:"max-recreate-state-depth"`
}

type ArbDebugConfig struct {
Expand All @@ -52,12 +53,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.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
Expand All @@ -68,6 +76,7 @@ var DefaultConfig = Config{
FilterTimeout: 5 * time.Minute,
FeeHistoryMaxBlockCount: 1024,
ClassicRedirect: "",
MaxRecreateStateDepth: UninitializedMaxRecreateStateDepth, // default value should be set for depending on node type (archive / non-archive)
ArbDebug: ArbDebugConfig{
BlockRangeBound: 256,
TimeoutQueueBound: 512,
Expand Down
54 changes: 12 additions & 42 deletions arbitrum/recordingdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -254,8 +253,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 {
Expand Down Expand Up @@ -305,56 +302,29 @@ 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
state, currentHeader, err := FindLastAvailableState(ctx, r.bc, r.StateFor, header, logFunc, -1)
if err != nil {
return nil, err
}
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)
}
lastHeader := currentHeader
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 (expected parent hash %v)", lastHeader.Number, lastHeader.Hash(), lastHeader.ParentHash)
}
stateDb, err = r.StateFor(currentHeader)
if err == nil {
lastRoot = currentHeader.Root
break
}
if currentHeader == header {
return state, nil
}
lastRoot := currentHeader.Root
defer func() {
if (lastRoot != common.Hash{}) {
r.dereferenceRoot(lastRoot)
}
}()
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{})
state, block, err := AdvanceStateByBlock(ctx, r.bc, state, header, blockToRecreate, prevHash, logFunc)
if err != nil {
return nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err)
return nil, err
}
err = r.addStateVerify(stateDb, block.Root())
prevHash = block.Hash()
err = r.addStateVerify(state, block.Root())
if err != nil {
return nil, fmt.Errorf("failed committing state for block %d : %w", blockToRecreate, err)
}
Expand All @@ -366,7 +336,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++
}
Expand Down
103 changes: 103 additions & 0 deletions arbitrum/recreatestate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 (
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)

// 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
var err error
var l2GasUsed uint64
for ctx.Err() == nil {
lastHeader := currentHeader
state, err = stateFor(currentHeader)
if err == nil {
break
}
if maxDepthInL2Gas > 0 {
receipts := bc.GetReceiptsByHash(currentHeader.Hash())
if receipts == nil {
return nil, lastHeader, fmt.Errorf("failed to get receipts for hash %v", currentHeader.Hash())
}
for _, receipt := range receipts {
l2GasUsed += receipt.GasUsed - receipt.GasUsedForL1
}
if l2GasUsed > uint64(maxDepthInL2Gas) {
return nil, lastHeader, ErrDepthLimitExceeded
}
} else if maxDepthInL2Gas != InfiniteMaxRecreateStateDepth {
return nil, lastHeader, err
}
if logFunc != nil {
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", targetHeader.Number.Uint64(), genesis))
}
currentHeader = bc.GetHeader(currentHeader.ParentHash, currentHeader.Number.Uint64()-1)
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
if currentHeader == nil {
return nil, lastHeader, fmt.Errorf("chain doesn't contain parent of block %d hash %v", lastHeader.Number, lastHeader.Hash())
}
}
return state, currentHeader, ctx.Err()
}

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, nil, fmt.Errorf("block not found while recreating: %d", blockToRecreate)
}
if block.ParentHash() != prevBlockHash {
return nil, nil, fmt.Errorf("reorg detected: number %d expectedPrev: %v foundPrev: %v", blockToRecreate, prevBlockHash, block.ParentHash())
}
if logFunc != nil {
logFunc(targetHeader, block.Header(), true)
}
_, _, _, err := bc.Processor().Process(block, state, vm.Config{})
if err != nil {
return nil, nil, fmt.Errorf("failed recreating state for block %d : %w", blockToRecreate, err)
}
return state, block, nil
}

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 {
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() != targetHeader.Hash() {
return nil, fmt.Errorf("blockHash doesn't match when recreating number: %d expected: %v got: %v", blockToRecreate, targetHeader.Hash(), block.Hash())
}
return state, nil
}
blockToRecreate++
}
return nil, ctx.Err()
}