diff options
author | Kris Shinn <raggamuffin.music@gmail.com> | 2019-01-21 22:38:13 +0800 |
---|---|---|
committer | Guillaume Ballet <gballet@gmail.com> | 2019-01-21 22:38:13 +0800 |
commit | f91312dbdbb9e04ef578946226e5d8069d5dfd5a (patch) | |
tree | 0f931ae2c506db08c4732e3f0b718a115571924d /graphql | |
parent | 105008b6a121ade656bf63125cecb467e2434d95 (diff) | |
download | go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar.gz go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar.bz2 go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar.lz go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar.xz go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.tar.zst go-tangerine-f91312dbdbb9e04ef578946226e5d8069d5dfd5a.zip |
GraphQL master FF for review (#18445)
* Initial work on a graphql API
* Added receipts, and more transaction fields.
* Finish receipts, add logs
* Add transactionCount to block
* Add types and .
* Update Block type to be compatible with ethql
* Rename nonce to transactionCount in Account, to be compatible with ethql
* Update transaction, receipt and log to match ethql
* Add query operator, for a range of blocks
* Added ommerCount to Block
* Add transactionAt and ommerAt to Block
* Added sendRawTransaction mutation
* Add Call and EstimateGas to graphQL API
* Refactored to use hexutil.Bytes instead of HexBytes
* Replace BigNum with hexutil.Big
* Refactor call and estimateGas to use ethapi struct type
* Replace ethgraphql.Address with common.Address
* Replace ethgraphql.Hash with common.Hash
* Converted most quantities to Long instead of Int
* Add support for logs
* Fix bug in runFilter
* Restructured Transaction to work primarily with headers, so uncle data is reported properly
* Add gasPrice API
* Add protocolVersion API
* Add syncing API
* Moved schema into its own source file
* Move some single use args types into anonymous structs
* Add doc-comments
* Fixed backend fetching to use context
* Added (very) basic tests
* Add documentation to the graphql schema
* Fix reversion for formatting of big numbers
* Correct spelling error
* s/BigInt/Long/
* Update common/types.go
* Fixes in response to review
* Fix lint error
* Updated calls on private functions
* Fix typo in graphql.go
* Rollback ethapi breaking changes for graphql support
Co-Authored-By: Arachnid <arachnid@notdot.net>
Diffstat (limited to 'graphql')
-rw-r--r-- | graphql/grahpql.go | 1104 | ||||
-rw-r--r-- | graphql/graphiql.go | 95 | ||||
-rw-r--r-- | graphql/graphql_test.go | 29 | ||||
-rw-r--r-- | graphql/schema.go | 305 |
4 files changed, 1533 insertions, 0 deletions
diff --git a/graphql/grahpql.go b/graphql/grahpql.go new file mode 100644 index 000000000..1eca78956 --- /dev/null +++ b/graphql/grahpql.go @@ -0,0 +1,1104 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +// Package graphql provides a GraphQL interface to Ethereum node data. +package graphql + +import ( + "context" + "fmt" + "net" + "net/http" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + graphqlgo "github.com/graph-gophers/graphql-go" + "github.com/graph-gophers/graphql-go/relay" +) + +// Account represents an Ethereum account at a particular block. +type Account struct { + backend *eth.EthAPIBackend + address common.Address + blockNumber rpc.BlockNumber +} + +// 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) + return state, err +} + +func (a *Account) Address(ctx context.Context) (common.Address, error) { + return a.address, nil +} + +func (a *Account) Balance(ctx context.Context) (hexutil.Big, error) { + state, err := a.getState(ctx) + if err != nil { + return hexutil.Big{}, err + } + + return hexutil.Big(*state.GetBalance(a.address)), nil +} + +func (a *Account) TransactionCount(ctx context.Context) (hexutil.Uint64, error) { + state, err := a.getState(ctx) + if err != nil { + return 0, err + } + + return hexutil.Uint64(state.GetNonce(a.address)), nil +} + +func (a *Account) Code(ctx context.Context) (hexutil.Bytes, error) { + state, err := a.getState(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + + return hexutil.Bytes(state.GetCode(a.address)), nil +} + +func (a *Account) Storage(ctx context.Context, args struct{ Slot common.Hash }) (common.Hash, error) { + state, err := a.getState(ctx) + if err != nil { + return common.Hash{}, err + } + + return state.GetState(a.address, args.Slot), nil +} + +// Log represents an individual log message. All arguments are mandatory. +type Log struct { + backend *eth.EthAPIBackend + transaction *Transaction + log *types.Log +} + +func (l *Log) Transaction(ctx context.Context) *Transaction { + return l.transaction +} + +func (l *Log) Account(ctx context.Context, args BlockNumberArgs) *Account { + return &Account{ + backend: l.backend, + address: l.log.Address, + blockNumber: args.Number(), + } +} + +func (l *Log) Index(ctx context.Context) int32 { + return int32(l.log.Index) +} + +func (l *Log) Topics(ctx context.Context) []common.Hash { + return l.log.Topics +} + +func (l *Log) Data(ctx context.Context) hexutil.Bytes { + return hexutil.Bytes(l.log.Data) +} + +// Transactionn represents an Ethereum transaction. +// backend and hash are mandatory; all others will be fetched when required. +type Transaction struct { + backend *eth.EthAPIBackend + hash common.Hash + tx *types.Transaction + block *Block + index uint64 +} + +// resolve returns the internal transaction object, fetching it if needed. +func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, error) { + if t.tx == nil { + tx, blockHash, _, index := rawdb.ReadTransaction(t.backend.ChainDb(), t.hash) + if tx != nil { + t.tx = tx + t.block = &Block{ + backend: t.backend, + hash: blockHash, + } + t.index = index + } else { + t.tx = t.backend.GetPoolTransaction(t.hash) + } + } + return t.tx, nil +} + +func (tx *Transaction) Hash(ctx context.Context) common.Hash { + return tx.hash +} + +func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return hexutil.Bytes{}, err + } + return hexutil.Bytes(tx.Data()), nil +} + +func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return 0, err + } + return hexutil.Uint64(tx.Gas()), nil +} + +func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return hexutil.Big{}, err + } + return hexutil.Big(*tx.GasPrice()), nil +} + +func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return hexutil.Big{}, err + } + return hexutil.Big(*tx.Value()), nil +} + +func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return 0, err + } + return hexutil.Uint64(tx.Nonce()), nil +} + +func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return nil, err + } + + to := tx.To() + if to == nil { + return nil, nil + } + + return &Account{ + backend: t.backend, + address: *to, + blockNumber: args.Number(), + }, nil +} + +func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, error) { + tx, err := t.resolve(ctx) + if err != nil || tx == nil { + return nil, err + } + + var signer types.Signer = types.FrontierSigner{} + if tx.Protected() { + signer = types.NewEIP155Signer(tx.ChainId()) + } + from, _ := types.Sender(signer, tx) + + return &Account{ + backend: t.backend, + address: from, + blockNumber: args.Number(), + }, nil +} + +func (t *Transaction) Block(ctx context.Context) (*Block, error) { + if _, err := t.resolve(ctx); err != nil { + return nil, err + } + return t.block, nil +} + +func (t *Transaction) Index(ctx context.Context) (*int32, error) { + if _, err := t.resolve(ctx); err != nil { + return nil, err + } + if t.block == nil { + return nil, nil + } + index := int32(t.index) + return &index, nil +} + +// getReceipt returns the receipt associated with this transaction, if any. +func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { + if _, err := t.resolve(ctx); err != nil { + return nil, err + } + + if t.block == nil { + return nil, nil + } + + receipts, err := t.block.resolveReceipts(ctx) + if err != nil { + return nil, err + } + + return receipts[t.index], nil +} + +func (t *Transaction) Status(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + + ret := hexutil.Uint64(receipt.Status) + return &ret, nil +} + +func (t *Transaction) GasUsed(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + + ret := hexutil.Uint64(receipt.GasUsed) + return &ret, nil +} + +func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + + ret := hexutil.Uint64(receipt.CumulativeGasUsed) + return &ret, nil +} + +func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) (*Account, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil || receipt.ContractAddress == (common.Address{}) { + return nil, err + } + + return &Account{ + backend: t.backend, + address: receipt.ContractAddress, + blockNumber: args.Number(), + }, nil +} + +func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + + ret := make([]*Log, 0, len(receipt.Logs)) + for _, log := range receipt.Logs { + ret = append(ret, &Log{ + backend: t.backend, + transaction: t, + log: log, + }) + } + return &ret, nil +} + +// Block represennts 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 +} + +// resolve returns the internal Block object representing this block, fetching +// it if necessary. +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.GetBlock(ctx, b.hash) + } else { + b.block, err = b.backend.BlockByNumber(ctx, *b.num) + } + if b.block != nil { + b.header = b.block.Header() + } + return b.block, err +} + +// resolveHeader returns the internal Header object for this block, fetching it +// 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.header == nil { + if _, err := b.resolve(ctx); err != nil { + return nil, err + } + } + return b.header, nil +} + +// resolveReceipts returns the list of receipts for this block, fetching them +// if necessary. +func (b *Block) resolveReceipts(ctx context.Context) ([]*types.Receipt, error) { + if b.receipts == nil { + hash := b.hash + if hash == (common.Hash{}) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + hash = header.Hash() + } + + receipts, err := b.backend.GetReceipts(ctx, hash) + if err != nil { + return nil, err + } + b.receipts = []*types.Receipt(receipts) + } + return b.receipts, nil +} + +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 + } + return hexutil.Uint64(*b.num), nil +} + +func (b *Block) Hash(ctx context.Context) (common.Hash, error) { + if b.hash == (common.Hash{}) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + b.hash = header.Hash() + } + return b.hash, nil +} + +func (b *Block) GasLimit(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(header.GasLimit), nil +} + +func (b *Block) GasUsed(ctx context.Context) (hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return 0, err + } + return hexutil.Uint64(header.GasUsed), nil +} + +func (b *Block) Parent(ctx context.Context) (*Block, error) { + // If the block hasn't been fetched, and we'll need it, fetch it. + if b.num == nil && b.hash != (common.Hash{}) && b.header == nil { + if _, err := b.resolve(ctx); err != nil { + return nil, err + } + } + + 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, + }, nil + } else if b.num != nil && *b.num != 0 { + num := *b.num - 1 + return &Block{ + backend: b.backend, + num: &num, + }, nil + } + return nil, nil +} + +func (b *Block) Difficulty(ctx context.Context) (hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Big{}, err + } + return hexutil.Big(*header.Difficulty), nil +} + +func (b *Block) Timestamp(ctx context.Context) (hexutil.Big, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Big{}, err + } + return hexutil.Big(*header.Time), nil +} + +func (b *Block) Nonce(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return hexutil.Bytes(header.Nonce[:]), nil +} + +func (b *Block) MixHash(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.MixDigest, nil +} + +func (b *Block) TransactionsRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.TxHash, nil +} + +func (b *Block) StateRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.Root, nil +} + +func (b *Block) ReceiptsRoot(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.ReceiptHash, nil +} + +func (b *Block) OmmerHash(ctx context.Context) (common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return common.Hash{}, err + } + return header.UncleHash, nil +} + +func (b *Block) OmmerCount(ctx context.Context) (*int32, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + count := int32(len(block.Uncles())) + return &count, err +} + +func (b *Block) Ommers(ctx context.Context) (*[]*Block, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + + ret := make([]*Block, 0, len(block.Uncles())) + 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, + }) + } + return &ret, nil +} + +func (b *Block) ExtraData(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return hexutil.Bytes(header.Extra), nil +} + +func (b *Block) LogsBloom(ctx context.Context) (hexutil.Bytes, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Bytes{}, err + } + return hexutil.Bytes(header.Bloom.Bytes()), nil +} + +func (b *Block) TotalDifficulty(ctx context.Context) (hexutil.Big, error) { + h := b.hash + if h == (common.Hash{}) { + header, err := b.resolveHeader(ctx) + if err != nil { + return hexutil.Big{}, err + } + h = header.Hash() + } + + return hexutil.Big(*b.backend.GetTd(h)), nil +} + +// BlockNumberArgs encapsulates arguments to accessors that specify a block number. +type BlockNumberArgs struct { + Block *hexutil.Uint64 +} + +// Number returns the provided block number, or rpc.LatestBlockNumber if none +// was provided. +func (a BlockNumberArgs) Number() rpc.BlockNumber { + if a.Block != nil { + return rpc.BlockNumber(*a.Block) + } + return rpc.LatestBlockNumber +} + +func (b *Block) Miner(ctx context.Context, args BlockNumberArgs) (*Account, error) { + block, err := b.resolve(ctx) + if err != nil { + return nil, err + } + + return &Account{ + backend: b.backend, + address: block.Coinbase(), + blockNumber: args.Number(), + }, nil +} + +func (b *Block) TransactionCount(ctx context.Context) (*int32, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + count := int32(len(block.Transactions())) + return &count, err +} + +func (b *Block) Transactions(ctx context.Context) (*[]*Transaction, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + + ret := make([]*Transaction, 0, len(block.Transactions())) + for i, tx := range block.Transactions() { + ret = append(ret, &Transaction{ + backend: b.backend, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(i), + }) + } + return &ret, nil +} + +func (b *Block) TransactionAt(ctx context.Context, args struct{ Index int32 }) (*Transaction, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + + txes := block.Transactions() + if args.Index < 0 || int(args.Index) >= len(txes) { + return nil, nil + } + + tx := txes[args.Index] + return &Transaction{ + backend: b.backend, + hash: tx.Hash(), + tx: tx, + block: b, + index: uint64(args.Index), + }, nil +} + +func (b *Block) OmmerAt(ctx context.Context, args struct{ Index int32 }) (*Block, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + + uncles := block.Uncles() + if args.Index < 0 || int(args.Index) >= len(uncles) { + return nil, nil + } + + uncle := uncles[args.Index] + blockNumber := rpc.BlockNumber(uncle.Number.Uint64()) + return &Block{ + backend: b.backend, + num: &blockNumber, + hash: uncle.Hash(), + header: uncle, + }, nil +} + +// BlockFilterCriteria encapsulates criteria passed to a `logs` accessor inside +// a block. +type BlockFilterCriteria struct { + Addresses *[]common.Address // restricts matches to events created by specific contracts + + // The Topic list restricts matches to particular event topics. Each event has a list + // of topics. Topics matches a prefix of that list. An empty element slice 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 *[][]common.Hash +} + +// runFilter accepts a filter and executes it, returning all its results as +// `Log` objects. +func runFilter(ctx context.Context, be *eth.EthAPIBackend, filter *filters.Filter) ([]*Log, error) { + logs, err := filter.Logs(ctx) + if err != nil || logs == nil { + return nil, err + } + + ret := make([]*Log, 0, len(logs)) + for _, log := range logs { + ret = append(ret, &Log{ + backend: be, + transaction: &Transaction{backend: be, hash: log.TxHash}, + log: log, + }) + } + return ret, nil +} + +func (b *Block) Logs(ctx context.Context, args struct{ Filter BlockFilterCriteria }) ([]*Log, error) { + var addresses []common.Address + if args.Filter.Addresses != nil { + addresses = *args.Filter.Addresses + } + + var topics [][]common.Hash + if args.Filter.Topics != nil { + topics = *args.Filter.Topics + } + + hash := b.hash + if hash == (common.Hash{}) { + block, err := b.resolve(ctx) + if err != nil { + return nil, err + } + hash = block.Hash() + } + + // Construct the range filter + filter := filters.NewBlockFilter(b.backend, hash, addresses, topics) + + // Run the filter and return all the logs + return runFilter(ctx, b.backend, filter) +} + +// Resolver is the top-level object in the GraphQL hierarchy. +type Resolver struct { + backend *eth.EthAPIBackend +} + +func (r *Resolver) Block(ctx context.Context, args struct { + Number *hexutil.Uint64 + Hash *common.Hash +}) (*Block, error) { + var block *Block + if args.Number != nil { + num := rpc.BlockNumber(uint64(*args.Number)) + block = &Block{ + backend: r.backend, + num: &num, + } + } else if args.Hash != nil { + block = &Block{ + backend: r.backend, + hash: *args.Hash, + } + } else { + num := rpc.LatestBlockNumber + block = &Block{ + backend: r.backend, + num: &num, + } + } + + // Resolve the block; if it doesn't exist, return nil. + b, err := block.resolve(ctx) + if err != nil { + return nil, err + } else if b == nil { + return nil, nil + } + return block, nil +} + +func (r *Resolver) Blocks(ctx context.Context, args struct { + From hexutil.Uint64 + To *hexutil.Uint64 +}) ([]*Block, error) { + from := rpc.BlockNumber(args.From) + + var to rpc.BlockNumber + if args.To != nil { + to = rpc.BlockNumber(*args.To) + } else { + to = rpc.BlockNumber(r.backend.CurrentBlock().Number().Int64()) + } + + if to < from { + return []*Block{}, nil + } + + ret := make([]*Block, 0, to-from+1) + for i := from; i <= to; i++ { + num := i + ret = append(ret, &Block{ + backend: r.backend, + num: &num, + }) + } + 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) Transaction(ctx context.Context, args struct{ Hash common.Hash }) (*Transaction, error) { + tx := &Transaction{ + backend: r.backend, + hash: args.Hash, + } + + // Resolve the transaction; if it doesn't exist, return nil. + t, err := tx.resolve(ctx) + if err != nil { + return nil, err + } else if t == nil { + return nil, nil + } + return tx, nil +} + +func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(args.Data, tx); err != nil { + return common.Hash{}, err + } + hash, err := ethapi.SubmitTransaction(ctx, r.backend, tx) + 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 +} + +// FilterCritera 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 + ToBlock *hexutil.Uint64 // end of the range, nil means latest block + Addresses *[]common.Address // restricts matches to events created by specific contracts + + // The Topic list restricts matches to particular event topics. Each event has a list + // of topics. Topics matches a prefix of that list. An empty element slice 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 *[][]common.Hash +} + +func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria }) ([]*Log, error) { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if args.Filter.FromBlock != nil { + begin = int64(*args.Filter.FromBlock) + } + end := rpc.LatestBlockNumber.Int64() + if args.Filter.ToBlock != nil { + end = int64(*args.Filter.ToBlock) + } + + var addresses []common.Address + if args.Filter.Addresses != nil { + addresses = *args.Filter.Addresses + } + + var topics [][]common.Hash + if args.Filter.Topics != nil { + topics = *args.Filter.Topics + } + + // Construct the range filter + filter := filters.NewRangeFilter(filters.Backend(r.backend), begin, end, addresses, topics) + + return runFilter(ctx, r.backend, filter) +} + +func (r *Resolver) GasPrice(ctx context.Context) (hexutil.Big, error) { + price, err := r.backend.SuggestPrice(ctx) + return hexutil.Big(*price), err +} + +func (r *Resolver) ProtocolVersion(ctx context.Context) (int32, error) { + return int32(r.backend.ProtocolVersion()), nil +} + +// SyncState represents the synchronisation status returned from the `syncing` accessor. +type SyncState struct { + progress ethereum.SyncProgress +} + +func (s *SyncState) StartingBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.StartingBlock) +} + +func (s *SyncState) CurrentBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.CurrentBlock) +} + +func (s *SyncState) HighestBlock() hexutil.Uint64 { + return hexutil.Uint64(s.progress.HighestBlock) +} + +func (s *SyncState) PulledStates() *hexutil.Uint64 { + ret := hexutil.Uint64(s.progress.PulledStates) + return &ret +} + +func (s *SyncState) KnownStates() *hexutil.Uint64 { + ret := hexutil.Uint64(s.progress.KnownStates) + return &ret +} + +// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not +// yet received the latest block headers from its pears. In case it is synchronizing: +// - startingBlock: block number this node started to synchronise from +// - currentBlock: block number this node is currently importing +// - highestBlock: block number of the highest block header this node has received from peers +// - pulledStates: number of state entries processed until now +// - knownStates: number of known state entries that still need to be pulled +func (r *Resolver) Syncing() (*SyncState, error) { + progress := r.backend.Downloader().Progress() + + // Return not syncing if the synchronisation already completed + if progress.CurrentBlock >= progress.HighestBlock { + return nil, nil + } + // Otherwise gather the block sync stats + return &SyncState{progress}, nil +} + +// NewHandler returns a new `http.Handler` that will answer GraphQL queries. +// It additionally exports an interactive query browser on the / endpoint. +func NewHandler(be *eth.EthAPIBackend) (http.Handler, error) { + q := Resolver{be} + + s, err := graphqlgo.ParseSchema(schema, &q) + if err != nil { + return nil, err + } + h := &relay.Handler{Schema: s} + + mux := http.NewServeMux() + mux.Handle("/", GraphiQL{}) + mux.Handle("/graphql", h) + mux.Handle("/graphql/", h) + return mux, nil +} + +// Service encapsulates a GraphQL service. +type Service struct { + endpoint string // The host:port endpoint for this service. + cors []string // Allowed CORS domains + vhosts []string // Recognised vhosts + timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests. + backend *eth.EthAPIBackend // The backend that queries will operate onn. + handler http.Handler // The `http.Handler` used to answer queries. + listener net.Listener // The listening socket. +} + +// Protocols returns the list of protocols exported by this service. +func (s *Service) Protocols() []p2p.Protocol { return nil } + +// APIs returns the list of APIs exported by this service. +func (s *Service) APIs() []rpc.API { return nil } + +// Start is called after all services have been constructed and the networking +// layer was also initialized to spawn any goroutines required by the service. +func (s *Service) Start(server *p2p.Server) error { + var err error + s.handler, err = NewHandler(s.backend) + if err != nil { + return err + } + + if s.listener, err = net.Listen("tcp", s.endpoint); err != nil { + return err + } + + go rpc.NewHTTPServer(s.cors, s.vhosts, s.timeouts, s.handler).Serve(s.listener) + log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.endpoint)) + return nil +} + +// Stop terminates all goroutines belonging to the service, blocking until they +// are all terminated. +func (s *Service) Stop() error { + if s.listener != nil { + s.listener.Close() + s.listener = nil + log.Info("GraphQL endpoint closed", "url", fmt.Sprintf("http://%s", s.endpoint)) + } + return nil +} + +// NewService constructs a new service instance. +func NewService(backend *eth.EthAPIBackend, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) (*Service, error) { + return &Service{ + endpoint: endpoint, + cors: cors, + vhosts: vhosts, + timeouts: timeouts, + backend: backend, + }, nil +} + +// RegisterGraphQLService is a utility function to construct a new service and register it against a node. +func RegisterGraphQLService(stack *node.Node, endpoint string, cors, vhosts []string, timeouts rpc.HTTPTimeouts) error { + return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + var ethereum *eth.Ethereum + if err := ctx.Service(ðereum); err != nil { + return nil, err + } + return NewService(ethereum.APIBackend, endpoint, cors, vhosts, timeouts) + }) +} diff --git a/graphql/graphiql.go b/graphql/graphiql.go new file mode 100644 index 000000000..6d9dda3e8 --- /dev/null +++ b/graphql/graphiql.go @@ -0,0 +1,95 @@ +// The MIT License (MIT) +// +// Copyright (c) 2016 Muhammed Thanish +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package graphql + +import ( + "bytes" + "fmt" + "net/http" +) + +// GraphiQL is an in-browser IDE for exploring GraphiQL APIs. +// This handler returns GraphiQL when requested. +// +// For more information, see https://github.com/graphql/graphiql. +type GraphiQL struct{} + +func respond(w http.ResponseWriter, body []byte, code int) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(code) + _, _ = w.Write(body) +} + +func errorJSON(msg string) []byte { + buf := bytes.Buffer{} + fmt.Fprintf(&buf, `{"error": "%s"}`, msg) + return buf.Bytes() +} + +func (h GraphiQL) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + respond(w, errorJSON("only GET requests are supported"), http.StatusMethodNotAllowed) + return + } + + w.Write(graphiql) +} + +var graphiql = []byte(` +<!DOCTYPE html> +<html> + <head> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.css"/> + <script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.3/fetch.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.11.11/graphiql.min.js"></script> + </head> + <body style="width: 100%; height: 100%; margin: 0; overflow: hidden;"> + <div id="graphiql" style="height: 100vh;">Loading...</div> + <script> + function fetchGQL(params) { + return fetch("/graphql", { + method: "post", + body: JSON.stringify(params), + credentials: "include", + }).then(function (resp) { + return resp.text(); + }).then(function (body) { + try { + return JSON.parse(body); + } catch (error) { + return body; + } + }); + } + + ReactDOM.render( + React.createElement(GraphiQL, {fetcher: fetchGQL}), + document.getElementById("graphiql") + ) + </script> + </body> +</html> +`) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go new file mode 100644 index 000000000..d63418398 --- /dev/null +++ b/graphql/graphql_test.go @@ -0,0 +1,29 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package graphql + +import ( + "testing" +) + +func TestBuildSchema(t *testing.T) { + // Make sure the schema can be parsed and matched up to the object model. + _, err := NewHandler(nil) + if err != nil { + t.Errorf("Could not construct GraphQL handler: %v", err) + } +} diff --git a/graphql/schema.go b/graphql/schema.go new file mode 100644 index 000000000..c1ba87d2d --- /dev/null +++ b/graphql/schema.go @@ -0,0 +1,305 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package graphql + +const schema string = ` + # Bytes32 is a 32 byte binary string, represented as 0x-prefixed hexadecimal. + scalar Bytes32 + # 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. + 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 + # 0x-prefixed hexadecimal. + scalar BigInt + # Long is a 64 bit unsigned integer. + scalar Long + + schema { + query: Query + mutation: Mutation + } + + # Account is an Ethereum account at a particular block. + type Account { + # Address is the address owning the account. + address: Address! + # Balance is the balance of the account, in wei. + balance: BigInt! + # TransactionCount is the number of transactions sent from this account, + # or in the case of a contract, the number of contracts created. Otherwise + # known as the nonce. + transactionCount: Long! + # Code contains the smart contract code for this account, if the account + # is a (non-self-destructed) contract. + code: Bytes! + # Storage provides access to the storage of a contract account, indexed + # by its 32 byte slot identifier. + storage(slot: Bytes32!): Bytes32! + } + + # Log is an Ethereum event log. + type Log { + # Index is the index of this log in the block. + index: Int! + # Account is the account which generated this log - this will always + # be a contract account. + account(block: Long): Account! + # Topics is a list of 0-4 indexed topics for the log. + topics: [Bytes32!]! + # Data is unindexed data for this log. + data: Bytes! + # Transaction is the transaction that generated this log entry. + transaction: Transaction! + } + + # Transaction is an Ethereum transaction. + type Transaction { + # Hash is the hash of this transaction. + hash: Bytes32! + # 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. + index: Int + # From is the account that sent this transaction - this will always be + # an externally owned account. + from(block: Long): Account! + # To is the account the transaction was sent to. This is null for + # contract-creating transactions. + to(block: Long): Account + # Value is the value, in wei, sent along with this transaction. + value: BigInt! + # GasPrice is the price offered to miners for gas, in wei per unit. + gasPrice: BigInt! + # Gas is the maximum amount of gas this transaction can consume. + gas: Long! + # InputData is the data supplied to the target of the transaction. + inputData: Bytes! + # Block is the block this transaction was mined in. This will be null if + # the transaction has not yet been mined. + block: Block + + # Status is the return status of the transaction. This will be 1 if the + # transaction succeeded, or 0 if it failed (due to a revert, or due to + # running out of gas). If the transaction has not yet been mined, this + # field will be null. + status: Long + # GasUsed is the amount of gas that was used processing this transaction. + # If the transaction has not yet been mined, this field will be null. + gasUsed: Long + # CumulativeGasUsed is the total gas used in the block up to and including + # this transaction. If the transaction has not yet been mined, this field + # will be null. + cumulativeGasUsed: Long + # CreatedContract is the account that was created by a contract creation + # transaction. If the transaction was not a contract creation transaction, + # or it has not yet been mined, this field will be null. + createdContract(block: Long): Account + # Logs is a list of log entries emitted by this transaction. If the + # transaction has not yet been mined, this field will be null. + logs: [Log!] + } + + # BlockFilterCriteria encapsulates log filter criteria for a filter applied + # to a single block. + input BlockFilterCriteria { + # Addresses is list of addresses that are of interest. If this list is + # 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 + topics: [[Bytes32!]!] + } + + # Block is an Ethereum block. + type Block { + # Number is the number of this block, starting at 0 for the genesis block. + number: Long! + # Hash is the block hash of this block. + hash: Bytes32! + # Parent is the parent block of this block. + parent: Block + # Nonce is the block nonce, an 8 byte sequence determined by the miner. + nonce: Bytes! + # TransactionsRoot is the keccak256 hash of the root of the trie of transactions in this block. + transactionsRoot: Bytes32! + # TransactionCount is the number of transactions in this block. if + # transactions are not available for this block, this field will be null. + transactionCount: Int + # StateRoot is the keccak256 hash of the state trie after this block was processed. + stateRoot: Bytes32! + # ReceiptsRoot is the keccak256 hash of the trie of transaction receipts in this block. + receiptsRoot: Bytes32! + # Miner is the account that mined this block. + miner(block: Long): Account! + # ExtraData is an arbitrary data field supplied by the miner. + extraData: Bytes! + # GasLimit is the maximum amount of gas that was available to transactions in this block. + gasLimit: Long! + # GasUsed is the amount of gas that was used executing transactions in this block. + gasUsed: Long! + # Timestamp is the unix timestamp at which this block was mined. + timestamp: BigInt! + # LogsBloom is a bloom filter that can be used to check if a block may + # contain log entries matching a filter. + logsBloom: Bytes! + # MixHash is the hash that was used as an input to the PoW process. + mixHash: Bytes32! + # Difficulty is a measure of the difficulty of mining this block. + difficulty: BigInt! + # TotalDifficulty is the sum of all difficulty values up to and including + # this block. + totalDifficulty: BigInt! + # OmmerCount is the number of ommers (AKA uncles) associated with this + # block. If ommers are unavailable, this field will be null. + ommerCount: Int + # Ommers is a list of ommer (AKA uncle) blocks associated with this block. + # If ommers are unavailable, this field will be null. Depending on your + # node, the transactions, transactionAt, transactionCount, ommers, + # ommerCount and ommerAt fields may not be available on any ommer blocks. + ommers: [Block] + # OmmerAt returns the ommer (AKA uncle) at the specified index. If ommers + # are unavailable, or the index is out of bounds, this field will be null. + ommerAt(index: Int!): Block + # OmmerHash is the keccak256 hash of all the ommers (AKA uncles) + # associated with this block. + ommerHash: Bytes32! + # Transactions is a list of transactions associated with this block. If + # transactions are unavailable for this block, this field will be null. + transactions: [Transaction!] + # TransactionAt returns the transaction at the specified index. If + # transactions are unavailable for this block, or if the index is out of + # bounds, this field will be null. + transactionAt(index: Int!): Transaction + # Logs returns a filtered set of logs from this block. + logs(filter: BlockFilterCriteria!): [Log!]! + } + + # CallData represents the data associated with a local contract call. + # All fields are optional. + input CallData { + # From is the address making the call. + from: Address + # To is the address the call is sent to. + to: Address + # Gas is the amount of gas sent with the call. + gas: Long + # GasPrice is the price, in wei, offered for each unit of gas. + gasPrice: BigInt + # Value is the value, in wei, sent along with the call. + value: BigInt + # Data is the data sent to the callee. + data: Bytes + } + + # CallResult is the result of a local call operationn. + type CallResult { + # Data is the return data of the called contract. + data: Bytes! + # GasUsed is the amount of gas used by the call, after any refunds. + gasUsed: Long! + # Status is the result of the call - 1 for success or 0 for failure. + status: Long! + } + + # FilterCriteria encapsulates log filter criteria for searching log entries. + input FilterCriteria { + # FromBlock is the block at which to start searching, inclusive. Defaults + # to the latest block if not supplied. + fromBlock: Long + # ToBlock is the block at which to stop searching, inclusive. Defaults + # to the latest block if not supplied. + toBlock: Long + # Addresses is a list of addresses that are of interest. If this list is + # 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 + topics: [[Bytes32!]!] + } + + # SyncState contains the current synchronisation state of the client. + type SyncState{ + # StartingBlock is the block number at which synchronisation started. + startingBlock: Long! + # CurrentBlock is the point at which synchronisation has presently reached. + currentBlock: Long! + # HighestBlock is the latest known block number. + highestBlock: Long! + # PulledStates is the number of state entries fetched so far, or null + # if this is not known or not relevant. + pulledStates: Long + # KnownStates is the number of states the node knows of so far, or null + # if this is not known or not relevant. + knownStates: 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!]! + # 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 + # ensure a transaction is mined in a timely fashion. + gasPrice: BigInt! + # ProtocolVersion returns the current wire protocol version number. + protocolVersion: Int! + # Syncing returns information on the current synchronisation state. + syncing: SyncState + } + + type Mutation { + # SendRawTransaction sends an RLP-encoded transaction to the network. + sendRawTransaction(data: Bytes!): Bytes32! + } +` |