mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete ethclient directory
This commit is contained in:
parent
b4c9f356e4
commit
b4c66584a6
5 changed files with 0 additions and 2428 deletions
|
|
@ -1,716 +0,0 @@
|
|||
// Copyright 2016 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 ethclient provides a client for the Ethereum RPC API.
|
||||
package ethclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Client defines typed wrappers for the Ethereum RPC API.
|
||||
type Client struct {
|
||||
c *rpc.Client
|
||||
}
|
||||
|
||||
// Dial connects a client to the given URL.
|
||||
func Dial(rawurl string) (*Client, error) {
|
||||
return DialContext(context.Background(), rawurl)
|
||||
}
|
||||
|
||||
// DialContext connects a client to the given URL with context.
|
||||
func DialContext(ctx context.Context, rawurl string) (*Client, error) {
|
||||
c, err := rpc.DialContext(ctx, rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClient(c), nil
|
||||
}
|
||||
|
||||
// NewClient creates a client that uses the given RPC client.
|
||||
func NewClient(c *rpc.Client) *Client {
|
||||
return &Client{c}
|
||||
}
|
||||
|
||||
// Close closes the underlying RPC connection.
|
||||
func (ec *Client) Close() {
|
||||
ec.c.Close()
|
||||
}
|
||||
|
||||
// Client gets the underlying RPC client.
|
||||
func (ec *Client) Client() *rpc.Client {
|
||||
return ec.c
|
||||
}
|
||||
|
||||
// Blockchain Access
|
||||
|
||||
// ChainID retrieves the current chain ID for transaction replay protection.
|
||||
func (ec *Client) ChainID(ctx context.Context) (*big.Int, error) {
|
||||
var result hexutil.Big
|
||||
err := ec.c.CallContext(ctx, &result, "eth_chainId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*big.Int)(&result), err
|
||||
}
|
||||
|
||||
// BlockByHash returns the given full block.
|
||||
//
|
||||
// Note that loading full blocks requires two requests. Use HeaderByHash
|
||||
// if you don't need all transactions or uncle headers.
|
||||
func (ec *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
return ec.getBlock(ctx, "eth_getBlockByHash", hash, true)
|
||||
}
|
||||
|
||||
// BlockByNumber returns a block from the current canonical chain. If number is nil, the
|
||||
// latest known block is returned.
|
||||
//
|
||||
// Note that loading full blocks requires two requests. Use HeaderByNumber
|
||||
// if you don't need all transactions or uncle headers.
|
||||
func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||
return ec.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true)
|
||||
}
|
||||
|
||||
// BlockNumber returns the most recent block number
|
||||
func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
var result hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &result, "eth_blockNumber")
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// PeerCount returns the number of p2p peers as reported by the net_peerCount method.
|
||||
func (ec *Client) PeerCount(ctx context.Context) (uint64, error) {
|
||||
var result hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &result, "net_peerCount")
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// BlockReceipts returns the receipts of a given block number or hash.
|
||||
func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error) {
|
||||
var r []*types.Receipt
|
||||
err := ec.c.CallContext(ctx, &r, "eth_getBlockReceipts", blockNrOrHash.String())
|
||||
if err == nil && r == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
type rpcBlock struct {
|
||||
Hash common.Hash `json:"hash"`
|
||||
Transactions []rpcTransaction `json:"transactions"`
|
||||
UncleHashes []common.Hash `json:"uncles"`
|
||||
Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"`
|
||||
}
|
||||
|
||||
func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) {
|
||||
var raw json.RawMessage
|
||||
err := ec.c.CallContext(ctx, &raw, method, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Decode header and transactions.
|
||||
var head *types.Header
|
||||
if err := json.Unmarshal(raw, &head); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When the block is not found, the API returns JSON null.
|
||||
if head == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
|
||||
var body rpcBlock
|
||||
if err := json.Unmarshal(raw, &body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
|
||||
if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 {
|
||||
return nil, errors.New("server returned non-empty uncle list but block header indicates no uncles")
|
||||
}
|
||||
if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 {
|
||||
return nil, errors.New("server returned empty uncle list but block header indicates uncles")
|
||||
}
|
||||
if head.TxHash == types.EmptyTxsHash && len(body.Transactions) > 0 {
|
||||
return nil, errors.New("server returned non-empty transaction list but block header indicates no transactions")
|
||||
}
|
||||
if head.TxHash != types.EmptyTxsHash && len(body.Transactions) == 0 {
|
||||
return nil, errors.New("server returned empty transaction list but block header indicates transactions")
|
||||
}
|
||||
// Load uncles because they are not included in the block response.
|
||||
var uncles []*types.Header
|
||||
if len(body.UncleHashes) > 0 {
|
||||
uncles = make([]*types.Header, len(body.UncleHashes))
|
||||
reqs := make([]rpc.BatchElem, len(body.UncleHashes))
|
||||
for i := range reqs {
|
||||
reqs[i] = rpc.BatchElem{
|
||||
Method: "eth_getUncleByBlockHashAndIndex",
|
||||
Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))},
|
||||
Result: &uncles[i],
|
||||
}
|
||||
}
|
||||
if err := ec.c.BatchCallContext(ctx, reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range reqs {
|
||||
if reqs[i].Error != nil {
|
||||
return nil, reqs[i].Error
|
||||
}
|
||||
if uncles[i] == nil {
|
||||
return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fill the sender cache of transactions in the block.
|
||||
txs := make([]*types.Transaction, len(body.Transactions))
|
||||
for i, tx := range body.Transactions {
|
||||
if tx.From != nil {
|
||||
setSenderFromServer(tx.tx, *tx.From, body.Hash)
|
||||
}
|
||||
txs[i] = tx.tx
|
||||
}
|
||||
return types.NewBlockWithHeader(head).WithBody(txs, uncles).WithWithdrawals(body.Withdrawals), nil
|
||||
}
|
||||
|
||||
// HeaderByHash returns the block header with the given hash.
|
||||
func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||
var head *types.Header
|
||||
err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false)
|
||||
if err == nil && head == nil {
|
||||
err = ethereum.NotFound
|
||||
}
|
||||
return head, err
|
||||
}
|
||||
|
||||
// HeaderByNumber returns a block header from the current canonical chain. If number is
|
||||
// nil, the latest known header is returned.
|
||||
func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
var head *types.Header
|
||||
err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false)
|
||||
if err == nil && head == nil {
|
||||
err = ethereum.NotFound
|
||||
}
|
||||
return head, err
|
||||
}
|
||||
|
||||
type rpcTransaction struct {
|
||||
tx *types.Transaction
|
||||
txExtraInfo
|
||||
}
|
||||
|
||||
type txExtraInfo struct {
|
||||
BlockNumber *string `json:"blockNumber,omitempty"`
|
||||
BlockHash *common.Hash `json:"blockHash,omitempty"`
|
||||
From *common.Address `json:"from,omitempty"`
|
||||
}
|
||||
|
||||
func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error {
|
||||
if err := json.Unmarshal(msg, &tx.tx); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(msg, &tx.txExtraInfo)
|
||||
}
|
||||
|
||||
// TransactionByHash returns the transaction with the given hash.
|
||||
func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
|
||||
var json *rpcTransaction
|
||||
err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
} else if json == nil {
|
||||
return nil, false, ethereum.NotFound
|
||||
} else if _, r, _ := json.tx.RawSignatureValues(); r == nil {
|
||||
return nil, false, errors.New("server returned transaction without signature")
|
||||
}
|
||||
if json.From != nil && json.BlockHash != nil {
|
||||
setSenderFromServer(json.tx, *json.From, *json.BlockHash)
|
||||
}
|
||||
return json.tx, json.BlockNumber == nil, nil
|
||||
}
|
||||
|
||||
// TransactionSender returns the sender address of the given transaction. The transaction
|
||||
// must be known to the remote node and included in the blockchain at the given block and
|
||||
// index. The sender is the one derived by the protocol at the time of inclusion.
|
||||
//
|
||||
// There is a fast-path for transactions retrieved by TransactionByHash and
|
||||
// TransactionInBlock. Getting their sender address can be done without an RPC interaction.
|
||||
func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) {
|
||||
// Try to load the address from the cache.
|
||||
sender, err := types.Sender(&senderFromServer{blockhash: block}, tx)
|
||||
if err == nil {
|
||||
return sender, nil
|
||||
}
|
||||
|
||||
// It was not found in cache, ask the server.
|
||||
var meta struct {
|
||||
Hash common.Hash
|
||||
From common.Address
|
||||
}
|
||||
if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() {
|
||||
return common.Address{}, errors.New("wrong inclusion block/index")
|
||||
}
|
||||
return meta.From, nil
|
||||
}
|
||||
|
||||
// TransactionCount returns the total number of transactions in the given block.
|
||||
func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) {
|
||||
var num hexutil.Uint
|
||||
err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash)
|
||||
return uint(num), err
|
||||
}
|
||||
|
||||
// TransactionInBlock returns a single transaction at index in the given block.
|
||||
func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) {
|
||||
var json *rpcTransaction
|
||||
err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if json == nil {
|
||||
return nil, ethereum.NotFound
|
||||
} else if _, r, _ := json.tx.RawSignatureValues(); r == nil {
|
||||
return nil, errors.New("server returned transaction without signature")
|
||||
}
|
||||
if json.From != nil && json.BlockHash != nil {
|
||||
setSenderFromServer(json.tx, *json.From, *json.BlockHash)
|
||||
}
|
||||
return json.tx, err
|
||||
}
|
||||
|
||||
// TransactionReceipt returns the receipt of a transaction by transaction hash.
|
||||
// Note that the receipt is not available for pending transactions.
|
||||
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
var r *types.Receipt
|
||||
err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash)
|
||||
if err == nil {
|
||||
if r == nil {
|
||||
return nil, ethereum.NotFound
|
||||
}
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
// SyncProgress retrieves the current progress of the sync algorithm. If there's
|
||||
// no sync currently running, it returns nil.
|
||||
func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) {
|
||||
var raw json.RawMessage
|
||||
if err := ec.c.CallContext(ctx, &raw, "eth_syncing"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Handle the possible response types
|
||||
var syncing bool
|
||||
if err := json.Unmarshal(raw, &syncing); err == nil {
|
||||
return nil, nil // Not syncing (always false)
|
||||
}
|
||||
var p *rpcProgress
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.toSyncProgress(), nil
|
||||
}
|
||||
|
||||
// SubscribeNewHead subscribes to notifications about the current blockchain head
|
||||
// on the given channel.
|
||||
func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) {
|
||||
sub, err := ec.c.EthSubscribe(ctx, ch, "newHeads")
|
||||
if err != nil {
|
||||
// Defensively prefer returning nil interface explicitly on error-path, instead
|
||||
// of letting default golang behavior wrap it with non-nil interface that stores
|
||||
// nil concrete type value.
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
// State Access
|
||||
|
||||
// NetworkID returns the network ID for this client.
|
||||
func (ec *Client) NetworkID(ctx context.Context) (*big.Int, error) {
|
||||
version := new(big.Int)
|
||||
var ver string
|
||||
if err := ec.c.CallContext(ctx, &ver, "net_version"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := version.SetString(ver, 10); !ok {
|
||||
return nil, fmt.Errorf("invalid net_version result %q", ver)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// BalanceAt returns the wei balance of the given account.
|
||||
// The block number can be nil, in which case the balance is taken from the latest known block.
|
||||
func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
var result hexutil.Big
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber))
|
||||
return (*big.Int)(&result), err
|
||||
}
|
||||
|
||||
// BalanceAtHash returns the wei balance of the given account.
|
||||
func (ec *Client) BalanceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (*big.Int, error) {
|
||||
var result hexutil.Big
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||
return (*big.Int)(&result), err
|
||||
}
|
||||
|
||||
// StorageAt returns the value of key in the contract storage of the given account.
|
||||
// The block number can be nil, in which case the value is taken from the latest known block.
|
||||
func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// StorageAtHash returns the value of key in the contract storage of the given account.
|
||||
func (ec *Client) StorageAtHash(ctx context.Context, account common.Address, key common.Hash, blockHash common.Hash) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// CodeAt returns the contract code of the given account.
|
||||
// The block number can be nil, in which case the code is taken from the latest known block.
|
||||
func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// CodeAtHash returns the contract code of the given account.
|
||||
func (ec *Client) CodeAtHash(ctx context.Context, account common.Address, blockHash common.Hash) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getCode", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||
return result, err
|
||||
}
|
||||
|
||||
// NonceAt returns the account nonce of the given account.
|
||||
// The block number can be nil, in which case the nonce is taken from the latest known block.
|
||||
func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
|
||||
var result hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber))
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// NonceAtHash returns the account nonce of the given account.
|
||||
func (ec *Client) NonceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (uint64, error) {
|
||||
var result hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// Filters
|
||||
|
||||
// FilterLogs executes a filter query.
|
||||
func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||
var result []types.Log
|
||||
arg, err := toFilterArg(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ec.c.CallContext(ctx, &result, "eth_getLogs", arg)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// SubscribeFilterLogs subscribes to the results of a streaming filter query.
|
||||
func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
arg, err := toFilterArg(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sub, err := ec.c.EthSubscribe(ctx, ch, "logs", arg)
|
||||
if err != nil {
|
||||
// Defensively prefer returning nil interface explicitly on error-path, instead
|
||||
// of letting default golang behavior wrap it with non-nil interface that stores
|
||||
// nil concrete type value.
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func toFilterArg(q ethereum.FilterQuery) (interface{}, error) {
|
||||
arg := map[string]interface{}{
|
||||
"address": q.Addresses,
|
||||
"topics": q.Topics,
|
||||
}
|
||||
if q.BlockHash != nil {
|
||||
arg["blockHash"] = *q.BlockHash
|
||||
if q.FromBlock != nil || q.ToBlock != nil {
|
||||
return nil, errors.New("cannot specify both BlockHash and FromBlock/ToBlock")
|
||||
}
|
||||
} else {
|
||||
if q.FromBlock == nil {
|
||||
arg["fromBlock"] = "0x0"
|
||||
} else {
|
||||
arg["fromBlock"] = toBlockNumArg(q.FromBlock)
|
||||
}
|
||||
arg["toBlock"] = toBlockNumArg(q.ToBlock)
|
||||
}
|
||||
return arg, nil
|
||||
}
|
||||
|
||||
// Pending State
|
||||
|
||||
// PendingBalanceAt returns the wei balance of the given account in the pending state.
|
||||
func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) {
|
||||
var result hexutil.Big
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending")
|
||||
return (*big.Int)(&result), err
|
||||
}
|
||||
|
||||
// PendingStorageAt returns the value of key in the contract storage of the given account in the pending state.
|
||||
func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// PendingCodeAt returns the contract code of the given account in the pending state.
|
||||
func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
|
||||
var result hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending")
|
||||
return result, err
|
||||
}
|
||||
|
||||
// PendingNonceAt returns the account nonce of the given account in the pending state.
|
||||
// This is the nonce that should be used for the next transaction.
|
||||
func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
||||
var result hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending")
|
||||
return uint64(result), err
|
||||
}
|
||||
|
||||
// PendingTransactionCount returns the total number of transactions in the pending state.
|
||||
func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) {
|
||||
var num hexutil.Uint
|
||||
err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending")
|
||||
return uint(num), err
|
||||
}
|
||||
|
||||
// Contract Calling
|
||||
|
||||
// CallContract executes a message call transaction, which is directly executed in the VM
|
||||
// of the node, but never mined into the blockchain.
|
||||
//
|
||||
// blockNumber selects the block height at which the call runs. It can be nil, in which
|
||||
// case the code is taken from the latest known block. Note that state from very old
|
||||
// blocks might not be available.
|
||||
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
// CallContractAtHash is almost the same as CallContract except that it selects
|
||||
// the block by block hash instead of block height.
|
||||
func (ec *Client) CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), rpc.BlockNumberOrHashWithHash(blockHash, false))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
// PendingCallContract executes a message call transaction using the EVM.
|
||||
// The state seen by the contract call is the pending state.
|
||||
func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hex, nil
|
||||
}
|
||||
|
||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||
// execution of a transaction.
|
||||
func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
var hex hexutil.Big
|
||||
if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*big.Int)(&hex), nil
|
||||
}
|
||||
|
||||
// SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to
|
||||
// allow a timely execution of a transaction.
|
||||
func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
||||
var hex hexutil.Big
|
||||
if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*big.Int)(&hex), nil
|
||||
}
|
||||
|
||||
type feeHistoryResultMarshaling struct {
|
||||
OldestBlock *hexutil.Big `json:"oldestBlock"`
|
||||
Reward [][]*hexutil.Big `json:"reward,omitempty"`
|
||||
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||
GasUsedRatio []float64 `json:"gasUsedRatio"`
|
||||
}
|
||||
|
||||
// FeeHistory retrieves the fee market history.
|
||||
func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error) {
|
||||
var res feeHistoryResultMarshaling
|
||||
if err := ec.c.CallContext(ctx, &res, "eth_feeHistory", hexutil.Uint(blockCount), toBlockNumArg(lastBlock), rewardPercentiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reward := make([][]*big.Int, len(res.Reward))
|
||||
for i, r := range res.Reward {
|
||||
reward[i] = make([]*big.Int, len(r))
|
||||
for j, r := range r {
|
||||
reward[i][j] = (*big.Int)(r)
|
||||
}
|
||||
}
|
||||
baseFee := make([]*big.Int, len(res.BaseFee))
|
||||
for i, b := range res.BaseFee {
|
||||
baseFee[i] = (*big.Int)(b)
|
||||
}
|
||||
return ðereum.FeeHistory{
|
||||
OldestBlock: (*big.Int)(res.OldestBlock),
|
||||
Reward: reward,
|
||||
BaseFee: baseFee,
|
||||
GasUsedRatio: res.GasUsedRatio,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EstimateGas tries to estimate the gas needed to execute a specific transaction based on
|
||||
// the current pending state of the backend blockchain. There is no guarantee that this is
|
||||
// the true gas limit requirement as other transactions may be added or removed by miners,
|
||||
// but it should provide a basis for setting a reasonable default.
|
||||
func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
|
||||
var hex hexutil.Uint64
|
||||
err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint64(hex), nil
|
||||
}
|
||||
|
||||
// SendTransaction injects a signed transaction into the pending pool for execution.
|
||||
//
|
||||
// If the transaction was a contract creation use the TransactionReceipt method to get the
|
||||
// contract address after the transaction has been mined.
|
||||
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
data, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
|
||||
}
|
||||
|
||||
func toBlockNumArg(number *big.Int) string {
|
||||
if number == nil {
|
||||
return "latest"
|
||||
}
|
||||
if number.Sign() >= 0 {
|
||||
return hexutil.EncodeBig(number)
|
||||
}
|
||||
// It's negative.
|
||||
if number.IsInt64() {
|
||||
return rpc.BlockNumber(number.Int64()).String()
|
||||
}
|
||||
// It's negative and large, which is invalid.
|
||||
return fmt.Sprintf("<invalid %d>", number)
|
||||
}
|
||||
|
||||
func toCallArg(msg ethereum.CallMsg) interface{} {
|
||||
arg := map[string]interface{}{
|
||||
"from": msg.From,
|
||||
"to": msg.To,
|
||||
}
|
||||
if len(msg.Data) > 0 {
|
||||
arg["input"] = hexutil.Bytes(msg.Data)
|
||||
}
|
||||
if msg.Value != nil {
|
||||
arg["value"] = (*hexutil.Big)(msg.Value)
|
||||
}
|
||||
if msg.Gas != 0 {
|
||||
arg["gas"] = hexutil.Uint64(msg.Gas)
|
||||
}
|
||||
if msg.GasPrice != nil {
|
||||
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
|
||||
}
|
||||
if msg.GasFeeCap != nil {
|
||||
arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap)
|
||||
}
|
||||
if msg.GasTipCap != nil {
|
||||
arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap)
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
// rpcProgress is a copy of SyncProgress with hex-encoded fields.
|
||||
type rpcProgress struct {
|
||||
StartingBlock hexutil.Uint64
|
||||
CurrentBlock hexutil.Uint64
|
||||
HighestBlock hexutil.Uint64
|
||||
|
||||
PulledStates hexutil.Uint64
|
||||
KnownStates hexutil.Uint64
|
||||
|
||||
SyncedAccounts hexutil.Uint64
|
||||
SyncedAccountBytes hexutil.Uint64
|
||||
SyncedBytecodes hexutil.Uint64
|
||||
SyncedBytecodeBytes hexutil.Uint64
|
||||
SyncedStorage hexutil.Uint64
|
||||
SyncedStorageBytes hexutil.Uint64
|
||||
HealedTrienodes hexutil.Uint64
|
||||
HealedTrienodeBytes hexutil.Uint64
|
||||
HealedBytecodes hexutil.Uint64
|
||||
HealedBytecodeBytes hexutil.Uint64
|
||||
HealingTrienodes hexutil.Uint64
|
||||
HealingBytecode hexutil.Uint64
|
||||
}
|
||||
|
||||
func (p *rpcProgress) toSyncProgress() *ethereum.SyncProgress {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return ðereum.SyncProgress{
|
||||
StartingBlock: uint64(p.StartingBlock),
|
||||
CurrentBlock: uint64(p.CurrentBlock),
|
||||
HighestBlock: uint64(p.HighestBlock),
|
||||
PulledStates: uint64(p.PulledStates),
|
||||
KnownStates: uint64(p.KnownStates),
|
||||
SyncedAccounts: uint64(p.SyncedAccounts),
|
||||
SyncedAccountBytes: uint64(p.SyncedAccountBytes),
|
||||
SyncedBytecodes: uint64(p.SyncedBytecodes),
|
||||
SyncedBytecodeBytes: uint64(p.SyncedBytecodeBytes),
|
||||
SyncedStorage: uint64(p.SyncedStorage),
|
||||
SyncedStorageBytes: uint64(p.SyncedStorageBytes),
|
||||
HealedTrienodes: uint64(p.HealedTrienodes),
|
||||
HealedTrienodeBytes: uint64(p.HealedTrienodeBytes),
|
||||
HealedBytecodes: uint64(p.HealedBytecodes),
|
||||
HealedBytecodeBytes: uint64(p.HealedBytecodeBytes),
|
||||
HealingTrienodes: uint64(p.HealingTrienodes),
|
||||
HealingBytecode: uint64(p.HealingBytecode),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,745 +0,0 @@
|
|||
// Copyright 2016 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 ethclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Verify that Client implements the ethereum interfaces.
|
||||
var (
|
||||
_ = ethereum.ChainReader(&Client{})
|
||||
_ = ethereum.TransactionReader(&Client{})
|
||||
_ = ethereum.ChainStateReader(&Client{})
|
||||
_ = ethereum.ChainSyncReader(&Client{})
|
||||
_ = ethereum.ContractCaller(&Client{})
|
||||
_ = ethereum.GasEstimator(&Client{})
|
||||
_ = ethereum.GasPricer(&Client{})
|
||||
_ = ethereum.LogFilterer(&Client{})
|
||||
_ = ethereum.PendingStateReader(&Client{})
|
||||
// _ = ethereum.PendingStateEventer(&Client{})
|
||||
_ = ethereum.PendingContractCaller(&Client{})
|
||||
)
|
||||
|
||||
func TestToFilterArg(t *testing.T) {
|
||||
blockHashErr := errors.New("cannot specify both BlockHash and FromBlock/ToBlock")
|
||||
addresses := []common.Address{
|
||||
common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"),
|
||||
}
|
||||
blockHash := common.HexToHash(
|
||||
"0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb",
|
||||
)
|
||||
|
||||
for _, testCase := range []struct {
|
||||
name string
|
||||
input ethereum.FilterQuery
|
||||
output interface{}
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"without BlockHash",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
FromBlock: big.NewInt(1),
|
||||
ToBlock: big.NewInt(2),
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"address": addresses,
|
||||
"fromBlock": "0x1",
|
||||
"toBlock": "0x2",
|
||||
"topics": [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"with nil fromBlock and nil toBlock",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"address": addresses,
|
||||
"fromBlock": "0x0",
|
||||
"toBlock": "latest",
|
||||
"topics": [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"with negative fromBlock and negative toBlock",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
FromBlock: big.NewInt(-1),
|
||||
ToBlock: big.NewInt(-1),
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"address": addresses,
|
||||
"fromBlock": "pending",
|
||||
"toBlock": "pending",
|
||||
"topics": [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"with blockhash",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
BlockHash: &blockHash,
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"address": addresses,
|
||||
"blockHash": blockHash,
|
||||
"topics": [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"with blockhash and from block",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
BlockHash: &blockHash,
|
||||
FromBlock: big.NewInt(1),
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
blockHashErr,
|
||||
},
|
||||
{
|
||||
"with blockhash and to block",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
BlockHash: &blockHash,
|
||||
ToBlock: big.NewInt(1),
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
blockHashErr,
|
||||
},
|
||||
{
|
||||
"with blockhash and both from / to block",
|
||||
ethereum.FilterQuery{
|
||||
Addresses: addresses,
|
||||
BlockHash: &blockHash,
|
||||
FromBlock: big.NewInt(1),
|
||||
ToBlock: big.NewInt(2),
|
||||
Topics: [][]common.Hash{},
|
||||
},
|
||||
nil,
|
||||
blockHashErr,
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
output, err := toFilterArg(testCase.input)
|
||||
if (testCase.err == nil) != (err == nil) {
|
||||
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
||||
}
|
||||
if testCase.err != nil {
|
||||
if testCase.err.Error() != err.Error() {
|
||||
t.Fatalf("expected error %v but got %v", testCase.err, err)
|
||||
}
|
||||
} else if !reflect.DeepEqual(testCase.output, output) {
|
||||
t.Fatalf("expected filter arg %v but got %v", testCase.output, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
testBalance = big.NewInt(2e15)
|
||||
)
|
||||
|
||||
var genesis = &core.Genesis{
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}},
|
||||
ExtraData: []byte("test genesis"),
|
||||
Timestamp: 9000,
|
||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
||||
}
|
||||
|
||||
var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
|
||||
Nonce: 0,
|
||||
Value: big.NewInt(12),
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
Gas: params.TxGas,
|
||||
To: &common.Address{2},
|
||||
})
|
||||
|
||||
var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
|
||||
Nonce: 1,
|
||||
Value: big.NewInt(8),
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
Gas: params.TxGas,
|
||||
To: &common.Address{2},
|
||||
})
|
||||
|
||||
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||
// Generate test chain.
|
||||
blocks := generateTestChain()
|
||||
|
||||
// Create node
|
||||
n, err := node.New(&node.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("can't create new node: %v", err)
|
||||
}
|
||||
// Create Ethereum Service
|
||||
config := ðconfig.Config{Genesis: genesis}
|
||||
ethservice, err := eth.New(n, config)
|
||||
if err != nil {
|
||||
t.Fatalf("can't create new ethereum service: %v", err)
|
||||
}
|
||||
// Import the test chain.
|
||||
if err := n.Start(); err != nil {
|
||||
t.Fatalf("can't start test node: %v", err)
|
||||
}
|
||||
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
|
||||
t.Fatalf("can't import test blocks: %v", err)
|
||||
}
|
||||
return n, blocks
|
||||
}
|
||||
|
||||
func generateTestChain() []*types.Block {
|
||||
generate := func(i int, g *core.BlockGen) {
|
||||
g.OffsetTime(5)
|
||||
g.SetExtra([]byte("test"))
|
||||
if i == 1 {
|
||||
// Test transactions are included in block #2.
|
||||
g.AddTx(testTx1)
|
||||
g.AddTx(testTx2)
|
||||
}
|
||||
}
|
||||
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate)
|
||||
return append([]*types.Block{genesis.ToBlock()}, blocks...)
|
||||
}
|
||||
|
||||
func TestEthClient(t *testing.T) {
|
||||
backend, chain := newTestBackend(t)
|
||||
client := backend.Attach()
|
||||
defer backend.Close()
|
||||
defer client.Close()
|
||||
|
||||
tests := map[string]struct {
|
||||
test func(t *testing.T)
|
||||
}{
|
||||
"Header": {
|
||||
func(t *testing.T) { testHeader(t, chain, client) },
|
||||
},
|
||||
"BalanceAt": {
|
||||
func(t *testing.T) { testBalanceAt(t, client) },
|
||||
},
|
||||
"TxInBlockInterrupted": {
|
||||
func(t *testing.T) { testTransactionInBlockInterrupted(t, client) },
|
||||
},
|
||||
"ChainID": {
|
||||
func(t *testing.T) { testChainID(t, client) },
|
||||
},
|
||||
"GetBlock": {
|
||||
func(t *testing.T) { testGetBlock(t, client) },
|
||||
},
|
||||
"StatusFunctions": {
|
||||
func(t *testing.T) { testStatusFunctions(t, client) },
|
||||
},
|
||||
"CallContract": {
|
||||
func(t *testing.T) { testCallContract(t, client) },
|
||||
},
|
||||
"CallContractAtHash": {
|
||||
func(t *testing.T) { testCallContractAtHash(t, client) },
|
||||
},
|
||||
"AtFunctions": {
|
||||
func(t *testing.T) { testAtFunctions(t, client) },
|
||||
},
|
||||
"TransactionSender": {
|
||||
func(t *testing.T) { testTransactionSender(t, client) },
|
||||
},
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
for name, tt := range tests {
|
||||
t.Run(name, tt.test)
|
||||
}
|
||||
}
|
||||
|
||||
func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) {
|
||||
tests := map[string]struct {
|
||||
block *big.Int
|
||||
want *types.Header
|
||||
wantErr error
|
||||
}{
|
||||
"genesis": {
|
||||
block: big.NewInt(0),
|
||||
want: chain[0].Header(),
|
||||
},
|
||||
"first_block": {
|
||||
block: big.NewInt(1),
|
||||
want: chain[1].Header(),
|
||||
},
|
||||
"future_block": {
|
||||
block: big.NewInt(1000000000),
|
||||
want: nil,
|
||||
wantErr: ethereum.NotFound,
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ec := NewClient(client)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
got, err := ec.HeaderByNumber(ctx, tt.block)
|
||||
if !errors.Is(err, tt.wantErr) {
|
||||
t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr)
|
||||
}
|
||||
if got != nil && got.Number != nil && got.Number.Sign() == 0 {
|
||||
got.Number = big.NewInt(0) // hack to make DeepEqual work
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("HeaderByNumber(%v)\n = %v\nwant %v", tt.block, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testBalanceAt(t *testing.T, client *rpc.Client) {
|
||||
tests := map[string]struct {
|
||||
account common.Address
|
||||
block *big.Int
|
||||
want *big.Int
|
||||
wantErr error
|
||||
}{
|
||||
"valid_account_genesis": {
|
||||
account: testAddr,
|
||||
block: big.NewInt(0),
|
||||
want: testBalance,
|
||||
},
|
||||
"valid_account": {
|
||||
account: testAddr,
|
||||
block: big.NewInt(1),
|
||||
want: testBalance,
|
||||
},
|
||||
"non_existent_account": {
|
||||
account: common.Address{1},
|
||||
block: big.NewInt(1),
|
||||
want: big.NewInt(0),
|
||||
},
|
||||
"future_block": {
|
||||
account: testAddr,
|
||||
block: big.NewInt(1000000000),
|
||||
want: big.NewInt(0),
|
||||
wantErr: errors.New("header not found"),
|
||||
},
|
||||
}
|
||||
for name, tt := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ec := NewClient(client)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
got, err := ec.BalanceAt(ctx, tt.account, tt.block)
|
||||
if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) {
|
||||
t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr)
|
||||
}
|
||||
if got.Cmp(tt.want) != 0 {
|
||||
t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
// Get current block by number.
|
||||
block, err := ec.BlockByNumber(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Test tx in block interrupted.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
<-ctx.Done() // Ensure the close of the Done channel
|
||||
tx, err := ec.TransactionInBlock(ctx, block.Hash(), 0)
|
||||
if tx != nil {
|
||||
t.Fatal("transaction should be nil")
|
||||
}
|
||||
if err == nil || err == ethereum.NotFound {
|
||||
t.Fatal("error should not be nil/notfound")
|
||||
}
|
||||
|
||||
// Test tx in block not found.
|
||||
if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 20); err != ethereum.NotFound {
|
||||
t.Fatal("error should be ethereum.NotFound")
|
||||
}
|
||||
}
|
||||
|
||||
func testChainID(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
id, err := ec.ChainID(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 {
|
||||
t.Fatalf("ChainID returned wrong number: %+v", id)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetBlock(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
// Get current block number
|
||||
blockNumber, err := ec.BlockNumber(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if blockNumber != 2 {
|
||||
t.Fatalf("BlockNumber returned wrong number: %d", blockNumber)
|
||||
}
|
||||
// Get current block by number
|
||||
block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if block.NumberU64() != blockNumber {
|
||||
t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64())
|
||||
}
|
||||
// Get current block by hash
|
||||
blockH, err := ec.BlockByHash(context.Background(), block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if block.Hash() != blockH.Hash() {
|
||||
t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex())
|
||||
}
|
||||
// Get header by number
|
||||
header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if block.Header().Hash() != header.Hash() {
|
||||
t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex())
|
||||
}
|
||||
// Get header by hash
|
||||
headerH, err := ec.HeaderByHash(context.Background(), block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if block.Header().Hash() != headerH.Hash() {
|
||||
t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func testStatusFunctions(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
// Sync progress
|
||||
progress, err := ec.SyncProgress(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if progress != nil {
|
||||
t.Fatalf("unexpected progress: %v", progress)
|
||||
}
|
||||
|
||||
// NetworkID
|
||||
networkID, err := ec.NetworkID(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if networkID.Cmp(big.NewInt(1337)) != 0 {
|
||||
t.Fatalf("unexpected networkID: %v", networkID)
|
||||
}
|
||||
|
||||
// SuggestGasPrice
|
||||
gasPrice, err := ec.SuggestGasPrice(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gasPrice.Cmp(big.NewInt(1000000000)) != 0 {
|
||||
t.Fatalf("unexpected gas price: %v", gasPrice)
|
||||
}
|
||||
|
||||
// SuggestGasTipCap
|
||||
gasTipCap, err := ec.SuggestGasTipCap(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gasTipCap.Cmp(big.NewInt(234375000)) != 0 {
|
||||
t.Fatalf("unexpected gas tip cap: %v", gasTipCap)
|
||||
}
|
||||
|
||||
// FeeHistory
|
||||
history, err := ec.FeeHistory(context.Background(), 1, big.NewInt(2), []float64{95, 99})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
want := ðereum.FeeHistory{
|
||||
OldestBlock: big.NewInt(2),
|
||||
Reward: [][]*big.Int{
|
||||
{
|
||||
big.NewInt(234375000),
|
||||
big.NewInt(234375000),
|
||||
},
|
||||
},
|
||||
BaseFee: []*big.Int{
|
||||
big.NewInt(765625000),
|
||||
big.NewInt(671627818),
|
||||
},
|
||||
GasUsedRatio: []float64{0.008912678667376286},
|
||||
}
|
||||
if !reflect.DeepEqual(history, want) {
|
||||
t.Fatalf("FeeHistory result doesn't match expected: (got: %v, want: %v)", history, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testCallContractAtHash(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
// EstimateGas
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 21000,
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
gas, err := ec.EstimateGas(context.Background(), msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gas != 21000 {
|
||||
t.Fatalf("unexpected gas price: %v", gas)
|
||||
}
|
||||
block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
|
||||
if err != nil {
|
||||
t.Fatalf("BlockByNumber error: %v", err)
|
||||
}
|
||||
// CallContract
|
||||
if _, err := ec.CallContractAtHash(context.Background(), msg, block.Hash()); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testCallContract(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
// EstimateGas
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 21000,
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
gas, err := ec.EstimateGas(context.Background(), msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gas != 21000 {
|
||||
t.Fatalf("unexpected gas price: %v", gas)
|
||||
}
|
||||
// CallContract
|
||||
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// PendingCallContract
|
||||
if _, err := ec.PendingCallContract(context.Background(), msg); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func testAtFunctions(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
|
||||
block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
|
||||
if err != nil {
|
||||
t.Fatalf("BlockByNumber error: %v", err)
|
||||
}
|
||||
|
||||
// send a transaction for some interesting pending status
|
||||
sendTransaction(ec)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Check pending transaction count
|
||||
pending, err := ec.PendingTransactionCount(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if pending != 1 {
|
||||
t.Fatalf("unexpected pending, wanted 1 got: %v", pending)
|
||||
}
|
||||
// Query balance
|
||||
balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
hashBalance, err := ec.BalanceAtHash(context.Background(), testAddr, block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if balance.Cmp(hashBalance) == 0 {
|
||||
t.Fatalf("unexpected balance at hash: %v %v", balance, hashBalance)
|
||||
}
|
||||
penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if balance.Cmp(penBalance) == 0 {
|
||||
t.Fatalf("unexpected balance: %v %v", balance, penBalance)
|
||||
}
|
||||
// NonceAt
|
||||
nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
hashNonce, err := ec.NonceAtHash(context.Background(), testAddr, block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if hashNonce == nonce {
|
||||
t.Fatalf("unexpected nonce at hash: %v %v", nonce, hashNonce)
|
||||
}
|
||||
penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if penNonce != nonce+1 {
|
||||
t.Fatalf("unexpected nonce: %v %v", nonce, penNonce)
|
||||
}
|
||||
// StorageAt
|
||||
storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
hashStorage, err := ec.StorageAtHash(context.Background(), testAddr, common.Hash{}, block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(storage, hashStorage) {
|
||||
t.Fatalf("unexpected storage at hash: %v %v", storage, hashStorage)
|
||||
}
|
||||
penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(storage, penStorage) {
|
||||
t.Fatalf("unexpected storage: %v %v", storage, penStorage)
|
||||
}
|
||||
// CodeAt
|
||||
code, err := ec.CodeAt(context.Background(), testAddr, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
hashCode, err := ec.CodeAtHash(context.Background(), common.Address{}, block.Hash())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(code, hashCode) {
|
||||
t.Fatalf("unexpected code at hash: %v %v", code, hashCode)
|
||||
}
|
||||
penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(code, penCode) {
|
||||
t.Fatalf("unexpected code: %v %v", code, penCode)
|
||||
}
|
||||
}
|
||||
|
||||
func testTransactionSender(t *testing.T, client *rpc.Client) {
|
||||
ec := NewClient(client)
|
||||
ctx := context.Background()
|
||||
|
||||
// Retrieve testTx1 via RPC.
|
||||
block2, err := ec.HeaderByNumber(ctx, big.NewInt(2))
|
||||
if err != nil {
|
||||
t.Fatal("can't get block 1:", err)
|
||||
}
|
||||
tx1, err := ec.TransactionInBlock(ctx, block2.Hash(), 0)
|
||||
if err != nil {
|
||||
t.Fatal("can't get tx:", err)
|
||||
}
|
||||
if tx1.Hash() != testTx1.Hash() {
|
||||
t.Fatalf("wrong tx hash %v, want %v", tx1.Hash(), testTx1.Hash())
|
||||
}
|
||||
|
||||
// The sender address is cached in tx1, so no additional RPC should be required in
|
||||
// TransactionSender. Ensure the server is not asked by canceling the context here.
|
||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
<-canceledCtx.Done() // Ensure the close of the Done channel
|
||||
sender1, err := ec.TransactionSender(canceledCtx, tx1, block2.Hash(), 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sender1 != testAddr {
|
||||
t.Fatal("wrong sender:", sender1)
|
||||
}
|
||||
|
||||
// Now try to get the sender of testTx2, which was not fetched through RPC.
|
||||
// TransactionSender should query the server here.
|
||||
sender2, err := ec.TransactionSender(ctx, testTx2, block2.Hash(), 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if sender2 != testAddr {
|
||||
t.Fatal("wrong sender:", sender2)
|
||||
}
|
||||
}
|
||||
|
||||
func sendTransaction(ec *Client) error {
|
||||
chainID, err := ec.ChainID(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nonce, err := ec.PendingNonceAt(context.Background(), testAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signer := types.LatestSignerForChainID(chainID)
|
||||
tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{
|
||||
Nonce: nonce,
|
||||
To: &common.Address{2},
|
||||
Value: big.NewInt(1),
|
||||
Gas: 22000,
|
||||
GasPrice: big.NewInt(params.InitialBaseFee),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ec.SendTransaction(context.Background(), tx)
|
||||
}
|
||||
|
|
@ -1,335 +0,0 @@
|
|||
// Copyright 2021 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 gethclient provides an RPC client for geth-specific APIs.
|
||||
package gethclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Client is a wrapper around rpc.Client that implements geth-specific functionality.
|
||||
//
|
||||
// If you want to use the standardized Ethereum RPC functionality, use ethclient.Client instead.
|
||||
type Client struct {
|
||||
c *rpc.Client
|
||||
}
|
||||
|
||||
// New creates a client that uses the given RPC client.
|
||||
func New(c *rpc.Client) *Client {
|
||||
return &Client{c}
|
||||
}
|
||||
|
||||
// CreateAccessList tries to create an access list for a specific transaction based on the
|
||||
// current pending state of the blockchain.
|
||||
func (ec *Client) CreateAccessList(ctx context.Context, msg ethereum.CallMsg) (*types.AccessList, uint64, string, error) {
|
||||
type accessListResult struct {
|
||||
Accesslist *types.AccessList `json:"accessList"`
|
||||
Error string `json:"error,omitempty"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed"`
|
||||
}
|
||||
var result accessListResult
|
||||
if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil {
|
||||
return nil, 0, "", err
|
||||
}
|
||||
return result.Accesslist, uint64(result.GasUsed), result.Error, nil
|
||||
}
|
||||
|
||||
// AccountResult is the result of a GetProof operation.
|
||||
type AccountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *big.Int `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []StorageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
// StorageResult provides a proof for a key-value pair.
|
||||
type StorageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *big.Int `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
// GetProof returns the account and storage values of the specified account including the Merkle-proof.
|
||||
// The block number can be nil, in which case the value is taken from the latest known block.
|
||||
func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) {
|
||||
type storageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
type accountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []storageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
// Avoid keys being 'null'.
|
||||
if keys == nil {
|
||||
keys = []string{}
|
||||
}
|
||||
|
||||
var res accountResult
|
||||
err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, toBlockNumArg(blockNumber))
|
||||
// Turn hexutils back to normal datatypes
|
||||
storageResults := make([]StorageResult, 0, len(res.StorageProof))
|
||||
for _, st := range res.StorageProof {
|
||||
storageResults = append(storageResults, StorageResult{
|
||||
Key: st.Key,
|
||||
Value: st.Value.ToInt(),
|
||||
Proof: st.Proof,
|
||||
})
|
||||
}
|
||||
result := AccountResult{
|
||||
Address: res.Address,
|
||||
AccountProof: res.AccountProof,
|
||||
Balance: res.Balance.ToInt(),
|
||||
Nonce: uint64(res.Nonce),
|
||||
CodeHash: res.CodeHash,
|
||||
StorageHash: res.StorageHash,
|
||||
StorageProof: storageResults,
|
||||
}
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// CallContract executes a message call transaction, which is directly executed in the VM
|
||||
// of the node, but never mined into the blockchain.
|
||||
//
|
||||
// blockNumber selects the block height at which the call runs. It can be nil, in which
|
||||
// case the code is taken from the latest known block. Note that state from very old
|
||||
// blocks might not be available.
|
||||
//
|
||||
// overrides specifies a map of contract states that should be overwritten before executing
|
||||
// the message call.
|
||||
// Please use ethclient.CallContract instead if you don't need the override functionality.
|
||||
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(
|
||||
ctx, &hex, "eth_call", toCallArg(msg),
|
||||
toBlockNumArg(blockNumber), overrides,
|
||||
)
|
||||
return hex, err
|
||||
}
|
||||
|
||||
// CallContractWithBlockOverrides executes a message call transaction, which is directly executed
|
||||
// in the VM of the node, but never mined into the blockchain.
|
||||
//
|
||||
// blockNumber selects the block height at which the call runs. It can be nil, in which
|
||||
// case the code is taken from the latest known block. Note that state from very old
|
||||
// blocks might not be available.
|
||||
//
|
||||
// overrides specifies a map of contract states that should be overwritten before executing
|
||||
// the message call.
|
||||
//
|
||||
// blockOverrides specifies block fields exposed to the EVM that can be overridden for the call.
|
||||
//
|
||||
// Please use ethclient.CallContract instead if you don't need the override functionality.
|
||||
func (ec *Client) CallContractWithBlockOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount, blockOverrides BlockOverrides) ([]byte, error) {
|
||||
var hex hexutil.Bytes
|
||||
err := ec.c.CallContext(
|
||||
ctx, &hex, "eth_call", toCallArg(msg),
|
||||
toBlockNumArg(blockNumber), overrides, blockOverrides,
|
||||
)
|
||||
return hex, err
|
||||
}
|
||||
|
||||
// GCStats retrieves the current garbage collection stats from a geth node.
|
||||
func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) {
|
||||
var result debug.GCStats
|
||||
err := ec.c.CallContext(ctx, &result, "debug_gcStats")
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// MemStats retrieves the current memory stats from a geth node.
|
||||
func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) {
|
||||
var result runtime.MemStats
|
||||
err := ec.c.CallContext(ctx, &result, "debug_memStats")
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// SetHead sets the current head of the local chain by block number.
|
||||
// Note, this is a destructive action and may severely damage your chain.
|
||||
// Use with extreme caution.
|
||||
func (ec *Client) SetHead(ctx context.Context, number *big.Int) error {
|
||||
return ec.c.CallContext(ctx, nil, "debug_setHead", toBlockNumArg(number))
|
||||
}
|
||||
|
||||
// GetNodeInfo retrieves the node info of a geth node.
|
||||
func (ec *Client) GetNodeInfo(ctx context.Context) (*p2p.NodeInfo, error) {
|
||||
var result p2p.NodeInfo
|
||||
err := ec.c.CallContext(ctx, &result, "admin_nodeInfo")
|
||||
return &result, err
|
||||
}
|
||||
|
||||
// SubscribeFullPendingTransactions subscribes to new pending transactions.
|
||||
func (ec *Client) SubscribeFullPendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (*rpc.ClientSubscription, error) {
|
||||
return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions", true)
|
||||
}
|
||||
|
||||
// SubscribePendingTransactions subscribes to new pending transaction hashes.
|
||||
func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) {
|
||||
return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions")
|
||||
}
|
||||
|
||||
func toBlockNumArg(number *big.Int) string {
|
||||
if number == nil {
|
||||
return "latest"
|
||||
}
|
||||
if number.Sign() >= 0 {
|
||||
return hexutil.EncodeBig(number)
|
||||
}
|
||||
// It's negative.
|
||||
if number.IsInt64() {
|
||||
return rpc.BlockNumber(number.Int64()).String()
|
||||
}
|
||||
// It's negative and large, which is invalid.
|
||||
return fmt.Sprintf("<invalid %d>", number)
|
||||
}
|
||||
|
||||
func toCallArg(msg ethereum.CallMsg) interface{} {
|
||||
arg := map[string]interface{}{
|
||||
"from": msg.From,
|
||||
"to": msg.To,
|
||||
}
|
||||
if len(msg.Data) > 0 {
|
||||
arg["input"] = hexutil.Bytes(msg.Data)
|
||||
}
|
||||
if msg.Value != nil {
|
||||
arg["value"] = (*hexutil.Big)(msg.Value)
|
||||
}
|
||||
if msg.Gas != 0 {
|
||||
arg["gas"] = hexutil.Uint64(msg.Gas)
|
||||
}
|
||||
if msg.GasPrice != nil {
|
||||
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
// OverrideAccount specifies the state of an account to be overridden.
|
||||
type OverrideAccount struct {
|
||||
// Nonce sets nonce of the account. Note: the nonce override will only
|
||||
// be applied when it is set to a non-zero value.
|
||||
Nonce uint64
|
||||
|
||||
// Code sets the contract code. The override will be applied
|
||||
// when the code is non-nil, i.e. setting empty code is possible
|
||||
// using an empty slice.
|
||||
Code []byte
|
||||
|
||||
// Balance sets the account balance.
|
||||
Balance *big.Int
|
||||
|
||||
// State sets the complete storage. The override will be applied
|
||||
// when the given map is non-nil. Using an empty map wipes the
|
||||
// entire contract storage during the call.
|
||||
State map[common.Hash]common.Hash
|
||||
|
||||
// StateDiff allows overriding individual storage slots.
|
||||
StateDiff map[common.Hash]common.Hash
|
||||
}
|
||||
|
||||
func (a OverrideAccount) MarshalJSON() ([]byte, error) {
|
||||
type acc struct {
|
||||
Nonce hexutil.Uint64 `json:"nonce,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Balance *hexutil.Big `json:"balance,omitempty"`
|
||||
State interface{} `json:"state,omitempty"`
|
||||
StateDiff map[common.Hash]common.Hash `json:"stateDiff,omitempty"`
|
||||
}
|
||||
|
||||
output := acc{
|
||||
Nonce: hexutil.Uint64(a.Nonce),
|
||||
Balance: (*hexutil.Big)(a.Balance),
|
||||
StateDiff: a.StateDiff,
|
||||
}
|
||||
if a.Code != nil {
|
||||
output.Code = hexutil.Encode(a.Code)
|
||||
}
|
||||
if a.State != nil {
|
||||
output.State = a.State
|
||||
}
|
||||
return json.Marshal(output)
|
||||
}
|
||||
|
||||
// BlockOverrides specifies the set of header fields to override.
|
||||
type BlockOverrides struct {
|
||||
// Number overrides the block number.
|
||||
Number *big.Int
|
||||
// Difficulty overrides the block difficulty.
|
||||
Difficulty *big.Int
|
||||
// Time overrides the block timestamp. Time is applied only when
|
||||
// it is non-zero.
|
||||
Time uint64
|
||||
// GasLimit overrides the block gas limit. GasLimit is applied only when
|
||||
// it is non-zero.
|
||||
GasLimit uint64
|
||||
// Coinbase overrides the block coinbase. Coinbase is applied only when
|
||||
// it is different from the zero address.
|
||||
Coinbase common.Address
|
||||
// Random overrides the block extra data which feeds into the RANDOM opcode.
|
||||
// Random is applied only when it is a non-zero hash.
|
||||
Random common.Hash
|
||||
// BaseFee overrides the block base fee.
|
||||
BaseFee *big.Int
|
||||
}
|
||||
|
||||
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
|
||||
type override struct {
|
||||
Number *hexutil.Big `json:"number,omitempty"`
|
||||
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
|
||||
Time hexutil.Uint64 `json:"time,omitempty"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
|
||||
Coinbase *common.Address `json:"coinbase,omitempty"`
|
||||
Random *common.Hash `json:"random,omitempty"`
|
||||
BaseFee *hexutil.Big `json:"baseFee,omitempty"`
|
||||
}
|
||||
|
||||
output := override{
|
||||
Number: (*hexutil.Big)(o.Number),
|
||||
Difficulty: (*hexutil.Big)(o.Difficulty),
|
||||
Time: hexutil.Uint64(o.Time),
|
||||
GasLimit: hexutil.Uint64(o.GasLimit),
|
||||
BaseFee: (*hexutil.Big)(o.BaseFee),
|
||||
}
|
||||
if o.Coinbase != (common.Address{}) {
|
||||
output.Coinbase = &o.Coinbase
|
||||
}
|
||||
if o.Random != (common.Hash{}) {
|
||||
output.Random = &o.Random
|
||||
}
|
||||
return json.Marshal(output)
|
||||
}
|
||||
|
|
@ -1,570 +0,0 @@
|
|||
// Copyright 2021 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 gethclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/ethconfig"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
var (
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
|
||||
testContract = common.HexToAddress("0xbeef")
|
||||
testEmpty = common.HexToAddress("0xeeee")
|
||||
testSlot = common.HexToHash("0xdeadbeef")
|
||||
testValue = crypto.Keccak256Hash(testSlot[:])
|
||||
testBalance = big.NewInt(2e15)
|
||||
)
|
||||
|
||||
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
|
||||
// Generate test chain.
|
||||
genesis, blocks := generateTestChain()
|
||||
// Create node
|
||||
n, err := node.New(&node.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("can't create new node: %v", err)
|
||||
}
|
||||
// Create Ethereum Service
|
||||
config := ðconfig.Config{Genesis: genesis}
|
||||
ethservice, err := eth.New(n, config)
|
||||
if err != nil {
|
||||
t.Fatalf("can't create new ethereum service: %v", err)
|
||||
}
|
||||
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
|
||||
n.RegisterAPIs([]rpc.API{{
|
||||
Namespace: "eth",
|
||||
Service: filters.NewFilterAPI(filterSystem, false),
|
||||
}})
|
||||
|
||||
// Import the test chain.
|
||||
if err := n.Start(); err != nil {
|
||||
t.Fatalf("can't start test node: %v", err)
|
||||
}
|
||||
if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
|
||||
t.Fatalf("can't import test blocks: %v", err)
|
||||
}
|
||||
return n, blocks
|
||||
}
|
||||
|
||||
func generateTestChain() (*core.Genesis, []*types.Block) {
|
||||
genesis := &core.Genesis{
|
||||
Config: params.AllEthashProtocolChanges,
|
||||
Alloc: core.GenesisAlloc{
|
||||
testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}},
|
||||
testContract: {Nonce: 1, Code: []byte{0x13, 0x37}},
|
||||
testEmpty: {Balance: big.NewInt(1)},
|
||||
},
|
||||
ExtraData: []byte("test genesis"),
|
||||
Timestamp: 9000,
|
||||
}
|
||||
generate := func(i int, g *core.BlockGen) {
|
||||
g.OffsetTime(5)
|
||||
g.SetExtra([]byte("test"))
|
||||
}
|
||||
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate)
|
||||
blocks = append([]*types.Block{genesis.ToBlock()}, blocks...)
|
||||
return genesis, blocks
|
||||
}
|
||||
|
||||
func TestGethClient(t *testing.T) {
|
||||
backend, _ := newTestBackend(t)
|
||||
client := backend.Attach()
|
||||
defer backend.Close()
|
||||
defer client.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
test func(t *testing.T)
|
||||
}{
|
||||
{
|
||||
"TestGetProof1",
|
||||
func(t *testing.T) { testGetProof(t, client, testAddr) },
|
||||
}, {
|
||||
"TestGetProof2",
|
||||
func(t *testing.T) { testGetProof(t, client, testContract) },
|
||||
}, {
|
||||
"TestGetProofEmpty",
|
||||
func(t *testing.T) { testGetProof(t, client, testEmpty) },
|
||||
}, {
|
||||
"TestGetProofNonExistent",
|
||||
func(t *testing.T) { testGetProofNonExistent(t, client) },
|
||||
}, {
|
||||
"TestGetProofCanonicalizeKeys",
|
||||
func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) },
|
||||
}, {
|
||||
"TestGCStats",
|
||||
func(t *testing.T) { testGCStats(t, client) },
|
||||
}, {
|
||||
"TestMemStats",
|
||||
func(t *testing.T) { testMemStats(t, client) },
|
||||
}, {
|
||||
"TestGetNodeInfo",
|
||||
func(t *testing.T) { testGetNodeInfo(t, client) },
|
||||
}, {
|
||||
"TestSubscribePendingTxHashes",
|
||||
func(t *testing.T) { testSubscribePendingTransactions(t, client) },
|
||||
}, {
|
||||
"TestSubscribePendingTxs",
|
||||
func(t *testing.T) { testSubscribeFullPendingTransactions(t, client) },
|
||||
}, {
|
||||
"TestCallContract",
|
||||
func(t *testing.T) { testCallContract(t, client) },
|
||||
}, {
|
||||
"TestCallContractWithBlockOverrides",
|
||||
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
|
||||
},
|
||||
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
|
||||
// one block. The `testAcessList` fails if the miner has not yet created a
|
||||
// new pending-block after the import event.
|
||||
// Hence: this test should be last, execute the tests serially.
|
||||
{
|
||||
"TestAccessList",
|
||||
func(t *testing.T) { testAccessList(t, client) },
|
||||
}, {
|
||||
"TestSetHead",
|
||||
func(t *testing.T) { testSetHead(t, client) },
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, tt.test)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccessList(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
// Test transfer
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 21000,
|
||||
GasPrice: big.NewInt(765625000),
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
al, gas, vmErr, err := ec.CreateAccessList(context.Background(), msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if vmErr != "" {
|
||||
t.Fatalf("unexpected vm error: %v", vmErr)
|
||||
}
|
||||
if gas != 21000 {
|
||||
t.Fatalf("unexpected gas used: %v", gas)
|
||||
}
|
||||
if len(*al) != 0 {
|
||||
t.Fatalf("unexpected length of accesslist: %v", len(*al))
|
||||
}
|
||||
// Test reverting transaction
|
||||
msg = ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: nil,
|
||||
Gas: 100000,
|
||||
GasPrice: big.NewInt(1000000000),
|
||||
Value: big.NewInt(1),
|
||||
Data: common.FromHex("0x608060806080608155fd"),
|
||||
}
|
||||
al, gas, vmErr, err = ec.CreateAccessList(context.Background(), msg)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if vmErr == "" {
|
||||
t.Fatalf("wanted vmErr, got none")
|
||||
}
|
||||
if gas == 21000 {
|
||||
t.Fatalf("unexpected gas used: %v", gas)
|
||||
}
|
||||
if len(*al) != 1 || al.StorageKeys() != 1 {
|
||||
t.Fatalf("unexpected length of accesslist: %v", len(*al))
|
||||
}
|
||||
// address changes between calls, so we can't test for it.
|
||||
if (*al)[0].Address == common.HexToAddress("0x0") {
|
||||
t.Fatalf("unexpected address: %v", (*al)[0].Address)
|
||||
}
|
||||
if (*al)[0].StorageKeys[0] != common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000081") {
|
||||
t.Fatalf("unexpected storage key: %v", (*al)[0].StorageKeys[0])
|
||||
}
|
||||
}
|
||||
|
||||
func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) {
|
||||
ec := New(client)
|
||||
ethcl := ethclient.NewClient(client)
|
||||
result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Address != addr {
|
||||
t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr)
|
||||
}
|
||||
// test nonce
|
||||
if nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce {
|
||||
t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce)
|
||||
}
|
||||
// test balance
|
||||
if balance, _ := ethcl.BalanceAt(context.Background(), addr, nil); result.Balance.Cmp(balance) != 0 {
|
||||
t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance)
|
||||
}
|
||||
// test storage
|
||||
if len(result.StorageProof) != 1 {
|
||||
t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof))
|
||||
}
|
||||
for _, proof := range result.StorageProof {
|
||||
if proof.Key != testSlot.String() {
|
||||
t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key)
|
||||
}
|
||||
slotValue, _ := ethcl.StorageAt(context.Background(), addr, common.HexToHash(proof.Key), nil)
|
||||
if have, want := common.BigToHash(proof.Value), common.BytesToHash(slotValue); have != want {
|
||||
t.Fatalf("addr %x, invalid storage proof value: have: %v, want: %v", addr, have, want)
|
||||
}
|
||||
}
|
||||
// test code
|
||||
code, _ := ethcl.CodeAt(context.Background(), addr, nil)
|
||||
if have, want := result.CodeHash, crypto.Keccak256Hash(code); have != want {
|
||||
t.Fatalf("codehash wrong, have %v want %v ", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetProofCanonicalizeKeys(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
|
||||
// Tests with non-canon input for storage keys.
|
||||
// Here we check that the storage key is canonicalized.
|
||||
result, err := ec.GetProof(context.Background(), testAddr, []string{"0x0dEadbeef"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.StorageProof[0].Key != "0xdeadbeef" {
|
||||
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
||||
}
|
||||
if result, err = ec.GetProof(context.Background(), testAddr, []string{"0x000deadbeef"}, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.StorageProof[0].Key != "0xdeadbeef" {
|
||||
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
||||
}
|
||||
|
||||
// If the requested storage key is 32 bytes long, it will be returned as is.
|
||||
hashSizedKey := "0x00000000000000000000000000000000000000000000000000000000deadbeef"
|
||||
result, err = ec.GetProof(context.Background(), testAddr, []string{hashSizedKey}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.StorageProof[0].Key != hashSizedKey {
|
||||
t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key)
|
||||
}
|
||||
}
|
||||
|
||||
func testGetProofNonExistent(t *testing.T, client *rpc.Client) {
|
||||
addr := common.HexToAddress("0x0001")
|
||||
ec := New(client)
|
||||
result, err := ec.GetProof(context.Background(), addr, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.Address != addr {
|
||||
t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr)
|
||||
}
|
||||
// test nonce
|
||||
if result.Nonce != 0 {
|
||||
t.Fatalf("invalid nonce, want: %v got: %v", 0, result.Nonce)
|
||||
}
|
||||
// test balance
|
||||
if result.Balance.Cmp(big.NewInt(0)) != 0 {
|
||||
t.Fatalf("invalid balance, want: %v got: %v", 0, result.Balance)
|
||||
}
|
||||
// test storage
|
||||
if have := len(result.StorageProof); have != 0 {
|
||||
t.Fatalf("invalid storage proof, want 0 proof, got %v proof(s)", have)
|
||||
}
|
||||
// test codeHash
|
||||
if have, want := result.CodeHash, (common.Hash{}); have != want {
|
||||
t.Fatalf("codehash wrong, have %v want %v ", have, want)
|
||||
}
|
||||
// test codeHash
|
||||
if have, want := result.StorageHash, (common.Hash{}); have != want {
|
||||
t.Fatalf("storagehash wrong, have %v want %v ", have, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testGCStats(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
_, err := ec.GCStats(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testMemStats(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
stats, err := ec.MemStats(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stats.Alloc == 0 {
|
||||
t.Fatal("Invalid mem stats retrieved")
|
||||
}
|
||||
}
|
||||
|
||||
func testGetNodeInfo(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
info, err := ec.GetNodeInfo(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.Name == "" {
|
||||
t.Fatal("Invalid node info retrieved")
|
||||
}
|
||||
}
|
||||
|
||||
func testSetHead(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
err := ec.SetHead(context.Background(), big.NewInt(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
ethcl := ethclient.NewClient(client)
|
||||
// Subscribe to Transactions
|
||||
ch := make(chan common.Hash)
|
||||
ec.SubscribePendingTransactions(context.Background(), ch)
|
||||
// Send a transaction
|
||||
chainID, err := ethcl.ChainID(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create transaction
|
||||
tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
|
||||
signer := types.LatestSignerForChainID(chainID)
|
||||
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedTx, err := tx.WithSignature(signer, signature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Send transaction
|
||||
err = ethcl.SendTransaction(context.Background(), signedTx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that the transaction was sent over the channel
|
||||
hash := <-ch
|
||||
if hash != signedTx.Hash() {
|
||||
t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
func testSubscribeFullPendingTransactions(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
ethcl := ethclient.NewClient(client)
|
||||
// Subscribe to Transactions
|
||||
ch := make(chan *types.Transaction)
|
||||
ec.SubscribeFullPendingTransactions(context.Background(), ch)
|
||||
// Send a transaction
|
||||
chainID, err := ethcl.ChainID(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create transaction
|
||||
tx := types.NewTransaction(1, common.Address{1}, big.NewInt(1), 22000, big.NewInt(1), nil)
|
||||
signer := types.LatestSignerForChainID(chainID)
|
||||
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedTx, err := tx.WithSignature(signer, signature)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Send transaction
|
||||
err = ethcl.SendTransaction(context.Background(), signedTx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Check that the transaction was sent over the channel
|
||||
tx = <-ch
|
||||
if tx.Hash() != signedTx.Hash() {
|
||||
t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
func testCallContract(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 21000,
|
||||
GasPrice: big.NewInt(1000000000),
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
// CallContract without override
|
||||
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// CallContract with override
|
||||
override := OverrideAccount{
|
||||
Nonce: 1,
|
||||
}
|
||||
mapAcc := make(map[common.Address]OverrideAccount)
|
||||
mapAcc[testAddr] = override
|
||||
if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideAccountMarshal(t *testing.T) {
|
||||
om := map[common.Address]OverrideAccount{
|
||||
{0x11}: {
|
||||
// Zero-valued nonce is not overridden, but simply dropped by the encoder.
|
||||
Nonce: 0,
|
||||
},
|
||||
{0xaa}: {
|
||||
Nonce: 5,
|
||||
},
|
||||
{0xbb}: {
|
||||
Code: []byte{1},
|
||||
},
|
||||
{0xcc}: {
|
||||
// 'code', 'balance', 'state' should be set when input is
|
||||
// a non-nil but empty value.
|
||||
Code: []byte{},
|
||||
Balance: big.NewInt(0),
|
||||
State: map[common.Hash]common.Hash{},
|
||||
// For 'stateDiff' the behavior is different, empty map
|
||||
// is ignored because it makes no difference.
|
||||
StateDiff: map[common.Hash]common.Hash{},
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.MarshalIndent(&om, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expected := `{
|
||||
"0x1100000000000000000000000000000000000000": {},
|
||||
"0xaa00000000000000000000000000000000000000": {
|
||||
"nonce": "0x5"
|
||||
},
|
||||
"0xbb00000000000000000000000000000000000000": {
|
||||
"code": "0x01"
|
||||
},
|
||||
"0xcc00000000000000000000000000000000000000": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"state": {}
|
||||
}
|
||||
}`
|
||||
|
||||
if string(marshalled) != expected {
|
||||
t.Error("wrong output:", string(marshalled))
|
||||
t.Error("want:", expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockOverridesMarshal(t *testing.T) {
|
||||
for i, tt := range []struct {
|
||||
bo BlockOverrides
|
||||
want string
|
||||
}{
|
||||
{
|
||||
bo: BlockOverrides{},
|
||||
want: `{}`,
|
||||
},
|
||||
{
|
||||
bo: BlockOverrides{
|
||||
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
||||
},
|
||||
want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`,
|
||||
},
|
||||
{
|
||||
bo: BlockOverrides{
|
||||
Number: big.NewInt(1),
|
||||
Difficulty: big.NewInt(2),
|
||||
Time: 3,
|
||||
GasLimit: 4,
|
||||
BaseFee: big.NewInt(5),
|
||||
},
|
||||
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`,
|
||||
},
|
||||
} {
|
||||
marshalled, err := json.Marshal(&tt.bo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if string(marshalled) != tt.want {
|
||||
t.Errorf("Testcase #%d failed. expected\n%s\ngot\n%s", i, tt.want, string(marshalled))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
|
||||
ec := New(client)
|
||||
msg := ethereum.CallMsg{
|
||||
From: testAddr,
|
||||
To: &common.Address{},
|
||||
Gas: 50000,
|
||||
GasPrice: big.NewInt(1000000000),
|
||||
Value: big.NewInt(1),
|
||||
}
|
||||
override := OverrideAccount{
|
||||
// Returns coinbase address.
|
||||
Code: common.FromHex("0x41806000526014600cf3"),
|
||||
}
|
||||
mapAcc := make(map[common.Address]OverrideAccount)
|
||||
mapAcc[common.Address{}] = override
|
||||
res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) {
|
||||
t.Fatalf("unexpected result: %x", res)
|
||||
}
|
||||
|
||||
// Now test with block overrides
|
||||
bo := BlockOverrides{
|
||||
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
|
||||
}
|
||||
res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) {
|
||||
t.Fatalf("unexpected result: %x", res)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright 2017 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 ethclient
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// senderFromServer is a types.Signer that remembers the sender address returned by the RPC
|
||||
// server. It is stored in the transaction's sender address cache to avoid an additional
|
||||
// request in TransactionSender.
|
||||
type senderFromServer struct {
|
||||
addr common.Address
|
||||
blockhash common.Hash
|
||||
}
|
||||
|
||||
var errNotCached = errors.New("sender not cached")
|
||||
|
||||
func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) {
|
||||
// Use types.Sender for side-effect to store our signer into the cache.
|
||||
types.Sender(&senderFromServer{addr, block}, tx)
|
||||
}
|
||||
|
||||
func (s *senderFromServer) Equal(other types.Signer) bool {
|
||||
os, ok := other.(*senderFromServer)
|
||||
return ok && os.blockhash == s.blockhash
|
||||
}
|
||||
|
||||
func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) {
|
||||
if s.addr == (common.Address{}) {
|
||||
return common.Address{}, errNotCached
|
||||
}
|
||||
return s.addr, nil
|
||||
}
|
||||
|
||||
func (s *senderFromServer) ChainID() *big.Int {
|
||||
panic("can't sign with senderFromServer")
|
||||
}
|
||||
func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash {
|
||||
panic("can't sign with senderFromServer")
|
||||
}
|
||||
func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||
panic("can't sign with senderFromServer")
|
||||
}
|
||||
Loading…
Reference in a new issue