lnwallet: update Channel[Reservation+Contribution] for new funding flow

This commit updates the channel reservation workflow in order to
properly implement the new funding workflow defined in BOLT-0002.

The workflow itself hasn’t changed significantly, but the contents of
the contributions of both sides have. The bulk of the fields within the
contribution of both sides has been boiled down into a pointer to the
ChannelConfig which houses all the data required to handle all states
of the channel, and commitment state machine.

For the two portions which are dictated by the other party, we now add
builder-like modifiers to allow specifying the constraints after the
initial portion of the workflow.
This commit is contained in:
Olaoluwa Osuntokun 2017-07-29 18:58:55 -07:00
parent e930af4b43
commit c63c7cd22f
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -6,16 +6,17 @@ import (
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
) )
// ChannelContribution is the primary constituent of the funding workflow within // ChannelContribution is the primary constituent of the funding workflow
// lnwallet. Each side first exchanges their respective contributions along with // within lnwallet. Each side first exchanges their respective contributions
// channel specific parameters like the min fee/KB. Once contributions have been // along with channel specific parameters like the min fee/KB. Once
// exchanged, each side will then produce signatures for all their inputs to the // contributions have been exchanged, each side will then produce signatures
// funding transactions, and finally a signature for the other party's version // for all their inputs to the funding transactions, and finally a signature
// of the commitment transaction. // for the other party's version of the commitment transaction.
type ChannelContribution struct { type ChannelContribution struct {
// FundingOutpoint is the amount of funds contributed to the funding // FundingOutpoint is the amount of funds contributed to the funding
// transaction. // transaction.
@ -29,31 +30,22 @@ type ChannelContribution struct {
// channel capacity. // channel capacity.
ChangeOutputs []*wire.TxOut ChangeOutputs []*wire.TxOut
// MultiSigKey is the the key to be used for the funding transaction's // FirstCommitmentPoint is the first commitment point that will be used
// P2SH multi-sig 2-of-2 output. // to create the revocation key in the first commitment transaction we
// TODO(roasbeef): replace with CDP // send to the remote party.
MultiSigKey *btcec.PublicKey FirstCommitmentPoint *btcec.PublicKey
// CommitKey is the key to be used for this party's version of the // ChannelConfig is the concrete contribution that this node is
// commitment transaction. // offering to the channel. This includes all the various constraints
CommitKey *btcec.PublicKey // such as the min HTLC, and also all the keys which will be used for
// the duration of the channel.
*channeldb.ChannelConfig
}
// DeliveryAddress is the address to be used for delivery of cleared // toChanConfig returns the raw channel configuration generated by a node's
// channel funds in the scenario of a cooperative channel closure. // contribution to the channel.
DeliveryAddress btcutil.Address func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
return *c.ChannelConfig
// RevocationKey is the key to be used in the revocation clause for the
// initial version of this party's commitment transaction.
RevocationKey *btcec.PublicKey
// DustLimit is the threshold below which no HTLC output should be
// generated for this party. HTLCs below this amount are not
// enforceable on-chain from this party's point of view.
DustLimit btcutil.Amount
// CsvDelay The delay (in blocks) to be used for the pay-to-self output
// in this party's version of the commitment transaction.
CsvDelay uint32
} }
// InputScript represents any script inputs required to redeem a previous // InputScript represents any script inputs required to redeem a previous
@ -65,8 +57,8 @@ type InputScript struct {
} }
// ChannelReservation represents an intent to open a lightning payment channel // ChannelReservation represents an intent to open a lightning payment channel
// a counterparty. The funding processes from reservation to channel opening // a counterparty. The funding processes from reservation to channel opening is
// is a 3-step process. In order to allow for full concurrency during the // a 3-step process. In order to allow for full concurrency during the
// reservation workflow, resources consumed by a contribution are "locked" // reservation workflow, resources consumed by a contribution are "locked"
// themselves. This prevents a number of race conditions such as two funding // themselves. This prevents a number of race conditions such as two funding
// transactions double-spending the same input. A reservation can also be // transactions double-spending the same input. A reservation can also be
@ -122,10 +114,6 @@ type ChannelReservation struct {
// throughout its lifetime. // throughout its lifetime.
reservationID uint64 reservationID uint64
// numConfsToOpen is the number of confirmations required before the
// channel should be considered open.
numConfsToOpen uint16
// pushSat the amount of satoshis that should be pushed to the // pushSat the amount of satoshis that should be pushed to the
// responder of a single funding channel as part of the initial // responder of a single funding channel as part of the initial
// commitment state. // commitment state.
@ -145,9 +133,9 @@ type ChannelReservation struct {
// used only internally by lnwallet. In order to concurrent safety, the // used only internally by lnwallet. In order to concurrent safety, the
// creation of all channel reservations should be carried out via the // creation of all channel reservations should be carried out via the
// lnwallet.InitChannelReservation interface. // lnwallet.InitChannelReservation interface.
func NewChannelReservation(capacity, fundingAmt btcutil.Amount, func NewChannelReservation(capacity, fundingAmt, feePerKw btcutil.Amount,
feePerKw btcutil.Amount, wallet *LightningWallet, id uint64, wallet *LightningWallet, id uint64, pushSat btcutil.Amount,
numConfs uint16, pushSat btcutil.Amount) *ChannelReservation { chainHash *chainhash.Hash) *ChannelReservation {
var ( var (
ourBalance btcutil.Amount ourBalance btcutil.Amount
@ -206,38 +194,66 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount,
return &ChannelReservation{ return &ChannelReservation{
ourContribution: &ChannelContribution{ ourContribution: &ChannelContribution{
FundingAmount: ourBalance, FundingAmount: ourBalance,
ChannelConfig: &channeldb.ChannelConfig{},
}, },
theirContribution: &ChannelContribution{ theirContribution: &ChannelContribution{
FundingAmount: theirBalance, FundingAmount: theirBalance,
ChannelConfig: &channeldb.ChannelConfig{},
}, },
partialState: &channeldb.OpenChannel{ partialState: &channeldb.OpenChannel{
Capacity: capacity, ChainHash: *chainHash,
IsInitiator: initiator, Db: wallet.Cfg.Database,
IsPending: true, Capacity: capacity,
ChanType: chanType, IsInitiator: initiator,
OurBalance: ourBalance, IsPending: true,
TheirBalance: theirBalance, ChanType: chanType,
FeePerKw: feePerKw, LocalBalance: ourBalance,
Db: wallet.ChannelDB, RemoteBalance: theirBalance,
CommitFee: commitFee, FeePerKw: feePerKw,
CommitFee: commitFee,
}, },
numConfsToOpen: numConfs, pushSat: pushSat,
pushSat: pushSat, reservationID: id,
reservationID: id, chanOpen: make(chan *openChanDetails, 1),
chanOpen: make(chan *openChanDetails, 1), chanOpenErr: make(chan error, 1),
chanOpenErr: make(chan error, 1), wallet: wallet,
wallet: wallet,
} }
} }
// SetNumConfsRequired sets the number of confirmations that are required for
// the ultimate funding transaction before the channel can be considered open.
// This is distinct from the main reservation workflow as it allows
// implementations a bit more flexibility w.r.t to if the responder of the
// initiator sets decides the number of confirmations needed.
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
r.Lock()
defer r.Unlock()
r.partialState.NumConfsRequired = numConfs
}
// RequireLocalDelay sets the mandatory CSV delay that MUST be used when
// creating the local commitment transaction. This is distinct from the normal
// reservation workflow as the remote party will dictate this value for us.
//
// TODO(roasbeef): abstract out dictation of params remote side gives
func (r *ChannelReservation) RequireLocalDelay(csvDelay uint16) {
r.Lock()
defer r.Unlock()
r.ourContribution.ChannelConfig.CsvDelay = csvDelay
}
// OurContribution returns the wallet's fully populated contribution to the // OurContribution returns the wallet's fully populated contribution to the
// pending payment channel. See 'ChannelContribution' for further details // pending payment channel. See 'ChannelContribution' for further details
// regarding the contents of a contribution. // regarding the contents of a contribution.
//
// NOTE: This SHOULD NOT be modified. // NOTE: This SHOULD NOT be modified.
// TODO(roasbeef): make copy? // TODO(roasbeef): make copy?
func (r *ChannelReservation) OurContribution() *ChannelContribution { func (r *ChannelReservation) OurContribution() *ChannelContribution {
r.RLock() r.RLock()
defer r.RUnlock() defer r.RUnlock()
return r.ourContribution return r.ourContribution
} }
@ -277,9 +293,10 @@ func (r *ChannelReservation) ProcessSingleContribution(theirContribution *Channe
} }
// TheirContribution returns the counterparty's pending contribution to the // TheirContribution returns the counterparty's pending contribution to the
// payment channel. See 'ChannelContribution' for further details regarding // payment channel. See 'ChannelContribution' for further details regarding the
// the contents of a contribution. This attribute will ONLY be available // contents of a contribution. This attribute will ONLY be available after a
// after a call to .ProcesContribution(). // call to .ProcesContribution().
//
// NOTE: This SHOULD NOT be modified. // NOTE: This SHOULD NOT be modified.
func (r *ChannelReservation) TheirContribution() *ChannelContribution { func (r *ChannelReservation) TheirContribution() *ChannelContribution {
r.RLock() r.RLock()
@ -292,6 +309,7 @@ func (r *ChannelReservation) TheirContribution() *ChannelContribution {
// version of the commitment transaction. The signatures for the wallet's // version of the commitment transaction. The signatures for the wallet's
// inputs to the funding transaction are returned in sorted order according to // inputs to the funding transaction are returned in sorted order according to
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki. // BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
//
// NOTE: These signatures will only be populated after a call to // NOTE: These signatures will only be populated after a call to
// .ProcesContribution() // .ProcesContribution()
func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) { func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) {
@ -300,18 +318,19 @@ func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) {
return r.ourFundingInputScripts, r.ourCommitmentSig return r.ourFundingInputScripts, r.ourCommitmentSig
} }
// CompleteReservation finalizes the pending channel reservation, // CompleteReservation finalizes the pending channel reservation, transitioning
// transitioning from a pending payment channel, to an open payment // from a pending payment channel, to an open payment channel. All passed
// channel. All passed signatures to the counterparty's inputs to the funding // signatures to the counterparty's inputs to the funding transaction will be
// transaction will be fully verified. Signatures are expected to be passed in // fully verified. Signatures are expected to be passed in sorted order
// sorted order according to BIP-69: // according to BIP-69:
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki. Additionally, // https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
// verification is performed in order to ensure that the counterparty supplied // Additionally, verification is performed in order to ensure that the
// a valid signature to our version of the commitment transaction. // counterparty supplied a valid signature to our version of the commitment
// Once this method returns, caller's should then call .WaitForChannelOpen() // transaction. Once this method returns, caller's should then call
// which will block until the funding transaction obtains the configured number // .WaitForChannelOpen() which will block until the funding transaction obtains
// of confirmations. Once the method unblocks, a LightningChannel instance is // the configured number of confirmations. Once the method unblocks, a
// returned, marking the channel available for updates. // LightningChannel instance is returned, marking the channel available for
// updates.
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript, func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript,
commitmentSig []byte) (*channeldb.OpenChannel, error) { commitmentSig []byte) (*channeldb.OpenChannel, error) {
@ -331,27 +350,24 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScr
} }
// CompleteReservationSingle finalizes the pending single funder channel // CompleteReservationSingle finalizes the pending single funder channel
// reservation. Using the funding outpoint of the constructed funding transaction, // reservation. Using the funding outpoint of the constructed funding
// and the initiator's signature for our version of the commitment transaction, // transaction, and the initiator's signature for our version of the commitment
// we are able to verify the correctness of our committment transaction as // transaction, we are able to verify the correctness of our commitment
// crafted by the initiator. Once this method returns, our signature for the // transaction as crafted by the initiator. Once this method returns, our
// initiator's version of the commitment transaction is available via // signature for the initiator's version of the commitment transaction is
// the .OurSignatures() method. As this method should only be called as a // available via the .OurSignatures() method. As this method should only be
// response to a single funder channel, only a commitment signature will be // called as a response to a single funder channel, only a commitment signature
// populated. // will be populated.
func (r *ChannelReservation) CompleteReservationSingle( func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
revocationKey *btcec.PublicKey, fundingPoint *wire.OutPoint, commitSig []byte) (*channeldb.OpenChannel, error) {
commitSig []byte, obsfucator [StateHintSize]byte) (*channeldb.OpenChannel, error) {
errChan := make(chan error, 1) errChan := make(chan error, 1)
completeChan := make(chan *channeldb.OpenChannel, 1) completeChan := make(chan *channeldb.OpenChannel, 1)
r.wallet.msgChan <- &addSingleFunderSigsMsg{ r.wallet.msgChan <- &addSingleFunderSigsMsg{
pendingFundingID: r.reservationID, pendingFundingID: r.reservationID,
revokeKey: revocationKey,
fundingOutpoint: fundingPoint, fundingOutpoint: fundingPoint,
theirCommitmentSig: commitSig, theirCommitmentSig: commitSig,
obsfucator: obsfucator,
completeChan: completeChan, completeChan: completeChan,
err: errChan, err: errChan,
} }
@ -363,6 +379,7 @@ func (r *ChannelReservation) CompleteReservationSingle(
// funding transaction belonging to them, as well as their signature for the // funding transaction belonging to them, as well as their signature for the
// wallet's version of the commitment transaction. This methods is provided for // wallet's version of the commitment transaction. This methods is provided for
// additional verification, such as needed by tests. // additional verification, such as needed by tests.
//
// NOTE: These attributes will be unpopulated before a call to // NOTE: These attributes will be unpopulated before a call to
// .CompleteReservation(). // .CompleteReservation().
func (r *ChannelReservation) TheirSignatures() ([]*InputScript, []byte) { func (r *ChannelReservation) TheirSignatures() ([]*InputScript, []byte) {
@ -383,34 +400,6 @@ func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
return r.fundingTx return r.fundingTx
} }
// FundingRedeemScript returns the fully populated funding redeem script.
//
// NOTE: This method will only return a non-nil value after either
// ProcesContribution or ProcessSingleContribution have been executed and
// returned without error.
func (r *ChannelReservation) FundingRedeemScript() []byte {
r.RLock()
defer r.RUnlock()
return r.partialState.FundingWitnessScript
}
// LocalCommitTx returns the commitment transaction for the local node involved
// in this funding reservation.
func (r *ChannelReservation) LocalCommitTx() *wire.MsgTx {
r.RLock()
defer r.RUnlock()
return r.partialState.OurCommitTx
}
// SetTheirDustLimit set dust limit of the remote party.
func (r *ChannelReservation) SetTheirDustLimit(dustLimit btcutil.Amount) {
r.Lock()
defer r.Unlock()
r.partialState.TheirDustLimit = dustLimit
}
// FundingOutpoint returns the outpoint of the funding transaction. // FundingOutpoint returns the outpoint of the funding transaction.
// //
// NOTE: The pointer returned will only be set once the .ProcesContribution() // NOTE: The pointer returned will only be set once the .ProcesContribution()
@ -420,20 +409,7 @@ func (r *ChannelReservation) SetTheirDustLimit(dustLimit btcutil.Amount) {
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint { func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
r.RLock() r.RLock()
defer r.RUnlock() defer r.RUnlock()
return r.partialState.FundingOutpoint return &r.partialState.FundingOutpoint
}
// StateNumObfuscator returns the bytes to be used to obsfucate the state
// number hints for all future states of the commitment transaction for this
// workflow.
//
// NOTE: This value will only be available for a single funder workflow after
// the CompleteReservation or CompleteReservationSingle methods have been
// successfully executed.
func (r *ChannelReservation) StateNumObfuscator() [StateHintSize]byte {
r.RLock()
defer r.RUnlock()
return r.partialState.StateHintObsfucator
} }
// Cancel abandons this channel reservation. This method should be called in // Cancel abandons this channel reservation. This method should be called in
@ -459,8 +435,8 @@ type OpenChannelDetails struct {
// ChannelReservation and the required funding workflow. // ChannelReservation and the required funding workflow.
Channel *LightningChannel Channel *LightningChannel
// ConfirmationHeight is the block height within the chain that included // ConfirmationHeight is the block height within the chain that
// the channel. // included the channel.
ConfirmationHeight uint32 ConfirmationHeight uint32
// TransactionIndex is the index within the confirming block that the // TransactionIndex is the index within the confirming block that the