rpcserver+mempool: implement gettxspendingprevout for btcd

This commit adds the RPC method `gettxspendingprevout` for btcd.
This commit is contained in:
yyforyongyu 2024-02-23 15:29:57 +08:00
parent dd31767617
commit 72bbdd55a6
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
4 changed files with 144 additions and 0 deletions

View File

@ -117,5 +117,9 @@ func (m *MockTxMempool) CheckMempoolAcceptance(
func (m *MockTxMempool) CheckSpend(op wire.OutPoint) *btcutil.Tx {
args := m.Called(op)
if args.Get(0) == nil {
return nil
}
return args.Get(0).(*btcutil.Tx)
}

View File

@ -185,6 +185,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
"verifymessage": handleVerifyMessage,
"version": handleVersion,
"testmempoolaccept": handleTestMempoolAccept,
"gettxspendingprevout": handleGetTxSpendingPrevOut,
}
// list of commands that we recognize, but for which btcd has no support because
@ -3906,6 +3907,49 @@ func handleTestMempoolAccept(s *rpcServer, cmd interface{},
return results, nil
}
// handleGetTxSpendingPrevOut implements the gettxspendingprevout command.
func handleGetTxSpendingPrevOut(s *rpcServer, cmd interface{},
closeChan <-chan struct{}) (interface{}, error) {
c := cmd.(*btcjson.GetTxSpendingPrevOutCmd)
// Convert the outpoints.
ops := make([]wire.OutPoint, 0, len(c.Outputs))
for _, o := range c.Outputs {
hash, err := chainhash.NewHashFromStr(o.Txid)
if err != nil {
return nil, err
}
ops = append(ops, wire.OutPoint{
Hash: *hash,
Index: o.Vout,
})
}
// Check mempool spend for all the outpoints.
results := make([]*btcjson.GetTxSpendingPrevOutResult, 0, len(ops))
for _, op := range ops {
// Create a result entry.
result := &btcjson.GetTxSpendingPrevOutResult{
Txid: op.Hash.String(),
Vout: op.Index,
}
// Check the mempool spend.
spendingTx := s.cfg.TxMemPool.CheckSpend(op)
// Set the spending txid if found.
if spendingTx != nil {
result.SpendingTxid = spendingTx.Hash().String()
}
results = append(results, result)
}
return results, nil
}
// validateFeeRate checks that the fee rate used by transaction doesn't exceed
// the max fee rate specified.
func validateFeeRate(feeSats btcutil.Amount, txSize int64,

View File

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mempool"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/require"
)
@ -411,3 +412,86 @@ func TestHandleTestMempoolAcceptFees(t *testing.T) {
})
}
}
// TestGetTxSpendingPrevOut checks that handleGetTxSpendingPrevOut handles the
// cmd as expected.
func TestGetTxSpendingPrevOut(t *testing.T) {
t.Parallel()
require := require.New(t)
// Create a mock mempool.
mm := &mempool.MockTxMempool{}
defer mm.AssertExpectations(t)
// Create a testing server with the mock mempool.
s := &rpcServer{cfg: rpcserverConfig{
TxMemPool: mm,
}}
// First, check the error case.
//
// Create a request that will cause an error.
cmd := &btcjson.GetTxSpendingPrevOutCmd{
Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{
{Txid: "invalid"},
},
}
// Call the method handler and assert the error is returned.
closeChan := make(chan struct{})
results, err := handleGetTxSpendingPrevOut(s, cmd, closeChan)
require.Error(err)
require.Nil(results)
// We now check the normal case. Two outputs will be tested - one found
// in mempool and other not.
//
// Decode the hex so we can assert the mock mempool is called with it.
tx := decodeTxHex(t, txHex1)
// Create testing outpoints.
opInMempool := wire.OutPoint{Hash: chainhash.Hash{1}, Index: 1}
opNotInMempool := wire.OutPoint{Hash: chainhash.Hash{2}, Index: 1}
// We only expect to see one output being found as spent in mempool.
expectedResults := []*btcjson.GetTxSpendingPrevOutResult{
{
Txid: opInMempool.Hash.String(),
Vout: opInMempool.Index,
SpendingTxid: tx.Hash().String(),
},
{
Txid: opNotInMempool.Hash.String(),
Vout: opNotInMempool.Index,
},
}
// We mock the first call to `CheckSpend` to return a result saying the
// output is found.
mm.On("CheckSpend", opInMempool).Return(tx).Once()
// We mock the second call to `CheckSpend` to return a result saying the
// output is NOT found.
mm.On("CheckSpend", opNotInMempool).Return(nil).Once()
// Create a request with the above outputs.
cmd = &btcjson.GetTxSpendingPrevOutCmd{
Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{
{
Txid: opInMempool.Hash.String(),
Vout: opInMempool.Index,
},
{
Txid: opNotInMempool.Hash.String(),
Vout: opNotInMempool.Index,
},
},
}
// Call the method handler and assert the expected result is returned.
closeChan = make(chan struct{})
results, err = handleGetTxSpendingPrevOut(s, cmd, closeChan)
require.NoError(err)
require.Equal(expectedResults, results)
}

View File

@ -734,6 +734,17 @@ var helpDescsEnUS = map[string]string{
"testmempoolacceptfees-base": "Transaction fees (only present if 'allowed' is true).",
"testmempoolacceptfees-effective-feerate": "The effective feerate in BTC per KvB.",
"testmempoolacceptfees-effective-includes": "Transactions whose fees and vsizes are included in effective-feerate. Each item is a transaction wtxid in hex.",
// GetTxSpendingPrevOutCmd help.
"gettxspendingprevout--synopsis": "Scans the mempool to find transactions spending any of the given outputs",
"gettxspendingprevout-outputs": "The transaction outputs that we want to check, and within each, the txid (string) vout (numeric).",
"gettxspendingprevout-txid": "The transaction id",
"gettxspendingprevout-vout": "The output number",
// GetTxSpendingPrevOutCmd result help.
"gettxspendingprevoutresult-txid": "The transaction hash in hex.",
"gettxspendingprevoutresult-vout": "The output index.",
"gettxspendingprevoutresult-spendingtxid": "The hash of the transaction that spends the output.",
}
// rpcResultTypes specifies the result types that each RPC command can return.
@ -790,6 +801,7 @@ var rpcResultTypes = map[string][]interface{}{
"verifymessage": {(*bool)(nil)},
"version": {(*map[string]btcjson.VersionResult)(nil)},
"testmempoolaccept": {(*[]btcjson.TestMempoolAcceptResult)(nil)},
"gettxspendingprevout": {(*[]btcjson.GetTxSpendingPrevOutResult)(nil)},
// Websocket commands.
"loadtxfilter": nil,