lnwallet+sweep: add new method CheckMempoolAcceptance

This commit is contained in:
yyforyongyu committed 2024-04-19 21:33:28 +08:00
1 parent cd5d074099
commit f85661d94a
8 files changed
+310

No files matched your search

+31
View File
@@ -1898,3 +1898,34 @@ func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
})
}
// CheckMempoolAcceptance is a wrapper around `TestMempoolAccept` which checks
// the mempool acceptance of a transaction.
func (b *BtcWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
// Use a max feerate of 0 means the default value will be used when
// testing mempool acceptance. The default max feerate is 0.10 BTC/kvb,
// or 10,000 sat/vb.
results, err := b.chain.TestMempoolAccept([]*wire.MsgTx{tx}, 0)
if err != nil {
return err
}
// Sanity check that the expected single result is returned.
if len(results) != 1 {
return fmt.Errorf("expected 1 result from TestMempoolAccept, "+
"instead got %v", len(results))
}
result := results[0]
log.Debugf("TestMempoolAccept result: %s", spew.Sdump(result))
// Mempool check failed, we now map the reject reason to a proper RPC
// error and return it.
if !result.Allowed {
err := rpcclient.MapRPCErr(errors.New(result.RejectReason))
return fmt.Errorf("mempool rejection: %w", err)
}
return nil
}
+90
View File
@@ -3,8 +3,12 @@ package btcwallet
import (
"testing"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/lnmock"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
@@ -132,3 +136,89 @@ func TestPreviousOutpoints(t *testing.T) {
})
}
}
// TestCheckMempoolAcceptance asserts the CheckMempoolAcceptance behaves as
// expected.
func TestCheckMempoolAcceptance(t *testing.T) {
t.Parallel()
rt := require.New(t)
// Create a mock chain.Interface.
mockChain := &lnmock.MockChain{}
defer mockChain.AssertExpectations(t)
// Create a test tx and a test max feerate.
tx := wire.NewMsgTx(2)
maxFeeRate := float64(0)
// Create a test wallet.
wallet := &BtcWallet{
chain: mockChain,
}
// Assert that when the chain backend doesn't support
// `TestMempoolAccept`, an error is returned.
//
// Mock the chain backend to not support `TestMempoolAccept`.
mockChain.On("TestMempoolAccept", []*wire.MsgTx{tx}, maxFeeRate).Return(
nil, rpcclient.ErrBackendVersion).Once()
err := wallet.CheckMempoolAcceptance(tx)
rt.ErrorIs(err, rpcclient.ErrBackendVersion)
// Assert that when the chain backend doesn't implement
// `TestMempoolAccept`, an error is returned.
//
// Mock the chain backend to not support `TestMempoolAccept`.
mockChain.On("TestMempoolAccept", []*wire.MsgTx{tx}, maxFeeRate).Return(
nil, chain.ErrUnimplemented).Once()
// Now call the method under test.
err = wallet.CheckMempoolAcceptance(tx)
rt.ErrorIs(err, chain.ErrUnimplemented)
// Assert that when the returned results are not as expected, an error
// is returned.
//
// Mock the chain backend to return more than one result.
results := []*btcjson.TestMempoolAcceptResult{
{Txid: "txid1", Allowed: true},
{Txid: "txid2", Allowed: false},
}
mockChain.On("TestMempoolAccept", []*wire.MsgTx{tx}, maxFeeRate).Return(
results, nil).Once()
// Now call the method under test.
err = wallet.CheckMempoolAcceptance(tx)
rt.ErrorContains(err, "expected 1 result from TestMempoolAccept")
// Assert that when the tx is rejected, the reason is converted to an
// RPC error and returned.
//
// Mock the chain backend to return one result.
results = []*btcjson.TestMempoolAcceptResult{{
Txid: tx.TxHash().String(),
Allowed: false,
RejectReason: "insufficient fee",
}}
mockChain.On("TestMempoolAccept", []*wire.MsgTx{tx}, maxFeeRate).Return(
results, nil).Once()
// Now call the method under test.
err = wallet.CheckMempoolAcceptance(tx)
rt.ErrorIs(err, rpcclient.ErrInsufficientFee)
// Assert that when the tx is accepted, no error is returned.
//
// Mock the chain backend to return one result.
results = []*btcjson.TestMempoolAcceptResult{
{Txid: tx.TxHash().String(), Allowed: true},
}
mockChain.On("TestMempoolAccept", []*wire.MsgTx{tx}, maxFeeRate).Return(
results, nil).Once()
// Now call the method under test.
err = wallet.CheckMempoolAcceptance(tx)
rt.NoError(err)
}
+5
View File
@@ -536,6 +536,11 @@ type WalletController interface {
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
// service.
BackEnd() string
// CheckMempoolAcceptance checks whether a transaction follows mempool
// policies and returns an error if it cannot be accepted into the
// mempool.
CheckMempoolAcceptance(tx *wire.MsgTx) error
}
// BlockChainIO is a dedicated source which will be used to obtain queries
+4
View File
@@ -294,6 +294,10 @@ func (w *mockWalletController) RemoveDescendants(*wire.MsgTx) error {
return nil
}
func (w *mockWalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
return nil
}
// mockChainNotifier is a mock implementation of the ChainNotifier interface.
type mockChainNotifier struct {
SpendChan chan *chainntnfs.SpendDetail