Skip to content

Commit

Permalink
use finalizer instead of returning state release function
Browse files Browse the repository at this point in the history
  • Loading branch information
magicxyyz committed Dec 18, 2023
1 parent 8d5951a commit 814e540
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 83 deletions.
62 changes: 40 additions & 22 deletions arbitrum/apibackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math/big"
"runtime"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -229,11 +230,10 @@ func (a *APIBackend) FeeHistory(

// use the most recent average compute rate for all blocks
// note: while we could query this value for each block, it'd be prohibitively expensive
state, _, release, err := a.StateAndHeaderByNumber(ctx, newestBlock)
state, _, err := a.StateAndHeaderByNumber(ctx, newestBlock)
if err != nil {
return common.Big0, nil, nil, nil, err
}
defer release()
speedLimit, err := core.GetArbOSSpeedLimitPerSecond(state)
if err != nil {
return common.Big0, nil, nil, nil, err
Expand Down Expand Up @@ -434,59 +434,77 @@ func (a *APIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.
return nil, errors.New("invalid arguments; neither block nor hash specified")
}

func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types.Header, err error) (*state.StateDB, *types.Header, ethapi.StateReleaseFunc, error) {
func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types.Header, err error) (*state.StateDB, *types.Header, error) {
if err != nil {
return nil, header, nil, err
return nil, header, err
}
if header == nil {
return nil, nil, nil, errors.New("header not found")
return nil, nil, errors.New("header not found")
}
if !a.BlockChain().Config().IsArbitrumNitro(header.Number) {
return nil, header, nil, types.ErrUseFallback
return nil, header, types.ErrUseFallback
}
bc := a.BlockChain()
stateFor := func(header *types.Header) (*state.StateDB, error) {
if header.Root != (common.Hash{}) {
// Try referencing the root, if it isn't in dirties cache then Reference will have no effect
bc.StateCache().TrieDB().Reference(header.Root, common.Hash{})
}
state, err := bc.StateAt(header.Root)
return state, err
statedb, err := state.New(header.Root, bc.StateCache(), bc.Snapshots())
if err != nil {
return nil, err
}
if header.Root != (common.Hash{}) {
// we are setting finalizer instead of returning a StateReleaseFunc to avoid changing ethapi.Backend interface to minimize diff to upstream
headerRoot := header.Root
runtime.SetFinalizer(statedb, func(_ *state.StateDB) {
bc.StateCache().TrieDB().Dereference(headerRoot)
})
}
return statedb, err
}
lastState, lastHeader, err := FindLastAvailableState(ctx, bc, stateFor, header, nil, a.b.config.MaxRecreateStateDepth)
if err != nil {
return nil, nil, nil, err
}
release := func() {
if lastHeader.Root != (common.Hash{}) {
bc.StateCache().TrieDB().Dereference(lastHeader.Root)
}
return nil, nil, err
}
if lastHeader == header {
return lastState, header, release, nil
return lastState, header, nil
}
if lastHeader.Root != (common.Hash{}) {
defer bc.StateCache().TrieDB().Dereference(lastHeader.Root)
}
defer release()
targetBlock := bc.GetBlockByNumber(header.Number.Uint64())
if targetBlock == nil {
return nil, nil, nil, errors.New("target block not found")
return nil, nil, errors.New("target block not found")
}
lastBlock := bc.GetBlockByNumber(lastHeader.Number.Uint64())
if lastBlock == nil {
return nil, nil, nil, errors.New("last block not found")
return nil, nil, errors.New("last block not found")
}
reexec := uint64(0)
checkLive := false
preferDisk := true
state, release, err := eth.NewArbEthereum(a.b.arb.BlockChain(), a.ChainDb()).StateAtBlock(ctx, targetBlock, reexec, lastState, lastBlock, checkLive, preferDisk)
return state, header, release, err
statedb, release, err := eth.NewArbEthereum(a.b.arb.BlockChain(), a.ChainDb()).StateAtBlock(ctx, targetBlock, reexec, lastState, lastBlock, checkLive, preferDisk)
if err != nil {
return nil, nil, err
}
// we are setting finalizer instead of returning a StateReleaseFunc to avoid changing ethapi.Backend interface to minimize diff to upstream
// to set a finalizer we need to allocated the obj in current block
statedb, err = state.New(header.Root, statedb.Database(), nil)
if header.Root != (common.Hash{}) {
runtime.SetFinalizer(statedb, func(_ *state.StateDB) {
release()
})
}
return statedb, header, err
}

func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, ethapi.StateReleaseFunc, error) {
func (a *APIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
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, ethapi.StateReleaseFunc, error) {
func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
header, err := a.HeaderByNumberOrHash(ctx, blockNrOrHash)
return a.stateAndHeaderFromHeader(ctx, header, err)
}
Expand Down
25 changes: 12 additions & 13 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
Expand Down Expand Up @@ -199,46 +198,46 @@ func (b *EthAPIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts)
return b.eth.miner.PendingBlockAndReceipts()
}

func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, ethapi.StateReleaseFunc, error) {
func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if number == rpc.PendingBlockNumber {
block, state := b.eth.miner.Pending()
if block == nil || state == nil {
return nil, nil, nil, errors.New("pending state is not available")
return nil, nil, errors.New("pending state is not available")
}
return state, block.Header(), ethapi.NoOpStateRelease, nil
return state, block.Header(), nil
}
// Otherwise resolve the block number and return its state
header, err := b.HeaderByNumber(ctx, number)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
if header == nil {
return nil, nil, nil, errors.New("header not found")
return nil, nil, errors.New("header not found")
}
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
return stateDb, header, ethapi.NoOpStateRelease, err
return stateDb, header, err
}

func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, ethapi.StateReleaseFunc, error) {
func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.StateAndHeaderByNumber(ctx, blockNr)
}
if hash, ok := blockNrOrHash.Hash(); ok {
header, err := b.HeaderByHash(ctx, hash)
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
if header == nil {
return nil, nil, nil, errors.New("header for hash not found")
return nil, nil, errors.New("header for hash not found")
}
if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, nil, nil, errors.New("hash is not currently canonical")
return nil, nil, errors.New("hash is not currently canonical")
}
stateDb, err := b.eth.BlockChain().StateAt(header.Root)
return stateDb, header, ethapi.NoOpStateRelease, err
return stateDb, header, err
}
return nil, nil, nil, errors.New("invalid arguments; neither block nor hash specified")
return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
}

func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
Expand Down
3 changes: 1 addition & 2 deletions eth/catalyst/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1143,11 +1143,10 @@ func TestWithdrawals(t *testing.T) {
}

// 11: verify withdrawals were processed.
db, _, release, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number))
if err != nil {
t.Fatalf("unable to load db: %v", err)
}
defer release()
for i, w := range blockParams.Withdrawals {
// w.Amount is in gwei, balance in wei
if db.GetBalance(w.Address).Uint64() != w.Amount*params.GWei {
Expand Down
9 changes: 4 additions & 5 deletions graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,20 @@ type Account struct {
}

// getState fetches the StateDB object for an account.
func (a *Account) getState(ctx context.Context) (*state.StateDB, ethapi.StateReleaseFunc, error) {
state, _, release, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
return state, release, err
func (a *Account) getState(ctx context.Context) (*state.StateDB, error) {
state, _, err := a.r.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
return state, err
}

func (a *Account) Address(ctx context.Context) (common.Address, error) {
return a.address, nil
}

func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) {
state, release, err := a.getState(ctx)
state, err := a.getState(ctx)
if err != nil {
return hexutil.Big{}, err
}
defer release()
balance := state.GetBalance(a.address)
if balance == nil {
return hexutil.Big{}, fmt.Errorf("failed to load balance %x", a.address)
Expand Down
30 changes: 10 additions & 20 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ func (s *BlockChainAPI) BlockNumber() hexutil.Uint64 {
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
state, _, release, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
if client := fallbackClientFor(s.b, err); client != nil {
var res hexutil.Big
Expand All @@ -629,7 +629,6 @@ func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address,
}
return nil, err
}
defer release()
return (*hexutil.Big)(state.GetBalance(address)), state.Error()
}

Expand Down Expand Up @@ -682,11 +681,10 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
}
}

state, _, release, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
defer release()
if storageTrie, err = state.StorageTrie(address); err != nil {
return nil, err
}
Expand Down Expand Up @@ -869,7 +867,7 @@ func (s *BlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, blockHash

// GetCode returns the code stored at the given address in the state for the given block number.
func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, release, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
if client := fallbackClientFor(s.b, err); client != nil {
var res hexutil.Bytes
Expand All @@ -878,7 +876,6 @@ func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blo
}
return nil, err
}
defer release()
code := state.GetCode(address)
return code, state.Error()
}
Expand All @@ -891,7 +888,7 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address
if err != nil {
return nil, fmt.Errorf("unable to decode storage key: %s", err)
}
state, _, release, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
if client := fallbackClientFor(s.b, err); client != nil {
var res hexutil.Bytes
Expand All @@ -900,7 +897,6 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address
}
return nil, err
}
defer release()
res := state.GetState(address, key)
return res[:], state.Error()
}
Expand Down Expand Up @@ -1129,11 +1125,10 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64, runMode core.MessageRunMode) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, release, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
defer release()

return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap, runMode)
}
Expand Down Expand Up @@ -1237,11 +1232,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
}
// Recap the highest gas limit with account's available balance.
if feeCap.BitLen() != 0 {
state, _, release, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
}
defer release()
err = overrides.Apply(state)
if err != nil {
return 0, err
Expand Down Expand Up @@ -1271,11 +1265,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
// Arbitrum: raise the gas cap to ignore L1 costs so that it's compute-only
vanillaGasCap := gasCap
{
state, header, release, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return 0, err
}
defer release()
gasCap, err = args.L2OnlyGasCap(gasCap, header, state, core.MessageGasEstimationMode)
if err != nil {
return 0, err
Expand All @@ -1302,11 +1295,10 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
}
return result.Failed(), result, nil
}
state, header, release, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return 0, err
}
defer release()
err = overrides.Apply(state)
if err != nil {
return 0, err
Expand Down Expand Up @@ -1703,11 +1695,10 @@ func (s *BlockChainAPI) CreateAccessList(ctx context.Context, args TransactionAr
// If the transaction itself fails, an vmErr is returned.
func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrHash, args TransactionArgs) (acl types.AccessList, gasUsed uint64, vmErr error, err error) {
// Retrieve the execution context
db, header, release, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
db, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if db == nil || err != nil {
return nil, 0, nil, err
}
defer release()
// If the gas amount is not set, default to RPC gas cap.
if args.Gas == nil {
tmp := hexutil.Uint64(b.RPCGasCap())
Expand Down Expand Up @@ -1838,7 +1829,7 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
state, _, release, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
if client := fallbackClientFor(s.b, err); client != nil {
var res hexutil.Uint64
Expand All @@ -1847,7 +1838,6 @@ func (s *TransactionAPI) GetTransactionCount(ctx context.Context, address common
}
return nil, err
}
defer release()
nonce := state.GetNonce(address)
return (*hexutil.Uint64)(&nonce), state.Error()
}
Expand Down
6 changes: 3 additions & 3 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.
func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil
}
func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, StateReleaseFunc, error) {
func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
if number == rpc.PendingBlockNumber {
panic("pending state not implemented")
}
Expand All @@ -449,9 +449,9 @@ func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.Bloc
return nil, nil, nil, errors.New("header not found")
}
stateDb, err := b.chain.StateAt(header.Root)
return stateDb, header, NoOpStateRelease, err
return stateDb, header, err
}
func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, StateReleaseFunc, error) {
func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.StateAndHeaderByNumber(ctx, blockNr)
}
Expand Down
Loading

0 comments on commit 814e540

Please sign in to comment.