aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Johnson <arachnid@notdot.net>2019-03-18 14:24:43 +0800
committerGuillaume Ballet <gballet@gmail.com>2019-03-18 14:24:43 +0800
commitacebccc3bf8198910a3f5848a7b60e5a14858a9e (patch)
tree14f7b7a383d949c767b3c09cf91ac31cfa2d453b
parent6e401792ce09b5e998141030d1f715981a804a90 (diff)
downloadgo-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar.gz
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar.bz2
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar.lz
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar.xz
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.tar.zst
go-tangerine-acebccc3bf8198910a3f5848a7b60e5a14858a9e.zip
graphql: Updates to graphql support to match EIP1767 (#19238)
Updates to match EIP1767
-rw-r--r--graphql/graphql.go347
-rw-r--r--graphql/schema.go79
2 files changed, 285 insertions, 141 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
diff --git a/graphql/schema.go b/graphql/schema.go
index c1ba87d2d..e266e429e 100644
--- a/graphql/schema.go
+++ b/graphql/schema.go
@@ -22,6 +22,7 @@ const schema string = `
# Address is a 20 byte Ethereum address, represented as 0x-prefixed hexadecimal.
scalar Address
# Bytes is an arbitrary length binary string, represented as 0x-prefixed hexadecimal.
+ # An empty byte string is represented as '0x'. Byte strings must have an even number of hexadecimal nybbles.
scalar Bytes
# BigInt is a large integer. Input is accepted as either a JSON number or as a string.
# Strings may be either decimal or 0x-prefixed hexadecimal. Output values are all
@@ -75,7 +76,7 @@ const schema string = `
# Nonce is the nonce of the account this transaction was generated with.
nonce: Long!
# Index is the index of this transaction in the parent block. This will
- # be null if the transaction has not yet beenn mined.
+ # be null if the transaction has not yet been mined.
index: Int
# From is the account that sent this transaction - this will always be
# an externally owned account.
@@ -123,16 +124,16 @@ const schema string = `
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
- # of topics. Topics matches a prefix of that list. An empty element array matches any
- # topic. Non-empty elements represent an alternative that matches any of the
- # contained topics.
- #
- # Examples:
- # - [] or nil matches any topic list
- # - [[A]] matches topic A in first position
- # - [[], [B]] matches any topic in first position, B in second position
- # - [[A], [B]] matches topic A in first position, B in second position
- # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
+ # of topics. Topics matches a prefix of that list. An empty element array matches any
+ # topic. Non-empty elements represent an alternative that matches any of the
+ # contained topics.
+ #
+ # Examples:
+ # - [] or nil matches any topic list
+ # - [[A]] matches topic A in first position
+ # - [[], [B]] matches any topic in first position, B in second position
+ # - [[A], [B]] matches topic A in first position, B in second position
+ # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
@@ -198,6 +199,13 @@ const schema string = `
transactionAt(index: Int!): Transaction
# Logs returns a filtered set of logs from this block.
logs(filter: BlockFilterCriteria!): [Log!]!
+ # Account fetches an Ethereum account at the current block's state.
+ account(address: Address!): Account!
+ # Call executes a local call operation at the current block's state.
+ call(data: CallData!): CallResult
+ # EstimateGas estimates the amount of gas that will be required for
+ # successful execution of a transaction at the current block's state.
+ estimateGas(data: CallData!): Long!
}
# CallData represents the data associated with a local contract call.
@@ -217,7 +225,7 @@ const schema string = `
data: Bytes
}
- # CallResult is the result of a local call operationn.
+ # CallResult is the result of a local call operation.
type CallResult {
# Data is the return data of the called contract.
data: Bytes!
@@ -239,16 +247,16 @@ const schema string = `
# empty, results will not be filtered by address.
addresses: [Address!]
# Topics list restricts matches to particular event topics. Each event has a list
- # of topics. Topics matches a prefix of that list. An empty element array matches any
- # topic. Non-empty elements represent an alternative that matches any of the
- # contained topics.
- #
- # Examples:
- # - [] or nil matches any topic list
- # - [[A]] matches topic A in first position
- # - [[], [B]] matches any topic in first position, B in second position
- # - [[A], [B]] matches topic A in first position, B in second position
- # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
+ # of topics. Topics matches a prefix of that list. An empty element array matches any
+ # topic. Non-empty elements represent an alternative that matches any of the
+ # contained topics.
+ #
+ # Examples:
+ # - [] or nil matches any topic list
+ # - [[A]] matches topic A in first position
+ # - [[], [B]] matches any topic in first position, B in second position
+ # - [[A], [B]] matches topic A in first position, B in second position
+ # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position
topics: [[Bytes32!]!]
}
@@ -268,25 +276,32 @@ const schema string = `
knownStates: Long
}
+ # Pending represents the current pending state.
+ type Pending {
+ # TransactionCount is the number of transactions in the pending state.
+ transactionCount: Int!
+ # Transactions is a list of transactions in the current pending state.
+ transactions: [Transaction!]
+ # Account fetches an Ethereum account for the pending state.
+ account(address: Address!): Account!
+ # Call executes a local call operation for the pending state.
+ call(data: CallData!): CallResult
+ # EstimateGas estimates the amount of gas that will be required for
+ # successful execution of a transaction for the pending state.
+ estimateGas(data: CallData!): Long!
+ }
+
type Query {
- # Account fetches an Ethereum account at the specified block number.
- # If blockNumber is not provided, it defaults to the most recent block.
- account(address: Address!, blockNumber: Long): Account!
# Block fetches an Ethereum block by number or by hash. If neither is
# supplied, the most recent known block is returned.
block(number: Long, hash: Bytes32): Block
# Blocks returns all the blocks between two numbers, inclusive. If
# to is not supplied, it defaults to the most recent known block.
blocks(from: Long!, to: Long): [Block!]!
+ # Pending returns the current pending state.
+ pending: Pending!
# Transaction returns a transaction specified by its hash.
transaction(hash: Bytes32!): Transaction
- # Call executes a local call operation. If blockNumber is not specified,
- # it defaults to the most recent known block.
- call(data: CallData!, blockNumber: Long): CallResult
- # EstimateGas estimates the amount of gas that will be required for
- # successful execution of a transaction. If blockNumber is not specified,
- # it defaults ot the most recent known block.
- estimateGas(data: CallData!, blockNumber: Long): Long!
# Logs returns log entries matching the provided filter.
logs(filter: FilterCriteria!): [Log!]!
# GasPrice returns the node's estimate of a gas price sufficient to