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 2019-09-30 19:59:10 -07:00
parent 6ebada112f
commit 0e13c5ac3f
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465
3 changed files with 100 additions and 32 deletions

View File

@ -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)
}

View File

@ -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,

View File

@ -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
}