From d4a6be1c9f2b3ebe8720f3e03b8dff23ab4e69ff Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 25 Jul 2023 17:19:47 -0700 Subject: [PATCH] contractcourt: for taproot channels, wait for conf in closeObserver This ensures that we end up playing the target output on chain for taproot channels. --- contractcourt/chain_watcher.go | 53 +++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 39d160538..36a1a96f3 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -207,6 +207,13 @@ type chainWatcher struct { // the current state number on the commitment transactions. stateHintObfuscator [lnwallet.StateHintSize]byte + // fundingPkScript is the pkScript of the funding output. + fundingPkScript []byte + + // heightHint is the height hint used to checkpoint scans on chain for + // conf/spend events. + heightHint uint32 + // All the fields below are protected by this mutex. sync.Mutex @@ -267,9 +274,9 @@ func (c *chainWatcher) Start() error { // As a height hint, we'll try to use the opening height, but if the // channel isn't yet open, then we'll use the height it was broadcast // at. This may be an unconfirmed zero-conf channel. - heightHint := c.cfg.chanState.ShortChanID().BlockHeight - if heightHint == 0 { - heightHint = chanState.BroadcastHeight() + c.heightHint = c.cfg.chanState.ShortChanID().BlockHeight + if c.heightHint == 0 { + c.heightHint = chanState.BroadcastHeight() } // Since no zero-conf state is stored in a channel backup, the below @@ -279,11 +286,11 @@ func (c *chainWatcher) Start() error { if chanState.ZeroConfConfirmed() { // If the zero-conf channel is confirmed, we'll use the // confirmed SCID's block height. - heightHint = chanState.ZeroConfRealScid().BlockHeight + c.heightHint = chanState.ZeroConfRealScid().BlockHeight } else { // The zero-conf channel is unconfirmed. We'll need to // use the FundingBroadcastHeight. - heightHint = chanState.BroadcastHeight() + c.heightHint = chanState.BroadcastHeight() } } @@ -291,11 +298,10 @@ func (c *chainWatcher) Start() error { remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey var ( - pkScript []byte - err error + err error ) if chanState.ChanType.IsTaproot() { - pkScript, _, err = input.GenTaprootFundingScript( + c.fundingPkScript, _, err = input.GenTaprootFundingScript( localKey, remoteKey, 0, ) if err != nil { @@ -309,14 +315,14 @@ func (c *chainWatcher) Start() error { if err != nil { return err } - pkScript, err = input.WitnessScriptHash(multiSigScript) + c.fundingPkScript, err = input.WitnessScriptHash(multiSigScript) if err != nil { return err } } spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn( - fundingOut, pkScript, heightHint, + fundingOut, c.fundingPkScript, c.heightHint, ) if err != nil { return err @@ -582,6 +588,33 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { log.Infof("Close observer for ChannelPoint(%v) active", c.cfg.chanState.FundingOutpoint) + // If this is a taproot channel, before we proceed, we want to ensure + // that the expected funding output has confirmed on chain. + if c.cfg.chanState.ChanType.IsTaproot() { + fundingPoint := c.cfg.chanState.FundingOutpoint + + confNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn( + &fundingPoint.Hash, c.fundingPkScript, 1, c.heightHint, + ) + if err != nil { + log.Warnf("unable to register for conf: %v", err) + } + + log.Infof("Waiting for taproot ChannelPoint(%v) to confirm...", + c.cfg.chanState.FundingOutpoint) + + select { + case _, ok := <-confNtfn.Confirmed: + // If the channel was closed, then this means that the + // notifier exited, so we will as well. + if !ok { + return + } + case <-c.quit: + return + } + } + select { // We've detected a spend of the channel onchain! Depending on the type // of spend, we'll act accordingly, so we'll examine the spending