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

core, eth, ethapi, les, light, rpc: Support Block Number or Hash on state-related RPCs. #19459

Closed
Closed
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
core, eth, ethapi, les, light, rpc: Support Block Number or Hash on s…
…tate-related RPCs.
  • Loading branch information
ryanschneider committed Apr 22, 2019
commit fecbf991007e82ce5dfaf631af76fb6127bc7853
5 changes: 5 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,11 @@ func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool {
return bc.hc.HasHeader(hash, number)
}

// GetCanonicalHash returns the canonical hash for a given block number
func (bc *BlockChain) GetCanonicalHash(number uint64) common.Hash {
return bc.hc.GetCanonicalHash(number)
}

// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
// hash, fetching towards the genesis block.
func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash {
Expand Down
4 changes: 4 additions & 0 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header {
return hc.GetHeader(hash, number)
}

func (hc *HeaderChain) GetCanonicalHash(number uint64) common.Hash {
return rawdb.ReadCanonicalHash(hc.chainDb, number)
}

// CurrentHeader retrieves the current head header of the canonical chain. The
// header is retrieved from the HeaderChain's internal cache.
func (hc *HeaderChain) CurrentHeader() *types.Header {
Expand Down
74 changes: 74 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package eth

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts"
Expand Down Expand Up @@ -69,6 +70,27 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr)), nil
}

func (b *EthAPIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr)
}

if hash, ok := blockNrOrHash.Hash(); ok {
header := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, fmt.Errorf("header for hash not found")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, fmt.Errorf("hash is not currently canonical")
}

return header, nil
}

return nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return b.eth.blockchain.GetHeaderByHash(hash), nil
}
Expand All @@ -86,6 +108,32 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb
return b.eth.blockchain.GetBlockByNumber(uint64(blockNr)), nil
}

func (b *EthAPIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr)
}

if hash, ok := blockNrOrHash.Hash(); ok {
header := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, fmt.Errorf("header for hash not found")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, fmt.Errorf("hash is not currently canonical")
}

block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
if block == nil {
return nil, fmt.Errorf("block not found with hash at expected number")
}

return block, nil
}

return nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
// Pending state is only known by the miner
if blockNr == rpc.PendingBlockNumber {
Expand All @@ -101,6 +149,32 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.
return stateDb, header, err
}

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, err
}

if header == nil {
return nil, nil, fmt.Errorf("header for hash not found")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, nil, fmt.Errorf("hash is not currently canonical")
}

stateDb, err := b.eth.BlockChain().StateAt(header.Root)
return stateDb, header, err
}

return nil, nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *EthAPIBackend) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(hash), nil
}
Expand Down
33 changes: 17 additions & 16 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,8 @@ func (s *PublicBlockChainAPI) BlockNumber() hexutil.Uint64 {
// GetBalance returns the amount of wei for the given address in the state of the
// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta
// block numbers are also allowed.
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Big, error) {
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Big, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
Expand All @@ -525,8 +525,8 @@ type StorageResult struct {
}

// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) {
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
Expand Down Expand Up @@ -652,8 +652,8 @@ func (s *PublicBlockChainAPI) GetUncleCountByBlockHash(ctx context.Context, bloc
}

// GetCode returns the code stored at the given address in the state for the given block number.
func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
Expand All @@ -664,8 +664,8 @@ func (s *PublicBlockChainAPI) GetCode(ctx context.Context, address common.Addres
// GetStorageAt returns the storage from the state at the given address, key and
// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block
// numbers are also allowed.
func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, key string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
Expand All @@ -683,10 +683,10 @@ type CallArgs struct {
Data hexutil.Bytes `json:"data"`
}

func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, 0, false, err
}
Expand Down Expand Up @@ -751,8 +751,8 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr

// Call executes the given transaction on the state for the given block number.
// It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values.
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) {
result, _, _, err := s.doCall(ctx, args, blockNr, 5*time.Second, s.b.RPCGasCap())
func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) {
result, _, _, err := s.doCall(ctx, args, blockNrOrHash, 5*time.Second, s.b.RPCGasCap())
return (hexutil.Bytes)(result), err
}

Expand Down Expand Up @@ -786,7 +786,8 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h
executable := func(gas uint64) bool {
args.Gas = hexutil.Uint64(gas)

_, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, 0, gasCap)
blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
_, _, failed, err := s.doCall(ctx, args, blockNrOrHash, 0, gasCap)
if err != nil || failed {
return false
}
Expand Down Expand Up @@ -1082,17 +1083,17 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx cont
}

// GetTransactionCount returns the number of transactions the given address has sent for the given block number
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Uint64, error) {
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*hexutil.Uint64, error) {
// Ask transaction pool for the nonce which includes pending transactions
if blockNr == rpc.PendingBlockNumber {
if blockNr, ok := blockNrOrHash.Number(); ok && blockNr == rpc.PendingBlockNumber {
nonce, err := s.b.GetPoolNonce(ctx, address)
if err != nil {
return nil, err
}
return (*hexutil.Uint64)(&nonce), nil
}
// Resolve block number and use its state to ask for the nonce
state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr)
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ type Backend interface {
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error)
StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error)
HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error)
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
GetTd(blockHash common.Hash) *big.Int
Expand Down
72 changes: 72 additions & 0 deletions les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package les

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum/accounts"
Expand Down Expand Up @@ -63,6 +64,31 @@ func (b *LesApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(blockNr))
}

func (b *LesApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.HeaderByNumber(ctx, blockNr)
}

if hash, ok := blockNrOrHash.Hash(); ok {
header, err := b.HeaderByHash(ctx, hash)
if err != nil {
return nil, err
}

if header == nil {
return nil, fmt.Errorf("header for hash not found")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, fmt.Errorf("hash is not currently canonical")
}

return header, nil
}

return nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *LesApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return b.eth.blockchain.GetHeaderByHash(hash), nil
}
Expand All @@ -75,6 +101,31 @@ func (b *LesApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb
return b.GetBlock(ctx, header.Hash())
}

func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) {
if blockNr, ok := blockNrOrHash.Number(); ok {
return b.BlockByNumber(ctx, blockNr)
}

if hash, ok := blockNrOrHash.Hash(); ok {
block, err := b.GetBlock(ctx, hash)
if err != nil {
return nil, err
}

if block == nil {
return nil, fmt.Errorf("block not found with hash at expected number")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(block.NumberU64()) != hash {
return nil, fmt.Errorf("hash is not currently canonical")
}

return block, nil
}

return nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
header, err := b.HeaderByNumber(ctx, blockNr)
if header == nil || err != nil {
Expand All @@ -83,6 +134,27 @@ func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.
return light.NewState(ctx, header, b.eth.odr), header, nil
}

func (b *LesApiBackend) 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 := b.eth.blockchain.GetHeaderByHash(hash)
if header == nil {
return nil, nil, fmt.Errorf("header for hash not found")
}

if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
return nil, nil, fmt.Errorf("hash is not currently canonical")
}

return light.NewState(ctx, header, b.eth.odr), header, nil
}

return nil, nil, fmt.Errorf("specifier neither block nor hash")
}

func (b *LesApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) {
return b.eth.blockchain.GetBlockByHash(ctx, blockHash)
}
Expand Down
1 change: 1 addition & 0 deletions les/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func errResp(code errCode, format string, v ...interface{}) error {
type BlockChain interface {
Config() *params.ChainConfig
HasHeader(hash common.Hash, number uint64) bool
GetCanonicalHash(number uint64) common.Hash
GetHeader(hash common.Hash, number uint64) *types.Header
GetHeaderByHash(hash common.Hash) *types.Header
CurrentHeader() *types.Header
Expand Down
5 changes: 5 additions & 0 deletions light/lightchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,11 @@ func (bc *LightChain) HasHeader(hash common.Hash, number uint64) bool {
return bc.hc.HasHeader(hash, number)
}

// GetCanonicalHash returns the canonical hash for a given block number
func (bc *LightChain) GetCanonicalHash(number uint64) common.Hash {
return bc.hc.GetCanonicalHash(number)
}

// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
// hash, fetching towards the genesis block.
func (self *LightChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash {
Expand Down
Loading