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