From 285ba711a11e0d5762e7c83d972c0ad31477a506 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 9 Jan 2017 17:24:13 -0800 Subject: [PATCH] lnwallet: add support for the push-during-funding workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support to the wallet’s internal funding workflow for pushing a certain amount of BTC to the responder’s side for a single funder workflow as part of the first commitment. --- lnwallet/interface_test.go | 26 +++++++++++++++----------- lnwallet/reservation.go | 38 ++++++++++++++++++++++++++------------ lnwallet/wallet.go | 21 +++++++++++++++------ 3 files changed, 56 insertions(+), 29 deletions(-) diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 84e5ce1c..7ddb6929 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -364,7 +364,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet // Bob initiates a channel funded with 5 BTC for each side, so 10 // BTC total. He also generates 2 BTC in change. chanReservation, err := wallet.InitChannelReservation(fundingAmount*2, - fundingAmount, bobNode.id, bobAddr, numReqConfs, 4, 540) + fundingAmount, bobNode.id, bobAddr, numReqConfs, 4, 540, 0) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } @@ -527,7 +527,7 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, // Create a single channel asking for 16 BTC total. fundingAmount := btcutil.Amount(8 * 1e8) _, err := wallet.InitChannelReservation(fundingAmount, fundingAmount, - testPub, bobAddr, numReqConfs, 4, 540) + testPub, bobAddr, numReqConfs, 4, 540, 0) if err != nil { t.Fatalf("unable to initialize funding reservation 1: %v", err) } @@ -537,7 +537,7 @@ func testFundingTransactionLockedOutputs(miner *rpctest.Harness, // that aren't locked, so this should fail. amt := btcutil.Amount(900 * 1e8) failedReservation, err := wallet.InitChannelReservation(amt, amt, - testPub, bobAddr, numReqConfs, 4, 540) + testPub, bobAddr, numReqConfs, 4, 540, 0) if err == nil { t.Fatalf("not error returned, should fail on coin selection") } @@ -557,14 +557,14 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // Create a reservation for 44 BTC. fundingAmount := btcutil.Amount(44 * 1e8) chanReservation, err := wallet.InitChannelReservation(fundingAmount, - fundingAmount, testPub, bobAddr, numReqConfs, 4, 540) + fundingAmount, testPub, bobAddr, numReqConfs, 4, 540, 0) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } // Attempt to create another channel with 44 BTC, this should fail. _, err = wallet.InitChannelReservation(fundingAmount, - fundingAmount, testPub, bobAddr, numReqConfs, 4, 540) + fundingAmount, testPub, bobAddr, numReqConfs, 4, 540, 0) if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { t.Fatalf("coin selection succeded should have insufficient funds: %v", err) @@ -594,7 +594,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness, // Request to fund a new channel should now succeed. _, err = wallet.InitChannelReservation(fundingAmount, fundingAmount, - testPub, bobAddr, numReqConfs, 4, 540) + testPub, bobAddr, numReqConfs, 4, 540, 0) if err != nil { t.Fatalf("unable to initialize funding reservation: %v", err) } @@ -606,7 +606,8 @@ func testCancelNonExistantReservation(miner *rpctest.Harness, t.Log("Running cancel reservation tests") // Create our own reservation, give it some ID. - res := lnwallet.NewChannelReservation(1000, 1000, 5000, wallet, 22, numReqConfs) + res := lnwallet.NewChannelReservation(1000, 1000, 5000, wallet, 22, + numReqConfs, 10) // Attempt to cancel this reservation. This should fail, we know // nothing of it. @@ -630,10 +631,13 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, t.Fatalf("unable to create bob node: %v", err) } - // Initialize a reservation for a channel with 4 BTC funded solely by us. + // Initialize a reservation for a channel with 4 BTC funded solely by + // us. We'll also initially push 1 BTC of the channel towards Bob's + // side. fundingAmt := btcutil.Amount(4 * 1e8) + pushAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin) chanReservation, err := wallet.InitChannelReservation(fundingAmt, - fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540) + fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540, pushAmt) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } @@ -705,7 +709,7 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness, t.Fatalf("bob's final delivery address not found") } if theirContribution.RevocationKey == nil { - t.Fatalf("bob's revocaiton hash not found") + t.Fatalf("bob's revocation hash not found") } // With this contribution processed, we're able to create the @@ -779,7 +783,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness, // contribution and the necessary resources. fundingAmt := btcutil.Amount(0) chanReservation, err := wallet.InitChannelReservation(capacity, - fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540) + fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540, 0) if err != nil { t.Fatalf("unable to init channel reservation: %v", err) } diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go index b64e3ac0..d7c59ecb 100644 --- a/lnwallet/reservation.go +++ b/lnwallet/reservation.go @@ -121,6 +121,11 @@ type ChannelReservation struct { // channel should be considered open. numConfsToOpen uint16 + // pushSat the amount of satoshis that should be pushed to the + // responder of a single funding channel as part of the initial + // commitment state. + pushSat btcutil.Amount + // chanOpen houses a struct containing the channel and additional // confirmation details will be sent on once the channel is considered // 'open'. A channel is open once the funding transaction has reached a @@ -131,11 +136,12 @@ type ChannelReservation struct { } // NewChannelReservation creates a new channel reservation. This function is -// used only internally by lnwallet. In order to concurrent safety, the creation -// of all channel reservations should be carried out via the +// used only internally by lnwallet. In order to concurrent safety, the +// creation of all channel reservations should be carried out via the // lnwallet.InitChannelReservation interface. func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcutil.Amount, - wallet *LightningWallet, id uint64, numConfs uint16) *ChannelReservation { + wallet *LightningWallet, id uint64, numConfs uint16, + pushSat btcutil.Amount) *ChannelReservation { var ( ourBalance btcutil.Amount @@ -143,10 +149,11 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut ) // If we're the responder to a single-funder reservation, then we have - // no initial balance in the channel. + // no initial balance in the channel unless the remote party is pushing + // some funds to us within the first commitment state. if fundingAmt == 0 { - ourBalance = 0 - theirBalance = capacity - commitFee + ourBalance = pushSat + theirBalance = capacity - commitFee - pushSat } else { // TODO(roasbeef): need to rework fee structure in general and // also when we "unlock" dual funder within the daemon @@ -154,8 +161,9 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut if capacity == fundingAmt+commitFee { // If we're initiating a single funder workflow, then // we pay all the initial fees within the commitment - // transaction. - ourBalance = capacity - commitFee + // transaction. We also deduct our balance by the + // amount pushed as part of the initial state. + ourBalance = capacity - commitFee - pushSat } else { // Otherwise, this is a dual funder workflow where both // slides split the amount funded and the commitment @@ -163,7 +171,7 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut ourBalance = fundingAmt - commitFee } - theirBalance = capacity - fundingAmt - commitFee + theirBalance = capacity - fundingAmt - commitFee + pushSat } var ( @@ -171,14 +179,19 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut chanType channeldb.ChannelType ) switch { - // If our balance is zero, then we're the responder to a single funder + // If our balance is zero, or we're being pushed our entire balance in + // the first state, then we're the responder to a single funder // channel workflow. + case pushSat != 0 && ourBalance-pushSat == 0: + fallthrough case ourBalance == 0: initiator = false chanType = channeldb.SingleFunder - // Or, if their balance is zero, then we're the initiator to a single - // funder channel workflow. + // If their balance is zero, or being pushed entirely to them, then + // we're the initiator to a single funder channel workflow. + case pushSat != 0 && theirBalance-pushSat == 0: + fallthrough case theirBalance == 0: initiator = true chanType = channeldb.SingleFunder @@ -208,6 +221,7 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut Db: wallet.ChannelDB, }, numConfsToOpen: numConfs, + pushSat: pushSat, reservationID: id, chanOpen: make(chan *openChanDetails, 1), wallet: wallet, diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index e2197247..8311538e 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -105,6 +105,10 @@ type initFundingReserveMsg struct { // this amount are not enforceable onchain from our point of view. ourDustLimit btcutil.Amount + // pushSat is the number of satoshis that should be pushed over the the + // responder as part of the initial channel creation. + pushSat btcutil.Amount + // The delay on the "pay-to-self" output(s) of the commitment transaction. csvDelay uint32 @@ -489,7 +493,11 @@ out: func (l *LightningWallet) InitChannelReservation(capacity, ourFundAmt btcutil.Amount, theirID *btcec.PublicKey, theirAddr *net.TCPAddr, numConfs uint16, - csvDelay uint32, ourDustLimit btcutil.Amount) (*ChannelReservation, error) { + csvDelay uint32, ourDustLimit btcutil.Amount, + pushSat btcutil.Amount) (*ChannelReservation, error) { + + // TODO(roasbeef): make the above into an initial config as part of the + // refactor to implement spec compliant funding flow errChan := make(chan error, 1) respChan := make(chan *ChannelReservation, 1) @@ -500,6 +508,7 @@ func (l *LightningWallet) InitChannelReservation(capacity, fundingAmount: ourFundAmt, csvDelay: csvDelay, ourDustLimit: ourDustLimit, + pushSat: pushSat, nodeID: theirID, nodeAddr: theirAddr, err: errChan, @@ -523,7 +532,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg id := atomic.AddUint64(&l.nextFundingID, 1) totalCapacity := req.capacity + commitFee reservation := NewChannelReservation(totalCapacity, req.fundingAmount, - req.minFeeRate, l, id, req.numConfs) + req.minFeeRate, l, id, req.numConfs, req.pushSat) // Grab the mutex on the ChannelReservation to ensure thread-safety reservation.Lock() @@ -778,8 +787,8 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) { // With the funding tx complete, create both commitment transactions. // TODO(roasbeef): much cleanup + de-duplication - ourBalance := ourContribution.FundingAmount - theirBalance := theirContribution.FundingAmount + ourBalance := pendingReservation.partialState.OurBalance + theirBalance := pendingReservation.partialState.TheirBalance ourCommitKey := ourContribution.CommitKey ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourRevokeKey, ourContribution.CsvDelay, @@ -1099,8 +1108,8 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) { // remote node's commitment transactions. ourCommitKey := pendingReservation.ourContribution.CommitKey theirCommitKey := pendingReservation.theirContribution.CommitKey - ourBalance := pendingReservation.ourContribution.FundingAmount - theirBalance := pendingReservation.theirContribution.FundingAmount + ourBalance := pendingReservation.partialState.OurBalance + theirBalance := pendingReservation.partialState.TheirBalance ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, pendingReservation.ourContribution.RevocationKey, pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)