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

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

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