From 3ca593aff1bf756152fcb050ec35964149fc4037 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 10 Jan 2019 16:39:36 +0100 Subject: [PATCH] Handle error tx not found #94 --- api/worker.go | 10 +++++++++- bchain/baseparser.go | 4 ++-- bchain/coins/btc/bitcoinparser.go | 1 + bchain/coins/btc/bitcoinrpc.go | 13 +++++++++++++ bchain/coins/eth/ethrpc.go | 4 ++-- bchain/coins/liquid/liquidparser.go | 4 ++-- bchain/types.go | 4 +++- db/rocksdb.go | 2 +- server/public_test.go | 6 +++--- tests/dbtestdata/fakechain.go | 2 +- tests/rpc/rpc.go | 18 ++---------------- 11 files changed, 39 insertions(+), 29 deletions(-) diff --git a/api/worker.go b/api/worker.go index a7683230..c98aea4d 100644 --- a/api/worker.go +++ b/api/worker.go @@ -107,7 +107,10 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) { func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) { bchainTx, height, err := w.txCache.GetTransaction(txid) if err != nil { - return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true) + if err == bchain.ErrTxNotFound { + return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found", txid), true) + } + return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found (%v)", txid, err), true) } return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON) } @@ -158,6 +161,11 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32, // try to load from backend otx, _, err := w.txCache.GetTransaction(bchainVin.Txid) if err != nil { + if err == bchain.ErrTxNotFound { + // try to get AddrDesc using coin specific handling and continue processing the tx + vin.AddrDesc = w.chainParser.GetAddrDescForUnknownInput(bchainTx, i) + continue + } return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid) } if len(otx.Vout) > int(vin.Vout) { diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 10cd7096..d18da3b4 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -28,12 +28,12 @@ func (p *BaseParser) ParseTx(b []byte) (*Tx, error) { } // GetAddrDescForUnknownInput returns nil AddressDescriptor -func (p *BaseParser) GetAddrDescForUnknownInput(block *Block, tx *Tx, input int) AddressDescriptor { +func (p *BaseParser) GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescriptor { var iTxid string if len(tx.Vin) > input { iTxid = tx.Vin[input].Txid } - glog.Warningf("height %d, tx %v, input tx %v not found in txAddresses", block.Height, tx.Txid, iTxid) + glog.Warningf("tx %v, input tx %v not found in txAddresses", tx.Txid, iTxid) return nil } diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go index aaa04328..f3731e0a 100644 --- a/bchain/coins/btc/bitcoinparser.go +++ b/bchain/coins/btc/bitcoinparser.go @@ -154,6 +154,7 @@ func (p *BitcoinParser) outputScriptToAddresses(script []byte) ([]string, bool, return rv, s, nil } +// TxFromMsgTx converts bitcoin wire Tx to bchain.Tx func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx { vin := make([]bchain.Vin, len(t.TxIn)) for i, in := range t.TxIn { diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index 3a4e46ba..a714ebe2 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -633,6 +633,13 @@ func (b *BitcoinRPC) GetMempool() ([]string, error) { return res.Result, nil } +func isMissingTx(err *bchain.RPCError) bool { + if err.Code == -5 { // "No such mempool or blockchain transaction" + return true + } + return false +} + // GetTransactionForMempool returns a transaction by the transaction ID // It could be optimized for mempool, i.e. without block time and confirmations func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { @@ -647,6 +654,9 @@ func (b *BitcoinRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { return nil, errors.Annotatef(err, "txid %v", txid) } if res.Error != nil { + if isMissingTx(res.Error) { + return nil, bchain.ErrTxNotFound + } return nil, errors.Annotatef(res.Error, "txid %v", txid) } data, err := hex.DecodeString(res.Result) @@ -696,6 +706,9 @@ func (b *BitcoinRPC) getRawTransaction(txid string) (json.RawMessage, error) { return nil, errors.Annotatef(err, "txid %v", txid) } if res.Error != nil { + if isMissingTx(res.Error) { + return nil, bchain.ErrTxNotFound + } return nil, errors.Annotatef(res.Error, "txid %v", txid) } return res.Result, nil diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 8aba3598..6f1cd31b 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -520,7 +520,7 @@ func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) { func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) { tx, err := b.GetTransaction(txid) // it there is an error getting the tx or the tx is confirmed, remove it from pending transactions - if err != nil || (tx != nil && tx.Confirmations > 0) { + if err == bchain.ErrTxNotFound || (tx != nil && tx.Confirmations > 0) { b.pendingTransactionsLock.Lock() delete(b.pendingTransactions, txid) b.pendingTransactionsLock.Unlock() @@ -538,7 +538,7 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) { if err != nil { return nil, err } else if tx == nil { - return nil, ethereum.NotFound + return nil, bchain.ErrTxNotFound } var btx *bchain.Tx if tx.BlockNumber == "" { diff --git a/bchain/coins/liquid/liquidparser.go b/bchain/coins/liquid/liquidparser.go index f6758895..926a7c6c 100644 --- a/bchain/coins/liquid/liquidparser.go +++ b/bchain/coins/liquid/liquidparser.go @@ -74,7 +74,7 @@ func (p *LiquidParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { // GetAddrDescForUnknownInput processes inputs that were not found in txAddresses - they are bitcoin transactions // create a special script for the input in the form OP_INVALIDOPCODE -func (p *LiquidParser) GetAddrDescForUnknownInput(block *bchain.Block, tx *bchain.Tx, input int) bchain.AddressDescriptor { +func (p *LiquidParser) GetAddrDescForUnknownInput(tx *bchain.Tx, input int) bchain.AddressDescriptor { var iTxid string s := make([]byte, 0, 40) if len(tx.Vin) > input { @@ -88,7 +88,7 @@ func (p *LiquidParser) GetAddrDescForUnknownInput(block *bchain.Block, tx *bchai s = append(s, buf[:l]...) } } - glog.Info("height ", block.Height, ", tx ", tx.Txid, ", encountered Bitcoin tx ", iTxid) + glog.Info("tx ", tx.Txid, ", encountered Bitcoin tx ", iTxid) return s } diff --git a/bchain/types.go b/bchain/types.go index e65bada3..a643b9dd 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -31,6 +31,8 @@ var ( // ErrTxidMissing is returned if txid is not specified // for example coinbase transactions in Bitcoin ErrTxidMissing = errors.New("Txid missing") + // ErrTxNotFound is returned if transaction was not found + ErrTxNotFound = errors.New("Tx not found") ) // Outpoint is txid together with output (or input) index @@ -256,7 +258,7 @@ type BlockChainParser interface { ParseTxFromJson(json.RawMessage) (*Tx, error) PackTx(tx *Tx, height uint32, blockTime int64) ([]byte, error) UnpackTx(buf []byte) (*Tx, uint32, error) - GetAddrDescForUnknownInput(block *Block, tx *Tx, input int) AddressDescriptor + GetAddrDescForUnknownInput(tx *Tx, input int) AddressDescriptor // blocks PackBlockHash(hash string) ([]byte, error) UnpackBlockHash(buf []byte) (string, error) diff --git a/db/rocksdb.go b/db/rocksdb.go index 94437caa..04fd015b 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -491,7 +491,7 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses add } if ita == nil { // allow parser to process unknown input, some coins may implement special handling, default is to log warning - tai.AddrDesc = d.chainParser.GetAddrDescForUnknownInput(block, tx, i) + tai.AddrDesc = d.chainParser.GetAddrDescForUnknownInput(tx, i) continue } txAddressesMap[stxID] = ita diff --git a/server/public_test.go b/server/public_test.go index de570af2..7bb9f440 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -19,7 +19,7 @@ import ( "github.com/golang/glog" "github.com/martinboehm/btcutil/chaincfg" - "github.com/martinboehm/golang-socketio" + gosocketio "github.com/martinboehm/golang-socketio" "github.com/martinboehm/golang-socketio/transport" ) @@ -388,7 +388,7 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusBadRequest, contentType: "application/json; charset=utf-8", body: []string{ - `{"error":"Tx not found, Not found"}`, + `{"error":"Transaction '1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07' not found"}`, }, }, { @@ -406,7 +406,7 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusBadRequest, contentType: "application/json; charset=utf-8", body: []string{ - `{"error":"Tx not found, Not found"}`, + `{"error":"Transaction '1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07' not found"}`, }, }, { diff --git a/tests/dbtestdata/fakechain.go b/tests/dbtestdata/fakechain.go index ffc0b2ce..549e01f9 100644 --- a/tests/dbtestdata/fakechain.go +++ b/tests/dbtestdata/fakechain.go @@ -138,7 +138,7 @@ func (c *fakeBlockChain) GetTransaction(txid string) (v *bchain.Tx, err error) { if v != nil { return v, nil } - return nil, errors.New("Not found") + return nil, bchain.ErrTxNotFound } func (c *fakeBlockChain) GetTransactionSpecific(tx *bchain.Tx) (v json.RawMessage, err error) { diff --git a/tests/rpc/rpc.go b/tests/rpc/rpc.go index ff430d78..6b1fd345 100644 --- a/tests/rpc/rpc.go +++ b/tests/rpc/rpc.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/deckarep/golang-set" + mapset "github.com/deckarep/golang-set" "github.com/juju/errors" ) @@ -351,7 +351,7 @@ func getTxid2addrs(t *testing.T, h *TestHandler, txs []string) map[string][]stri for i := range txs { tx, err := h.Chain.GetTransactionForMempool(txs[i]) if err != nil { - if isMissingTx(err) { + if err == bchain.ErrTxNotFound { continue } t.Fatal(err) @@ -370,20 +370,6 @@ func getTxid2addrs(t *testing.T, h *TestHandler, txs []string) map[string][]stri return txid2addrs } -func isMissingTx(err error) bool { - switch e1 := err.(type) { - case *errors.Err: - switch e2 := e1.Cause().(type) { - case *bchain.RPCError: - if e2.Code == -5 { // "No such mempool or blockchain transaction" - return true - } - } - } - - return false -} - func intersect(a, b []string) []string { setA := mapset.NewSet() for _, v := range a {