blockbook/bchain/coins/eth/ethrpc.go

379 lines
9.0 KiB
Go
Raw Normal View History

2018-03-15 01:19:02 -06:00
package eth
import (
"blockbook/bchain"
2018-03-15 04:38:31 -06:00
"context"
2018-03-19 05:05:16 -06:00
"encoding/json"
"fmt"
2018-03-19 10:34:51 -06:00
"math/big"
"strconv"
"sync"
2018-03-15 01:19:02 -06:00
"time"
2018-03-15 04:38:31 -06:00
"github.com/golang/glog"
2018-03-19 05:05:16 -06:00
"github.com/juju/errors"
2018-03-15 04:38:31 -06:00
ethereum "github.com/ethereum/go-ethereum"
2018-03-19 10:34:51 -06:00
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
2018-03-15 01:19:02 -06:00
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
2018-03-15 01:19:02 -06:00
)
2018-03-19 10:34:51 -06:00
type EthereumNet uint32
const (
MainNet EthereumNet = 1
TestNet EthereumNet = 3
)
2018-03-15 01:19:02 -06:00
// EthRPC is an interface to JSON-RPC eth service.
type EthRPC struct {
client *ethclient.Client
rpc *rpc.Client
timeout time.Duration
rpcURL string
Parser *EthParser
Testnet bool
Network string
Mempool *bchain.Mempool
bestHeaderMu sync.Mutex
bestHeader *ethtypes.Header
2018-03-15 01:19:02 -06:00
}
2018-03-19 05:05:16 -06:00
type configuration struct {
RPCURL string `json:"rpcURL"`
RPCTimeout int `json:"rpcTimeout"`
}
2018-03-15 01:19:02 -06:00
// NewEthRPC returns new EthRPC instance.
func NewEthRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage)) (bchain.BlockChain, error) {
2018-03-19 05:05:16 -06:00
var err error
var c configuration
err = json.Unmarshal(config, &c)
if err != nil {
return nil, errors.Annotatef(err, "Invalid configuration file")
}
rc, err := rpc.Dial(c.RPCURL)
2018-03-15 01:19:02 -06:00
if err != nil {
return nil, err
}
ec := ethclient.NewClient(rc)
2018-03-15 01:19:02 -06:00
s := &EthRPC{
client: ec,
rpc: rc,
rpcURL: c.RPCURL,
2018-03-15 01:19:02 -06:00
}
// always create parser
s.Parser = &EthParser{}
2018-03-19 10:34:51 -06:00
s.timeout = time.Duration(c.RPCTimeout) * time.Second
2018-03-15 01:19:02 -06:00
return s, nil
}
func (b *EthRPC) Initialize() error {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
2018-03-19 10:34:51 -06:00
defer cancel()
id, err := b.client.NetworkID(ctx)
2018-03-15 04:38:31 -06:00
if err != nil {
return err
2018-03-15 04:38:31 -06:00
}
2018-03-19 10:34:51 -06:00
// parameters for getInfo request
switch EthereumNet(id.Uint64()) {
case MainNet:
b.Testnet = false
b.Network = "livenet"
2018-03-19 10:34:51 -06:00
break
case TestNet:
b.Testnet = true
b.Network = "testnet"
2018-03-19 10:34:51 -06:00
break
default:
return errors.Errorf("Unknown network id %v", id)
2018-03-19 10:34:51 -06:00
}
glog.Info("rpc: block chain ", b.Network)
2018-03-15 01:19:02 -06:00
// b.Mempool = bchain.NewMempool(s, metrics)
return nil
2018-03-15 01:19:02 -06:00
}
2018-03-19 05:05:16 -06:00
func (b *EthRPC) Shutdown() error {
return nil
}
2018-03-15 01:19:02 -06:00
func (b *EthRPC) IsTestnet() bool {
return b.Testnet
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) GetNetworkName() string {
return b.Network
}
func (b *EthRPC) getBestHeader() (*ethtypes.Header, error) {
b.bestHeaderMu.Lock()
defer b.bestHeaderMu.Unlock()
if b.bestHeader == nil {
var err error
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
b.bestHeader, err = b.client.HeaderByNumber(ctx, nil)
if err != nil {
return nil, err
}
}
return b.bestHeader, nil
2018-03-15 01:19:02 -06:00
}
func ethHashToHash(h ethcommon.Hash) string {
return h.Hex()[2:]
}
2018-03-15 01:19:02 -06:00
func (b *EthRPC) GetBestBlockHash() (string, error) {
h, err := b.getBestHeader()
if err != nil {
return "", err
}
return ethHashToHash(h.Hash()), nil
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) GetBestBlockHeight() (uint32, error) {
h, err := b.getBestHeader()
if err != nil {
return 0, err
}
// TODO - can it grow over 2^32 ?
return uint32(h.Number.Uint64()), nil
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) 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.client.HeaderByNumber(ctx, &n)
if err != nil {
return "", err
}
return ethHashToHash(h.Hash()), nil
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) {
hn := h.Number.Uint64()
c, err := b.computeConfirmations(hn)
if err != nil {
return nil, err
}
return &bchain.BlockHeader{
Hash: ethHashToHash(h.Hash()),
Height: uint32(hn),
Confirmations: int(c),
// Next
// Prev
}, nil
}
func (b *EthRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
h, err := b.client.HeaderByHash(ctx, ethcommon.HexToHash(hash))
if err != nil {
return nil, err
}
return b.ethHeaderToBlockHeader(h)
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) computeConfirmations(n uint64) (uint32, error) {
bh, err := b.getBestHeader()
if err != nil {
return 0, err
}
bn := bh.Number.Uint64()
return uint32(bn - n), nil
}
type rpcTransaction struct {
tx *ethtypes.Transaction
txExtraInfo
}
type txExtraInfo struct {
BlockNumber *string
BlockHash ethcommon.Hash
From ethcommon.Address
TransactionIndex string `json:"transactionIndex"`
}
type rpcBlock struct {
Hash ethcommon.Hash `json:"hash"`
Transactions []rpcTransaction `json:"transactions"`
UncleHashes []ethcommon.Hash `json:"uncles"`
}
func ethTxToTx(rtx *rpcTransaction, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
txid := ethHashToHash(rtx.tx.Hash())
n, err := strconv.ParseInt(rtx.TransactionIndex, 16, 64)
if err != nil {
return nil, err
}
var from, to string
ethTo := rtx.tx.To()
if ethTo != nil {
to = ethTo.Hex()[2:]
}
from = rtx.From.Hex()[2:]
return &bchain.Tx{
Blocktime: blocktime,
Confirmations: confirmations,
// Hex
// LockTime
Time: blocktime,
Txid: txid,
Vin: []bchain.Vin{
{
Addresses: []string{from},
// Coinbase
// ScriptSig
// Sequence
// Txid
// Vout
},
},
Vout: []bchain.Vout{
{
N: uint32(n),
Value: float64(rtx.tx.Value().Int64()),
ScriptPubKey: bchain.ScriptPubKey{
// Hex
Addresses: []string{to},
},
},
},
}, nil
}
2018-03-15 01:19:02 -06:00
func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var raw json.RawMessage
err := b.rpc.CallContext(ctx, &raw, "eth_getBlockByHash", ethcommon.HexToHash(hash), true)
if err != nil {
return nil, err
} else if len(raw) == 0 {
return nil, ethereum.NotFound
}
// Decode header and transactions.
var head *ethtypes.Header
var body rpcBlock
if err := json.Unmarshal(raw, &head); err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &body); err != nil {
return nil, err
}
// Quick-verify transaction and uncle lists. This mostly helps with debugging the server.
if head.UncleHash == ethtypes.EmptyUncleHash && len(body.UncleHashes) > 0 {
return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles")
}
if head.UncleHash != ethtypes.EmptyUncleHash && len(body.UncleHashes) == 0 {
return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles")
}
if head.TxHash == ethtypes.EmptyRootHash && len(body.Transactions) > 0 {
return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions")
}
if head.TxHash != ethtypes.EmptyRootHash && len(body.Transactions) == 0 {
return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions")
}
bbh, err := b.ethHeaderToBlockHeader(head)
btxs := make([]bchain.Tx, len(body.Transactions))
for i, tx := range body.Transactions {
btx, err := ethTxToTx(&tx, int64(head.Time.Uint64()), uint32(bbh.Confirmations))
if err != nil {
return nil, err
}
btxs[i] = *btx
}
bbk := bchain.Block{
BlockHeader: *bbh,
Txs: btxs,
}
return &bbk, nil
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var json *rpcTransaction
err := b.rpc.CallContext(ctx, &json, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
if err != nil {
return nil, err
} else if json == nil {
return nil, ethereum.NotFound
} else if _, r, _ := json.tx.RawSignatureValues(); r == nil {
return nil, fmt.Errorf("server returned transaction without signature")
}
var btx *bchain.Tx
if json.BlockNumber == nil {
// mempool tx
btx, err = ethTxToTx(json, 0, 0)
if err != nil {
return nil, err
}
} else {
// non mempool tx - we must read the block header to get the block time
n, err := strconv.ParseInt((*json.BlockNumber)[2:], 16, 64)
if err != nil {
return nil, err
}
h, err := b.client.HeaderByHash(ctx, json.BlockHash)
if err != nil {
return nil, err
}
confirmations, err := b.computeConfirmations(uint64(n))
if err != nil {
return nil, err
}
btx, err = ethTxToTx(json, h.Time.Int64(), confirmations)
if err != nil {
return nil, err
}
}
return btx, nil
}
func (b *EthRPC) GetMempool() ([]string, error) {
panic("not implemented")
2018-03-15 01:19:02 -06:00
}
func (b *EthRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
panic("not implemented")
}
func (b *EthRPC) SendRawTransaction(tx string) (string, error) {
panic("not implemented")
}
func (b *EthRPC) ResyncMempool(onNewTxAddr func(txid string, addr string)) error {
panic("not implemented")
}
func (b *EthRPC) GetMempoolTransactions(address string) ([]string, error) {
2018-03-15 01:19:02 -06:00
panic("not implemented")
}
func (b *EthRPC) GetMempoolSpentOutput(outputTxid string, vout uint32) string {
panic("not implemented")
}
func (b *EthRPC) GetMempoolEntry(txid string) (*bchain.MempoolEntry, error) {
panic("not implemented")
}
func (b *EthRPC) GetChainParser() bchain.BlockChainParser {
return b.Parser
2018-03-15 01:19:02 -06:00
}