Skip to content

Commit

Permalink
Merge pull request #2125 from yyforyongyu/add-gettxspendingprevout
Browse files Browse the repository at this point in the history
Add `gettxspendingprevout` for `btcd` and fix version check
  • Loading branch information
Roasbeef committed Mar 1, 2024
2 parents 9ea2eea + 0d4ed16 commit 1a2b599
Show file tree
Hide file tree
Showing 16 changed files with 806 additions and 172 deletions.
33 changes: 33 additions & 0 deletions btcjson/chainsvrcmds.go
Expand Up @@ -1064,6 +1064,38 @@ func NewTestMempoolAcceptCmd(rawTxns []string,
}
}

// GetTxSpendingPrevOutCmd defines the gettxspendingprevout JSON-RPC command.
type GetTxSpendingPrevOutCmd struct {
// Outputs is a list of transaction outputs to query.
Outputs []*GetTxSpendingPrevOutCmdOutput
}

// GetTxSpendingPrevOutCmdOutput defines the output to query for the
// gettxspendingprevout JSON-RPC command.
type GetTxSpendingPrevOutCmdOutput struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
}

// NewGetTxSpendingPrevOutCmd returns a new instance which can be used to issue
// a gettxspendingprevout JSON-RPC command.
func NewGetTxSpendingPrevOutCmd(
outpoints []wire.OutPoint) *GetTxSpendingPrevOutCmd {

outputs := make([]*GetTxSpendingPrevOutCmdOutput, 0, len(outpoints))

for _, op := range outpoints {
outputs = append(outputs, &GetTxSpendingPrevOutCmdOutput{
Txid: op.Hash.String(),
Vout: op.Index,
})
}

return &GetTxSpendingPrevOutCmd{
Outputs: outputs,
}
}

func init() {
// No special flags for commands in this file.
flags := UsageFlag(0)
Expand Down Expand Up @@ -1125,4 +1157,5 @@ func init() {
MustRegisterCmd("verifymessage", (*VerifyMessageCmd)(nil), flags)
MustRegisterCmd("verifytxoutproof", (*VerifyTxOutProofCmd)(nil), flags)
MustRegisterCmd("testmempoolaccept", (*TestMempoolAcceptCmd)(nil), flags)
MustRegisterCmd("gettxspendingprevout", (*GetTxSpendingPrevOutCmd)(nil), flags)
}
24 changes: 24 additions & 0 deletions btcjson/chainsvrcmds_test.go
Expand Up @@ -13,6 +13,7 @@ import (
"testing"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)

Expand Down Expand Up @@ -1500,6 +1501,29 @@ func TestChainSvrCmds(t *testing.T) {
MaxFeeRate: 0.01,
},
},
{
name: "gettxspendingprevout",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd(
"gettxspendingprevout",
[]*btcjson.GetTxSpendingPrevOutCmdOutput{
{Txid: "0000000000000000000000000000000000000000000000000000000000000001", Vout: 0},
})
},
staticCmd: func() interface{} {
outputs := []wire.OutPoint{
{Hash: chainhash.Hash{1}, Index: 0},
}
return btcjson.NewGetTxSpendingPrevOutCmd(outputs)
},
marshalled: `{"jsonrpc":"1.0","method":"gettxspendingprevout","params":[[{"txid":"0000000000000000000000000000000000000000000000000000000000000001","vout":0}]],"id":1}`,
unmarshalled: &btcjson.GetTxSpendingPrevOutCmd{
Outputs: []*btcjson.GetTxSpendingPrevOutCmdOutput{{
Txid: "0000000000000000000000000000000000000000000000000000000000000001",
Vout: 0,
}},
},
},
}

t.Logf("Running %d tests", len(tests))
Expand Down
14 changes: 14 additions & 0 deletions btcjson/chainsvrresults.go
Expand Up @@ -911,3 +911,17 @@ type TestMempoolAcceptFees struct {
// NOTE: this field only exists in bitcoind v25.0 and above.
EffectiveIncludes []string `json:"effective-includes"`
}

// GetTxSpendingPrevOutResult defines a single item returned from the
// gettxspendingprevout command.
type GetTxSpendingPrevOutResult struct {
// Txid is the transaction id of the checked output.
Txid string `json:"txid"`

// Vout is the vout value of the checked output.
Vout uint32 `json:"vout"`

// SpendingTxid is the transaction id of the mempool transaction
// spending this output (omitted if unspent).
SpendingTxid string `json:"spendingtxid,omitempty"`
}
146 changes: 146 additions & 0 deletions integration/chain_test.go
@@ -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
}
4 changes: 4 additions & 0 deletions mempool/mocks.go
Expand Up @@ -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)
}

0 comments on commit 1a2b599

Please sign in to comment.