lnwallet+funding: expose new ValidateChannel method for 1st party validation

In this commit, we use the recently added `chanvalidate` package to
verify channels once they have been confirmed in the funding manager. We
expose a new method on the `LightningWallet` struct: `ValidateChannels`
which calls the new shared 1st party verification code.

After the channel is fully confirmed in the funding manager, we'll now
use this newly exposed method to handle all validation. As a result, we
can remove the existing validation code in the funding manager, and rely
on the new code in isolation.
This commit is contained in:
Olaoluwa Osuntokun committed 2019-10-03 16:23:23 -07:00
1 parent 6ebada112f
commit 0e13c5ac3f
3 files changed
+100 -32

No files matched your search

+42 -30
View File
@@ -969,7 +969,7 @@ func (f *fundingManager) stateStep(channel *channeldb.OpenChannel,
func (f *fundingManager) advancePendingChannelState( func (f *fundingManager) advancePendingChannelState(
channel *channeldb.OpenChannel, pendingChanID [32]byte) error { channel *channeldb.OpenChannel, pendingChanID [32]byte) error {
shortChanID, err := f.waitForFundingWithTimeout(channel) confChannel, err := f.waitForFundingWithTimeout(channel)
if err == ErrConfirmationTimeout { if err == ErrConfirmationTimeout {
// We'll get a timeout if the number of blocks mined // We'll get a timeout if the number of blocks mined
// since the channel was initiated reaches // since the channel was initiated reaches
@@ -1031,9 +1031,9 @@ func (f *fundingManager) advancePendingChannelState(
// Success, funding transaction was confirmed. // Success, funding transaction was confirmed.
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint)
fndgLog.Debugf("ChannelID(%v) is now fully confirmed! "+ 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 { if err != nil {
return fmt.Errorf("unable to handle funding "+ return fmt.Errorf("unable to handle funding "+
"confirmation for ChannelPoint(%v): %v", "confirmation for ChannelPoint(%v): %v",
@@ -1792,15 +1792,28 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
go f.advanceFundingState(completeChan, pendingChanID, resCtx.updates) 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 // waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation and
// waitForTimeout that will return ErrConfirmationTimeout if we are not the // waitForTimeout that will return ErrConfirmationTimeout if we are not the
// channel initiator and the maxWaitNumBlocksFundingConf has passed from the // channel initiator and the maxWaitNumBlocksFundingConf has passed from the
// funding broadcast height. In case of confirmation, the short channel ID of // 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( 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) timeoutChan := make(chan error, 1)
cancelChan := make(chan struct{}) cancelChan := make(chan struct{})
@@ -1816,8 +1829,6 @@ func (f *fundingManager) waitForFundingWithTimeout(
} }
defer close(cancelChan) defer close(cancelChan)
var shortChanID *lnwire.ShortChannelID
var ok bool
select { select {
case err := <-timeoutChan: case err := <-timeoutChan:
if err != nil { if err != nil {
@@ -1830,12 +1841,12 @@ func (f *fundingManager) waitForFundingWithTimeout(
// startup. // startup.
return nil, ErrFundingManagerShuttingDown return nil, ErrFundingManagerShuttingDown
case shortChanID, ok = <-confChan: case confirmedChannel, ok := <-confChan:
if !ok { if !ok {
return nil, fmt.Errorf("waiting for funding" + return nil, fmt.Errorf("waiting for funding" +
"confirmation failed") "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. // NOTE: This MUST be run as a goroutine.
func (f *fundingManager) waitForFundingConfirmation( func (f *fundingManager) waitForFundingConfirmation(
completeChan *channeldb.OpenChannel, cancelChan <-chan struct{}, completeChan *channeldb.OpenChannel, cancelChan <-chan struct{},
confChan chan<- *lnwire.ShortChannelID) { confChan chan<- *confirmedChannel) {
defer f.wg.Done() defer f.wg.Done()
defer close(confChan) defer close(confChan)
@@ -1924,22 +1935,8 @@ func (f *fundingManager) waitForFundingConfirmation(
} }
fundingPoint := completeChan.FundingOutpoint 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)", 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 // With the block height and the transaction index known, we can
// construct the compact chanID which is used on the network to unique // construct the compact chanID which is used on the network to unique
@@ -1951,7 +1948,10 @@ func (f *fundingManager) waitForFundingConfirmation(
} }
select { select {
case confChan <- &shortChanID: case confChan <- &confirmedChannel{
shortChanID: shortChanID,
fundingTx: confDetails.Tx,
}:
case <-f.quit: case <-f.quit:
return return
} }
@@ -2022,7 +2022,7 @@ func (f *fundingManager) waitForTimeout(completeChan *channeldb.OpenChannel,
// for this channel. // for this channel.
func (f *fundingManager) handleFundingConfirmation( func (f *fundingManager) handleFundingConfirmation(
completeChan *channeldb.OpenChannel, completeChan *channeldb.OpenChannel,
shortChanID lnwire.ShortChannelID) error { confChannel *confirmedChannel) error {
fundingPoint := completeChan.FundingOutpoint fundingPoint := completeChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint)
@@ -2030,13 +2030,24 @@ func (f *fundingManager) handleFundingConfirmation(
// TODO(roasbeef): ideally persistent state update for chan above // TODO(roasbeef): ideally persistent state update for chan above
// should be abstracted // 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 funding transaction now being confirmed, we add this channel to
// the fundingManager's internal persistent state machine that we use // the fundingManager's internal persistent state machine that we use
// to track the remaining process of the channel opening. This is // to track the remaining process of the channel opening. This is
// useful to resume the opening process in case of restarts. We set the // useful to resume the opening process in case of restarts. We set the
// opening state before we mark the channel opened in the database, // opening state before we mark the channel opened in the database,
// such that we can receover from one of the db writes failing. // 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 { if err != nil {
return fmt.Errorf("error setting channel state to markedOpen: %v", return fmt.Errorf("error setting channel state to markedOpen: %v",
err) err)
@@ -2044,7 +2055,8 @@ func (f *fundingManager) handleFundingConfirmation(
// Now that the channel has been fully confirmed and we successfully // Now that the channel has been fully confirmed and we successfully
// saved the opening state, we'll mark it as open within the database. // 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: "+ return fmt.Errorf("error setting channel pending flag to false: "+
"%v", err) "%v", err)
} }
+2 -2
View File
@@ -143,7 +143,7 @@ type Context struct {
FundingTx *wire.MsgTx FundingTx *wire.MsgTx
// CommitCtx is an optional additional set of validation context // 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. // Script VM validation will be performed.
CommitCtx *CommitmentContext 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 // 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. // able to close the channel using this initial state.
vm, err := txscript.NewEngine( vm, err := txscript.NewEngine(
ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx, ctx.MultiSigPkScript, ctx.CommitCtx.FullySignedCommitTx,
+56
View File
@@ -21,6 +21,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet/chanvalidate"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/shachain" "github.com/lightningnetwork/lnd/shachain"
) )
@@ -1636,3 +1637,58 @@ func coinSelectSubtractFees(feeRate SatPerKWeight, amt,
return selectedUtxos, outputAmt, changeAmt, nil 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
}