|
|
@@ -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
|