Extend the eth rpc interface, create tests for interface
parent
95af897e8e
commit
4486795c3d
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue