From 5b2aa276ee3cc0046a7f5cc855cf623f50deccd8 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Wed, 4 Apr 2018 20:25:55 +0200 Subject: [PATCH] Implement EthereumParser.PackTx using protobuf --- Gopkg.lock | 2 +- Gopkg.toml | 4 + bchain/coins/eth/ethparser.go | 161 +++++++++++++++++++++++++- bchain/coins/eth/ethparser_test.go | 57 ++++++++++ bchain/coins/eth/ethrpc.go | 81 ------------- bchain/coins/eth/tx.pb.go | 175 +++++++++++++++++++++++++++++ bchain/coins/eth/tx.proto | 19 ++++ 7 files changed, 414 insertions(+), 85 deletions(-) create mode 100644 bchain/coins/eth/tx.pb.go create mode 100644 bchain/coins/eth/tx.proto diff --git a/Gopkg.lock b/Gopkg.lock index fb28e79a..4bab6d77 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index 54e4074c..a70bb0c3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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" diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 3d9038e6..6a0c901b 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -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) { diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index b38701db..93c1b012 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -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) + } + }) + } +} diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index e5d2de8c..f6a5d6b1 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -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) diff --git a/bchain/coins/eth/tx.pb.go b/bchain/coins/eth/tx.pb.go new file mode 100644 index 00000000..17b0860c --- /dev/null +++ b/bchain/coins/eth/tx.pb.go @@ -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, +} diff --git a/bchain/coins/eth/tx.proto b/bchain/coins/eth/tx.proto new file mode 100644 index 00000000..c7ed0fa9 --- /dev/null +++ b/bchain/coins/eth/tx.proto @@ -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; + } \ No newline at end of file