diff --git a/api/worker.go b/api/worker.go index 1a4f5197..a33cbc50 100644 --- a/api/worker.go +++ b/api/worker.go @@ -46,26 +46,26 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript // setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output // there is not an index, it must be found using addresses -> txaddresses -> tx -func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height, bestheight uint32) error { +func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error { err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error { if isOutput == false { tsp, err := w.db.GetTxAddresses(t) if err != nil { + return err + } else if tsp == nil { glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses") - } else { - if len(tsp.Inputs) > int(index) { - if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 { - spentTx, spentHeight, err := w.txCache.GetTransaction(t, bestheight) - if err != nil { - glog.Warning("Tx ", t, ": not found") - } else { - if len(spentTx.Vin) > int(index) { - if spentTx.Vin[index].Txid == txid { - vout.SpentTxID = t - vout.SpentHeight = int(spentHeight) - vout.SpentIndex = int(index) - return &db.StopIteration{} - } + } else if len(tsp.Inputs) > int(index) { + if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 { + spentTx, spentHeight, err := w.txCache.GetTransaction(t) + if err != nil { + glog.Warning("Tx ", t, ": not found") + } else { + if len(spentTx.Vin) > int(index) { + if spentTx.Vin[index].Txid == txid { + vout.SpentTxID = t + vout.SpentHeight = int(spentHeight) + vout.SpentIndex = int(index) + return &db.StopIteration{} } } } @@ -80,18 +80,14 @@ func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height, bestheight // GetSpendingTxid returns transaction id of transaction that spent given output func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { start := time.Now() - bestheight, _, err := w.db.GetBestBlock() - if err != nil { - return "", err - } - tx, err := w.GetTransaction(txid, bestheight, false) + tx, err := w.GetTransaction(txid, false) if err != nil { return "", err } if n >= len(tx.Vout) || n < 0 { return "", NewApiError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false) } - err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight), bestheight) + err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight)) if err != nil { return "", err } @@ -100,9 +96,9 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { } // GetTransaction reads transaction data from txid -func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool) (*Tx, error) { +func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) { start := time.Now() - bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight) + bchainTx, height, err := w.txCache.GetTransaction(txid) if err != nil { return nil, NewApiError(fmt.Sprintf("Tx not found, %v", err), true) } @@ -140,7 +136,7 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses") } // try to load from backend - otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight) + otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) if err != nil { return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) } @@ -181,7 +177,7 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool if ta != nil { vout.Spent = ta.Outputs[i].Spent if spendingTxs && vout.Spent { - err = w.setSpendingTxToVout(vout, bchainTx.Txid, height, bestheight) + err = w.setSpendingTxToVout(vout, bchainTx.Txid, height) if err != nil { glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc, vout.N) } @@ -406,7 +402,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, bestheight, false) + tx, err := w.GetTransaction(tx, false) // mempool transaction may fail if err != nil { glog.Error("GetTransaction in mempool ", tx, ": ", err) diff --git a/blockbook.go b/blockbook.go index 1c4df0b1..769e7ff2 100644 --- a/blockbook.go +++ b/blockbook.go @@ -237,7 +237,7 @@ func main() { return } - if txCache, err = db.NewTxCache(index, chain, metrics, !*noTxCache); err != nil { + if txCache, err = db.NewTxCache(index, chain, metrics, internalState, !*noTxCache); err != nil { glog.Error("txCache ", err) return } diff --git a/db/txcache.go b/db/txcache.go index 8c30b42a..25f17bff 100644 --- a/db/txcache.go +++ b/db/txcache.go @@ -12,11 +12,12 @@ type TxCache struct { db *RocksDB chain bchain.BlockChain metrics *common.Metrics + is *common.InternalState enabled bool } // NewTxCache creates new TxCache interface and returns its handle -func NewTxCache(db *RocksDB, chain bchain.BlockChain, metrics *common.Metrics, enabled bool) (*TxCache, error) { +func NewTxCache(db *RocksDB, chain bchain.BlockChain, metrics *common.Metrics, is *common.InternalState, enabled bool) (*TxCache, error) { if !enabled { glog.Info("txcache: disabled") } @@ -24,13 +25,14 @@ func NewTxCache(db *RocksDB, chain bchain.BlockChain, metrics *common.Metrics, e db: db, chain: chain, metrics: metrics, + is: is, enabled: enabled, }, nil } // GetTransaction returns transaction either from RocksDB or if not present from blockchain // it the transaction is confirmed, it is stored in the RocksDB -func (c *TxCache) GetTransaction(txid string, bestheight uint32) (*bchain.Tx, uint32, error) { +func (c *TxCache) GetTransaction(txid string) (*bchain.Tx, uint32, error) { var tx *bchain.Tx var h uint32 var err error @@ -41,6 +43,7 @@ func (c *TxCache) GetTransaction(txid string, bestheight uint32) (*bchain.Tx, ui } if tx != nil { // number of confirmations is not stored in cache, they change all the time + _, bestheight, _ := c.is.GetSyncState() tx.Confirmations = bestheight - h + 1 c.metrics.TxCacheEfficiency.With(common.Labels{"status": "hit"}).Inc() return tx, h, nil @@ -51,10 +54,21 @@ func (c *TxCache) GetTransaction(txid string, bestheight uint32) (*bchain.Tx, ui return nil, 0, err } c.metrics.TxCacheEfficiency.With(common.Labels{"status": "miss"}).Inc() - // do not cache mempool transactions + // cache only confirmed transactions if tx.Confirmations > 0 { - // the transaction in the currently best block has 1 confirmation - h = bestheight - tx.Confirmations + 1 + ta, err := c.db.GetTxAddresses(txid) + if err != nil { + return nil, 0, err + } + // the transaction may me not yet indexed, in that case get the height from the backend + if ta == nil { + h, err = c.chain.GetBestBlockHeight() + if err != nil { + return nil, 0, err + } + } else { + h = ta.Height + } if c.enabled { err = c.db.PutTx(tx, h, tx.Blocktime) // do not return caching error, only log it diff --git a/server/public.go b/server/public.go index 4a7cb432..79b40912 100644 --- a/server/public.go +++ b/server/public.go @@ -366,13 +366,11 @@ 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 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:] - bestheight, _, err := s.db.GetBestBlock() - if err == nil { - tx, err = s.api.GetTransaction(txid, bestheight, false) - } + tx, err = s.api.GetTransaction(txid, false) if err != nil { return errorTpl, nil, err } @@ -483,7 +481,6 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t var tx *api.Tx var address *api.Address var block *api.Block - var bestheight uint32 var err error s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc() if len(q) > 0 { @@ -492,13 +489,10 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t http.Redirect(w, r, joinURL("/block/", block.Hash), 302) return noTpl, nil, nil } - bestheight, _, err = s.db.GetBestBlock() + tx, err = s.api.GetTransaction(q, false) if err == nil { - tx, err = s.api.GetTransaction(q, bestheight, false) - if err == nil { - http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) - return noTpl, nil, nil - } + http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) + return noTpl, nil, nil } address, err = s.api.GetAddress(q, 0, 1, true) if err == nil { @@ -600,10 +594,7 @@ func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) { s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx"}).Inc() if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 { txid := r.URL.Path[i+1:] - bestheight, _, err := s.db.GetBestBlock() - if err == nil { - tx, err = s.api.GetTransaction(txid, bestheight, true) - } + tx, err = s.api.GetTransaction(txid, true) } return tx, err } diff --git a/server/socketio.go b/server/socketio.go index 2d610157..13676a53 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -377,10 +377,6 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r if err != nil { return } - bestheight, _, err := s.db.GetBestBlock() - if err != nil { - return - } txids := txr.Result res.Result.TotalCount = len(txids) res.Result.Items = make([]addressHistoryItem, 0) @@ -389,7 +385,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], bestheight, false) + tx, err := s.api.GetTransaction(txids[txi], false) // for i, txid := range txids { // if i >= opts.From && i < opts.To { // tx, err := s.api.GetTransaction(txid, bestheight, false) @@ -632,11 +628,7 @@ type resultGetDetailedTransaction struct { } func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetailedTransaction, err error) { - bestheight, _, err := s.db.GetBestBlock() - if err != nil { - return - } - tx, err := s.api.GetTransaction(txid, bestheight, false) + tx, err := s.api.GetTransaction(txid, false) if err != nil { return res, err } diff --git a/tests/sync/connectblocks.go b/tests/sync/connectblocks.go index 1c481ee3..41c9f0e9 100644 --- a/tests/sync/connectblocks.go +++ b/tests/sync/connectblocks.go @@ -176,6 +176,10 @@ func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) { if err != nil { t.Fatal(err) } + if ta == nil { + t.Errorf("Tx %s: not found in TxAddresses", tx.Txid) + continue + } txInfo := getTxInfo(tx) taInfo, err := getTaInfo(parser, ta) diff --git a/tests/sync/handlefork.go b/tests/sync/handlefork.go index d9ff3358..f51d03a2 100644 --- a/tests/sync/handlefork.go +++ b/tests/sync/handlefork.go @@ -80,6 +80,10 @@ func verifyAddresses2(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks if err != nil { t.Fatal(err) } + if ta == nil { + t.Errorf("Tx %s: not found in TxAddresses", tx.Txid) + continue + } txInfo := getTxInfo(&tx) taInfo, err := getTaInfo(parser, ta)