Merge pull request #5181 from yyforyongyu/refactor-itest
itest: move funding tests into one file
This commit is contained in:
commit
cd87fe89eb
502
lntest/itest/lnd_funding_test.go
Normal file
502
lntest/itest/lnd_funding_test.go
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
package itest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testBasicChannelFunding performs a test exercising expected behavior from a
|
||||||
|
// basic funding workflow. The test creates a new channel between Alice and
|
||||||
|
// Bob, then immediately closes the channel after asserting some expected post
|
||||||
|
// conditions. Finally, the chain itself is checked to ensure the closing
|
||||||
|
// transaction was mined.
|
||||||
|
func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// Run through the test with combinations of all the different
|
||||||
|
// commitment types.
|
||||||
|
allTypes := []commitType{
|
||||||
|
commitTypeLegacy,
|
||||||
|
commitTypeTweakless,
|
||||||
|
commitTypeAnchors,
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFunding is a function closure that takes Carol and Dave's
|
||||||
|
// commitment types and test the funding flow.
|
||||||
|
testFunding := func(carolCommitType, daveCommitType commitType) {
|
||||||
|
// Based on the current tweak variable for Carol, we'll
|
||||||
|
// preferentially signal the legacy commitment format. We do
|
||||||
|
// the same for Dave shortly below.
|
||||||
|
carolArgs := carolCommitType.Args()
|
||||||
|
carol, err := net.NewNode("Carol", carolArgs)
|
||||||
|
require.NoError(t.t, err, "unable to create new node")
|
||||||
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
|
// Each time, we'll send Carol a new set of coins in order to
|
||||||
|
// fund the channel.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
|
||||||
|
require.NoError(t.t, err, "unable to send coins to carol")
|
||||||
|
|
||||||
|
daveArgs := daveCommitType.Args()
|
||||||
|
dave, err := net.NewNode("Dave", daveArgs)
|
||||||
|
require.NoError(t.t, err, "unable to create new node")
|
||||||
|
defer shutdownAndAssert(net, t, dave)
|
||||||
|
|
||||||
|
// Before we start the test, we'll ensure both sides are
|
||||||
|
// connected to the funding flow can properly be executed.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.EnsureConnected(ctxt, carol, dave)
|
||||||
|
require.NoError(t.t, err, "unable to connect peers")
|
||||||
|
|
||||||
|
carolChan, daveChan, closeChan, err := basicChannelFundingTest(
|
||||||
|
t, net, carol, dave, nil,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err, "failed funding flow")
|
||||||
|
|
||||||
|
// Both nodes should report the same commitment
|
||||||
|
// type.
|
||||||
|
chansCommitType := carolChan.CommitmentType
|
||||||
|
require.Equal(
|
||||||
|
t.t, chansCommitType,
|
||||||
|
daveChan.CommitmentType,
|
||||||
|
"commit types don't match",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Now check that the commitment type reported
|
||||||
|
// by both nodes is what we expect. It will be
|
||||||
|
// the minimum of the two nodes' preference, in
|
||||||
|
// the order Legacy, Tweakless, Anchors.
|
||||||
|
expType := carolCommitType
|
||||||
|
|
||||||
|
switch daveCommitType {
|
||||||
|
|
||||||
|
// Dave supports anchors, type will be what
|
||||||
|
// Carol supports.
|
||||||
|
case commitTypeAnchors:
|
||||||
|
|
||||||
|
// Dave only supports tweakless, channel will
|
||||||
|
// be downgraded to this type if Carol supports
|
||||||
|
// anchors.
|
||||||
|
case commitTypeTweakless:
|
||||||
|
if expType == commitTypeAnchors {
|
||||||
|
expType = commitTypeTweakless
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dave only supoprts legacy type, channel will
|
||||||
|
// be downgraded to this type.
|
||||||
|
case commitTypeLegacy:
|
||||||
|
expType = commitTypeLegacy
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Fatalf("invalid commit type %v", daveCommitType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the signalled type matches what we
|
||||||
|
// expect.
|
||||||
|
switch {
|
||||||
|
case expType == commitTypeAnchors &&
|
||||||
|
chansCommitType == lnrpc.CommitmentType_ANCHORS:
|
||||||
|
|
||||||
|
case expType == commitTypeTweakless &&
|
||||||
|
chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
||||||
|
|
||||||
|
case expType == commitTypeLegacy &&
|
||||||
|
chansCommitType == lnrpc.CommitmentType_LEGACY:
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Fatalf("expected nodes to signal "+
|
||||||
|
"commit type %v, instead got "+
|
||||||
|
"%v", expType, chansCommitType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As we've concluded this sub-test case we'll
|
||||||
|
// now close out the channel for both sides.
|
||||||
|
closeChan()
|
||||||
|
}
|
||||||
|
|
||||||
|
test:
|
||||||
|
// We'll test all possible combinations of the feature bit presence
|
||||||
|
// that both nodes can signal for this new channel type. We'll make a
|
||||||
|
// new Carol+Dave for each test instance as well.
|
||||||
|
for _, carolCommitType := range allTypes {
|
||||||
|
for _, daveCommitType := range allTypes {
|
||||||
|
cc := carolCommitType
|
||||||
|
dc := daveCommitType
|
||||||
|
|
||||||
|
testName := fmt.Sprintf(
|
||||||
|
"carol_commit=%v,dave_commit=%v", cc, dc,
|
||||||
|
)
|
||||||
|
|
||||||
|
success := t.t.Run(testName, func(t *testing.T) {
|
||||||
|
testFunding(cc, dc)
|
||||||
|
})
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
break test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// basicChannelFundingTest is a sub-test of the main testBasicChannelFunding
|
||||||
|
// test. Given two nodes: Alice and Bob, it'll assert proper channel creation,
|
||||||
|
// then return a function closure that should be called to assert proper
|
||||||
|
// channel closure.
|
||||||
|
func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness,
|
||||||
|
alice *lntest.HarnessNode, bob *lntest.HarnessNode,
|
||||||
|
fundingShim *lnrpc.FundingShim) (*lnrpc.Channel, *lnrpc.Channel,
|
||||||
|
func(), error) {
|
||||||
|
|
||||||
|
chanAmt := funding.MaxBtcFundingAmount
|
||||||
|
pushAmt := btcutil.Amount(100000)
|
||||||
|
satPerVbyte := btcutil.Amount(1)
|
||||||
|
|
||||||
|
// Record nodes' channel balance before testing.
|
||||||
|
aliceChannelBalance := getChannelBalance(t, alice)
|
||||||
|
bobChannelBalance := getChannelBalance(t, bob)
|
||||||
|
|
||||||
|
// Creates a helper closure to be used below which asserts the proper
|
||||||
|
// response to a channel balance RPC.
|
||||||
|
checkChannelBalance := func(node *lntest.HarnessNode,
|
||||||
|
oldChannelBalance *lnrpc.ChannelBalanceResponse,
|
||||||
|
local, remote btcutil.Amount) {
|
||||||
|
|
||||||
|
newResp := oldChannelBalance
|
||||||
|
|
||||||
|
newResp.LocalBalance.Sat += uint64(local)
|
||||||
|
newResp.LocalBalance.Msat += uint64(
|
||||||
|
lnwire.NewMSatFromSatoshis(local),
|
||||||
|
)
|
||||||
|
newResp.RemoteBalance.Sat += uint64(remote)
|
||||||
|
newResp.RemoteBalance.Msat += uint64(
|
||||||
|
lnwire.NewMSatFromSatoshis(remote),
|
||||||
|
)
|
||||||
|
// Deprecated fields.
|
||||||
|
newResp.Balance += int64(local)
|
||||||
|
assertChannelBalanceResp(t, node, newResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First establish a channel with a capacity of 0.5 BTC between Alice
|
||||||
|
// and Bob with Alice pushing 100k satoshis to Bob's side during
|
||||||
|
// funding. This function will block until the channel itself is fully
|
||||||
|
// open or an error occurs in the funding process. A series of
|
||||||
|
// assertions will be executed to ensure the funding process completed
|
||||||
|
// successfully.
|
||||||
|
ctxb := context.Background()
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
|
||||||
|
chanPoint := openChannelAndAssert(
|
||||||
|
ctxt, t, net, alice, bob,
|
||||||
|
lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
FundingShim: fundingShim,
|
||||||
|
SatPerVByte: satPerVbyte,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
|
||||||
|
err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
|
require.NoError(t.t, err, "alice didn't report channel")
|
||||||
|
|
||||||
|
err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
||||||
|
require.NoError(t.t, err, "bob didn't report channel")
|
||||||
|
|
||||||
|
cType, err := channelCommitType(alice, chanPoint)
|
||||||
|
require.NoError(t.t, err, "unable to get channnel type")
|
||||||
|
|
||||||
|
// With the channel open, ensure that the amount specified above has
|
||||||
|
// properly been pushed to Bob.
|
||||||
|
aliceLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
|
||||||
|
checkChannelBalance(
|
||||||
|
alice, aliceChannelBalance, aliceLocalBalance, pushAmt,
|
||||||
|
)
|
||||||
|
checkChannelBalance(
|
||||||
|
bob, bobChannelBalance, pushAmt, aliceLocalBalance,
|
||||||
|
)
|
||||||
|
|
||||||
|
req := &lnrpc.ListChannelsRequest{}
|
||||||
|
aliceChannel, err := alice.ListChannels(context.Background(), req)
|
||||||
|
require.NoError(t.t, err, "unable to obtain chan")
|
||||||
|
|
||||||
|
bobChannel, err := bob.ListChannels(context.Background(), req)
|
||||||
|
require.NoError(t.t, err, "unable to obtain chan")
|
||||||
|
|
||||||
|
closeChan := func() {
|
||||||
|
// Finally, immediately close the channel. This function will
|
||||||
|
// also block until the channel is closed and will additionally
|
||||||
|
// assert the relevant channel closing post conditions.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
|
closeChannelAndAssert(ctxt, t, net, alice, chanPoint, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliceChannel.Channels[0], bobChannel.Channels[0], closeChan, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
||||||
|
// be used to fund channels.
|
||||||
|
func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
const (
|
||||||
|
chanAmt = funding.MaxBtcFundingAmount
|
||||||
|
pushAmt = btcutil.Amount(100000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// We'll start off by creating a node for Carol.
|
||||||
|
carol, err := net.NewNode("Carol", nil)
|
||||||
|
require.NoError(t.t, err, "unable to create carol's node")
|
||||||
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
|
// We'll send her some confirmed funds.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.SendCoins(ctxt, 2*chanAmt, carol)
|
||||||
|
require.NoError(t.t, err, "unable to send coins to carol")
|
||||||
|
|
||||||
|
// Now let Carol send some funds to herself, making a unconfirmed
|
||||||
|
// change output.
|
||||||
|
addrReq := &lnrpc.NewAddressRequest{
|
||||||
|
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
resp, err := carol.NewAddress(ctxt, addrReq)
|
||||||
|
require.NoError(t.t, err, "unable to get new address")
|
||||||
|
|
||||||
|
sendReq := &lnrpc.SendCoinsRequest{
|
||||||
|
Addr: resp.Address,
|
||||||
|
Amount: int64(chanAmt) / 5,
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
_, err = carol.SendCoins(ctxt, sendReq)
|
||||||
|
require.NoError(t.t, err, "unable to send coins")
|
||||||
|
|
||||||
|
// Make sure the unconfirmed tx is seen in the mempool.
|
||||||
|
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
||||||
|
require.NoError(t.t, err, "failed to find tx in miner mempool")
|
||||||
|
|
||||||
|
// Now, we'll connect her to Alice so that they can open a channel
|
||||||
|
// together. The funding flow should select Carol's unconfirmed output
|
||||||
|
// as she doesn't have any other funds since it's a new node.
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.ConnectNodes(ctxt, carol, net.Alice)
|
||||||
|
require.NoError(t.t, err, "unable to connect carol to alice")
|
||||||
|
|
||||||
|
chanOpenUpdate := openChannelStream(
|
||||||
|
ctxt, t, net, carol, net.Alice,
|
||||||
|
lntest.OpenChannelParams{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
SpendUnconfirmed: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Creates a helper closure to be used below which asserts the proper
|
||||||
|
// response to a channel balance RPC.
|
||||||
|
checkChannelBalance := func(node *lntest.HarnessNode,
|
||||||
|
local, remote, pendingLocal, pendingRemote btcutil.Amount) {
|
||||||
|
expectedResponse := &lnrpc.ChannelBalanceResponse{
|
||||||
|
LocalBalance: &lnrpc.Amount{
|
||||||
|
Sat: uint64(local),
|
||||||
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
||||||
|
local,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
RemoteBalance: &lnrpc.Amount{
|
||||||
|
Sat: uint64(remote),
|
||||||
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
||||||
|
remote,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PendingOpenLocalBalance: &lnrpc.Amount{
|
||||||
|
Sat: uint64(pendingLocal),
|
||||||
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
||||||
|
pendingLocal,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PendingOpenRemoteBalance: &lnrpc.Amount{
|
||||||
|
Sat: uint64(pendingRemote),
|
||||||
|
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
||||||
|
pendingRemote,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
UnsettledLocalBalance: &lnrpc.Amount{},
|
||||||
|
UnsettledRemoteBalance: &lnrpc.Amount{},
|
||||||
|
// Deprecated fields.
|
||||||
|
Balance: int64(local),
|
||||||
|
PendingOpenBalance: int64(pendingLocal),
|
||||||
|
}
|
||||||
|
assertChannelBalanceResp(t, node, expectedResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As the channel is pending open, it's expected Carol has both zero
|
||||||
|
// local and remote balances, and pending local/remote should not be
|
||||||
|
// zero.
|
||||||
|
//
|
||||||
|
// Note that atm we haven't obtained the chanPoint yet, so we use the
|
||||||
|
// type directly.
|
||||||
|
cType := commitTypeTweakless
|
||||||
|
carolLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
|
||||||
|
checkChannelBalance(carol, 0, 0, carolLocalBalance, pushAmt)
|
||||||
|
|
||||||
|
// For Alice, her local/remote balances should be zero, and the
|
||||||
|
// local/remote balances are the mirror of Carol's.
|
||||||
|
checkChannelBalance(net.Alice, 0, 0, pushAmt, carolLocalBalance)
|
||||||
|
|
||||||
|
// Confirm the channel and wait for it to be recognized by both
|
||||||
|
// parties. Two transactions should be mined, the unconfirmed spend and
|
||||||
|
// the funding tx.
|
||||||
|
mineBlocks(t, net, 6, 2)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
|
||||||
|
require.NoError(t.t, err, "error while waitinng for channel open")
|
||||||
|
|
||||||
|
// With the channel open, we'll check the balances on each side of the
|
||||||
|
// channel as a sanity check to ensure things worked out as intended.
|
||||||
|
checkChannelBalance(carol, carolLocalBalance, pushAmt, 0, 0)
|
||||||
|
checkChannelBalance(net.Alice, pushAmt, carolLocalBalance, 0, 0)
|
||||||
|
|
||||||
|
// Now that we're done with the test, the channel can be closed.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
|
closeChannelAndAssert(ctxt, t, net, carol, chanPoint, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testexternalfundingchanpoint tests that we're able to carry out a normal
|
||||||
|
// channel funding workflow given a channel point that was constructed outside
|
||||||
|
// the main daemon.
|
||||||
|
func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
// First, we'll create two new nodes that we'll use to open channel
|
||||||
|
// between for this test.
|
||||||
|
carol, err := net.NewNode("carol", nil)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
defer shutdownAndAssert(net, t, carol)
|
||||||
|
|
||||||
|
dave, err := net.NewNode("dave", nil)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
defer shutdownAndAssert(net, t, dave)
|
||||||
|
|
||||||
|
// Carol will be funding the channel, so we'll send some coins over to
|
||||||
|
// her and ensure they have enough confirmations before we proceed.
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Before we start the test, we'll ensure both sides are connected to
|
||||||
|
// the funding flow can properly be executed.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = net.EnsureConnected(ctxt, carol, dave)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// At this point, we're ready to simulate our external channel funding
|
||||||
|
// flow. To start with, we'll create a pending channel with a shim for
|
||||||
|
// a transaction that will never be published.
|
||||||
|
const thawHeight uint32 = 10
|
||||||
|
const chanSize = funding.MaxBtcFundingAmount
|
||||||
|
fundingShim1, chanPoint1, _ := deriveFundingShim(
|
||||||
|
net, t, carol, dave, chanSize, thawHeight, 1, false,
|
||||||
|
)
|
||||||
|
_ = openChannelStream(
|
||||||
|
ctxb, t, net, carol, dave, lntest.OpenChannelParams{
|
||||||
|
Amt: chanSize,
|
||||||
|
FundingShim: fundingShim1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
assertNumOpenChannelsPending(ctxt, t, carol, dave, 1)
|
||||||
|
|
||||||
|
// That channel is now pending forever and normally would saturate the
|
||||||
|
// max pending channel limit for both nodes. But because the channel is
|
||||||
|
// externally funded, we should still be able to open another one. Let's
|
||||||
|
// do exactly that now. For this one we publish the transaction so we
|
||||||
|
// can mine it later.
|
||||||
|
fundingShim2, chanPoint2, _ := deriveFundingShim(
|
||||||
|
net, t, carol, dave, chanSize, thawHeight, 2, true,
|
||||||
|
)
|
||||||
|
|
||||||
|
// At this point, we'll now carry out the normal basic channel funding
|
||||||
|
// test as everything should now proceed as normal (a regular channel
|
||||||
|
// funding flow).
|
||||||
|
carolChan, daveChan, _, err := basicChannelFundingTest(
|
||||||
|
t, net, carol, dave, fundingShim2,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Both channels should be marked as frozen with the proper thaw
|
||||||
|
// height.
|
||||||
|
require.Equal(
|
||||||
|
t.t, thawHeight, carolChan.ThawHeight, "thaw height unmatched",
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t.t, thawHeight, daveChan.ThawHeight, "thaw height unmatched",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next, to make sure the channel functions as normal, we'll make some
|
||||||
|
// payments within the channel.
|
||||||
|
payAmt := btcutil.Amount(100000)
|
||||||
|
invoice := &lnrpc.Invoice{
|
||||||
|
Memo: "new chans",
|
||||||
|
Value: int64(payAmt),
|
||||||
|
}
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
resp, err := dave.AddInvoice(ctxt, invoice)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
err = completePaymentRequests(
|
||||||
|
ctxt, carol, carol.RouterClient, []string{resp.PaymentRequest},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// Now that the channels are open, and we've confirmed that they're
|
||||||
|
// operational, we'll now ensure that the channels are frozen as
|
||||||
|
// intended (if requested).
|
||||||
|
//
|
||||||
|
// First, we'll try to close the channel as Carol, the initiator. This
|
||||||
|
// should fail as a frozen channel only allows the responder to
|
||||||
|
// initiate a channel close.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
|
_, _, err = net.CloseChannel(ctxt, carol, chanPoint2, false)
|
||||||
|
require.Error(t.t, err,
|
||||||
|
"carol wasn't denied a co-op close attempt "+
|
||||||
|
"for a frozen channel",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Next we'll try but this time with Dave (the responder) as the
|
||||||
|
// initiator. This time the channel should be closed as normal.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
|
closeChannelAndAssert(ctxt, t, net, dave, chanPoint2, false)
|
||||||
|
|
||||||
|
// As a last step, we check if we still have the pending channel hanging
|
||||||
|
// around because we never published the funding TX.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
assertNumOpenChannelsPending(ctxt, t, carol, dave, 1)
|
||||||
|
|
||||||
|
// Let's make sure we can abandon it.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
_, err = carol.AbandonChannel(ctxt, &lnrpc.AbandonChannelRequest{
|
||||||
|
ChannelPoint: chanPoint1,
|
||||||
|
PendingFundingShimOnly: true,
|
||||||
|
})
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
|
_, err = dave.AbandonChannel(ctxt, &lnrpc.AbandonChannelRequest{
|
||||||
|
ChannelPoint: chanPoint1,
|
||||||
|
PendingFundingShimOnly: true,
|
||||||
|
})
|
||||||
|
require.NoError(t.t, err)
|
||||||
|
|
||||||
|
// It should now not appear in the pending channels anymore.
|
||||||
|
assertNumOpenChannelsPending(ctxt, t, carol, dave, 0)
|
||||||
|
}
|
@ -1243,394 +1243,6 @@ func getChannelBalance(t *harnessTest,
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// basicChannelFundingTest is a sub-test of the main testBasicChannelFunding
|
|
||||||
// test. Given two nodes: Alice and Bob, it'll assert proper channel creation,
|
|
||||||
// then return a function closure that should be called to assert proper
|
|
||||||
// channel closure.
|
|
||||||
func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness,
|
|
||||||
alice *lntest.HarnessNode, bob *lntest.HarnessNode,
|
|
||||||
fundingShim *lnrpc.FundingShim) (*lnrpc.Channel, *lnrpc.Channel, func(), error) {
|
|
||||||
|
|
||||||
chanAmt := funding.MaxBtcFundingAmount
|
|
||||||
pushAmt := btcutil.Amount(100000)
|
|
||||||
satPerVbyte := btcutil.Amount(1)
|
|
||||||
|
|
||||||
// Record nodes' channel balance before testing.
|
|
||||||
aliceChannelBalance := getChannelBalance(t, alice)
|
|
||||||
bobChannelBalance := getChannelBalance(t, bob)
|
|
||||||
|
|
||||||
// Creates a helper closure to be used below which asserts the proper
|
|
||||||
// response to a channel balance RPC.
|
|
||||||
checkChannelBalance := func(node *lntest.HarnessNode,
|
|
||||||
oldChannelBalance *lnrpc.ChannelBalanceResponse,
|
|
||||||
local, remote btcutil.Amount) {
|
|
||||||
|
|
||||||
newResp := oldChannelBalance
|
|
||||||
|
|
||||||
newResp.LocalBalance.Sat += uint64(local)
|
|
||||||
newResp.LocalBalance.Msat += uint64(
|
|
||||||
lnwire.NewMSatFromSatoshis(local),
|
|
||||||
)
|
|
||||||
newResp.RemoteBalance.Sat += uint64(remote)
|
|
||||||
newResp.RemoteBalance.Msat += uint64(
|
|
||||||
lnwire.NewMSatFromSatoshis(remote),
|
|
||||||
)
|
|
||||||
// Deprecated fields.
|
|
||||||
newResp.Balance += int64(local)
|
|
||||||
assertChannelBalanceResp(t, node, newResp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First establish a channel with a capacity of 0.5 BTC between Alice
|
|
||||||
// and Bob with Alice pushing 100k satoshis to Bob's side during
|
|
||||||
// funding. This function will block until the channel itself is fully
|
|
||||||
// open or an error occurs in the funding process. A series of
|
|
||||||
// assertions will be executed to ensure the funding process completed
|
|
||||||
// successfully.
|
|
||||||
ctxb := context.Background()
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout)
|
|
||||||
chanPoint := openChannelAndAssert(
|
|
||||||
ctxt, t, net, alice, bob,
|
|
||||||
lntest.OpenChannelParams{
|
|
||||||
Amt: chanAmt,
|
|
||||||
PushAmt: pushAmt,
|
|
||||||
FundingShim: fundingShim,
|
|
||||||
SatPerVByte: satPerVbyte,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err := alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("alice didn't report "+
|
|
||||||
"channel: %v", err)
|
|
||||||
}
|
|
||||||
err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("bob didn't report "+
|
|
||||||
"channel: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cType, err := channelCommitType(alice, chanPoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("unable to get channel "+
|
|
||||||
"type: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the channel open, ensure that the amount specified above has
|
|
||||||
// properly been pushed to Bob.
|
|
||||||
aliceLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
|
|
||||||
checkChannelBalance(
|
|
||||||
alice, aliceChannelBalance, aliceLocalBalance, pushAmt,
|
|
||||||
)
|
|
||||||
checkChannelBalance(
|
|
||||||
bob, bobChannelBalance, pushAmt, aliceLocalBalance,
|
|
||||||
)
|
|
||||||
|
|
||||||
req := &lnrpc.ListChannelsRequest{}
|
|
||||||
aliceChannel, err := alice.ListChannels(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("unable to obtain chan: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bobChannel, err := bob.ListChannels(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("unable to obtain chan: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
closeChan := func() {
|
|
||||||
// Finally, immediately close the channel. This function will
|
|
||||||
// also block until the channel is closed and will additionally
|
|
||||||
// assert the relevant channel closing post conditions.
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout)
|
|
||||||
closeChannelAndAssert(ctxt, t, net, alice, chanPoint, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliceChannel.Channels[0], bobChannel.Channels[0], closeChan, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testBasicChannelFunding performs a test exercising expected behavior from a
|
|
||||||
// basic funding workflow. The test creates a new channel between Alice and
|
|
||||||
// Bob, then immediately closes the channel after asserting some expected post
|
|
||||||
// conditions. Finally, the chain itself is checked to ensure the closing
|
|
||||||
// transaction was mined.
|
|
||||||
func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|
||||||
|
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
// Run through the test with combinations of all the different
|
|
||||||
// commitment types.
|
|
||||||
allTypes := []commitType{
|
|
||||||
commitTypeLegacy,
|
|
||||||
commitTypeTweakless,
|
|
||||||
commitTypeAnchors,
|
|
||||||
}
|
|
||||||
|
|
||||||
test:
|
|
||||||
// We'll test all possible combinations of the feature bit presence
|
|
||||||
// that both nodes can signal for this new channel type. We'll make a
|
|
||||||
// new Carol+Dave for each test instance as well.
|
|
||||||
for _, carolCommitType := range allTypes {
|
|
||||||
for _, daveCommitType := range allTypes {
|
|
||||||
// Based on the current tweak variable for Carol, we'll
|
|
||||||
// preferentially signal the legacy commitment format.
|
|
||||||
// We do the same for Dave shortly below.
|
|
||||||
carolArgs := carolCommitType.Args()
|
|
||||||
carol, err := net.NewNode("Carol", carolArgs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create new node: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each time, we'll send Carol a new set of coins in
|
|
||||||
// order to fund the channel.
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to send coins to carol: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
daveArgs := daveCommitType.Args()
|
|
||||||
dave, err := net.NewNode("Dave", daveArgs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create new node: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Before we start the test, we'll ensure both sides
|
|
||||||
// are connected to the funding flow can properly be
|
|
||||||
// executed.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = net.EnsureConnected(ctxt, carol, dave)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to connect peers: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testName := fmt.Sprintf("carol_commit=%v,dave_commit=%v",
|
|
||||||
carolCommitType, daveCommitType)
|
|
||||||
|
|
||||||
ht := t
|
|
||||||
carolCommitType := carolCommitType
|
|
||||||
daveCommitType := daveCommitType
|
|
||||||
success := t.t.Run(testName, func(t *testing.T) {
|
|
||||||
carolChannel, daveChannel, closeChan, err := basicChannelFundingTest(
|
|
||||||
ht, net, carol, dave, nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed funding flow: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both nodes should report the same commitment
|
|
||||||
// type.
|
|
||||||
chansCommitType := carolChannel.CommitmentType
|
|
||||||
if daveChannel.CommitmentType != chansCommitType {
|
|
||||||
t.Fatalf("commit types don't match, "+
|
|
||||||
"carol got %v, dave got %v",
|
|
||||||
carolChannel.CommitmentType,
|
|
||||||
daveChannel.CommitmentType,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now check that the commitment type reported
|
|
||||||
// by both nodes is what we expect. It will be
|
|
||||||
// the minimum of the two nodes' preference, in
|
|
||||||
// the order Legacy, Tweakless, Anchors.
|
|
||||||
expType := carolCommitType
|
|
||||||
|
|
||||||
switch daveCommitType {
|
|
||||||
|
|
||||||
// Dave supports anchors, type will be what
|
|
||||||
// Carol supports.
|
|
||||||
case commitTypeAnchors:
|
|
||||||
|
|
||||||
// Dave only supports tweakless, channel will
|
|
||||||
// be downgraded to this type if Carol supports
|
|
||||||
// anchors.
|
|
||||||
case commitTypeTweakless:
|
|
||||||
if expType == commitTypeAnchors {
|
|
||||||
expType = commitTypeTweakless
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dave only supoprts legacy type, channel will
|
|
||||||
// be downgraded to this type.
|
|
||||||
case commitTypeLegacy:
|
|
||||||
expType = commitTypeLegacy
|
|
||||||
|
|
||||||
default:
|
|
||||||
t.Fatalf("invalid commit type %v",
|
|
||||||
daveCommitType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the signalled type matches what we
|
|
||||||
// expect.
|
|
||||||
switch {
|
|
||||||
case expType == commitTypeAnchors &&
|
|
||||||
chansCommitType == lnrpc.CommitmentType_ANCHORS:
|
|
||||||
|
|
||||||
case expType == commitTypeTweakless &&
|
|
||||||
chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
|
||||||
|
|
||||||
case expType == commitTypeLegacy &&
|
|
||||||
chansCommitType == lnrpc.CommitmentType_LEGACY:
|
|
||||||
|
|
||||||
default:
|
|
||||||
t.Fatalf("expected nodes to signal "+
|
|
||||||
"commit type %v, instead got "+
|
|
||||||
"%v", expType, chansCommitType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// As we've concluded this sub-test case we'll
|
|
||||||
// now close out the channel for both sides.
|
|
||||||
closeChan()
|
|
||||||
})
|
|
||||||
if !success {
|
|
||||||
break test
|
|
||||||
}
|
|
||||||
|
|
||||||
shutdownAndAssert(net, t, carol)
|
|
||||||
shutdownAndAssert(net, t, dave)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
|
||||||
// be used to fund channels.
|
|
||||||
func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
|
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
const (
|
|
||||||
chanAmt = funding.MaxBtcFundingAmount
|
|
||||||
pushAmt = btcutil.Amount(100000)
|
|
||||||
)
|
|
||||||
|
|
||||||
// We'll start off by creating a node for Carol.
|
|
||||||
carol, err := net.NewNode("Carol", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to create carol's node: %v", err)
|
|
||||||
}
|
|
||||||
defer shutdownAndAssert(net, t, carol)
|
|
||||||
|
|
||||||
// We'll send her some confirmed funds.
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = net.SendCoins(ctxt, 2*chanAmt, carol)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to send coins to carol: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now let Carol send some funds to herself, making a unconfirmed
|
|
||||||
// change output.
|
|
||||||
addrReq := &lnrpc.NewAddressRequest{
|
|
||||||
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
|
||||||
}
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
resp, err := carol.NewAddress(ctxt, addrReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to get new address: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sendReq := &lnrpc.SendCoinsRequest{
|
|
||||||
Addr: resp.Address,
|
|
||||||
Amount: int64(chanAmt) / 5,
|
|
||||||
}
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
_, err = carol.SendCoins(ctxt, sendReq)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to send coins: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the unconfirmed tx is seen in the mempool.
|
|
||||||
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to find tx in miner mempool: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, we'll connect her to Alice so that they can open a channel
|
|
||||||
// together. The funding flow should select Carol's unconfirmed output
|
|
||||||
// as she doesn't have any other funds since it's a new node.
|
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil {
|
|
||||||
t.Fatalf("unable to connect dave to alice: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
chanOpenUpdate := openChannelStream(
|
|
||||||
ctxt, t, net, carol, net.Alice,
|
|
||||||
lntest.OpenChannelParams{
|
|
||||||
Amt: chanAmt,
|
|
||||||
PushAmt: pushAmt,
|
|
||||||
SpendUnconfirmed: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
// Creates a helper closure to be used below which asserts the proper
|
|
||||||
// response to a channel balance RPC.
|
|
||||||
checkChannelBalance := func(node *lntest.HarnessNode,
|
|
||||||
local, remote, pendingLocal, pendingRemote btcutil.Amount) {
|
|
||||||
expectedResponse := &lnrpc.ChannelBalanceResponse{
|
|
||||||
LocalBalance: &lnrpc.Amount{
|
|
||||||
Sat: uint64(local),
|
|
||||||
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
||||||
local,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
RemoteBalance: &lnrpc.Amount{
|
|
||||||
Sat: uint64(remote),
|
|
||||||
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
||||||
remote,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
PendingOpenLocalBalance: &lnrpc.Amount{
|
|
||||||
Sat: uint64(pendingLocal),
|
|
||||||
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
||||||
pendingLocal,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
PendingOpenRemoteBalance: &lnrpc.Amount{
|
|
||||||
Sat: uint64(pendingRemote),
|
|
||||||
Msat: uint64(lnwire.NewMSatFromSatoshis(
|
|
||||||
pendingRemote,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
UnsettledLocalBalance: &lnrpc.Amount{},
|
|
||||||
UnsettledRemoteBalance: &lnrpc.Amount{},
|
|
||||||
// Deprecated fields.
|
|
||||||
Balance: int64(local),
|
|
||||||
PendingOpenBalance: int64(pendingLocal),
|
|
||||||
}
|
|
||||||
assertChannelBalanceResp(t, node, expectedResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
// As the channel is pending open, it's expected Carol has both zero
|
|
||||||
// local and remote balances, and pending local/remote should not be
|
|
||||||
// zero.
|
|
||||||
//
|
|
||||||
// Note that atm we haven't obtained the chanPoint yet, so we use the
|
|
||||||
// type directly.
|
|
||||||
cType := commitTypeTweakless
|
|
||||||
carolLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
|
|
||||||
checkChannelBalance(carol, 0, 0, carolLocalBalance, pushAmt)
|
|
||||||
|
|
||||||
// For Alice, her local/remote balances should be zero, and the
|
|
||||||
// local/remote balances are the mirror of Carol's.
|
|
||||||
checkChannelBalance(net.Alice, 0, 0, pushAmt, carolLocalBalance)
|
|
||||||
|
|
||||||
// Confirm the channel and wait for it to be recognized by both
|
|
||||||
// parties. Two transactions should be mined, the unconfirmed spend and
|
|
||||||
// the funding tx.
|
|
||||||
mineBlocks(t, net, 6, 2)
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error while waiting for channel open: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the channel open, we'll check the balances on each side of the
|
|
||||||
// channel as a sanity check to ensure things worked out as intended.
|
|
||||||
checkChannelBalance(carol, carolLocalBalance, pushAmt, 0, 0)
|
|
||||||
checkChannelBalance(net.Alice, pushAmt, carolLocalBalance, 0, 0)
|
|
||||||
|
|
||||||
// Now that we're done with the test, the channel can be closed.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
|
||||||
closeChannelAndAssert(ctxt, t, net, carol, chanPoint, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// testPaymentFollowingChannelOpen tests that the channel transition from
|
// testPaymentFollowingChannelOpen tests that the channel transition from
|
||||||
// 'pending' to 'open' state does not cause any inconsistencies within other
|
// 'pending' to 'open' state does not cause any inconsistencies within other
|
||||||
// subsystems trying to update the channel state in the db. We follow this
|
// subsystems trying to update the channel state in the db. We follow this
|
||||||
@ -14533,138 +14145,6 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testExternalFundingChanPoint tests that we're able to carry out a normal
|
|
||||||
// channel funding workflow given a channel point that was constructed outside
|
|
||||||
// the main daemon.
|
|
||||||
func testExternalFundingChanPoint(net *lntest.NetworkHarness, t *harnessTest) {
|
|
||||||
ctxb := context.Background()
|
|
||||||
|
|
||||||
// First, we'll create two new nodes that we'll use to open channel
|
|
||||||
// between for this test.
|
|
||||||
carol, err := net.NewNode("carol", nil)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
defer shutdownAndAssert(net, t, carol)
|
|
||||||
|
|
||||||
dave, err := net.NewNode("dave", nil)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
defer shutdownAndAssert(net, t, dave)
|
|
||||||
|
|
||||||
// Carol will be funding the channel, so we'll send some coins over to
|
|
||||||
// her and ensure they have enough confirmations before we proceed.
|
|
||||||
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Before we start the test, we'll ensure both sides are connected to
|
|
||||||
// the funding flow can properly be executed.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = net.EnsureConnected(ctxt, carol, dave)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// At this point, we're ready to simulate our external channel funding
|
|
||||||
// flow. To start with, we'll create a pending channel with a shim for
|
|
||||||
// a transaction that will never be published.
|
|
||||||
const thawHeight uint32 = 10
|
|
||||||
const chanSize = funding.MaxBtcFundingAmount
|
|
||||||
fundingShim1, chanPoint1, _ := deriveFundingShim(
|
|
||||||
net, t, carol, dave, chanSize, thawHeight, 1, false,
|
|
||||||
)
|
|
||||||
_ = openChannelStream(
|
|
||||||
ctxb, t, net, carol, dave, lntest.OpenChannelParams{
|
|
||||||
Amt: chanSize,
|
|
||||||
FundingShim: fundingShim1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
assertNumOpenChannelsPending(ctxt, t, carol, dave, 1)
|
|
||||||
|
|
||||||
// That channel is now pending forever and normally would saturate the
|
|
||||||
// max pending channel limit for both nodes. But because the channel is
|
|
||||||
// externally funded, we should still be able to open another one. Let's
|
|
||||||
// do exactly that now. For this one we publish the transaction so we
|
|
||||||
// can mine it later.
|
|
||||||
fundingShim2, chanPoint2, _ := deriveFundingShim(
|
|
||||||
net, t, carol, dave, chanSize, thawHeight, 2, true,
|
|
||||||
)
|
|
||||||
|
|
||||||
// At this point, we'll now carry out the normal basic channel funding
|
|
||||||
// test as everything should now proceed as normal (a regular channel
|
|
||||||
// funding flow).
|
|
||||||
carolChan, daveChan, _, err := basicChannelFundingTest(
|
|
||||||
t, net, carol, dave, fundingShim2,
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Both channels should be marked as frozen with the proper thaw
|
|
||||||
// height.
|
|
||||||
if carolChan.ThawHeight != thawHeight {
|
|
||||||
t.Fatalf("expected thaw height of %v, got %v",
|
|
||||||
carolChan.ThawHeight, thawHeight)
|
|
||||||
}
|
|
||||||
if daveChan.ThawHeight != thawHeight {
|
|
||||||
t.Fatalf("expected thaw height of %v, got %v",
|
|
||||||
daveChan.ThawHeight, thawHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, to make sure the channel functions as normal, we'll make some
|
|
||||||
// payments within the channel.
|
|
||||||
payAmt := btcutil.Amount(100000)
|
|
||||||
invoice := &lnrpc.Invoice{
|
|
||||||
Memo: "new chans",
|
|
||||||
Value: int64(payAmt),
|
|
||||||
}
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
resp, err := dave.AddInvoice(ctxt, invoice)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
err = completePaymentRequests(
|
|
||||||
ctxt, carol, carol.RouterClient, []string{resp.PaymentRequest},
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// Now that the channels are open, and we've confirmed that they're
|
|
||||||
// operational, we'll now ensure that the channels are frozen as
|
|
||||||
// intended (if requested).
|
|
||||||
//
|
|
||||||
// First, we'll try to close the channel as Carol, the initiator. This
|
|
||||||
// should fail as a frozen channel only allows the responder to
|
|
||||||
// initiate a channel close.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
|
||||||
_, _, err = net.CloseChannel(ctxt, carol, chanPoint2, false)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("carol wasn't denied a co-op close attempt for a " +
|
|
||||||
"frozen channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next we'll try but this time with Dave (the responder) as the
|
|
||||||
// initiator. This time the channel should be closed as normal.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
|
||||||
closeChannelAndAssert(ctxt, t, net, dave, chanPoint2, false)
|
|
||||||
|
|
||||||
// As a last step, we check if we still have the pending channel hanging
|
|
||||||
// around because we never published the funding TX.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
assertNumOpenChannelsPending(ctxt, t, carol, dave, 1)
|
|
||||||
|
|
||||||
// Let's make sure we can abandon it.
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
_, err = carol.AbandonChannel(ctxt, &lnrpc.AbandonChannelRequest{
|
|
||||||
ChannelPoint: chanPoint1,
|
|
||||||
PendingFundingShimOnly: true,
|
|
||||||
})
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
|
||||||
_, err = dave.AbandonChannel(ctxt, &lnrpc.AbandonChannelRequest{
|
|
||||||
ChannelPoint: chanPoint1,
|
|
||||||
PendingFundingShimOnly: true,
|
|
||||||
})
|
|
||||||
require.NoError(t.t, err)
|
|
||||||
|
|
||||||
// It should now not appear in the pending channels anymore.
|
|
||||||
assertNumOpenChannelsPending(ctxt, t, carol, dave, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deriveFundingShim creates a channel funding shim by deriving the necessary
|
// deriveFundingShim creates a channel funding shim by deriving the necessary
|
||||||
// keys on both sides.
|
// keys on both sides.
|
||||||
func deriveFundingShim(net *lntest.NetworkHarness, t *harnessTest,
|
func deriveFundingShim(net *lntest.NetworkHarness, t *harnessTest,
|
||||||
|
Loading…
Reference in New Issue
Block a user