diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 3b2193d1..cf2eae50 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -3,6 +3,7 @@ package eth import ( "blockbook/bchain" "context" + "encoding/hex" "encoding/json" "fmt" "math/big" @@ -197,15 +198,21 @@ func (b *EthRPC) computeConfirmations(n uint64) (uint32, error) { } type rpcTransaction struct { - tx *ethtypes.Transaction - txExtraInfo -} - -type txExtraInfo struct { - BlockNumber *string + AccountNonce string `json:"nonce" gencodec:"required"` + Price string `json:"gasPrice" gencodec:"required"` + GasLimit string `json:"gas" gencodec:"required"` + To string `json:"to" rlp:"nil"` // nil means contract creation + Value string `json:"value" gencodec:"required"` + Payload string `json:"input" gencodec:"required"` + Hash ethcommon.Hash `json:"hash" rlp:"-"` + BlockNumber string BlockHash ethcommon.Hash - From ethcommon.Address + From string TransactionIndex string `json:"transactionIndex"` + // Signature values + V string `json:"v" gencodec:"required"` + R string `json:"r" gencodec:"required"` + S string `json:"s" gencodec:"required"` } type rpcBlock struct { @@ -214,22 +221,39 @@ type rpcBlock struct { UncleHashes []ethcommon.Hash `json:"uncles"` } -func ethTxToTx(rtx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) { - txid := ethHashToHash(rtx.tx.Hash()) - n, err := strconv.ParseInt(rtx.TransactionIndex, 16, 64) +func ethNumber(n string) (int64, error) { + if len(n) > 2 { + return strconv.ParseInt(n[2:], 16, 64) + } + return 0, errors.Errorf("Not a number: '%v'", n) +} + +func ethTxToTx(tx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) { + txid := ethHashToHash(tx.Hash) + n, err := ethNumber(tx.TransactionIndex) if err != nil { return nil, err } var from, to string - ethTo := rtx.tx.To() - if ethTo != nil { - to = ethTo.Hex()[2:] + if len(tx.To) > 2 { + to = tx.To[2:] } - from = rtx.From.Hex()[2:] + if len(tx.From) > 2 { + from = tx.From[2:] + } + v, err := ethNumber(tx.Value) + if err != nil { + return nil, err + } + b, err := json.Marshal(tx) + if err != nil { + return nil, err + } + h := hex.EncodeToString(b) return &bchain.Tx{ Blocktime: blocktime, Confirmations: confirmations, - // Hex + Hex: h, // LockTime Time: blocktime, Txid: txid, @@ -246,7 +270,7 @@ func ethTxToTx(rtx *rpcTransaction, blocktime int64, confirmations uint32) (*bch Vout: []bchain.Vout{ { N: uint32(n), - Value: float64(rtx.tx.Value().Int64()), + Value: float64(v), ScriptPubKey: bchain.ScriptPubKey{ // Hex Addresses: []string{to}, @@ -307,29 +331,29 @@ func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() - var json *rpcTransaction - err := b.rpc.CallContext(ctx, &json, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) + var tx *rpcTransaction + err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) if err != nil { return nil, err - } else if json == nil { + } else if tx == nil { return nil, ethereum.NotFound - } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { + } else if tx.R == "" { return nil, fmt.Errorf("server returned transaction without signature") } var btx *bchain.Tx - if json.BlockNumber == nil { + if tx.BlockNumber == "" { // mempool tx - btx, err = ethTxToTx(json, 0, 0) + btx, err = ethTxToTx(tx, 0, 0) if err != nil { return nil, err } } else { // non mempool tx - we must read the block header to get the block time - n, err := strconv.ParseInt((*json.BlockNumber)[2:], 16, 64) + n, err := ethNumber(tx.BlockNumber) if err != nil { return nil, err } - h, err := b.client.HeaderByHash(ctx, json.BlockHash) + h, err := b.client.HeaderByHash(ctx, tx.BlockHash) if err != nil { return nil, err } @@ -337,7 +361,7 @@ func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) { if err != nil { return nil, err } - btx, err = ethTxToTx(json, h.Time.Int64(), confirmations) + btx, err = ethTxToTx(tx, h.Time.Int64(), confirmations) if err != nil { return nil, err } diff --git a/bchain/coins/eth/ethrpc_test.go b/bchain/coins/eth/ethrpc_test.go index 277eec0b..2f8631fe 100644 --- a/bchain/coins/eth/ethrpc_test.go +++ b/bchain/coins/eth/ethrpc_test.go @@ -8,21 +8,26 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" ) var rpcURL = "ws://10.34.3.4:18546" -var client *ethclient.Client +var ethClient *ethclient.Client +var ethRpcClient *rpc.Client func setupEthRPC() *EthRPC { - if client == nil { - ec, err := ethclient.Dial(rpcURL) + if ethClient == nil { + rc, err := rpc.Dial(rpcURL) if err != nil { panic(err) } - client = ec + ec := ethclient.NewClient(rc) + ethRpcClient = rc + ethClient = ec } return &EthRPC{ - client: client, + client: ethClient, + rpc: ethRpcClient, timeout: time.Duration(25) * time.Second, rpcURL: "ws://10.34.3.4:18546", } @@ -231,11 +236,12 @@ func TestEthRPC_GetBlock(t *testing.T) { height uint32 } tests := []struct { - name string - fields fields - args args - want *bchain.Block - wantErr bool + name string + fields fields + args args + want *bchain.Block + wantTxCount int + wantErr bool }{ { name: "2870000", @@ -252,6 +258,7 @@ func TestEthRPC_GetBlock(t *testing.T) { Confirmations: int(uint32(bh.Number.Uint64()) - 2870000), }, }, + wantTxCount: 12, }, } for _, tt := range tests { @@ -261,8 +268,114 @@ func TestEthRPC_GetBlock(t *testing.T) { 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) + if got.Hash != tt.want.Hash { + t.Errorf("EthRPC.GetBlock().Hash = %v, want %v", got.Hash, tt.want.Hash) + return + } + if got.Height != tt.want.Height { + t.Errorf("EthRPC.GetBlock().Height = %v, want %v", got.Height, tt.want.Height) + return + } + if got.Confirmations != tt.want.Confirmations { + t.Errorf("EthRPC.GetBlock().Confirmations = %v, want %v", got.Confirmations, tt.want.Confirmations) + return + } + if len(got.Txs) != tt.wantTxCount { + t.Errorf("EthRPC.GetBlock().Txs = %v, want %v", len(got.Txs), tt.wantTxCount) + return + } + }) + } +} + +func TestEthRPC_GetTransaction(t *testing.T) { + bh, err := setupEthRPC().getBestHeader() + if err != nil { + panic(err) + } + type fields struct { + b *EthRPC + } + type args struct { + txid string + } + tests := []struct { + name string + fields fields + args args + want *bchain.Tx + wantErr bool + }{ + { + name: "1", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + txid: "e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d", + }, + want: &bchain.Tx{ + Blocktime: 1521515026, + Confirmations: uint32(bh.Number.Uint64()) - 2870000, + Hex: "7b226e6f6e6365223a2230783239666165222c226761735072696365223a223078313261303566323030222c22676173223a2230786462626130222c22746f223a22307836383262373930336131313039386366373730633761656634616130326138356233663336303161222c2276616c7565223a22307830222c22696e707574223a223078663032356361616630303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030323235222c2268617368223a22307865366231363864366262336438656437386530336462663832386236626664316662363133663665313239636261363234393634393834353533373234633564222c22426c6f636b4e756d626572223a223078326263616630222c22426c6f636b48617368223a22307865636364366230303331303135613139636237643465313066323835393062613635613661353461643162616133323262353066653561643136393033383935222c2246726f6d223a22307864616363396336313735346130633436313666633533323364633934366538396562323732333032222c227472616e73616374696f6e496e646578223a22307831222c2276223a2230783162222c2272223a22307831626434306133313132326330333931386466366431363664373430613661336132326630386132353933346365623136383863363239373736363163383063222c2273223a22307836303766626331356331663739393561343235386635613962636363363362303430333632643139393164356566653133363163353632323265346361383966227d", + Time: 1521515026, + Txid: "e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d", + Vin: []bchain.Vin{ + { + Addresses: []string{"dacc9c61754a0c4616fc5323dc946e89eb272302"}, + }, + }, + Vout: []bchain.Vout{ + { + N: uint32(1), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"682b7903a11098cf770c7aef4aa02a85b3f3601a"}, + }, + Value: 0, + }, + }, + }, + }, + { + name: "1", + fields: fields{ + b: setupEthRPC(), + }, + args: args{ + txid: "cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + }, + want: &bchain.Tx{ + Blocktime: 1521533434, + Confirmations: uint32(bh.Number.Uint64()) - 2871048, + Hex: "7b226e6f6e6365223a22307862323663222c226761735072696365223a223078343330653233343030222c22676173223a22307835323038222c22746f223a22307835353565653131666264646330653439613962616233353861383934316164393566666462343866222c2276616c7565223a22307831626330313539643533306536303030222c22696e707574223a223078222c2268617368223a22307863643634373135313535326235313332623261656637633962653030646336663733616663353930316464653135376161623133313333356261616138353362222c22426c6f636b4e756d626572223a223078326263663038222c22426c6f636b48617368223a22307863303266396632623736633265393537643464656139643030366263643636356239303462613866383461653466343836373561383662373536326461366239222c2246726f6d223a22307833653361336436396463363662613130373337663533316564303838393534613965633839643937222c227472616e73616374696f6e496e646578223a22307861222c2276223a2230783239222c2272223a22307866373136316331373064343335373361643963386437303163646166373134666632613534386135363262306463363339323330643137383839666364343035222c2273223a22307833633439373766633930333835613237656661303033326531376234396664353735623238323663623536653364316563663231353234663261393466393135227d", + Time: 1521533434, + Txid: "cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + Vin: []bchain.Vin{ + { + Addresses: []string{"3e3a3d69dc66ba10737f531ed088954a9ec89d97"}, + }, + }, + Vout: []bchain.Vout{ + { + N: uint32(10), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"555ee11fbddc0e49a9bab358a8941ad95ffdb48f"}, + }, + Value: 1.999622e+18, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.fields.b.GetTransaction(tt.args.txid) + if (err != nil) != tt.wantErr { + t.Errorf("EthRPC.GetTransaction() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("EthRPC.GetTransaction() = %v, want %v", got, tt.want) } }) }