lnwallet: add single funder workflow to ChannelReservation

This commit adds 3 methods to lnwallet.ChannelReservation intended to
facilitating a single funder channel workflow between two nodes. A
single funder workflow is characterized as the initiator committing all
the funds to a channel, with the responder only providing public keys,
and a revocation hash.

The workflow remains the same for the initiator of the funding
transaction, however for the responder, the following methods are
instead called in order:
  * .ProcessSingleConribution()
  * .CompleteSingleContribution()
  * .FinalizeReservation()

These methods are required for the responder as they are never able to
construct the full funding transaction, and only receive the out point
of the funding transaction once available.
This commit is contained in:
Olaoluwa Osuntokun 2016-06-20 22:31:57 -07:00
parent 45236fa092
commit e62fc414cb
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -4,7 +4,6 @@ import (
"sync" "sync"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
@ -24,7 +23,7 @@ type ChannelContribution struct {
Inputs []*wire.TxIn Inputs []*wire.TxIn
// Outputs to be used in the case that the total value of the fund // Outputs to be used in the case that the total value of the fund
// ing inputs is greather than the total potential channel capacity. // ing inputs is greater than the total potential channel capacity.
ChangeOutputs []*wire.TxOut ChangeOutputs []*wire.TxOut
// The key to be used for the funding transaction's P2SH multi-sig // The key to be used for the funding transaction's P2SH multi-sig
@ -33,6 +32,7 @@ type ChannelContribution struct {
// The key to be used for this party's version of the commitment // The key to be used for this party's version of the commitment
// transaction. // transaction.
// TODO(roasbeef): replace with CDP
CommitKey *btcec.PublicKey CommitKey *btcec.PublicKey
// Address to be used for delivery of cleared channel funds in the scenario // Address to be used for delivery of cleared channel funds in the scenario
@ -73,24 +73,28 @@ type InputScript struct {
// accessible via the .OurContribution() method. This contribution // accessible via the .OurContribution() method. This contribution
// contains the neccessary items to allow the remote party to build both // contains the neccessary items to allow the remote party to build both
// the funding, and commitment transactions. // the funding, and commitment transactions.
// 2. ChannelReservation.ProcessContribution // 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
// * The counterparty presents their contribution to the payment channel. // * The counterparty presents their contribution to the payment channel.
// This allows us to build the funding, and commitment transactions // This allows us to build the funding, and commitment transactions
// ourselves. // ourselves.
// * We're now able to sign our inputs to the funding transactions, and // * We're now able to sign our inputs to the funding transactions, and
// the counterparty's version of the commitment transaction. // the counterparty's version of the commitment transaction.
// * All signatures crafted by us, are now available via .OurSignatures(). // * All signatures crafted by us, are now available via .OurSignatures().
// 3. ChannelReservation.CompleteReservation // 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
// * The final step in the workflow. The counterparty presents the // * The final step in the workflow. The counterparty presents the
// signatures for all their inputs to the funding transation, as well // signatures for all their inputs to the funding transation, as well
// as a signature to our version of the commitment transaction. // as a signature to our version of the commitment transaction.
// * We then verify the validity of all signatures before considering the // * We then verify the validity of all signatures before considering the
// channel "open". // channel "open".
// TODO(roasbeef): update with single funder description
type ChannelReservation struct { type ChannelReservation struct {
// This mutex MUST be held when either reading or modifying any of the // This mutex MUST be held when either reading or modifying any of the
// fields below. // fields below.
sync.RWMutex sync.RWMutex
// fundingTx is the funding transaction for this pending channel.
fundingTx *wire.MsgTx
// For CLTV it is nLockTime, for CSV it's nSequence, for segwit it's // For CLTV it is nLockTime, for CSV it's nSequence, for segwit it's
// not needed // not needed
fundingLockTime uint32 fundingLockTime uint32
@ -113,6 +117,10 @@ 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
// A channel which will be sent on once the channel is considered // A channel which will be sent on once the channel is considered
// 'open'. A channel is open once the funding transaction has reached // 'open'. A channel is open once the funding transaction has reached
// a sufficient number of confirmations. // a sufficient number of confirmations.
@ -125,26 +133,37 @@ type ChannelReservation struct {
// used only internally by lnwallet. In order to concurrent safety, the creation // used only internally by lnwallet. In order to concurrent safety, the creation
// of all channel reservations should be carried out via the // of all channel reservations should be carried out via the
// lnwallet.InitChannelReservation interface. // lnwallet.InitChannelReservation interface.
func newChannelReservation(fundingAmt btcutil.Amount, minFeeRate btcutil.Amount, func newChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount,
wallet *LightningWallet, id uint64) *ChannelReservation { wallet *LightningWallet, id uint64, numConfs uint16) *ChannelReservation {
// TODO(roasbeef): CSV here, or on delay? var ourBalance btcutil.Amount
var theirBalance btcutil.Amount
if fundingAmt == 0 {
ourBalance = 0
theirBalance = capacity
} else {
ourBalance = fundingAmt
theirBalance = capacity - fundingAmt
}
return &ChannelReservation{ return &ChannelReservation{
ourContribution: &ChannelContribution{ ourContribution: &ChannelContribution{
FundingAmount: fundingAmt, FundingAmount: ourBalance,
}, },
theirContribution: &ChannelContribution{ theirContribution: &ChannelContribution{
FundingAmount: fundingAmt, FundingAmount: theirBalance,
}, },
partialState: &channeldb.OpenChannel{ partialState: &channeldb.OpenChannel{
// TODO(roasbeef): assumes balanced symmetric channels. Capacity: capacity,
Capacity: fundingAmt * 2, OurBalance: ourBalance,
OurBalance: fundingAmt, TheirBalance: theirBalance,
TheirBalance: fundingAmt,
MinFeePerKb: minFeeRate, MinFeePerKb: minFeeRate,
Db: wallet.channelDB, Db: wallet.channelDB,
}, },
reservationID: id, numConfsToOpen: numConfs,
wallet: wallet, reservationID: id,
chanOpen: make(chan *LightningChannel, 1),
wallet: wallet,
} }
} }
@ -178,6 +197,22 @@ func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContr
return <-errChan return <-errChan
} }
// ProcessSingleContribution verifies, and records the initiator's contribution
// to this pending single funder channel. Internally, no further action is
// taken other than recording the initiator's contribution to the single funder
// channel.
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &addSingleContributionMsg{
pendingFundingID: r.reservationID,
contribution: theirContribution,
err: errChan,
}
return <-errChan
}
// 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 contents of a contribution. This attribute will ONLY be available // the contents of a contribution. This attribute will ONLY be available
@ -217,6 +252,7 @@ func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) {
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript, func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript,
commitmentSig []byte) error { commitmentSig []byte) error {
// TODO(roasbeef): add flag for watch or not?
errChan := make(chan error, 1) errChan := make(chan error, 1)
r.wallet.msgChan <- &addCounterPartySigsMsg{ r.wallet.msgChan <- &addCounterPartySigsMsg{
@ -229,6 +265,29 @@ func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScr
return <-errChan return <-errChan
} }
// CompleteReservationSingle finalizes the pending single funder channel
// reservation. Using the funding outpoint of the constructed funding transaction,
// and the initiator's signature for our version of the commitment transaction,
// we are able to verify the correctness of our committment transaction as
// crafted by the initiator. Once this method returns, our signature for the
// initiator's version of the commitment transaction is available via
// the .OurSignatures() method. As this method should only be called as a
// response to a single funder channel, only a commitment signature will be
// populated.
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
commitSig []byte) error {
errChan := make(chan error, 1)
r.wallet.msgChan <- &addSingleFunderSigsMsg{
pendingFundingID: r.reservationID,
fundingOutpoint: fundingPoint,
theirCommitmentSig: commitSig,
err: errChan,
}
return <-errChan
}
// OurSignatures returns the counterparty's signatures to all inputs to the // OurSignatures returns the counterparty's signatures to all inputs to the
// 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
@ -243,10 +302,26 @@ func (r *ChannelReservation) TheirSignatures() ([]*InputScript, []byte) {
// FinalFundingTx returns the finalized, fully signed funding transaction for // FinalFundingTx returns the finalized, fully signed funding transaction for
// this reservation. // this reservation.
//
// NOTE: If this reservation was created as the non-initiator to a single
// funding workflow, then the full funding transaction will not be available.
// Instead we will only have the final outpoint of the funding transaction.
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx { func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
r.RLock() r.RLock()
defer r.RUnlock() defer r.RUnlock()
return r.partialState.FundingTx return r.fundingTx
}
// FundingOutpoint returns the outpoint of the funding transaction.
//
// NOTE: The pointer returned will only be set once the .ProcesContribution()
// method is called in the case of the initiator of a single funder workflow,
// and after the .CompleteReservationSingle() method is called in the case of
// a responder to a single funder workflow.
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
r.RLock()
defer r.RUnlock()
return r.partialState.FundingOutpoint
} }
// Cancel abandons this channel reservation. This method should be called in // Cancel abandons this channel reservation. This method should be called in
@ -264,16 +339,29 @@ func (r *ChannelReservation) Cancel() error {
return <-errChan return <-errChan
} }
// WaitForChannelOpen blocks until the funding transaction for this pending // DispatchChan returns a channel which will be sent on once the funding
// payment channel obtains the configured number of confirmations. Once // transaction for this pending payment channel obtains the configured number
// confirmations have been obtained, a fully initialized LightningChannel // of confirmations. Once confirmations have been obtained, a fully initialized
// instance is returned, allowing for channel updates. // LightningChannel instance is returned, allowing for channel updates.
// NOTE: If this method is called before .CompleteReservation(), it will block // NOTE: If this method is called before .CompleteReservation(), it will block
// indefinitely. // indefinitely.
func (r *ChannelReservation) WaitForChannelOpen() *LightningChannel { func (r *ChannelReservation) DispatchChan() <-chan *LightningChannel {
return <-r.chanOpen return r.chanOpen
} }
// * finish reset of tests // FinalizeReservation completes the pending reservation, returning an active
// * start on commitment side // open LightningChannel. This method should be called after the responder to
// * channel should have active namespace to it's bucket, query at that point fo past commits etc // the single funder workflow receives and verifies a proof from the initiator
// of an open channel.
//
// NOTE: This method should *only* be called as the last step when one is the
// responder to an initiated single funder workflow.
func (r *ChannelReservation) FinalizeReservation() (*LightningChannel, error) {
errChan := make(chan error, 1)
r.wallet.msgChan <- &channelOpenMsg{
pendingFundingID: r.reservationID,
err: errChan,
}
return <-r.chanOpen, <-errChan
}