diff --git a/btcclient/client_wallet.go b/btcclient/client_wallet.go index d591bbf..4fa3c1e 100644 --- a/btcclient/client_wallet.go +++ b/btcclient/client_wallet.go @@ -2,6 +2,7 @@ package btcclient import ( "fmt" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/btcutil" @@ -15,6 +16,17 @@ import ( "github.com/babylonlabs-io/vigilante/netparams" ) +type TxStatus int + +const ( + TxNotFound TxStatus = iota + TxInMemPool + TxInChain +) +const ( + txNotFoundErrMsgBitcoind = "No such mempool or blockchain transaction" +) + // NewWallet creates a new BTC wallet // used by vigilant submitter // a wallet is essentially a BTC client @@ -125,3 +137,42 @@ func (c *Client) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool func (c *Client) GetRawTransaction(txHash *chainhash.Hash) (*btcutil.Tx, error) { return c.Client.GetRawTransaction(txHash) } + +func notifierStateToWalletState(state notifier.TxConfStatus) TxStatus { + switch state { + case notifier.TxNotFoundIndex: + return TxNotFound + case notifier.TxFoundMempool: + return TxInMemPool + case notifier.TxFoundIndex: + return TxInChain + case notifier.TxNotFoundManually: + return TxNotFound + case notifier.TxFoundManually: + return TxInChain + default: + panic(fmt.Sprintf("unknown notifier state: %s", state)) + } +} + +func (c *Client) getTxDetails(req notifier.ConfRequest, msg string) (*notifier.TxConfirmation, TxStatus, error) { + res, state, err := notifier.ConfDetailsFromTxIndex(c.Client, req, msg) + + if err != nil { + return nil, TxNotFound, err + } + + return res, notifierStateToWalletState(state), nil +} + +// TxDetails Fetch info about transaction from mempool or blockchain, requires node to have enabled transaction index +func (c *Client) TxDetails(txHash *chainhash.Hash, pkScript []byte) (*notifier.TxConfirmation, TxStatus, error) { + req, err := notifier.NewConfRequest(txHash, pkScript) + + if err != nil { + return nil, TxNotFound, err + } + + return c.getTxDetails(req, txNotFoundErrMsgBitcoind) + +} diff --git a/btcclient/interface.go b/btcclient/interface.go index 5324dea..58e42ff 100644 --- a/btcclient/interface.go +++ b/btcclient/interface.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/types" @@ -39,4 +40,5 @@ type BTCWallet interface { FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) GetRawTransaction(txHash *chainhash.Hash) (*btcutil.Tx, error) + TxDetails(txHash *chainhash.Hash, pkScript []byte) (*notifier.TxConfirmation, TxStatus, error) } diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index ade1cc2..42733e5 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -253,13 +253,16 @@ func (rl *Relayer) calculateBumpedFee(ckptInfo *types.CheckpointInfo) btcutil.Am // resendSecondTxOfCheckpointToBTC resends the second tx of the checkpoint with bumpedFee func (rl *Relayer) resendSecondTxOfCheckpointToBTC(tx2 *types.BtcTxInfo, bumpedFee btcutil.Amount) (*types.BtcTxInfo, error) { - found, err := rl.inMempool() + _, status, err := rl.TxDetails(rl.lastSubmittedCheckpoint.Tx2.TxId, + rl.lastSubmittedCheckpoint.Tx2.Tx.TxOut[changePosition].PkScript) if err != nil { return nil, err } // No need to resend, transaction already confirmed - if !found { + if status != btcclient.TxInMemPool && status != btcclient.TxNotFound { + rl.logger.Debugf("Transaction %v is already confirmed or has an unexpected state: %v", + rl.lastSubmittedCheckpoint.Tx2.TxId, status) return nil, nil } diff --git a/testutil/mocks/btcclient.go b/testutil/mocks/btcclient.go index 105f29f..b8a6a07 100644 --- a/testutil/mocks/btcclient.go +++ b/testutil/mocks/btcclient.go @@ -7,6 +7,7 @@ package mocks import ( reflect "reflect" + btcclient "github.com/babylonlabs-io/vigilante/btcclient" config "github.com/babylonlabs-io/vigilante/config" types "github.com/babylonlabs-io/vigilante/types" btcjson "github.com/btcsuite/btcd/btcjson" @@ -15,6 +16,7 @@ import ( chainhash "github.com/btcsuite/btcd/chaincfg/chainhash" wire "github.com/btcsuite/btcd/wire" gomock "github.com/golang/mock/gomock" + chainntnfs "github.com/lightningnetwork/lnd/chainntnfs" ) // MockBTCClient is a mock of BTCClient interface. @@ -399,6 +401,22 @@ func (mr *MockBTCWalletMockRecorder) Stop() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockBTCWallet)(nil).Stop)) } +// TxDetails mocks base method. +func (m *MockBTCWallet) TxDetails(txHash *chainhash.Hash, pkScript []byte) (*chainntnfs.TxConfirmation, btcclient.TxStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TxDetails", txHash, pkScript) + ret0, _ := ret[0].(*chainntnfs.TxConfirmation) + ret1, _ := ret[1].(btcclient.TxStatus) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// TxDetails indicates an expected call of TxDetails. +func (mr *MockBTCWalletMockRecorder) TxDetails(txHash, pkScript interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TxDetails", reflect.TypeOf((*MockBTCWallet)(nil).TxDetails), txHash, pkScript) +} + // WalletPassphrase mocks base method. func (m *MockBTCWallet) WalletPassphrase(passphrase string, timeoutSecs int64) error { m.ctrl.T.Helper()