package hmyclient import ( "context" "encoding/json" "fmt" "math/big" types2 "github.com/harmony-one/harmony/staking/types" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/harmony-one/harmony/block" "github.com/harmony-one/harmony/core/types" ) // 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) } 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 client func (c *Client) Close() { c.c.Close() } // BlockNumber returns the block height. func (c *Client) BlockNumber(ctx context.Context) (hexutil.Uint64, error) { var raw json.RawMessage err := c.c.CallContext(ctx, &raw, "hmy_blockNumber") if err != nil { return 0, err } else if len(raw) == 0 { return 0, ethereum.NotFound } var blockNumber hexutil.Uint64 if err := json.Unmarshal(raw, &blockNumber); err != nil { return 0, err } return blockNumber, nil } // 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 (c *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return c.getBlock(ctx, "hmy_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 (c *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { return c.getBlock(ctx, "hmy_getBlockByNumber", toBlockNumArg(number), true) } // NetworkID returns the network ID (also known as the chain ID) for this chain. func (c *Client) NetworkID(ctx context.Context) (*big.Int, error) { version := new(big.Int) var ver string if err := c.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 } func (c *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { var raw json.RawMessage err := c.c.CallContext(ctx, &raw, method, args...) if err != nil { return nil, err } else if len(raw) == 0 { return nil, ethereum.NotFound } // Decode header and transactions. var head *block.Header var body rpcBlock if err := json.Unmarshal(raw, &head); err != nil { return nil, err } if err := json.Unmarshal(raw, &body); err != nil { return nil, err } // Quick-verify transaction. This mostly helps with debugging the server. if head.TxHash() == types.EmptyRootHash && len(body.Transactions) > 0 { return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions") } if head.TxHash() != types.EmptyRootHash && len(body.Transactions) == 0 { return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions") } // 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 } stakingTxs := make([]*types2.StakingTransaction, len(body.StakingTransactions)) // TODO: add staking txns return types.NewBlockWithHeader(head).WithBody(txs, stakingTxs, []*block.Header{}, nil), nil } func toBlockNumArg(number *big.Int) string { if number == nil { return "latest" } return hexutil.EncodeBig(number) }