Implement eth GetBlock and GetTransaction
parent
cfdbeb8a9b
commit
40198ae437
|
@ -4,16 +4,20 @@ import (
|
|||
"blockbook/bchain"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
ethcommon "github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type EthereumNet uint32
|
||||
|
@ -26,6 +30,7 @@ const (
|
|||
// 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
|
||||
|
@ -49,12 +54,15 @@ func NewEthRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage)) (bch
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Invalid configuration file")
|
||||
}
|
||||
ec, err := ethclient.Dial(c.RPCURL)
|
||||
rc, err := rpc.Dial(c.RPCURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ec := ethclient.NewClient(rc)
|
||||
|
||||
s := &EthRPC{
|
||||
client: ec,
|
||||
rpc: rc,
|
||||
rpcURL: c.RPCURL,
|
||||
}
|
||||
|
||||
|
@ -154,16 +162,15 @@ func (b *EthRPC) GetBlockHash(height uint32) (string, error) {
|
|||
}
|
||||
|
||||
func (b *EthRPC) ethHeaderToBlockHeader(h *ethtypes.Header) (*bchain.BlockHeader, error) {
|
||||
bh, err := b.getBestHeader()
|
||||
hn := h.Number.Uint64()
|
||||
c, err := b.computeConfirmations(hn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hn := uint32(h.Number.Uint64())
|
||||
bn := uint32(bh.Number.Uint64())
|
||||
return &bchain.BlockHeader{
|
||||
Hash: ethHashToHash(h.Hash()),
|
||||
Height: hn,
|
||||
Confirmations: int(bn - hn),
|
||||
Height: uint32(hn),
|
||||
Confirmations: int(c),
|
||||
// Next
|
||||
// Prev
|
||||
|
||||
|
@ -180,27 +187,115 @@ func (b *EthRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
|||
return b.ethHeaderToBlockHeader(h)
|
||||
}
|
||||
|
||||
func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
bk, err := b.client.BlockByHash(ctx, ethcommon.HexToHash(hash))
|
||||
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
|
||||
}
|
||||
// TODO maybe not the most optimal way to get the header
|
||||
bbh, err := b.ethHeaderToBlockHeader(bk.Header())
|
||||
txs := bk.Transactions()
|
||||
btxs := make([]bchain.Tx, len(txs))
|
||||
for i, tx := range txs {
|
||||
btxs[i] = bchain.Tx{
|
||||
// Blocktime
|
||||
Confirmations: uint32(bbh.Confirmations),
|
||||
// Hex
|
||||
// LockTime
|
||||
// Time
|
||||
Txid: ethHashToHash(tx.Hash()),
|
||||
// Vin
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -209,32 +304,49 @@ func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
|||
return &bbk, nil
|
||||
}
|
||||
|
||||
func (b *EthRPC) GetMempool() ([]string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
||||
// bh, err := b.getBestHeader()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
tx, _, err := b.client.TransactionByHash(ctx, ethcommon.StringToHash(txid))
|
||||
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")
|
||||
}
|
||||
btx := bchain.Tx{
|
||||
// Blocktime
|
||||
// Confirmations
|
||||
// Hex
|
||||
// LockTime
|
||||
// Time
|
||||
Txid: ethHashToHash(tx.Hash()),
|
||||
// Vin
|
||||
// Vout
|
||||
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
|
||||
return btx, nil
|
||||
}
|
||||
|
||||
func (b *EthRPC) GetMempool() ([]string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (b *EthRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {
|
||||
|
|
|
@ -13,6 +13,7 @@ type Vin struct {
|
|||
Vout uint32 `json:"vout"`
|
||||
ScriptSig ScriptSig `json:"scriptSig"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
}
|
||||
|
||||
type ScriptPubKey struct {
|
||||
|
|
Loading…
Reference in New Issue