diff --git a/ethgraphql/main.go b/ethgraphql/main.go index cd36ab3a92..8e60cb02d7 100644 --- a/ethgraphql/main.go +++ b/ethgraphql/main.go @@ -23,12 +23,14 @@ import ( "net" "net/http" "strconv" + "time" "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/internal/ethapi" "github.com/ethereum/go-ethereum/log" @@ -892,6 +894,96 @@ func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data Hex return Bytes32{hash}, err } +type CallData struct { + From *Address + To *Address + Gas *int32 + GasPrice *BigNum + Value *BigNum + Data *HexBytes + BlockNumber *int32 +} + +type CallResult struct { + data HexBytes + gasUsed int32 + status int32 +} + +func (c *CallResult) Data() HexBytes { + return c.data +} + +func (c *CallResult) GasUsed() int32 { + return c.gasUsed +} + +func (c *CallResult) Status() int32 { + return c.status +} + +func convertCallData(data CallData) (ethapi.CallArgs, rpc.BlockNumber) { + callArgs := ethapi.CallArgs{} + if data.From != nil { + callArgs.From = data.From.Address + } + if data.To != nil { + addr := data.To.Address + callArgs.To = &addr + } + if data.Gas != nil { + callArgs.Gas = hexutil.Uint64(*data.Gas) + } + if data.GasPrice != nil { + callArgs.GasPrice = hexutil.Big(*data.GasPrice.Int) + } + if data.Value != nil { + callArgs.Value = hexutil.Big(*data.Value.Int) + } + if data.Data != nil { + callArgs.Data = data.Data.Bytes + } + + blockNumber := rpc.LatestBlockNumber + if data.BlockNumber != nil { + blockNumber = rpc.BlockNumber(*data.BlockNumber) + } + + return callArgs, blockNumber +} + +func (r *Resolver) Call(ctx context.Context, args struct{ Data CallData }) (*CallResult, error) { + be, err := getBackend(r.node) + if err != nil { + return nil, err + } + + callArgs, blockNumber := convertCallData(args.Data) + + result, gas, failed, err := ethapi.DoCall(ctx, be, callArgs, blockNumber, vm.Config{}, 5*time.Second) + status := int32(1) + if failed { + status = 0 + } + return &CallResult{ + data: HexBytes{result}, + gasUsed: int32(gas), + status: status, + }, err +} + +func (r *Resolver) EstimateGas(ctx context.Context, args struct{ Data CallData }) (int32, error) { + be, err := getBackend(r.node) + if err != nil { + return 0, err + } + + callArgs, blockNumber := convertCallData(args.Data) + + gas, err := ethapi.DoEstimateGas(ctx, be, callArgs, blockNumber) + return int32(gas), err +} + func NewHandler(n *node.Node) (http.Handler, error) { q := Resolver{n} @@ -967,11 +1059,29 @@ func NewHandler(n *node.Node) (http.Handler, error) { transactionAt(index: Int!): Transaction } + input CallData { + from: Address + to: Address + gas: Int + gasPrice: BigNum + value: BigNum + data: HexBytes + blockNumber: Int + } + + type CallResult { + data: HexBytes! + gasUsed: Int! + status: Int! + } + type Query { account(address: Address!, blockNumber: Int): Account! block(number: Int, hash: Bytes32): Block blocks(from: Int!, to: Int): [Block!]! transaction(hash: Bytes32!): Transaction + call(data: CallData!): CallResult + estimateGas(data: CallData!): Int! } type Mutation { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f8933f1e4d..75f638d91e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -617,17 +617,17 @@ type CallArgs struct { Data hexutil.Bytes `json:"data"` } -func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { +func DoCall(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber, vmCfg vm.Config, timeout time.Duration) ([]byte, uint64, bool, error) { defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - state, header, err := s.b.StateAndHeaderByNumber(ctx, blockNr) + state, header, err := b.StateAndHeaderByNumber(ctx, blockNr) if state == nil || err != nil { return nil, 0, false, err } // Set sender address or use a default if none specified addr := args.From if addr == (common.Address{}) { - if wallets := s.b.AccountManager().Wallets(); len(wallets) > 0 { + if wallets := b.AccountManager().Wallets(); len(wallets) > 0 { if accounts := wallets[0].Accounts(); len(accounts) > 0 { addr = accounts[0].Address } @@ -658,7 +658,7 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr defer cancel() // Get a new instance of the EVM. - evm, vmError, err := s.b.GetEVM(ctx, msg, state, header, vmCfg) + evm, vmError, err := b.GetEVM(ctx, msg, state, header, vmCfg) if err != nil { return nil, 0, false, err } @@ -682,13 +682,13 @@ func (s *PublicBlockChainAPI) doCall(ctx context.Context, args CallArgs, blockNr // Call executes the given transaction on the state for the given block number. // It doesn't make and changes in the state/blockchain and is useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Bytes, error) { - result, _, _, err := s.doCall(ctx, args, blockNr, vm.Config{}, 5*time.Second) + result, _, _, err := DoCall(ctx, s.b, args, blockNr, vm.Config{}, 5*time.Second) return (hexutil.Bytes)(result), err } -// EstimateGas returns an estimate of the amount of gas needed to execute the +// DoEstimateGas returns an estimate of the amount of gas needed to execute the // given transaction against the current pending block. -func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { +func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNr rpc.BlockNumber) (hexutil.Uint64, error) { // Binary search the gas requirement, as it may be higher than the amount used var ( lo uint64 = params.TxGas - 1 @@ -698,8 +698,8 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h if uint64(args.Gas) >= params.TxGas { hi = uint64(args.Gas) } else { - // Retrieve the current pending block to act as the gas ceiling - block, err := s.b.BlockByNumber(ctx, rpc.PendingBlockNumber) + // Retrieve the block to act as the gas ceiling + block, err := b.BlockByNumber(ctx, blockNr) if err != nil { return 0, err } @@ -711,7 +711,7 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h executable := func(gas uint64) bool { args.Gas = hexutil.Uint64(gas) - _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{}, 0) + _, _, failed, err := DoCall(ctx, b, args, blockNr, vm.Config{}, 0) if err != nil || failed { return false } @@ -735,6 +735,10 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (h return hexutil.Uint64(hi), nil } +func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (hexutil.Uint64, error) { + return DoEstimateGas(ctx, s.b, args, rpc.PendingBlockNumber) +} + // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value