Merge 598819a661
into 1f6cddd4ab
commit
60901429ef
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/trezor/blockbook/bchain/coins/digibyte"
|
||||
"github.com/trezor/blockbook/bchain/coins/divi"
|
||||
"github.com/trezor/blockbook/bchain/coins/dogecoin"
|
||||
"github.com/trezor/blockbook/bchain/coins/energi"
|
||||
"github.com/trezor/blockbook/bchain/coins/eth"
|
||||
"github.com/trezor/blockbook/bchain/coins/firo"
|
||||
"github.com/trezor/blockbook/bchain/coins/flo"
|
||||
|
@ -69,6 +70,7 @@ func init() {
|
|||
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Energi"] = energi.NewEnergiRPC
|
||||
BlockChainFactories["Bcash"] = bch.NewBCashRPC
|
||||
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
|
||||
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 |
|
||||
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
||||
| Bitcoin Gold | 9035 | 9135 | 8035 | 38335 |
|
||||
| Ethereum | 9036 | 9136 | 8036 | 38336 p2p, 8136 http |
|
||||
| Ethereum | 9036 | 9136 | 8036 | 8136 http, 38336 p2p |
|
||||
| Ethereum Classic | 9037 | 9137 | 8037 | |
|
||||
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
||||
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
||||
|
@ -45,6 +45,7 @@
|
|||
| Omotenashicoin | 9094 | 9194 | 8094 | 38394 |
|
||||
| BitZeny | 9095 | 9195 | 8095 | 38395 |
|
||||
| Trezarcoin | 9096 | 9196 | 8096 | 38396 |
|
||||
| Energi | 9097 | 9197 | 8097 | 38397 p2p, 8197 http |
|
||||
| Bitcoin Signet | 19020 | 19120 | 18020 | 48320 |
|
||||
| Ethereum Goerli | 19026 | 19126 | 18026 | 48326 p2p |
|
||||
| Bitcoin Testnet | 19030 | 19130 | 18030 | 48330 |
|
||||
|
|
Loading…
Reference in New Issue