add energi support

pull/537/head
Danilo Pantani 2020-12-14 19:35:21 -03:00
parent 3fe28d185c
commit f3f7feccd2
8 changed files with 2533 additions and 0 deletions

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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,
}

View File

@ -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;
}

View File

@ -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"
}
}