From 10929d80cc2531d3ee4fe39bb14fbe7e85744b8c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Thu, 23 Feb 2023 17:59:06 -0800 Subject: [PATCH] lnwallet: add new rebroadcaster interface, use for background tx publish In this commit, we add a new Rebroadcaster interface to be used for publishing transactions passively in the background until they've been confirmed on chain. This is useful if a tx drops out of the mempool, but then the pool clears down and has more space available to accept the tx at the current fee level. --- lnwallet/config.go | 5 +++ lnwallet/rebroadcaster.go | 27 +++++++++++++++ lnwallet/wallet.go | 72 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 lnwallet/rebroadcaster.go diff --git a/lnwallet/config.go b/lnwallet/config.go index cf7f3f4b8..f18d6e529 100644 --- a/lnwallet/config.go +++ b/lnwallet/config.go @@ -56,4 +56,9 @@ type Config struct { // NetParams is the set of parameters that tells the wallet which chain // it will be operating on. NetParams chaincfg.Params + + // Rebroadcaster is an optional config param that can be used to + // passively rebroadcast transactions in the background until they're + // detected as being confirmed. + Rebroadcaster Rebroadcaster } diff --git a/lnwallet/rebroadcaster.go b/lnwallet/rebroadcaster.go new file mode 100644 index 000000000..bb139f0a6 --- /dev/null +++ b/lnwallet/rebroadcaster.go @@ -0,0 +1,27 @@ +package lnwallet + +import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +// Rebroadcaster is an abstract rebroadcaster instance that'll continually +// rebroadcast transactions in the background until they're confirmed. +type Rebroadcaster interface { + // Start launches all goroutines the rebroadcaster needs to operate. + Start() error + + // Started returns true if the broadcaster is already active. + Started() bool + + // Stop terminates the rebroadcaster and all goroutines it spawned. + Stop() + + // Broadcast enqueues a transaction to be rebroadcast until it's been + // confirmed. + Broadcast(tx *wire.MsgTx) error + + // MarkAsConfirmed marks a transaction as confirmed, so it won't be + // rebroadcast. + MarkAsConfirmed(txid chainhash.Hash) +} diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index a76c706dd..091affa34 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -407,6 +407,15 @@ func (l *LightningWallet) Startup() error { return err } + if l.Cfg.Rebroadcaster != nil { + go func() { + if err := l.Cfg.Rebroadcaster.Start(); err != nil { + walletLog.Errorf("unable to start "+ + "rebroadcaster: %v", err) + } + }() + } + l.wg.Add(1) // TODO(roasbeef): multiple request handlers? go l.requestHandler() @@ -426,11 +435,74 @@ func (l *LightningWallet) Shutdown() error { return err } + if l.Cfg.Rebroadcaster != nil && l.Cfg.Rebroadcaster.Started() { + l.Cfg.Rebroadcaster.Stop() + } + close(l.quit) l.wg.Wait() return nil } +// PublishTransaction wraps the wallet controller tx publish method with an +// extra rebroadcaster layer if the sub-system is configured. +func (l *LightningWallet) PublishTransaction(tx *wire.MsgTx, + label string) error { + + sendTxToWallet := func() error { + return l.WalletController.PublishTransaction(tx, label) + } + + // If we don't have rebroadcaster then we can exit early (and send only + // to the wallet). + if l.Cfg.Rebroadcaster == nil || !l.Cfg.Rebroadcaster.Started() { + return sendTxToWallet() + } + + // We pass this into the rebroadcaster first, so the initial attempt + // will succeed if the transaction isn't yet in the mempool. However we + // ignore the error here as this might be resent on start up and the + // transaction already exists. + _ = l.Cfg.Rebroadcaster.Broadcast(tx) + + // Then we pass things into the wallet as normal, which'll add the + // transaction label on disk. + if err := sendTxToWallet(); err != nil { + return err + } + + // TODO(roasbeef): want diff height actually? no context though + _, bestHeight, err := l.Cfg.ChainIO.GetBestBlock() + if err != nil { + return err + } + + txHash := tx.TxHash() + go func() { + const numConfs = 6 + + txConf, err := l.Cfg.Notifier.RegisterConfirmationsNtfn( + &txHash, tx.TxOut[0].PkScript, numConfs, uint32(bestHeight), + ) + if err != nil { + return + } + + select { + case <-txConf.Confirmed: + // TODO(roasbeef): also want to remove from + // rebroadcaster if conflict happens...deeper wallet + // integration? + l.Cfg.Rebroadcaster.MarkAsConfirmed(tx.TxHash()) + + case <-l.quit: + return + } + }() + + return nil +} + // ConfirmedBalance returns the current confirmed balance of a wallet account. // This methods wraps the internal WalletController method so we're able to // properly hold the coin select mutex while we compute the balance.