mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-05 20:49:48 +02:00
lnwallet+sweep: add new method CheckMempoolAcceptance
This commit is contained in:
parent
cd5d074099
commit
f85661d94a
159
lnmock/chain.go
Normal file
159
lnmock/chain.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package lnmock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/btcjson"
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcwallet/chain"
|
||||||
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockChain is a mock implementation of the Chain interface.
|
||||||
|
type MockChain struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time constraint to ensure MockChain implements the Chain interface.
|
||||||
|
var _ chain.Interface = (*MockChain)(nil)
|
||||||
|
|
||||||
|
func (m *MockChain) Start() error {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) Stop() {
|
||||||
|
m.Called()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) WaitForShutdown() {
|
||||||
|
m.Called()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) GetBestBlock() (*chainhash.Hash, int32, error) {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Get(1).(int32), args.Error(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*chainhash.Hash), args.Get(1).(int32), args.Error(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) GetBlock(hash *chainhash.Hash) (*wire.MsgBlock, error) {
|
||||||
|
args := m.Called(hash)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*wire.MsgBlock), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) GetBlockHash(height int64) (*chainhash.Hash, error) {
|
||||||
|
args := m.Called(height)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*chainhash.Hash), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) GetBlockHeader(hash *chainhash.Hash) (
|
||||||
|
*wire.BlockHeader, error) {
|
||||||
|
|
||||||
|
args := m.Called(hash)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*wire.BlockHeader), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) IsCurrent() bool {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
return args.Bool(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) FilterBlocks(req *chain.FilterBlocksRequest) (
|
||||||
|
*chain.FilterBlocksResponse, error) {
|
||||||
|
|
||||||
|
args := m.Called(req)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*chain.FilterBlocksResponse), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) BlockStamp() (*waddrmgr.BlockStamp, error) {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*waddrmgr.BlockStamp), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (
|
||||||
|
*chainhash.Hash, error) {
|
||||||
|
|
||||||
|
args := m.Called(tx, allowHighFees)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).(*chainhash.Hash), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) Rescan(startHash *chainhash.Hash, addrs []btcutil.Address,
|
||||||
|
outPoints map[wire.OutPoint]btcutil.Address) error {
|
||||||
|
|
||||||
|
args := m.Called(startHash, addrs, outPoints)
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) NotifyReceived(addrs []btcutil.Address) error {
|
||||||
|
args := m.Called(addrs)
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) NotifyBlocks() error {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) Notifications() <-chan interface{} {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
return args.Get(0).(<-chan interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) BackEnd() string {
|
||||||
|
args := m.Called()
|
||||||
|
|
||||||
|
return args.String(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockChain) TestMempoolAccept(txns []*wire.MsgTx, maxFeeRate float64) (
|
||||||
|
[]*btcjson.TestMempoolAcceptResult, error) {
|
||||||
|
|
||||||
|
args := m.Called(txns, maxFeeRate)
|
||||||
|
|
||||||
|
if args.Get(0) == nil {
|
||||||
|
return nil, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Get(0).([]*btcjson.TestMempoolAcceptResult), args.Error(1)
|
||||||
|
}
|
@ -282,3 +282,7 @@ func (w *WalletController) FetchTx(chainhash.Hash) (*wire.MsgTx, error) {
|
|||||||
func (w *WalletController) RemoveDescendants(*wire.MsgTx) error {
|
func (w *WalletController) RemoveDescendants(*wire.MsgTx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1898,3 +1898,34 @@ func (b *BtcWallet) RemoveDescendants(tx *wire.MsgTx) error {
|
|||||||
return b.wallet.TxStore.RemoveUnminedTx(wtxmgrNs, txRecord)
|
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
|
||||||
|
}
|
||||||
|
@ -3,8 +3,12 @@ package btcwallet
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcjson"
|
||||||
|
"github.com/btcsuite/btcd/rpcclient"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcwallet/chain"
|
||||||
"github.com/btcsuite/btcwallet/wallet"
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnmock"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/stretchr/testify/require"
|
"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)
|
||||||
|
}
|
||||||
|
@ -536,6 +536,11 @@ type WalletController interface {
|
|||||||
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
|
// which could be e.g. btcd, bitcoind, neutrino, or another consensus
|
||||||
// service.
|
// service.
|
||||||
BackEnd() string
|
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
|
// BlockChainIO is a dedicated source which will be used to obtain queries
|
||||||
|
@ -294,6 +294,10 @@ func (w *mockWalletController) RemoveDescendants(*wire.MsgTx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *mockWalletController) CheckMempoolAcceptance(tx *wire.MsgTx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// mockChainNotifier is a mock implementation of the ChainNotifier interface.
|
// mockChainNotifier is a mock implementation of the ChainNotifier interface.
|
||||||
type mockChainNotifier struct {
|
type mockChainNotifier struct {
|
||||||
SpendChan chan *chainntnfs.SpendDetail
|
SpendChan chan *chainntnfs.SpendDetail
|
||||||
|
@ -41,4 +41,9 @@ type Wallet interface {
|
|||||||
// used to ensure that invalid transactions (inputs spent) aren't
|
// used to ensure that invalid transactions (inputs spent) aren't
|
||||||
// retried in the background.
|
// retried in the background.
|
||||||
CancelRebroadcast(tx chainhash.Hash)
|
CancelRebroadcast(tx chainhash.Hash)
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,10 @@ func newMockBackend(t *testing.T, notifier *MockNotifier) *mockBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *mockBackend) CheckMempoolAcceptance(tx *wire.MsgTx) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *mockBackend) publishTransaction(tx *wire.MsgTx) error {
|
func (b *mockBackend) publishTransaction(tx *wire.MsgTx) error {
|
||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
@ -344,6 +348,14 @@ type MockWallet struct {
|
|||||||
// Compile-time constraint to ensure MockWallet implements Wallet.
|
// Compile-time constraint to ensure MockWallet implements Wallet.
|
||||||
var _ Wallet = (*MockWallet)(nil)
|
var _ Wallet = (*MockWallet)(nil)
|
||||||
|
|
||||||
|
// CheckMempoolAcceptance checks if the transaction can be accepted to the
|
||||||
|
// mempool.
|
||||||
|
func (m *MockWallet) CheckMempoolAcceptance(tx *wire.MsgTx) error {
|
||||||
|
args := m.Called(tx)
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
// PublishTransaction performs cursory validation (dust checks, etc) and
|
// PublishTransaction performs cursory validation (dust checks, etc) and
|
||||||
// broadcasts the passed transaction to the Bitcoin network.
|
// broadcasts the passed transaction to the Bitcoin network.
|
||||||
func (m *MockWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
|
func (m *MockWallet) PublishTransaction(tx *wire.MsgTx, label string) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user