From 8a61af6a55b1d77b0f5285932505bbf161c2a63e Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Sun, 16 Sep 2018 10:40:10 +0200 Subject: [PATCH] fundingmanager: make advanceFundingState handle pending channels This commit makes advanceFundingState check whether a channel is still pending before checking the channel opening state. This lets us call it directly, without checking whether a channel has confirmed first. --- fundingmanager.go | 226 +++++++++++++++++++++------------------------- 1 file changed, 104 insertions(+), 122 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index 14a88342..0f037ec0 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -526,60 +526,7 @@ func (f *fundingManager) start() error { } f.wg.Add(1) - go func(ch *channeldb.OpenChannel) { - defer f.wg.Done() - - shortChanID, err := f.waitForFundingWithTimeout(ch) - if err == ErrConfirmationTimeout { - fndgLog.Warnf("Timeout waiting for funding "+ - "confirmation of ChannelPoint(%v)", - ch.FundingOutpoint) - - // We'll get a timeout if the number of blocks - // mined since the channel was initiated - // reaches maxWaitNumBlocksFundingConf and we - // are not the channel initiator. - localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis() - closeInfo := &channeldb.ChannelCloseSummary{ - ChainHash: ch.ChainHash, - ChanPoint: ch.FundingOutpoint, - RemotePub: ch.IdentityPub, - Capacity: ch.Capacity, - SettledBalance: localBalance, - CloseType: channeldb.FundingCanceled, - RemoteCurrentRevocation: ch.RemoteCurrentRevocation, - RemoteNextRevocation: ch.RemoteNextRevocation, - LocalChanConfig: ch.LocalChanCfg, - } - - if err := ch.CloseChannel(closeInfo); err != nil { - fndgLog.Errorf("Failed closing channel "+ - "%v: %v", ch.FundingOutpoint, err) - return - } - return - } else if err != nil { - fndgLog.Errorf("Failed waiting for funding "+ - "confirmation for ChannelPoint(%v): %v", - ch.FundingOutpoint, err) - return - } - - // Success, funding transaction was confirmed. - fndgLog.Debugf("ChannelID(%v) is now fully confirmed! "+ - "(shortChanID=%v)", chanID, shortChanID) - - err = f.handleFundingConfirmation(ch, *shortChanID) - if err != nil { - fndgLog.Errorf("unable to handle funding "+ - "confirmation for ChannelPoint(%v): %v", - ch.FundingOutpoint, err) - return - } - - f.wg.Add(1) - go f.advanceFundingState(ch, nil) - }(channel) + go f.advanceFundingState(channel, chanID, nil) } // Fetch all our open channels, and make sure they all finalized the @@ -593,8 +540,10 @@ func (f *fundingManager) start() error { } for _, channel := range openChannels { + chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) + f.wg.Add(1) - go f.advanceFundingState(channel, nil) + go f.advanceFundingState(channel, chanID, nil) } f.wg.Add(1) // TODO(roasbeef): tune @@ -819,18 +768,31 @@ func (f *fundingManager) reservationCoordinator() { } } -// advanceFundingState will advance the channel fromt the markedOpen state to -// the point where the channel is ready for operation. This includes sending -// funding locked to the peer, adding the channel to the router graph, and -// announcing the channel. The updateChan can be set non-nil to get -// OpenStatusUpdates. +// advanceFundingState will advance the channel through the steps after the +// funding transaction is broadcasted, up until the point where the channel is +// ready for operation. This includes waiting for the funding transaction to +// confirm, sending funding locked to the peer, adding the channel to the +// router graph, and announcing the channel. The updateChan can be set non-nil +// to get OpenStatusUpdates. // // NOTE: This MUST be run as a goroutine. func (f *fundingManager) advanceFundingState(channel *channeldb.OpenChannel, - updateChan chan<- *lnrpc.OpenStatusUpdate) { + pendingChanID [32]byte, updateChan chan<- *lnrpc.OpenStatusUpdate) { defer f.wg.Done() + // If the channel is still pending we must wait for the funding + // transaction to confirm. + if channel.IsPending { + err := f.advancePendingChannelState(channel, pendingChanID) + if err != nil { + fndgLog.Errorf("Unable to advance pending state of "+ + "ChannelPoint(%v): %v", + channel.FundingOutpoint, err) + return + } + } + // We create the state-machine object which wraps the database state. lnChannel, err := lnwallet.NewLightningChannel( nil, channel, nil, @@ -1014,6 +976,85 @@ func (f *fundingManager) stateStep(channel *channeldb.OpenChannel, return fmt.Errorf("undefined channelState: %v", channelState) } +// advancePendingChannelState waits for a pending channel's funding tx to +// confirm, and marks it open in the database when that happens. +func (f *fundingManager) advancePendingChannelState( + channel *channeldb.OpenChannel, pendingChanID [32]byte) error { + + shortChanID, err := f.waitForFundingWithTimeout(channel) + if err == ErrConfirmationTimeout { + // We'll get a timeout if the number of blocks mined + // since the channel was initiated reaches + // maxWaitNumBlocksFundingConf and we are not the + // channel initiator. + ch := channel + localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis() + closeInfo := &channeldb.ChannelCloseSummary{ + ChainHash: ch.ChainHash, + ChanPoint: ch.FundingOutpoint, + RemotePub: ch.IdentityPub, + Capacity: ch.Capacity, + SettledBalance: localBalance, + CloseType: channeldb.FundingCanceled, + RemoteCurrentRevocation: ch.RemoteCurrentRevocation, + RemoteNextRevocation: ch.RemoteNextRevocation, + LocalChanConfig: ch.LocalChanCfg, + } + + if err := ch.CloseChannel(closeInfo); err != nil { + return fmt.Errorf("failed closing channel "+ + "%v: %v", ch.FundingOutpoint, err) + } + + timeoutErr := fmt.Errorf("timeout waiting for funding tx "+ + "(%v) to confirm", channel.FundingOutpoint) + + // When the peer comes online, we'll notify it that we + // are now considering the channel flow canceled. + f.wg.Add(1) + go func() { + defer f.wg.Done() + + peerChan := make(chan lnpeer.Peer, 1) + var peerKey [33]byte + copy(peerKey[:], ch.IdentityPub.SerializeCompressed()) + + f.cfg.NotifyWhenOnline(peerKey, peerChan) + + var peer lnpeer.Peer + select { + case peer = <-peerChan: + case <-f.quit: + return + } + // TODO(halseth): should this send be made + // reliable? + f.failFundingFlow(peer, pendingChanID, timeoutErr) + }() + + return timeoutErr + + } else if err != nil { + return fmt.Errorf("error waiting for funding "+ + "confirmation for ChannelPoint(%v): %v", + channel.FundingOutpoint, err) + } + + // Success, funding transaction was confirmed. + chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) + fndgLog.Debugf("ChannelID(%v) is now fully confirmed! "+ + "(shortChanID=%v)", chanID, shortChanID) + + err = f.handleFundingConfirmation(channel, *shortChanID) + if err != nil { + return fmt.Errorf("unable to handle funding "+ + "confirmation for ChannelPoint(%v): %v", + channel.FundingOutpoint, err) + } + + return nil +} + // handlePendingChannels responds to a request for details concerning all // currently pending channels waiting for the final phase of the funding // workflow (funding txn confirmation). @@ -1605,40 +1646,7 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) { // transaction in 288 blocks (~ 48 hrs), by canceling the reservation // and canceling the wait for the funding confirmation. f.wg.Add(1) - go func() { - defer f.wg.Done() - - shortChanID, err := f.waitForFundingWithTimeout(completeChan) - if err == ErrConfirmationTimeout { - // We did not see the funding confirmation before - // timeout, so we forget the channel. - err := fmt.Errorf("timeout waiting for funding tx "+ - "(%v) to confirm", completeChan.FundingOutpoint) - fndgLog.Warnf(err.Error()) - f.failFundingFlow(fmsg.peer, pendingChanID, err) - deleteFromDatabase() - return - } else if err != nil { - fndgLog.Errorf(err.Error()) - return - } - - // Success, funding transaction was confirmed. - fndgLog.Debugf("ChannelID(%v) is now fully confirmed! "+ - "(shortChanID=%v)", channelID, shortChanID) - - err = f.handleFundingConfirmation(completeChan, *shortChanID) - if err != nil { - fndgLog.Errorf("unable to handle funding "+ - "confirmation for ChannelPoint(%v): %v", - completeChan.FundingOutpoint, err) - return - } - - f.wg.Add(1) - go f.advanceFundingState(completeChan, nil) - - }() + go f.advanceFundingState(completeChan, pendingChanID, nil) } // processFundingSigned sends a single funding sign complete message along with @@ -1763,33 +1771,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { // At this point we have broadcast the funding transaction and done all // necessary processing. f.wg.Add(1) - go func() { - defer f.wg.Done() - - shortChanID, err := f.waitForFundingWithTimeout(completeChan) - if err != nil { - // Since we are the channel initiator, we don't expect - // to get ErrConfirmationTimeout. - fndgLog.Errorf("Failed waiting for funding "+ - "confirmation for ChannelPoint(%v): %v", - completeChan.FundingOutpoint, err) - return - } - - err = f.handleFundingConfirmation(completeChan, *shortChanID) - if err != nil { - fndgLog.Errorf("unable to handle funding confirmation "+ - "for ChannelPoint(%v): %v", - completeChan.FundingOutpoint, err) - return - } - - fndgLog.Debugf("Channel with ShortChanID %v now confirmed", - shortChanID.ToUint64()) - - f.wg.Add(1) - go f.advanceFundingState(completeChan, resCtx.updates) - }() + go f.advanceFundingState(completeChan, pendingChanID, resCtx.updates) } // waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation and