add hmyclient

pull/1217/head
Richard Liu 5 years ago
parent 1e13aa884f
commit 117ffd5039
  1. 57
      cmd/hmyclient/main.go
  2. 2
      core/types/block.go
  3. 157
      hmyclient/hmyclient.go
  4. 42
      hmyclient/signer.go
  5. 23
      hmyclient/type.go

@ -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)
}

@ -69,7 +69,7 @@ func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
}
// Header represents a block header in the Ethereum blockchain.
// Header represents a block header in the Harmony blockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`

@ -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…
Cancel
Save