lnwallet: add support for the push-during-funding workflow

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.
This commit is contained in:
Olaoluwa Osuntokun 2017-01-09 17:24:13 -08:00
parent 35776a9906
commit 285ba711a1
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
3 changed files with 56 additions and 29 deletions

@ -364,7 +364,7 @@ func testDualFundingReservationWorkflow(miner *rpctest.Harness, wallet *lnwallet
// Bob initiates a channel funded with 5 BTC for each side, so 10 // Bob initiates a channel funded with 5 BTC for each side, so 10
// BTC total. He also generates 2 BTC in change. // BTC total. He also generates 2 BTC in change.
chanReservation, err := wallet.InitChannelReservation(fundingAmount*2, chanReservation, err := wallet.InitChannelReservation(fundingAmount*2,
fundingAmount, bobNode.id, bobAddr, numReqConfs, 4, 540) fundingAmount, bobNode.id, bobAddr, numReqConfs, 4, 540, 0)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) 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. // Create a single channel asking for 16 BTC total.
fundingAmount := btcutil.Amount(8 * 1e8) fundingAmount := btcutil.Amount(8 * 1e8)
_, err := wallet.InitChannelReservation(fundingAmount, fundingAmount, _, err := wallet.InitChannelReservation(fundingAmount, fundingAmount,
testPub, bobAddr, numReqConfs, 4, 540) testPub, bobAddr, numReqConfs, 4, 540, 0)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation 1: %v", err) 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. // that aren't locked, so this should fail.
amt := btcutil.Amount(900 * 1e8) amt := btcutil.Amount(900 * 1e8)
failedReservation, err := wallet.InitChannelReservation(amt, amt, failedReservation, err := wallet.InitChannelReservation(amt, amt,
testPub, bobAddr, numReqConfs, 4, 540) testPub, bobAddr, numReqConfs, 4, 540, 0)
if err == nil { if err == nil {
t.Fatalf("not error returned, should fail on coin selection") 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. // Create a reservation for 44 BTC.
fundingAmount := btcutil.Amount(44 * 1e8) fundingAmount := btcutil.Amount(44 * 1e8)
chanReservation, err := wallet.InitChannelReservation(fundingAmount, chanReservation, err := wallet.InitChannelReservation(fundingAmount,
fundingAmount, testPub, bobAddr, numReqConfs, 4, 540) fundingAmount, testPub, bobAddr, numReqConfs, 4, 540, 0)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) t.Fatalf("unable to initialize funding reservation: %v", err)
} }
// Attempt to create another channel with 44 BTC, this should fail. // Attempt to create another channel with 44 BTC, this should fail.
_, err = wallet.InitChannelReservation(fundingAmount, _, err = wallet.InitChannelReservation(fundingAmount,
fundingAmount, testPub, bobAddr, numReqConfs, 4, 540) fundingAmount, testPub, bobAddr, numReqConfs, 4, 540, 0)
if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok { if _, ok := err.(*lnwallet.ErrInsufficientFunds); !ok {
t.Fatalf("coin selection succeded should have insufficient funds: %v", t.Fatalf("coin selection succeded should have insufficient funds: %v",
err) err)
@ -594,7 +594,7 @@ func testFundingCancellationNotEnoughFunds(miner *rpctest.Harness,
// Request to fund a new channel should now succeed. // Request to fund a new channel should now succeed.
_, err = wallet.InitChannelReservation(fundingAmount, fundingAmount, _, err = wallet.InitChannelReservation(fundingAmount, fundingAmount,
testPub, bobAddr, numReqConfs, 4, 540) testPub, bobAddr, numReqConfs, 4, 540, 0)
if err != nil { if err != nil {
t.Fatalf("unable to initialize funding reservation: %v", err) t.Fatalf("unable to initialize funding reservation: %v", err)
} }
@ -606,7 +606,8 @@ func testCancelNonExistantReservation(miner *rpctest.Harness,
t.Log("Running cancel reservation tests") t.Log("Running cancel reservation tests")
// Create our own reservation, give it some ID. // 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 // Attempt to cancel this reservation. This should fail, we know
// nothing of it. // nothing of it.
@ -630,10 +631,13 @@ func testSingleFunderReservationWorkflowInitiator(miner *rpctest.Harness,
t.Fatalf("unable to create bob node: %v", err) 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) fundingAmt := btcutil.Amount(4 * 1e8)
pushAmt := btcutil.Amount(btcutil.SatoshiPerBitcoin)
chanReservation, err := wallet.InitChannelReservation(fundingAmt, chanReservation, err := wallet.InitChannelReservation(fundingAmt,
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540) fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540, pushAmt)
if err != nil { if err != nil {
t.Fatalf("unable to init channel reservation: %v", err) 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") t.Fatalf("bob's final delivery address not found")
} }
if theirContribution.RevocationKey == nil { 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 // With this contribution processed, we're able to create the
@ -779,7 +783,7 @@ func testSingleFunderReservationWorkflowResponder(miner *rpctest.Harness,
// contribution and the necessary resources. // contribution and the necessary resources.
fundingAmt := btcutil.Amount(0) fundingAmt := btcutil.Amount(0)
chanReservation, err := wallet.InitChannelReservation(capacity, chanReservation, err := wallet.InitChannelReservation(capacity,
fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540) fundingAmt, bobNode.id, bobAddr, numReqConfs, 4, 540, 0)
if err != nil { if err != nil {
t.Fatalf("unable to init channel reservation: %v", err) t.Fatalf("unable to init channel reservation: %v", err)
} }

@ -121,6 +121,11 @@ type ChannelReservation struct {
// channel should be considered open. // channel should be considered open.
numConfsToOpen uint16 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 // chanOpen houses a struct containing the channel and additional
// confirmation details will be sent on once the channel is considered // confirmation details will be sent on once the channel is considered
// 'open'. A channel is open once the funding transaction has reached a // '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 // NewChannelReservation creates a new channel reservation. This function is
// used only internally by lnwallet. In order to concurrent safety, the creation // used only internally by lnwallet. In order to concurrent safety, the
// 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, minFeeRate btcutil.Amount, 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 ( var (
ourBalance btcutil.Amount 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 // 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 { if fundingAmt == 0 {
ourBalance = 0 ourBalance = pushSat
theirBalance = capacity - commitFee theirBalance = capacity - commitFee - pushSat
} else { } else {
// TODO(roasbeef): need to rework fee structure in general and // TODO(roasbeef): need to rework fee structure in general and
// also when we "unlock" dual funder within the daemon // 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 capacity == fundingAmt+commitFee {
// If we're initiating a single funder workflow, then // If we're initiating a single funder workflow, then
// we pay all the initial fees within the commitment // we pay all the initial fees within the commitment
// transaction. // transaction. We also deduct our balance by the
ourBalance = capacity - commitFee // amount pushed as part of the initial state.
ourBalance = capacity - commitFee - pushSat
} else { } else {
// Otherwise, this is a dual funder workflow where both // Otherwise, this is a dual funder workflow where both
// slides split the amount funded and the commitment // slides split the amount funded and the commitment
@ -163,7 +171,7 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
ourBalance = fundingAmt - commitFee ourBalance = fundingAmt - commitFee
} }
theirBalance = capacity - fundingAmt - commitFee theirBalance = capacity - fundingAmt - commitFee + pushSat
} }
var ( var (
@ -171,14 +179,19 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
chanType channeldb.ChannelType chanType channeldb.ChannelType
) )
switch { 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. // channel workflow.
case pushSat != 0 && ourBalance-pushSat == 0:
fallthrough
case ourBalance == 0: case ourBalance == 0:
initiator = false initiator = false
chanType = channeldb.SingleFunder chanType = channeldb.SingleFunder
// Or, if their balance is zero, then we're the initiator to a single // If their balance is zero, or being pushed entirely to them, then
// funder channel workflow. // we're the initiator to a single funder channel workflow.
case pushSat != 0 && theirBalance-pushSat == 0:
fallthrough
case theirBalance == 0: case theirBalance == 0:
initiator = true initiator = true
chanType = channeldb.SingleFunder chanType = channeldb.SingleFunder
@ -208,6 +221,7 @@ func NewChannelReservation(capacity, fundingAmt btcutil.Amount, minFeeRate btcut
Db: wallet.ChannelDB, Db: wallet.ChannelDB,
}, },
numConfsToOpen: numConfs, numConfsToOpen: numConfs,
pushSat: pushSat,
reservationID: id, reservationID: id,
chanOpen: make(chan *openChanDetails, 1), chanOpen: make(chan *openChanDetails, 1),
wallet: wallet, wallet: wallet,

@ -105,6 +105,10 @@ type initFundingReserveMsg struct {
// this amount are not enforceable onchain from our point of view. // this amount are not enforceable onchain from our point of view.
ourDustLimit btcutil.Amount 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. // The delay on the "pay-to-self" output(s) of the commitment transaction.
csvDelay uint32 csvDelay uint32
@ -489,7 +493,11 @@ out:
func (l *LightningWallet) InitChannelReservation(capacity, func (l *LightningWallet) InitChannelReservation(capacity,
ourFundAmt btcutil.Amount, theirID *btcec.PublicKey, ourFundAmt btcutil.Amount, theirID *btcec.PublicKey,
theirAddr *net.TCPAddr, numConfs uint16, 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) errChan := make(chan error, 1)
respChan := make(chan *ChannelReservation, 1) respChan := make(chan *ChannelReservation, 1)
@ -500,6 +508,7 @@ func (l *LightningWallet) InitChannelReservation(capacity,
fundingAmount: ourFundAmt, fundingAmount: ourFundAmt,
csvDelay: csvDelay, csvDelay: csvDelay,
ourDustLimit: ourDustLimit, ourDustLimit: ourDustLimit,
pushSat: pushSat,
nodeID: theirID, nodeID: theirID,
nodeAddr: theirAddr, nodeAddr: theirAddr,
err: errChan, err: errChan,
@ -523,7 +532,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg
id := atomic.AddUint64(&l.nextFundingID, 1) id := atomic.AddUint64(&l.nextFundingID, 1)
totalCapacity := req.capacity + commitFee totalCapacity := req.capacity + commitFee
reservation := NewChannelReservation(totalCapacity, req.fundingAmount, 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 // Grab the mutex on the ChannelReservation to ensure thread-safety
reservation.Lock() reservation.Lock()
@ -778,8 +787,8 @@ func (l *LightningWallet) handleContributionMsg(req *addContributionMsg) {
// With the funding tx complete, create both commitment transactions. // With the funding tx complete, create both commitment transactions.
// TODO(roasbeef): much cleanup + de-duplication // TODO(roasbeef): much cleanup + de-duplication
ourBalance := ourContribution.FundingAmount ourBalance := pendingReservation.partialState.OurBalance
theirBalance := theirContribution.FundingAmount theirBalance := pendingReservation.partialState.TheirBalance
ourCommitKey := ourContribution.CommitKey ourCommitKey := ourContribution.CommitKey
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
ourRevokeKey, ourContribution.CsvDelay, ourRevokeKey, ourContribution.CsvDelay,
@ -1099,8 +1108,8 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
// remote node's commitment transactions. // remote node's commitment transactions.
ourCommitKey := pendingReservation.ourContribution.CommitKey ourCommitKey := pendingReservation.ourContribution.CommitKey
theirCommitKey := pendingReservation.theirContribution.CommitKey theirCommitKey := pendingReservation.theirContribution.CommitKey
ourBalance := pendingReservation.ourContribution.FundingAmount ourBalance := pendingReservation.partialState.OurBalance
theirBalance := pendingReservation.theirContribution.FundingAmount theirBalance := pendingReservation.partialState.TheirBalance
ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey, ourCommitTx, err := CreateCommitTx(fundingTxIn, ourCommitKey, theirCommitKey,
pendingReservation.ourContribution.RevocationKey, pendingReservation.ourContribution.RevocationKey,
pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance) pendingReservation.ourContribution.CsvDelay, ourBalance, theirBalance)