Merge 598819a661
into 1f6cddd4ab
commit
60901429ef
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/trezor/blockbook/bchain/coins/digibyte"
|
"github.com/trezor/blockbook/bchain/coins/digibyte"
|
||||||
"github.com/trezor/blockbook/bchain/coins/divi"
|
"github.com/trezor/blockbook/bchain/coins/divi"
|
||||||
"github.com/trezor/blockbook/bchain/coins/dogecoin"
|
"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/eth"
|
||||||
"github.com/trezor/blockbook/bchain/coins/firo"
|
"github.com/trezor/blockbook/bchain/coins/firo"
|
||||||
"github.com/trezor/blockbook/bchain/coins/flo"
|
"github.com/trezor/blockbook/bchain/coins/flo"
|
||||||
|
@ -69,6 +70,7 @@ func init() {
|
||||||
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
|
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
|
||||||
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
|
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
|
||||||
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
|
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
|
||||||
|
BlockChainFactories["Energi"] = energi.NewEnergiRPC
|
||||||
BlockChainFactories["Bcash"] = bch.NewBCashRPC
|
BlockChainFactories["Bcash"] = bch.NewBCashRPC
|
||||||
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
|
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
|
||||||
BlockChainFactories["Bgold"] = btg.NewBGoldRPC
|
BlockChainFactories["Bgold"] = btg.NewBGoldRPC
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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
|
||||||
|
}
|
|
@ -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 := NewEnergiParser(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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,520 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
// EnergiParser handle
|
||||||
|
type EnergiParser struct {
|
||||||
|
*bchain.BaseParser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEnergiParser returns new EnergiParser instance
|
||||||
|
func NewEnergiParser(b int) *EnergiParser {
|
||||||
|
return &EnergiParser{&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 *EnergiParser) 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 *EnergiParser) 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 *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:]
|
||||||
|
}
|
||||||
|
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 *EnergiParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
|
||||||
|
return []string{EIP55Address(addrDesc)}, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScriptFromAddrDesc returns output script for given address descriptor
|
||||||
|
func (p *EnergiParser) 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 *EnergiParser) 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 *EnergiParser) 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 *EnergiParser) PackedTxidLen() int {
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackTxid packs txid to byte array
|
||||||
|
func (p *EnergiParser) PackTxid(txid string) ([]byte, error) {
|
||||||
|
if has0xPrefix(txid) {
|
||||||
|
txid = txid[2:]
|
||||||
|
}
|
||||||
|
return hex.DecodeString(txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackTxid unpacks byte array to txid
|
||||||
|
func (p *EnergiParser) UnpackTxid(buf []byte) (string, error) {
|
||||||
|
return hexutil.Encode(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PackBlockHash packs block hash to byte array
|
||||||
|
func (p *EnergiParser) PackBlockHash(hash string) ([]byte, error) {
|
||||||
|
if has0xPrefix(hash) {
|
||||||
|
hash = hash[2:]
|
||||||
|
}
|
||||||
|
return hex.DecodeString(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackBlockHash unpacks byte array to block hash
|
||||||
|
func (p *EnergiParser) UnpackBlockHash(buf []byte) (string, error) {
|
||||||
|
return hexutil.Encode(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx
|
||||||
|
func (p *EnergiParser) 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func toBlockNumArg(number *big.Int) string {
|
||||||
|
if number == nil {
|
||||||
|
return "latest"
|
||||||
|
}
|
||||||
|
return hexutil.EncodeBig(number)
|
||||||
|
}
|
|
@ -0,0 +1,433 @@
|
||||||
|
// +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 := 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)
|
||||||
|
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 TestEnergiParser_PackTx(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
tx *bchain.Tx
|
||||||
|
height uint32
|
||||||
|
blockTime int64
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
p *EnergiParser
|
||||||
|
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 := 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("EnergiParser.PackTx() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h := hex.EncodeToString(got)
|
||||||
|
if !reflect.DeepEqual(h, tt.want) {
|
||||||
|
t.Errorf("EnergiParser.PackTx() = %v, want %v", h, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnergiParser_UnpackTx(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
hex string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
p *EnergiParser
|
||||||
|
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 := NewEnergiParser(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("EnergiParser.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("EnergiParser.UnpackTx() gc got = %+v, want %+v", gc, wc)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(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("EnergiParser.UnpackTx() gs.Receipt got = %+v, want %+v", gs.Receipt, ws.Receipt)
|
||||||
|
}
|
||||||
|
if got1 != tt.want1 {
|
||||||
|
t.Errorf("EnergiParser.UnpackTx() got1 = %v, want %v", got1, tt.want1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnergiParser_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("EnergiParser.GetEthereumTxData() = %v, want %v", got.Data, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,790 @@
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnergiRPC is an interface to JSON-RPC eth service.
|
||||||
|
type EnergiRPC struct {
|
||||||
|
*bchain.BaseChain
|
||||||
|
client *ethclient.Client
|
||||||
|
rpc *rpc.Client
|
||||||
|
timeout time.Duration
|
||||||
|
Parser *EnergiParser
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
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 := &EnergiRPC{
|
||||||
|
BaseChain: &bchain.BaseChain{},
|
||||||
|
client: ec,
|
||||||
|
rpc: rc,
|
||||||
|
ChainConfig: &c,
|
||||||
|
}
|
||||||
|
|
||||||
|
// always create parser
|
||||||
|
s.Parser = NewEnergiParser(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 *EnergiRPC) 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 *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)
|
||||||
|
}
|
||||||
|
return b.Mempool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeMempool creates subscriptions to newHeads and newPendingTransactions
|
||||||
|
func (b *EnergiRPC) 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 *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
|
||||||
|
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 *EnergiRPC) 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 *EnergiRPC) closeRPC() {
|
||||||
|
if b.newBlockSubscription != nil {
|
||||||
|
b.newBlockSubscription.Unsubscribe()
|
||||||
|
}
|
||||||
|
if b.newTxSubscription != nil {
|
||||||
|
b.newTxSubscription.Unsubscribe()
|
||||||
|
}
|
||||||
|
if b.rpc != nil {
|
||||||
|
b.rpc.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *EnergiRPC) 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 *EnergiRPC) Shutdown(ctx context.Context) error {
|
||||||
|
b.closeRPC()
|
||||||
|
close(b.chanNewBlock)
|
||||||
|
glog.Info("rpc: shutdown")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCoinName returns coin name
|
||||||
|
func (b *EnergiRPC) GetCoinName() string {
|
||||||
|
return b.ChainConfig.CoinName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubversion returns empty string, ethereum does not have subversion
|
||||||
|
func (b *EnergiRPC) GetSubversion() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChainInfo returns information about the connected backend
|
||||||
|
func (b *EnergiRPC) 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 *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
|
||||||
|
// 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *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
|
||||||
|
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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||||
|
return b.GetTransaction(txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction returns a transaction by the transaction ID.
|
||||||
|
func (b *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) 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 *EnergiRPC) EstimateFee(blocks int) (big.Int, error) {
|
||||||
|
return b.EstimateSmartFee(blocks, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstimateSmartFee returns fee estimation
|
||||||
|
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
|
||||||
|
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 *EnergiRPC) 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 *EnergiRPC) 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 *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 *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 *EnergiRPC) GetChainParser() bchain.BlockChainParser {
|
||||||
|
return b.Parser
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChainParser returns ethereum block header by number.
|
||||||
|
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 {
|
||||||
|
return nil, err
|
||||||
|
} else if len(head.Hash) == 0 {
|
||||||
|
return nil, bchain.ErrBlockNotFound
|
||||||
|
}
|
||||||
|
return &head, nil
|
||||||
|
}
|
|
@ -0,0 +1,261 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// source: bchain/coins/energi/ethtx.proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package energi is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
bchain/coins/energi/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/energi/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,
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"coin": {
|
||||||
|
"name": "Energi",
|
||||||
|
"shortcut": "NRG",
|
||||||
|
"label": "Energi",
|
||||||
|
"alias": "energi"
|
||||||
|
},
|
||||||
|
"ports": {
|
||||||
|
"backend_rpc": 8097,
|
||||||
|
"backend_message_queue": 0,
|
||||||
|
"backend_p2p": 38397,
|
||||||
|
"backend_http": 8197,
|
||||||
|
"blockbook_internal": 9097,
|
||||||
|
"blockbook_public": 9197
|
||||||
|
},
|
||||||
|
"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.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",
|
||||||
|
"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 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",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
| Dash | 9033 | 9133 | 8033 | 38333 |
|
| Dash | 9033 | 9133 | 8033 | 38333 |
|
||||||
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
||||||
| Bitcoin Gold | 9035 | 9135 | 8035 | 38335 |
|
| 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 | |
|
| Ethereum Classic | 9037 | 9137 | 8037 | |
|
||||||
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
||||||
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
| Omotenashicoin | 9094 | 9194 | 8094 | 38394 |
|
| Omotenashicoin | 9094 | 9194 | 8094 | 38394 |
|
||||||
| BitZeny | 9095 | 9195 | 8095 | 38395 |
|
| BitZeny | 9095 | 9195 | 8095 | 38395 |
|
||||||
| Trezarcoin | 9096 | 9196 | 8096 | 38396 |
|
| Trezarcoin | 9096 | 9196 | 8096 | 38396 |
|
||||||
|
| Energi | 9097 | 9197 | 8097 | 38397 p2p, 8197 http |
|
||||||
| Bitcoin Signet | 19020 | 19120 | 18020 | 48320 |
|
| Bitcoin Signet | 19020 | 19120 | 18020 | 48320 |
|
||||||
| Ethereum Goerli | 19026 | 19126 | 18026 | 48326 p2p |
|
| Ethereum Goerli | 19026 | 19126 | 18026 | 48326 p2p |
|
||||||
| Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 |
|
| Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 |
|
||||||
|
|
Loading…
Reference in New Issue