diff --git a/core/blockchain.go b/core/blockchain.go index fe6de0979e..8d64d5bc7a 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -735,14 +735,6 @@ func (bc *BlockChain) initializeHistoryPruning(latest uint64) error { // was snap synced or full synced and in which state, the method will try to // delete minimal data from disk whilst retaining chain consistency. func (bc *BlockChain) SetHead(head uint64) error { - // Only allowed to rewind to a block that is later than the oldest state block. - firstStateBlock, err := bc.triedb.FirstStateBlock() - if err != nil { - return err - } - if head < firstStateBlock { - return fmt.Errorf("cannot rewind to block %d, oldest available state is at block %d", head, firstStateBlock) - } if _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false); err != nil { return err } @@ -2827,3 +2819,8 @@ func (bc *BlockChain) GetTrieFlushInterval() time.Duration { func (bc *BlockChain) StateSizer() *state.SizeTracker { return bc.stateSizer } + +// FirstStateBlock returns the first available state block number that is stored in the database. +func (bc *BlockChain) FirstStateBlock() (uint64, error) { + return bc.triedb.FirstStateBlock() +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 3ae73e78af..d830dc4a6f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -19,6 +19,7 @@ package eth import ( "context" "errors" + "fmt" "math/big" "time" @@ -61,9 +62,16 @@ func (b *EthAPIBackend) CurrentBlock() *types.Header { return b.eth.blockchain.CurrentBlock() } -func (b *EthAPIBackend) SetHead(number uint64) { +func (b *EthAPIBackend) SetHead(number uint64) error { + firstStateBlock, err := b.eth.blockchain.FirstStateBlock() + if err != nil { + return err + } + if number < firstStateBlock { + return fmt.Errorf("cannot rewind to block %d, oldest available state is at block %d", number, firstStateBlock) + } b.eth.handler.downloader.Cancel() - b.eth.blockchain.SetHead(number) + return b.eth.blockchain.SetHead(number) } func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 554f525290..56c67cf70f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1933,8 +1933,7 @@ func (api *DebugAPI) SetHead(number hexutil.Uint64) error { if header.Number.Uint64() <= uint64(number) { return errors.New("not allowed to rewind to a future block") } - api.b.SetHead(uint64(number)) - return nil + return api.b.SetHead(uint64(number)) } // NetAPI offers network related RPC methods diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index c0a8fe9a58..69635b9435 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -487,7 +487,7 @@ func (b testBackend) RPCGasCap() uint64 { return 10000000 func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second } func (b testBackend) RPCTxFeeCap() float64 { return 0 } func (b testBackend) UnprotectedAllowed() bool { return false } -func (b testBackend) SetHead(number uint64) {} +func (b testBackend) SetHead(number uint64) error { return nil } func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.LatestBlockNumber { return b.chain.CurrentBlock(), nil diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f709a1fcdc..d9c98dbfe4 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -55,7 +55,7 @@ type Backend interface { UnprotectedAllowed() bool // allows only for EIP155 transactions. // Blockchain API - SetHead(number uint64) + SetHead(number uint64) error HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 30791f32b5..aa1c110bca 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -336,7 +336,7 @@ func (b *backendMock) RPCGasCap() uint64 { return 0 } func (b *backendMock) RPCEVMTimeout() time.Duration { return time.Second } func (b *backendMock) RPCTxFeeCap() float64 { return 0 } func (b *backendMock) UnprotectedAllowed() bool { return false } -func (b *backendMock) SetHead(number uint64) {} +func (b *backendMock) SetHead(number uint64) error { return nil } func (b *backendMock) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { return nil, nil } diff --git a/triedb/database.go b/triedb/database.go index a77ddb8c7c..2c12a8abce 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -385,7 +385,7 @@ func (db *Database) SnapshotCompleted() bool { return pdb.SnapshotCompleted() } -// FirstStateBlock +// FirstStateBlock returns the first available state block number that is stored in the database. func (db *Database) FirstStateBlock() (uint64, error) { pdb, ok := db.backend.(*pathdb.Database) if !ok { diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index a9c528f8e7..f4ccd5cde5 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -695,20 +695,23 @@ func (db *Database) SnapshotCompleted() bool { // FirstStateBlock returns the block number of the oldest state snapshot in the freezer or disk layer. func (db *Database) FirstStateBlock() (uint64, error) { - var ( - m meta - err error - tailID = db.tree.bottom().stateID() - ) - - if db.stateFreezer != nil { - tailID, err = db.stateFreezer.Tail() - if err != nil { - return 0, err - } + freezer := db.stateFreezer + if freezer == nil { + return 0, errors.New("freezer is not available") } - blob := rawdb.ReadStateHistoryMeta(db.diskdb, tailID) + tailID, err := freezer.Tail() + if err != nil { + return 0, err + } + + // No state has been persistent + if tailID == 0 { + return 0, nil + } + + blob := rawdb.ReadStateHistoryMeta(freezer, tailID+1) + var m meta if err := m.decode(blob); err != nil { return 0, err }