fundingmanager: make waitForFundingTimeout sync

This commit makes the waitForFundingTimeout method synchronous, and
return ErrConfirmationTimeout in case the timeout is reached.

We also simplify the internals by using waitForTimout defined earlier.
This commit is contained in:
Johan T. Halseth 2018-09-16 10:40:10 +02:00
parent 47fae26dc4
commit 893c6cbc59
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26

@ -85,6 +85,12 @@ var (
// been signaled to shut down. // been signaled to shut down.
ErrFundingManagerShuttingDown = errors.New("funding manager shutting " + ErrFundingManagerShuttingDown = errors.New("funding manager shutting " +
"down") "down")
// ErrConfirmationTimeout is an error returned when we as a responder
// are waiting for a funding transaction to confirm, but too many
// blocks pass without confirmation.
ErrConfirmationTimeout = errors.New("timeout waiting for funding " +
"confirmation")
) )
// reservationWithCtx encapsulates a pending channel reservation. This wrapper // reservationWithCtx encapsulates a pending channel reservation. This wrapper
@ -519,20 +525,20 @@ func (f *fundingManager) start() error {
} }
} }
confChan := make(chan *lnwire.ShortChannelID) f.wg.Add(1)
timeoutChan := make(chan struct{})
go func(ch *channeldb.OpenChannel) { go func(ch *channeldb.OpenChannel) {
go f.waitForFundingWithTimeout(ch, confChan, timeoutChan) defer f.wg.Done()
var shortChanID *lnwire.ShortChannelID shortChanID, err := f.waitForFundingWithTimeout(ch)
var ok bool if err == ErrConfirmationTimeout {
select { fndgLog.Warnf("Timeout waiting for funding "+
case <-timeoutChan: "confirmation of ChannelPoint(%v)",
// Timeout channel will be triggered if the number of blocks ch.FundingOutpoint)
// mined since the channel was initiated reaches
// maxWaitNumBlocksFundingConf and we are not the channel // We'll get a timeout if the number of blocks
// initiator. // mined since the channel was initiated
// reaches maxWaitNumBlocksFundingConf and we
// are not the channel initiator.
localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis() localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis()
closeInfo := &channeldb.ChannelCloseSummary{ closeInfo := &channeldb.ChannelCloseSummary{
ChainHash: ch.ChainHash, ChainHash: ch.ChainHash,
@ -552,19 +558,11 @@ func (f *fundingManager) start() error {
return return
} }
return return
} else if err != nil {
case <-f.quit: fndgLog.Errorf("Failed waiting for funding "+
// The fundingManager is shutting down, and will "confirmation for ChannelPoint(%v): %v",
// resume wait on startup. ch.FundingOutpoint, err)
return return
case shortChanID, ok = <-confChan:
if !ok {
fndgLog.Errorf("Waiting for funding" +
"confirmation failed")
return
}
// Fallthrough.
} }
// Success, funding transaction was confirmed. // Success, funding transaction was confirmed.
@ -1609,15 +1607,9 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
f.wg.Add(1) f.wg.Add(1)
go func() { go func() {
defer f.wg.Done() defer f.wg.Done()
confChan := make(chan *lnwire.ShortChannelID)
timeoutChan := make(chan struct{})
go f.waitForFundingWithTimeout(completeChan, confChan,
timeoutChan)
var shortChanID *lnwire.ShortChannelID shortChanID, err := f.waitForFundingWithTimeout(completeChan)
var ok bool if err == ErrConfirmationTimeout {
select {
case <-timeoutChan:
// We did not see the funding confirmation before // We did not see the funding confirmation before
// timeout, so we forget the channel. // timeout, so we forget the channel.
err := fmt.Errorf("timeout waiting for funding tx "+ err := fmt.Errorf("timeout waiting for funding tx "+
@ -1626,17 +1618,9 @@ func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
f.failFundingFlow(fmsg.peer, pendingChanID, err) f.failFundingFlow(fmsg.peer, pendingChanID, err)
deleteFromDatabase() deleteFromDatabase()
return return
case <-f.quit: } else if err != nil {
// The fundingManager is shutting down, will resume fndgLog.Errorf(err.Error())
// wait for funding transaction on startup.
return return
case shortChanID, ok = <-confChan:
if !ok {
fndgLog.Errorf("waiting for funding confirmation" +
" failed")
return
}
// Fallthrough.
} }
// Success, funding transaction was confirmed. // Success, funding transaction was confirmed.
@ -1781,28 +1765,15 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
f.wg.Add(1) f.wg.Add(1)
go func() { go func() {
defer f.wg.Done() defer f.wg.Done()
confChan := make(chan *lnwire.ShortChannelID)
cancelChan := make(chan struct{})
// In case the fundingManager is stopped at some point during shortChanID, err := f.waitForFundingWithTimeout(completeChan)
// the remaining part of the opening process, we must wait for if err != nil {
// this process to finish (either successfully or with some // Since we are the channel initiator, we don't expect
// error), before the fundingManager can be shut down. // to get ErrConfirmationTimeout.
f.wg.Add(1) fndgLog.Errorf("Failed waiting for funding "+
go f.waitForFundingConfirmation(completeChan, cancelChan, "confirmation for ChannelPoint(%v): %v",
confChan) completeChan.FundingOutpoint, err)
var shortChanID *lnwire.ShortChannelID
var ok bool
select {
case <-f.quit:
return return
case shortChanID, ok = <-confChan:
if !ok {
fndgLog.Errorf("waiting for funding " +
"confirmation failed")
return
}
} }
err = f.handleFundingConfirmation(completeChan, *shortChanID) err = f.handleFundingConfirmation(completeChan, *shortChanID)
@ -1821,82 +1792,50 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
}() }()
} }
// waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation that // waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation and
// will cancel the wait for confirmation if we are not the channel initiator and // waitForTimeout that will return ErrConfirmationTimeout if we are not the
// the maxWaitNumBlocksFundingConf has passed from bestHeight. // channel initiator and the maxWaitNumBlocksFundingConf has passed from the
// In the case of timeout, the timeoutChan will be closed. In case of error, // funding broadcast height. In case of confirmation, the short channel ID of
// confChan will be closed. In case of success, a *lnwire.ShortChannelID will be // the channel will be returned.
// passed to confChan. func (f *fundingManager) waitForFundingWithTimeout(
func (f *fundingManager) waitForFundingWithTimeout(completeChan *channeldb.OpenChannel, ch *channeldb.OpenChannel) (*lnwire.ShortChannelID, error) {
confChan chan<- *lnwire.ShortChannelID, timeoutChan chan<- struct{}) {
epochClient, err := f.cfg.Notifier.RegisterBlockEpochNtfn(nil) confChan := make(chan *lnwire.ShortChannelID)
if err != nil { timeoutChan := make(chan error, 1)
fndgLog.Errorf("unable to register for epoch notification: %v",
err)
close(confChan)
return
}
defer epochClient.Cancel()
waitingConfChan := make(chan *lnwire.ShortChannelID)
cancelChan := make(chan struct{}) cancelChan := make(chan struct{})
// Add this goroutine to wait group so we can be sure that it is
// properly stopped before the funding manager can be shut down.
f.wg.Add(1) f.wg.Add(1)
go f.waitForFundingConfirmation(completeChan, cancelChan, go f.waitForFundingConfirmation(ch, cancelChan, confChan)
waitingConfChan)
// On block maxHeight we will cancel the funding confirmation wait. // If we are not the initiator, we have no money at stake and will
maxHeight := completeChan.FundingBroadcastHeight + maxWaitNumBlocksFundingConf // timeout waiting for the funding transaction to confirm after a
for { // while.
select { if !ch.IsInitiator {
case epoch, ok := <-epochClient.Epochs: f.wg.Add(1)
if !ok { go f.waitForTimeout(ch, cancelChan, timeoutChan)
fndgLog.Warnf("Epoch client shutting down") }
return defer close(cancelChan)
}
// If we are not the channel initiator it's safe var shortChanID *lnwire.ShortChannelID
// to timeout the channel var ok bool
if uint32(epoch.Height) >= maxHeight && !completeChan.IsInitiator { select {
fndgLog.Warnf("waited for %v blocks without "+ case err := <-timeoutChan:
"seeing funding transaction confirmed,"+ if err != nil {
" cancelling.", maxWaitNumBlocksFundingConf) return nil, err
// Cancel the waitForFundingConfirmation
// goroutine.
close(cancelChan)
// Notify the caller of the timeout.
close(timeoutChan)
return
}
// TODO: If we are the channel initiator implement
// a method for recovering the funds from the funding
// transaction
case <-f.quit:
// The fundingManager is shutting down, will resume
// waiting for the funding transaction on startup.
return
case shortChanID, ok := <-waitingConfChan:
if !ok {
// Failed waiting for confirmation, close
// confChan to indicate failure.
close(confChan)
return
}
select {
case confChan <- shortChanID:
case <-f.quit:
return
}
} }
return nil, ErrConfirmationTimeout
case <-f.quit:
// The fundingManager is shutting down, and will resume wait on
// startup.
return nil, ErrFundingManagerShuttingDown
case shortChanID, ok = <-confChan:
if !ok {
return nil, fmt.Errorf("waiting for funding" +
"confirmation failed")
}
return shortChanID, nil
} }
} }