Handle coin specific tx data more efficiently

ethereum
Martin Boehm 2018-11-13 10:31:27 +01:00
parent 975c98b5b7
commit 6072aa5e9e
12 changed files with 111 additions and 90 deletions

View File

@ -70,24 +70,25 @@ type Vout struct {
// Tx holds information about a transaction
type Tx struct {
Txid string `json:"txid"`
Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"`
Fees string `json:"fees"`
FeesSat big.Int `json:"-"`
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"`
Fees string `json:"fees"`
FeesSat big.Int `json:"-"`
Hex string `json:"hex"`
CoinSpecificData interface{} `json:"-"`
}
// Paging contains information about paging for address, blocks and block

View File

@ -5,6 +5,7 @@ import (
"blockbook/common"
"blockbook/db"
"bytes"
"encoding/json"
"fmt"
"math/big"
"strconv"
@ -82,7 +83,7 @@ func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) err
// GetSpendingTxid returns transaction id of transaction that spent given output
func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
start := time.Now()
tx, err := w.GetTransaction(txid, false)
tx, err := w.GetTransaction(txid, false, false)
if err != nil {
return "", err
}
@ -98,7 +99,7 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
}
// GetTransaction reads transaction data from txid
func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) {
func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificData bool) (*Tx, error) {
start := time.Now()
bchainTx, height, err := w.txCache.GetTransaction(txid)
if err != nil {
@ -210,24 +211,32 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) {
}
// for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2
var sd json.RawMessage
if specificData {
sd, err = w.chain.GetTransactionSpecific(bchainTx)
if err != nil {
return nil, err
}
}
r := &Tx{
Blockhash: blockhash,
Blockheight: int(height),
Blocktime: bchainTx.Blocktime,
Confirmations: bchainTx.Confirmations,
Fees: w.chainParser.AmountToDecimalString(&feesSat),
FeesSat: feesSat,
Locktime: bchainTx.LockTime,
Time: bchainTx.Time,
Txid: bchainTx.Txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
ValueInSat: valInSat,
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
ValueOutSat: valOutSat,
Version: bchainTx.Version,
Hex: bchainTx.Hex,
Vin: vins,
Vout: vouts,
Blockhash: blockhash,
Blockheight: int(height),
Blocktime: bchainTx.Blocktime,
Confirmations: bchainTx.Confirmations,
Fees: w.chainParser.AmountToDecimalString(&feesSat),
FeesSat: feesSat,
Locktime: bchainTx.LockTime,
Time: bchainTx.Time,
Txid: bchainTx.Txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
ValueInSat: valInSat,
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
ValueOutSat: valOutSat,
Version: bchainTx.Version,
Hex: bchainTx.Hex,
Vin: vins,
Vout: vouts,
CoinSpecificData: sd,
}
if spendingTxs {
glog.Info("GetTransaction ", txid, " finished in ", time.Since(start))
@ -426,7 +435,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
// load mempool transactions
var uBalSat big.Int
for _, tx := range txm {
tx, err := w.GetTransaction(tx, false)
tx, err := w.GetTransaction(tx, false, false)
// mempool transaction may fail
if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err)

View File

@ -187,9 +187,9 @@ func (c *blockChainWithMetrics) GetTransaction(txid string) (v *bchain.Tx, err e
return c.b.GetTransaction(txid)
}
func (c *blockChainWithMetrics) GetTransactionSpecific(txid string) (v json.RawMessage, err error) {
func (c *blockChainWithMetrics) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetTransactionSpecific", s, err) }(time.Now())
return c.b.GetTransactionSpecific(txid)
return c.b.GetTransactionSpecific(tx)
}
func (c *blockChainWithMetrics) GetTransactionForMempool(txid string) (v *bchain.Tx, err error) {

View File

@ -670,11 +670,12 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
// GetTransaction returns a transaction by the transaction ID
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := b.GetTransactionSpecific(txid)
r, err := b.getRawTransaction(txid)
if err != nil {
return nil, err
}
tx, err := b.Parser.ParseTxFromJson(r)
tx.CoinSpecificData = r
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -682,7 +683,15 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
}
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
func (b *BitcoinRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
func (b *BitcoinRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
if csd, ok := tx.CoinSpecificData.(json.RawMessage); ok {
return csd, nil
}
return b.getRawTransaction(tx.Txid)
}
// getRawTransaction returns json as returned by backend, with all coin specific data
func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) {
glog.V(1).Info("rpc: getrawtransaction ", txid)
res := ResGetRawTransaction{}

View File

@ -79,7 +79,7 @@ func ethNumber(n string) (int64, error) {
return 0, errors.Errorf("Not a number: '%v'", n)
}
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32) (*bchain.Tx, error) {
func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, blocktime int64, confirmations uint32, marshallHex bool) (*bchain.Tx, error) {
txid := ethHashToHash(tx.Hash)
var (
fa, ta []string
@ -91,22 +91,24 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc
if len(tx.To) > 2 {
ta = []string{tx.To}
}
// completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
bh := tx.BlockHash
tx.BlockHash = nil
ct := completeTransaction{
Tx: tx,
Receipt: receipt,
}
b, err := json.Marshal(ct)
if err != nil {
return nil, err
}
tx.BlockHash = bh
h := hex.EncodeToString(b)
if receipt != nil {
glog.Info(tx.Hash.Hex(), ": ", h)
var h string
if marshallHex {
// completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
bh := tx.BlockHash
tx.BlockHash = nil
b, err := json.Marshal(ct)
if err != nil {
return nil, err
}
tx.BlockHash = bh
h = hex.EncodeToString(b)
if receipt != nil {
glog.Info(tx.Hash.Hex(), ": ", h)
}
}
vs, err := hexutil.DecodeBig(tx.Value)
if err != nil {
@ -139,6 +141,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc
},
},
},
CoinSpecificData: ct,
}, nil
}
@ -330,7 +333,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
Logs: logs,
}
}
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0)
tx, err := p.ethTxToTx(&rt, rr, int64(pt.BlockTime), 0, true)
if err != nil {
return nil, 0, err
}

View File

@ -199,6 +199,8 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr)
return
}
// CoinSpecificData are not set in want struct
got.CoinSpecificData = nil
// DeepEqual compares empty nil slices as not equal
if fmt.Sprint(got) != fmt.Sprint(tt.want) {
t.Errorf("EthereumParser.UnpackTx() got = %+v, want %+v", got, tt.want)

View File

@ -447,7 +447,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
// TODO - get ERC20 events
btxs := make([]bchain.Tx, len(body.Transactions))
for i, tx := range body.Transactions {
btx, err := b.Parser.ethTxToTx(&tx, nil, int64(head.Time.Uint64()), uint32(bbh.Confirmations))
btx, err := b.Parser.ethTxToTx(&tx, nil, int64(head.Time.Uint64()), uint32(bbh.Confirmations), false)
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String())
}
@ -493,7 +493,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
var btx *bchain.Tx
if tx.BlockNumber == "" {
// mempool tx
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0)
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -516,7 +516,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
btx, err = b.Parser.ethTxToTx(tx, &receipt, h.Time.Int64(), confirmations)
btx, err = b.Parser.ethTxToTx(tx, &receipt, h.Time.Int64(), confirmations, true)
if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid)
}
@ -525,17 +525,20 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
}
// GetTransactionSpecific returns json as returned by backend, with all coin specific data
func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var tx json.RawMessage
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid))
if err != nil {
return nil, err
} else if tx == nil {
return nil, ethereum.NotFound
func (b *EthereumRPC) 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")
}
}
return tx, nil
m, err := json.Marshal(&csd)
return json.RawMessage(m), err
}
type rpcMempoolBlock struct {

View File

@ -75,9 +75,10 @@ type Tx struct {
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
// BlockHash string `json:"blockhash,omitempty"`
Confirmations uint32 `json:"confirmations,omitempty"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime,omitempty"`
Confirmations uint32 `json:"confirmations,omitempty"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime,omitempty"`
CoinSpecificData interface{} `json:"-"`
}
// Block is block header and list of transactions
@ -185,7 +186,7 @@ type BlockChain interface {
GetMempool() ([]string, error)
GetTransaction(txid string) (*Tx, error)
GetTransactionForMempool(txid string) (*Tx, error)
GetTransactionSpecific(txid string) (json.RawMessage, error)
GetTransactionSpecific(tx *Tx) (json.RawMessage, error)
EstimateSmartFee(blocks int, conservative bool) (big.Int, error)
EstimateFee(blocks int) (big.Int, error)
SendRawTransaction(tx string) (string, error)

View File

@ -336,7 +336,6 @@ type TemplateData struct {
Address *api.Address
AddrStr string
Tx *api.Tx
TxSpecific json.RawMessage
Error *api.APIError
Blocks *api.Blocks
Block *api.Block
@ -392,23 +391,17 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var tx *api.Tx
var txSpecific json.RawMessage
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
txid := r.URL.Path[i+1:]
tx, err = s.api.GetTransaction(txid, false)
if err != nil {
return errorTpl, nil, err
}
txSpecific, err = s.chain.GetTransactionSpecific(txid)
tx, err = s.api.GetTransaction(txid, false, true)
if err != nil {
return errorTpl, nil, err
}
}
data := s.newTemplateData()
data.Tx = tx
data.TxSpecific = txSpecific
return txTpl, data, nil
}
@ -521,7 +514,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
http.Redirect(w, r, joinURL("/block/", block.Hash), 302)
return noTpl, nil, nil
}
tx, err = s.api.GetTransaction(q, false)
tx, err = s.api.GetTransaction(q, false, false)
if err == nil {
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil
@ -656,7 +649,7 @@ func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
return nil, api.NewAPIError("Parameter 'spending' cannot be converted to boolean", true)
}
}
tx, err = s.api.GetTransaction(txid, spendingTxs)
tx, err = s.api.GetTransaction(txid, spendingTxs, false)
}
return tx, err
}
@ -667,7 +660,7 @@ func (s *PublicServer) apiTxSpecific(r *http.Request) (interface{}, error) {
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx-specific"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
txid := r.URL.Path[i+1:]
tx, err = s.chain.GetTransactionSpecific(txid)
tx, err = s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
}
return tx, err
}

View File

@ -387,7 +387,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r
to = opts.To
}
for txi := opts.From; txi < to; txi++ {
tx, err := s.api.GetTransaction(txids[txi], false)
tx, err := s.api.GetTransaction(txids[txi], false, false)
if err != nil {
return res, err
}
@ -627,7 +627,7 @@ type resultGetDetailedTransaction struct {
}
func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetailedTransaction, err error) {
tx, err := s.api.GetTransaction(txid, false)
tx, err := s.api.GetTransaction(txid, false, false)
if err != nil {
return res, err
}

View File

@ -1,4 +1,4 @@
{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}{{$txSpecific := .TxSpecific}}
{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}
<h1>Transaction</h1>
<div class="alert alert-data ellipsis">
<span class="data">{{$tx.Txid}}</span>
@ -47,7 +47,7 @@
<pre id="txSpecific"></pre>
</div>
<script type="text/javascript">
txSpecific = {{ $txSpecific }};
txSpecific = {{$tx.CoinSpecificData}};
function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 2);
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

View File

@ -141,8 +141,8 @@ func (c *fakeBlockChain) GetTransaction(txid string) (v *bchain.Tx, err error) {
return nil, errors.New("Not found")
}
func (c *fakeBlockChain) GetTransactionSpecific(txid string) (v json.RawMessage, err error) {
tx, err := c.GetTransaction(txid)
func (c *fakeBlockChain) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) {
tx, err = c.GetTransaction(tx.Txid)
if err != nil {
return nil, err
}