Bläddra i källkod

internal/ethapi: support block number or hash on state-related methods (#19491)

This change adds support for EIP-1898.
Ryan Schneider 6 år sedan
förälder
incheckning
ad03d9801c
10 ändrade filer med 408 tillägg och 166 borttagningar
  1. 5 0
      core/blockchain.go
  2. 4 0
      core/headerchain.go
  3. 59 0
      eth/api_backend.go
  4. 102 146
      graphql/graphql.go
  5. 22 20
      internal/ethapi/api.go
  6. 3 0
      internal/ethapi/backend.go
  7. 57 0
      les/api_backend.go
  8. 5 0
      light/lightchain.go
  9. 93 0
      rpc/types.go
  10. 58 0
      rpc/types_test.go

+ 5 - 0
core/blockchain.go

@@ -2139,6 +2139,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 {

+ 4 - 0
core/headerchain.go

@@ -448,6 +448,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 {

+ 59 - 0
eth/api_backend.go

@@ -72,6 +72,23 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
 	return b.eth.blockchain.GetHeaderByNumber(uint64(number)), 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, errors.New("header for hash not found")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
+			return nil, errors.New("hash is not currently canonical")
+		}
+		return header, nil
+	}
+	return nil, errors.New("invalid arguments; neither block nor hash specified")
+}
+
 func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
 	return b.eth.blockchain.GetHeaderByHash(hash), nil
 }
@@ -93,6 +110,27 @@ func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*typ
 	return b.eth.blockchain.GetBlockByHash(hash), 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, errors.New("header for hash not found")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
+			return nil, errors.New("hash is not currently canonical")
+		}
+		block := b.eth.blockchain.GetBlock(hash, header.Number.Uint64())
+		if block == nil {
+			return nil, errors.New("header found, but block body is missing")
+		}
+		return block, nil
+	}
+	return nil, errors.New("invalid arguments; neither block nor hash specified")
+}
+
 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 {
@@ -111,6 +149,27 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B
 	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, errors.New("header for hash not found")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
+			return nil, nil, errors.New("hash is not currently canonical")
+		}
+		stateDb, err := b.eth.BlockChain().StateAt(header.Root)
+		return stateDb, header, err
+	}
+	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) {
 	return b.eth.blockchain.GetReceiptsByHash(hash), nil
 }

+ 102 - 146
graphql/graphql.go

@@ -36,20 +36,19 @@ import (
 )
 
 var (
-	errOnlyOnMainChain = errors.New("this operation is only available for blocks on the canonical chain")
-	errBlockInvariant  = errors.New("block objects must be instantiated with at least one of num or hash")
+	errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash")
 )
 
 // Account represents an Ethereum account at a particular block.
 type Account struct {
-	backend     ethapi.Backend
-	address     common.Address
-	blockNumber rpc.BlockNumber
+	backend       ethapi.Backend
+	address       common.Address
+	blockNrOrHash rpc.BlockNumberOrHash
 }
 
 // getState fetches the StateDB object for an account.
 func (a *Account) getState(ctx context.Context) (*state.StateDB, error) {
-	state, _, err := a.backend.StateAndHeaderByNumber(ctx, a.blockNumber)
+	state, _, err := a.backend.StateAndHeaderByNumberOrHash(ctx, a.blockNrOrHash)
 	return state, err
 }
 
@@ -102,9 +101,9 @@ func (l *Log) Transaction(ctx context.Context) *Transaction {
 
 func (l *Log) Account(ctx context.Context, args BlockNumberArgs) *Account {
 	return &Account{
-		backend:     l.backend,
-		address:     l.log.Address,
-		blockNumber: args.Number(),
+		backend:       l.backend,
+		address:       l.log.Address,
+		blockNrOrHash: args.NumberOrLatest(),
 	}
 }
 
@@ -136,10 +135,10 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) {
 		tx, blockHash, _, index := rawdb.ReadTransaction(t.backend.ChainDb(), t.hash)
 		if tx != nil {
 			t.tx = tx
+			blockNrOrHash := rpc.BlockNumberOrHashWithHash(blockHash, false)
 			t.block = &Block{
-				backend:   t.backend,
-				hash:      blockHash,
-				canonical: unknown,
+				backend:      t.backend,
+				numberOrHash: &blockNrOrHash,
 			}
 			t.index = index
 		} else {
@@ -203,9 +202,9 @@ func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, e
 		return nil, nil
 	}
 	return &Account{
-		backend:     t.backend,
-		address:     *to,
-		blockNumber: args.Number(),
+		backend:       t.backend,
+		address:       *to,
+		blockNrOrHash: args.NumberOrLatest(),
 	}, nil
 }
 
@@ -221,9 +220,9 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account,
 	from, _ := types.Sender(signer, tx)
 
 	return &Account{
-		backend:     t.backend,
-		address:     from,
-		blockNumber: args.Number(),
+		backend:       t.backend,
+		address:       from,
+		blockNrOrHash: args.NumberOrLatest(),
 	}, nil
 }
 
@@ -293,9 +292,9 @@ func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs)
 		return nil, err
 	}
 	return &Account{
-		backend:     t.backend,
-		address:     receipt.ContractAddress,
-		blockNumber: args.Number(),
+		backend:       t.backend,
+		address:       receipt.ContractAddress,
+		blockNrOrHash: args.NumberOrLatest(),
 	}, nil
 }
 
@@ -317,45 +316,16 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) {
 
 type BlockType int
 
-const (
-	unknown BlockType = iota
-	isCanonical
-	notCanonical
-)
-
 // Block represents an Ethereum block.
-// backend, and either num or hash are mandatory. All other fields are lazily fetched
+// backend, and numberOrHash are mandatory. All other fields are lazily fetched
 // when required.
 type Block struct {
-	backend   ethapi.Backend
-	num       *rpc.BlockNumber
-	hash      common.Hash
-	header    *types.Header
-	block     *types.Block
-	receipts  []*types.Receipt
-	canonical BlockType // Indicates if this block is on the main chain or not.
-}
-
-func (b *Block) onMainChain(ctx context.Context) error {
-	if b.canonical == unknown {
-		header, err := b.resolveHeader(ctx)
-		if err != nil {
-			return err
-		}
-		canonHeader, err := b.backend.HeaderByNumber(ctx, rpc.BlockNumber(header.Number.Uint64()))
-		if err != nil {
-			return err
-		}
-		if header.Hash() == canonHeader.Hash() {
-			b.canonical = isCanonical
-		} else {
-			b.canonical = notCanonical
-		}
-	}
-	if b.canonical != isCanonical {
-		return errOnlyOnMainChain
-	}
-	return nil
+	backend      ethapi.Backend
+	numberOrHash *rpc.BlockNumberOrHash
+	hash         common.Hash
+	header       *types.Header
+	block        *types.Block
+	receipts     []*types.Receipt
 }
 
 // resolve returns the internal Block object representing this block, fetching
@@ -364,14 +334,17 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
 	if b.block != nil {
 		return b.block, nil
 	}
-	var err error
-	if b.hash != (common.Hash{}) {
-		b.block, err = b.backend.BlockByHash(ctx, b.hash)
-	} else {
-		b.block, err = b.backend.BlockByNumber(ctx, *b.num)
+	if b.numberOrHash == nil {
+		latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
+		b.numberOrHash = &latest
 	}
+	var err error
+	b.block, err = b.backend.BlockByNumberOrHash(ctx, *b.numberOrHash)
 	if b.block != nil && b.header == nil {
 		b.header = b.block.Header()
+		if hash, ok := b.numberOrHash.Hash(); ok {
+			b.hash = hash
+		}
 	}
 	return b.block, err
 }
@@ -380,7 +353,7 @@ func (b *Block) resolve(ctx context.Context) (*types.Block, error) {
 // if necessary. Call this function instead of `resolve` unless you need the
 // additional data (transactions and uncles).
 func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
-	if b.num == nil && b.hash == (common.Hash{}) {
+	if b.numberOrHash == nil && b.hash == (common.Hash{}) {
 		return nil, errBlockInvariant
 	}
 	var err error
@@ -388,7 +361,7 @@ func (b *Block) resolveHeader(ctx context.Context) (*types.Header, error) {
 		if b.hash != (common.Hash{}) {
 			b.header, err = b.backend.HeaderByHash(ctx, b.hash)
 		} else {
-			b.header, err = b.backend.HeaderByNumber(ctx, *b.num)
+			b.header, err = b.backend.HeaderByNumberOrHash(ctx, *b.numberOrHash)
 		}
 	}
 	return b.header, err
@@ -416,15 +389,12 @@ func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) {
 }
 
 func (b *Block) Number(ctx context.Context) (hexutil.Uint64, error) {
-	if b.num == nil || *b.num == rpc.LatestBlockNumber {
-		header, err := b.resolveHeader(ctx)
-		if err != nil {
-			return 0, err
-		}
-		num := rpc.BlockNumber(header.Number.Uint64())
-		b.num = &num
+	header, err := b.resolveHeader(ctx)
+	if err != nil {
+		return 0, err
 	}
-	return hexutil.Uint64(*b.num), nil
+
+	return hexutil.Uint64(header.Number.Uint64()), nil
 }
 
 func (b *Block) Hash(ctx context.Context) (common.Hash, error) {
@@ -456,26 +426,17 @@ func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) {
 
 func (b *Block) Parent(ctx context.Context) (*Block, error) {
 	// If the block header hasn't been fetched, and we'll need it, fetch it.
-	if b.num == nil && b.hash != (common.Hash{}) && b.header == nil {
+	if b.numberOrHash == nil && b.header == nil {
 		if _, err := b.resolveHeader(ctx); err != nil {
 			return nil, err
 		}
 	}
 	if b.header != nil && b.header.Number.Uint64() > 0 {
-		num := rpc.BlockNumber(b.header.Number.Uint64() - 1)
+		num := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(b.header.Number.Uint64() - 1))
 		return &Block{
-			backend:   b.backend,
-			num:       &num,
-			hash:      b.header.ParentHash,
-			canonical: unknown,
-		}, nil
-	}
-	if b.num != nil && *b.num != 0 {
-		num := *b.num - 1
-		return &Block{
-			backend:   b.backend,
-			num:       &num,
-			canonical: isCanonical,
+			backend:      b.backend,
+			numberOrHash: &num,
+			hash:         b.header.ParentHash,
 		}, nil
 	}
 	return nil, nil
@@ -561,13 +522,11 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) {
 	}
 	ret := make([]*Block, 0, len(block.Uncles()))
 	for _, uncle := range block.Uncles() {
-		blockNumber := rpc.BlockNumber(uncle.Number.Uint64())
+		blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false)
 		ret = append(ret, &Block{
-			backend:   b.backend,
-			num:       &blockNumber,
-			hash:      uncle.Hash(),
-			header:    uncle,
-			canonical: notCanonical,
+			backend:      b.backend,
+			numberOrHash: &blockNumberOrHash,
+			header:       uncle,
 		})
 	}
 	return &ret, nil
@@ -603,16 +562,26 @@ func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) {
 
 // BlockNumberArgs encapsulates arguments to accessors that specify a block number.
 type BlockNumberArgs struct {
+	// TODO: Ideally we could use input unions to allow the query to specify the
+	// block parameter by hash, block number, or tag but input unions aren't part of the
+	// standard GraphQL schema SDL yet, see: https://github.com/graphql/graphql-spec/issues/488
 	Block *hexutil.Uint64
 }
 
-// Number returns the provided block number, or rpc.LatestBlockNumber if none
+// NumberOr returns the provided block number argument, or the "current" block number or hash if none
 // was provided.
-func (a BlockNumberArgs) Number() rpc.BlockNumber {
+func (a BlockNumberArgs) NumberOr(current rpc.BlockNumberOrHash) rpc.BlockNumberOrHash {
 	if a.Block != nil {
-		return rpc.BlockNumber(*a.Block)
+		blockNr := rpc.BlockNumber(*a.Block)
+		return rpc.BlockNumberOrHashWithNumber(blockNr)
 	}
-	return rpc.LatestBlockNumber
+	return current
+}
+
+// NumberOrLatest returns the provided block number argument, or the "latest" block number if none
+// was provided.
+func (a BlockNumberArgs) NumberOrLatest() rpc.BlockNumberOrHash {
+	return a.NumberOr(rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber))
 }
 
 func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, error) {
@@ -621,9 +590,9 @@ func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, erro
 		return nil, err
 	}
 	return &Account{
-		backend:     b.backend,
-		address:     header.Coinbase,
-		blockNumber: args.Number(),
+		backend:       b.backend,
+		address:       header.Coinbase,
+		blockNrOrHash: args.NumberOrLatest(),
 	}, nil
 }
 
@@ -683,13 +652,11 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block
 		return nil, nil
 	}
 	uncle := uncles[args.Index]
-	blockNumber := rpc.BlockNumber(uncle.Number.Uint64())
+	blockNumberOrHash := rpc.BlockNumberOrHashWithHash(uncle.Hash(), false)
 	return &Block{
-		backend:   b.backend,
-		num:       &blockNumber,
-		hash:      uncle.Hash(),
-		header:    uncle,
-		canonical: notCanonical,
+		backend:      b.backend,
+		numberOrHash: &blockNumberOrHash,
+		header:       uncle,
 	}, nil
 }
 
@@ -757,20 +724,16 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri
 func (b *Block) Account(ctx context.Context, args struct {
 	Address common.Address
 }) (*Account, error) {
-	err := b.onMainChain(ctx)
-	if err != nil {
-		return nil, err
-	}
-	if b.num == nil {
+	if b.numberOrHash == nil {
 		_, err := b.resolveHeader(ctx)
 		if err != nil {
 			return nil, err
 		}
 	}
 	return &Account{
-		backend:     b.backend,
-		address:     args.Address,
-		blockNumber: *b.num,
+		backend:       b.backend,
+		address:       args.Address,
+		blockNrOrHash: *b.numberOrHash,
 	}, nil
 }
 
@@ -807,17 +770,13 @@ func (c *CallResult) Status() hexutil.Uint64 {
 func (b *Block) Call(ctx context.Context, args struct {
 	Data ethapi.CallArgs
 }) (*CallResult, error) {
-	err := b.onMainChain(ctx)
-	if err != nil {
-		return nil, err
-	}
-	if b.num == nil {
-		_, err := b.resolveHeader(ctx)
+	if b.numberOrHash == nil {
+		_, err := b.resolve(ctx)
 		if err != nil {
 			return nil, err
 		}
 	}
-	result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
+	result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
 	status := hexutil.Uint64(1)
 	if failed {
 		status = 0
@@ -832,17 +791,13 @@ func (b *Block) Call(ctx context.Context, args struct {
 func (b *Block) EstimateGas(ctx context.Context, args struct {
 	Data ethapi.CallArgs
 }) (hexutil.Uint64, error) {
-	err := b.onMainChain(ctx)
-	if err != nil {
-		return hexutil.Uint64(0), err
-	}
-	if b.num == nil {
+	if b.numberOrHash == nil {
 		_, err := b.resolveHeader(ctx)
 		if err != nil {
 			return hexutil.Uint64(0), err
 		}
 	}
-	gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.num, b.backend.RPCGasCap())
+	gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap())
 	return gas, err
 }
 
@@ -875,17 +830,19 @@ func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) {
 func (p *Pending) Account(ctx context.Context, args struct {
 	Address common.Address
 }) *Account {
+	pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
 	return &Account{
-		backend:     p.backend,
-		address:     args.Address,
-		blockNumber: rpc.PendingBlockNumber,
+		backend:       p.backend,
+		address:       args.Address,
+		blockNrOrHash: pendingBlockNr,
 	}
 }
 
 func (p *Pending) Call(ctx context.Context, args struct {
 	Data ethapi.CallArgs
 }) (*CallResult, error) {
-	result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, rpc.PendingBlockNumber, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
+	pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+	result, gas, failed, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
 	status := hexutil.Uint64(1)
 	if failed {
 		status = 0
@@ -900,7 +857,8 @@ func (p *Pending) Call(ctx context.Context, args struct {
 func (p *Pending) EstimateGas(ctx context.Context, args struct {
 	Data ethapi.CallArgs
 }) (hexutil.Uint64, error) {
-	return ethapi.DoEstimateGas(ctx, p.backend, args.Data, rpc.PendingBlockNumber, p.backend.RPCGasCap())
+	pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+	return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap())
 }
 
 // Resolver is the top-level object in the GraphQL hierarchy.
@@ -914,24 +872,23 @@ func (r *Resolver) Block(ctx context.Context, args struct {
 }) (*Block, error) {
 	var block *Block
 	if args.Number != nil {
-		num := rpc.BlockNumber(uint64(*args.Number))
+		number := rpc.BlockNumber(uint64(*args.Number))
+		numberOrHash := rpc.BlockNumberOrHashWithNumber(number)
 		block = &Block{
-			backend:   r.backend,
-			num:       &num,
-			canonical: isCanonical,
+			backend:      r.backend,
+			numberOrHash: &numberOrHash,
 		}
 	} else if args.Hash != nil {
+		numberOrHash := rpc.BlockNumberOrHashWithHash(*args.Hash, false)
 		block = &Block{
-			backend:   r.backend,
-			hash:      *args.Hash,
-			canonical: unknown,
+			backend:      r.backend,
+			numberOrHash: &numberOrHash,
 		}
 	} else {
-		num := rpc.LatestBlockNumber
+		numberOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
 		block = &Block{
-			backend:   r.backend,
-			num:       &num,
-			canonical: isCanonical,
+			backend:      r.backend,
+			numberOrHash: &numberOrHash,
 		}
 	}
 	// Resolve the header, return nil if it doesn't exist.
@@ -963,11 +920,10 @@ func (r *Resolver) Blocks(ctx context.Context, args struct {
 	}
 	ret := make([]*Block, 0, to-from+1)
 	for i := from; i <= to; i++ {
-		num := i
+		numberOrHash := rpc.BlockNumberOrHashWithNumber(i)
 		ret = append(ret, &Block{
-			backend:   r.backend,
-			num:       &num,
-			canonical: isCanonical,
+			backend:      r.backend,
+			numberOrHash: &numberOrHash,
 		})
 	}
 	return ret, nil

+ 22 - 20
internal/ethapi/api.go

@@ -530,8 +530,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
 	}
@@ -555,8 +555,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
 	}
@@ -712,8 +712,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
 	}
@@ -724,8 +724,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
 	}
@@ -757,10 +757,10 @@ type account struct {
 	StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
 }
 
-func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) ([]byte, uint64, bool, error) {
+func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, 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 := b.StateAndHeaderByNumber(ctx, blockNr)
+	state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
 	if state == nil || err != nil {
 		return nil, 0, false, err
 	}
@@ -874,16 +874,16 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumb
 //
 // Note, this function 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, overrides *map[common.Address]account) (hexutil.Bytes, error) {
+func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *map[common.Address]account) (hexutil.Bytes, error) {
 	var accounts map[common.Address]account
 	if overrides != nil {
 		accounts = *overrides
 	}
-	result, _, _, err := DoCall(ctx, s.b, args, blockNr, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
+	result, _, _, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
 	return (hexutil.Bytes)(result), err
 }
 
-func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, gasCap *big.Int) (hexutil.Uint64, error) {
+func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
 	// Binary search the gas requirement, as it may be higher than the amount used
 	var (
 		lo  uint64 = params.TxGas - 1
@@ -894,7 +894,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
 		hi = uint64(*args.Gas)
 	} else {
 		// Retrieve the block to act as the gas ceiling
-		block, err := b.BlockByNumber(ctx, blockNr)
+		block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
 		if err != nil {
 			return 0, err
 		}
@@ -910,7 +910,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
 	executable := func(gas uint64) bool {
 		args.Gas = (*hexutil.Uint64)(&gas)
 
-		_, _, failed, err := DoCall(ctx, b, args, rpc.PendingBlockNumber, nil, vm.Config{}, 0, gasCap)
+		_, _, failed, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
 		if err != nil || failed {
 			return false
 		}
@@ -937,7 +937,8 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.Bl
 // EstimateGas returns an estimate of the amount of gas needed to execute the
 // given transaction against the current pending block.
 func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) {
-	return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber, s.b.RPCGasCap())
+	blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+	return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap())
 }
 
 // ExecutionResult groups all structured logs emitted by the EVM
@@ -1224,9 +1225,9 @@ 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
@@ -1234,7 +1235,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr
 		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
 	}
@@ -1405,7 +1406,8 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
 			Value:    args.Value,
 			Data:     input,
 		}
-		estimated, err := DoEstimateGas(ctx, b, callArgs, rpc.PendingBlockNumber, b.RPCGasCap())
+		pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
+		estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
 		if err != nil {
 			return err
 		}

+ 3 - 0
internal/ethapi/backend.go

@@ -52,9 +52,12 @@ type Backend interface {
 	SetHead(number uint64)
 	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)
 	BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
 	BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
+	BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
 	StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
+	StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
 	GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
 	GetTd(hash common.Hash) *big.Int
 	GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error)

+ 57 - 0
les/api_backend.go

@@ -65,6 +65,26 @@ func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb
 	return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number))
 }
 
+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, errors.New("header for hash not found")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
+			return nil, errors.New("hash is not currently canonical")
+		}
+		return header, nil
+	}
+	return nil, errors.New("invalid arguments; neither block nor hash specified")
+}
+
 func (b *LesApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
 	return b.eth.blockchain.GetHeaderByHash(hash), nil
 }
@@ -81,6 +101,26 @@ func (b *LesApiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*typ
 	return b.eth.blockchain.GetBlockByHash(ctx, 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.BlockByHash(ctx, hash)
+		if err != nil {
+			return nil, err
+		}
+		if block == nil {
+			return nil, errors.New("header found, but block body is missing")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(block.NumberU64()) != hash {
+			return nil, errors.New("hash is not currently canonical")
+		}
+		return block, nil
+	}
+	return nil, errors.New("invalid arguments; neither block nor hash specified")
+}
+
 func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
 	header, err := b.HeaderByNumber(ctx, number)
 	if err != nil {
@@ -92,6 +132,23 @@ func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B
 	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, errors.New("header for hash not found")
+		}
+		if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash {
+			return nil, nil, errors.New("hash is not currently canonical")
+		}
+		return light.NewState(ctx, header, b.eth.odr), header, nil
+	}
+	return nil, nil, errors.New("invalid arguments; neither block nor hash specified")
+}
+
 func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
 	if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil {
 		return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number)

+ 5 - 0
light/lightchain.go

@@ -426,6 +426,11 @@ func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool {
 	return lc.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 (lc *LightChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash {

+ 93 - 0
rpc/types.go

@@ -18,10 +18,12 @@ package rpc
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"math"
 	"strings"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 )
 
@@ -105,3 +107,94 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
 func (bn BlockNumber) Int64() int64 {
 	return (int64)(bn)
 }
+
+type BlockNumberOrHash struct {
+	BlockNumber      *BlockNumber `json:"blockNumber,omitempty"`
+	BlockHash        *common.Hash `json:"blockHash,omitempty"`
+	RequireCanonical bool         `json:"requireCanonical,omitempty"`
+}
+
+func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
+	type erased BlockNumberOrHash
+	e := erased{}
+	err := json.Unmarshal(data, &e)
+	if err == nil {
+		if e.BlockNumber != nil && e.BlockHash != nil {
+			return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
+		}
+		bnh.BlockNumber = e.BlockNumber
+		bnh.BlockHash = e.BlockHash
+		bnh.RequireCanonical = e.RequireCanonical
+		return nil
+	}
+	var input string
+	err = json.Unmarshal(data, &input)
+	if err != nil {
+		return err
+	}
+	switch input {
+	case "earliest":
+		bn := EarliestBlockNumber
+		bnh.BlockNumber = &bn
+		return nil
+	case "latest":
+		bn := LatestBlockNumber
+		bnh.BlockNumber = &bn
+		return nil
+	case "pending":
+		bn := PendingBlockNumber
+		bnh.BlockNumber = &bn
+		return nil
+	default:
+		if len(input) == 66 {
+			hash := common.Hash{}
+			err := hash.UnmarshalText([]byte(input))
+			if err != nil {
+				return err
+			}
+			bnh.BlockHash = &hash
+			return nil
+		} else {
+			blckNum, err := hexutil.DecodeUint64(input)
+			if err != nil {
+				return err
+			}
+			if blckNum > math.MaxInt64 {
+				return fmt.Errorf("blocknumber too high")
+			}
+			bn := BlockNumber(blckNum)
+			bnh.BlockNumber = &bn
+			return nil
+		}
+	}
+}
+
+func (bnh *BlockNumberOrHash) Number() (BlockNumber, bool) {
+	if bnh.BlockNumber != nil {
+		return *bnh.BlockNumber, true
+	}
+	return BlockNumber(0), false
+}
+
+func (bnh *BlockNumberOrHash) Hash() (common.Hash, bool) {
+	if bnh.BlockHash != nil {
+		return *bnh.BlockHash, true
+	}
+	return common.Hash{}, false
+}
+
+func BlockNumberOrHashWithNumber(blockNr BlockNumber) BlockNumberOrHash {
+	return BlockNumberOrHash{
+		BlockNumber:      &blockNr,
+		BlockHash:        nil,
+		RequireCanonical: false,
+	}
+}
+
+func BlockNumberOrHashWithHash(hash common.Hash, canonical bool) BlockNumberOrHash {
+	return BlockNumberOrHash{
+		BlockNumber:      nil,
+		BlockHash:        &hash,
+		RequireCanonical: canonical,
+	}
+}

+ 58 - 0
rpc/types_test.go

@@ -20,6 +20,7 @@ import (
 	"encoding/json"
 	"testing"
 
+	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/common/math"
 )
 
@@ -64,3 +65,60 @@ func TestBlockNumberJSONUnmarshal(t *testing.T) {
 		}
 	}
 }
+
+func TestBlockNumberOrHash_UnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		input    string
+		mustFail bool
+		expected BlockNumberOrHash
+	}{
+		0:  {`"0x"`, true, BlockNumberOrHash{}},
+		1:  {`"0x0"`, false, BlockNumberOrHashWithNumber(0)},
+		2:  {`"0X1"`, false, BlockNumberOrHashWithNumber(1)},
+		3:  {`"0x00"`, true, BlockNumberOrHash{}},
+		4:  {`"0x01"`, true, BlockNumberOrHash{}},
+		5:  {`"0x1"`, false, BlockNumberOrHashWithNumber(1)},
+		6:  {`"0x12"`, false, BlockNumberOrHashWithNumber(18)},
+		7:  {`"0x7fffffffffffffff"`, false, BlockNumberOrHashWithNumber(math.MaxInt64)},
+		8:  {`"0x8000000000000000"`, true, BlockNumberOrHash{}},
+		9:  {"0", true, BlockNumberOrHash{}},
+		10: {`"ff"`, true, BlockNumberOrHash{}},
+		11: {`"pending"`, false, BlockNumberOrHashWithNumber(PendingBlockNumber)},
+		12: {`"latest"`, false, BlockNumberOrHashWithNumber(LatestBlockNumber)},
+		13: {`"earliest"`, false, BlockNumberOrHashWithNumber(EarliestBlockNumber)},
+		14: {`someString`, true, BlockNumberOrHash{}},
+		15: {`""`, true, BlockNumberOrHash{}},
+		16: {``, true, BlockNumberOrHash{}},
+		17: {`"0x0000000000000000000000000000000000000000000000000000000000000000"`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
+		18: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
+		19: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","requireCanonical":false}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), false)},
+		20: {`{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","requireCanonical":true}`, false, BlockNumberOrHashWithHash(common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), true)},
+		21: {`{"blockNumber":"0x1"}`, false, BlockNumberOrHashWithNumber(1)},
+		22: {`{"blockNumber":"pending"}`, false, BlockNumberOrHashWithNumber(PendingBlockNumber)},
+		23: {`{"blockNumber":"latest"}`, false, BlockNumberOrHashWithNumber(LatestBlockNumber)},
+		24: {`{"blockNumber":"earliest"}`, false, BlockNumberOrHashWithNumber(EarliestBlockNumber)},
+		25: {`{"blockNumber":"0x1", "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}`, true, BlockNumberOrHash{}},
+	}
+
+	for i, test := range tests {
+		var bnh BlockNumberOrHash
+		err := json.Unmarshal([]byte(test.input), &bnh)
+		if test.mustFail && err == nil {
+			t.Errorf("Test %d should fail", i)
+			continue
+		}
+		if !test.mustFail && err != nil {
+			t.Errorf("Test %d should pass but got err: %v", i, err)
+			continue
+		}
+		hash, hashOk := bnh.Hash()
+		expectedHash, expectedHashOk := test.expected.Hash()
+		num, numOk := bnh.Number()
+		expectedNum, expectedNumOk := test.expected.Number()
+		if bnh.RequireCanonical != test.expected.RequireCanonical ||
+			hash != expectedHash || hashOk != expectedHashOk ||
+			num != expectedNum || numOk != expectedNumOk {
+			t.Errorf("Test %d got unexpected value, want %v, got %v", i, test.expected, bnh)
+		}
+	}
+}