From 4486795c3d4760c6f6d350176571d7a02ca60703 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Tue, 20 Mar 2018 14:43:15 +0100 Subject: [PATCH] Extend the eth rpc interface, create tests for interface --- bchain/coins/eth/ethrpc.go | 107 ++++++++++--- bchain/coins/eth/ethrpc_test.go | 269 ++++++++++++++++++++++++++++++++ 2 files changed, 351 insertions(+), 25 deletions(-) create mode 100644 bchain/coins/eth/ethrpc_test.go diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index f4da1374..e41e765e 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "math/big" + "sync" "time" "github.com/golang/glog" @@ -25,15 +26,16 @@ const ( // EthRPC is an interface to JSON-RPC eth service. type EthRPC struct { - client *ethclient.Client - timeout time.Duration - rpcURL string - Parser *EthParser - Testnet bool - Network string - Mempool *bchain.Mempool - metrics *common.Metrics - bestHeader *ethtypes.Header + client *ethclient.Client + timeout time.Duration + rpcURL string + Parser *EthParser + Testnet bool + Network string + Mempool *bchain.Mempool + metrics *common.Metrics + bestHeaderMu sync.Mutex + bestHeader *ethtypes.Header } type configuration struct { @@ -104,7 +106,8 @@ func (b *EthRPC) GetNetworkName() string { } func (b *EthRPC) getBestHeader() (*ethtypes.Header, error) { - // TODO lock + b.bestHeaderMu.Lock() + defer b.bestHeaderMu.Unlock() if b.bestHeader == nil { var err error ctx, cancel := context.WithTimeout(context.Background(), b.timeout) @@ -117,12 +120,16 @@ func (b *EthRPC) getBestHeader() (*ethtypes.Header, error) { return b.bestHeader, nil } +func ethHashToHash(h ethcommon.Hash) string { + return h.Hex()[2:] +} + func (b *EthRPC) GetBestBlockHash() (string, error) { h, err := b.getBestHeader() if err != nil { return "", err } - return h.TxHash.Hex()[2:], nil + return ethHashToHash(h.Hash()), nil } func (b *EthRPC) GetBestBlockHeight() (uint32, error) { @@ -143,33 +150,63 @@ func (b *EthRPC) GetBlockHash(height uint32) (string, error) { if err != nil { return "", err } - return h.TxHash.Hex()[2:], nil + return ethHashToHash(h.Hash()), nil } -func (b *EthRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { +func (b *EthRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) { bh, err := b.getBestHeader() if err != nil { return nil, err } - ctx, cancel := context.WithTimeout(context.Background(), b.timeout) - defer cancel() - h, err := b.client.HeaderByHash(ctx, ethcommon.StringToHash(hash)) - if err != nil { - return nil, err - } hn := uint32(h.Number.Uint64()) bn := uint32(bh.Number.Uint64()) - rv := bchain.BlockHeader{ - Hash: h.TxHash.Hex()[2:], + return &bchain.BlockHeader{ + Hash: ethHashToHash(h.Hash()), Height: hn, Confirmations: int(bn - hn), - // TODO Next tx hash + // Next + // Prev + + }, nil +} + +func (b *EthRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash)) + if err != nil { + return nil, err } - return &rv, nil + return b.ethHeaderToBlockHeader(h) } func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { - panic("not implemented") + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + bk, err := b.client.BlockByHash(ctx, ethcommon.HexToHash(hash)) + if err != nil { + return nil, err + } + // TODO maybe not the most optimal way to get the header + bbh, err := b.ethHeaderToBlockHeader(bk.Header()) + txs := bk.Transactions() + btxs := make([]bchain.Tx, len(txs)) + for i, tx := range txs { + btxs[i] = bchain.Tx{ + // Blocktime + Confirmations: uint32(bbh.Confirmations), + // Hex + // LockTime + // Time + Txid: ethHashToHash(tx.Hash()), + // Vin + } + } + bbk := bchain.Block{ + BlockHeader: *bbh, + Txs: btxs, + } + return &bbk, nil } func (b *EthRPC) GetMempool() ([]string, error) { @@ -177,7 +214,27 @@ func (b *EthRPC) GetMempool() ([]string, error) { } func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) { - panic("not implemented") + // bh, err := b.getBestHeader() + // if err != nil { + // return nil, err + // } + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + tx, _, err := b.client.TransactionByHash(ctx, ethcommon.StringToHash(txid)) + if err != nil { + return nil, err + } + btx := bchain.Tx{ + // Blocktime + // Confirmations + // Hex + // LockTime + // Time + Txid: ethHashToHash(tx.Hash()), + // Vin + // Vout + } + return &btx, nil } func (b *EthRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) { diff --git a/bchain/coins/eth/ethrpc_test.go b/bchain/coins/eth/ethrpc_test.go new file mode 100644 index 00000000..277eec0b --- /dev/null +++ b/bchain/coins/eth/ethrpc_test.go @@ -0,0 +1,269 @@ +package eth + +import ( + "blockbook/bchain" + "reflect" + "testing" + "time" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" +) + +var rpcURL = "ws://10.34.3.4:18546" +var client *ethclient.Client + +func setupEthRPC() *EthRPC { + if client == nil { + ec, err := ethclient.Dial(rpcURL) + if err != nil { + panic(err) + } + client = ec + } + return &EthRPC{ + client: client, + timeout: time.Duration(25) * time.Second, + rpcURL: "ws://10.34.3.4:18546", + } +} + +func TestEthRPC_getBestHeader(t *testing.T) { + type fields struct { + b *EthRPC + } + tests := []struct { + name string + fields fields + want *ethtypes.Header + wantErr bool + }{ + { + name: "1", + fields: fields{ + b: setupEthRPC(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := tt.fields.b.getBestHeader() + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.getBestHeader() error = %v, wantErr %v", err, tt.wantErr) + return + } + // the header is always different, do not compare what we got + }) + } +} + +func TestEthRPC_GetBestBlockHash(t *testing.T) { + type fields struct { + b *EthRPC + } + tests := []struct { + name string + fields fields + want int + wantErr bool + }{ + { + name: "1", + fields: fields{ + b: setupEthRPC(), + }, + want: 64, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetBestBlockHash() + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetBestBlockHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + // the hash is always different, compare only the length of hash + if len(got) != tt.want { + t.Errorf("EthRPC.GetBestBlockHash() = %v, len %v, want len %v", got, len(got), tt.want) + } + }) + } +} + +func TestEthRPC_GetBestBlockHeight(t *testing.T) { + type fields struct { + b *EthRPC + } + tests := []struct { + name string + fields fields + want uint32 + wantErr bool + }{ + { + name: "1", + fields: fields{ + b: setupEthRPC(), + }, + want: 1000000, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetBestBlockHeight() + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetBestBlockHeight() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got < tt.want { + t.Errorf("EthRPC.GetBestBlockHeight() = %v, want at least %v", got, tt.want) + } + }) + } +} + +func TestEthRPC_GetBlockHash(t *testing.T) { + type fields struct { + b *EthRPC + } + type args struct { + height uint32 + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "1000000", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + height: 1000000, + }, + want: "6e6b2e771a3026a1981227ab4a4c8d018edb568494f17df46bcddfa427df686e", + }, + { + name: "2870000", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + height: 2870000, + }, + want: "eccd6b0031015a19cb7d4e10f28590ba65a6a54ad1baa322b50fe5ad16903895", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetBlockHash(tt.args.height) + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetBlockHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("EthRPC.GetBlockHash() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEthRPC_GetBlockHeader(t *testing.T) { + bh, err := setupEthRPC().getBestHeader() + if err != nil { + panic(err) + } + type fields struct { + b *EthRPC + } + type args struct { + hash string + } + tests := []struct { + name string + fields fields + args args + want *bchain.BlockHeader + wantErr bool + }{ + { + name: "2870000", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + hash: "eccd6b0031015a19cb7d4e10f28590ba65a6a54ad1baa322b50fe5ad16903895", + }, + want: &bchain.BlockHeader{ + Hash: "eccd6b0031015a19cb7d4e10f28590ba65a6a54ad1baa322b50fe5ad16903895", + Height: 2870000, + Confirmations: int(uint32(bh.Number.Uint64()) - 2870000), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetBlockHeader(tt.args.hash) + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetBlockHeader() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("EthRPC.GetBlockHeader() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEthRPC_GetBlock(t *testing.T) { + bh, err := setupEthRPC().getBestHeader() + if err != nil { + panic(err) + } + type fields struct { + b *EthRPC + } + type args struct { + hash string + height uint32 + } + tests := []struct { + name string + fields fields + args args + want *bchain.Block + wantErr bool + }{ + { + name: "2870000", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + hash: "eccd6b0031015a19cb7d4e10f28590ba65a6a54ad1baa322b50fe5ad16903895", + }, + want: &bchain.Block{ + BlockHeader: bchain.BlockHeader{ + Hash: "eccd6b0031015a19cb7d4e10f28590ba65a6a54ad1baa322b50fe5ad16903895", + Height: 2870000, + Confirmations: int(uint32(bh.Number.Uint64()) - 2870000), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetBlock(tt.args.hash, tt.args.height) + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetBlock() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("EthRPC.GetBlock() = %v, want %v", got, tt.want) + } + }) + } +}