diff options
Diffstat (limited to 'graphql/graphql.go')
-rw-r--r-- | graphql/graphql.go | 347 |
1 files changed, 238 insertions, 109 deletions
diff --git a/graphql/graphql.go b/graphql/graphql.go index 68d36f997..b16f93e46 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -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 |