Handle coin specific tx data more efficiently

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

View File

@ -5,6 +5,7 @@ import (
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"strconv" "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 // GetSpendingTxid returns transaction id of transaction that spent given output
func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
start := time.Now() start := time.Now()
tx, err := w.GetTransaction(txid, false) tx, err := w.GetTransaction(txid, false, false)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -98,7 +99,7 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
} }
// GetTransaction reads transaction data from txid // 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() start := time.Now()
bchainTx, height, err := w.txCache.GetTransaction(txid) bchainTx, height, err := w.txCache.GetTransaction(txid)
if err != nil { 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 // for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2 // 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{ r := &Tx{
Blockhash: blockhash, Blockhash: blockhash,
Blockheight: int(height), Blockheight: int(height),
Blocktime: bchainTx.Blocktime, Blocktime: bchainTx.Blocktime,
Confirmations: bchainTx.Confirmations, Confirmations: bchainTx.Confirmations,
Fees: w.chainParser.AmountToDecimalString(&feesSat), Fees: w.chainParser.AmountToDecimalString(&feesSat),
FeesSat: feesSat, FeesSat: feesSat,
Locktime: bchainTx.LockTime, Locktime: bchainTx.LockTime,
Time: bchainTx.Time, Time: bchainTx.Time,
Txid: bchainTx.Txid, Txid: bchainTx.Txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat), ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
ValueInSat: valInSat, ValueInSat: valInSat,
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
ValueOutSat: valOutSat, ValueOutSat: valOutSat,
Version: bchainTx.Version, Version: bchainTx.Version,
Hex: bchainTx.Hex, Hex: bchainTx.Hex,
Vin: vins, Vin: vins,
Vout: vouts, Vout: vouts,
CoinSpecificData: sd,
} }
if spendingTxs { if spendingTxs {
glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) 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 // load mempool transactions
var uBalSat big.Int var uBalSat big.Int
for _, tx := range txm { for _, tx := range txm {
tx, err := w.GetTransaction(tx, false) tx, err := w.GetTransaction(tx, false, false)
// mempool transaction may fail // mempool transaction may fail
if err != nil { if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err) 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) 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()) 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) { 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 // GetTransaction returns a transaction by the transaction ID
func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) { func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
r, err := b.GetTransactionSpecific(txid) r, err := b.getRawTransaction(txid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tx, err := b.Parser.ParseTxFromJson(r) tx, err := b.Parser.ParseTxFromJson(r)
tx.CoinSpecificData = r
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid) 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 // 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) glog.V(1).Info("rpc: getrawtransaction ", txid)
res := ResGetRawTransaction{} res := ResGetRawTransaction{}

View File

@ -79,7 +79,7 @@ func ethNumber(n string) (int64, error) {
return 0, errors.Errorf("Not a number: '%v'", n) 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) txid := ethHashToHash(tx.Hash)
var ( var (
fa, ta []string fa, ta []string
@ -91,22 +91,24 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc
if len(tx.To) > 2 { if len(tx.To) > 2 {
ta = []string{tx.To} ta = []string{tx.To}
} }
// completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
bh := tx.BlockHash
tx.BlockHash = nil
ct := completeTransaction{ ct := completeTransaction{
Tx: tx, Tx: tx,
Receipt: receipt, Receipt: receipt,
} }
b, err := json.Marshal(ct) var h string
if err != nil { if marshallHex {
return nil, err // completeTransaction without BlockHash is marshalled and hex encoded to bchain.Tx.Hex
} bh := tx.BlockHash
tx.BlockHash = bh tx.BlockHash = nil
h := hex.EncodeToString(b) b, err := json.Marshal(ct)
if receipt != nil { if err != nil {
glog.Info(tx.Hash.Hex(), ": ", h) 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) vs, err := hexutil.DecodeBig(tx.Value)
if err != nil { if err != nil {
@ -139,6 +141,7 @@ func (p *EthereumParser) ethTxToTx(tx *rpcTransaction, receipt *rpcReceipt, bloc
}, },
}, },
}, },
CoinSpecificData: ct,
}, nil }, nil
} }
@ -330,7 +333,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
Logs: logs, 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 { if err != nil {
return nil, 0, err 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) t.Errorf("EthereumParser.UnpackTx() error = %v, wantErr %v", err, tt.wantErr)
return return
} }
// CoinSpecificData are not set in want struct
got.CoinSpecificData = nil
// DeepEqual compares empty nil slices as not equal // DeepEqual compares empty nil slices as not equal
if fmt.Sprint(got) != fmt.Sprint(tt.want) { if fmt.Sprint(got) != fmt.Sprint(tt.want) {
t.Errorf("EthereumParser.UnpackTx() got = %+v, want %+v", got, 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 // TODO - get ERC20 events
btxs := make([]bchain.Tx, len(body.Transactions)) btxs := make([]bchain.Tx, len(body.Transactions))
for i, tx := range 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 { if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v, txid %v", hash, height, tx.Hash.String()) 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 var btx *bchain.Tx
if tx.BlockNumber == "" { if tx.BlockNumber == "" {
// mempool tx // mempool tx
btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0) btx, err = b.Parser.ethTxToTx(tx, nil, 0, 0, true)
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid) return nil, errors.Annotatef(err, "txid %v", txid)
} }
@ -516,7 +516,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid) 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 { if err != nil {
return nil, errors.Annotatef(err, "txid %v", txid) 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 // GetTransactionSpecific returns json as returned by backend, with all coin specific data
func (b *EthereumRPC) GetTransactionSpecific(txid string) (json.RawMessage, error) { func (b *EthereumRPC) GetTransactionSpecific(tx *bchain.Tx) (json.RawMessage, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout) csd, ok := tx.CoinSpecificData.(completeTransaction)
defer cancel() if !ok {
var tx json.RawMessage ntx, err := b.GetTransaction(tx.Txid)
err := b.rpc.CallContext(ctx, &tx, "eth_getTransactionByHash", ethcommon.HexToHash(txid)) if err != nil {
if err != nil { return nil, err
return nil, err }
} else if tx == nil { csd, ok = ntx.CoinSpecificData.(completeTransaction)
return nil, ethereum.NotFound 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 { type rpcMempoolBlock struct {

View File

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

View File

@ -336,7 +336,6 @@ type TemplateData struct {
Address *api.Address Address *api.Address
AddrStr string AddrStr string
Tx *api.Tx Tx *api.Tx
TxSpecific json.RawMessage
Error *api.APIError Error *api.APIError
Blocks *api.Blocks Blocks *api.Blocks
Block *api.Block 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) { func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var tx *api.Tx var tx *api.Tx
var txSpecific json.RawMessage
var err error var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc() s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
txid := r.URL.Path[i+1:] txid := r.URL.Path[i+1:]
tx, err = s.api.GetTransaction(txid, false) tx, err = s.api.GetTransaction(txid, false, true)
if err != nil {
return errorTpl, nil, err
}
txSpecific, err = s.chain.GetTransactionSpecific(txid)
if err != nil { if err != nil {
return errorTpl, nil, err return errorTpl, nil, err
} }
} }
data := s.newTemplateData() data := s.newTemplateData()
data.Tx = tx data.Tx = tx
data.TxSpecific = txSpecific
return txTpl, data, nil 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) http.Redirect(w, r, joinURL("/block/", block.Hash), 302)
return noTpl, nil, nil return noTpl, nil, nil
} }
tx, err = s.api.GetTransaction(q, false) tx, err = s.api.GetTransaction(q, false, false)
if err == nil { if err == nil {
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil 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) 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 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() s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx-specific"}).Inc()
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
txid := r.URL.Path[i+1:] txid := r.URL.Path[i+1:]
tx, err = s.chain.GetTransactionSpecific(txid) tx, err = s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
} }
return tx, err return tx, err
} }

View File

@ -387,7 +387,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r
to = opts.To to = opts.To
} }
for txi := opts.From; txi < to; txi++ { 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 { if err != nil {
return res, err return res, err
} }
@ -627,7 +627,7 @@ type resultGetDetailedTransaction struct {
} }
func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetailedTransaction, err error) { 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 { if err != nil {
return res, err 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> <h1>Transaction</h1>
<div class="alert alert-data ellipsis"> <div class="alert alert-data ellipsis">
<span class="data">{{$tx.Txid}}</span> <span class="data">{{$tx.Txid}}</span>
@ -47,7 +47,7 @@
<pre id="txSpecific"></pre> <pre id="txSpecific"></pre>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
txSpecific = {{ $txSpecific }}; txSpecific = {{$tx.CoinSpecificData}};
function syntaxHighlight(json) { function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 2); json = JSON.stringify(json, undefined, 2);
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); 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") return nil, errors.New("Not found")
} }
func (c *fakeBlockChain) GetTransactionSpecific(txid string) (v json.RawMessage, err error) { func (c *fakeBlockChain) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) {
tx, err := c.GetTransaction(txid) tx, err = c.GetTransaction(tx.Txid)
if err != nil { if err != nil {
return nil, err return nil, err
} }