From 91a44c001c6830d67714fc14359b895189672949 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 27 Jun 2019 19:09:44 -0700 Subject: [PATCH] chainntnfs/multi: add interfaced ConfDetailsFromTxIndex Removes the code duplication between bitcoind and btcd, by creating an abstracted version of confDetailsFromTxIndex in the main chainntfns package. --- chainntnfs/bitcoindnotify/bitcoind.go | 97 +----------------------- chainntnfs/btcdnotify/btcd.go | 97 +----------------------- chainntnfs/interface.go | 103 ++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 186 deletions(-) diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index b7e598fb4..77c2579c7 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -1,11 +1,8 @@ package bitcoindnotify import ( - "bytes" - "encoding/hex" "errors" "fmt" - "strings" "sync" "sync/atomic" @@ -458,7 +455,10 @@ func (b *BitcoindNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ // // We'll first attempt to retrieve the transaction using the node's // txindex. - txConf, txStatus, err := b.confDetailsFromTxIndex(&confRequest.TxID) + txNotFoundErr := "No such mempool or blockchain transaction" + txConf, txStatus, err := chainntnfs.ConfDetailsFromTxIndex( + b.chainConn, &confRequest.TxID, txNotFoundErr, + ) // We'll then check the status of the transaction lookup returned to // determine whether we should proceed with any fallback methods. @@ -489,95 +489,6 @@ func (b *BitcoindNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequ return txConf, txStatus, nil } -// confDetailsFromTxIndex looks up whether a transaction is already included in -// a block in the active chain by using the backend node's transaction index. -// If the transaction is found its TxConfStatus is returned. If it was found in -// the mempool this will be TxFoundMempool, if it is found in a block this will -// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found -// in a block its confirmation details are also returned. -func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash, -) (*chainntnfs.TxConfirmation, chainntnfs.TxConfStatus, error) { - - // If the transaction has some or all of its confirmations required, - // then we may be able to dispatch it immediately. - rawTxRes, err := b.chainConn.GetRawTransactionVerbose(txid) - if err != nil { - // If the transaction lookup was successful, but it wasn't found - // within the index itself, then we can exit early. We'll also - // need to look at the error message returned as the error code - // is used for multiple errors. - txNotFoundErr := "No such mempool or blockchain transaction" - jsonErr, ok := err.(*btcjson.RPCError) - if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo && - strings.Contains(jsonErr.Message, txNotFoundErr) { - - return nil, chainntnfs.TxNotFoundIndex, nil - } - - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to query for txid %v: %v", txid, err) - } - - // Make sure we actually retrieved a transaction that is included in a - // block. If not, the transaction must be unconfirmed (in the mempool), - // and we'll return TxFoundMempool together with a nil TxConfirmation. - if rawTxRes.BlockHash == "" { - return nil, chainntnfs.TxFoundMempool, nil - } - - // As we need to fully populate the returned TxConfirmation struct, - // grab the block in which the transaction was confirmed so we can - // locate its exact index within the block. - blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash) - if err != nil { - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to get block hash %v for "+ - "historical dispatch: %v", rawTxRes.BlockHash, err) - } - block, err := b.chainConn.GetBlockVerbose(blockHash) - if err != nil { - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to get block with hash %v for "+ - "historical dispatch: %v", blockHash, err) - } - - // If the block was obtained, locate the transaction's index within the - // block so we can give the subscriber full confirmation details. - txidStr := txid.String() - for txIndex, txHash := range block.Tx { - if txHash != txidStr { - continue - } - - // Deserialize the hex-encoded transaction to include it in the - // confirmation details. - rawTx, err := hex.DecodeString(rawTxRes.Hex) - if err != nil { - return nil, chainntnfs.TxFoundIndex, - fmt.Errorf("unable to deserialize tx %v: %v", - txHash, err) - } - var tx wire.MsgTx - if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil { - return nil, chainntnfs.TxFoundIndex, - fmt.Errorf("unable to deserialize tx %v: %v", - txHash, err) - } - - return &chainntnfs.TxConfirmation{ - Tx: &tx, - BlockHash: blockHash, - BlockHeight: uint32(block.Height), - TxIndex: uint32(txIndex), - }, chainntnfs.TxFoundIndex, nil - } - - // We return an error because we should have found the transaction - // within the block, but didn't. - return nil, chainntnfs.TxNotFoundIndex, fmt.Errorf("unable to locate "+ - "tx %v in block %v", txid, blockHash) -} - // confDetailsManually looks up whether a transaction/output script has already // been included in a block in the active chain by scanning the chain's blocks // within the given range. If the transaction/output script is found, its diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 3de8566df..18857f70f 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -1,11 +1,8 @@ package btcdnotify import ( - "bytes" - "encoding/hex" "errors" "fmt" - "strings" "sync" "sync/atomic" "time" @@ -480,7 +477,10 @@ func (b *BtcdNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest, // // We'll first attempt to retrieve the transaction using the node's // txindex. - txConf, txStatus, err := b.confDetailsFromTxIndex(&confRequest.TxID) + txNotFoundErr := "No information available about transaction" + txConf, txStatus, err := chainntnfs.ConfDetailsFromTxIndex( + b.chainConn, &confRequest.TxID, txNotFoundErr, + ) // We'll then check the status of the transaction lookup returned to // determine whether we should proceed with any fallback methods. @@ -515,95 +515,6 @@ func (b *BtcdNotifier) historicalConfDetails(confRequest chainntnfs.ConfRequest, return txConf, txStatus, nil } -// confDetailsFromTxIndex looks up whether a transaction is already included in -// a block in the active chain by using the backend node's transaction index. -// If the transaction is found its TxConfStatus is returned. If it was found in -// the mempool this will be TxFoundMempool, if it is found in a block this will -// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found -// in a block its confirmation details are also returned. -func (b *BtcdNotifier) confDetailsFromTxIndex(txid *chainhash.Hash, -) (*chainntnfs.TxConfirmation, chainntnfs.TxConfStatus, error) { - - // If the transaction has some or all of its confirmations required, - // then we may be able to dispatch it immediately. - rawTxRes, err := b.chainConn.GetRawTransactionVerbose(txid) - if err != nil { - // If the transaction lookup was successful, but it wasn't found - // within the index itself, then we can exit early. We'll also - // need to look at the error message returned as the error code - // is used for multiple errors. - txNotFoundErr := "No information available about transaction" - jsonErr, ok := err.(*btcjson.RPCError) - if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo && - strings.Contains(jsonErr.Message, txNotFoundErr) { - - return nil, chainntnfs.TxNotFoundIndex, nil - } - - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to query for txid %v: %v", txid, err) - } - - // Make sure we actually retrieved a transaction that is included in a - // block. If not, the transaction must be unconfirmed (in the mempool), - // and we'll return TxFoundMempool together with a nil TxConfirmation. - if rawTxRes.BlockHash == "" { - return nil, chainntnfs.TxFoundMempool, nil - } - - // As we need to fully populate the returned TxConfirmation struct, - // grab the block in which the transaction was confirmed so we can - // locate its exact index within the block. - blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash) - if err != nil { - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to get block hash %v for "+ - "historical dispatch: %v", rawTxRes.BlockHash, err) - } - block, err := b.chainConn.GetBlockVerbose(blockHash) - if err != nil { - return nil, chainntnfs.TxNotFoundIndex, - fmt.Errorf("unable to get block with hash %v for "+ - "historical dispatch: %v", blockHash, err) - } - - // If the block was obtained, locate the transaction's index within the - // block so we can give the subscriber full confirmation details. - txidStr := txid.String() - for txIndex, txHash := range block.Tx { - if txHash != txidStr { - continue - } - - // Deserialize the hex-encoded transaction to include it in the - // confirmation details. - rawTx, err := hex.DecodeString(rawTxRes.Hex) - if err != nil { - return nil, chainntnfs.TxFoundIndex, - fmt.Errorf("unable to deserialize tx %v: %v", - txHash, err) - } - var tx wire.MsgTx - if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil { - return nil, chainntnfs.TxFoundIndex, - fmt.Errorf("unable to deserialize tx %v: %v", - txHash, err) - } - - return &chainntnfs.TxConfirmation{ - Tx: &tx, - BlockHash: blockHash, - BlockHeight: uint32(block.Height), - TxIndex: uint32(txIndex), - }, chainntnfs.TxFoundIndex, nil - } - - // We return an error because we should have found the transaction - // within the block, but didn't. - return nil, chainntnfs.TxNotFoundIndex, fmt.Errorf("unable to locate "+ - "tx %v in block %v", txid, blockHash) -} - // confDetailsManually looks up whether a transaction/output script has already // been included in a block in the active chain by scanning the chain's blocks // within the given range. If the transaction/output script is found, its diff --git a/chainntnfs/interface.go b/chainntnfs/interface.go index 97a621938..9b3c2dfc3 100644 --- a/chainntnfs/interface.go +++ b/chainntnfs/interface.go @@ -1,8 +1,11 @@ package chainntnfs import ( + "bytes" + "encoding/hex" "errors" "fmt" + "strings" "sync" "github.com/btcsuite/btcd/btcjson" @@ -583,3 +586,103 @@ func getMissedBlocks(chainConn ChainConn, startingHeight, return missedBlocks, nil } + +// TxIndexConn abstracts an RPC backend with txindex enabled. +type TxIndexConn interface { + // GetRawTransactionVerbose returns the transaction identified by the + // passed chain hash, and returns additional information such as the + // block that the transaction confirmed. + GetRawTransactionVerbose(*chainhash.Hash) (*btcjson.TxRawResult, error) + + // GetBlockVerbose returns the block identified by the chain hash along + // with additional information such as the block's height in the chain. + GetBlockVerbose(*chainhash.Hash) (*btcjson.GetBlockVerboseResult, error) +} + +// ConfDetailsFromTxIndex looks up whether a transaction is already included in +// a block in the active chain by using the backend node's transaction index. +// If the transaction is found its TxConfStatus is returned. If it was found in +// the mempool this will be TxFoundMempool, if it is found in a block this will +// be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found +// in a block its confirmation details are also returned. +func ConfDetailsFromTxIndex(chainConn TxIndexConn, txid *chainhash.Hash, + txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) { + + // If the transaction has some or all of its confirmations required, + // then we may be able to dispatch it immediately. + rawTxRes, err := chainConn.GetRawTransactionVerbose(txid) + if err != nil { + // If the transaction lookup was successful, but it wasn't found + // within the index itself, then we can exit early. We'll also + // need to look at the error message returned as the error code + // is used for multiple errors. + jsonErr, ok := err.(*btcjson.RPCError) + if ok && jsonErr.Code == btcjson.ErrRPCNoTxInfo && + strings.Contains(jsonErr.Message, txNotFoundErr) { + + return nil, TxNotFoundIndex, nil + } + + return nil, TxNotFoundIndex, + fmt.Errorf("unable to query for txid %v: %v", txid, err) + } + + // Make sure we actually retrieved a transaction that is included in a + // block. If not, the transaction must be unconfirmed (in the mempool), + // and we'll return TxFoundMempool together with a nil TxConfirmation. + if rawTxRes.BlockHash == "" { + return nil, TxFoundMempool, nil + } + + // As we need to fully populate the returned TxConfirmation struct, + // grab the block in which the transaction was confirmed so we can + // locate its exact index within the block. + blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash) + if err != nil { + return nil, TxNotFoundIndex, + fmt.Errorf("unable to get block hash %v for "+ + "historical dispatch: %v", rawTxRes.BlockHash, err) + } + block, err := chainConn.GetBlockVerbose(blockHash) + if err != nil { + return nil, TxNotFoundIndex, + fmt.Errorf("unable to get block with hash %v for "+ + "historical dispatch: %v", blockHash, err) + } + + // If the block was obtained, locate the transaction's index within the + // block so we can give the subscriber full confirmation details. + txidStr := txid.String() + for txIndex, txHash := range block.Tx { + if txHash != txidStr { + continue + } + + // Deserialize the hex-encoded transaction to include it in the + // confirmation details. + rawTx, err := hex.DecodeString(rawTxRes.Hex) + if err != nil { + return nil, TxFoundIndex, + fmt.Errorf("unable to deserialize tx %v: %v", + txHash, err) + } + var tx wire.MsgTx + if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil { + return nil, TxFoundIndex, + fmt.Errorf("unable to deserialize tx %v: %v", + txHash, err) + } + + return &TxConfirmation{ + Tx: &tx, + BlockHash: blockHash, + BlockHeight: uint32(block.Height), + TxIndex: uint32(txIndex), + }, TxFoundIndex, nil + } + + // We return an error because we should have found the transaction + // within the block, but didn't. + return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+ + "tx %v in block %v", txid, blockHash) +}