diff --git a/fundingmanager.go b/fundingmanager.go index b6108437..99161a14 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -969,7 +969,7 @@ func (f *fundingManager) stateStep(channel *channeldb.OpenChannel, func (f *fundingManager) advancePendingChannelState( channel *channeldb.OpenChannel, pendingChanID [32]byte) error { - shortChanID, err := f.waitForFundingWithTimeout(channel) + confChannel, err := f.waitForFundingWithTimeout(channel) if err == ErrConfirmationTimeout { // We'll get a timeout if the number of blocks mined // since the channel was initiated reaches @@ -1031,9 +1031,9 @@ func (f *fundingManager) advancePendingChannelState( // Success, funding transaction was confirmed. chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) fndgLog.Debugf("ChannelID(%v) is now fully confirmed! "+ - "(shortChanID=%v)", chanID, shortChanID) + "(shortChanID=%v)", chanID, confChannel.shortChanID) - err = f.handleFundingConfirmation(channel, *shortChanID) + err = f.handleFundingConfirmation(channel, confChannel) if err != nil { return fmt.Errorf("unable to handle funding "+ "confirmation for ChannelPoint(%v): %v", @@ -1792,15 +1792,28 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { go f.advanceFundingState(completeChan, pendingChanID, resCtx.updates) } +// confirmedChannel wraps a confirmed funding transaction, as well as the short +// channel ID which identifies that channel into a single struct. We'll use +// this to pass around the final state of a channel after it has been +// confirmed. +type confirmedChannel struct { + // shortChanID expresses where in the block the funding transaction was + // located. + shortChanID lnwire.ShortChannelID + + // fundingTx is the funding transaction that created the channel. + fundingTx *wire.MsgTx +} + // waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation and // waitForTimeout that will return ErrConfirmationTimeout if we are not the // channel initiator and the maxWaitNumBlocksFundingConf has passed from the // funding broadcast height. In case of confirmation, the short channel ID of -// the channel will be returned. +// the channel and the funding transaction will be returned. func (f *fundingManager) waitForFundingWithTimeout( - ch *channeldb.OpenChannel) (*lnwire.ShortChannelID, error) { + ch *channeldb.OpenChannel) (*confirmedChannel, error) { - confChan := make(chan *lnwire.ShortChannelID) + confChan := make(chan *confirmedChannel) timeoutChan := make(chan error, 1) cancelChan := make(chan struct{}) @@ -1816,8 +1829,6 @@ func (f *fundingManager) waitForFundingWithTimeout( } defer close(cancelChan) - var shortChanID *lnwire.ShortChannelID - var ok bool select { case err := <-timeoutChan: if err != nil { @@ -1830,12 +1841,12 @@ func (f *fundingManager) waitForFundingWithTimeout( // startup. return nil, ErrFundingManagerShuttingDown - case shortChanID, ok = <-confChan: + case confirmedChannel, ok := <-confChan: if !ok { return nil, fmt.Errorf("waiting for funding" + "confirmation failed") } - return shortChanID, nil + return confirmedChannel, nil } } @@ -1864,7 +1875,7 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { // NOTE: This MUST be run as a goroutine. func (f *fundingManager) waitForFundingConfirmation( completeChan *channeldb.OpenChannel, cancelChan <-chan struct{}, - confChan chan<- *lnwire.ShortChannelID) { + confChan chan<- *confirmedChannel) { defer f.wg.Done() defer close(confChan) @@ -1924,22 +1935,8 @@ func (f *fundingManager) waitForFundingConfirmation( } fundingPoint := completeChan.FundingOutpoint - chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) - if int(fundingPoint.Index) >= len(confDetails.Tx.TxOut) { - fndgLog.Warnf("Funding point index does not exist for "+ - "ChannelPoint(%v)", completeChan.FundingOutpoint) - return - } - - outputAmt := btcutil.Amount(confDetails.Tx.TxOut[fundingPoint.Index].Value) - if outputAmt != completeChan.Capacity { - fndgLog.Warnf("Invalid output value for ChannelPoint(%v)", - completeChan.FundingOutpoint) - return - } - fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)", - fundingPoint, chanID[:]) + fundingPoint, lnwire.NewChanIDFromOutPoint(&fundingPoint)) // With the block height and the transaction index known, we can // construct the compact chanID which is used on the network to unique @@ -1951,7 +1948,10 @@ func (f *fundingManager) waitForFundingConfirmation( } select { - case confChan <- &shortChanID: + case confChan <- &confirmedChannel{ + shortChanID: shortChanID, + fundingTx: confDetails.Tx, + }: case <-f.quit: return } @@ -2022,7 +2022,7 @@ func (f *fundingManager) waitForTimeout(completeChan *channeldb.OpenChannel, // for this channel. func (f *fundingManager) handleFundingConfirmation( completeChan *channeldb.OpenChannel, - shortChanID lnwire.ShortChannelID) error { + confChannel *confirmedChannel) error { fundingPoint := completeChan.FundingOutpoint chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) @@ -2030,13 +2030,24 @@ func (f *fundingManager) handleFundingConfirmation( // TODO(roasbeef): ideally persistent state update for chan above // should be abstracted + // Now that that the channel has been fully confirmed, we'll request + // that the wallet fully verify this channel to ensure that it can be + // used. + err := f.cfg.Wallet.ValidateChannel(completeChan, confChannel.fundingTx) + if err != nil { + // TODO(roasbeef): delete chan state? + return fmt.Errorf("unable to validate channel: %v", err) + } + // The funding transaction now being confirmed, we add this channel to // the fundingManager's internal persistent state machine that we use // to track the remaining process of the channel opening. This is // useful to resume the opening process in case of restarts. We set the // opening state before we mark the channel opened in the database, // such that we can receover from one of the db writes failing. - err := f.saveChannelOpeningState(&fundingPoint, markedOpen, &shortChanID) + err = f.saveChannelOpeningState( + &fundingPoint, markedOpen, &confChannel.shortChanID, + ) if err != nil { return fmt.Errorf("error setting channel state to markedOpen: %v", err) @@ -2044,7 +2055,8 @@ func (f *fundingManager) handleFundingConfirmation( // Now that the channel has been fully confirmed and we successfully // saved the opening state, we'll mark it as open within the database. - if err := completeChan.MarkAsOpen(shortChanID); err != nil { + err = completeChan.MarkAsOpen(confChannel.shortChanID) + if err != nil { return fmt.Errorf("error setting channel pending flag to false: "+ "%v", err) } diff --git a/lnwallet/chanvalidate/validate.go b/lnwallet/chanvalidate/validate.go index 5e02b3e5..213767fd 100644 --- a/lnwallet/chanvalidate/validate.go +++ b/lnwallet/chanvalidate/validate.go @@ -143,7 +143,7 @@ type Context struct { FundingTx *wire.MsgTx // CommitCtx is an optional additional set of validation context - // required to validate a self-owned channel. If present, then fully + // required to validate a self-owned channel. If present, then a full // Script VM validation will be performed. CommitCtx *CommitmentContext } @@ -184,7 +184,7 @@ func Validate(ctx *Context) (*wire.OutPoint, error) { } // If we reach this point, then all other checks have succeeded, so - // we'll now attempt fully Script VM execution to ensure that we're + // we'll now attempt a full Script VM execution to ensure that we're // able to close the channel using this initial state. vm, err := txscript.NewEngine( ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 02321baf..85bf2d60 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -21,6 +21,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet/chanvalidate" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" ) @@ -1636,3 +1637,58 @@ func coinSelectSubtractFees(feeRate SatPerKWeight, amt, return selectedUtxos, outputAmt, changeAmt, nil } + +// ValidateChannel will attempt to fully validate a newly mined channel, given +// its funding transaction and existing channel state. If this method returns +// an error, then the mined channel is invalid, and shouldn't be used. +func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel, + fundingTx *wire.MsgTx) error { + + // First, we'll obtain a fully signed commitment transaction so we can + // pass into it on the chanvalidate package for verification. + channel, err := NewLightningChannel(l.Cfg.Signer, channelState, nil) + if err != nil { + return err + } + signedCommitTx, err := channel.getSignedCommitTx() + if err != nil { + return err + } + + // We'll also need the multi-sig witness script itself so the + // chanvalidate package can check it for correctness against the + // funding transaction, and also commitment validity. + localKey := channelState.LocalChanCfg.MultiSigKey.PubKey + remoteKey := channelState.RemoteChanCfg.MultiSigKey.PubKey + witnessScript, err := input.GenMultiSigScript( + localKey.SerializeCompressed(), + remoteKey.SerializeCompressed(), + ) + if err != nil { + return err + } + pkScript, err := input.WitnessScriptHash(witnessScript) + if err != nil { + return err + } + + // Finally, we'll pass in all the necessary context needed to fully + // validate that this channel is indeed what we expect, and can be + // used. + _, err = chanvalidate.Validate(&chanvalidate.Context{ + Locator: &chanvalidate.OutPointChanLocator{ + ChanPoint: channelState.FundingOutpoint, + }, + MultiSigPkScript: pkScript, + FundingTx: fundingTx, + CommitCtx: &chanvalidate.CommitmentContext{ + Value: channel.Capacity, + FullySignedCommitTx: signedCommitTx, + }, + }) + if err != nil { + return err + } + + return nil +}