Implement eth GetBlock and GetTransaction

indexv1
Martin Boehm 2018-03-22 15:56:21 +01:00
parent cfdbeb8a9b
commit 40198ae437
2 changed files with 155 additions and 42 deletions

View File

@ -4,16 +4,20 @@ import (
"blockbook/bchain" "blockbook/bchain"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"math/big" "math/big"
"strconv"
"sync" "sync"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/juju/errors" "github.com/juju/errors"
ethereum "github.com/ethereum/go-ethereum"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
) )
type EthereumNet uint32 type EthereumNet uint32
@ -26,6 +30,7 @@ const (
// EthRPC is an interface to JSON-RPC eth service. // EthRPC is an interface to JSON-RPC eth service.
type EthRPC struct { type EthRPC struct {
client *ethclient.Client client *ethclient.Client
rpc *rpc.Client
timeout time.Duration timeout time.Duration
rpcURL string rpcURL string
Parser *EthParser Parser *EthParser
@ -49,12 +54,15 @@ func NewEthRPC(config json.RawMessage, pushHandler func(*bchain.MQMessage)) (bch
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "Invalid configuration file") return nil, errors.Annotatef(err, "Invalid configuration file")
} }
ec, err := ethclient.Dial(c.RPCURL) rc, err := rpc.Dial(c.RPCURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ec := ethclient.NewClient(rc)
s := &EthRPC{ s := &EthRPC{
client: ec, client: ec,
rpc: rc,
rpcURL: c.RPCURL, 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) { 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 { if err != nil {
return nil, err return nil, err
} }
hn := uint32(h.Number.Uint64())
bn := uint32(bh.Number.Uint64())
return &bchain.BlockHeader{ return &bchain.BlockHeader{
Hash: ethHashToHash(h.Hash()), Hash: ethHashToHash(h.Hash()),
Height: hn, Height: uint32(hn),
Confirmations: int(bn - hn), Confirmations: int(c),
// Next // Next
// Prev // Prev
@ -180,27 +187,115 @@ func (b *EthRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
return b.ethHeaderToBlockHeader(h) return b.ethHeaderToBlockHeader(h)
} }
func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) { func (b *EthRPC) computeConfirmations(n uint64) (uint32, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout) bh, err := b.getBestHeader()
defer cancel() if err != nil {
bk, err := b.client.BlockByHash(ctx, ethcommon.HexToHash(hash)) 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 { if err != nil {
return nil, err return nil, err
} }
// TODO maybe not the most optimal way to get the header var from, to string
bbh, err := b.ethHeaderToBlockHeader(bk.Header()) ethTo := rtx.tx.To()
txs := bk.Transactions() if ethTo != nil {
btxs := make([]bchain.Tx, len(txs)) to = ethTo.Hex()[2:]
for i, tx := range txs { }
btxs[i] = bchain.Tx{ from = rtx.From.Hex()[2:]
// Blocktime return &bchain.Tx{
Confirmations: uint32(bbh.Confirmations), Blocktime: blocktime,
// Hex Confirmations: confirmations,
// LockTime // Hex
// Time // LockTime
Txid: ethHashToHash(tx.Hash()), Time: blocktime,
// Vin 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{ bbk := bchain.Block{
BlockHeader: *bbh, BlockHeader: *bbh,
@ -209,32 +304,49 @@ func (b *EthRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
return &bbk, nil return &bbk, nil
} }
func (b *EthRPC) GetMempool() ([]string, error) {
panic("not implemented")
}
func (b *EthRPC) GetTransaction(txid string) (*bchain.Tx, error) { 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) ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel() 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 { if err != nil {
return nil, err 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{ var btx *bchain.Tx
// Blocktime if json.BlockNumber == nil {
// Confirmations // mempool tx
// Hex btx, err = ethTxToTx(json, 0, 0)
// LockTime if err != nil {
// Time return nil, err
Txid: ethHashToHash(tx.Hash()), }
// Vin } else {
// Vout // 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) { func (b *EthRPC) EstimateSmartFee(blocks int, conservative bool) (float64, error) {

View File

@ -13,6 +13,7 @@ type Vin struct {
Vout uint32 `json:"vout"` Vout uint32 `json:"vout"`
ScriptSig ScriptSig `json:"scriptSig"` ScriptSig ScriptSig `json:"scriptSig"`
Sequence uint32 `json:"sequence"` Sequence uint32 `json:"sequence"`
Addresses []string `json:"addresses,omitempty"`
} }
type ScriptPubKey struct { type ScriptPubKey struct {