From f3f7feccd2729977fa84d0296e1d0a27e36fd8c4 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 14 Dec 2020 19:35:21 -0300 Subject: [PATCH 1/8] add energi support --- bchain/coins/energi/erc20.go | 242 ++++++++ bchain/coins/energi/erc20_test.go | 204 +++++++ bchain/coins/energi/ethparser.go | 533 +++++++++++++++++ bchain/coins/energi/ethparser_test.go | 402 +++++++++++++ bchain/coins/energi/ethrpc.go | 796 ++++++++++++++++++++++++++ bchain/coins/energi/ethtx.pb.go | 261 +++++++++ bchain/coins/energi/ethtx.proto | 30 + configs/coins/energi.json | 65 +++ 8 files changed, 2533 insertions(+) create mode 100644 bchain/coins/energi/erc20.go create mode 100644 bchain/coins/energi/erc20_test.go create mode 100644 bchain/coins/energi/ethparser.go create mode 100644 bchain/coins/energi/ethparser_test.go create mode 100644 bchain/coins/energi/ethrpc.go create mode 100644 bchain/coins/energi/ethtx.pb.go create mode 100644 bchain/coins/energi/ethtx.proto create mode 100644 configs/coins/energi.json diff --git a/bchain/coins/energi/erc20.go b/bchain/coins/energi/erc20.go new file mode 100644 index 00000000..920b0588 --- /dev/null +++ b/bchain/coins/energi/erc20.go @@ -0,0 +1,242 @@ +package energi + +import ( + "bytes" + "context" + "encoding/hex" + "math/big" + "strings" + "sync" + "unicode/utf8" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/golang/glog" + "github.com/juju/errors" + "github.com/trezor/blockbook/bchain" +) + +var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"}, +{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"}, +{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"}, +{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"}, +{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"}, +{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"}, +{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"}, +{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"}, +{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"}, +{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}, +{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}, +{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"}, +{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"}, +{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` + +// doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event +const erc20TransferMethodSignature = "0xa9059cbb" +const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +const erc20NameSignature = "0x06fdde03" +const erc20SymbolSignature = "0x95d89b41" +const erc20DecimalsSignature = "0x313ce567" +const erc20BalanceOf = "0x70a08231" + +var cachedContracts = make(map[string]*bchain.Erc20Contract) +var cachedContractsMux sync.Mutex + +func addressFromPaddedHex(s string) (string, error) { + var t big.Int + var ok bool + if has0xPrefix(s) { + _, ok = t.SetString(s[2:], 16) + } else { + _, ok = t.SetString(s, 16) + } + if !ok { + return "", errors.New("Data is not a number") + } + a := ethcommon.BigToAddress(&t) + return a.String(), nil +} + +func erc20GetTransfersFromLog(logs []*rpcLog) ([]bchain.Erc20Transfer, error) { + var r []bchain.Erc20Transfer + for _, l := range logs { + if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature { + var t big.Int + _, ok := t.SetString(l.Data, 0) + if !ok { + return nil, errors.New("Data is not a number") + } + from, err := addressFromPaddedHex(l.Topics[1]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(l.Topics[2]) + if err != nil { + return nil, err + } + r = append(r, bchain.Erc20Transfer{ + Contract: EIP55AddressFromAddress(l.Address), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + Tokens: t, + }) + } + } + return r, nil +} + +func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) { + var r []bchain.Erc20Transfer + if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) { + to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)]) + if err != nil { + return nil, err + } + var t big.Int + _, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16) + if !ok { + return nil, errors.New("Data is not a number") + } + r = append(r, bchain.Erc20Transfer{ + Contract: EIP55AddressFromAddress(tx.To), + From: EIP55AddressFromAddress(tx.From), + To: EIP55AddressFromAddress(to), + Tokens: t, + }) + } + return r, nil +} + +func (b *EthereumRPC) ethCall(data, to string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var r string + err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{ + "data": data, + "to": to, + }, "latest") + if err != nil { + return "", err + } + return r, nil +} + +func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) == 64 { + var n big.Int + _, ok := n.SetString(data, 16) + if ok { + return &n + } + } + if glog.V(1) { + glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) + } + return nil +} + +func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) > 128 { + n := parseErc20NumericProperty(contractDesc, data[64:128]) + if n != nil { + l := n.Uint64() + if l > 0 && 2*int(l) <= len(data)-128 { + b, err := hex.DecodeString(data[128 : 128+2*l]) + if err == nil { + return string(b) + } + } + } + } + // allow string properties as UTF-8 data + b, err := hex.DecodeString(data) + if err == nil { + i := bytes.Index(b, []byte{0}) + if i > 32 { + i = 32 + } + if i > 0 { + b = b[:i] + } + if utf8.Valid(b) { + return string(b) + } + } + if glog.V(1) { + glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) + } + return "" +} + +// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract +func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { + cds := string(contractDesc) + cachedContractsMux.Lock() + contract, found := cachedContracts[cds] + cachedContractsMux.Unlock() + if !found { + address := EIP55Address(contractDesc) + data, err := b.ethCall(erc20NameSignature, address) + if err != nil { + // ignore the error from the eth_call - since geth v1.9.15 they changed the behavior + // and returning error "execution reverted" for some non contract addresses + // https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672 + glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address)) + return nil, nil + // return nil, errors.Annotatef(err, "erc20NameSignature %v", address) + } + name := parseErc20StringProperty(contractDesc, data) + if name != "" { + data, err = b.ethCall(erc20SymbolSignature, address) + if err != nil { + glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address)) + return nil, nil + // return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address) + } + symbol := parseErc20StringProperty(contractDesc, data) + data, err = b.ethCall(erc20DecimalsSignature, address) + if err != nil { + glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address)) + // return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address) + } + contract = &bchain.Erc20Contract{ + Contract: address, + Name: name, + Symbol: symbol, + } + d := parseErc20NumericProperty(contractDesc, data) + if d != nil { + contract.Decimals = int(uint8(d.Uint64())) + } else { + contract.Decimals = EtherAmountDecimalPoint + } + } else { + contract = nil + } + cachedContractsMux.Lock() + cachedContracts[cds] = contract + cachedContractsMux.Unlock() + } + return contract, nil +} + +// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address +func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { + addr := EIP55Address(addrDesc) + contract := EIP55Address(contractDesc) + req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] + data, err := b.ethCall(req, contract) + if err != nil { + return nil, err + } + r := parseErc20NumericProperty(contractDesc, data) + if r == nil { + return nil, errors.New("Invalid balance") + } + return r, nil +} diff --git a/bchain/coins/energi/erc20_test.go b/bchain/coins/energi/erc20_test.go new file mode 100644 index 00000000..c70a9920 --- /dev/null +++ b/bchain/coins/energi/erc20_test.go @@ -0,0 +1,204 @@ +// +build unittest + +package energi + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/trezor/blockbook/bchain" + "github.com/trezor/blockbook/tests/dbtestdata" +) + +func TestErc20_erc20GetTransfersFromLog(t *testing.T) { + tests := []struct { + name string + args []*rpcLog + want []bchain.Erc20Transfer + wantErr bool + }{ + { + name: "1", + args: []*rpcLog{ + { + Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8", + "0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2", + }, + Data: "0x0000000000000000000000000000000000000000000000000000000000000123", + }, + }, + want: []bchain.Erc20Transfer{ + { + Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96", + From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8", + To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2", + Tokens: *big.NewInt(0x123), + }, + }, + }, + { + name: "2", + args: []*rpcLog{ + { // Transfer + Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + }, + Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b", + }, + { // Transfer + Address: "0xc778417e063141139fce010982780140aa0cd5ab", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + }, + Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0", + }, + { // not Transfer + Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac", + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000", + }, + { // not Transfer + Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b", + "0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + }, + want: []bchain.Erc20Transfer{ + { + Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", + To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", + Tokens: *big.NewInt(0x6a8313d60b1f606b), + }, + { + Contract: "0xc778417e063141139fce010982780140aa0cd5ab", + From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", + To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", + Tokens: *big.NewInt(0x308fd0e798ac0), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := erc20GetTransfersFromLog(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr) + return + } + // the addresses could have different case + if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) { + t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want) + } + }) + } +} + +func TestErc20_parseErc20StringProperty(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "1", + args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000", + want: "XPLODDE", + }, + { + name: "2", + args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000", + want: "BitClave - Consumer Activity Token", + }, + { + name: "short", + args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000", + want: "Dai Stablecoin v1.0", + }, + { + name: "short2", + args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444", + want: "Dai Stablecoin v1.0 DDDDDDDDDDDD", + }, + { + name: "long", + args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + want: "Uniswap V1", + }, + { + name: "garbage", + args: "0x2234880850896048596206002535425366538144616734015984380565810000", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseErc20StringProperty(nil, tt.args) + // the addresses could have different case + if got != tt.want { + t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want) + } + }) + } +} + +func TestErc20_erc20GetTransfersFromTx(t *testing.T) { + p := NewEthereumParser(1) + b := dbtestdata.GetTestEthereumTypeBlock1(p) + bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16) + tests := []struct { + name string + args *rpcTransaction + want []bchain.Erc20Transfer + }{ + { + name: "0", + args: (b.Txs[0].CoinSpecificData.(completeTransaction)).Tx, + want: []bchain.Erc20Transfer{}, + }, + { + name: "1", + args: (b.Txs[1].CoinSpecificData.(completeTransaction)).Tx, + want: []bchain.Erc20Transfer{ + { + Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", + From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", + To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", + Tokens: *bn, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := erc20GetTransfersFromTx(tt.args) + if err != nil { + t.Errorf("erc20GetTransfersFromTx error = %v", err) + return + } + // the addresses could have different case + if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) { + t.Errorf("erc20GetTransfersFromTx = %+v, want %+v", got, tt.want) + } + }) + } +} diff --git a/bchain/coins/energi/ethparser.go b/bchain/coins/energi/ethparser.go new file mode 100644 index 00000000..d67609f2 --- /dev/null +++ b/bchain/coins/energi/ethparser.go @@ -0,0 +1,533 @@ +package energi + +import ( + "encoding/hex" + "math/big" + "strconv" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/protobuf/proto" + "github.com/juju/errors" + "github.com/trezor/blockbook/bchain" + "golang.org/x/crypto/sha3" +) + +// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length +const EthereumTypeAddressDescriptorLen = 20 + +// EtherAmountDecimalPoint defines number of decimal points in Ether amounts +const EtherAmountDecimalPoint = 18 + +// EthereumParser handle +type EthereumParser struct { + *bchain.BaseParser +} + +// NewEthereumParser returns new EthereumParser instance +func NewEthereumParser(b int) *EthereumParser { + return &EthereumParser{&bchain.BaseParser{ + BlockAddressesToKeep: b, + AmountDecimalPoint: EtherAmountDecimalPoint, + }} +} + +type rpcHeader struct { + Hash string `json:"hash"` + ParentHash string `json:"parentHash"` + Difficulty string `json:"difficulty"` + Number string `json:"number"` + Time string `json:"timestamp"` + Size string `json:"size"` + Nonce string `json:"nonce"` +} + +type rpcTransaction struct { + AccountNonce string `json:"nonce"` + GasPrice string `json:"gasPrice"` + GasLimit string `json:"gas"` + To string `json:"to"` // nil means contract creation + Value string `json:"value"` + Payload string `json:"input"` + Hash string `json:"hash"` + BlockNumber string `json:"blockNumber"` + BlockHash string `json:"blockHash,omitempty"` + From string `json:"from"` + TransactionIndex string `json:"transactionIndex"` + // Signature values - ignored + // V string `json:"v"` + // R string `json:"r"` + // S string `json:"s"` +} + +type rpcLog struct { + Address string `json:"address"` + Topics []string `json:"topics"` + Data string `json:"data"` +} + +type rpcLogWithTxHash struct { + rpcLog + Hash string `json:"transactionHash"` +} + +type rpcReceipt struct { + GasUsed string `json:"gasUsed"` + Status string `json:"status"` + Logs []*rpcLog `json:"logs"` +} + +type completeTransaction struct { + Tx *rpcTransaction `json:"tx"` + Receipt *rpcReceipt `json:"receipt,omitempty"` +} + +type rpcBlockTransactions struct { + Transactions []rpcTransaction `json:"transactions"` +} + +type rpcBlockTxids struct { + Transactions []string `json:"transactions"` +} + +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) +} + +type hash string + +type header struct { + Difficulty hexutil.Big `json:"difficulty"` + Number hexutil.Big `json:"number"` + Hash hash `json:"hash"` +} + +func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) { + txid := tx.Hash + var ( + fa, ta []string + err error + ) + if len(tx.From) > 2 { + if fixEIP55 { + tx.From = EIP55AddressFromAddress(tx.From) + } + fa = []string{tx.From} + } + if len(tx.To) > 2 { + if fixEIP55 { + tx.To = EIP55AddressFromAddress(tx.To) + } + ta = []string{tx.To} + } + if fixEIP55 && receipt != nil && receipt.Logs != nil { + for _, l := range receipt.Logs { + if len(l.Address) > 2 { + l.Address = EIP55AddressFromAddress(l.Address) + } + } + } + ct := completeTransaction{ + Tx: tx, + Receipt: receipt, + } + vs, err := hexutil.DecodeBig(tx.Value) + if err != nil { + return nil, err + } + return &bchain.Tx{ + Blocktime: blocktime, + Confirmations: confirmations, + // Hex + // 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 + ValueSat: *vs, + ScriptPubKey: bchain.ScriptPubKey{ + // Hex + Addresses: ta, + }, + }, + }, + CoinSpecificData: ct, + }, nil +} + +// GetAddrDescFromVout returns internal address representation of given transaction output +func (p *EthereumParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) { + if len(output.ScriptPubKey.Addresses) != 1 { + return nil, bchain.ErrAddressMissing + } + return p.GetAddrDescFromAddress(output.ScriptPubKey.Addresses[0]) +} + +func has0xPrefix(s string) bool { + return len(s) >= 2 && s[0] == '0' && (s[1]|32) == 'x' +} + +// GetAddrDescFromAddress returns internal address representation of given address +func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) { + // github.com/ethereum/go-ethereum/common.HexToAddress does not handle address errors, using own decoding + if has0xPrefix(address) { + address = address[2:] + } + if len(address) != EthereumTypeAddressDescriptorLen*2 { + return nil, bchain.ErrAddressMissing + } + return hex.DecodeString(address) +} + +// EIP55Address returns an EIP55-compliant hex string representation of the address +func EIP55Address(addrDesc bchain.AddressDescriptor) string { + raw := hexutil.Encode(addrDesc) + if len(raw) != 42 { + return raw + } + sha := sha3.NewLegacyKeccak256() + result := []byte(raw) + sha.Write(result[2:]) + hash := sha.Sum(nil) + + for i := 2; i < len(result); i++ { + hashByte := hash[(i-2)>>1] + if i%2 == 0 { + hashByte = hashByte >> 4 + } else { + hashByte &= 0xf + } + if result[i] > '9' && hashByte > 7 { + result[i] -= 32 + } + } + return string(result) +} + +// EIP55AddressFromAddress returns an EIP55-compliant hex string representation of the address +func EIP55AddressFromAddress(address string) string { + if has0xPrefix(address) { + address = address[2:] + } + b, err := hex.DecodeString(address) + if err != nil { + return address + } + return EIP55Address(b) +} + +// GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable +func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { + return []string{EIP55Address(addrDesc)}, true, nil +} + +// GetScriptFromAddrDesc returns output script for given address descriptor +func (p *EthereumParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]byte, error) { + return addrDesc, nil +} + +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 hexEncodeBig(b []byte) string { + var i big.Int + i.SetBytes(b) + return hexutil.EncodeBig(&i) +} + +// PackTx packs transaction to byte array +func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { + var err error + var n uint64 + r, ok := tx.CoinSpecificData.(completeTransaction) + if !ok { + return nil, errors.New("Missing CoinSpecificData") + } + pt := &ProtoCompleteTransaction{} + pt.Tx = &ProtoCompleteTransaction_TxType{} + if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil { + return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce) + } + // pt.BlockNumber = height + if n, err = hexutil.DecodeUint64(r.Tx.BlockNumber); err != nil { + return nil, errors.Annotatef(err, "BlockNumber %v", r.Tx.BlockNumber) + } + pt.BlockNumber = uint32(n) + pt.BlockTime = uint64(blockTime) + if pt.Tx.From, err = hexDecode(r.Tx.From); err != nil { + return nil, errors.Annotatef(err, "From %v", r.Tx.From) + } + if pt.Tx.GasLimit, err = hexutil.DecodeUint64(r.Tx.GasLimit); err != nil { + return nil, errors.Annotatef(err, "GasLimit %v", r.Tx.GasLimit) + } + if pt.Tx.Hash, err = hexDecode(r.Tx.Hash); err != nil { + return nil, errors.Annotatef(err, "Hash %v", r.Tx.Hash) + } + if pt.Tx.Payload, err = hexDecode(r.Tx.Payload); err != nil { + return nil, errors.Annotatef(err, "Payload %v", r.Tx.Payload) + } + if pt.Tx.GasPrice, err = hexDecodeBig(r.Tx.GasPrice); err != nil { + return nil, errors.Annotatef(err, "Price %v", r.Tx.GasPrice) + } + // if pt.R, err = hexDecodeBig(r.R); err != nil { + // return nil, errors.Annotatef(err, "R %v", r.R) + // } + // if pt.S, err = hexDecodeBig(r.S); err != nil { + // return nil, errors.Annotatef(err, "S %v", r.S) + // } + // if pt.V, err = hexDecodeBig(r.V); err != nil { + // return nil, errors.Annotatef(err, "V %v", r.V) + // } + if pt.Tx.To, err = hexDecode(r.Tx.To); err != nil { + return nil, errors.Annotatef(err, "To %v", r.Tx.To) + } + if n, err = hexutil.DecodeUint64(r.Tx.TransactionIndex); err != nil { + return nil, errors.Annotatef(err, "TransactionIndex %v", r.Tx.TransactionIndex) + } + pt.Tx.TransactionIndex = uint32(n) + if pt.Tx.Value, err = hexDecodeBig(r.Tx.Value); err != nil { + return nil, errors.Annotatef(err, "Value %v", r.Tx.Value) + } + if r.Receipt != nil { + pt.Receipt = &ProtoCompleteTransaction_ReceiptType{} + if pt.Receipt.GasUsed, err = hexDecodeBig(r.Receipt.GasUsed); err != nil { + return nil, errors.Annotatef(err, "GasUsed %v", r.Receipt.GasUsed) + } + if r.Receipt.Status != "" { + if pt.Receipt.Status, err = hexDecodeBig(r.Receipt.Status); err != nil { + return nil, errors.Annotatef(err, "Status %v", r.Receipt.Status) + } + } else { + // unknown status, use 'U' as status bytes + // there is a potential for conflict with value 0x55 but this is not used by any chain at this moment + pt.Receipt.Status = []byte{'U'} + } + ptLogs := make([]*ProtoCompleteTransaction_ReceiptType_LogType, len(r.Receipt.Logs)) + for i, l := range r.Receipt.Logs { + a, err := hexutil.Decode(l.Address) + if err != nil { + return nil, errors.Annotatef(err, "Address cannot be decoded %v", l) + } + d, err := hexutil.Decode(l.Data) + if err != nil { + return nil, errors.Annotatef(err, "Data cannot be decoded %v", l) + } + t := make([][]byte, len(l.Topics)) + for j, s := range l.Topics { + t[j], err = hexutil.Decode(s) + if err != nil { + return nil, errors.Annotatef(err, "Topic cannot be decoded %v", l) + } + } + ptLogs[i] = &ProtoCompleteTransaction_ReceiptType_LogType{ + Address: a, + Data: d, + Topics: t, + } + + } + pt.Receipt.Log = ptLogs + } + return proto.Marshal(pt) +} + +// UnpackTx unpacks transaction from byte array +func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { + var pt ProtoCompleteTransaction + err := proto.Unmarshal(buf, &pt) + if err != nil { + return nil, 0, err + } + rt := rpcTransaction{ + AccountNonce: hexutil.EncodeUint64(pt.Tx.AccountNonce), + BlockNumber: hexutil.EncodeUint64(uint64(pt.BlockNumber)), + From: EIP55Address(pt.Tx.From), + GasLimit: hexutil.EncodeUint64(pt.Tx.GasLimit), + Hash: hexutil.Encode(pt.Tx.Hash), + Payload: hexutil.Encode(pt.Tx.Payload), + GasPrice: hexEncodeBig(pt.Tx.GasPrice), + // R: hexEncodeBig(pt.R), + // S: hexEncodeBig(pt.S), + // V: hexEncodeBig(pt.V), + To: EIP55Address(pt.Tx.To), + TransactionIndex: hexutil.EncodeUint64(uint64(pt.Tx.TransactionIndex)), + Value: hexEncodeBig(pt.Tx.Value), + } + var rr *rpcReceipt + if pt.Receipt != nil { + logs := make([]*rpcLog, len(pt.Receipt.Log)) + for i, l := range pt.Receipt.Log { + topics := make([]string, len(l.Topics)) + for j, t := range l.Topics { + topics[j] = hexutil.Encode(t) + } + logs[i] = &rpcLog{ + Address: EIP55Address(l.Address), + Data: hexutil.Encode(l.Data), + Topics: topics, + } + } + status := "" + // handle a special value []byte{'U'} as unknown state + if len(pt.Receipt.Status) != 1 || pt.Receipt.Status[0] != 'U' { + status = hexEncodeBig(pt.Receipt.Status) + } + rr = &rpcReceipt{ + GasUsed: hexEncodeBig(pt.Receipt.GasUsed), + Status: status, + Logs: logs, + } + } + tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, false) + if err != nil { + return nil, 0, err + } + return tx, pt.BlockNumber, nil +} + +// PackedTxidLen returns length in bytes of packed txid +func (p *EthereumParser) PackedTxidLen() int { + return 32 +} + +// PackTxid packs txid to byte array +func (p *EthereumParser) PackTxid(txid string) ([]byte, error) { + if has0xPrefix(txid) { + txid = txid[2:] + } + return hex.DecodeString(txid) +} + +// UnpackTxid unpacks byte array to txid +func (p *EthereumParser) UnpackTxid(buf []byte) (string, error) { + return hexutil.Encode(buf), nil +} + +// PackBlockHash packs block hash to byte array +func (p *EthereumParser) PackBlockHash(hash string) ([]byte, error) { + if has0xPrefix(hash) { + hash = hash[2:] + } + return hex.DecodeString(hash) +} + +// UnpackBlockHash unpacks byte array to block hash +func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { + return hexutil.Encode(buf), nil +} + +// GetChainType returns EthereumType +func (p *EthereumParser) GetChainType() bchain.ChainType { + return bchain.ChainEthereumType +} + +// GetHeightFromTx returns ethereum specific data from bchain.Tx +func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { + var bn string + csd, ok := tx.CoinSpecificData.(completeTransaction) + if !ok { + return 0, errors.New("Missing CoinSpecificData") + } + bn = csd.Tx.BlockNumber + n, err := hexutil.DecodeUint64(bn) + if err != nil { + return 0, errors.Annotatef(err, "BlockNumber %v", bn) + } + return uint32(n), nil +} + +// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx +func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) { + var r []bchain.Erc20Transfer + var err error + csd, ok := tx.CoinSpecificData.(completeTransaction) + if ok { + if csd.Receipt != nil { + r, err = erc20GetTransfersFromLog(csd.Receipt.Logs) + } else { + r, err = erc20GetTransfersFromTx(csd.Tx) + } + if err != nil { + return nil, err + } + } + return r, nil +} + +// TxStatus is status of transaction +type TxStatus int + +// statuses of transaction +const ( + TxStatusUnknown = TxStatus(iota - 2) + TxStatusPending + TxStatusFailure + TxStatusOK +) + +// EthereumTxData contains ethereum specific transaction data +type EthereumTxData struct { + Status TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending, -2 unknown + Nonce uint64 `json:"nonce"` + GasLimit *big.Int `json:"gaslimit"` + GasUsed *big.Int `json:"gasused"` + GasPrice *big.Int `json:"gasprice"` + Data string `json:"data"` +} + +// GetEthereumTxData returns EthereumTxData from bchain.Tx +func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData { + return GetEthereumTxDataFromSpecificData(tx.CoinSpecificData) +} + +// GetEthereumTxDataFromSpecificData returns EthereumTxData from coinSpecificData +func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTxData { + etd := EthereumTxData{Status: TxStatusPending} + csd, ok := coinSpecificData.(completeTransaction) + if ok { + if csd.Tx != nil { + etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce) + etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit) + etd.GasPrice, _ = hexutil.DecodeBig(csd.Tx.GasPrice) + etd.Data = csd.Tx.Payload + } + if csd.Receipt != nil { + switch csd.Receipt.Status { + case "0x1": + etd.Status = TxStatusOK + case "": // old transactions did not set status + etd.Status = TxStatusUnknown + default: + etd.Status = TxStatusFailure + } + etd.GasUsed, _ = hexutil.DecodeBig(csd.Receipt.GasUsed) + } + } + return &etd +} diff --git a/bchain/coins/energi/ethparser_test.go b/bchain/coins/energi/ethparser_test.go new file mode 100644 index 00000000..459131d9 --- /dev/null +++ b/bchain/coins/energi/ethparser_test.go @@ -0,0 +1,402 @@ +// +build unittest + +package energi + +import ( + "encoding/hex" + "fmt" + "math/big" + "reflect" + "testing" + + "github.com/trezor/blockbook/bchain" + "github.com/trezor/blockbook/tests/dbtestdata" +) + +func TestEthParser_GetAddrDescFromAddress(t *testing.T) { + type args struct { + address string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "with 0x prefix", + args: args{address: "0x81b7e08f65bdf5648606c89998a9cc8164397647"}, + want: "81b7e08f65bdf5648606c89998a9cc8164397647", + }, + { + name: "without 0x prefix", + args: args{address: "47526228d673e9f079630d6cdaff5a2ed13e0e60"}, + want: "47526228d673e9f079630d6cdaff5a2ed13e0e60", + }, + { + name: "address of wrong length", + args: args{address: "7526228d673e9f079630d6cdaff5a2ed13e0e60"}, + want: "", + wantErr: true, + }, + { + name: "ErrAddressMissing", + args: args{address: ""}, + want: "", + wantErr: true, + }, + { + name: "error - not eth address", + args: args{address: "1JKgN43B9SyLuZH19H5ECvr4KcfrbVHzZ6"}, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewEthereumParser(1) + got, err := p.GetAddrDescFromAddress(tt.args.address) + if (err != nil) != tt.wantErr { + t.Errorf("EthParser.GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr) + return + } + h := hex.EncodeToString(got) + if !reflect.DeepEqual(h, tt.want) { + t.Errorf("EthParser.GetAddrDescFromAddress() = %v, want %v", h, tt.want) + } + }) + } +} + +var testTx1, testTx2, testTx1Failed, testTx1NoStatus bchain.Tx + +func init() { + + testTx1 = bchain.Tx{ + Blocktime: 1534858022, + Time: 1534858022, + Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + Vin: []bchain.Vin{ + { + Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"}, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(1999622000000000000), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"}, + }, + }, + }, + CoinSpecificData: completeTransaction{ + Tx: &rpcTransaction{ + AccountNonce: "0xb26c", + GasPrice: "0x430e23400", + GasLimit: "0x5208", + To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f", + Value: "0x1bc0159d530e6000", + Payload: "0x", + Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + BlockNumber: "0x41eee8", + From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97", + TransactionIndex: "0xa", + }, + Receipt: &rpcReceipt{ + GasUsed: "0x5208", + Status: "0x1", + Logs: []*rpcLog{}, + }, + }, + } + + testTx2 = bchain.Tx{ + Blocktime: 1534858022, + Time: 1534858022, + Txid: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101", + Vin: []bchain.Vin{ + { + Addresses: []string{"0x20cD153de35D469BA46127A0C8F18626b59a256A"}, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(0), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"0x4af4114F73d1c1C903aC9E0361b379D1291808A2"}, + }, + }, + }, + CoinSpecificData: completeTransaction{ + Tx: &rpcTransaction{ + AccountNonce: "0xd0", + GasPrice: "0x9502f9000", + GasLimit: "0x130d5", + To: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2", + Value: "0x0", + Payload: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000", + Hash: "0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101", + BlockNumber: "0x41eee8", + From: "0x20cD153de35D469BA46127A0C8F18626b59a256A", + TransactionIndex: "0x0"}, + Receipt: &rpcReceipt{ + GasUsed: "0xcb39", + Status: "0x1", + Logs: []*rpcLog{ + { + Address: "0x4af4114F73d1c1C903aC9E0361b379D1291808A2", + Data: "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a", + "0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f", + }, + }, + }, + }, + }, + } + + testTx1Failed = bchain.Tx{ + Blocktime: 1534858022, + Time: 1534858022, + Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + Vin: []bchain.Vin{ + { + Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"}, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(1999622000000000000), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"}, + }, + }, + }, + CoinSpecificData: completeTransaction{ + Tx: &rpcTransaction{ + AccountNonce: "0xb26c", + GasPrice: "0x430e23400", + GasLimit: "0x5208", + To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f", + Value: "0x1bc0159d530e6000", + Payload: "0x", + Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + BlockNumber: "0x41eee8", + From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97", + TransactionIndex: "0xa", + }, + Receipt: &rpcReceipt{ + GasUsed: "0x5208", + Status: "0x0", + Logs: []*rpcLog{}, + }, + }, + } + + testTx1NoStatus = bchain.Tx{ + Blocktime: 1534858022, + Time: 1534858022, + Txid: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + Vin: []bchain.Vin{ + { + Addresses: []string{"0x3E3a3D69dc66bA10737F531ed088954a9EC89d97"}, + }, + }, + Vout: []bchain.Vout{ + { + ValueSat: *big.NewInt(1999622000000000000), + ScriptPubKey: bchain.ScriptPubKey{ + Addresses: []string{"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"}, + }, + }, + }, + CoinSpecificData: completeTransaction{ + Tx: &rpcTransaction{ + AccountNonce: "0xb26c", + GasPrice: "0x430e23400", + GasLimit: "0x5208", + To: "0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f", + Value: "0x1bc0159d530e6000", + Payload: "0x", + Hash: "0xcd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b", + BlockNumber: "0x41eee8", + From: "0x3E3a3D69dc66bA10737F531ed088954a9EC89d97", + TransactionIndex: "0xa", + }, + Receipt: &rpcReceipt{ + GasUsed: "0x5208", + Status: "", + Logs: []*rpcLog{}, + }, + }, + } + +} + +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: &testTx1, + height: 4321000, + blockTime: 1534858022, + }, + want: dbtestdata.EthTx1Packed, + }, + { + name: "2", + args: args{ + tx: &testTx2, + height: 4321000, + blockTime: 1534858022, + }, + want: dbtestdata.EthTx2Packed, + }, + { + name: "3", + args: args{ + tx: &testTx1Failed, + height: 4321000, + blockTime: 1534858022, + }, + want: dbtestdata.EthTx1FailedPacked, + }, + { + name: "4", + args: args{ + tx: &testTx1NoStatus, + height: 4321000, + blockTime: 1534858022, + }, + want: dbtestdata.EthTx1NoStatusPacked, + }, + } + p := NewEthereumParser(1) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + 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) + } + }) + } +} + +func TestEthereumParser_UnpackTx(t *testing.T) { + type args struct { + hex string + } + tests := []struct { + name string + p *EthereumParser + args args + want *bchain.Tx + want1 uint32 + wantErr bool + }{ + { + name: "1", + args: args{hex: dbtestdata.EthTx1Packed}, + want: &testTx1, + want1: 4321000, + }, + { + name: "2", + args: args{hex: dbtestdata.EthTx2Packed}, + want: &testTx2, + want1: 4321000, + }, + { + name: "3", + args: args{hex: dbtestdata.EthTx1FailedPacked}, + want: &testTx1Failed, + want1: 4321000, + }, + { + name: "4", + args: args{hex: dbtestdata.EthTx1NoStatusPacked}, + want: &testTx1NoStatus, + want1: 4321000, + }, + } + p := NewEthereumParser(1) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b, err := hex.DecodeString(tt.args.hex) + if err != nil { + panic(err) + } + got, got1, err := p.UnpackTx(b) + if (err != nil) != tt.wantErr { + t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr) + return + } + // DeepEqual has problems with pointers in completeTransaction + gs := got.CoinSpecificData.(completeTransaction) + ws := tt.want.CoinSpecificData.(completeTransaction) + gc := *got + wc := *tt.want + gc.CoinSpecificData = nil + wc.CoinSpecificData = nil + if fmt.Sprint(gc) != fmt.Sprint(wc) { + // if !reflect.DeepEqual(gc, wc) { + t.Errorf("EthereumParser.UnpackTx() gc got = %+v, want %+v", gc, wc) + } + if !reflect.DeepEqual(gs.Tx, ws.Tx) { + t.Errorf("EthereumParser.UnpackTx() gs.Tx got = %+v, want %+v", gs.Tx, ws.Tx) + } + if !reflect.DeepEqual(gs.Receipt, ws.Receipt) { + t.Errorf("EthereumParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt) + } + if got1 != tt.want1 { + t.Errorf("EthereumParser.UnpackTx() got1 = %v, want %v", got1, tt.want1) + } + }) + } +} + +func TestEthereumParser_GetEthereumTxData(t *testing.T) { + tests := []struct { + name string + tx *bchain.Tx + want string + }{ + { + name: "Test empty data", + tx: &testTx1, + want: "0x", + }, + { + name: "Test non empty data", + tx: &testTx2, + want: "0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := GetEthereumTxData(tt.tx) + if got.Data != tt.want { + t.Errorf("EthereumParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want) + } + }) + } +} diff --git a/bchain/coins/energi/ethrpc.go b/bchain/coins/energi/ethrpc.go new file mode 100644 index 00000000..eeb4c672 --- /dev/null +++ b/bchain/coins/energi/ethrpc.go @@ -0,0 +1,796 @@ +package energi + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "strconv" + "sync" + "time" + + ethereum "github.com/ethereum/go-ethereum" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/golang/glog" + "github.com/juju/errors" + "github.com/trezor/blockbook/bchain" + "github.com/trezor/blockbook/common" +) + +// NRGNet type specifies the type of Energi network +type NRGNet uint32 + +const ( + // MainNet is production network + MainNet NRGNet = 39797 + // TestNet is Ropsten test network + TestNet NRGNet = 49797 +) + +// Configuration represents json config file +type Configuration struct { + CoinName string `json:"coin_name"` + CoinShortcut string `json:"coin_shortcut"` + RPCURL string `json:"rpc_url"` + RPCTimeout int `json:"rpc_timeout"` + BlockAddressesToKeep int `json:"block_addresses_to_keep"` + MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"` + QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"` +} + +// EthereumRPC is an interface to JSON-RPC eth service. +type EthereumRPC struct { + *bchain.BaseChain + client *ethclient.Client + rpc *rpc.Client + timeout time.Duration + Parser *EthereumParser + Mempool *bchain.MempoolEthereumType + mempoolInitialized bool + bestHeaderLock sync.Mutex + bestHeader *header + bestHeaderTime time.Time + chanNewBlock chan *header + newBlockSubscription *rpc.ClientSubscription + chanNewTx chan ethcommon.Hash + newTxSubscription *rpc.ClientSubscription + ChainConfig *Configuration +} + +// NewEthereumRPC returns new EthRPC instance. +func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { + var err error + var c Configuration + err = json.Unmarshal(config, &c) + if err != nil { + return nil, errors.Annotatef(err, "Invalid configuration file") + } + // keep at least 100 mappings block->addresses to allow rollback + if c.BlockAddressesToKeep < 100 { + c.BlockAddressesToKeep = 100 + } + + rc, ec, err := openRPC(c.RPCURL) + if err != nil { + return nil, err + } + + s := &EthereumRPC{ + BaseChain: &bchain.BaseChain{}, + client: ec, + rpc: rc, + ChainConfig: &c, + } + + // always create parser + s.Parser = NewEthereumParser(c.BlockAddressesToKeep) + s.timeout = time.Duration(c.RPCTimeout) * time.Second + + // new blocks notifications handling + // the subscription is done in Initialize + s.chanNewBlock = make(chan *header) + go func() { + for { + h, ok := <-s.chanNewBlock + if !ok { + break + } + glog.V(2).Info("rpc: new block header ", h.Number) + // update best header to the new header + s.bestHeaderLock.Lock() + s.bestHeader = h + s.bestHeaderTime = time.Now() + s.bestHeaderLock.Unlock() + // notify blockbook + pushHandler(bchain.NotificationNewBlock) + } + }() + + // new mempool transaction notifications handling + // the subscription is done in Initialize + s.chanNewTx = make(chan ethcommon.Hash) + go func() { + for { + t, ok := <-s.chanNewTx + if !ok { + break + } + hex := t.Hex() + if glog.V(2) { + glog.Info("rpc: new tx ", hex) + } + s.Mempool.AddTransactionToMempool(hex) + pushHandler(bchain.NotificationNewTx) + } + }() + + return s, nil +} + +func openRPC(url string) (*rpc.Client, *ethclient.Client, error) { + rc, err := rpc.Dial(url) + if err != nil { + return nil, nil, err + } + ec := ethclient.NewClient(rc) + return rc, ec, nil +} + +// Initialize initializes ethereum rpc interface +func (b *EthereumRPC) Initialize() error { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + + id, err := b.client.NetworkID(ctx) + if err != nil { + return err + } + + // parameters for getInfo request + switch NRGNet(id.Uint64()) { + case MainNet: + b.Testnet = false + b.Network = "livenet" + break + case TestNet: + b.Testnet = true + b.Network = "testnet" + break + default: + return errors.Errorf("Unknown network id %v", id) + } + glog.Info("rpc: block chain ", b.Network) + + return nil +} + +// CreateMempool creates mempool if not already created, however does not initialize it +func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { + if b.Mempool == nil { + b.Mempool = bchain.NewMempoolEthereumType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync) + glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync) + } + return b.Mempool, nil +} + +// InitializeMempool creates subscriptions to newHeads and newPendingTransactions +func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error { + if b.Mempool == nil { + return errors.New("Mempool not created") + } + + // get initial mempool transactions + txs, err := b.GetMempoolTransactions() + if err != nil { + return err + } + for _, txid := range txs { + b.Mempool.AddTransactionToMempool(txid) + } + + b.Mempool.OnNewTxAddr = onNewTxAddr + b.Mempool.OnNewTx = onNewTx + + if err = b.subscribeEvents(); err != nil { + return err + } + + b.mempoolInitialized = true + + return nil +} + +func (b *EthereumRPC) subscribeEvents() error { + // subscriptions + if err := b.subscribe(func() (*rpc.ClientSubscription, error) { + // invalidate the previous subscription - it is either the first one or there was an error + b.newBlockSubscription = nil + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads") + if err != nil { + return nil, errors.Annotatef(err, "EthSubscribe newHeads") + } + b.newBlockSubscription = sub + glog.Info("Subscribed to newHeads") + return sub, nil + }); err != nil { + return err + } + + if err := b.subscribe(func() (*rpc.ClientSubscription, error) { + // invalidate the previous subscription - it is either the first one or there was an error + b.newTxSubscription = nil + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + sub, err := b.rpc.EthSubscribe(ctx, b.chanNewTx, "newPendingTransactions") + if err != nil { + return nil, errors.Annotatef(err, "EthSubscribe newPendingTransactions") + } + b.newTxSubscription = sub + glog.Info("Subscribed to newPendingTransactions") + return sub, nil + }); err != nil { + return err + } + + return nil +} + +// subscribe subscribes notification and tries to resubscribe in case of error +func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error { + s, err := f() + if err != nil { + return err + } + go func() { + Loop: + for { + // wait for error in subscription + e := <-s.Err() + // nil error means sub.Unsubscribe called, exit goroutine + if e == nil { + return + } + glog.Error("Subscription error ", e) + timer := time.NewTimer(time.Second * 2) + // try in 2 second interval to resubscribe + for { + select { + case e = <-s.Err(): + if e == nil { + return + } + case <-timer.C: + ns, err := f() + if err == nil { + // subscription successful, restart wait for next error + s = ns + continue Loop + } + glog.Error("Resubscribe error ", err) + timer.Reset(time.Second * 2) + } + } + } + }() + return nil +} + +func (b *EthereumRPC) closeRPC() { + if b.newBlockSubscription != nil { + b.newBlockSubscription.Unsubscribe() + } + if b.newTxSubscription != nil { + b.newTxSubscription.Unsubscribe() + } + if b.rpc != nil { + b.rpc.Close() + } +} + +func (b *EthereumRPC) reconnectRPC() error { + glog.Info("Reconnecting RPC") + b.closeRPC() + rc, ec, err := openRPC(b.ChainConfig.RPCURL) + if err != nil { + return err + } + b.rpc = rc + b.client = ec + return b.subscribeEvents() +} + +// Shutdown cleans up rpc interface to ethereum +func (b *EthereumRPC) Shutdown(ctx context.Context) error { + b.closeRPC() + close(b.chanNewBlock) + glog.Info("rpc: shutdown") + return nil +} + +// GetCoinName returns coin name +func (b *EthereumRPC) GetCoinName() string { + return b.ChainConfig.CoinName +} + +// GetSubversion returns empty string, ethereum does not have subversion +func (b *EthereumRPC) GetSubversion() string { + return "" +} + +// GetChainInfo returns information about the connected backend +func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { + h, err := b.getBestHeader() + if err != nil { + return nil, err + } + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + id, err := b.client.NetworkID(ctx) + if err != nil { + return nil, err + } + var ver, protocol string + if err := b.rpc.CallContext(ctx, &ver, "web3_clientVersion"); err != nil { + return nil, err + } + if err := b.rpc.CallContext(ctx, &protocol, "eth_protocolVersion"); err != nil { + return nil, err + } + rv := &bchain.ChainInfo{ + Blocks: int(h.Number.ToInt().Int64()), + Bestblockhash: string(h.Hash), + Difficulty: h.Difficulty.String(), + Version: ver, + ProtocolVersion: protocol, + } + idi := int(id.Uint64()) + if idi == 1 { + rv.Chain = "mainnet" + } else { + rv.Chain = "testnet " + strconv.Itoa(idi) + } + return rv, nil +} + +func (b *EthereumRPC) getBestHeader() (*header, error) { + b.bestHeaderLock.Lock() + defer b.bestHeaderLock.Unlock() + // if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC + // do it only in case of normal operation, not initial synchronization + if b.bestHeaderTime.Add(15*time.Minute).Before(time.Now()) && !b.bestHeaderTime.IsZero() && b.mempoolInitialized { + err := b.reconnectRPC() + if err != nil { + return nil, err + } + b.bestHeader = nil + } + if b.bestHeader == nil { + var err error + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + b.bestHeader, err = b.headerByNumber(ctx, nil) + if err != nil { + b.bestHeader = nil + return nil, err + } + b.bestHeaderTime = time.Now() + } + return b.bestHeader, nil +} + +// GetBestBlockHash returns hash of the tip of the best-block-chain +func (b *EthereumRPC) GetBestBlockHash() (string, error) { + h, err := b.getBestHeader() + if err != nil { + return "", err + } + return string(h.Hash), nil +} + +// GetBestBlockHeight returns height of the tip of the best-block-chain +func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { + h, err := b.getBestHeader() + if err != nil { + return 0, err + } + return uint32(h.Number.ToInt().Uint64()), nil +} + +// GetBlockHash returns hash of block in best-block-chain at given height +func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { + var n big.Int + n.SetUint64(uint64(height)) + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + h, err := b.headerByNumber(ctx, &n) + if err != nil { + if err == ethereum.NotFound { + return "", bchain.ErrBlockNotFound + } + return "", errors.Annotatef(err, "height %v", height) + } + return string(h.Hash), nil +} + +func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) { + height, err := ethNumber(h.Number) + if err != nil { + return nil, err + } + c, err := b.computeConfirmations(uint64(height)) + if err != nil { + return nil, err + } + time, err := ethNumber(h.Time) + if err != nil { + return nil, err + } + size, err := ethNumber(h.Size) + if err != nil { + return nil, err + } + return &bchain.BlockHeader{ + Hash: h.Hash, + Prev: h.ParentHash, + Height: uint32(height), + Confirmations: int(c), + Time: time, + Size: int(size), + }, nil +} + +// GetBlockHeader returns header of block with given hash +func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { + raw, err := b.getBlockRaw(hash, 0, false) + if err != nil { + return nil, err + } + var h rpcHeader + if err := json.Unmarshal(raw, &h); err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + return b.ethHeaderToBlockHeader(&h) +} + +func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) { + bh, err := b.getBestHeader() + if err != nil { + return 0, err + } + bn := bh.Number.ToInt().Uint64() + // transaction in the best block has 1 confirmation + return uint32(bn - n + 1), nil +} + +func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var raw json.RawMessage + var err error + if hash != "" { + if hash == "pending" { + err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", hash, fullTxs) + } else { + err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), fullTxs) + } + } else { + err = b.rpc.CallContext(ctx, &raw, "eth_getBlockByNumber", fmt.Sprintf("%#x", height), fullTxs) + } + if err != nil { + return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) + } else if len(raw) == 0 { + return nil, bchain.ErrBlockNotFound + } + return raw, nil +} + +func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var logs []rpcLogWithTxHash + err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ + "fromBlock": blockNumber, + "toBlock": blockNumber, + "topics": []string{erc20TransferEventSignature}, + }) + if err != nil { + return nil, errors.Annotatef(err, "blockNumber %v", blockNumber) + } + r := make(map[string][]*rpcLog) + for i := range logs { + l := &logs[i] + r[l.Hash] = append(r[l.Hash], &l.rpcLog) + } + return r, 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) { + raw, err := b.getBlockRaw(hash, height, true) + if err != nil { + return nil, err + } + var head rpcHeader + if err := json.Unmarshal(raw, &head); err != nil { + return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) + } + var body rpcBlockTransactions + if err := json.Unmarshal(raw, &body); err != nil { + return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) + } + bbh, err := b.ethHeaderToBlockHeader(&head) + if err != nil { + return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) + } + // get ERC20 events + logs, err := b.getERC20EventsForBlock(head.Number) + if err != nil { + return nil, err + } + btxs := make([]bchain.Tx, len(body.Transactions)) + for i := range body.Transactions { + tx := &body.Transactions[i] + btx, err := b.Parser.ethTxToTx(tx, &rpcReceipt{Logs: logs[tx.Hash]}, bbh.Time, uint32(bbh.Confirmations), true) + if err != nil { + return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash) + } + btxs[i] = *btx + if b.mempoolInitialized { + b.Mempool.RemoveTransactionFromMempool(tx.Hash) + } + } + bbk := bchain.Block{ + BlockHeader: *bbh, + Txs: btxs, + } + return &bbk, nil +} + +// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids +func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { + raw, err := b.getBlockRaw(hash, 0, false) + if err != nil { + return nil, err + } + var head rpcHeader + var txs rpcBlockTxids + if err := json.Unmarshal(raw, &head); err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + if err = json.Unmarshal(raw, &txs); err != nil { + return nil, err + } + bch, err := b.ethHeaderToBlockHeader(&head) + if err != nil { + return nil, err + } + return &bchain.BlockInfo{ + BlockHeader: *bch, + Difficulty: common.JSONNumber(head.Difficulty), + Nonce: common.JSONNumber(head.Nonce), + Txids: txs.Transactions, + }, nil +} + +// GetTransactionForMempool returns a transaction by the transaction ID. +// It could be optimized for mempool, i.e. without block time and confirmations +func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { + return b.GetTransaction(txid) +} + +// GetTransaction returns a transaction by the transaction ID. +func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var tx *rpcTransaction + hash := ethcommon.HexToHash(txid) + err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", hash) + if err != nil { + return nil, err + } else if tx == nil { + if b.mempoolInitialized { + b.Mempool.RemoveTransactionFromMempool(txid) + } + return nil, bchain.ErrTxNotFound + } + var btx *bchain.Tx + if tx.BlockNumber == "" { + // mempool tx + btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + } else { + // non mempool tx - read the block header to get the block time + raw, err := b.getBlockRaw(tx.BlockHash, 0, false) + if err != nil { + return nil, err + } + var ht struct { + Time string `json:"timestamp"` + } + if err := json.Unmarshal(raw, &ht); err != nil { + return nil, errors.Annotatef(err, "hash %v", hash) + } + var time int64 + if time, err = ethNumber(ht.Time); err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + var receipt rpcReceipt + err = b.rpc.CallContext(ctx, &receipt, "eth_getTransactionReceipt", hash) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + n, err := ethNumber(tx.BlockNumber) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + confirmations, err := b.computeConfirmations(uint64(n)) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + btx, err = b.Parser.ethTxToTx(tx, &receipt, time, confirmations, true) + if err != nil { + return nil, errors.Annotatef(err, "txid %v", txid) + } + // remove tx from mempool if it is there + if b.mempoolInitialized { + b.Mempool.RemoveTransactionFromMempool(txid) + } + } + return btx, nil +} + +// GetTransactionSpecific returns json as returned by backend, with all coin specific data +func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { + csd, ok := tx.CoinSpecificData.(completeTransaction) + if !ok { + ntx, err := b.GetTransaction(tx.Txid) + if err != nil { + return nil, err + } + csd, ok = ntx.CoinSpecificData.(completeTransaction) + if !ok { + return nil, errors.New("Cannot get CoinSpecificData") + } + } + m, err := json.Marshal(&csd) + return json.RawMessage(m), err +} + +// GetMempoolTransactions returns transactions in mempool +func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) { + raw, err := b.getBlockRaw("pending", 0, false) + if err != nil { + return nil, err + } + var body rpcBlockTxids + if len(raw) > 0 { + if err := json.Unmarshal(raw, &body); err != nil { + return nil, err + } + } + return body.Transactions, nil +} + +// EstimateFee returns fee estimation +func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) { + return b.EstimateSmartFee(blocks, true) +} + +// EstimateSmartFee returns fee estimation +func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var r big.Int + gp, err := b.client.SuggestGasPrice(ctx) + if err == nil && b != nil { + r = *gp + } + return r, err +} + +func getStringFromMap(p string, params map[string]interface{}) (string, bool) { + v, ok := params[p] + if ok { + s, ok := v.(string) + return s, ok + } + return "", false +} + +// EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters +func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + msg := ethereum.CallMsg{} + s, ok := getStringFromMap("from", params) + if ok && len(s) > 0 { + msg.From = ethcommon.HexToAddress(s) + } + s, ok = getStringFromMap("to", params) + if ok && len(s) > 0 { + a := ethcommon.HexToAddress(s) + msg.To = &a + } + s, ok = getStringFromMap("data", params) + if ok && len(s) > 0 { + msg.Data = ethcommon.FromHex(s) + } + s, ok = getStringFromMap("value", params) + if ok && len(s) > 0 { + msg.Value, _ = hexutil.DecodeBig(s) + } + s, ok = getStringFromMap("gas", params) + if ok && len(s) > 0 { + msg.Gas, _ = hexutil.DecodeUint64(s) + } + s, ok = getStringFromMap("gasPrice", params) + if ok && len(s) > 0 { + msg.GasPrice, _ = hexutil.DecodeBig(s) + } + return b.client.EstimateGas(ctx, msg) +} + +// SendRawTransaction sends raw transaction +func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var raw json.RawMessage + err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex) + if err != nil { + return "", err + } else if len(raw) == 0 { + return "", errors.New("SendRawTransaction: failed") + } + var result string + if err := json.Unmarshal(raw, &result); err != nil { + return "", errors.Annotatef(err, "raw result %v", raw) + } + if result == "" { + return "", errors.New("SendRawTransaction: failed, empty result") + } + return result, nil +} + +// EthereumTypeGetBalance returns current balance of an address +func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) +} + +// EthereumTypeGetNonce returns current balance of an address +func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) +} + +// GetChainParser returns ethereum BlockChainParser +func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { + return b.Parser +} + +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + return hexutil.EncodeBig(number) +} + +func (b *EthereumRPC) headerByNumber(ctx context.Context, number *big.Int) (*header, error) { + var head header + err := b.rpc.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) + if err != nil { + return nil, err + } else if len(head.Hash) == 0 { + return nil, bchain.ErrBlockNotFound + } + return &head, nil +} diff --git a/bchain/coins/energi/ethtx.pb.go b/bchain/coins/energi/ethtx.pb.go new file mode 100644 index 00000000..c8c426fc --- /dev/null +++ b/bchain/coins/energi/ethtx.pb.go @@ -0,0 +1,261 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: bchain/coins/eth/ethtx.proto + +/* +Package eth is a generated protocol buffer package. + +It is generated from these files: + bchain/coins/eth/ethtx.proto + +It has these top-level messages: + ProtoCompleteTransaction +*/ +package energi + +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 ProtoCompleteTransaction struct { + BlockNumber uint32 `protobuf:"varint,1,opt,name=BlockNumber" json:"BlockNumber,omitempty"` + BlockTime uint64 `protobuf:"varint,2,opt,name=BlockTime" json:"BlockTime,omitempty"` + Tx *ProtoCompleteTransaction_TxType `protobuf:"bytes,3,opt,name=Tx" json:"Tx,omitempty"` + Receipt *ProtoCompleteTransaction_ReceiptType `protobuf:"bytes,4,opt,name=Receipt" json:"Receipt,omitempty"` +} + +func (m *ProtoCompleteTransaction) Reset() { *m = ProtoCompleteTransaction{} } +func (m *ProtoCompleteTransaction) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction) ProtoMessage() {} +func (*ProtoCompleteTransaction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *ProtoCompleteTransaction) GetBlockNumber() uint32 { + if m != nil { + return m.BlockNumber + } + return 0 +} + +func (m *ProtoCompleteTransaction) GetBlockTime() uint64 { + if m != nil { + return m.BlockTime + } + return 0 +} + +func (m *ProtoCompleteTransaction) GetTx() *ProtoCompleteTransaction_TxType { + if m != nil { + return m.Tx + } + return nil +} + +func (m *ProtoCompleteTransaction) GetReceipt() *ProtoCompleteTransaction_ReceiptType { + if m != nil { + return m.Receipt + } + return nil +} + +type ProtoCompleteTransaction_TxType struct { + AccountNonce uint64 `protobuf:"varint,1,opt,name=AccountNonce" json:"AccountNonce,omitempty"` + GasPrice []byte `protobuf:"bytes,2,opt,name=GasPrice,proto3" json:"GasPrice,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"` + To []byte `protobuf:"bytes,7,opt,name=To,proto3" json:"To,omitempty"` + From []byte `protobuf:"bytes,8,opt,name=From,proto3" json:"From,omitempty"` + TransactionIndex uint32 `protobuf:"varint,9,opt,name=TransactionIndex" json:"TransactionIndex,omitempty"` +} + +func (m *ProtoCompleteTransaction_TxType) Reset() { *m = ProtoCompleteTransaction_TxType{} } +func (m *ProtoCompleteTransaction_TxType) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction_TxType) ProtoMessage() {} +func (*ProtoCompleteTransaction_TxType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 0} +} + +func (m *ProtoCompleteTransaction_TxType) GetAccountNonce() uint64 { + if m != nil { + return m.AccountNonce + } + return 0 +} + +func (m *ProtoCompleteTransaction_TxType) GetGasPrice() []byte { + if m != nil { + return m.GasPrice + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetGasLimit() uint64 { + if m != nil { + return m.GasLimit + } + return 0 +} + +func (m *ProtoCompleteTransaction_TxType) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetPayload() []byte { + if m != nil { + return m.Payload + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetTo() []byte { + if m != nil { + return m.To + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetFrom() []byte { + if m != nil { + return m.From + } + return nil +} + +func (m *ProtoCompleteTransaction_TxType) GetTransactionIndex() uint32 { + if m != nil { + return m.TransactionIndex + } + return 0 +} + +type ProtoCompleteTransaction_ReceiptType struct { + GasUsed []byte `protobuf:"bytes,1,opt,name=GasUsed,proto3" json:"GasUsed,omitempty"` + Status []byte `protobuf:"bytes,2,opt,name=Status,proto3" json:"Status,omitempty"` + Log []*ProtoCompleteTransaction_ReceiptType_LogType `protobuf:"bytes,3,rep,name=Log" json:"Log,omitempty"` +} + +func (m *ProtoCompleteTransaction_ReceiptType) Reset() { *m = ProtoCompleteTransaction_ReceiptType{} } +func (m *ProtoCompleteTransaction_ReceiptType) String() string { return proto.CompactTextString(m) } +func (*ProtoCompleteTransaction_ReceiptType) ProtoMessage() {} +func (*ProtoCompleteTransaction_ReceiptType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 1} +} + +func (m *ProtoCompleteTransaction_ReceiptType) GetGasUsed() []byte { + if m != nil { + return m.GasUsed + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType) GetStatus() []byte { + if m != nil { + return m.Status + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType) GetLog() []*ProtoCompleteTransaction_ReceiptType_LogType { + if m != nil { + return m.Log + } + return nil +} + +type ProtoCompleteTransaction_ReceiptType_LogType struct { + Address []byte `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"` + Topics [][]byte `protobuf:"bytes,3,rep,name=Topics,proto3" json:"Topics,omitempty"` +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) Reset() { + *m = ProtoCompleteTransaction_ReceiptType_LogType{} +} +func (m *ProtoCompleteTransaction_ReceiptType_LogType) String() string { + return proto.CompactTextString(m) +} +func (*ProtoCompleteTransaction_ReceiptType_LogType) ProtoMessage() {} +func (*ProtoCompleteTransaction_ReceiptType_LogType) Descriptor() ([]byte, []int) { + return fileDescriptor0, []int{0, 1, 0} +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *ProtoCompleteTransaction_ReceiptType_LogType) GetTopics() [][]byte { + if m != nil { + return m.Topics + } + return nil +} + +func init() { + proto.RegisterType((*ProtoCompleteTransaction)(nil), "eth.ProtoCompleteTransaction") + proto.RegisterType((*ProtoCompleteTransaction_TxType)(nil), "eth.ProtoCompleteTransaction.TxType") + proto.RegisterType((*ProtoCompleteTransaction_ReceiptType)(nil), "eth.ProtoCompleteTransaction.ReceiptType") + proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType") +} + +func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 409 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x8a, 0xd4, 0x30, + 0x18, 0xc5, 0xe9, 0x9f, 0x99, 0xd9, 0xfd, 0xa6, 0x8a, 0x04, 0x91, 0x30, 0xec, 0x45, 0x59, 0xbc, + 0x18, 0xbd, 0xe8, 0xe2, 0xea, 0x0b, 0xac, 0x23, 0xae, 0xc2, 0xb0, 0x0e, 0x31, 0x7a, 0x9f, 0x49, + 0xc3, 0x36, 0x38, 0x6d, 0x4a, 0x93, 0x42, 0xf7, 0x8d, 0x7c, 0x21, 0xdf, 0xc5, 0x4b, 0xc9, 0xd7, + 0x74, 0x1d, 0x11, 0x65, 0x2f, 0x0a, 0xf9, 0x9d, 0x7e, 0xa7, 0x39, 0x27, 0x29, 0x9c, 0xed, 0x65, + 0x25, 0x74, 0x73, 0x21, 0x8d, 0x6e, 0xec, 0x85, 0x72, 0x95, 0x7f, 0xdc, 0x50, 0xb4, 0x9d, 0x71, + 0x86, 0x24, 0xca, 0x55, 0xe7, 0xdf, 0x67, 0x40, 0x77, 0x1e, 0x37, 0xa6, 0x6e, 0x0f, 0xca, 0x29, + 0xde, 0x89, 0xc6, 0x0a, 0xe9, 0xb4, 0x69, 0x48, 0x0e, 0xcb, 0xb7, 0x07, 0x23, 0xbf, 0xdd, 0xf4, + 0xf5, 0x5e, 0x75, 0x34, 0xca, 0xa3, 0xf5, 0x23, 0x76, 0x2c, 0x91, 0x33, 0x38, 0x45, 0xe4, 0xba, + 0x56, 0x34, 0xce, 0xa3, 0x75, 0xca, 0x7e, 0x0b, 0xe4, 0x0d, 0xc4, 0x7c, 0xa0, 0x49, 0x1e, 0xad, + 0x97, 0x97, 0xcf, 0x0b, 0xe5, 0xaa, 0xe2, 0x5f, 0x5b, 0x15, 0x7c, 0xe0, 0x77, 0xad, 0x62, 0x31, + 0x1f, 0xc8, 0x06, 0x16, 0x4c, 0x49, 0xa5, 0x5b, 0x47, 0x53, 0xb4, 0xbe, 0xf8, 0xbf, 0x35, 0x0c, + 0xa3, 0x7f, 0x72, 0xae, 0x7e, 0x46, 0x30, 0x1f, 0xbf, 0x49, 0xce, 0x21, 0xbb, 0x92, 0xd2, 0xf4, + 0x8d, 0xbb, 0x31, 0x8d, 0x54, 0x58, 0x23, 0x65, 0x7f, 0x68, 0x64, 0x05, 0x27, 0xd7, 0xc2, 0xee, + 0x3a, 0x2d, 0xc7, 0x1a, 0x19, 0xbb, 0xe7, 0xf0, 0x6e, 0xab, 0x6b, 0xed, 0xb0, 0x4b, 0xca, 0xee, + 0x99, 0x3c, 0x85, 0xd9, 0x57, 0x71, 0xe8, 0x15, 0x26, 0xcd, 0xd8, 0x08, 0x84, 0xc2, 0x62, 0x27, + 0xee, 0x0e, 0x46, 0x94, 0x74, 0x86, 0xfa, 0x84, 0x84, 0x40, 0xfa, 0x41, 0xd8, 0x8a, 0xce, 0x51, + 0xc6, 0x35, 0x79, 0x0c, 0x31, 0x37, 0x74, 0x81, 0x4a, 0xcc, 0x8d, 0x9f, 0x79, 0xdf, 0x99, 0x9a, + 0x9e, 0x8c, 0x33, 0x7e, 0x4d, 0x5e, 0xc2, 0x93, 0xa3, 0xca, 0x1f, 0x9b, 0x52, 0x0d, 0xf4, 0x14, + 0xaf, 0xe3, 0x2f, 0x7d, 0xf5, 0x23, 0x82, 0xe5, 0xd1, 0x99, 0xf8, 0x34, 0xd7, 0xc2, 0x7e, 0xb1, + 0xaa, 0xc4, 0xea, 0x19, 0x9b, 0x90, 0x3c, 0x83, 0xf9, 0x67, 0x27, 0x5c, 0x6f, 0x43, 0xe7, 0x40, + 0x64, 0x03, 0xc9, 0xd6, 0xdc, 0xd2, 0x24, 0x4f, 0xd6, 0xcb, 0xcb, 0x57, 0x0f, 0x3e, 0xfd, 0x62, + 0x6b, 0x6e, 0xf1, 0x16, 0xbc, 0x7b, 0xf5, 0x09, 0x16, 0x81, 0x7d, 0x82, 0xab, 0xb2, 0xec, 0x94, + 0xb5, 0x53, 0x82, 0x80, 0xbe, 0xeb, 0x3b, 0xe1, 0x44, 0xd8, 0x1f, 0xd7, 0x3e, 0x15, 0x37, 0xad, + 0x96, 0x16, 0x03, 0x64, 0x2c, 0xd0, 0x7e, 0x8e, 0xbf, 0xed, 0xeb, 0x5f, 0x01, 0x00, 0x00, 0xff, + 0xff, 0xc2, 0x69, 0x8d, 0xdf, 0xd6, 0x02, 0x00, 0x00, +} diff --git a/bchain/coins/energi/ethtx.proto b/bchain/coins/energi/ethtx.proto new file mode 100644 index 00000000..ac65da00 --- /dev/null +++ b/bchain/coins/energi/ethtx.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + package energi; + + message ProtoCompleteTransaction { + message TxType { + uint64 AccountNonce = 1; + bytes GasPrice = 2; + uint64 GasLimit = 3; + bytes Value = 4; + bytes Payload = 5; + bytes Hash = 6; + bytes To = 7; + bytes From = 8; + uint32 TransactionIndex = 9; + } + message ReceiptType { + message LogType { + bytes Address = 1; + bytes Data = 2; + repeated bytes Topics = 3; + } + bytes GasUsed = 1; + bytes Status = 2; + repeated LogType Log = 3; + } + uint32 BlockNumber = 1; + uint64 BlockTime = 2; + TxType Tx = 3; + ReceiptType Receipt = 4; + } \ No newline at end of file diff --git a/configs/coins/energi.json b/configs/coins/energi.json new file mode 100644 index 00000000..d2e49a48 --- /dev/null +++ b/configs/coins/energi.json @@ -0,0 +1,65 @@ +{ + "coin": { + "name": "Energi", + "shortcut": "NRG", + "label": "Energi", + "alias": "energi" + }, + "ports": { + "backend_rpc": 8196, + "backend_message_queue": 0, + "backend_p2p": 38496, + "backend_http": 8296, + "blockbook_internal": 9196, + "blockbook_public": 9296 + }, + "ipc": { + "rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}", + "rpc_timeout": 25 + }, + "backend": { + "package_name": "backend-energi", + "package_revision": "Pantani", + "system_user": "energi", + "version": "3.0.6-b0f6ac4", + "binary_url": "https://s3-us-west-2.amazonaws.com/download.energi.software/releases/energi3/3.0.7/energi3-3.0.7-linux-amd64.tgz", + "verification_type": "sha256", + "verification_source": "6f6e0fe03f9cd38fe29addffb42ae4217656a7a9ee9be3a824f104b6a4ac7237", + "extract_command": "tar -C backend --strip 1 -xf", + "exclude_files": [], + "exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/energi3 --ipcdisable --syncmode full --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 38496 --ws --wsapi eth,net,web3 --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" --rpc --rpcapi personal,eth,net,web3b --rpcport 8296 -rpcaddr 0.0.0.0 --rpccorsdomain \"*\" --rpcvhosts \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'", + "logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log", + "postinst_script_template": "", + "service_type": "forking", + "service_additional_params_template": "", + "protect_memory": true, + "mainnet": true, + "server_config_file": "", + "client_config_file": "" + }, + "blockbook": { + "package_name": "blockbook-energi", + "system_user": "blockbook-energi", + "internal_binding_template": ":{{.Ports.BlockbookInternal}}", + "public_binding_template": ":{{.Ports.BlockbookPublic}}", + "explorer_url": "", + "additional_params": "", + "block_chain": { + "parse": true, + "mempool_workers": 8, + "mempool_sub_workers": 2, + "block_addresses_to_keep": 300, + "slip44": 9797, + "additional_params": { + "mempoolTxTimeoutHours": 48, + "queryBackendOnMempoolResync": false, + "fiat_rates": "coingecko", + "fiat_rates_params": "{\"url\": \"https://api.coingecko.com/api/v3\", \"coin\": \"energi\", \"periodSeconds\": 60}" + } + } + }, + "meta": { + "package_maintainer": "Pantani", + "package_maintainer_email": "danpantani@gmail.com" + } +} \ No newline at end of file From a509d7f16ec66329844082f74b7ed50db97022e8 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 14 Dec 2020 19:42:22 -0300 Subject: [PATCH 2/8] add port definitions --- docs/ports.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/ports.md b/docs/ports.md index d38a9f03..3656ab88 100644 --- a/docs/ports.md +++ b/docs/ports.md @@ -45,6 +45,7 @@ | Omotenashicoin | 9094 | 9194 | 8094 | 38394 | | BitZeny | 9095 | 9195 | 8095 | 38395 | | Trezarcoin | 9096 | 9196 | 8096 | 38396 | +| Energi | 9196 | 9296 | 8196 | 38496 p2p, 8296 http | | Bitcoin Signet | 19020 | 19120 | 18020 | 48320 | | Ethereum Goerli | 19026 | 19126 | 18026 | 48326 p2p | | Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 | From ce7977c67b676c1d3cbbfa2d7e8dcd24e2b7fc07 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 14 Dec 2020 19:59:55 -0300 Subject: [PATCH 3/8] add more unit tests --- bchain/coins/energi/erc20.go | 15 ------------- bchain/coins/energi/ethparser.go | 7 ++++++ bchain/coins/energi/ethparser_test.go | 31 +++++++++++++++++++++++++++ bchain/coins/energi/ethrpc.go | 8 +------ 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/bchain/coins/energi/erc20.go b/bchain/coins/energi/erc20.go index 920b0588..b659d188 100644 --- a/bchain/coins/energi/erc20.go +++ b/bchain/coins/energi/erc20.go @@ -15,21 +15,6 @@ import ( "github.com/trezor/blockbook/bchain" ) -var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"}, -{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"}, -{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"}, -{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"}, -{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"}, -{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"}, -{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"}, -{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"}, -{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"}, -{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}, -{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}, -{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"}, -{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"}, -{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` - // doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event const erc20TransferMethodSignature = "0xa9059cbb" const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" diff --git a/bchain/coins/energi/ethparser.go b/bchain/coins/energi/ethparser.go index d67609f2..b15be22d 100644 --- a/bchain/coins/energi/ethparser.go +++ b/bchain/coins/energi/ethparser.go @@ -531,3 +531,10 @@ func GetEthereumTxDataFromSpecificData(coinSpecificData interface{}) *EthereumTx } return &etd } + +func toBlockNumArg(number *big.Int) string { + if number == nil { + return "latest" + } + return hexutil.EncodeBig(number) +} diff --git a/bchain/coins/energi/ethparser_test.go b/bchain/coins/energi/ethparser_test.go index 459131d9..add33968 100644 --- a/bchain/coins/energi/ethparser_test.go +++ b/bchain/coins/energi/ethparser_test.go @@ -400,3 +400,34 @@ func TestEthereumParser_GetEthereumTxData(t *testing.T) { }) } } + +func Test_toBlockNumArg(t *testing.T) { + tests := []struct { + name string + number *big.Int + want string + }{ + { + name: "test a valid number 1", + number: big.NewInt(1), + want: "0x1", + }, + { + name: "test a valid number 2", + number: big.NewInt(2), + want: "0x2", + }, + { + name: "test the latest block", + number: nil, + want: "latest", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := toBlockNumArg(tt.number); got != tt.want { + t.Errorf("toBlockNumArg() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/bchain/coins/energi/ethrpc.go b/bchain/coins/energi/ethrpc.go index eeb4c672..ebf79ec8 100644 --- a/bchain/coins/energi/ethrpc.go +++ b/bchain/coins/energi/ethrpc.go @@ -777,13 +777,7 @@ func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { return b.Parser } -func toBlockNumArg(number *big.Int) string { - if number == nil { - return "latest" - } - return hexutil.EncodeBig(number) -} - +// GetChainParser returns ethereum block header by number. func (b *EthereumRPC) headerByNumber(ctx context.Context, number *big.Int) (*header, error) { var head header err := b.rpc.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) From 0447b72add609c90a5bdfc3f54d2adede48a1962 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 14 Dec 2020 20:02:05 -0300 Subject: [PATCH 4/8] add energi blockchain --- bchain/coins/blockchain.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 38cae788..486b39cf 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -24,6 +24,7 @@ import ( "github.com/trezor/blockbook/bchain/coins/digibyte" "github.com/trezor/blockbook/bchain/coins/divi" "github.com/trezor/blockbook/bchain/coins/dogecoin" + "github.com/trezor/blockbook/bchain/coins/energi" "github.com/trezor/blockbook/bchain/coins/eth" "github.com/trezor/blockbook/bchain/coins/firo" "github.com/trezor/blockbook/bchain/coins/flo" @@ -69,6 +70,7 @@ func init() { BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC + BlockChainFactories["Energi"] = energi.NewEthereumRPC BlockChainFactories["Bcash"] = bch.NewBCashRPC BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC BlockChainFactories["Bgold"] = btg.NewBGoldRPC From 9ca0eb348c8327acf72fd435472f66f22d8749bb Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Mon, 14 Dec 2020 20:07:01 -0300 Subject: [PATCH 5/8] change energi signatures --- bchain/coins/blockchain.go | 2 +- bchain/coins/energi/erc20.go | 6 +- bchain/coins/energi/erc20_test.go | 2 +- bchain/coins/energi/ethparser.go | 38 ++++++------- bchain/coins/energi/ethparser_test.go | 32 +++++------ bchain/coins/energi/ethrpc.go | 82 +++++++++++++-------------- 6 files changed, 81 insertions(+), 81 deletions(-) diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 486b39cf..d627ebed 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -70,7 +70,7 @@ func init() { BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC - BlockChainFactories["Energi"] = energi.NewEthereumRPC + BlockChainFactories["Energi"] = energi.NewEnergiRPC BlockChainFactories["Bcash"] = bch.NewBCashRPC BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC BlockChainFactories["Bgold"] = btg.NewBGoldRPC diff --git a/bchain/coins/energi/erc20.go b/bchain/coins/energi/erc20.go index b659d188..48582c99 100644 --- a/bchain/coins/energi/erc20.go +++ b/bchain/coins/energi/erc20.go @@ -91,7 +91,7 @@ func erc20GetTransfersFromTx(tx *rpcTransaction) ([]bchain.Erc20Transfer, error) return r, nil } -func (b *EthereumRPC) ethCall(data, to string) (string, error) { +func (b *EnergiRPC) ethCall(data, to string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var r string @@ -159,7 +159,7 @@ func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string } // EthereumTypeGetErc20ContractInfo returns information about ERC20 contract -func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { +func (b *EnergiRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { cds := string(contractDesc) cachedContractsMux.Lock() contract, found := cachedContracts[cds] @@ -211,7 +211,7 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre } // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address -func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { +func (b *EnergiRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { addr := EIP55Address(addrDesc) contract := EIP55Address(contractDesc) req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] diff --git a/bchain/coins/energi/erc20_test.go b/bchain/coins/energi/erc20_test.go index c70a9920..ff82b92a 100644 --- a/bchain/coins/energi/erc20_test.go +++ b/bchain/coins/energi/erc20_test.go @@ -162,7 +162,7 @@ func TestErc20_parseErc20StringProperty(t *testing.T) { } func TestErc20_erc20GetTransfersFromTx(t *testing.T) { - p := NewEthereumParser(1) + p := NewEnergiParser(1) b := dbtestdata.GetTestEthereumTypeBlock1(p) bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16) tests := []struct { diff --git a/bchain/coins/energi/ethparser.go b/bchain/coins/energi/ethparser.go index b15be22d..f1773578 100644 --- a/bchain/coins/energi/ethparser.go +++ b/bchain/coins/energi/ethparser.go @@ -18,14 +18,14 @@ const EthereumTypeAddressDescriptorLen = 20 // EtherAmountDecimalPoint defines number of decimal points in Ether amounts const EtherAmountDecimalPoint = 18 -// EthereumParser handle -type EthereumParser struct { +// EnergiParser handle +type EnergiParser struct { *bchain.BaseParser } -// NewEthereumParser returns new EthereumParser instance -func NewEthereumParser(b int) *EthereumParser { - return &EthereumParser{&bchain.BaseParser{ +// NewEnergiParser returns new EnergiParser instance +func NewEnergiParser(b int) *EnergiParser { + return &EnergiParser{&bchain.BaseParser{ BlockAddressesToKeep: b, AmountDecimalPoint: EtherAmountDecimalPoint, }} @@ -104,7 +104,7 @@ type header struct { Hash hash `json:"hash"` } -func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) { +func (p *EnergiParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, fixEIP55 bool) (*bchain.Tx, error) { txid := tx.Hash var ( fa, ta []string @@ -169,7 +169,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc } // GetAddrDescFromVout returns internal address representation of given transaction output -func (p *EthereumParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) { +func (p *EnergiParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) { if len(output.ScriptPubKey.Addresses) != 1 { return nil, bchain.ErrAddressMissing } @@ -181,7 +181,7 @@ func has0xPrefix(s string) bool { } // GetAddrDescFromAddress returns internal address representation of given address -func (p *EthereumParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) { +func (p *EnergiParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) { // github.com/ethereum/go-ethereum/common.HexToAddress does not handle address errors, using own decoding if has0xPrefix(address) { address = address[2:] @@ -230,12 +230,12 @@ func EIP55AddressFromAddress(address string) string { } // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable -func (p *EthereumParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { +func (p *EnergiParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { return []string{EIP55Address(addrDesc)}, true, nil } // GetScriptFromAddrDesc returns output script for given address descriptor -func (p *EthereumParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]byte, error) { +func (p *EnergiParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]byte, error) { return addrDesc, nil } @@ -262,7 +262,7 @@ func hexEncodeBig(b []byte) string { } // PackTx packs transaction to byte array -func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { +func (p *EnergiParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { var err error var n uint64 r, ok := tx.CoinSpecificData.(completeTransaction) @@ -358,7 +358,7 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ( } // UnpackTx unpacks transaction from byte array -func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { +func (p *EnergiParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { var pt ProtoCompleteTransaction err := proto.Unmarshal(buf, &pt) if err != nil { @@ -412,12 +412,12 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { } // PackedTxidLen returns length in bytes of packed txid -func (p *EthereumParser) PackedTxidLen() int { +func (p *EnergiParser) PackedTxidLen() int { return 32 } // PackTxid packs txid to byte array -func (p *EthereumParser) PackTxid(txid string) ([]byte, error) { +func (p *EnergiParser) PackTxid(txid string) ([]byte, error) { if has0xPrefix(txid) { txid = txid[2:] } @@ -425,12 +425,12 @@ func (p *EthereumParser) PackTxid(txid string) ([]byte, error) { } // UnpackTxid unpacks byte array to txid -func (p *EthereumParser) UnpackTxid(buf []byte) (string, error) { +func (p *EnergiParser) UnpackTxid(buf []byte) (string, error) { return hexutil.Encode(buf), nil } // PackBlockHash packs block hash to byte array -func (p *EthereumParser) PackBlockHash(hash string) ([]byte, error) { +func (p *EnergiParser) PackBlockHash(hash string) ([]byte, error) { if has0xPrefix(hash) { hash = hash[2:] } @@ -438,12 +438,12 @@ func (p *EthereumParser) PackBlockHash(hash string) ([]byte, error) { } // UnpackBlockHash unpacks byte array to block hash -func (p *EthereumParser) UnpackBlockHash(buf []byte) (string, error) { +func (p *EnergiParser) UnpackBlockHash(buf []byte) (string, error) { return hexutil.Encode(buf), nil } // GetChainType returns EthereumType -func (p *EthereumParser) GetChainType() bchain.ChainType { +func (p *EnergiParser) GetChainType() bchain.ChainType { return bchain.ChainEthereumType } @@ -463,7 +463,7 @@ func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { } // EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx -func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) { +func (p *EnergiParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) { var r []bchain.Erc20Transfer var err error csd, ok := tx.CoinSpecificData.(completeTransaction) diff --git a/bchain/coins/energi/ethparser_test.go b/bchain/coins/energi/ethparser_test.go index add33968..115eed77 100644 --- a/bchain/coins/energi/ethparser_test.go +++ b/bchain/coins/energi/ethparser_test.go @@ -54,7 +54,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := NewEthereumParser(1) + p := NewEnergiParser(1) got, err := p.GetAddrDescFromAddress(tt.args.address) if (err != nil) != tt.wantErr { t.Errorf("EthParser.GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr) @@ -235,7 +235,7 @@ func init() { } -func TestEthereumParser_PackTx(t *testing.T) { +func TestEnergiParser_PackTx(t *testing.T) { type args struct { tx *bchain.Tx height uint32 @@ -243,7 +243,7 @@ func TestEthereumParser_PackTx(t *testing.T) { } tests := []struct { name string - p *EthereumParser + p *EnergiParser args args want string wantErr bool @@ -285,29 +285,29 @@ func TestEthereumParser_PackTx(t *testing.T) { want: dbtestdata.EthTx1NoStatusPacked, }, } - p := NewEthereumParser(1) + p := NewEnergiParser(1) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { 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) + t.Errorf("EnergiParser.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) + t.Errorf("EnergiParser.PackTx() = %v, want %v", h, tt.want) } }) } } -func TestEthereumParser_UnpackTx(t *testing.T) { +func TestEnergiParser_UnpackTx(t *testing.T) { type args struct { hex string } tests := []struct { name string - p *EthereumParser + p *EnergiParser args args want *bchain.Tx want1 uint32 @@ -338,7 +338,7 @@ func TestEthereumParser_UnpackTx(t *testing.T) { want1: 4321000, }, } - p := NewEthereumParser(1) + p := NewEnergiParser(1) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { b, err := hex.DecodeString(tt.args.hex) @@ -347,7 +347,7 @@ func TestEthereumParser_UnpackTx(t *testing.T) { } got, got1, err := p.UnpackTx(b) if (err != nil) != tt.wantErr { - t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("EnergiParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr) return } // DeepEqual has problems with pointers in completeTransaction @@ -359,22 +359,22 @@ func TestEthereumParser_UnpackTx(t *testing.T) { wc.CoinSpecificData = nil if fmt.Sprint(gc) != fmt.Sprint(wc) { // if !reflect.DeepEqual(gc, wc) { - t.Errorf("EthereumParser.UnpackTx() gc got = %+v, want %+v", gc, wc) + t.Errorf("EnergiParser.UnpackTx() gc got = %+v, want %+v", gc, wc) } if !reflect.DeepEqual(gs.Tx, ws.Tx) { - t.Errorf("EthereumParser.UnpackTx() gs.Tx got = %+v, want %+v", gs.Tx, ws.Tx) + t.Errorf("EnergiParser.UnpackTx() gs.Tx got = %+v, want %+v", gs.Tx, ws.Tx) } if !reflect.DeepEqual(gs.Receipt, ws.Receipt) { - t.Errorf("EthereumParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt) + t.Errorf("EnergiParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt) } if got1 != tt.want1 { - t.Errorf("EthereumParser.UnpackTx() got1 = %v, want %v", got1, tt.want1) + t.Errorf("EnergiParser.UnpackTx() got1 = %v, want %v", got1, tt.want1) } }) } } -func TestEthereumParser_GetEthereumTxData(t *testing.T) { +func TestEnergiParser_GetEthereumTxData(t *testing.T) { tests := []struct { name string tx *bchain.Tx @@ -395,7 +395,7 @@ func TestEthereumParser_GetEthereumTxData(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := GetEthereumTxData(tt.tx) if got.Data != tt.want { - t.Errorf("EthereumParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want) + t.Errorf("EnergiParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want) } }) } diff --git a/bchain/coins/energi/ethrpc.go b/bchain/coins/energi/ethrpc.go index ebf79ec8..57aaed7b 100644 --- a/bchain/coins/energi/ethrpc.go +++ b/bchain/coins/energi/ethrpc.go @@ -41,13 +41,13 @@ type Configuration struct { QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"` } -// EthereumRPC is an interface to JSON-RPC eth service. -type EthereumRPC struct { +// EnergiRPC is an interface to JSON-RPC eth service. +type EnergiRPC struct { *bchain.BaseChain client *ethclient.Client rpc *rpc.Client timeout time.Duration - Parser *EthereumParser + Parser *EnergiParser Mempool *bchain.MempoolEthereumType mempoolInitialized bool bestHeaderLock sync.Mutex @@ -60,8 +60,8 @@ type EthereumRPC struct { ChainConfig *Configuration } -// NewEthereumRPC returns new EthRPC instance. -func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { +// NewEnergiRPC returns new EthRPC instance. +func NewEnergiRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) { var err error var c Configuration err = json.Unmarshal(config, &c) @@ -78,7 +78,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification return nil, err } - s := &EthereumRPC{ + s := &EnergiRPC{ BaseChain: &bchain.BaseChain{}, client: ec, rpc: rc, @@ -86,7 +86,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification } // always create parser - s.Parser = NewEthereumParser(c.BlockAddressesToKeep) + s.Parser = NewEnergiParser(c.BlockAddressesToKeep) s.timeout = time.Duration(c.RPCTimeout) * time.Second // new blocks notifications handling @@ -140,7 +140,7 @@ func openRPC(url string) (*rpc.Client, *ethclient.Client, error) { } // Initialize initializes ethereum rpc interface -func (b *EthereumRPC) Initialize() error { +func (b *EnergiRPC) Initialize() error { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() @@ -168,7 +168,7 @@ func (b *EthereumRPC) Initialize() error { } // CreateMempool creates mempool if not already created, however does not initialize it -func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { +func (b *EnergiRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, error) { if b.Mempool == nil { b.Mempool = bchain.NewMempoolEthereumType(chain, b.ChainConfig.MempoolTxTimeoutHours, b.ChainConfig.QueryBackendOnMempoolResync) glog.Info("mempool created, MempoolTxTimeoutHours=", b.ChainConfig.MempoolTxTimeoutHours, ", QueryBackendOnMempoolResync=", b.ChainConfig.QueryBackendOnMempoolResync) @@ -177,7 +177,7 @@ func (b *EthereumRPC) CreateMempool(chain bchain.BlockChain) (bchain.Mempool, er } // InitializeMempool creates subscriptions to newHeads and newPendingTransactions -func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error { +func (b *EnergiRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOutpointFunc, onNewTxAddr bchain.OnNewTxAddrFunc, onNewTx bchain.OnNewTxFunc) error { if b.Mempool == nil { return errors.New("Mempool not created") } @@ -203,7 +203,7 @@ func (b *EthereumRPC) InitializeMempool(addrDescForOutpoint bchain.AddrDescForOu return nil } -func (b *EthereumRPC) subscribeEvents() error { +func (b *EnergiRPC) subscribeEvents() error { // subscriptions if err := b.subscribe(func() (*rpc.ClientSubscription, error) { // invalidate the previous subscription - it is either the first one or there was an error @@ -241,7 +241,7 @@ func (b *EthereumRPC) subscribeEvents() error { } // subscribe subscribes notification and tries to resubscribe in case of error -func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error { +func (b *EnergiRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error { s, err := f() if err != nil { return err @@ -280,7 +280,7 @@ func (b *EthereumRPC) subscribe(f func() (*rpc.ClientSubscription, error)) error return nil } -func (b *EthereumRPC) closeRPC() { +func (b *EnergiRPC) closeRPC() { if b.newBlockSubscription != nil { b.newBlockSubscription.Unsubscribe() } @@ -292,7 +292,7 @@ func (b *EthereumRPC) closeRPC() { } } -func (b *EthereumRPC) reconnectRPC() error { +func (b *EnergiRPC) reconnectRPC() error { glog.Info("Reconnecting RPC") b.closeRPC() rc, ec, err := openRPC(b.ChainConfig.RPCURL) @@ -305,7 +305,7 @@ func (b *EthereumRPC) reconnectRPC() error { } // Shutdown cleans up rpc interface to ethereum -func (b *EthereumRPC) Shutdown(ctx context.Context) error { +func (b *EnergiRPC) Shutdown(ctx context.Context) error { b.closeRPC() close(b.chanNewBlock) glog.Info("rpc: shutdown") @@ -313,17 +313,17 @@ func (b *EthereumRPC) Shutdown(ctx context.Context) error { } // GetCoinName returns coin name -func (b *EthereumRPC) GetCoinName() string { +func (b *EnergiRPC) GetCoinName() string { return b.ChainConfig.CoinName } // GetSubversion returns empty string, ethereum does not have subversion -func (b *EthereumRPC) GetSubversion() string { +func (b *EnergiRPC) GetSubversion() string { return "" } // GetChainInfo returns information about the connected backend -func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { +func (b *EnergiRPC) GetChainInfo() (*bchain.ChainInfo, error) { h, err := b.getBestHeader() if err != nil { return nil, err @@ -357,7 +357,7 @@ func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) { return rv, nil } -func (b *EthereumRPC) getBestHeader() (*header, error) { +func (b *EnergiRPC) getBestHeader() (*header, error) { b.bestHeaderLock.Lock() defer b.bestHeaderLock.Unlock() // if the best header was not updated for 15 minutes, there could be a subscription problem, reconnect RPC @@ -384,7 +384,7 @@ func (b *EthereumRPC) getBestHeader() (*header, error) { } // GetBestBlockHash returns hash of the tip of the best-block-chain -func (b *EthereumRPC) GetBestBlockHash() (string, error) { +func (b *EnergiRPC) GetBestBlockHash() (string, error) { h, err := b.getBestHeader() if err != nil { return "", err @@ -393,7 +393,7 @@ func (b *EthereumRPC) GetBestBlockHash() (string, error) { } // GetBestBlockHeight returns height of the tip of the best-block-chain -func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { +func (b *EnergiRPC) GetBestBlockHeight() (uint32, error) { h, err := b.getBestHeader() if err != nil { return 0, err @@ -402,7 +402,7 @@ func (b *EthereumRPC) GetBestBlockHeight() (uint32, error) { } // GetBlockHash returns hash of block in best-block-chain at given height -func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { +func (b *EnergiRPC) GetBlockHash(height uint32) (string, error) { var n big.Int n.SetUint64(uint64(height)) ctx, cancel := context.WithTimeout(context.Background(), b.timeout) @@ -417,7 +417,7 @@ func (b *EthereumRPC) GetBlockHash(height uint32) (string, error) { return string(h.Hash), nil } -func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) { +func (b *EnergiRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, error) { height, err := ethNumber(h.Number) if err != nil { return nil, err @@ -445,7 +445,7 @@ func (b *EthereumRPC) ethHeaderToBlockHeader(h *rpcHeader) (*bchain.BlockHeader, } // GetBlockHeader returns header of block with given hash -func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { +func (b *EnergiRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { raw, err := b.getBlockRaw(hash, 0, false) if err != nil { return nil, err @@ -457,7 +457,7 @@ func (b *EthereumRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) { return b.ethHeaderToBlockHeader(&h) } -func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) { +func (b *EnergiRPC) computeConfirmations(n uint64) (uint32, error) { bh, err := b.getBestHeader() if err != nil { return 0, err @@ -467,7 +467,7 @@ func (b *EthereumRPC) computeConfirmations(n uint64) (uint32, error) { return uint32(bn - n + 1), nil } -func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) { +func (b *EnergiRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (json.RawMessage, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var raw json.RawMessage @@ -489,7 +489,7 @@ func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (jso return raw, nil } -func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) { +func (b *EnergiRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*rpcLog, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var logs []rpcLogWithTxHash @@ -510,7 +510,7 @@ func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]* } // 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) { +func (b *EnergiRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { raw, err := b.getBlockRaw(hash, height, true) if err != nil { return nil, err @@ -552,7 +552,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error } // GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids -func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { +func (b *EnergiRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { raw, err := b.getBlockRaw(hash, 0, false) if err != nil { return nil, err @@ -579,12 +579,12 @@ func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { // GetTransactionForMempool returns a transaction by the transaction ID. // It could be optimized for mempool, i.e. without block time and confirmations -func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { +func (b *EnergiRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { return b.GetTransaction(txid) } // GetTransaction returns a transaction by the transaction ID. -func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { +func (b *EnergiRPC) GetTransaction(txid string) (*bchain.Tx, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var tx *rpcTransaction @@ -647,7 +647,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { } // GetTransactionSpecific returns json as returned by backend, with all coin specific data -func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { +func (b *EnergiRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) { csd, ok := tx.CoinSpecificData.(completeTransaction) if !ok { ntx, err := b.GetTransaction(tx.Txid) @@ -664,7 +664,7 @@ func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, er } // GetMempoolTransactions returns transactions in mempool -func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) { +func (b *EnergiRPC) GetMempoolTransactions() ([]string, error) { raw, err := b.getBlockRaw("pending", 0, false) if err != nil { return nil, err @@ -679,12 +679,12 @@ func (b *EthereumRPC) GetMempoolTransactions() ([]string, error) { } // EstimateFee returns fee estimation -func (b *EthereumRPC) EstimateFee(blocks int) (big.Int, error) { +func (b *EnergiRPC) EstimateFee(blocks int) (big.Int, error) { return b.EstimateSmartFee(blocks, true) } // EstimateSmartFee returns fee estimation -func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { +func (b *EnergiRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var r big.Int @@ -705,7 +705,7 @@ func getStringFromMap(p string, params map[string]interface{}) (string, bool) { } // EthereumTypeEstimateGas returns estimation of gas consumption for given transaction parameters -func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) { +func (b *EnergiRPC) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() msg := ethereum.CallMsg{} @@ -738,7 +738,7 @@ func (b *EthereumRPC) EthereumTypeEstimateGas(params map[string]interface{}) (ui } // SendRawTransaction sends raw transaction -func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { +func (b *EnergiRPC) SendRawTransaction(hex string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var raw json.RawMessage @@ -759,26 +759,26 @@ func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) { } // EthereumTypeGetBalance returns current balance of an address -func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) { +func (b *EnergiRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor) (*big.Int, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) } // EthereumTypeGetNonce returns current balance of an address -func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) { +func (b *EnergiRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil) } // GetChainParser returns ethereum BlockChainParser -func (b *EthereumRPC) GetChainParser() bchain.BlockChainParser { +func (b *EnergiRPC) GetChainParser() bchain.BlockChainParser { return b.Parser } // GetChainParser returns ethereum block header by number. -func (b *EthereumRPC) headerByNumber(ctx context.Context, number *big.Int) (*header, error) { +func (b *EnergiRPC) headerByNumber(ctx context.Context, number *big.Int) (*header, error) { var head header err := b.rpc.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) if err != nil { From f07fc04ae73b82730e30bdb7929a06edb25dbe20 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Tue, 15 Dec 2020 21:32:39 -0300 Subject: [PATCH 6/8] update backend version --- bchain/coins/energi/ethtx.pb.go | 8 ++++---- configs/coins/energi.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bchain/coins/energi/ethtx.pb.go b/bchain/coins/energi/ethtx.pb.go index c8c426fc..08789ebf 100644 --- a/bchain/coins/energi/ethtx.pb.go +++ b/bchain/coins/energi/ethtx.pb.go @@ -1,11 +1,11 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: bchain/coins/eth/ethtx.proto +// source: bchain/coins/energi/ethtx.proto /* -Package eth is a generated protocol buffer package. +Package energi is a generated protocol buffer package. It is generated from these files: - bchain/coins/eth/ethtx.proto + bchain/coins/energi/ethtx.proto It has these top-level messages: ProtoCompleteTransaction @@ -228,7 +228,7 @@ func init() { proto.RegisterType((*ProtoCompleteTransaction_ReceiptType_LogType)(nil), "eth.ProtoCompleteTransaction.ReceiptType.LogType") } -func init() { proto.RegisterFile("bchain/coins/eth/ethtx.proto", fileDescriptor0) } +func init() { proto.RegisterFile("bchain/coins/energi/ethtx.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 409 bytes of a gzipped FileDescriptorProto diff --git a/configs/coins/energi.json b/configs/coins/energi.json index d2e49a48..5701d2a3 100644 --- a/configs/coins/energi.json +++ b/configs/coins/energi.json @@ -21,7 +21,7 @@ "package_name": "backend-energi", "package_revision": "Pantani", "system_user": "energi", - "version": "3.0.6-b0f6ac4", + "version": "3.0.7", "binary_url": "https://s3-us-west-2.amazonaws.com/download.energi.software/releases/energi3/3.0.7/energi3-3.0.7-linux-amd64.tgz", "verification_type": "sha256", "verification_source": "6f6e0fe03f9cd38fe29addffb42ae4217656a7a9ee9be3a824f104b6a4ac7237", From db5352ec8c2e93609522f7408b927e7aa9b1f001 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Wed, 3 Feb 2021 18:30:35 -0300 Subject: [PATCH 7/8] fix energi ports --- configs/coins/energi.json | 12 ++++++------ docs/ports.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/configs/coins/energi.json b/configs/coins/energi.json index 5701d2a3..c9dc7522 100644 --- a/configs/coins/energi.json +++ b/configs/coins/energi.json @@ -6,12 +6,12 @@ "alias": "energi" }, "ports": { - "backend_rpc": 8196, + "backend_rpc": 8097, "backend_message_queue": 0, - "backend_p2p": 38496, - "backend_http": 8296, - "blockbook_internal": 9196, - "blockbook_public": 9296 + "backend_p2p": 38397, + "backend_http": 8197, + "blockbook_internal": 9097, + "blockbook_public": 9197 }, "ipc": { "rpc_url_template": "ws://127.0.0.1:{{.Ports.BackendRPC}}", @@ -27,7 +27,7 @@ "verification_source": "6f6e0fe03f9cd38fe29addffb42ae4217656a7a9ee9be3a824f104b6a4ac7237", "extract_command": "tar -C backend --strip 1 -xf", "exclude_files": [], - "exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/energi3 --ipcdisable --syncmode full --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 38496 --ws --wsapi eth,net,web3 --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" --rpc --rpcapi personal,eth,net,web3b --rpcport 8296 -rpcaddr 0.0.0.0 --rpccorsdomain \"*\" --rpcvhosts \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'", + "exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/energi3 --ipcdisable --syncmode full --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 38397 --ws --wsapi eth,net,web3 --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" --rpc --rpcapi personal,eth,net,web3b --rpcport 8197 -rpcaddr 0.0.0.0 --rpccorsdomain \"*\" --rpcvhosts \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'", "logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log", "postinst_script_template": "", "service_type": "forking", diff --git a/docs/ports.md b/docs/ports.md index 3656ab88..ec6cdacd 100644 --- a/docs/ports.md +++ b/docs/ports.md @@ -8,7 +8,7 @@ | Dash | 9033 | 9133 | 8033 | 38333 | | Litecoin | 9034 | 9134 | 8034 | 38334 | | Bitcoin Gold | 9035 | 9135 | 8035 | 38335 | -| Ethereum | 9036 | 9136 | 8036 | 38336 p2p, 8136 http | +| Ethereum | 9036 | 9136 | 8036 | 8136 http, 38336 p2p | | Ethereum Classic | 9037 | 9137 | 8037 | | | Dogecoin | 9038 | 9138 | 8038 | 38338 | | Namecoin | 9039 | 9139 | 8039 | 38339 | @@ -45,7 +45,7 @@ | Omotenashicoin | 9094 | 9194 | 8094 | 38394 | | BitZeny | 9095 | 9195 | 8095 | 38395 | | Trezarcoin | 9096 | 9196 | 8096 | 38396 | -| Energi | 9196 | 9296 | 8196 | 38496 p2p, 8296 http | +| Energi | 9097 | 9197 | 8097 | 38397 p2p, 8197 http | | Bitcoin Signet | 19020 | 19120 | 18020 | 48320 | | Ethereum Goerli | 19026 | 19126 | 18026 | 48326 p2p | | Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 | From 598819a661deb9e78cc37d26a6b42ff0d417f894 Mon Sep 17 00:00:00 2001 From: Danilo Pantani Date: Thu, 4 Feb 2021 18:18:22 -0300 Subject: [PATCH 8/8] fix energi type --- bchain/coins/energi/ethparser.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/bchain/coins/energi/ethparser.go b/bchain/coins/energi/ethparser.go index f1773578..82528192 100644 --- a/bchain/coins/energi/ethparser.go +++ b/bchain/coins/energi/ethparser.go @@ -442,26 +442,6 @@ func (p *EnergiParser) UnpackBlockHash(buf []byte) (string, error) { return hexutil.Encode(buf), nil } -// GetChainType returns EthereumType -func (p *EnergiParser) GetChainType() bchain.ChainType { - return bchain.ChainEthereumType -} - -// GetHeightFromTx returns ethereum specific data from bchain.Tx -func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { - var bn string - csd, ok := tx.CoinSpecificData.(completeTransaction) - if !ok { - return 0, errors.New("Missing CoinSpecificData") - } - bn = csd.Tx.BlockNumber - n, err := hexutil.DecodeUint64(bn) - if err != nil { - return 0, errors.Annotatef(err, "BlockNumber %v", bn) - } - return uint32(n), nil -} - // EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx func (p *EnergiParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) { var r []bchain.Erc20Transfer