lnwallet: add new test to exercise external channel funding

This commit is contained in:
Olaoluwa Osuntokun 2019-10-31 21:47:50 -07:00
parent 9926259da0
commit c3a7da5ce7
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465

@ -793,7 +793,9 @@ func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContributi
} }
func testSingleFunderReservationWorkflow(miner *rpctest.Harness, func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T, tweakless bool) { alice, bob *lnwallet.LightningWallet, t *testing.T, tweakless bool,
aliceChanFunder chanfunding.Assembler,
fetchFundingTx func() *wire.MsgTx, pendingChanID [32]byte) {
// For this scenario, Alice will be the channel initiator while bob // For this scenario, Alice will be the channel initiator while bob
// will act as the responder to the workflow. // will act as the responder to the workflow.
@ -812,6 +814,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
} }
aliceReq := &lnwallet.InitFundingReserveMsg{ aliceReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash, ChainHash: chainHash,
PendingChanID: pendingChanID,
NodeID: bobPub, NodeID: bobPub,
NodeAddr: bobAddr, NodeAddr: bobAddr,
LocalFundingAmt: fundingAmt, LocalFundingAmt: fundingAmt,
@ -821,6 +824,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
PushMSat: pushAmt, PushMSat: pushAmt,
Flags: lnwire.FFAnnounceChannel, Flags: lnwire.FFAnnounceChannel,
Tweakless: tweakless, Tweakless: tweakless,
ChanFunder: aliceChanFunder,
} }
aliceChanReservation, err := alice.InitChannelReservation(aliceReq) aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
if err != nil { if err != nil {
@ -840,15 +844,20 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
t.Fatalf("unable to verify constraints: %v", err) t.Fatalf("unable to verify constraints: %v", err)
} }
// Verify all contribution fields have been set properly. // Verify all contribution fields have been set properly, but only if
// Alice is the funder herself.
aliceContribution := aliceChanReservation.OurContribution() aliceContribution := aliceChanReservation.OurContribution()
if len(aliceContribution.Inputs) < 1 { if fetchFundingTx == nil {
t.Fatalf("outputs for funding tx not properly selected, have %v "+ if len(aliceContribution.Inputs) < 1 {
"outputs should at least 1", len(aliceContribution.Inputs)) t.Fatalf("outputs for funding tx not properly "+
} "selected, have %v outputs should at least 1",
if len(aliceContribution.ChangeOutputs) != 1 { len(aliceContribution.Inputs))
t.Fatalf("coin selection failed, should have one change outputs, "+ }
"instead have: %v", len(aliceContribution.ChangeOutputs)) if len(aliceContribution.ChangeOutputs) != 1 {
t.Fatalf("coin selection failed, should have one "+
"change outputs, instead have: %v",
len(aliceContribution.ChangeOutputs))
}
} }
assertContributionInitPopulated(t, aliceContribution) assertContributionInitPopulated(t, aliceContribution)
@ -856,6 +865,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
// reservation initiation, then consume Alice's contribution. // reservation initiation, then consume Alice's contribution.
bobReq := &lnwallet.InitFundingReserveMsg{ bobReq := &lnwallet.InitFundingReserveMsg{
ChainHash: chainHash, ChainHash: chainHash,
PendingChanID: pendingChanID,
NodeID: alicePub, NodeID: alicePub,
NodeAddr: aliceAddr, NodeAddr: aliceAddr,
LocalFundingAmt: 0, LocalFundingAmt: 0,
@ -898,10 +908,11 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
// At this point, Alice should have generated all the signatures // At this point, Alice should have generated all the signatures
// required for the funding transaction, as well as Alice's commitment // required for the funding transaction, as well as Alice's commitment
// signature to bob. // signature to bob, but only if the funding transaction was
// constructed internally.
aliceRemoteContribution := aliceChanReservation.TheirContribution() aliceRemoteContribution := aliceChanReservation.TheirContribution()
aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures() aliceFundingSigs, aliceCommitSig := aliceChanReservation.OurSignatures()
if aliceFundingSigs == nil { if fetchFundingTx == nil && aliceFundingSigs == nil {
t.Fatalf("funding sigs not found") t.Fatalf("funding sigs not found")
} }
if aliceCommitSig == nil { if aliceCommitSig == nil {
@ -910,7 +921,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
// Additionally, the funding tx and the funding outpoint should have // Additionally, the funding tx and the funding outpoint should have
// been populated. // been populated.
if aliceChanReservation.FinalFundingTx() == nil { if aliceChanReservation.FinalFundingTx() == nil && fetchFundingTx == nil {
t.Fatalf("funding transaction never created!") t.Fatalf("funding transaction never created!")
} }
if aliceChanReservation.FundingOutpoint() == nil { if aliceChanReservation.FundingOutpoint() == nil {
@ -952,9 +963,17 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
t.Fatalf("alice unable to complete reservation: %v", err) t.Fatalf("alice unable to complete reservation: %v", err)
} }
// If the caller provided an alternative way to obtain the funding tx,
// then we'll use that. Otherwise, we'll obtain it directly from Alice.
var fundingTx *wire.MsgTx
if fetchFundingTx != nil {
fundingTx = fetchFundingTx()
} else {
fundingTx = aliceChanReservation.FinalFundingTx()
}
// The resulting active channel state should have been persisted to the // The resulting active channel state should have been persisted to the
// DB for both Alice and Bob. // DB for both Alice and Bob.
fundingTx := aliceChanReservation.FinalFundingTx()
fundingSha := fundingTx.TxHash() fundingSha := fundingTx.TxHash()
aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub) aliceChannels, err := alice.Cfg.Database.FetchOpenChannels(bobPub)
if err != nil { if err != nil {
@ -2524,7 +2543,8 @@ var walletTests = []walletTestCase{
bob *lnwallet.LightningWallet, t *testing.T) { bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow( testSingleFunderReservationWorkflow(
miner, alice, bob, t, false, miner, alice, bob, t, false, nil, nil,
[32]byte{},
) )
}, },
}, },
@ -2534,10 +2554,15 @@ var walletTests = []walletTestCase{
bob *lnwallet.LightningWallet, t *testing.T) { bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow( testSingleFunderReservationWorkflow(
miner, alice, bob, t, true, miner, alice, bob, t, true, nil, nil,
[32]byte{},
) )
}, },
}, },
{
name: "single funding workflow external funding tx",
test: testSingleFunderExternalFundingTx,
},
{ {
name: "dual funder workflow", name: "dual funder workflow",
test: testDualFundingReservationWorkflow, test: testDualFundingReservationWorkflow,
@ -2676,6 +2701,114 @@ func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error {
return nil return nil
} }
// testSingleFunderExternalFundingTx tests that the wallet is able to properly
// carry out a funding flow backed by a channel point that has been crafted
// outside the wallet.
func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
// First, we'll obtain multi-sig keys from both Alice and Bob which
// simulates them exchanging keys on a higher level.
aliceFundingKey, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig)
if err != nil {
t.Fatalf("unable to obtain alice funding key: %v", err)
}
bobFundingKey, err := bob.DeriveNextKey(keychain.KeyFamilyMultiSig)
if err != nil {
t.Fatalf("unable to obtain bob funding key: %v", err)
}
// We'll now set up for them to open a 4 BTC channel, with 1 BTC pushed
// to Bob's side.
chanAmt := 4 * btcutil.SatoshiPerBitcoin
// Simulating external funding negotiation, we'll now create the
// funding transaction for both parties. Utilizing existing tools,
// we'll create a new chanfunding.Assembler hacked by Alice's wallet.
aliceChanFunder := chanfunding.NewWalletAssembler(chanfunding.WalletConfig{
CoinSource: lnwallet.NewCoinSource(alice),
CoinSelectLocker: alice,
CoinLocker: alice,
Signer: alice.Cfg.Signer,
DustLimit: 600,
})
// With the chan funder created, we'll now provision a funding intent,
// bind the keys we obtained above, and finally obtain our funding
// transaction and outpoint.
fundingIntent, err := aliceChanFunder.ProvisionChannel(&chanfunding.Request{
LocalAmt: btcutil.Amount(chanAmt),
MinConfs: 1,
FeeRate: 253,
ChangeAddr: func() (btcutil.Address, error) {
return alice.NewAddress(lnwallet.WitnessPubKey, true)
},
})
if err != nil {
t.Fatalf("unable to perform coin selection: %v", err)
}
// With our intent created, we'll instruct it to finalize the funding
// transaction, and also hand us the outpoint so we can simulate
// external crafting of the funding transaction.
var (
fundingTx *wire.MsgTx
chanPoint *wire.OutPoint
)
if fullIntent, ok := fundingIntent.(*chanfunding.FullIntent); ok {
fullIntent.BindKeys(&aliceFundingKey, bobFundingKey.PubKey)
fundingTx, err = fullIntent.CompileFundingTx(nil, nil)
if err != nil {
t.Fatalf("unable to compile funding tx: %v", err)
}
chanPoint, err = fullIntent.ChanPoint()
if err != nil {
t.Fatalf("unable to obtain chan point: %v", err)
}
} else {
t.Fatalf("expected full intent, instead got: %T", fullIntent)
}
// Now that we have the fully constructed funding transaction, we'll
// create a new shim external funder out of it for Alice, and prep a
// shim intent for Bob.
aliceExternalFunder := chanfunding.NewCannedAssembler(
*chanPoint, btcutil.Amount(chanAmt), &aliceFundingKey,
bobFundingKey.PubKey, true,
)
bobShimIntent, err := chanfunding.NewCannedAssembler(
*chanPoint, btcutil.Amount(chanAmt), &bobFundingKey,
aliceFundingKey.PubKey, false,
).ProvisionChannel(nil)
if err != nil {
t.Fatalf("unable to create shim intent for bob: %v", err)
}
// At this point, we have everything we need to carry out our test, so
// we'll being the funding flow between Alice and Bob.
//
// However, before we do so, we'll register a new shim intent for Bob,
// so he knows what keys to use when he receives the funding request
// from Alice.
pendingChanID := testHdSeed
err = bob.RegisterFundingIntent(pendingChanID, bobShimIntent)
if err != nil {
t.Fatalf("unable to register intent: %v", err)
}
// Now we can carry out the single funding flow as normal, we'll
// specify our external funder and funding transaction, as well as the
// pending channel ID generated above to allow Alice and Bob to track
// the funding flow externally.
testSingleFunderReservationWorkflow(
miner, alice, bob, t, true, aliceExternalFunder,
func() *wire.MsgTx {
return fundingTx
}, pendingChanID,
)
}
// TestInterfaces tests all registered interfaces with a unified set of tests // TestInterfaces tests all registered interfaces with a unified set of tests
// which exercise each of the required methods found within the WalletController // which exercise each of the required methods found within the WalletController
// interface. // interface.