parent
1e13aa884f
commit
117ffd5039
@ -0,0 +1,57 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/rpc" |
||||||
|
"github.com/harmony-one/harmony/hmyclient" |
||||||
|
) |
||||||
|
|
||||||
|
// newRPCClient creates a rpc client with specified node URL.
|
||||||
|
func newRPCClient(url string) *rpc.Client { |
||||||
|
client, err := rpc.Dial(url) |
||||||
|
if err != nil { |
||||||
|
fmt.Errorf("Failed to connect to Ethereum node: %v", err) |
||||||
|
} |
||||||
|
return client |
||||||
|
} |
||||||
|
|
||||||
|
func main() { |
||||||
|
ctx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) |
||||||
|
defer cancelFn() |
||||||
|
rpcClient := newRPCClient("http://localhost:9500") |
||||||
|
if rpcClient == nil { |
||||||
|
fmt.Errorf("Failed to create rpc client") |
||||||
|
} |
||||||
|
client := hmyclient.NewClient(rpcClient) |
||||||
|
if client == nil { |
||||||
|
fmt.Errorf("Failed to create client") |
||||||
|
} |
||||||
|
|
||||||
|
networkID, err := client.NetworkID(ctx) |
||||||
|
if err != nil { |
||||||
|
fmt.Errorf("Failed to get net_version: %v", err) |
||||||
|
} |
||||||
|
fmt.Printf("net_version: %v\n", networkID) |
||||||
|
|
||||||
|
blockNumber, err := client.BlockNumber(ctx) |
||||||
|
if err != nil { |
||||||
|
fmt.Errorf("Failed to get hmy_blockNumber: %v", err) |
||||||
|
} |
||||||
|
fmt.Printf("hmy_blockNumber: %v\n", blockNumber) |
||||||
|
|
||||||
|
block, err := client.BlockByNumber(ctx, new(big.Int).SetUint64(uint64(blockNumber))) |
||||||
|
if err != nil { |
||||||
|
fmt.Errorf("Failed to get hmy_getBlockByNumber %v: %v", blockNumber, err) |
||||||
|
} |
||||||
|
fmt.Printf("hmy_getBlockByNumber(%v): %v\n", blockNumber, block) |
||||||
|
|
||||||
|
block, err = client.BlockByNumber(ctx, nil) |
||||||
|
if err != nil { |
||||||
|
fmt.Errorf("Failed to get block: %v", err) |
||||||
|
} |
||||||
|
fmt.Printf("hmy_getBlockByNumber(latest): %v", block) |
||||||
|
} |
@ -0,0 +1,157 @@ |
|||||||
|
package hmyclient |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/rpc" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
) |
||||||
|
|
||||||
|
// NotFound is returned by API methods if the requested item does not exist.
|
||||||
|
var NotFound = errors.New("not found") |
||||||
|
|
||||||
|
// 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, 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, NotFound |
||||||
|
} |
||||||
|
// Decode header and transactions.
|
||||||
|
var head *types.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") |
||||||
|
} |
||||||
|
// 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 := c.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), nil |
||||||
|
} |
||||||
|
|
||||||
|
func toBlockNumArg(number *big.Int) string { |
||||||
|
if number == nil { |
||||||
|
return "latest" |
||||||
|
} |
||||||
|
return hexutil.EncodeBig(number) |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
package hmyclient |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
) |
||||||
|
|
||||||
|
var errNotCached = errors.New("sender not cached") |
||||||
|
|
||||||
|
// 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 |
||||||
|
} |
||||||
|
|
||||||
|
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.blockhash == (common.Hash{}) { |
||||||
|
return common.Address{}, errNotCached |
||||||
|
} |
||||||
|
return s.addr, nil |
||||||
|
} |
||||||
|
|
||||||
|
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") |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
package hmyclient |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/harmony-one/harmony/core/types" |
||||||
|
) |
||||||
|
|
||||||
|
type rpcBlock struct { |
||||||
|
Hash common.Hash `json:"hash"` |
||||||
|
Transactions []rpcTransaction `json:"transactions"` |
||||||
|
UncleHashes []common.Hash `json:"uncles"` |
||||||
|
} |
||||||
|
|
||||||
|
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"` |
||||||
|
} |
Loading…
Reference in new issue