|
|
|
@ -19,6 +19,7 @@ package graphql |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"context" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"net" |
|
|
|
|
"net/http" |
|
|
|
@ -43,6 +44,9 @@ import ( |
|
|
|
|
"github.com/graph-gophers/graphql-go/relay" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
var OnlyOnMainChainError = errors.New("This operation is only available for blocks on the canonical chain.") |
|
|
|
|
var BlockInvariantError = 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 *eth.EthAPIBackend |
|
|
|
@ -144,8 +148,9 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) { |
|
|
|
|
if tx != nil { |
|
|
|
|
t.tx = tx |
|
|
|
|
t.block = &Block{ |
|
|
|
|
backend: t.backend, |
|
|
|
|
hash: blockHash, |
|
|
|
|
backend: t.backend, |
|
|
|
|
hash: blockHash, |
|
|
|
|
canonical: unknown, |
|
|
|
|
} |
|
|
|
|
t.index = index |
|
|
|
|
} else { |
|
|
|
@ -332,16 +337,47 @@ func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { |
|
|
|
|
return &ret, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Block represennts an Ethereum block.
|
|
|
|
|
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
|
|
|
|
|
// when required.
|
|
|
|
|
type Block struct { |
|
|
|
|
backend *eth.EthAPIBackend |
|
|
|
|
num *rpc.BlockNumber |
|
|
|
|
hash common.Hash |
|
|
|
|
header *types.Header |
|
|
|
|
block *types.Block |
|
|
|
|
receipts []*types.Receipt |
|
|
|
|
backend *eth.EthAPIBackend |
|
|
|
|
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 OnlyOnMainChainError |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// resolve returns the internal Block object representing this block, fetching
|
|
|
|
@ -367,6 +403,10 @@ 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{}) { |
|
|
|
|
return nil, BlockInvariantError |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if b.header == nil { |
|
|
|
|
if _, err := b.resolve(ctx); err != nil { |
|
|
|
|
return nil, err |
|
|
|
@ -447,15 +487,18 @@ func (b *Block) Parent(ctx context.Context) (*Block, error) { |
|
|
|
|
if b.header != nil && b.block.NumberU64() > 0 { |
|
|
|
|
num := rpc.BlockNumber(b.header.Number.Uint64() - 1) |
|
|
|
|
return &Block{ |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &num, |
|
|
|
|
hash: b.header.ParentHash, |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &num, |
|
|
|
|
hash: b.header.ParentHash, |
|
|
|
|
canonical: unknown, |
|
|
|
|
}, nil |
|
|
|
|
} else if b.num != nil && *b.num != 0 { |
|
|
|
|
} |
|
|
|
|
if b.num != nil && *b.num != 0 { |
|
|
|
|
num := *b.num - 1 |
|
|
|
|
return &Block{ |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &num, |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &num, |
|
|
|
|
canonical: isCanonical, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
return nil, nil |
|
|
|
@ -544,10 +587,11 @@ func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { |
|
|
|
|
for _, uncle := range block.Uncles() { |
|
|
|
|
blockNumber := rpc.BlockNumber(uncle.Number.Uint64()) |
|
|
|
|
ret = append(ret, &Block{ |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &blockNumber, |
|
|
|
|
hash: uncle.Hash(), |
|
|
|
|
header: uncle, |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &blockNumber, |
|
|
|
|
hash: uncle.Hash(), |
|
|
|
|
header: uncle, |
|
|
|
|
canonical: notCanonical, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
return &ret, nil |
|
|
|
@ -672,10 +716,11 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block |
|
|
|
|
uncle := uncles[args.Index] |
|
|
|
|
blockNumber := rpc.BlockNumber(uncle.Number.Uint64()) |
|
|
|
|
return &Block{ |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &blockNumber, |
|
|
|
|
hash: uncle.Hash(), |
|
|
|
|
header: uncle, |
|
|
|
|
backend: b.backend, |
|
|
|
|
num: &blockNumber, |
|
|
|
|
hash: uncle.Hash(), |
|
|
|
|
header: uncle, |
|
|
|
|
canonical: notCanonical, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -744,6 +789,162 @@ func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteri |
|
|
|
|
return runFilter(ctx, b.backend, filter) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
_, err := b.resolveHeader(ctx) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &Account{ |
|
|
|
|
backend: b.backend, |
|
|
|
|
address: args.Address, |
|
|
|
|
blockNumber: *b.num, |
|
|
|
|
}, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CallData encapsulates arguments to `call` or `estimateGas`.
|
|
|
|
|
// All arguments are optional.
|
|
|
|
|
type CallData struct { |
|
|
|
|
From *common.Address // The Ethereum address the call is from.
|
|
|
|
|
To *common.Address // The Ethereum address the call is to.
|
|
|
|
|
Gas *hexutil.Uint64 // The amount of gas provided for the call.
|
|
|
|
|
GasPrice *hexutil.Big // The price of each unit of gas, in wei.
|
|
|
|
|
Value *hexutil.Big // The value sent along with the call.
|
|
|
|
|
Data *hexutil.Bytes // Any data sent with the call.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CallResult encapsulates the result of an invocation of the `call` accessor.
|
|
|
|
|
type CallResult struct { |
|
|
|
|
data hexutil.Bytes // The return data from the call
|
|
|
|
|
gasUsed hexutil.Uint64 // The amount of gas used
|
|
|
|
|
status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) Data() hexutil.Bytes { |
|
|
|
|
return c.data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) GasUsed() hexutil.Uint64 { |
|
|
|
|
return c.gasUsed |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) Status() hexutil.Uint64 { |
|
|
|
|
return c.status |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
result, gas, failed, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.num, vm.Config{}, 5*time.Second) |
|
|
|
|
status := hexutil.Uint64(1) |
|
|
|
|
if failed { |
|
|
|
|
status = 0 |
|
|
|
|
} |
|
|
|
|
return &CallResult{ |
|
|
|
|
data: hexutil.Bytes(result), |
|
|
|
|
gasUsed: hexutil.Uint64(gas), |
|
|
|
|
status: status, |
|
|
|
|
}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
_, err := b.resolveHeader(ctx) |
|
|
|
|
if err != nil { |
|
|
|
|
return hexutil.Uint64(0), err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.num) |
|
|
|
|
return gas, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Pending struct { |
|
|
|
|
backend *eth.EthAPIBackend |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *Pending) TransactionCount(ctx context.Context) (int32, error) { |
|
|
|
|
txs, err := p.backend.GetPoolTransactions() |
|
|
|
|
return int32(len(txs)), err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *Pending) Transactions(ctx context.Context) (*[]*Transaction, error) { |
|
|
|
|
txs, err := p.backend.GetPoolTransactions() |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ret := make([]*Transaction, 0, len(txs)) |
|
|
|
|
for i, tx := range txs { |
|
|
|
|
ret = append(ret, &Transaction{ |
|
|
|
|
backend: p.backend, |
|
|
|
|
hash: tx.Hash(), |
|
|
|
|
tx: tx, |
|
|
|
|
index: uint64(i), |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
return &ret, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (p *Pending) Account(ctx context.Context, args struct { |
|
|
|
|
Address common.Address |
|
|
|
|
}) *Account { |
|
|
|
|
return &Account{ |
|
|
|
|
backend: p.backend, |
|
|
|
|
address: args.Address, |
|
|
|
|
blockNumber: rpc.PendingBlockNumber, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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, vm.Config{}, 5*time.Second) |
|
|
|
|
status := hexutil.Uint64(1) |
|
|
|
|
if failed { |
|
|
|
|
status = 0 |
|
|
|
|
} |
|
|
|
|
return &CallResult{ |
|
|
|
|
data: hexutil.Bytes(result), |
|
|
|
|
gasUsed: hexutil.Uint64(gas), |
|
|
|
|
status: status, |
|
|
|
|
}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Resolver is the top-level object in the GraphQL hierarchy.
|
|
|
|
|
type Resolver struct { |
|
|
|
|
backend *eth.EthAPIBackend |
|
|
|
@ -757,19 +958,22 @@ func (r *Resolver) Block(ctx context.Context, args struct { |
|
|
|
|
if args.Number != nil { |
|
|
|
|
num := rpc.BlockNumber(uint64(*args.Number)) |
|
|
|
|
block = &Block{ |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
canonical: isCanonical, |
|
|
|
|
} |
|
|
|
|
} else if args.Hash != nil { |
|
|
|
|
block = &Block{ |
|
|
|
|
backend: r.backend, |
|
|
|
|
hash: *args.Hash, |
|
|
|
|
backend: r.backend, |
|
|
|
|
hash: *args.Hash, |
|
|
|
|
canonical: unknown, |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
num := rpc.LatestBlockNumber |
|
|
|
|
block = &Block{ |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
canonical: isCanonical, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -804,27 +1008,16 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { |
|
|
|
|
for i := from; i <= to; i++ { |
|
|
|
|
num := i |
|
|
|
|
ret = append(ret, &Block{ |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
backend: r.backend, |
|
|
|
|
num: &num, |
|
|
|
|
canonical: isCanonical, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
return ret, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Resolver) Account(ctx context.Context, args struct { |
|
|
|
|
Address common.Address |
|
|
|
|
BlockNumber *hexutil.Uint64 |
|
|
|
|
}) *Account { |
|
|
|
|
blockNumber := rpc.LatestBlockNumber |
|
|
|
|
if args.BlockNumber != nil { |
|
|
|
|
blockNumber = rpc.BlockNumber(*args.BlockNumber) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return &Account{ |
|
|
|
|
backend: r.backend, |
|
|
|
|
address: args.Address, |
|
|
|
|
blockNumber: blockNumber, |
|
|
|
|
} |
|
|
|
|
func (r *Resolver) Pending(ctx context.Context) *Pending { |
|
|
|
|
return &Pending{r.backend} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) (*Transaction, error) { |
|
|
|
@ -852,70 +1045,6 @@ func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hex |
|
|
|
|
return hash, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CallData encapsulates arguments to `call` or `estimateGas`.
|
|
|
|
|
// All arguments are optional.
|
|
|
|
|
type CallData struct { |
|
|
|
|
From *common.Address // The Ethereum address the call is from.
|
|
|
|
|
To *common.Address // The Ethereum address the call is to.
|
|
|
|
|
Gas *hexutil.Uint64 // The amount of gas provided for the call.
|
|
|
|
|
GasPrice *hexutil.Big // The price of each unit of gas, in wei.
|
|
|
|
|
Value *hexutil.Big // The value sent along with the call.
|
|
|
|
|
Data *hexutil.Bytes // Any data sent with the call.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// CallResult encapsulates the result of an invocation of the `call` accessor.
|
|
|
|
|
type CallResult struct { |
|
|
|
|
data hexutil.Bytes // The return data from the call
|
|
|
|
|
gasUsed hexutil.Uint64 // The amount of gas used
|
|
|
|
|
status hexutil.Uint64 // The return status of the call - 0 for failure or 1 for success.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) Data() hexutil.Bytes { |
|
|
|
|
return c.data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) GasUsed() hexutil.Uint64 { |
|
|
|
|
return c.gasUsed |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (c *CallResult) Status() hexutil.Uint64 { |
|
|
|
|
return c.status |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Resolver) Call(ctx context.Context, args struct { |
|
|
|
|
Data ethapi.CallArgs |
|
|
|
|
BlockNumber *hexutil.Uint64 |
|
|
|
|
}) (*CallResult, error) { |
|
|
|
|
blockNumber := rpc.LatestBlockNumber |
|
|
|
|
if args.BlockNumber != nil { |
|
|
|
|
blockNumber = rpc.BlockNumber(*args.BlockNumber) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
result, gas, failed, err := ethapi.DoCall(ctx, r.backend, args.Data, blockNumber, vm.Config{}, 5*time.Second) |
|
|
|
|
status := hexutil.Uint64(1) |
|
|
|
|
if failed { |
|
|
|
|
status = 0 |
|
|
|
|
} |
|
|
|
|
return &CallResult{ |
|
|
|
|
data: hexutil.Bytes(result), |
|
|
|
|
gasUsed: hexutil.Uint64(gas), |
|
|
|
|
status: status, |
|
|
|
|
}, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *Resolver) EstimateGas(ctx context.Context, args struct { |
|
|
|
|
Data ethapi.CallArgs |
|
|
|
|
BlockNumber *hexutil.Uint64 |
|
|
|
|
}) (hexutil.Uint64, error) { |
|
|
|
|
blockNumber := rpc.LatestBlockNumber |
|
|
|
|
if args.BlockNumber != nil { |
|
|
|
|
blockNumber = rpc.BlockNumber(*args.BlockNumber) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
gas, err := ethapi.DoEstimateGas(ctx, r.backend, args.Data, blockNumber) |
|
|
|
|
return gas, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FilterCriteria encapsulates the arguments to `logs` on the root resolver object.
|
|
|
|
|
type FilterCriteria struct { |
|
|
|
|
FromBlock *hexutil.Uint64 // beginning of the queried range, nil means genesis block
|
|
|
|
|