Implement EthereumParser.PackTx using protobuf

This commit is contained in:
Martin Boehm 2018-04-04 20:25:55 +02:00
parent 9fdc9ec4ac
commit 5b2aa276ee
7 changed files with 414 additions and 85 deletions

2
Gopkg.lock generated
View File

@ -184,6 +184,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "82a83b024230447e13a6c44c1682be3009d9309da76ea6f273092ee44bf3d6d6"
inputs-digest = "4b02d441181efce194d638a8c5ca005955d5cc4e4ff1f23d304429d415339ec7"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -68,3 +68,7 @@
[[constraint]]
name = "github.com/ethereum/go-ethereum"
version = "1.8.2"
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.0.0"

View File

@ -3,9 +3,97 @@ package eth
import (
"blockbook/bchain"
"encoding/hex"
"errors"
"encoding/json"
"strconv"
"github.com/ethereum/go-ethereum/common/hexutil"
proto "github.com/golang/protobuf/proto"
"github.com/juju/errors"
ethcommon "github.com/ethereum/go-ethereum/common"
)
type rpcTransaction struct {
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 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 {
Hash ethcommon.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []ethcommon.Hash `json:"uncles"`
}
func ethHashToHash(h ethcommon.Hash) string {
return h.Hex()[2:]
}
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)
var fa, ta []string
if len(tx.From) > 2 {
fa = []string{tx.From[2:]}
}
if len(tx.To) > 2 {
ta = []string{tx.To[2:]}
}
// temporarily, the complete rpcTransaction is marshalled and hex encoded to bchain.Tx.Hex
b, err := json.Marshal(tx)
if err != nil {
return nil, err
}
h := hex.EncodeToString(b)
return &bchain.Tx{
Blocktime: blocktime,
Confirmations: confirmations,
Hex: h,
// LockTime
Time: blocktime,
Txid: txid,
Vin: []bchain.Vin{
{
Addresses: fa,
// Coinbase
// ScriptSig
// Sequence
// Txid
// Vout
},
},
Vout: []bchain.Vout{
{
N: 0, // there is always up to one To address
// Value - cannot set, it does not fit precisely to float64
ScriptPubKey: bchain.ScriptPubKey{
// Hex
Addresses: ta,
},
},
},
}, nil
}
type EthereumParser struct {
}
@ -26,7 +114,7 @@ func (p *EthereumParser) GetAddrIDFromAddress(address string) ([]byte, error) {
if len(address) == 0 {
return nil, bchain.ErrAddressMissing
}
return nil, errors.New("Invalid address")
return nil, errors.Errorf("Invalid address '%v'", address)
}
if len(address)&1 == 1 {
address = "0" + address
@ -50,8 +138,75 @@ func (p *EthereumParser) ParseBlock(b []byte) (*bchain.Block, error) {
return nil, errors.New("ParseBlock: not implemented")
}
func hexDecode(s string) ([]byte, error) {
b, err := hexutil.Decode(s)
if err != nil && err != hexutil.ErrEmptyString {
return nil, err
}
return b, nil
}
func hexDecodeBig(s string) ([]byte, error) {
b, err := hexutil.DecodeBig(s)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
return nil, errors.New("PackTx: not implemented")
b, err := hex.DecodeString(tx.Hex)
if err != nil {
return nil, err
}
var r rpcTransaction
var n uint64
err = json.Unmarshal(b, &r)
if err != nil {
return nil, err
}
pt := &ProtoTransaction{}
if pt.AccountNonce, err = hexutil.DecodeUint64(r.AccountNonce); err != nil {
return nil, errors.Annotatef(err, "AccountNonce %v", r.AccountNonce)
}
if n, err = hexutil.DecodeUint64(r.BlockNumber); err != nil {
return nil, errors.Annotatef(err, "BlockNumber %v", r.BlockNumber)
}
pt.BlockNumber = uint32(n)
pt.BlockTime = uint64(blockTime)
if pt.From, err = hexDecode(r.From); err != nil {
return nil, errors.Annotatef(err, "From %v", r.From)
}
if pt.GasLimit, err = hexutil.DecodeUint64(r.GasLimit); err != nil {
return nil, errors.Annotatef(err, "GasLimit %v", r.GasLimit)
}
pt.Hash = r.Hash.Bytes()
if pt.Payload, err = hexDecode(r.Payload); err != nil {
return nil, errors.Annotatef(err, "Payload %v", r.Payload)
}
if pt.Price, err = hexDecodeBig(r.Price); err != nil {
return nil, errors.Annotatef(err, "Price %v", r.Price)
}
if pt.R, err = hexDecode(r.R); err != nil {
return nil, errors.Annotatef(err, "R %v", r.R)
}
if pt.S, err = hexDecode(r.S); err != nil {
return nil, errors.Annotatef(err, "S %v", r.S)
}
if pt.V, err = hexDecode(r.V); err != nil {
return nil, errors.Annotatef(err, "V %v", r.V)
}
if pt.To, err = hexDecode(r.To); err != nil {
return nil, errors.Annotatef(err, "To %v", r.To)
}
if n, err = hexutil.DecodeUint64(r.TransactionIndex); err != nil {
return nil, errors.Annotatef(err, "TransactionIndex %v", r.TransactionIndex)
}
pt.TransactionIndex = uint32(n)
if pt.Value, err = hexDecodeBig(r.Value); err != nil {
return nil, errors.Annotatef(err, "Value %v", r.Value)
}
return proto.Marshal(pt)
}
func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {

View File

@ -1,6 +1,7 @@
package eth
import (
"blockbook/bchain"
"encoding/hex"
"reflect"
"testing"
@ -59,3 +60,59 @@ func TestEthParser_GetAddrIDFromAddress(t *testing.T) {
})
}
}
func TestEthereumParser_PackTx(t *testing.T) {
type args struct {
tx *bchain.Tx
height uint32
blockTime int64
}
tests := []struct {
name string
p *EthereumParser
args args
want string
wantErr bool
}{
{
name: "1",
args: args{
tx: &bchain.Tx{
Blocktime: 1521515026,
Hex: "7b226e6f6e6365223a2230783239666165222c226761735072696365223a223078313261303566323030222c22676173223a2230786462626130222c22746f223a22307836383262373930336131313039386366373730633761656634616130326138356233663336303161222c2276616c7565223a22307830222c22696e707574223a223078663032356361616630303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030323235222c2268617368223a22307865366231363864366262336438656437386530336462663832386236626664316662363133663665313239636261363234393634393834353533373234633564222c22426c6f636b4e756d626572223a223078326263616630222c22426c6f636b48617368223a22307865636364366230303331303135613139636237643465313066323835393062613635613661353461643162616133323262353066653561643136393033383935222c2246726f6d223a22307864616363396336313735346130633436313666633533323364633934366538396562323732333032222c227472616e73616374696f6e496e646578223a22307831222c2276223a2230783162222c2272223a22307831626434306133313132326330333931386466366431363664373430613661336132326630386132353933346365623136383863363239373736363163383063222c2273223a22307836303766626331356331663739393561343235386635613962636363363362303430333632643139393164356566653133363163353632323265346361383966227d",
Time: 1521515026,
Txid: "e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d",
Vin: []bchain.Vin{
{
Addresses: []string{"dacc9c61754a0c4616fc5323dc946e89eb272302"},
},
},
Vout: []bchain.Vout{
{
ScriptPubKey: bchain.ScriptPubKey{
Addresses: []string{"682b7903a11098cf770c7aef4aa02a85b3f3601a"},
},
},
},
},
height: 2870000,
blockTime: 1521515026,
},
want: "08aebf0a1205012a05f20018a0f7362a24f025caaf00000000000000000000000000000000000000000000000000000000000002253220e6b168d6bb3d8ed78e03dbf828b6bfd1fb613f6e129cba624964984553724c5d38f095af014092f4c1d5054a14682b7903a11098cf770c7aef4aa02a85b3f3601a5214dacc9c61754a0c4616fc5323dc946e89eb272302580162011b6a201bd40a31122c03918df6d166d740a6a3a22f08a25934ceb1688c62977661c80c7220607fbc15c1f7995a4258f5a9bccc63b040362d1991d5efe1361c56222e4ca89f",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &EthereumParser{}
got, err := p.PackTx(tt.args.tx, tt.args.height, tt.args.blockTime)
if (err != nil) != tt.wantErr {
t.Errorf("EthereumParser.PackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
h := hex.EncodeToString(got)
if !reflect.DeepEqual(h, tt.want) {
t.Errorf("EthereumParser.PackTx() = %v, want %v", h, tt.want)
}
})
}
}

View File

@ -3,11 +3,9 @@ package eth
import (
"blockbook/bchain"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"strconv"
"sync"
"time"
@ -199,10 +197,6 @@ func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
return b.bestHeader, nil
}
func ethHashToHash(h ethcommon.Hash) string {
return h.Hex()[2:]
}
func (b *EthereumRPC) GetBestBlockHash() (string, error) {
h, err := b.getBestHeader()
if err != nil {
@ -273,81 +267,6 @@ func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) {
return uint32(bn - n), nil
}
type rpcTransaction struct {
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 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 {
Hash ethcommon.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []ethcommon.Hash `json:"uncles"`
}
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)
var fa, ta []string
if len(tx.From) > 2 {
fa = []string{tx.From[2:]}
}
if len(tx.To) > 2 {
ta = []string{tx.To[2:]}
}
b, err := json.Marshal(tx)
if err != nil {
return nil, err
}
h := hex.EncodeToString(b)
return &bchain.Tx{
Blocktime: blocktime,
Confirmations: confirmations,
Hex: h,
// LockTime
Time: blocktime,
Txid: txid,
Vin: []bchain.Vin{
{
Addresses: fa,
// Coinbase
// ScriptSig
// Sequence
// Txid
// Vout
},
},
Vout: []bchain.Vout{
{
N: 0, // there is always up to one To address
// Value - cannot set, it does not fit precisely to float64
ScriptPubKey: bchain.ScriptPubKey{
// Hex
Addresses: ta,
},
},
},
}, nil
}
// GetBlock returns block with given hash or height, hash has precedence if both passed
func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)

175
bchain/coins/eth/tx.pb.go Normal file
View File

@ -0,0 +1,175 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: tx.proto
/*
Package eth is a generated protocol buffer package.
It is generated from these files:
tx.proto
It has these top-level messages:
ProtoTransaction
*/
package eth
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type ProtoTransaction struct {
AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"`
Price []byte `protobuf:"bytes,2,opt,name=Price,proto3" json:"Price,omitempty"`
GasLimit uint64 `protobuf:"varint,3,opt,name=GasLimit" json:"GasLimit,omitempty"`
Value []byte `protobuf:"bytes,4,opt,name=Value,proto3" json:"Value,omitempty"`
Payload []byte `protobuf:"bytes,5,opt,name=Payload,proto3" json:"Payload,omitempty"`
Hash []byte `protobuf:"bytes,6,opt,name=Hash,proto3" json:"Hash,omitempty"`
BlockNumber uint32 `protobuf:"varint,7,opt,name=BlockNumber" json:"BlockNumber,omitempty"`
BlockTime uint64 `protobuf:"varint,8,opt,name=BlockTime" json:"BlockTime,omitempty"`
To []byte `protobuf:"bytes,9,opt,name=To,proto3" json:"To,omitempty"`
From []byte `protobuf:"bytes,10,opt,name=From,proto3" json:"From,omitempty"`
TransactionIndex uint32 `protobuf:"varint,11,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"`
V []byte `protobuf:"bytes,12,opt,name=V,proto3" json:"V,omitempty"`
R []byte `protobuf:"bytes,13,opt,name=R,proto3" json:"R,omitempty"`
S []byte `protobuf:"bytes,14,opt,name=S,proto3" json:"S,omitempty"`
}
func (m *ProtoTransaction) Reset() { *m = ProtoTransaction{} }
func (m *ProtoTransaction) String() string { return proto.CompactTextString(m) }
func (*ProtoTransaction) ProtoMessage() {}
func (*ProtoTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *ProtoTransaction) GetAccountNonce() uint64 {
if m != nil {
return m.AccountNonce
}
return 0
}
func (m *ProtoTransaction) GetPrice() []byte {
if m != nil {
return m.Price
}
return nil
}
func (m *ProtoTransaction) GetGasLimit() uint64 {
if m != nil {
return m.GasLimit
}
return 0
}
func (m *ProtoTransaction) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *ProtoTransaction) GetPayload() []byte {
if m != nil {
return m.Payload
}
return nil
}
func (m *ProtoTransaction) GetHash() []byte {
if m != nil {
return m.Hash
}
return nil
}
func (m *ProtoTransaction) GetBlockNumber() uint32 {
if m != nil {
return m.BlockNumber
}
return 0
}
func (m *ProtoTransaction) GetBlockTime() uint64 {
if m != nil {
return m.BlockTime
}
return 0
}
func (m *ProtoTransaction) GetTo() []byte {
if m != nil {
return m.To
}
return nil
}
func (m *ProtoTransaction) GetFrom() []byte {
if m != nil {
return m.From
}
return nil
}
func (m *ProtoTransaction) GetTransactionIndex() uint32 {
if m != nil {
return m.TransactionIndex
}
return 0
}
func (m *ProtoTransaction) GetV() []byte {
if m != nil {
return m.V
}
return nil
}
func (m *ProtoTransaction) GetR() []byte {
if m != nil {
return m.R
}
return nil
}
func (m *ProtoTransaction) GetS() []byte {
if m != nil {
return m.S
}
return nil
}
func init() {
proto.RegisterType((*ProtoTransaction)(nil), "eth.ProtoTransaction")
}
func init() { proto.RegisterFile("tx.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 262 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbd, 0x6a, 0xeb, 0x40,
0x10, 0x85, 0x59, 0x59, 0xb6, 0xe5, 0xb1, 0x6c, 0xcc, 0x70, 0x8b, 0xe1, 0x92, 0x42, 0xb8, 0x12,
0x29, 0xd2, 0xe4, 0x09, 0x92, 0x22, 0x3f, 0x10, 0x8c, 0x90, 0x85, 0xfa, 0xf5, 0x7a, 0xc1, 0x22,
0x92, 0x26, 0x48, 0x2b, 0x70, 0x5e, 0x38, 0xcf, 0x11, 0x76, 0x44, 0x12, 0x87, 0x74, 0xf3, 0x7d,
0x70, 0xf6, 0x2c, 0x07, 0x22, 0x77, 0xbe, 0x79, 0xeb, 0xd8, 0x31, 0x4e, 0xac, 0x3b, 0x6d, 0x3f,
0x02, 0xd8, 0x64, 0x1e, 0x8b, 0x4e, 0xb7, 0xbd, 0x36, 0xae, 0xe2, 0x16, 0xb7, 0x10, 0xdf, 0x19,
0xc3, 0x43, 0xeb, 0x76, 0xdc, 0x1a, 0x4b, 0x2a, 0x51, 0x69, 0x98, 0xff, 0x72, 0xf8, 0x0f, 0xa6,
0x59, 0x57, 0x19, 0x4b, 0x41, 0xa2, 0xd2, 0x38, 0x1f, 0x01, 0xff, 0x43, 0xf4, 0xa8, 0xfb, 0x97,
0xaa, 0xa9, 0x1c, 0x4d, 0x24, 0xf5, 0xcd, 0x3e, 0x51, 0xea, 0x7a, 0xb0, 0x14, 0x8e, 0x09, 0x01,
0x24, 0x98, 0x67, 0xfa, 0xbd, 0x66, 0x7d, 0xa4, 0xa9, 0xf8, 0x2f, 0x44, 0x84, 0xf0, 0x49, 0xf7,
0x27, 0x9a, 0x89, 0x96, 0x1b, 0x13, 0x58, 0xde, 0xd7, 0x6c, 0x5e, 0x77, 0x43, 0x73, 0xb0, 0x1d,
0xcd, 0x13, 0x95, 0xae, 0xf2, 0x4b, 0x85, 0x57, 0xb0, 0x10, 0x2c, 0xaa, 0xc6, 0x52, 0x24, 0x5f,
0xf8, 0x11, 0xb8, 0x86, 0xa0, 0x60, 0x5a, 0xc8, 0x8b, 0x41, 0xc1, 0xbe, 0xe3, 0xa1, 0xe3, 0x86,
0x60, 0xec, 0xf0, 0x37, 0x5e, 0xc3, 0xe6, 0x62, 0x8c, 0xe7, 0xf6, 0x68, 0xcf, 0xb4, 0x94, 0xa2,
0x3f, 0x1e, 0x63, 0x50, 0x25, 0xc5, 0x12, 0x56, 0xa5, 0xa7, 0x9c, 0x56, 0x23, 0xe5, 0x9e, 0xf6,
0xb4, 0x1e, 0x69, 0x7f, 0x98, 0xc9, 0xe8, 0xb7, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x15, 0xc8,
0xe4, 0x30, 0x80, 0x01, 0x00, 0x00,
}

19
bchain/coins/eth/tx.proto Normal file
View File

@ -0,0 +1,19 @@
syntax = "proto3";
package eth;
message ProtoTransaction {
uint64 AccountNonce = 1;
bytes Price = 2;
uint64 GasLimit = 3;
bytes Value = 4;
bytes Payload = 5;
bytes Hash = 6;
uint32 BlockNumber = 7;
uint64 BlockTime = 8;
bytes To = 9;
bytes From = 10;
uint32 TransactionIndex = 11;
bytes V = 12;
bytes R = 13;
bytes S = 14;
}