rpcclient+integration: add new method GetTxSpendingPrevOut

This commit is contained in:
yyforyongyu 2024-02-23 16:30:14 +08:00
parent 5f71df165e
commit a033b0d6e7
No known key found for this signature in database
GPG key ID: 9BCD95C4FF296868
2 changed files with 214 additions and 0 deletions

146
integration/chain_test.go Normal file
View file

@ -0,0 +1,146 @@
//go:build rpctest
// +build rpctest
package integration
import (
"testing"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/require"
)
// TestGetTxSpendingPrevOut checks that `GetTxSpendingPrevOut` behaves as
// expected.
// - an error is returned when invalid params are used.
// - orphan tx is rejected.
// - fee rate above the max is rejected.
// - a mixed of both allowed and rejected can be returned in the same response.
func TestGetTxSpendingPrevOut(t *testing.T) {
t.Parallel()
// Boilerplate codetestDir to make a pruned node.
btcdCfg := []string{"--rejectnonstd", "--debuglevel=debug"}
r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg, "")
require.NoError(t, err)
// Setup the node.
require.NoError(t, r.SetUp(true, 100))
t.Cleanup(func() {
require.NoError(t, r.TearDown())
})
// Create a tx and testing outpoints.
tx := createTxInMempool(t, r)
opInMempool := tx.TxIn[0].PreviousOutPoint
opNotInMempool := wire.OutPoint{
Hash: tx.TxHash(),
Index: 0,
}
testCases := []struct {
name string
outpoints []wire.OutPoint
expectedErr error
expectedResult []*btcjson.GetTxSpendingPrevOutResult
}{
{
// When no outpoints are provided, the method should
// return an error.
name: "empty outpoints",
expectedErr: rpcclient.ErrInvalidParam,
expectedResult: nil,
},
{
// When there are outpoints provided, check the
// expceted results are returned.
name: "outpoints",
outpoints: []wire.OutPoint{
opInMempool, opNotInMempool,
},
expectedErr: nil,
expectedResult: []*btcjson.GetTxSpendingPrevOutResult{
{
Txid: opInMempool.Hash.String(),
Vout: opInMempool.Index,
SpendingTxid: tx.TxHash().String(),
},
{
Txid: opNotInMempool.Hash.String(),
Vout: opNotInMempool.Index,
},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
results, err := r.Client.GetTxSpendingPrevOut(
tc.outpoints,
)
require.ErrorIs(err, tc.expectedErr)
require.Len(results, len(tc.expectedResult))
// Check each item is returned as expected.
for i, r := range results {
e := tc.expectedResult[i]
require.Equal(e.Txid, r.Txid)
require.Equal(e.Vout, r.Vout)
require.Equal(e.SpendingTxid, r.SpendingTxid)
}
})
}
}
// createTxInMempool creates a tx and puts it in the mempool.
func createTxInMempool(t *testing.T, r *rpctest.Harness) *wire.MsgTx {
// Create a fresh output for usage within the test below.
const outputValue = btcutil.SatoshiPerBitcoin
outputKey, testOutput, testPkScript, err := makeTestOutput(
r, t, outputValue,
)
require.NoError(t, err)
// Create a new transaction with a lock-time past the current known
// MTP.
tx := wire.NewMsgTx(1)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *testOutput,
})
// Fetch a fresh address from the harness, we'll use this address to
// send funds back into the Harness.
addr, err := r.NewAddress()
require.NoError(t, err)
addrScript, err := txscript.PayToAddrScript(addr)
require.NoError(t, err)
tx.AddTxOut(&wire.TxOut{
PkScript: addrScript,
Value: outputValue - 1000,
})
sigScript, err := txscript.SignatureScript(
tx, 0, testPkScript, txscript.SigHashAll, outputKey, true,
)
require.NoError(t, err)
tx.TxIn[0].SignatureScript = sigScript
// Send the tx.
_, err = r.Client.SendRawTransaction(tx, true)
require.NoError(t, err)
return tx
}

View file

@ -1012,3 +1012,71 @@ func (c *Client) TestMempoolAccept(txns []*wire.MsgTx,
return c.TestMempoolAcceptAsync(txns, maxFeeRate).Receive()
}
// FutureGetTxSpendingPrevOut is a future promise to deliver the result of a
// GetTxSpendingPrevOut RPC invocation (or an applicable error).
type FutureGetTxSpendingPrevOut chan *Response
// Receive waits for the Response promised by the future and returns the
// response from GetTxSpendingPrevOut.
func (r FutureGetTxSpendingPrevOut) Receive() (
[]*btcjson.GetTxSpendingPrevOutResult, error) {
response, err := ReceiveFuture(r)
if err != nil {
return nil, err
}
// Unmarshal as an array of GetTxSpendingPrevOutResult items.
var results []*btcjson.GetTxSpendingPrevOutResult
err = json.Unmarshal(response, &results)
if err != nil {
return nil, err
}
return results, nil
}
// GetTxSpendingPrevOutAsync returns an instance of a type that can be used to
// get the result of the RPC at some future time by invoking the Receive
// function on the returned instance.
//
// See GetTxSpendingPrevOut for the blocking version and more details.
func (c *Client) GetTxSpendingPrevOutAsync(
outpoints []wire.OutPoint) FutureGetTxSpendingPrevOut {
// Due to differences in the testmempoolaccept API for different
// backends, we'll need to inspect our version and construct the
// appropriate request.
version, err := c.BackendVersion()
if err != nil {
return newFutureError(err)
}
log.Debugf("GetTxSpendingPrevOutAsync: backend version %s", version)
// Exit early if the version is below 24.0.0.
if version < BitcoindPre24 {
err := fmt.Errorf("%w: %v", ErrBitcoindVersion, version)
return newFutureError(err)
}
// Exit early if an empty array of outpoints is provided.
if len(outpoints) == 0 {
err := fmt.Errorf("%w: no outpoints provided", ErrInvalidParam)
return newFutureError(err)
}
cmd := btcjson.NewGetTxSpendingPrevOutCmd(outpoints)
return c.SendCmd(cmd)
}
// GetTxSpendingPrevOut returns the result from calling `gettxspendingprevout`
// RPC.
func (c *Client) GetTxSpendingPrevOut(outpoints []wire.OutPoint) (
[]*btcjson.GetTxSpendingPrevOutResult, error) {
return c.GetTxSpendingPrevOutAsync(outpoints).Receive()
}