multi: move itest out of lntest

This commit moves all the test cases living in `itest` out of `lntest`,
further making `lntest` an independent package for general testing.
This commit is contained in:
yyforyongyu committed 2023-02-23 21:56:08 +08:00
1 parent 142b00711f
commit 0bc86a3b4b
59 files changed
+19 -19

No files matched your search

File diff suppressed because it is too large. Load diff
-8
View File
@@ -1,8 +0,0 @@
//go:build !rpctest
// +build !rpctest
package itest
import "github.com/lightningnetwork/lnd/lntemp"
var allTestCases = []*lntemp.TestCase{}
-515
View File
@@ -1,515 +0,0 @@
//go:build rpctest
// +build rpctest
package itest
import "github.com/lightningnetwork/lnd/lntemp"
var allTestCases = []*lntemp.TestCase{
{
Name: "update channel status",
TestFunc: testUpdateChanStatus,
},
{
Name: "basic funding flow",
TestFunc: testBasicChannelFunding,
},
{
Name: "multi hop htlc local timeout",
TestFunc: testMultiHopHtlcLocalTimeout,
},
{
Name: "multi hop receiver chain claim",
TestFunc: testMultiHopReceiverChainClaim,
},
{
Name: "multi hop local force close on-chain htlc timeout",
TestFunc: testMultiHopLocalForceCloseOnChainHtlcTimeout,
},
{
Name: "multi hop remote force close on-chain htlc timeout",
TestFunc: testMultiHopRemoteForceCloseOnChainHtlcTimeout,
},
{
Name: "multi hop htlc local chain claim",
TestFunc: testMultiHopHtlcLocalChainClaim,
},
{
Name: "multi hop htlc remote chain claim",
TestFunc: testMultiHopHtlcRemoteChainClaim,
},
{
Name: "multi hop htlc aggregation",
TestFunc: testMultiHopHtlcAggregation,
},
{
Name: "external channel funding",
TestFunc: testExternalFundingChanPoint,
},
{
Name: "channel backup restore basic",
TestFunc: testChannelBackupRestoreBasic,
},
{
Name: "channel backup restore unconfirmed",
TestFunc: testChannelBackupRestoreUnconfirmed,
},
{
Name: "channel backup restore commit types",
TestFunc: testChannelBackupRestoreCommitTypes,
},
{
Name: "channel backup restore force close",
TestFunc: testChannelBackupRestoreForceClose,
},
{
Name: "channel backup restore legacy",
TestFunc: testChannelBackupRestoreLegacy,
},
{
Name: "data loss protection",
TestFunc: testDataLossProtection,
},
{
Name: "sweep coins",
TestFunc: testSweepAllCoins,
},
{
Name: "disconnecting target peer",
TestFunc: testDisconnectingTargetPeer,
},
{
Name: "sphinx replay persistence",
TestFunc: testSphinxReplayPersistence,
},
{
Name: "list channels",
TestFunc: testListChannels,
},
{
Name: "max pending channel",
TestFunc: testMaxPendingChannels,
},
{
Name: "garbage collect link nodes",
TestFunc: testGarbageCollectLinkNodes,
},
{
Name: "reject onward htlc",
TestFunc: testRejectHTLC,
},
{
Name: "node sign verify",
TestFunc: testNodeSignVerify,
},
{
Name: "list addresses",
TestFunc: testListAddresses,
},
{
Name: "abandonchannel",
TestFunc: testAbandonChannel,
},
{
Name: "recovery info",
TestFunc: testGetRecoveryInfo,
},
{
Name: "onchain fund recovery",
TestFunc: testOnchainFundRecovery,
},
{
Name: "wallet rescan address detection",
TestFunc: testRescanAddressDetection,
},
{
Name: "basic funding flow with all input types",
TestFunc: testChannelFundingInputTypes,
},
{
Name: "unconfirmed channel funding",
TestFunc: testUnconfirmedChannelFunding,
},
{
Name: "funding flow persistence",
TestFunc: testChannelFundingPersistence,
},
{
Name: "batch channel funding",
TestFunc: testBatchChanFunding,
},
{
Name: "update channel policy",
TestFunc: testUpdateChannelPolicy,
},
{
Name: "send update disable channel",
TestFunc: testSendUpdateDisableChannel,
},
{
Name: "private channel update policy",
TestFunc: testUpdateChannelPolicyForPrivateChannel,
},
{
Name: "update channel policy fee rate accuracy",
TestFunc: testUpdateChannelPolicyFeeRateAccuracy,
},
{
Name: "connection timeout",
TestFunc: testNetworkConnectionTimeout,
},
{
Name: "reconnect after ip change",
TestFunc: testReconnectAfterIPChange,
},
{
Name: "addpeer config",
TestFunc: testAddPeerConfig,
},
{
Name: "unannounced channels",
TestFunc: testUnannouncedChannels,
},
{
Name: "graph topology notifications",
TestFunc: testGraphTopologyNotifications,
},
{
Name: "node announcement",
TestFunc: testNodeAnnouncement,
},
{
Name: "update node announcement rpc",
TestFunc: testUpdateNodeAnnouncement,
},
{
Name: "list outgoing payments",
TestFunc: testListPayments,
},
{
Name: "immediate payment after channel opened",
TestFunc: testPaymentFollowingChannelOpen,
},
{
Name: "invoice update subscription",
TestFunc: testInvoiceSubscriptions,
},
{
Name: "streaming channel backup update",
TestFunc: testChannelBackupUpdates,
},
{
Name: "export channel backup",
TestFunc: testExportChannelBackup,
},
{
Name: "channel balance",
TestFunc: testChannelBalance,
},
{
Name: "channel unsettled balance",
TestFunc: testChannelUnsettledBalance,
},
{
Name: "commitment deadline",
TestFunc: testCommitmentTransactionDeadline,
},
{
Name: "channel force closure",
TestFunc: testChannelForceClosure,
},
{
Name: "failing link",
TestFunc: testFailingChannel,
},
{
Name: "chain kit",
TestFunc: testChainKit,
},
{
Name: "neutrino kit",
TestFunc: testNeutrino,
},
{
Name: "etcd failover",
TestFunc: testEtcdFailover,
},
{
Name: "hold invoice force close",
TestFunc: testHoldInvoiceForceClose,
},
{
Name: "hold invoice sender persistence",
TestFunc: testHoldInvoicePersistence,
},
{
Name: "maximum channel size",
TestFunc: testMaxChannelSize,
},
{
Name: "wumbo channels",
TestFunc: testWumboChannels,
},
{
Name: "max htlc pathfind",
TestFunc: testMaxHtlcPathfind,
},
{
Name: "multi-hop htlc error propagation",
TestFunc: testHtlcErrorPropagation,
},
{
Name: "multi-hop payments",
TestFunc: testMultiHopPayments,
},
{
Name: "anchors reserved value",
TestFunc: testAnchorReservedValue,
},
{
Name: "3rd party anchor spend",
TestFunc: testAnchorThirdPartySpend,
},
{
Name: "open channel reorg test",
TestFunc: testOpenChannelAfterReorg,
},
{
Name: "psbt channel funding",
TestFunc: testPsbtChanFunding,
},
{
Name: "psbt channel funding external",
TestFunc: testPsbtChanFundingExternal,
},
{
Name: "psbt channel funding single step",
TestFunc: testPsbtChanFundingSingleStep,
},
{
Name: "sign psbt",
TestFunc: testSignPsbt,
},
{
Name: "resolution handoff",
TestFunc: testResHandoff,
},
{
Name: "REST API",
TestFunc: testRestAPI,
},
{
Name: "revoked uncooperative close retribution",
TestFunc: testRevokedCloseRetribution,
},
{
Name: "revoked uncooperative close retribution zero value " +
"remote output",
TestFunc: testRevokedCloseRetributionZeroValueRemoteOutput,
},
{
Name: "revoked uncooperative close retribution remote hodl",
TestFunc: testRevokedCloseRetributionRemoteHodl,
},
{
Name: "revoked uncooperative close retribution altruist " +
"watchtower",
TestFunc: testRevokedCloseRetributionAltruistWatchtower,
},
{
Name: "single-hop send to route",
TestFunc: testSingleHopSendToRoute,
},
{
Name: "multi-hop send to route",
TestFunc: testMultiHopSendToRoute,
},
{
Name: "send to route error propagation",
TestFunc: testSendToRouteErrorPropagation,
},
{
Name: "private channels",
TestFunc: testPrivateChannels,
},
{
Name: "invoice routing hints",
TestFunc: testInvoiceRoutingHints,
},
{
Name: "multi-hop payments over private channels",
TestFunc: testMultiHopOverPrivateChannels,
},
{
Name: "query routes",
TestFunc: testQueryRoutes,
},
{
Name: "route fee cutoff",
TestFunc: testRouteFeeCutoff,
},
{
Name: "rpc middleware interceptor",
TestFunc: testRPCMiddlewareInterceptor,
},
{
Name: "macaroon authentication",
TestFunc: testMacaroonAuthentication,
},
{
Name: "bake macaroon",
TestFunc: testBakeMacaroon,
},
{
Name: "delete macaroon id",
TestFunc: testDeleteMacaroonID,
},
{
Name: "stateless init",
TestFunc: testStatelessInit,
},
{
Name: "single hop invoice",
TestFunc: testSingleHopInvoice,
},
{
Name: "wipe forwarding packages",
TestFunc: testWipeForwardingPackages,
},
{
Name: "switch circuit persistence",
TestFunc: testSwitchCircuitPersistence,
},
{
Name: "switch offline delivery",
TestFunc: testSwitchOfflineDelivery,
},
{
Name: "switch offline delivery persistence",
TestFunc: testSwitchOfflineDeliveryPersistence,
},
{
Name: "switch offline delivery outgoing offline",
TestFunc: testSwitchOfflineDeliveryOutgoingOffline,
},
{
Name: "sendtoroute multi path payment",
TestFunc: testSendToRouteMultiPath,
},
{
Name: "send multi path payment",
TestFunc: testSendMultiPathPayment,
},
{
Name: "sendpayment amp invoice",
TestFunc: testSendPaymentAMPInvoice,
},
{
Name: "sendpayment amp invoice repeat",
TestFunc: testSendPaymentAMPInvoiceRepeat,
},
{
Name: "send payment amp",
TestFunc: testSendPaymentAMP,
},
{
Name: "sendtoroute amp",
TestFunc: testSendToRouteAMP,
},
{
Name: "forward interceptor dedup htlcs",
TestFunc: testForwardInterceptorDedupHtlc,
},
{
Name: "forward interceptor",
TestFunc: testForwardInterceptorBasic,
},
{
Name: "zero conf channel open",
TestFunc: testZeroConfChannelOpen,
},
{
Name: "option scid alias",
TestFunc: testOptionScidAlias,
},
{
Name: "scid alias channel update",
TestFunc: testUpdateChannelPolicyScidAlias,
},
{
Name: "scid alias upgrade",
TestFunc: testOptionScidUpgrade,
},
{
Name: "nonstd sweep",
TestFunc: testNonstdSweep,
},
{
Name: "multiple channel creation and update subscription",
TestFunc: testBasicChannelCreationAndUpdates,
},
{
Name: "derive shared key",
TestFunc: testDeriveSharedKey,
},
{
Name: "sign output raw",
TestFunc: testSignOutputRaw,
},
{
Name: "sign verify message",
TestFunc: testSignVerifyMessage,
},
{
Name: "cpfp",
TestFunc: testCPFP,
},
{
Name: "taproot",
TestFunc: testTaproot,
},
{
Name: "wallet import account",
TestFunc: testWalletImportAccount,
},
{
Name: "wallet import pubkey",
TestFunc: testWalletImportPubKey,
},
{
Name: "async payments benchmark",
TestFunc: testAsyncPayments,
},
{
Name: "remote signer",
TestFunc: testRemoteSigner,
},
{
Name: "taproot coop close",
TestFunc: testTaprootCoopClose,
},
{
Name: "trackpayments",
TestFunc: testTrackPayments,
},
{
Name: "open channel fee policy",
TestFunc: testOpenChannelUpdateFeePolicy,
},
{
Name: "custom message",
TestFunc: testCustomMessage,
},
{
Name: "sign verify message with addr",
TestFunc: testSignVerifyMessageWithAddr,
},
{
Name: "zero conf reorg edge existence",
TestFunc: testZeroConfReorg,
},
{
Name: "async bidirectional payments",
TestFunc: testBidirectionalAsyncPayments,
},
}
-660
View File
@@ -1,660 +0,0 @@
package itest
import (
"encoding/hex"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/amp"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// testSendPaymentAMPInvoice tests that we can send an AMP payment to a
// specified AMP invoice using SendPaymentV2.
func testSendPaymentAMPInvoice(ht *lntemp.HarnessTest) {
succeed := ht.Run("native payaddr", func(t *testing.T) {
tt := ht.Subtest(t)
testSendPaymentAMPInvoiceCase(tt, false)
})
// Abort the test if failed.
if !succeed {
return
}
ht.Run("external payaddr", func(t *testing.T) {
tt := ht.Subtest(t)
testSendPaymentAMPInvoiceCase(tt, true)
})
}
func testSendPaymentAMPInvoiceCase(ht *lntemp.HarnessTest,
useExternalPayAddr bool) {
mts := newMppTestScenario(ht)
// Subscribe to bob's invoices. Do this early in the test to make sure
// that the subscription has actually been completed when we add an
// invoice. Otherwise the notification will be missed.
req := &lnrpc.InvoiceSubscription{}
bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req)
const paymentAmt = btcutil.Amount(300000)
// Set up a network with three different paths Alice <-> Bob. Channel
// capacities are set such that the payment can only succeed if (at
// least) three paths are used.
//
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
mppReq := &mppOpenChannelRequest{
amtAliceCarol: 235000,
amtAliceDave: 135000,
amtCarolBob: 135000,
amtCarolEve: 135000,
amtDaveBob: 135000,
amtEveBob: 135000,
}
mts.openChannels(mppReq)
chanPointAliceDave := mts.channelPoints[1]
chanPointDaveBob := mts.channelPoints[4]
invoice := &lnrpc.Invoice{
Value: int64(paymentAmt),
IsAmp: true,
}
addInvoiceResp := mts.bob.RPC.AddInvoice(invoice)
// Ensure we get a notification of the invoice being added by Bob.
rpcInvoice := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, 0, len(rpcInvoice.Htlcs))
// Increase Dave's fee to make the test deterministic. Otherwise it
// would be unpredictable whether pathfinding would go through Charlie
// or Dave for the first shard.
expectedPolicy := mts.updateDaveGlobalPolicy()
// Make sure Alice has heard it for both Dave's channels.
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointDaveBob, false,
)
// Generate an external payment address when attempting to pseudo-reuse
// an AMP invoice. When using an external payment address, we'll also
// expect an extra invoice to appear in the ListInvoices response, since
// a new invoice will be JIT inserted under a different payment address
// than the one in the invoice.
var (
expNumInvoices = 1
externalPayAddr []byte
)
if useExternalPayAddr {
expNumInvoices = 2
externalPayAddr = ht.Random32Bytes()
}
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: addInvoiceResp.PaymentRequest,
PaymentAddr: externalPayAddr,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
payment := ht.SendPaymentAssertSettled(mts.alice, sendReq)
// Check that Alice split the payment in at least three shards. Because
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
// there is some non-determinism in the process. Depending on whether
// the new pathfinding round is started before or after the htlc is
// locked into the channel, different sharding may occur. Therefore we
// can only check if the number of shards isn't below the theoretical
// minimum.
succeeded := 0
for _, htlc := range payment.Htlcs {
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
succeeded++
}
}
const minExpectedShards = 3
require.GreaterOrEqual(ht, succeeded, minExpectedShards,
"expected num of shards not reached")
// When an external payment address is supplied, we'll get an extra
// notification for the JIT inserted invoice, since it differs from the
// original.
if useExternalPayAddr {
ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
}
// There should now be a settle event for the invoice.
rpcInvoice = ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
// the one received via the subscription.
invoices := ht.AssertNumInvoices(mts.bob, expNumInvoices)
ht.AssertInvoiceEqual(rpcInvoice, invoices[expNumInvoices-1])
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
// Finally, assert that the same set id is recorded for each htlc, and
// that the preimage hash pair is valid.
var setID []byte
require.Equal(ht, succeeded, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
if setID == nil {
setID = make([]byte, 32)
copy(setID, htlc.Amp.SetId)
}
require.Equal(ht, setID, htlc.Amp.SetId)
// Parse the child hash and child preimage, and assert they are
// well-formed.
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
require.NoError(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
}
// The set ID we extract above should be shown in the final settled
// state.
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
// Finally, close all channels.
mts.closeChannels()
}
// testSendPaymentAMPInvoiceRepeat tests that it's possible to pay an AMP
// invoice multiple times by having the client generate a new setID each time.
func testSendPaymentAMPInvoiceRepeat(ht *lntemp.HarnessTest) {
// In this basic test, we'll only need two nodes as we want to
// primarily test the recurring payment feature. So we'll re-use the
carol := ht.NewNode("Carol", nil)
// Send Carol enough coins to be able to open a channel to Dave.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
dave := ht.NewNode("Dave", nil)
// Set up an invoice subscription so we can be notified when Dave
// receives his repeated payments.
req := &lnrpc.InvoiceSubscription{}
invSubscription := dave.RPC.SubscribeInvoices(req)
// Before we start the test, we'll ensure both sides are connected to
// the funding flow can properly be executed.
ht.EnsureConnected(carol, dave)
// Establish a channel between Carol and Dave.
chanAmt := btcutil.Amount(100_000)
ht.OpenChannel(
carol, dave, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Create an AMP invoice of a trivial amount, that we'll pay repeatedly
// in this integration test.
paymentAmt := 10000
invoice := &lnrpc.Invoice{
Value: int64(paymentAmt),
IsAmp: true,
}
addInvoiceResp := dave.RPC.AddInvoice(invoice)
// We should get an initial notification that the HTLC has been added.
rpcInvoice := ht.ReceiveInvoiceUpdate(invSubscription)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, 0, len(rpcInvoice.Htlcs))
// Now we'll use Carol to pay the invoice that Dave created.
ht.CompletePaymentRequests(
carol, []string{addInvoiceResp.PaymentRequest},
)
// Dave should get a notification that the invoice has been settled.
invoiceNtfn := ht.ReceiveInvoiceUpdate(invSubscription)
// The notification should signal that the invoice is now settled, and
// should also include the set ID, and show the proper amount paid.
require.True(ht, invoiceNtfn.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceNtfn.State)
require.Equal(ht, paymentAmt, int(invoiceNtfn.AmtPaidSat))
require.Equal(ht, 1, len(invoiceNtfn.AmpInvoiceState))
var firstSetID []byte
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
firstSetID, _ = hex.DecodeString(setIDStr)
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED,
ampState.State)
}
// Pay the invoice again, we should get another notification that Dave
// has received another payment.
ht.CompletePaymentRequests(
carol, []string{addInvoiceResp.PaymentRequest},
)
// Dave should get another notification.
invoiceNtfn = ht.ReceiveInvoiceUpdate(invSubscription)
// The invoice should still be shown as settled, and also include the
// information about this newly generated setID, showing 2x the amount
// paid.
require.True(ht, invoiceNtfn.Settled)
require.Equal(ht, paymentAmt*2, int(invoiceNtfn.AmtPaidSat))
var secondSetID []byte
for setIDStr, ampState := range invoiceNtfn.AmpInvoiceState {
secondSetID, _ = hex.DecodeString(setIDStr)
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED,
ampState.State)
}
// The returned invoice should only include a single HTLC since we
// return the "projected" sub-invoice for a given setID.
require.Equal(ht, 1, len(invoiceNtfn.Htlcs))
// However the AMP state index should show that there've been two
// repeated payments to this invoice so far.
require.Equal(ht, 2, len(invoiceNtfn.AmpInvoiceState))
// Now we'll look up the invoice using the new LookupInvoice2 RPC call
// by the set ID of each of the invoices.
msg := &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
SetId: firstSetID,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
}
subInvoice1 := dave.RPC.LookupInvoiceV2(msg)
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_SetId{
SetId: secondSetID,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_ONLY,
}
subInvoice2 := dave.RPC.LookupInvoiceV2(msg)
// Each invoice should only show a single HTLC present, as we passed
// the HTLC set only modifier.
require.Equal(ht, 1, len(subInvoice1.Htlcs))
require.Equal(ht, 1, len(subInvoice2.Htlcs))
// If we look up the same invoice, by its payment address, but now with
// the HTLC blank modifier, then none of them should be returned.
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: addInvoiceResp.PaymentAddr,
},
LookupModifier: invoicesrpc.LookupModifier_HTLC_SET_BLANK,
}
rootInvoice := dave.RPC.LookupInvoiceV2(msg)
require.Equal(ht, 0, len(rootInvoice.Htlcs))
// If we look up the same invoice, by its payment address, but without
// that modified, then we should get all the relevant HTLCs.
msg = &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: addInvoiceResp.PaymentAddr,
},
}
rootInvoice = dave.RPC.LookupInvoiceV2(msg)
require.Equal(ht, 2, len(rootInvoice.Htlcs))
// Finally, we'll test that if we subscribe for notifications of
// settled invoices, we get a backlog, which includes the invoice we
// settled last (since you can only fetch from index 1 onwards), and
// only the relevant set of HTLCs.
req = &lnrpc.InvoiceSubscription{
SettleIndex: 1,
}
invSub2 := dave.RPC.SubscribeInvoices(req)
// The first invoice we get back should match the state of the invoice
// after our second payment: amt updated, but only a single HTLC shown
// through.
backlogInv := ht.ReceiveInvoiceUpdate(invSub2)
require.Equal(ht, 1, len(backlogInv.Htlcs))
require.Equal(ht, 2, len(backlogInv.AmpInvoiceState))
require.True(ht, backlogInv.Settled)
require.Equal(ht, paymentAmt*2, int(backlogInv.AmtPaidSat))
}
// testSendPaymentAMP tests that we can send an AMP payment to a specified
// destination using SendPaymentV2.
func testSendPaymentAMP(ht *lntemp.HarnessTest) {
mts := newMppTestScenario(ht)
const paymentAmt = btcutil.Amount(300000)
// Set up a network with three different paths Alice <-> Bob. Channel
// capacities are set such that the payment can only succeed if (at
// least) three paths are used.
//
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
mppReq := &mppOpenChannelRequest{
amtAliceCarol: 235000,
amtAliceDave: 135000,
amtCarolBob: 135000,
amtCarolEve: 135000,
amtDaveBob: 135000,
amtEveBob: 135000,
}
mts.openChannels(mppReq)
chanPointAliceDave := mts.channelPoints[1]
// Increase Dave's fee to make the test deterministic. Otherwise it
// would be unpredictable whether pathfinding would go through Charlie
// or Dave for the first shard.
expectedPolicy := mts.updateDaveGlobalPolicy()
// Make sure Alice has heard it.
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)
sendReq := &routerrpc.SendPaymentRequest{
Dest: mts.bob.PubKey[:],
Amt: int64(paymentAmt),
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
Amp: true,
}
payment := ht.SendPaymentAssertSettled(mts.alice, sendReq)
// Check that Alice split the payment in at least three shards. Because
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
// there is some non-determinism in the process. Depending on whether
// the new pathfinding round is started before or after the htlc is
// locked into the channel, different sharding may occur. Therefore we
// can only check if the number of shards isn't below the theoretical
// minimum.
succeeded := 0
for _, htlc := range payment.Htlcs {
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
succeeded++
}
}
const minExpectedShards = 3
require.GreaterOrEqual(ht, succeeded, minExpectedShards,
"expected num of shards not reached")
// Fetch Bob's invoices. There should only be one invoice.
invoices := ht.AssertNumInvoices(mts.bob, 1)
rpcInvoice := invoices[0]
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
// Finally, assert that the same set id is recorded for each htlc, and
// that the preimage hash pair is valid.
var setID []byte
require.Equal(ht, succeeded, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
if setID == nil {
setID = make([]byte, 32)
copy(setID, htlc.Amp.SetId)
}
require.Equal(ht, setID, htlc.Amp.SetId)
// Parse the child hash and child preimage, and assert they are
// well-formed.
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
require.NoError(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
}
// The set ID we extract above should be shown in the final settled
// state.
ampState := rpcInvoice.AmpInvoiceState[hex.EncodeToString(setID)]
require.Equal(ht, lnrpc.InvoiceHTLCState_SETTLED, ampState.State)
// Finally, close all channels.
mts.closeChannels()
}
func testSendToRouteAMP(ht *lntemp.HarnessTest) {
mts := newMppTestScenario(ht)
const (
paymentAmt = btcutil.Amount(300000)
numShards = 3
shardAmt = paymentAmt / numShards
chanAmt = shardAmt * 3 / 2
)
// Subscribe to bob's invoices.
req := &lnrpc.InvoiceSubscription{}
bobInvoiceSubscription := mts.bob.RPC.SubscribeInvoices(req)
// Set up a network with three different paths Alice <-> Bob.
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
///
mppReq := &mppOpenChannelRequest{
// Since the channel Alice-> Carol will have to carry two
// shards, we make it larger.
amtAliceCarol: chanAmt + shardAmt,
amtAliceDave: chanAmt,
amtCarolBob: chanAmt,
amtCarolEve: chanAmt,
amtDaveBob: chanAmt,
amtEveBob: chanAmt,
}
mts.openChannels(mppReq)
// We'll send shards along three routes from Alice.
sendRoutes := [numShards][]*node.HarnessNode{
{mts.carol, mts.bob},
{mts.dave, mts.bob},
{mts.carol, mts.eve, mts.bob},
}
payAddr := ht.Random32Bytes()
setID := ht.Random32Bytes()
var sharer amp.Sharer
sharer, err := amp.NewSeedSharer()
require.NoError(ht, err)
childPreimages := make(map[lntypes.Preimage]uint32)
responses := make(chan *lnrpc.HTLCAttempt, len(sendRoutes))
// Define a closure for sending each of the three shards.
sendShard := func(i int, hops []*node.HarnessNode) {
// Build a route for the specified hops.
r := mts.buildRoute(shardAmt, mts.alice, hops)
// Set the MPP records to indicate this is a payment shard.
hop := r.Hops[len(r.Hops)-1]
hop.TlvPayload = true
hop.MppRecord = &lnrpc.MPPRecord{
PaymentAddr: payAddr,
TotalAmtMsat: int64(paymentAmt * 1000),
}
var child *amp.Child
if i < len(sendRoutes)-1 {
var left amp.Sharer
left, sharer, err = sharer.Split()
require.NoError(ht, err)
child = left.Child(uint32(i))
} else {
child = sharer.Child(uint32(i))
}
childPreimages[child.Preimage] = child.Index
hop.AmpRecord = &lnrpc.AMPRecord{
RootShare: child.Share[:],
SetId: setID,
ChildIndex: child.Index,
}
// Send the shard.
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: child.Hash[:],
Route: r,
}
// We'll send all shards in their own goroutine, since
// SendToRoute will block as long as the payment is in flight.
go func() {
resp := mts.alice.RPC.SendToRouteV2(sendReq)
responses <- resp
}()
}
// Send the first shard, this cause Bob to JIT add an invoice.
sendShard(0, sendRoutes[0])
// Ensure we get a notification of the invoice being added by Bob.
rpcInvoice, err := bobInvoiceSubscription.Recv()
require.NoError(ht, err)
require.False(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_OPEN, rpcInvoice.State)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(0), rpcInvoice.AmtPaidMsat)
require.Equal(ht, payAddr, rpcInvoice.PaymentAddr)
require.Equal(ht, 0, len(rpcInvoice.Htlcs))
sendShard(1, sendRoutes[1])
sendShard(2, sendRoutes[2])
// Assert that all of the child preimages are unique.
require.Equal(ht, len(sendRoutes), len(childPreimages))
// Make a copy of the childPreimages map for validating the resulting
// invoice.
childPreimagesCopy := make(map[lntypes.Preimage]uint32)
for preimage, childIndex := range childPreimages {
childPreimagesCopy[preimage] = childIndex
}
// Wait for all responses to be back, and check that they all
// succeeded.
timer := time.After(defaultTimeout)
for range sendRoutes {
var resp *lnrpc.HTLCAttempt
select {
case resp = <-responses:
case <-timer:
require.Fail(ht, "response not received")
}
require.Nil(ht, resp.Failure, "received payment failure")
preimage, err := lntypes.MakePreimage(resp.Preimage)
require.NoError(ht, err)
// Assert that the response includes one of our child preimages.
_, ok := childPreimages[preimage]
require.True(ht, ok)
// Remove this preimage from out set so that we ensure all
// responses have a unique child preimage.
delete(childPreimages, preimage)
}
childPreimages = childPreimagesCopy
// There should now be a settle event for the invoice.
rpcInvoice, err = bobInvoiceSubscription.Recv()
require.NoError(ht, err)
// Also fetch Bob's invoice from ListInvoices and assert it is equal to
// the one received via the subscription.
invoices := ht.AssertNumInvoices(mts.bob, 1)
ht.AssertInvoiceEqual(rpcInvoice, invoices[0])
// Assert that the invoice is settled for the total payment amount and
// has the correct payment address.
require.True(ht, rpcInvoice.Settled)
require.Equal(ht, lnrpc.Invoice_SETTLED, rpcInvoice.State)
require.Equal(ht, int64(paymentAmt), rpcInvoice.AmtPaidSat)
require.Equal(ht, int64(paymentAmt*1000), rpcInvoice.AmtPaidMsat)
require.Equal(ht, payAddr, rpcInvoice.PaymentAddr)
// Finally, assert that the proper set id is recorded for each htlc, and
// that the preimage hash pair is valid.
require.Equal(ht, numShards, len(rpcInvoice.Htlcs))
for _, htlc := range rpcInvoice.Htlcs {
require.NotNil(ht, htlc.Amp)
require.Equal(ht, setID, htlc.Amp.SetId)
// Parse the child hash and child preimage, and assert they are
// well-formed.
childHash, err := lntypes.MakeHash(htlc.Amp.Hash)
require.NoError(ht, err)
childPreimage, err := lntypes.MakePreimage(htlc.Amp.Preimage)
require.NoError(ht, err)
// Assert that the preimage actually matches the hashes.
validPreimage := childPreimage.Matches(childHash)
require.True(ht, validPreimage)
// Assert that the HTLC includes one of our child preimages.
childIndex, ok := childPreimages[childPreimage]
require.True(ht, ok)
// Assert that the correct child index is reflected.
require.Equal(ht, childIndex, htlc.Amp.ChildIndex)
// Remove this preimage from our set so that we ensure all HTLCs
// have a unique child preimage.
delete(childPreimages, childPreimage)
}
// Finally, close all channels.
mts.closeChannels()
}
File diff suppressed because it is too large. Load diff
-208
View File
@@ -1,208 +0,0 @@
package itest
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// testChannelBalance creates a new channel between Alice and Bob, then checks
// channel balance to be equal amount specified while creation of channel.
func testChannelBalance(ht *lntemp.HarnessTest) {
// Open a channel with 0.16 BTC between Alice and Bob, ensuring the
// channel has been opened properly.
amount := funding.MaxBtcFundingAmount
// Creates a helper closure to be used below which asserts the proper
// response to a channel balance RPC.
checkChannelBalance := func(node *node.HarnessNode,
local, remote 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,
)),
},
UnsettledLocalBalance: &lnrpc.Amount{},
UnsettledRemoteBalance: &lnrpc.Amount{},
PendingOpenLocalBalance: &lnrpc.Amount{},
PendingOpenRemoteBalance: &lnrpc.Amount{},
// Deprecated fields.
Balance: int64(local),
}
ht.AssertChannelBalanceResp(node, expectedResponse)
}
// Before beginning, make sure alice and bob are connected.
alice, bob := ht.Alice, ht.Bob
ht.EnsureConnected(alice, bob)
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: amount},
)
cType := ht.GetChannelCommitType(alice, chanPoint)
// As this is a single funder channel, Alice's balance should be
// exactly 0.5 BTC since now state transitions have taken place yet.
checkChannelBalance(alice, amount-calcStaticFee(cType, 0), 0)
// Ensure Bob currently has no available balance within the channel.
checkChannelBalance(bob, 0, amount-calcStaticFee(cType, 0))
// Finally close the channel between Alice and Bob, asserting that the
// channel has been properly closed on-chain.
ht.CloseChannel(alice, chanPoint)
}
// testChannelUnsettledBalance will test that the UnsettledBalance field
// is updated according to the number of Pending Htlcs.
// Alice will send Htlcs to Carol while she is in hodl mode. This will result
// in a build of pending Htlcs. We expect the channels unsettled balance to
// equal the sum of all the Pending Htlcs.
func testChannelUnsettledBalance(ht *lntemp.HarnessTest) {
const chanAmt = btcutil.Amount(1000000)
// Creates a helper closure to be used below which asserts the proper
// response to a channel balance RPC.
checkChannelBalance := func(node *node.HarnessNode,
local, remote, unsettledLocal, unsettledRemote 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,
)),
},
UnsettledLocalBalance: &lnrpc.Amount{
Sat: uint64(unsettledLocal),
Msat: uint64(lnwire.NewMSatFromSatoshis(
unsettledLocal,
)),
},
UnsettledRemoteBalance: &lnrpc.Amount{
Sat: uint64(unsettledRemote),
Msat: uint64(lnwire.NewMSatFromSatoshis(
unsettledRemote,
)),
},
PendingOpenLocalBalance: &lnrpc.Amount{},
PendingOpenRemoteBalance: &lnrpc.Amount{},
// Deprecated fields.
Balance: int64(local),
}
ht.AssertChannelBalanceResp(node, expectedResponse)
}
// Create carol in hodl mode.
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
// Connect Alice to Carol.
alice := ht.Alice
ht.ConnectNodes(alice, carol)
// Open a channel between Alice and Carol.
chanPointAlice := ht.OpenChannel(
alice, carol, lntemp.OpenChannelParams{Amt: chanAmt},
)
cType := ht.GetChannelCommitType(alice, chanPointAlice)
// Check alice's channel balance, which should have zero remote and zero
// pending balance.
checkChannelBalance(alice, chanAmt-calcStaticFee(cType, 0), 0, 0, 0)
// Check carol's channel balance, which should have zero local and zero
// pending balance.
checkChannelBalance(carol, 0, chanAmt-calcStaticFee(cType, 0), 0, 0)
// Channel should be ready for payments.
const (
payAmt = 100
numInvoices = 6
)
// Simulateneously send numInvoices payments from Alice to Carol.
for i := 0; i < numInvoices; i++ {
go func() {
req := &routerrpc.SendPaymentRequest{
Dest: carol.PubKey[:],
Amt: int64(payAmt),
PaymentHash: ht.Random32Bytes(),
FinalCltvDelta: finalCltvDelta,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
alice.RPC.SendPayment(req)
}()
}
// There should be a number of PendingHtlcs equal
// to the amount of Invoices sent.
ht.AssertNumActiveHtlcs(alice, numInvoices)
ht.AssertNumActiveHtlcs(carol, numInvoices)
// Set the amount expected for the Unsettled Balance for this channel.
expectedBalance := numInvoices * payAmt
// Test that the UnsettledBalance for both Alice and Carol
// is equal to the amount of invoices * payAmt.
checkUnsettledBalance := func() error {
// Get channel info for the Alice.
chanInfo := ht.QueryChannelByChanPoint(alice, chanPointAlice)
// Check that UnsettledBalance is what we expect.
if int(chanInfo.UnsettledBalance) != expectedBalance {
return fmt.Errorf("unsettled balance failed "+
"expected: %v, received: %v", expectedBalance,
chanInfo.UnsettledBalance)
}
// Get channel info for the Carol.
chanInfo = ht.QueryChannelByChanPoint(carol, chanPointAlice)
// Check that UnsettledBalance is what we expect.
if int(chanInfo.UnsettledBalance) != expectedBalance {
return fmt.Errorf("unsettled balance failed "+
"expected: %v, received: %v", expectedBalance,
chanInfo.UnsettledBalance)
}
return nil
}
require.NoError(ht, wait.NoError(checkUnsettledBalance, defaultTimeout),
"timeout while checking unsettled balance")
// Check alice's channel balance, which should have a remote unsettled
// balance that equals to the amount of invoices * payAmt. The remote
// balance remains zero.
aliceLocal := chanAmt - calcStaticFee(cType, 0) - numInvoices*payAmt
checkChannelBalance(alice, aliceLocal, 0, 0, numInvoices*payAmt)
// Check carol's channel balance, which should have a local unsettled
// balance that equals to the amount of invoices * payAmt. The local
// balance remains zero.
checkChannelBalance(carol, 0, aliceLocal, numInvoices*payAmt, 0)
// Force and assert the channel closure.
ht.ForceCloseChannel(alice, chanPointAlice)
}
File diff suppressed because it is too large. Load diff
-716
View File
@@ -1,716 +0,0 @@
package itest
import (
"fmt"
"strings"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testUpdateChanStatus checks that calls to the UpdateChanStatus RPC update
// the channel graph as expected, and that channel state is properly updated
// in the presence of interleaved node disconnects / reconnects.
//
// NOTE: this test can be flaky as we are testing the chan-enable-timeout and
// chan-disable-timeout flags here.
//
// For chan-enable-timeout, setting this value too small will cause an enabled
// channel never be considered active by our channel status manager. Upon
// reconnection, our Brontide will send a request to enable this channel after
// the "chan-enable-timeout" has passed. The request is handled by the channel
// status manager, which will check the channel's eligibility to forward links
// by asking htlcswitch/link. Meanwhile, the htlcswitch/link won't mark the
// link as eligible unless it has finished its initialization, which takes some
// time. Thus, if the Brontide sends a request too early it will get a false
// report saying the channel link is not eligible because that link hasn't
// finished its initialization.
//
// For chan-disable-timeout, setting this value too small will cause an already
// enabled channel being marked as disabled. For instance, if some operations
// take more than 5 seconds to finish, the channel will be marked as disabled,
// thus a following operation will fail if it relies on the channel being
// enabled.
func testUpdateChanStatus(ht *lntemp.HarnessTest) {
// Create two fresh nodes and open a channel between them.
alice, bob := ht.Alice, ht.Bob
args := []string{
"--minbackoff=60s",
"--chan-enable-timeout=3s",
"--chan-disable-timeout=6s",
"--chan-status-sample-interval=0.5s",
}
ht.RestartNodeWithExtraArgs(alice, args)
ht.RestartNodeWithExtraArgs(bob, args)
ht.EnsureConnected(alice, bob)
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
chanAmt := btcutil.Amount(100000)
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
defer ht.CloseChannel(alice, chanPoint)
// assertEdgeDisabled ensures that Alice has the correct Disabled state
// for given channel from her DescribeGraph.
assertEdgeDisabled := func(disabled bool) {
outPoint := ht.OutPointFromChannelPoint(chanPoint)
err := wait.NoError(func() error {
edge := ht.AssertNumEdges(alice, 1, true)[0]
if edge.ChanPoint != outPoint.String() {
return fmt.Errorf("expected chan_point %v, "+
"got %v", outPoint, edge.ChanPoint)
}
var policy *lnrpc.RoutingPolicy
if alice.PubKeyStr == edge.Node1Pub {
policy = edge.Node1Policy
} else {
policy = edge.Node2Policy
}
if disabled != policy.Disabled {
return fmt.Errorf("expected policy.Disabled "+
"to be %v, but policy was %v", disabled,
policy.Disabled)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "assert edge disabled timeout")
}
// aliceSendReq sends an UpdateChanStatus request to Alice.
aliceSendReq := func(action routerrpc.ChanStatusAction) {
req := &routerrpc.UpdateChanStatusRequest{
ChanPoint: chanPoint,
Action: action,
}
alice.RPC.UpdateChanStatus(req)
}
// Initially, the channel between Alice and Bob should not be disabled.
//
// NOTE: This check should happen right after the channel openning as
// we've used a short timeout value for `--chan-disable-timeout`. If we
// wait longer than that we might get a flake saying the channel is
// disabled.
assertEdgeDisabled(false)
// Launch a node for Carol which will connect to Alice and Bob in order
// to receive graph updates. This will ensure that the channel updates
// are propagated throughout the network.
carol := ht.NewNode("Carol", nil)
// assertChannelUpdate checks that the required policy update has been
// heard in Carol's network.
assertChannelUpdate := func(node *node.HarnessNode,
policy *lnrpc.RoutingPolicy) {
ht.AssertChannelPolicyUpdate(
carol, node, policy, chanPoint, false,
)
}
// Connect both Alice and Bob to the new node Carol, so she can sync
// her graph.
ht.ConnectNodes(alice, carol)
ht.ConnectNodes(bob, carol)
ht.WaitForGraphSync(carol)
// If the above waitForGraphSync takes more than 4 seconds, the channel
// Alice=>Bob will be marked as disabled now. Thus we connect Alice and
// Bob again to make sure the channel is alive.
ht.EnsureConnected(alice, bob)
// When updating the state of the channel between Alice and Bob, we
// should expect to see channel updates with the default routing
// policy. The value of "Disabled" will depend on the specific scenario
// being tested.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat),
FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate),
TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
}
// Manually disable the channel and ensure that a "Disabled = true"
// update is propagated.
aliceSendReq(routerrpc.ChanStatusAction_DISABLE)
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
// Re-enable the channel and ensure that a "Disabled = false" update is
// propagated.
aliceSendReq(routerrpc.ChanStatusAction_ENABLE)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
// Manually enabling a channel should NOT prevent subsequent
// disconnections from automatically disabling the channel again (we
// don't want to clutter the network with channels that are falsely
// advertised as enabled when they don't work).
ht.DisconnectNodes(alice, bob)
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
// Reconnecting the nodes should propagate a "Disabled = false" update.
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
// Manually disabling the channel should prevent a subsequent
// disconnect/reconnect from re-enabling the channel on Alice's end.
// Note the asymmetry between manual enable and manual disable!
aliceSendReq(routerrpc.ChanStatusAction_DISABLE)
// Alice sends out the "Disabled = true" update in response to the
// ChanStatusAction_DISABLE request.
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
ht.DisconnectNodes(alice, bob)
// Bob sends a "Disabled = true" update upon detecting the disconnect.
expectedPolicy.Disabled = true
assertChannelUpdate(bob, expectedPolicy)
// Bob sends a "Disabled = false" update upon detecting the reconnect.
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(bob, expectedPolicy)
// However, since we manually disabled the channel on Alice's end, the
// policy on Alice's end should still be "Disabled = true". Again, note
// the asymmetry between manual enable and manual disable!
assertEdgeDisabled(true)
ht.DisconnectNodes(alice, bob)
// Bob sends a "Disabled = true" update upon detecting the disconnect.
expectedPolicy.Disabled = true
assertChannelUpdate(bob, expectedPolicy)
// After restoring automatic channel state management on Alice's end,
// BOTH Alice and Bob should set the channel state back to "enabled" on
// reconnect.
aliceSendReq(routerrpc.ChanStatusAction_AUTO)
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
assertEdgeDisabled(false)
}
// testUnannouncedChannels checks unannounced channels are not returned by
// describeGraph RPC request unless explicitly asked for.
func testUnannouncedChannels(ht *lntemp.HarnessTest) {
amount := funding.MaxBtcFundingAmount
alice, bob := ht.Alice, ht.Bob
// Open a channel between Alice and Bob, ensuring the
// channel has been opened properly.
chanOpenUpdate := ht.OpenChannelAssertStream(
alice, bob, lntemp.OpenChannelParams{Amt: amount},
)
// Mine 2 blocks, and check that the channel is opened but not yet
// announced to the network.
ht.MineBlocksAndAssertNumTxes(2, 1)
// One block is enough to make the channel ready for use, since the
// nodes have defaultNumConfs=1 set.
fundingChanPoint := ht.WaitForChannelOpenEvent(chanOpenUpdate)
// Alice should have 1 edge in her graph.
ht.AssertNumEdges(alice, 1, true)
// Channels should not be announced yet, hence Alice should have no
// announced edges in her graph.
ht.AssertNumEdges(alice, 0, false)
// Mine 4 more blocks, and check that the channel is now announced.
ht.MineBlocks(4)
// Give the network a chance to learn that auth proof is confirmed.
ht.AssertNumEdges(alice, 1, false)
// Close the channel used during the test.
ht.CloseChannel(alice, fundingChanPoint)
}
func testGraphTopologyNotifications(ht *lntemp.HarnessTest) {
ht.Run("pinned", func(t *testing.T) {
subT := ht.Subtest(t)
testGraphTopologyNtfns(subT, true)
})
ht.Run("unpinned", func(t *testing.T) {
subT := ht.Subtest(t)
testGraphTopologyNtfns(subT, false)
})
}
func testGraphTopologyNtfns(ht *lntemp.HarnessTest, pinned bool) {
const chanAmt = funding.MaxBtcFundingAmount
// Spin up Bob first, since we will need to grab his pubkey when
// starting Alice to test pinned syncing.
bob := ht.Bob
bobInfo := bob.RPC.GetInfo()
bobPubkey := bobInfo.IdentityPubkey
// Restart Bob as he may have leftover announcements from previous
// tests, causing the graph to be unsynced.
ht.RestartNodeWithExtraArgs(bob, nil)
// For unpinned syncing, start Alice as usual. Otherwise grab Bob's
// pubkey to include in his pinned syncer set.
var aliceArgs []string
if pinned {
aliceArgs = []string{
"--numgraphsyncpeers=0",
fmt.Sprintf("--gossip.pinned-syncers=%s", bobPubkey),
}
}
alice := ht.Alice
ht.RestartNodeWithExtraArgs(alice, aliceArgs)
// Connect Alice and Bob.
ht.EnsureConnected(alice, bob)
// Assert that Bob has the correct sync type before proceeding.
if pinned {
assertSyncType(ht, alice, bobPubkey, lnrpc.Peer_PINNED_SYNC)
} else {
assertSyncType(ht, alice, bobPubkey, lnrpc.Peer_ACTIVE_SYNC)
}
// Regardless of syncer type, ensure that both peers report having
// completed their initial sync before continuing to make a channel.
ht.WaitForGraphSync(alice)
// Open a new channel between Alice and Bob.
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// The channel opening above should have triggered a few notifications
// sent to the notification client. We'll expect two channel updates,
// and two node announcements.
ht.AssertNumChannelUpdates(alice, chanPoint, 2)
ht.AssertNumNodeAnns(alice, alice.PubKeyStr, 1)
ht.AssertNumNodeAnns(alice, bob.PubKeyStr, 1)
_, blockHeight := ht.Miner.GetBestBlock()
// Now we'll test that updates are properly sent after channels are
// closed within the network.
ht.CloseChannel(alice, chanPoint)
// Now that the channel has been closed, we should receive a
// notification indicating so.
closedChan := ht.AssertTopologyChannelClosed(alice, chanPoint)
require.Equal(ht, uint32(blockHeight+1), closedChan.ClosedHeight,
"close heights of channel mismatch")
fundingTxid := ht.OutPointFromChannelPoint(chanPoint)
closeTxid := ht.OutPointFromChannelPoint(closedChan.ChanPoint)
require.EqualValues(ht, fundingTxid, closeTxid,
"channel point hash mismatch")
// For the final portion of the test, we'll ensure that once a new node
// appears in the network, the proper notification is dispatched. Note
// that a node that does not have any channels open is ignored, so first
// we disconnect Alice and Bob, open a channel between Bob and Carol,
// and finally connect Alice to Bob again.
ht.DisconnectNodes(alice, bob)
carol := ht.NewNode("Carol", nil)
ht.ConnectNodes(bob, carol)
chanPoint = ht.OpenChannel(
bob, carol, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Reconnect Alice and Bob. This should result in the nodes syncing up
// their respective graph state, with the new addition being the
// existence of Carol in the graph, and also the channel between Bob
// and Carol. Note that we will also receive a node announcement from
// Bob, since a node will update its node announcement after a new
// channel is opened.
ht.EnsureConnected(alice, bob)
// We should receive an update advertising the newly connected node,
// Bob's new node announcement, and the channel between Bob and Carol.
ht.AssertNumChannelUpdates(alice, chanPoint, 2)
ht.AssertNumNodeAnns(alice, bob.PubKeyStr, 1)
// Close the channel between Bob and Carol.
ht.CloseChannel(bob, chanPoint)
}
// testNodeAnnouncement ensures that when a node is started with one or more
// external IP addresses specified on the command line, that those addresses
// announced to the network and reported in the network graph.
func testNodeAnnouncement(ht *lntemp.HarnessTest) {
alice, bob := ht.Alice, ht.Bob
advertisedAddrs := []string{
"192.168.1.1:8333",
"[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337",
"bkb6azqggsaiskzi.onion:9735",
"fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" +
"wid.onion:1234",
}
var lndArgs []string
for _, addr := range advertisedAddrs {
lndArgs = append(lndArgs, "--externalip="+addr)
}
dave := ht.NewNode("Dave", lndArgs)
// We must let Dave have an open channel before he can send a node
// announcement, so we open a channel with Bob,
ht.ConnectNodes(bob, dave)
// We'll then go ahead and open a channel between Bob and Dave. This
// ensures that Alice receives the node announcement from Bob as part of
// the announcement broadcast.
chanPoint := ht.OpenChannel(
bob, dave, lntemp.OpenChannelParams{Amt: 1000000},
)
assertAddrs := func(addrsFound []string, targetAddrs ...string) {
addrs := make(map[string]struct{}, len(addrsFound))
for _, addr := range addrsFound {
addrs[addr] = struct{}{}
}
for _, addr := range targetAddrs {
_, ok := addrs[addr]
require.True(ht, ok, "address %v not found in node "+
"announcement", addr)
}
}
// We'll then wait for Alice to receive Dave's node announcement
// including the expected advertised addresses from Bob since they
// should already be connected.
allUpdates := ht.AssertNumNodeAnns(alice, dave.PubKeyStr, 1)
nodeUpdate := allUpdates[len(allUpdates)-1]
assertAddrs(nodeUpdate.Addresses, advertisedAddrs...)
// Close the channel between Bob and Dave.
ht.CloseChannel(bob, chanPoint)
}
// testUpdateNodeAnnouncement ensures that the RPC endpoint validates
// the requests correctly and that the new node announcement is brodcasted
// with the right information after updating our node.
func testUpdateNodeAnnouncement(ht *lntemp.HarnessTest) {
alice, bob := ht.Alice, ht.Bob
var lndArgs []string
// Add some exta addresses to the default ones.
extraAddrs := []string{
"192.168.1.1:8333",
"[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337",
"bkb6azqggsaiskzi.onion:9735",
"fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" +
"wid.onion:1234",
}
for _, addr := range extraAddrs {
lndArgs = append(lndArgs, "--externalip="+addr)
}
dave := ht.NewNode("Dave", lndArgs)
// assertNodeAnn is a helper closure that checks a given node update
// from Dave is seen by Alice.
assertNodeAnn := func(expected *lnrpc.NodeUpdate) {
err := wait.NoError(func() error {
// Get a list of node updates seen by Alice.
updates := alice.Watcher.GetNodeUpdates(dave.PubKeyStr)
// Check at least one of the updates matches the given
// node update.
for _, update := range updates {
err := compareNodeAnns(update, expected)
// Found a match, return nil.
if err == nil {
return nil
}
}
// We've check all the updates and no match found.
return fmt.Errorf("alice didn't see the update: %v",
expected)
}, defaultTimeout)
require.NoError(ht, err, "assertNodeAnn failed")
}
// Get dave default information so we can compare it lately with the
// brodcasted updates.
resp := dave.RPC.GetInfo()
defaultAddrs := make([]*lnrpc.NodeAddress, 0, len(resp.Uris))
for _, uri := range resp.GetUris() {
values := strings.Split(uri, "@")
defaultAddrs = append(
defaultAddrs, &lnrpc.NodeAddress{
Addr: values[1],
Network: "tcp",
},
)
}
// This feature bit is used to test that our endpoint sets/unsets
// feature bits properly. If the current FeatureBit is set by default
// update this one for another one unset by default at random.
featureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ
featureIdx := uint32(featureBit)
_, ok := resp.Features[featureIdx]
require.False(ht, ok, "unexpected feature bit enabled by default")
defaultDaveNodeAnn := &lnrpc.NodeUpdate{
Alias: resp.Alias,
Color: resp.Color,
NodeAddresses: defaultAddrs,
}
// Dave must have an open channel before he can send a node
// announcement, so we open a channel with Bob.
ht.ConnectNodes(bob, dave)
// Go ahead and open a channel between Bob and Dave. This
// ensures that Alice receives the node announcement from Bob as part of
// the announcement broadcast.
chanPoint := ht.OpenChannel(
bob, dave, lntemp.OpenChannelParams{
Amt: 1000000,
},
)
// Wait for Alice to receive dave's node announcement with the default
// values.
assertNodeAnn(defaultDaveNodeAnn)
// We cannot differentiate between requests with Alias = "" and
// requests that do not provide that field. If a user sets Alias = ""
// in the request the field will simply be ignored. The request must
// fail because no modifiers are applied.
invalidNodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{Alias: ""}
dave.RPC.UpdateNodeAnnouncementErr(invalidNodeAnnReq)
// Alias too long.
invalidNodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
Alias: strings.Repeat("a", 50),
}
dave.RPC.UpdateNodeAnnouncementErr(invalidNodeAnnReq)
// Update Node.
newAlias := "new-alias"
newColor := "#2288ee"
newAddresses := []string{
"192.168.1.10:8333",
"192.168.1.11:8333",
}
updateAddressActions := []*peersrpc.UpdateAddressAction{
{
Action: peersrpc.UpdateAction_ADD,
Address: newAddresses[0],
},
{
Action: peersrpc.UpdateAction_ADD,
Address: newAddresses[1],
},
{
Action: peersrpc.UpdateAction_REMOVE,
Address: defaultAddrs[0].Addr,
},
}
updateFeatureActions := []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_ADD,
FeatureBit: featureBit,
},
}
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
Alias: newAlias,
Color: newColor,
AddressUpdates: updateAddressActions,
FeatureUpdates: updateFeatureActions,
}
response := dave.RPC.UpdateNodeAnnouncement(nodeAnnReq)
expectedOps := map[string]int{
"features": 1,
"color": 1,
"alias": 1,
"addresses": 3,
}
assertUpdateNodeAnnouncementResponse(ht, response, expectedOps)
newNodeAddresses := []*lnrpc.NodeAddress{}
// We removed the first address.
newNodeAddresses = append(newNodeAddresses, defaultAddrs[1:]...)
newNodeAddresses = append(
newNodeAddresses,
&lnrpc.NodeAddress{Addr: newAddresses[0], Network: "tcp"},
&lnrpc.NodeAddress{Addr: newAddresses[1], Network: "tcp"},
)
// After updating the node we expect the update to contain
// the requested color, requested alias and the new added addresses.
newDaveNodeAnn := &lnrpc.NodeUpdate{
Alias: newAlias,
Color: newColor,
NodeAddresses: newNodeAddresses,
}
// We'll then wait for Alice to receive dave's node announcement
// with the new values.
assertNodeAnn(newDaveNodeAnn)
// Check that the feature bit was set correctly.
resp = dave.RPC.GetInfo()
_, ok = resp.Features[featureIdx]
require.True(ht, ok, "failed to set feature bit")
// Check that we cannot set a feature bit that is already set.
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
// Check that we can unset feature bits.
updateFeatureActions = []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_REMOVE,
FeatureBit: featureBit,
},
}
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
response = dave.RPC.UpdateNodeAnnouncement(nodeAnnReq)
expectedOps = map[string]int{
"features": 1,
}
assertUpdateNodeAnnouncementResponse(ht, response, expectedOps)
resp = dave.RPC.GetInfo()
_, ok = resp.Features[featureIdx]
require.False(ht, ok, "failed to unset feature bit")
// Check that we cannot unset a feature bit that is already unset.
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
// Close the channel between Bob and Dave.
ht.CloseChannel(bob, chanPoint)
}
// assertSyncType asserts that the peer has an expected syncType.
//
// NOTE: only made for tests in this file.
func assertSyncType(ht *lntemp.HarnessTest, hn *node.HarnessNode,
peer string, syncType lnrpc.Peer_SyncType) {
resp := hn.RPC.ListPeers()
for _, rpcPeer := range resp.Peers {
if rpcPeer.PubKey != peer {
continue
}
require.Equal(ht, syncType, rpcPeer.SyncType)
return
}
ht.Fatalf("unable to find peer: %s", peer)
}
// compareNodeAnns compares that two node announcements match or returns an
// error.
//
// NOTE: only used for tests in this file.
func compareNodeAnns(n1, n2 *lnrpc.NodeUpdate) error {
// Alias should match.
if n1.Alias != n2.Alias {
return fmt.Errorf("alias not match")
}
// Color should match.
if n1.Color != n2.Color {
return fmt.Errorf("color not match")
}
// NodeAddresses should match.
if len(n1.NodeAddresses) != len(n2.NodeAddresses) {
return fmt.Errorf("node addresses don't match")
}
addrs := make(map[string]struct{}, len(n1.NodeAddresses))
for _, nodeAddr := range n1.NodeAddresses {
addrs[nodeAddr.Addr] = struct{}{}
}
for _, nodeAddr := range n2.NodeAddresses {
if _, ok := addrs[nodeAddr.Addr]; !ok {
return fmt.Errorf("address %v not found in node "+
"announcement", nodeAddr.Addr)
}
}
return nil
}
// assertUpdateNodeAnnouncementResponse is a helper function to assert
// the response expected values.
//
// NOTE: only used for tests in this file.
func assertUpdateNodeAnnouncementResponse(ht *lntemp.HarnessTest,
response *peersrpc.NodeAnnouncementUpdateResponse,
expectedOps map[string]int) {
require.Equal(
ht, len(response.Ops), len(expectedOps),
"unexpected number of Ops updating dave's node announcement",
)
ops := make(map[string]int, len(response.Ops))
for _, op := range response.Ops {
ops[op.Entity] = len(op.Actions)
}
for k, v := range expectedOps {
if v != ops[k] {
ht.Fatalf("unexpected number of actions for operation "+
"%s: got %d wanted %d", k, ops[k], v)
}
}
}
-853
View File
@@ -1,853 +0,0 @@
package itest
import (
"fmt"
"math"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// assertPolicyUpdate checks that a given policy update has been received by a
// list of given nodes.
// TODO(yy): delete.
func assertPolicyUpdate(t *harnessTest, nodes []*lntest.HarnessNode,
advertisingNode string, policy *lnrpc.RoutingPolicy,
chanPoint *lnrpc.ChannelPoint) {
for _, node := range nodes {
assertChannelPolicyUpdate(
t.t, node, advertisingNode, policy, chanPoint, false,
)
}
}
// testUpdateChannelPolicy tests that policy updates made to a channel
// gets propagated to other nodes in the network.
func testUpdateChannelPolicy(ht *lntemp.HarnessTest) {
const (
defaultFeeBase = 1000
defaultFeeRate = 1
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
defaultMinHtlc = 1000
)
defaultMaxHtlc := calculateMaxHtlc(funding.MaxBtcFundingAmount)
chanAmt := funding.MaxBtcFundingAmount
pushAmt := chanAmt / 2
alice, bob := ht.Alice, ht.Bob
// Create a channel Alice->Bob.
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
},
)
// We add all the nodes' update channels to a slice, such that we can
// make sure they all receive the expected updates.
nodes := []*node.HarnessNode{alice, bob}
// Alice and Bob should see each other's ChannelUpdates, advertising the
// default routing policies.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: defaultFeeBase,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
}
assertNodesPolicyUpdate(ht, nodes, alice, expectedPolicy, chanPoint)
assertNodesPolicyUpdate(ht, nodes, bob, expectedPolicy, chanPoint)
// They should now know about the default policies.
for _, node := range nodes {
ht.AssertChannelPolicy(
node, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
node, bob.PubKeyStr, expectedPolicy, chanPoint,
)
}
// Create Carol with options to rate limit channel updates up to 2 per
// day, and create a new channel Bob->Carol.
carol := ht.NewNode(
"Carol", []string{
"--gossip.max-channel-update-burst=2",
"--gossip.channel-update-interval=24h",
},
)
ht.ConnectNodes(carol, bob)
nodes = append(nodes, carol)
// Send some coins to Carol that can be used for channel funding.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Open the channel Carol->Bob with a custom min_htlc value set. Since
// Carol is opening the channel, she will require Bob to not forward
// HTLCs smaller than this value, and hence he should advertise it as
// part of his ChannelUpdate.
const customMinHtlc = 5000
chanPoint2 := ht.OpenChannel(
carol, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
MinHtlc: customMinHtlc,
},
)
expectedPolicyBob := &lnrpc.RoutingPolicy{
FeeBaseMsat: defaultFeeBase,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: customMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
}
expectedPolicyCarol := &lnrpc.RoutingPolicy{
FeeBaseMsat: defaultFeeBase,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
}
assertNodesPolicyUpdate(ht, nodes, bob, expectedPolicyBob, chanPoint2)
assertNodesPolicyUpdate(
ht, nodes, carol, expectedPolicyCarol, chanPoint2,
)
// Check that all nodes now know about the updated policies.
for _, node := range nodes {
ht.AssertChannelPolicy(
node, bob.PubKeyStr, expectedPolicyBob, chanPoint2,
)
ht.AssertChannelPolicy(
node, carol.PubKeyStr, expectedPolicyCarol, chanPoint2,
)
}
// Make sure Alice and Carol have seen each other's channels.
ht.AssertTopologyChannelOpen(alice, chanPoint2)
ht.AssertTopologyChannelOpen(carol, chanPoint)
// First we'll try to send a payment from Alice to Carol with an amount
// less than the min_htlc value required by Carol. This payment should
// fail, as the channel Bob->Carol cannot carry HTLCs this small.
payAmt := btcutil.Amount(4)
invoice := &lnrpc.Invoice{
Memo: "testing",
Value: int64(payAmt),
}
resp := carol.RPC.AddInvoice(invoice)
// Alice knows about the channel policy of Carol and should therefore
// not be able to find a path during routing.
payReq := &routerrpc.SendPaymentRequest{
PaymentRequest: resp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
ht.SendPaymentAssertFail(
alice, payReq,
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
)
// Now we try to send a payment over the channel with a value too low
// to be accepted. First we query for a route to route a payment of
// 5000 mSAT, as this is accepted.
payAmt = btcutil.Amount(5)
routesReq := &lnrpc.QueryRoutesRequest{
PubKey: carol.PubKeyStr,
Amt: int64(payAmt),
FinalCltvDelta: defaultTimeLockDelta,
}
routes := alice.RPC.QueryRoutes(routesReq)
require.Len(ht, routes.Routes, 1)
// We change the route to carry a payment of 4000 mSAT instead of 5000
// mSAT.
payAmt = btcutil.Amount(4)
amtSat := int64(payAmt)
amtMSat := int64(lnwire.NewMSatFromSatoshis(payAmt))
routes.Routes[0].Hops[0].AmtToForward = amtSat // nolint:staticcheck
routes.Routes[0].Hops[0].AmtToForwardMsat = amtMSat
routes.Routes[0].Hops[1].AmtToForward = amtSat // nolint:staticcheck
routes.Routes[0].Hops[1].AmtToForwardMsat = amtMSat
// Send the payment with the modified value.
alicePayStream := alice.RPC.SendToRoute()
sendReq := &lnrpc.SendToRouteRequest{
PaymentHash: resp.RHash,
Route: routes.Routes[0],
}
err := alicePayStream.Send(sendReq)
require.NoError(ht, err, "unable to send payment")
// We expect this payment to fail, and that the min_htlc value is
// communicated back to us, since the attempted HTLC value was too low.
sendResp, err := ht.ReceiveSendToRouteUpdate(alicePayStream)
require.NoError(ht, err, "unable to receive payment stream")
// Expected as part of the error message.
substrs := []string{
"AmountBelowMinimum",
"HtlcMinimumMsat: (lnwire.MilliSatoshi) 5000 mSAT",
}
for _, s := range substrs {
require.Contains(ht, sendResp.PaymentError, s)
}
// Make sure sending using the original value succeeds.
payAmt = btcutil.Amount(5)
amtSat = int64(payAmt)
amtMSat = int64(lnwire.NewMSatFromSatoshis(payAmt))
routes.Routes[0].Hops[0].AmtToForward = amtSat // nolint:staticcheck
routes.Routes[0].Hops[0].AmtToForwardMsat = amtMSat
routes.Routes[0].Hops[1].AmtToForward = amtSat // nolint:staticcheck
routes.Routes[0].Hops[1].AmtToForwardMsat = amtMSat
// Manually set the MPP payload a new for each payment since
// the payment addr will change with each invoice, although we
// can re-use the route itself.
route := routes.Routes[0]
route.Hops[len(route.Hops)-1].TlvPayload = true
route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{
PaymentAddr: resp.PaymentAddr,
TotalAmtMsat: amtMSat,
}
sendReq = &lnrpc.SendToRouteRequest{
PaymentHash: resp.RHash,
Route: route,
}
err = alicePayStream.Send(sendReq)
require.NoError(ht, err, "unable to send payment")
sendResp, err = ht.ReceiveSendToRouteUpdate(alicePayStream)
require.NoError(ht, err, "unable to receive payment stream")
require.Empty(ht, sendResp.PaymentError, "expected payment to succeed")
// With our little cluster set up, we'll update the fees and the max
// htlc size for the Bob side of the Alice->Bob channel, and make sure
// all nodes learn about it.
baseFee := int64(1500)
feeRate := int64(12)
timeLockDelta := uint32(66)
maxHtlc := uint64(500000)
expectedPolicy = &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFee,
FeeRateMilliMsat: testFeeBase * feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: maxHtlc,
}
req := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
MaxHtlcMsat: maxHtlc,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
},
}
bob.RPC.UpdateChannelPolicy(req)
// Wait for all nodes to have seen the policy update done by Bob.
assertNodesPolicyUpdate(ht, nodes, bob, expectedPolicy, chanPoint)
// Check that all nodes now know about Bob's updated policy.
for _, node := range nodes {
ht.AssertChannelPolicy(
node, bob.PubKeyStr, expectedPolicy, chanPoint,
)
}
// Now that all nodes have received the new channel update, we'll try
// to send a payment from Alice to Carol to ensure that Alice has
// internalized this fee update. This shouldn't affect the route that
// Alice takes though: we updated the Alice -> Bob channel and she
// doesn't pay for transit over that channel as it's direct.
// Note that the payment amount is >= the min_htlc value for the
// channel Bob->Carol, so it should successfully be forwarded.
payAmt = btcutil.Amount(5)
invoice = &lnrpc.Invoice{
Memo: "testing",
Value: int64(payAmt),
}
resp = carol.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
// We'll now open a channel from Alice directly to Carol.
ht.ConnectNodes(alice, carol)
chanPoint3 := ht.OpenChannel(
alice, carol, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
},
)
// Make sure Bob knows this channel.
ht.AssertTopologyChannelOpen(bob, chanPoint3)
// Make a global update, and check that both channels' new policies get
// propagated.
baseFee = int64(800)
feeRate = int64(123)
timeLockDelta = uint32(22)
maxHtlc *= 2
expectedPolicy.FeeBaseMsat = baseFee
expectedPolicy.FeeRateMilliMsat = testFeeBase * feeRate
expectedPolicy.TimeLockDelta = timeLockDelta
expectedPolicy.MaxHtlcMsat = maxHtlc
req = &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
MaxHtlcMsat: maxHtlc,
}
req.Scope = &lnrpc.PolicyUpdateRequest_Global{}
alice.RPC.UpdateChannelPolicy(req)
// Wait for all nodes to have seen the policy updates for both of
// Alice's channels.
assertNodesPolicyUpdate(ht, nodes, alice, expectedPolicy, chanPoint)
assertNodesPolicyUpdate(ht, nodes, alice, expectedPolicy, chanPoint3)
// And finally check that all nodes remembers the policy update they
// received.
for _, node := range nodes {
ht.AssertChannelPolicy(
node, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
node, alice.PubKeyStr, expectedPolicy, chanPoint3,
)
}
// Now, to test that Carol is properly rate limiting incoming updates,
// we'll send two more update from Alice. Carol should accept the first,
// but not the second, as she only allows two updates per day and a day
// has yet to elapse from the previous update.
// assertAliceAndBob is a helper closure which updates Alice's policy
// and asserts that both Alice and Bob have heard and updated the
// policy in their graph.
assertAliceAndBob := func(req *lnrpc.PolicyUpdateRequest,
expectedPolicy *lnrpc.RoutingPolicy) {
alice.RPC.UpdateChannelPolicy(req)
// Wait for all nodes to have seen the policy updates for both
// of Alice's channels. Carol will not see the last update as
// the limit has been reached.
assertNodesPolicyUpdate(
ht, []*node.HarnessNode{alice, bob},
alice, expectedPolicy, chanPoint,
)
assertNodesPolicyUpdate(
ht, []*node.HarnessNode{alice, bob},
alice, expectedPolicy, chanPoint3,
)
// Check that all nodes remember the policy update
// they received.
ht.AssertChannelPolicy(
alice, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
alice, alice.PubKeyStr, expectedPolicy, chanPoint3,
)
ht.AssertChannelPolicy(
bob, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
bob, alice.PubKeyStr, expectedPolicy, chanPoint3,
)
}
// Double the base fee and attach to the policy.
baseFee1 := baseFee * 2
expectedPolicy.FeeBaseMsat = baseFee1
req.BaseFeeMsat = baseFee1
assertAliceAndBob(req, expectedPolicy)
// Check that Carol has both heard the policy and updated it in her
// graph.
assertNodesPolicyUpdate(
ht, []*node.HarnessNode{carol},
alice, expectedPolicy, chanPoint,
)
assertNodesPolicyUpdate(
ht, []*node.HarnessNode{carol},
alice, expectedPolicy, chanPoint3,
)
ht.AssertChannelPolicy(
carol, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
carol, alice.PubKeyStr, expectedPolicy, chanPoint3,
)
// Double the base fee and attach to the policy.
baseFee2 := baseFee1 * 2
expectedPolicy.FeeBaseMsat = baseFee2
req.BaseFeeMsat = baseFee2
assertAliceAndBob(req, expectedPolicy)
// Since Carol didn't receive the last update, she still has Alice's
// old policy. We validate this by checking the base fee is the older
// one.
expectedPolicy.FeeBaseMsat = baseFee1
ht.AssertChannelPolicy(
carol, alice.PubKeyStr, expectedPolicy, chanPoint,
)
ht.AssertChannelPolicy(
carol, alice.PubKeyStr, expectedPolicy, chanPoint3,
)
// Close all channels.
ht.CloseChannel(alice, chanPoint)
ht.CloseChannel(bob, chanPoint2)
ht.CloseChannel(alice, chanPoint3)
}
// testSendUpdateDisableChannel ensures that a channel update with the disable
// flag set is sent once a channel has been either unilaterally or cooperatively
// closed.
//
// NOTE: this test can be flaky as we are testing the chan-enable-timeout and
// chan-disable-timeout flags here. For instance, if some operations take more
// than 6 seconds to finish, the channel will be marked as disabled, thus a
// following operation will fail if it relies on the channel being enabled.
func testSendUpdateDisableChannel(ht *lntemp.HarnessTest) {
const chanAmt = 100000
alice, bob := ht.Alice, ht.Bob
// Create a new node Eve, which will be restarted later with a config
// that has an inactive channel timeout of just 6 seconds (down from
// the default 20m). It will be used to test channel updates for
// channels going inactive.
//
// NOTE: we don't create Eve with the chan-disable-timeout here because
// the following channel openings might take longer than that timeout
// value, which will cause the channel Eve=>Carol being marked as
// disabled.
eve := ht.NewNode("Eve", nil)
// Create a new node Carol, which will later be restarted with the same
// config as Eve's.
carol := ht.NewNode("Carol", nil)
// Launch a node for Dave which will connect to Bob in order to receive
// graph updates from. This will ensure that the channel updates are
// propagated throughout the network.
dave := ht.NewNode("Dave", nil)
// We will start our test by creating the following topology,
// Alice --- Bob --- Dave
// | |
// Carol --- Eve
ht.EnsureConnected(alice, bob)
ht.ConnectNodes(alice, carol)
ht.ConnectNodes(bob, dave)
ht.ConnectNodes(eve, carol)
// Connect Eve and Bob using a persistent connection. Later after Eve
// is restarted, they will connect again automatically.
ht.ConnectNodesPerm(bob, eve)
// Give Eve some coins.
ht.FundCoins(btcutil.SatoshiPerBitcoin, eve)
// We now proceed to open channels: Alice=>Bob, Alice=>Carol and
// Eve=>Carol.
p := lntemp.OpenChannelParams{Amt: chanAmt}
reqs := []*lntemp.OpenChannelRequest{
{Local: alice, Remote: bob, Param: p},
{Local: alice, Remote: carol, Param: p},
{Local: eve, Remote: carol, Param: p},
}
resp := ht.OpenMultiChannelsAsync(reqs)
// Extract channel points from the response.
chanPointAliceBob := resp[0]
chanPointAliceCarol := resp[1]
chanPointEveCarol := resp[2]
// We will use 10 seconds as the disable timeout.
chanDisableTimeout := 10
chanEnableTimeout := 5
// waitChanDisabled is a helper closure to wait the chanDisableTimeout
// seconds such that the channel disable logic is taking effect.
waitChanDisabled := func() {
time.Sleep(time.Duration(chanDisableTimeout) * time.Second)
}
// With the channels open, we now restart Carol and Eve to use
// customized timeout values.
nodeCfg := []string{
"--minbackoff=60s",
fmt.Sprintf("--chan-enable-timeout=%ds", chanEnableTimeout),
fmt.Sprintf("--chan-disable-timeout=%ds", chanDisableTimeout),
"--chan-status-sample-interval=.5s",
}
ht.RestartNodeWithExtraArgs(carol, nodeCfg)
ht.RestartNodeWithExtraArgs(eve, nodeCfg)
// Dave should know all the channels.
ht.AssertTopologyChannelOpen(dave, chanPointAliceBob)
ht.AssertTopologyChannelOpen(dave, chanPointAliceCarol)
ht.AssertTopologyChannelOpen(dave, chanPointEveCarol)
// We should expect to see a channel update with the default routing
// policy, except that it should indicate the channel is disabled.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat),
FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate),
TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: calculateMaxHtlc(chanAmt),
Disabled: true,
}
// assertPolicyUpdate checks that the required policy update has
// happened on the given node.
assertPolicyUpdate := func(node *node.HarnessNode,
policy *lnrpc.RoutingPolicy, chanPoint *lnrpc.ChannelPoint,
numUpdates int) {
ht.AssertNumPolicyUpdates(dave, chanPoint, node, numUpdates)
ht.AssertChannelPolicyUpdate(
dave, node, policy, chanPoint, false,
)
}
// Let Carol go offline. Since Eve has an inactive timeout of 6s, we
// expect her to send an update disabling the channel.
restartCarol := ht.SuspendNode(carol)
// We expect to see a total of 2 channel policy updates from the
// channel Carol <-> Eve and advertised by Eve using the route
// Eve->Bob->Dave.
waitChanDisabled()
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 2)
// We restart Carol. Since the channel now becomes active again, Eve
// should send a ChannelUpdate setting the channel no longer disabled.
require.NoError(ht, restartCarol(), "unable to restart carol")
expectedPolicy.Disabled = false
// We expect to see a total of 3 channel policy updates from the
// channel Carol <-> Eve and advertised by Eve using the route
// Eve->Bob->Dave.
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 3)
// Wait until Carol and Eve are reconnected before we disconnect them
// again.
ht.EnsureConnected(eve, carol)
// Now we'll test a long disconnection. Disconnect Carol and Eve and
// ensure they both detect each other as disabled. Their min backoffs
// are high enough to not interfere with disabling logic.
ht.DisconnectNodes(carol, eve)
// Wait for a disable from both Carol and Eve to come through.
expectedPolicy.Disabled = true
// We expect to see a total of 4 channel policy updates from the
// channel Carol <-> Eve and advertised by Eve using the route
// Eve->Bob->Dave.
waitChanDisabled()
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 4)
// Because Carol has restarted twice before, depending on how much time
// it has taken, she might mark the channel disabled and enable it
// multiple times. Thus we could see a total of 2 or 4 or 6 channel
// policy updates from the channel Carol <-> Eve and advertised by
// Carol using the route Carol->Alice->Bob->Dave.
//
// Assume there are 2 channel policy updates from Carol, and update it
// if more has found
numCarol := 2
op := ht.OutPointFromChannelPoint(chanPointEveCarol)
policyMap := dave.Watcher.GetPolicyUpdates(op)
nodePolicy, ok := policyMap[carol.PubKeyStr]
switch {
case !ok:
break
case len(nodePolicy) > 2:
numCarol = 4
case len(nodePolicy) > 4:
numCarol = 6
}
assertPolicyUpdate(carol, expectedPolicy, chanPointEveCarol, numCarol)
// Reconnect Carol and Eve, this should cause them to reenable the
// channel from both ends after a short delay.
ht.EnsureConnected(carol, eve)
expectedPolicy.Disabled = false
// We expect to see a total of 5 channel policy updates from the
// channel Carol <-> Eve and advertised by Eve using the route
// Eve->Bob->Dave.
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 5)
// We expect to see a total of 3 or 5 channel policy updates from the
// channel Carol <-> Eve and advertised by Carol using the route
// Carol->Alice->Bob->Dave.
numCarol++
assertPolicyUpdate(carol, expectedPolicy, chanPointEveCarol, numCarol)
// Now we'll test a short disconnection. Disconnect Carol and Eve, then
// reconnect them after one second so that their scheduled disables are
// aborted. One second is twice the status sample interval, so this
// should allow for the disconnect to be detected, but still leave time
// to cancel the announcement before the 6 second inactive timeout is
// hit.
ht.DisconnectNodes(carol, eve)
time.Sleep(time.Second)
ht.EnsureConnected(eve, carol)
// Since the disable should have been canceled by both Carol and Eve,
// we expect no channel updates to appear on the network, which means
// we expect the polices stay unchanged(Disable == false).
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 5)
assertPolicyUpdate(carol, expectedPolicy, chanPointEveCarol, numCarol)
// Close Alice's channels with Bob and Carol cooperatively and
// unilaterally respectively. Note that the CloseChannel will mine a
// block and check that the closing transaction can be found in both
// the mempool and the block.
ht.CloseChannel(alice, chanPointAliceBob)
ht.ForceCloseChannel(alice, chanPointAliceCarol)
// Now that the channel close processes have been started, we should
// receive an update marking each as disabled.
expectedPolicy.Disabled = true
// We expect to see a total of 2 channel policy updates from the
// channel Alice <-> Bob and advertised by Alice using the route
// Alice->Bob->Dave.
assertPolicyUpdate(alice, expectedPolicy, chanPointAliceBob, 2)
// We expect to see a total of 2 channel policy updates from the
// channel Alice <-> Carol and advertised by Alice using the route
// Alice->Bob->Dave.
assertPolicyUpdate(alice, expectedPolicy, chanPointAliceCarol, 2)
// Also do this check for Eve's channel with Carol.
ht.CloseChannel(eve, chanPointEveCarol)
// We expect to see a total of 5 channel policy updates from the
// channel Carol <-> Eve and advertised by Eve using the route
// Eve->Bob->Dave.
assertPolicyUpdate(eve, expectedPolicy, chanPointEveCarol, 6)
}
// testUpdateChannelPolicyForPrivateChannel tests when a private channel
// updates its channel edge policy, we will use the updated policy to send our
// payment.
// The topology is created as: Alice -> Bob -> Carol, where Alice -> Bob is
// public and Bob -> Carol is private. After an invoice is created by Carol,
// Bob will update the base fee via UpdateChannelPolicy, we will test that
// Alice will not fail the payment and send it using the updated channel
// policy.
func testUpdateChannelPolicyForPrivateChannel(ht *lntemp.HarnessTest) {
const (
chanAmt = btcutil.Amount(100000)
paymentAmt = 20000
baseFeeMSat = 33000
)
// We'll create the following topology first,
// Alice <--public:100k--> Bob <--private:100k--> Carol
alice, bob := ht.Alice, ht.Bob
// Open a channel with 100k satoshis between Alice and Bob.
chanPointAliceBob := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
},
)
// Create a new node Carol.
carol := ht.NewNode("Carol", nil)
// Connect Carol to Bob.
ht.ConnectNodes(carol, bob)
// Open a channel with 100k satoshis between Bob and Carol.
chanPointBobCarol := ht.OpenChannel(
bob, carol, lntemp.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
// Carol should be aware of the channel between Alice and Bob.
ht.AssertTopologyChannelOpen(carol, chanPointAliceBob)
// We should have the following topology now,
// Alice <--public:100k--> Bob <--private:100k--> Carol
//
// Now we will create an invoice for Carol.
invoice := &lnrpc.Invoice{
Memo: "routing hints",
Value: paymentAmt,
Private: true,
}
resp := carol.RPC.AddInvoice(invoice)
// Bob now updates the channel edge policy for the private channel.
timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta)
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFeeMSat,
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPointBobCarol,
},
}
bob.RPC.UpdateChannelPolicy(updateFeeReq)
// Alice pays the invoices. She will use the updated baseFeeMSat in the
// payment
payReqs := []string{resp.PaymentRequest}
ht.CompletePaymentRequests(alice, payReqs)
// Check that Alice did make the payment with two HTLCs, one failed and
// one succeeded.
payment := ht.AssertNumPayments(alice, 1)[0]
htlcs := payment.Htlcs
require.Equal(ht, 2, len(htlcs), "expected to have 2 HTLCs")
require.Equal(ht, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status,
"the first HTLC attempt should fail")
require.Equal(ht, lnrpc.HTLCAttempt_SUCCEEDED, htlcs[1].Status,
"the second HTLC attempt should succeed")
// Carol should have received 20k satoshis from Bob.
ht.AssertAmountPaid("Carol(remote) [<=private] Bob(local)",
carol, chanPointBobCarol, 0, paymentAmt)
// Bob should have sent 20k satoshis to Carol.
ht.AssertAmountPaid("Bob(local) [private=>] Carol(remote)",
bob, chanPointBobCarol, paymentAmt, 0)
// Calculate the amount in satoshis.
amtExpected := int64(paymentAmt + baseFeeMSat/1000)
// Bob should have received 20k satoshis + fee from Alice.
ht.AssertAmountPaid("Bob(remote) <= Alice(local)",
bob, chanPointAliceBob, 0, amtExpected)
// Alice should have sent 20k satoshis + fee to Bob.
ht.AssertAmountPaid("Alice(local) => Bob(remote)",
alice, chanPointAliceBob, amtExpected, 0)
// Finally, close the channels.
ht.CloseChannel(alice, chanPointAliceBob)
ht.CloseChannel(bob, chanPointBobCarol)
}
// testUpdateChannelPolicyFeeRateAccuracy tests that updating the channel policy
// rounds fee rate values correctly as well as setting fee rate with ppm works
// as expected.
func testUpdateChannelPolicyFeeRateAccuracy(ht *lntemp.HarnessTest) {
chanAmt := funding.MaxBtcFundingAmount
pushAmt := chanAmt / 2
// Create a channel Alice -> Bob.
alice, bob := ht.Alice, ht.Bob
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
},
)
// Nodes that we need to make sure receive the channel updates.
nodes := []*node.HarnessNode{alice, bob}
baseFee := int64(1500)
timeLockDelta := uint32(66)
maxHtlc := uint64(500000)
defaultMinHtlc := int64(1000)
// Originally LND did not properly round up fee rates which caused
// inaccuracy where fee rates were simply rounded down due to the
// integer conversion.
//
// We'll use a fee rate of 0.031337 which without rounding up would
// have resulted in a fee rate ppm of 31336.
feeRate := 0.031337
// Expected fee rate will be rounded up.
expectedFeeRateMilliMsat := int64(math.Round(testFeeBase * feeRate))
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFee,
FeeRateMilliMsat: expectedFeeRateMilliMsat,
TimeLockDelta: timeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: maxHtlc,
}
req := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: feeRate,
TimeLockDelta: timeLockDelta,
MaxHtlcMsat: maxHtlc,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
},
}
alice.RPC.UpdateChannelPolicy(req)
// Make sure that both Alice and Bob sees the same policy after update.
assertNodesPolicyUpdate(ht, nodes, alice, expectedPolicy, chanPoint)
// Now use the new PPM feerate field and make sure that the feerate is
// correctly set.
feeRatePPM := uint32(32337)
req.FeeRate = 0 // Can't set both at the same time.
req.FeeRatePpm = feeRatePPM
expectedPolicy.FeeRateMilliMsat = int64(feeRatePPM)
alice.RPC.UpdateChannelPolicy(req)
// Make sure that both Alice and Bob sees the same policy after update.
assertNodesPolicyUpdate(ht, nodes, alice, expectedPolicy, chanPoint)
ht.CloseChannel(alice, chanPoint)
}
// assertNodesPolicyUpdate checks that a given policy update has been received
// by a list of given nodes.
func assertNodesPolicyUpdate(ht *lntemp.HarnessTest, nodes []*node.HarnessNode,
advertisingNode *node.HarnessNode, policy *lnrpc.RoutingPolicy,
chanPoint *lnrpc.ChannelPoint) {
for _, node := range nodes {
ht.AssertChannelPolicyUpdate(
node, advertisingNode, policy, chanPoint, false,
)
}
}
-157
View File
@@ -1,157 +0,0 @@
package itest
import (
"fmt"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// testCustomMessage tests sending and receiving of overridden custom message
// types (within the message type range usually reserved for protocol messages)
// via the send and subscribe custom message APIs.
func testCustomMessage(ht *lntemp.HarnessTest) {
alice, bob := ht.Alice, ht.Bob
var (
overrideType1 uint32 = 554
overrideType2 uint32 = 555
msgOverrideArg = "--protocol.custom-message=%v"
)
// Update Alice to accept custom protocol messages with type 1 but do
// not allow Bob to handle them yet.
extraArgs := []string{
fmt.Sprintf(msgOverrideArg, overrideType1),
}
ht.RestartNodeWithExtraArgs(alice, extraArgs)
// Subscribe Alice to custom messages before we send any, so that we
// don't miss any.
msgClient, cancel := alice.RPC.SubscribeCustomMessages()
defer cancel()
// Create a channel to receive custom messages on.
messages := make(chan *lnrpc.CustomMessage)
go func() {
for {
// If we fail to receive, just exit. The test should
// fail elsewhere if it doesn't get a message that it
// was expecting.
msg, err := msgClient.Recv()
if err != nil {
return
}
// Deliver the message into our channel or exit if the
// test is shutting down.
select {
case messages <- msg:
case <-ht.Context().Done():
return
}
}
}()
// Connect alice and bob so that they can exchange messages.
ht.EnsureConnected(alice, bob)
// Create a custom message that is within our allowed range.
msgType := uint32(lnwire.CustomTypeStart + 1)
msgData := []byte{1, 2, 3}
// Send it from Bob to Alice.
bobMsg := &lnrpc.SendCustomMessageRequest{
Peer: alice.PubKey[:],
Type: msgType,
Data: msgData,
}
bob.RPC.SendCustomMessage(bobMsg)
// Wait for Alice to receive the message. It should come through
// because it is within our allowed range.
select {
case msg := <-messages:
// Check our type and data and (sanity) check the peer we got
// it from.
require.Equal(ht, msgType, msg.Type, "first msg type wrong")
require.Equal(ht, msgData, msg.Data, "first msg data wrong")
require.Equal(ht, bob.PubKey[:], msg.Peer, "first msg "+
"peer wrong")
case <-time.After(defaultTimeout):
ht.Fatalf("alice did not receive first custom message: %v",
msgType)
}
// Try to send a message from Bob to Alice which has a message type
// outside of the custom type range and assert that it fails.
bobMsg = &lnrpc.SendCustomMessageRequest{
Peer: alice.PubKey[:],
Type: overrideType1,
Data: msgData,
}
_, err := bob.RPC.LN.SendCustomMessage(ht.Context(), bobMsg)
require.Error(ht, err, "bob should not be able to send type 1")
// Now, restart Bob with the ability to send two different custom
// protocol messages.
extraArgs = []string{
fmt.Sprintf(msgOverrideArg, overrideType1),
fmt.Sprintf(msgOverrideArg, overrideType2),
}
ht.RestartNodeWithExtraArgs(bob, extraArgs)
// Make sure Bob and Alice are connected after his restart.
ht.EnsureConnected(alice, bob)
// Send a message from Bob to Alice with a type that Bob is allowed to
// send, but Alice will not handle as a custom message.
bobMsg = &lnrpc.SendCustomMessageRequest{
Peer: alice.PubKey[:],
Type: overrideType2,
Data: msgData,
}
bob.RPC.SendCustomMessage(bobMsg)
// Do a quick check that Alice did not receive this message in her
// stream. Note that this is an instant check, so could miss the
// message being received. We'll also check below that she didn't get
// it, this is just a sanity check.
select {
case msg := <-messages:
ht.Fatalf("unexpected message: %v", msg)
default:
}
// Finally, send a custom message with a type that Bob is allowed to
// send and Alice is configured to receive.
bobMsg = &lnrpc.SendCustomMessageRequest{
Peer: alice.PubKey[:],
Type: overrideType1,
Data: msgData,
}
bob.RPC.SendCustomMessage(bobMsg)
// Wait to receive a message from Bob. This check serves to ensure that
// our message type 1 was delivered, and assert that the preceding one
// was not (we could have missed it in our check above). When we
// receive the second message, we know that the first one did not go
// through, because we expect our messages to deliver in order.
select {
case msg := <-messages:
// Check our type and data and (sanity) check the peer we got
// it from.
require.Equal(ht, overrideType1, msg.Type, "second message "+
"type")
require.Equal(ht, msgData, msg.Data, "second message data")
require.Equal(ht, bob.PubKey[:], msg.Peer, "second "+
"message peer")
case <-time.After(defaultTimeout):
ht.Fatalf("alice did not receive second custom message")
}
}
-140
View File
@@ -1,140 +0,0 @@
//go:build kvdb_etcd
// +build kvdb_etcd
package itest
import (
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/cluster"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
)
func assertLeader(ht *lntemp.HarnessTest, observer cluster.LeaderElector,
expected string) {
leader, err := observer.Leader(ht.Context())
require.NoError(ht, err, "Unable to query leader")
require.Equalf(ht, expected, leader,
"Leader should be '%v', got: '%v'", expected, leader)
}
// testEtcdFailover tests that in a cluster setup where two LND nodes form a
// single cluster (sharing the same identity) one can hand over the leader role
// to the other (failing over after graceful shutdown or forceful abort).
func testEtcdFailover(ht *lntemp.HarnessTest) {
testCases := []struct {
name string
kill bool
}{{
name: "failover after shutdown",
kill: false,
}, {
name: "failover after abort",
kill: true,
}}
for _, test := range testCases {
test := test
success := ht.Run(test.name, func(t1 *testing.T) {
st := ht.Subtest(t1)
testEtcdFailoverCase(st, test.kill)
})
if !success {
return
}
}
}
func testEtcdFailoverCase(ht *lntemp.HarnessTest, kill bool) {
etcdCfg, cleanup, err := kvdb.StartEtcdTestBackend(
ht.T.TempDir(), uint16(lntest.NextAvailablePort()),
uint16(lntest.NextAvailablePort()), "",
)
require.NoError(ht, err, "Failed to start etcd instance")
defer cleanup()
alice := ht.NewNode("Alice", nil)
// Give Alice some coins to fund the channel.
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
// Make leader election session TTL 5 sec to make the test run fast.
const leaderSessionTTL = 5
observer, err := cluster.MakeLeaderElector(
ht.Context(), cluster.EtcdLeaderElector, "observer",
lncfg.DefaultEtcdElectionPrefix, leaderSessionTTL, etcdCfg,
)
require.NoError(ht, err, "Cannot start election observer")
password := []byte("the quick brown fox jumps the lazy dog")
entropy := [16]byte{1, 2, 3}
stateless := false
cluster := true
carol1, _, _ := ht.NewNodeWithSeedEtcd(
"Carol-1", etcdCfg, password, entropy[:], stateless, cluster,
leaderSessionTTL,
)
info1 := carol1.RPC.GetInfo()
ht.ConnectNodes(carol1, alice)
// Open a channel with 100k satoshis between Carol and Alice with Alice
// being the sole funder of the channel.
chanAmt := btcutil.Amount(100_000)
ht.OpenChannel(alice, carol1, lntemp.OpenChannelParams{Amt: chanAmt})
// At this point Carol-1 is the elected leader, while Carol-2 will wait
// to become the leader when Carol-1 stops.
carol2 := ht.NewNodeEtcd(
"Carol-2", etcdCfg, password, cluster, leaderSessionTTL,
)
assertLeader(ht, observer, "Carol-1")
amt := btcutil.Amount(1000)
payReqs, _, _ := ht.CreatePayReqs(carol1, amt, 2)
ht.CompletePaymentRequests(alice, []string{payReqs[0]})
// Shut down or kill Carol-1 and wait for Carol-2 to become the leader.
failoverTimeout := time.Duration(2*leaderSessionTTL) * time.Second
if kill {
ht.KillNode(carol1)
} else {
ht.Shutdown(carol1)
}
err = carol2.WaitUntilLeader(failoverTimeout)
require.NoError(ht, err, "Waiting for Carol-2 to become the leader "+
"failed")
assertLeader(ht, observer, "Carol-2")
req := &lnrpc.UnlockWalletRequest{WalletPassword: password}
err = carol2.Unlock(req)
require.NoError(ht, err, "Unlocking Carol-2 failed")
// Make sure Carol-1 and Carol-2 have the same identity.
info2 := carol2.RPC.GetInfo()
require.Equal(ht, info1.IdentityPubkey, info2.IdentityPubkey,
"Carol-1 and Carol-2 must have the same identity")
// Make sure the nodes are connected before moving forward. Otherwise
// we may get a link not found error.
ht.AssertConnected(alice, carol2)
// Now let Alice pay the second invoice but this time we expect Carol-2
// to receive the payment.
ht.CompletePaymentRequests(alice, []string{payReqs[1]})
// Manually shutdown the node as it will mess up with our cleanup
// process.
ht.Shutdown(carol2)
}
@@ -1,496 +0,0 @@
package itest
import (
"fmt"
"strings"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var (
customTestKey uint64 = 394829
customTestValue = []byte{1, 3, 5}
)
type interceptorTestCase struct {
amountMsat int64
payAddr []byte
invoice *lnrpc.Invoice
shouldHold bool
interceptorAction routerrpc.ResolveHoldForwardAction
}
// testForwardInterceptorDedupHtlc tests that upon reconnection, duplicate
// HTLCs aren't re-notified using the HTLC interceptor API.
func testForwardInterceptorDedupHtlc(ht *lntemp.HarnessTest) {
// Initialize the test context with 3 connected nodes.
ts := newInterceptorTestScenario(ht)
alice, bob, carol := ts.alice, ts.bob, ts.carol
// Open and wait for channels.
const chanAmt = btcutil.Amount(300000)
p := lntemp.OpenChannelParams{Amt: chanAmt}
reqs := []*lntemp.OpenChannelRequest{
{Local: alice, Remote: bob, Param: p},
{Local: bob, Remote: carol, Param: p},
}
resp := ht.OpenMultiChannelsAsync(reqs)
cpAB, cpBC := resp[0], resp[1]
// Make sure Alice is aware of channel Bob=>Carol.
ht.AssertTopologyChannelOpen(alice, cpBC)
// Connect the interceptor.
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
// Prepare the test cases.
req := &lnrpc.Invoice{ValueMsat: 1000}
addResponse := carol.RPC.AddInvoice(req)
invoice := carol.RPC.LookupInvoice(addResponse.RHash)
tc := &interceptorTestCase{
amountMsat: 1000,
invoice: invoice,
payAddr: invoice.PaymentAddr,
}
// We initiate a payment from Alice.
done := make(chan struct{})
go func() {
// Signal that all the payments have been sent.
defer close(done)
ts.sendPaymentAndAssertAction(tc)
}()
// We start the htlc interceptor with a simple implementation that
// saves all intercepted packets. These packets are held to simulate a
// pending payment.
packet := ht.ReceiveHtlcInterceptor(interceptor)
// Here we should wait for the channel to contain a pending htlc, and
// also be shown as being active.
err := wait.NoError(func() error {
channel := ht.QueryChannelByChanPoint(bob, cpAB)
if len(channel.PendingHtlcs) == 0 {
return fmt.Errorf("expect alice <> bob channel to " +
"have pending htlcs")
}
if channel.Active {
return nil
}
return fmt.Errorf("channel not active")
}, defaultTimeout)
require.NoError(
ht, err, "alice <> bob channel pending htlc never arrived",
)
// At this point we want to make bob's link send all pending htlcs to
// the switch again. We force this behavior by disconnecting and
// connecting to the peer.
ht.DisconnectNodes(bob, alice)
ht.EnsureConnected(bob, alice)
// Here we wait for the channel to be active again.
ht.AssertChannelExists(bob, cpAB)
// Now that the channel is active we make sure the test passes as
// expected.
// We expect one in flight payment since we held the htlcs.
var preimage lntypes.Preimage
copy(preimage[:], invoice.RPreimage)
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_IN_FLIGHT)
// At this point if we have more than one held htlcs then we should
// fail. This means we hold the same htlc twice which is a risk we want
// to eliminate. If we don't have the same htlc twice in theory we can
// cancel one and settle the other by mistake.
errDone := make(chan struct{})
go func() {
defer close(errDone)
_, err := interceptor.Recv()
require.Error(ht, err, "expected an error from interceptor")
status, ok := status.FromError(err)
switch {
// If it is just the error result of the context cancellation
// the we exit silently.
case ok && status.Code() == codes.Canceled:
fallthrough
// When the test ends, during the node's shutdown it will close
// the connection.
case strings.Contains(err.Error(), "closed network connection"):
fallthrough
case strings.Contains(err.Error(), "EOF"):
return
}
// Otherwise we receive an unexpected error.
require.Failf(ht, "interceptor", "unexpected err: %v", err)
}()
// We now fail all htlcs to cancel the payment.
err = interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: packet.IncomingCircuitKey,
Action: routerrpc.ResolveHoldForwardAction_FAIL,
})
require.NoError(ht, err, "failed to send request")
// Cancel the context, which will disconnect the above interceptor.
cancelInterceptor()
// Make sure all goroutines are finished.
select {
case <-done:
case <-time.After(defaultTimeout):
require.Fail(ht, "timeout waiting for sending payment")
}
select {
case <-errDone:
case <-time.After(defaultTimeout):
require.Fail(ht, "timeout waiting for interceptor error")
}
// Finally, close channels.
ht.CloseChannel(alice, cpAB)
ht.CloseChannel(bob, cpBC)
}
// testForwardInterceptorBasic tests the forward interceptor RPC layer.
// The test creates a cluster of 3 connected nodes: Alice -> Bob -> Carol
// Alice sends 4 different payments to Carol while the interceptor handles
// differently the htlcs.
// The test ensures that:
// 1. Intercepted failed htlcs result in no payment (invoice is not settled).
// 2. Intercepted resumed htlcs result in a payment (invoice is settled).
// 3. Intercepted held htlcs result in no payment (invoice is not settled).
// 4. When Interceptor disconnects it resumes all held htlcs, which result in
// valid payment (invoice is settled).
func testForwardInterceptorBasic(ht *lntemp.HarnessTest) {
ts := newInterceptorTestScenario(ht)
alice, bob, carol := ts.alice, ts.bob, ts.carol
// Open and wait for channels.
const chanAmt = btcutil.Amount(300000)
p := lntemp.OpenChannelParams{Amt: chanAmt}
reqs := []*lntemp.OpenChannelRequest{
{Local: alice, Remote: bob, Param: p},
{Local: bob, Remote: carol, Param: p},
}
resp := ht.OpenMultiChannelsAsync(reqs)
cpAB, cpBC := resp[0], resp[1]
// Make sure Alice is aware of channel Bob=>Carol.
ht.AssertTopologyChannelOpen(alice, cpBC)
// Connect the interceptor.
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
// Prepare the test cases.
testCases := ts.prepareTestCases()
// For each test case make sure we initiate a payment from Alice to
// Carol routed through Bob. For each payment we also test its final
// status according to the interceptorAction specified in the test
// case.
done := make(chan struct{})
go func() {
// Signal that all the payments have been sent.
defer close(done)
for _, tc := range testCases {
attempt := ts.sendPaymentAndAssertAction(tc)
ts.assertAction(tc, attempt)
}
}()
// We make sure here the interceptor has processed all packets before
// we check the payment statuses.
for _, tc := range testCases {
request := ht.ReceiveHtlcInterceptor(interceptor)
// Assert sanity of informational packet data.
require.NotZero(ht, request.OutgoingRequestedChanId)
require.NotZero(ht, request.IncomingExpiry)
require.NotZero(ht, request.IncomingAmountMsat)
require.Less(ht, request.OutgoingExpiry,
request.IncomingExpiry)
require.Less(ht, request.OutgoingAmountMsat,
request.IncomingAmountMsat)
value, ok := request.CustomRecords[customTestKey]
require.True(ht, ok, "expected custom record")
require.Equal(ht, customTestValue, value)
// For held packets we ignore, keeping them in hold status.
if tc.shouldHold {
continue
}
// For all other packets we resolve according to the test case.
err := interceptor.Send(&routerrpc.ForwardHtlcInterceptResponse{
IncomingCircuitKey: request.IncomingCircuitKey,
Action: tc.interceptorAction,
Preimage: tc.invoice.RPreimage,
})
require.NoError(ht, err, "failed to send request")
}
// At this point we are left with the held packets, we want to make
// sure each one of them has a corresponding 'in-flight' payment at
// Alice's node.
for _, testCase := range testCases {
if !testCase.shouldHold {
continue
}
var preimage lntypes.Preimage
copy(preimage[:], testCase.invoice.RPreimage)
payment := ht.AssertPaymentStatus(
alice, preimage, lnrpc.Payment_IN_FLIGHT,
)
expectedAmt := testCase.invoice.ValueMsat
require.Equal(ht, expectedAmt, payment.ValueMsat,
"incorrect in flight amount")
}
// Cancel the context, which will disconnect the above interceptor.
cancelInterceptor()
// Disconnect interceptor should cause resume held packets. After that
// we wait for all go routines to finish, including the one that tests
// the payment final status for the held payment.
select {
case <-done:
case <-time.After(defaultTimeout):
require.Fail(ht, "timeout waiting for sending payment")
}
// Verify that we don't get notified about already completed HTLCs
// We do that by restarting alice, the sender the HTLCs. Under
// https://github.com/lightningnetwork/lnd/issues/5115
// this should cause all HTLCs settled or failed by the interceptor to
// renotify.
restartAlice := ht.SuspendNode(alice)
require.NoError(ht, restartAlice(), "failed to restart alice")
// Make sure the channel is active from Bob's PoV.
ht.AssertChannelExists(bob, cpAB)
// Create a new interceptor as the old one has quit.
interceptor, cancelInterceptor = bob.RPC.HtlcInterceptor()
done = make(chan struct{})
go func() {
defer close(done)
_, err := interceptor.Recv()
require.Error(ht, err, "expected an error from interceptor")
status, ok := status.FromError(err)
switch {
// If it is just the error result of the context cancellation
// the we exit silently.
case ok && status.Code() == codes.Canceled:
fallthrough
// When the test ends, during the node's shutdown it will close
// the connection.
case strings.Contains(err.Error(), "closed network connection"):
fallthrough
case strings.Contains(err.Error(), "EOF"):
return
}
// Otherwise we receive an unexpected error.
require.Failf(ht, "iinterceptor", "unexpected err: %v", err)
}()
// Cancel the context, which will disconnect the above interceptor.
cancelInterceptor()
select {
case <-done:
case <-time.After(defaultTimeout):
require.Fail(ht, "timeout waiting for interceptor error")
}
// Finally, close channels.
ht.CloseChannel(alice, cpAB)
ht.CloseChannel(bob, cpBC)
}
// interceptorTestScenario is a helper struct to hold the test context and
// provide the needed functionality.
type interceptorTestScenario struct {
ht *lntemp.HarnessTest
alice, bob, carol *node.HarnessNode
}
// newInterceptorTestScenario initializes a new test scenario with three nodes
// and connects them to have the following topology,
//
// Alice --> Bob --> Carol
//
// Among them, Alice and Bob are standby nodes and Carol is a new node.
func newInterceptorTestScenario(
ht *lntemp.HarnessTest) *interceptorTestScenario {
alice, bob := ht.Alice, ht.Bob
carol := ht.NewNode("carol", nil)
ht.EnsureConnected(alice, bob)
ht.EnsureConnected(bob, carol)
return &interceptorTestScenario{
ht: ht,
alice: alice,
bob: bob,
carol: carol,
}
}
// prepareTestCases prepares 4 tests:
// 1. failed htlc.
// 2. resumed htlc.
// 3. settling htlc externally.
// 4. held htlc that is resumed later.
func (c *interceptorTestScenario) prepareTestCases() []*interceptorTestCase {
var (
actionFail = routerrpc.ResolveHoldForwardAction_FAIL
actionResume = routerrpc.ResolveHoldForwardAction_RESUME
actionSettle = routerrpc.ResolveHoldForwardAction_SETTLE
)
cases := []*interceptorTestCase{
{
amountMsat: 1000, shouldHold: false,
interceptorAction: actionFail,
},
{
amountMsat: 1000, shouldHold: false,
interceptorAction: actionResume,
},
{
amountMsat: 1000, shouldHold: false,
interceptorAction: actionSettle,
},
{
amountMsat: 1000, shouldHold: true,
interceptorAction: actionResume,
},
}
for _, t := range cases {
inv := &lnrpc.Invoice{ValueMsat: t.amountMsat}
addResponse := c.carol.RPC.AddInvoice(inv)
invoice := c.carol.RPC.LookupInvoice(addResponse.RHash)
// We'll need to also decode the returned invoice so we can
// grab the payment address which is now required for ALL
// payments.
payReq := c.carol.RPC.DecodePayReq(invoice.PaymentRequest)
t.invoice = invoice
t.payAddr = payReq.PaymentAddr
}
return cases
}
// sendPaymentAndAssertAction sends a payment from alice to carol and asserts
// that the specified interceptor action is taken.
func (c *interceptorTestScenario) sendPaymentAndAssertAction(
tc *interceptorTestCase) *lnrpc.HTLCAttempt {
// Build a route from alice to carol.
route := c.buildRoute(
tc.amountMsat, []*node.HarnessNode{c.bob, c.carol}, tc.payAddr,
)
// Send a custom record to the forwarding node.
route.Hops[0].CustomRecords = map[uint64][]byte{
customTestKey: customTestValue,
}
// Send the payment.
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: tc.invoice.RHash,
Route: route,
}
return c.alice.RPC.SendToRouteV2(sendReq)
}
func (c *interceptorTestScenario) assertAction(tc *interceptorTestCase,
attempt *lnrpc.HTLCAttempt) {
// Now check the expected action has been taken.
switch tc.interceptorAction {
// For 'fail' interceptor action we make sure the payment failed.
case routerrpc.ResolveHoldForwardAction_FAIL:
require.Equal(c.ht, lnrpc.HTLCAttempt_FAILED, attempt.Status,
"expected payment to fail")
// Assert that we get a temporary channel failure which has a
// channel update.
require.NotNil(c.ht, attempt.Failure)
require.NotNil(c.ht, attempt.Failure.ChannelUpdate)
require.Equal(c.ht, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
attempt.Failure.Code)
// For settle and resume we make sure the payment is successful.
case routerrpc.ResolveHoldForwardAction_SETTLE:
fallthrough
case routerrpc.ResolveHoldForwardAction_RESUME:
require.Equal(c.ht, lnrpc.HTLCAttempt_SUCCEEDED,
attempt.Status, "expected payment to succeed")
}
}
// buildRoute is a helper function to build a route with given hops.
func (c *interceptorTestScenario) buildRoute(amtMsat int64,
hops []*node.HarnessNode, payAddr []byte) *lnrpc.Route {
rpcHops := make([][]byte, 0, len(hops))
for _, hop := range hops {
k := hop.PubKeyStr
pubkey, err := route.NewVertexFromStr(k)
require.NoErrorf(c.ht, err, "error parsing %v: %v", k, err)
rpcHops = append(rpcHops, pubkey[:])
}
req := &routerrpc.BuildRouteRequest{
AmtMsat: amtMsat,
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
HopPubkeys: rpcHops,
PaymentAddr: payAddr,
}
routeResp := c.alice.RPC.BuildRoute(req)
return routeResp.Route
}
-886
View File
@@ -1,886 +0,0 @@
package itest
import (
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/labels"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"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(ht *lntemp.HarnessTest) {
// Run through the test with combinations of all the different
// commitment types.
allTypes := []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_STATIC_REMOTE_KEY,
lnrpc.CommitmentType_ANCHORS,
}
// testFunding is a function closure that takes Carol and Dave's
// commitment types and test the funding flow.
testFunding := func(ht *lntemp.HarnessTest, carolCommitType,
daveCommitType lnrpc.CommitmentType) {
// 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 := nodeArgsForCommitType(carolCommitType)
carol := ht.NewNode("Carol", carolArgs)
// Each time, we'll send Carol a new set of coins in order to
// fund the channel.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
daveArgs := nodeArgsForCommitType(daveCommitType)
dave := ht.NewNode("Dave", daveArgs)
// Before we start the test, we'll ensure both sides are
// connected to the funding flow can properly be executed.
ht.EnsureConnected(carol, dave)
carolChan, daveChan, closeChan := basicChannelFundingTest(
ht, carol, dave, nil,
)
// Both nodes should report the same commitment
// type.
chansCommitType := carolChan.CommitmentType
require.Equal(ht, 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 lnrpc.CommitmentType_ANCHORS:
// Dave only supports tweakless, channel will be downgraded to
// this type if Carol supports anchors.
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
if expType == lnrpc.CommitmentType_ANCHORS {
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
}
// Dave only supoprts legacy type, channel will be downgraded
// to this type.
case lnrpc.CommitmentType_LEGACY:
expType = lnrpc.CommitmentType_LEGACY
default:
ht.Fatalf("invalid commit type %v", daveCommitType)
}
// Check that the signalled type matches what we expect.
switch {
case expType == lnrpc.CommitmentType_ANCHORS &&
chansCommitType == lnrpc.CommitmentType_ANCHORS:
case expType == lnrpc.CommitmentType_STATIC_REMOTE_KEY &&
chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY:
case expType == lnrpc.CommitmentType_LEGACY &&
chansCommitType == lnrpc.CommitmentType_LEGACY:
default:
ht.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 := ht.Run(testName, func(t *testing.T) {
st := ht.Subtest(t)
testFunding(st, 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(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode,
fundingShim *lnrpc.FundingShim) (*lnrpc.Channel,
*lnrpc.Channel, func()) {
chanAmt := funding.MaxBtcFundingAmount
pushAmt := btcutil.Amount(100000)
satPerVbyte := btcutil.Amount(1)
// Record nodes' channel balance before testing.
aliceChannelBalance := alice.RPC.ChannelBalance()
bobChannelBalance := bob.RPC.ChannelBalance()
// Creates a helper closure to be used below which asserts the proper
// response to a channel balance RPC.
checkChannelBalance := func(node *node.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) // nolint:staticcheck
ht.AssertChannelBalanceResp(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.
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
FundingShim: fundingShim,
SatPerVByte: satPerVbyte,
},
)
cType := ht.GetChannelCommitType(alice, chanPoint)
// With the channel open, ensure that the amount specified above has
// properly been pushed to Bob.
aliceLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 0)
checkChannelBalance(
alice, aliceChannelBalance, aliceLocalBalance, pushAmt,
)
checkChannelBalance(
bob, bobChannelBalance, pushAmt, aliceLocalBalance,
)
aliceChannel := ht.GetChannelByChanPoint(alice, chanPoint)
bobChannel := ht.GetChannelByChanPoint(bob, chanPoint)
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.
ht.CloseChannel(alice, chanPoint)
}
return aliceChannel, bobChannel, closeChan
}
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
// be used to fund channels.
func testUnconfirmedChannelFunding(ht *lntemp.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
pushAmt = btcutil.Amount(100000)
)
// We'll start off by creating a node for Carol.
carol := ht.NewNode("Carol", nil)
alice := ht.Alice
// We'll send her some unconfirmed funds.
ht.FundCoinsUnconfirmed(2*chanAmt, carol)
// 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.
ht.ConnectNodes(carol, alice)
chanOpenUpdate := ht.OpenChannelAssertStream(
carol, alice, lntemp.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 *node.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),
}
ht.AssertChannelBalanceResp(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 := lnrpc.CommitmentType_STATIC_REMOTE_KEY
carolLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 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(alice, 0, 0, pushAmt, carolLocalBalance)
// Confirm the channel and wait for it to be recognized by both
// parties. For neutrino backend, the funding transaction should be
// mined. Otherwise, two transactions should be mined, the unconfirmed
// spend and the funding tx.
if ht.IsNeutrinoBackend() {
ht.MineBlocksAndAssertNumTxes(6, 1)
} else {
ht.MineBlocksAndAssertNumTxes(6, 2)
}
chanPoint := ht.WaitForChannelOpenEvent(chanOpenUpdate)
// 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(alice, pushAmt, carolLocalBalance, 0, 0)
// TODO(yy): remove the sleep once the following bug is fixed.
//
// We may get the error `unable to gracefully close channel while peer
// is offline (try force closing it instead): channel link not found`.
// This happens because the channel link hasn't been added yet but we
// now proceed to closing the channel. We may need to revisit how the
// channel open event is created and make sure the event is only sent
// after all relevant states have been updated.
time.Sleep(2 * time.Second)
// Now that we're done with the test, the channel can be closed.
ht.CloseChannel(carol, chanPoint)
}
// testChannelFundingInputTypes tests that any type of supported input type can
// be used to fund channels.
func testChannelFundingInputTypes(ht *lntemp.HarnessTest) {
// We'll start off by creating a node for Carol.
carol := ht.NewNode("Carol", nil)
// Now, we'll connect her to Alice so that they can open a
// channel together.
ht.ConnectNodes(carol, ht.Alice)
runChannelFundingInputTypes(ht, ht.Alice, carol)
}
// runChannelFundingInputTypes tests that any type of supported input type can
// be used to fund channels.
func runChannelFundingInputTypes(ht *lntemp.HarnessTest, alice,
carol *node.HarnessNode) {
const (
chanAmt = funding.MaxBtcFundingAmount
burnAddr = "bcrt1qxsnqpdc842lu8c0xlllgvejt6rhy49u6fmpgyz"
)
fundMixed := func(amt btcutil.Amount, target *node.HarnessNode) {
ht.FundCoins(amt/5, target)
ht.FundCoins(amt/5, target)
ht.FundCoinsP2TR(amt/5, target)
ht.FundCoinsP2TR(amt/5, target)
ht.FundCoinsP2TR(amt/5, target)
}
fundMultipleP2TR := func(amt btcutil.Amount, target *node.HarnessNode) {
ht.FundCoinsP2TR(amt/4, target)
ht.FundCoinsP2TR(amt/4, target)
ht.FundCoinsP2TR(amt/4, target)
ht.FundCoinsP2TR(amt/4, target)
}
fundWithTypes := []func(amt btcutil.Amount, target *node.HarnessNode){
ht.FundCoins, ht.FundCoinsNP2WKH, ht.FundCoinsP2TR, fundMixed,
fundMultipleP2TR,
}
// Creates a helper closure to be used below which asserts the
// proper response to a channel balance RPC.
checkChannelBalance := func(node *node.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),
}
ht.AssertChannelBalanceResp(node, expectedResponse)
}
for _, funder := range fundWithTypes {
// We'll send her some confirmed funds. We send 10% more than
// we need to account for fees.
funder((chanAmt*11)/10, carol)
chanOpenUpdate := ht.OpenChannelAssertStream(
carol, alice, lntemp.OpenChannelParams{
Amt: chanAmt,
},
)
// 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 := lnrpc.CommitmentType_STATIC_REMOTE_KEY
carolLocalBalance := chanAmt - calcStaticFee(cType, 0)
checkChannelBalance(carol, 0, 0, carolLocalBalance, 0)
// For Alice, her local/remote balances should be zero, and the
// local/remote balances are the mirror of Carol's.
checkChannelBalance(alice, 0, 0, 0, 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.
ht.MineBlocksAndAssertNumTxes(1, 1)
chanPoint := ht.WaitForChannelOpenEvent(chanOpenUpdate)
// 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, 0, 0, 0)
checkChannelBalance(alice, 0, carolLocalBalance, 0, 0)
// TODO(yy): remove the sleep once the following bug is fixed.
//
// We may get the error `unable to gracefully close channel
// while peer is offline (try force closing it instead):
// channel link not found`. This happens because the channel
// link hasn't been added yet but we now proceed to closing the
// channel. We may need to revisit how the channel open event
// is created and make sure the event is only sent after all
// relevant states have been updated.
time.Sleep(2 * time.Second)
// Now that we're done with the test, the channel can be closed.
ht.CloseChannel(carol, chanPoint)
// Empty out the wallet so there aren't any lingering coins.
sendAllCoinsConfirm(ht, carol, burnAddr)
}
}
// sendAllCoinsConfirm sends all coins of the node's wallet to the given address
// and awaits one confirmation.
func sendAllCoinsConfirm(ht *lntemp.HarnessTest, node *node.HarnessNode,
addr string) {
sweepReq := &lnrpc.SendCoinsRequest{
Addr: addr,
SendAll: true,
}
node.RPC.SendCoins(sweepReq)
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// 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(ht *lntemp.HarnessTest) {
// First, we'll create two new nodes that we'll use to open channel
// between for this test.
carol := ht.NewNode("carol", nil)
dave := ht.NewNode("dave", nil)
// Carol will be funding the channel, so we'll send some coins over to
// her and ensure they have enough confirmations before we proceed.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Before we start the test, we'll ensure both sides are connected to
// the funding flow can properly be executed.
ht.EnsureConnected(carol, dave)
// 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(
ht, carol, dave, chanSize, thawHeight, false,
)
ht.OpenChannelAssertPending(
carol, dave, lntemp.OpenChannelParams{
Amt: chanSize,
FundingShim: fundingShim1,
},
)
ht.AssertNodesNumPendingOpenChannels(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(
ht, carol, dave, chanSize, thawHeight, 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, _ := basicChannelFundingTest(
ht, carol, dave, fundingShim2,
)
// Both channels should be marked as frozen with the proper thaw
// height.
require.Equal(ht, thawHeight, carolChan.ThawHeight,
"thaw height unmatched")
require.Equal(ht, 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),
}
resp := dave.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})
// 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.
err := ht.CloseChannelAssertErr(carol, chanPoint2, false)
require.Contains(ht, err.Error(), "cannot co-op close frozen channel")
// Before Dave closes the channel, he needs to check the invoice is
// settled to avoid an error saying cannot close channel due to active
// HTLCs.
ht.AssertInvoiceSettled(dave, resp.PaymentAddr)
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
// Next we'll try but this time with Dave (the responder) as the
// initiator. This time the channel should be closed as normal.
ht.CloseChannel(dave, chanPoint2)
// As a last step, we check if we still have the pending channel
// hanging around because we never published the funding TX.
ht.AssertNodesNumPendingOpenChannels(carol, dave, 1)
// Let's make sure we can abandon it.
carol.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint1,
PendingFundingShimOnly: true,
})
dave.RPC.AbandonChannel(&lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint1,
PendingFundingShimOnly: true,
})
// It should now not appear in the pending channels anymore.
ht.AssertNodesNumPendingOpenChannels(carol, dave, 0)
}
// testFundingPersistence is intended to ensure that the Funding Manager
// persists the state of new channels prior to broadcasting the channel's
// funding transaction. This ensures that the daemon maintains an up-to-date
// representation of channels if the system is restarted or disconnected.
// testFundingPersistence mirrors testBasicChannelFunding, but adds restarts
// and checks for the state of channels with unconfirmed funding transactions.
func testChannelFundingPersistence(ht *lntemp.HarnessTest) {
chanAmt := funding.MaxBtcFundingAmount
pushAmt := btcutil.Amount(0)
// As we need to create a channel that requires more than 1
// confirmation before it's open, with the current set of defaults,
// we'll need to create a new node instance.
const numConfs = 5
carolArgs := []string{
fmt.Sprintf("--bitcoin.defaultchanconfs=%v", numConfs),
}
carol := ht.NewNode("Carol", carolArgs)
alice := ht.Alice
ht.ConnectNodes(alice, carol)
// Create a new channel that requires 5 confs before it's considered
// open, then broadcast the funding transaction
param := lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
}
update := ht.OpenChannelAssertPending(alice, carol, param)
// At this point, the channel's funding transaction will have been
// broadcast, but not confirmed. Alice and Bob's nodes should reflect
// this when queried via RPC.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(carol, 1)
// Restart both nodes to test that the appropriate state has been
// persisted and that both nodes recover gracefully.
ht.RestartNode(alice)
ht.RestartNode(carol)
fundingTxID, err := chainhash.NewHash(update.Txid)
require.NoError(ht, err, "unable to convert funding txid "+
"into chainhash.Hash")
// Mine a block, then wait for Alice's node to notify us that the
// channel has been opened. The funding transaction should be found
// within the newly mined block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
ht.Miner.AssertTxInBlock(block, fundingTxID)
// Get the height that our transaction confirmed at.
_, height := ht.Miner.GetBestBlock()
// Restart both nodes to test that the appropriate state has been
// persisted and that both nodes recover gracefully.
ht.RestartNode(alice)
ht.RestartNode(carol)
// The following block ensures that after both nodes have restarted,
// they have reconnected before the execution of the next test.
ht.EnsureConnected(alice, carol)
// Next, mine enough blocks s.t the channel will open with a single
// additional block mined.
ht.MineBlocks(3)
// Assert that our wallet has our opening transaction with a label
// that does not have a channel ID set yet, because we have not
// reached our required confirmations.
tx := ht.AssertTxAtHeight(alice, height, fundingTxID)
// At this stage, we expect the transaction to be labelled, but not with
// our channel ID because our transaction has not yet confirmed.
label := labels.MakeLabel(labels.LabelTypeChannelOpen, nil)
require.Equal(ht, label, tx.Label, "open channel label wrong")
// Both nodes should still show a single channel as pending.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(carol, 1)
// Finally, mine the last block which should mark the channel as open.
ht.MineBlocks(1)
// At this point, the channel should be fully opened and there should
// be no pending channels remaining for either node.
ht.AssertNumPendingOpenChannels(alice, 0)
ht.AssertNumPendingOpenChannels(carol, 0)
// The channel should be listed in the peer information returned by
// both peers.
chanPoint := lntemp.ChanPointFromPendingUpdate(update)
// Re-lookup our transaction in the block that it confirmed in.
tx = ht.AssertTxAtHeight(alice, height, fundingTxID)
// Check both nodes to ensure that the channel is ready for operation.
chanAlice := ht.AssertChannelExists(alice, chanPoint)
ht.AssertChannelExists(carol, chanPoint)
// Create an additional check for our channel assertion that will
// check that our label is as expected.
shortChanID := lnwire.NewShortChanIDFromInt(chanAlice.ChanId)
label = labels.MakeLabel(labels.LabelTypeChannelOpen, &shortChanID)
require.Equal(ht, label, tx.Label, "open channel label not updated")
// 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.
ht.CloseChannel(alice, chanPoint)
}
// testBatchChanFunding makes sure multiple channels can be opened in one batch
// transaction in an atomic way.
func testBatchChanFunding(ht *lntemp.HarnessTest) {
// First, we'll create two new nodes that we'll use to open channels
// to during this test. Carol has a high minimum funding amount that
// we'll use to trigger an error during the batch channel open.
carol := ht.NewNode("carol", []string{"--minchansize=200000"})
dave := ht.NewNode("dave", nil)
alice, bob := ht.Alice, ht.Bob
// Before we start the test, we'll ensure Alice is connected to Carol
// and Dave so she can open channels to both of them (and Bob).
ht.EnsureConnected(alice, bob)
ht.EnsureConnected(alice, carol)
ht.EnsureConnected(alice, dave)
// Let's create our batch TX request. This first one should fail as we
// open a channel to Carol that is too small for her min chan size.
batchReq := &lnrpc.BatchOpenChannelRequest{
SatPerVbyte: 12,
MinConfs: 1,
Channels: []*lnrpc.BatchOpenChannel{{
NodePubkey: bob.PubKey[:],
LocalFundingAmount: 100_000,
}, {
NodePubkey: carol.PubKey[:],
LocalFundingAmount: 100_000,
}, {
NodePubkey: dave.PubKey[:],
LocalFundingAmount: 100_000,
}},
}
err := alice.RPC.BatchOpenChannelAssertErr(batchReq)
require.Contains(ht, err.Error(), "initial negotiation failed")
// Let's fix the minimum amount for Alice now and try again.
batchReq.Channels[1].LocalFundingAmount = 200_000
batchResp := alice.RPC.BatchOpenChannel(batchReq)
require.Len(ht, batchResp.PendingChannels, 3)
txHash, err := chainhash.NewHash(batchResp.PendingChannels[0].Txid)
require.NoError(ht, err)
chanPoint1 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: batchResp.PendingChannels[0].Txid,
},
OutputIndex: batchResp.PendingChannels[0].OutputIndex,
}
chanPoint2 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: batchResp.PendingChannels[1].Txid,
},
OutputIndex: batchResp.PendingChannels[1].OutputIndex,
}
chanPoint3 := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: batchResp.PendingChannels[2].Txid,
},
OutputIndex: batchResp.PendingChannels[2].OutputIndex,
}
block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
ht.Miner.AssertTxInBlock(block, txHash)
ht.AssertTopologyChannelOpen(alice, chanPoint1)
ht.AssertTopologyChannelOpen(alice, chanPoint2)
ht.AssertTopologyChannelOpen(alice, chanPoint3)
// With the channel open, ensure that it is counted towards Alice's
// total channel balance.
balRes := alice.RPC.ChannelBalance()
require.NotEqual(ht, int64(0), balRes.LocalBalance.Sat)
// 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),
}
resp := carol.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
// To conclude, we'll close the newly created channel between Carol and
// Dave. This function will also block until the channel is closed and
// will additionally assert the relevant channel closing post
// conditions.
ht.CloseChannel(alice, chanPoint1)
ht.CloseChannel(alice, chanPoint2)
ht.CloseChannel(alice, chanPoint3)
}
// deriveFundingShim creates a channel funding shim by deriving the necessary
// keys on both sides.
func deriveFundingShim(ht *lntemp.HarnessTest,
carol, dave *node.HarnessNode, chanSize btcutil.Amount,
thawHeight uint32, publish bool) (*lnrpc.FundingShim,
*lnrpc.ChannelPoint, *chainhash.Hash) {
keyLoc := &signrpc.KeyLocator{KeyFamily: 9999}
carolFundingKey := carol.RPC.DeriveKey(keyLoc)
daveFundingKey := dave.RPC.DeriveKey(keyLoc)
// Now that we have the multi-sig keys for each party, we can manually
// construct the funding transaction. We'll instruct the backend to
// immediately create and broadcast a transaction paying out an exact
// amount. Normally this would reside in the mempool, but we just
// confirm it now for simplicity.
_, fundingOutput, err := input.GenFundingPkScript(
carolFundingKey.RawKeyBytes, daveFundingKey.RawKeyBytes,
int64(chanSize),
)
require.NoError(ht, err)
var txid *chainhash.Hash
targetOutputs := []*wire.TxOut{fundingOutput}
if publish {
txid = ht.Miner.SendOutputsWithoutChange(targetOutputs, 5)
} else {
tx := ht.Miner.CreateTransaction(targetOutputs, 5)
txHash := tx.TxHash()
txid = &txHash
}
// At this point, we can being our external channel funding workflow.
// We'll start by generating a pending channel ID externally that will
// be used to track this new funding type.
pendingChanID := ht.Random32Bytes()
// Now that we have the pending channel ID, Dave (our responder) will
// register the intent to receive a new channel funding workflow using
// the pending channel ID.
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: txid[:],
},
}
chanPointShim := &lnrpc.ChanPointShim{
Amt: int64(chanSize),
ChanPoint: chanPoint,
LocalKey: &lnrpc.KeyDescriptor{
RawKeyBytes: daveFundingKey.RawKeyBytes,
KeyLoc: &lnrpc.KeyLocator{
KeyFamily: daveFundingKey.KeyLoc.KeyFamily,
KeyIndex: daveFundingKey.KeyLoc.KeyIndex,
},
},
RemoteKey: carolFundingKey.RawKeyBytes,
PendingChanId: pendingChanID,
ThawHeight: thawHeight,
}
fundingShim := &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_ChanPointShim{
ChanPointShim: chanPointShim,
},
}
dave.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
ShimRegister: fundingShim,
},
})
// If we attempt to register the same shim (has the same pending chan
// ID), then we should get an error.
dave.RPC.FundingStateStepAssertErr(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_ShimRegister{
ShimRegister: fundingShim,
},
})
// We'll take the chan point shim we just registered for Dave (the
// responder), and swap the local/remote keys before we feed it in as
// Carol's funding shim as the initiator.
fundingShim.GetChanPointShim().LocalKey = &lnrpc.KeyDescriptor{
RawKeyBytes: carolFundingKey.RawKeyBytes,
KeyLoc: &lnrpc.KeyLocator{
KeyFamily: carolFundingKey.KeyLoc.KeyFamily,
KeyIndex: carolFundingKey.KeyLoc.KeyIndex,
},
}
fundingShim.GetChanPointShim().RemoteKey = daveFundingKey.RawKeyBytes
return fundingShim, chanPoint, txid
}
-149
View File
@@ -1,149 +0,0 @@
package itest
import (
"fmt"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// testHoldInvoiceForceClose tests cancellation of accepted hold invoices which
// would otherwise trigger force closes when they expire.
func testHoldInvoiceForceClose(ht *lntemp.HarnessTest) {
// Open a channel between alice and bob.
alice, bob := ht.Alice, ht.Bob
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: 300000},
)
// Create a non-dust hold invoice for bob.
var (
preimage = lntypes.Preimage{1, 2, 3}
payHash = preimage.Hash()
)
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
Value: 30000,
CltvExpiry: 40,
Hash: payHash[:],
}
bobInvoice := bob.RPC.AddHoldInvoice(invoiceReq)
// Subscribe the invoice.
stream := bob.RPC.SubscribeSingleInvoice(payHash[:])
// Pay this invoice from Alice -> Bob, we should achieve this with a
// single htlc.
req := &routerrpc.SendPaymentRequest{
PaymentRequest: bobInvoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
alice.RPC.SendPayment(req)
ht.AssertInvoiceState(stream, lnrpc.Invoice_ACCEPTED)
// Once the HTLC has cleared, alice and bob should both have a single
// htlc locked in.
ht.AssertActiveHtlcs(alice, payHash[:])
ht.AssertActiveHtlcs(bob, payHash[:])
// Get our htlc expiry height and current block height so that we
// can mine the exact number of blocks required to expire the htlc.
channel := ht.QueryChannelByChanPoint(alice, chanPoint)
require.Len(ht, channel.PendingHtlcs, 1)
activeHtlc := channel.PendingHtlcs[0]
_, currentHeight := ht.Miner.GetBestBlock()
// Now we will mine blocks until the htlc expires, and wait for each
// node to sync to our latest height. Sanity check that we won't
// underflow.
require.Greater(ht, activeHtlc.ExpirationHeight, uint32(currentHeight),
"expected expiry after current height")
blocksTillExpiry := activeHtlc.ExpirationHeight - uint32(currentHeight)
// Alice will go to chain with some delta, sanity check that we won't
// underflow and subtract this from our mined blocks.
require.Greater(ht, blocksTillExpiry,
uint32(lncfg.DefaultOutgoingBroadcastDelta))
// blocksTillForce is the number of blocks should be mined to
// trigger a force close from Alice iff the invoice cancelation
// failed. This value is 48 in current test setup.
blocksTillForce := blocksTillExpiry -
lncfg.DefaultOutgoingBroadcastDelta
// blocksTillCancel is the number of blocks should be mined to trigger
// an invoice cancelation from Bob. This value is 30 in current test
// setup.
blocksTillCancel := blocksTillExpiry -
lncfg.DefaultHoldInvoiceExpiryDelta
// When using ht.MineBlocks, for bitcoind backend, the block height
// synced differ significantly among subsystems. From observation, the
// LNWL syncs much faster than other subsystems, with more than 10
// blocks ahead. For this test case, CRTR may be lagging behind for
// more than 20 blocks. Thus we use slow mining instead.
// TODO(yy): fix block height asymmetry among all the subsystems.
//
// We first mine enough blocks to trigger an invoice cancelation.
ht.MineBlocks(blocksTillCancel)
// Wait for the nodes to be synced.
ht.WaitForBlockchainSync(alice)
ht.WaitForBlockchainSync(bob)
// Check that the invoice is canceled by Bob.
err := wait.NoError(func() error {
inv := bob.RPC.LookupInvoice(payHash[:])
if inv.State != lnrpc.Invoice_CANCELED {
return fmt.Errorf("expected canceled invoice, got: %v",
inv.State)
}
for _, htlc := range inv.Htlcs {
if htlc.State != lnrpc.InvoiceHTLCState_CANCELED {
return fmt.Errorf("expected htlc canceled, "+
"got: %v", htlc.State)
}
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "expected canceled invoice")
// We now continue to mine more blocks to the point where it could have
// triggered a force close if the invoice cancelation was failed.
//
// NOTE: we need to mine blocks in two sections because of a following
// case has happened frequently with bitcoind backend,
// - when mining all the blocks together, subsystems were syncing
// blocks under very different speed.
// - Bob would cancel the invoice in INVC, and send an UpdateFailHTLC
// in PEER.
// - Alice, however, would need to receive the message before her
// subsystem CNCT being synced to the force close height. This didn't
// happen in bitcoind backend, as Alice's CNCT was syncing way faster
// than Bob's INVC, causing the channel being force closed before the
// invoice cancelation message was received by Alice.
ht.MineBlocks(blocksTillForce - blocksTillCancel)
// Wait for the nodes to be synced.
ht.WaitForBlockchainSync(alice)
ht.WaitForBlockchainSync(bob)
// Check that Alice has not closed the channel because there are no
// outgoing HTLCs in her channel as the only HTLC has already been
// canceled.
ht.AssertNumPendingForceClose(alice, 0)
// Clean up the channel.
ht.CloseChannel(alice, chanPoint)
}
-227
View File
@@ -1,227 +0,0 @@
package itest
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// testHoldInvoicePersistence tests that a sender to a hold-invoice, can be
// restarted before the payment gets settled, and still be able to receive the
// preimage.
func testHoldInvoicePersistence(ht *lntemp.HarnessTest) {
const (
chanAmt = btcutil.Amount(1000000)
numPayments = 10
reason = lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS //nolint:lll
)
// Create carol, and clean up when the test finishes.
carol := ht.NewNode("Carol", nil)
// Connect Alice to Carol.
alice, bob := ht.Alice, ht.Bob
ht.ConnectNodes(alice, carol)
// Open a channel between Alice and Carol which is private so that we
// cover the addition of hop hints for hold invoices.
chanPointAlice := ht.OpenChannel(
alice, carol, lntemp.OpenChannelParams{
Amt: chanAmt,
Private: true,
},
)
// For Carol to include her private channel with Alice as a hop hint,
// we need Alice to be perceived as a "public" node, meaning that she
// has at least one public channel in the graph. We open a public
// channel from Alice -> Bob and wait for Carol to see it.
chanPointBob := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
},
)
// Wait for Carol to see the open channel Alice-Bob.
ht.AssertTopologyChannelOpen(carol, chanPointBob)
// Create preimages for all payments we are going to initiate.
var preimages []lntypes.Preimage
for i := 0; i < numPayments; i++ {
var preimage lntypes.Preimage
copy(preimage[:], ht.Random32Bytes())
preimages = append(preimages, preimage)
}
// Let Carol create hold-invoices for all the payments.
var (
payAmt = btcutil.Amount(4)
payReqs []string
invoiceStreams []rpc.SingleInvoiceClient
)
assertInvoiceState := func(state lnrpc.Invoice_InvoiceState) {
for _, client := range invoiceStreams {
ht.AssertInvoiceState(client, state)
}
}
for _, preimage := range preimages {
payHash := preimage.Hash()
// Make our invoices private so that we get coverage for adding
// hop hints.
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
Memo: "testing",
Value: int64(payAmt),
Hash: payHash[:],
Private: true,
}
resp := carol.RPC.AddHoldInvoice(invoiceReq)
payReqs = append(payReqs, resp.PaymentRequest)
// We expect all of our invoices to have hop hints attached,
// since Carol and Alice are connected with a private channel.
// We assert that we have one hop hint present to ensure that
// we've got coverage for hop hints.
invoice := alice.RPC.DecodePayReq(resp.PaymentRequest)
require.Len(ht, invoice.RouteHints, 1)
stream := carol.RPC.SubscribeSingleInvoice(payHash[:])
invoiceStreams = append(invoiceStreams, stream)
}
// Wait for all the invoices to reach the OPEN state.
assertInvoiceState(lnrpc.Invoice_OPEN)
// Let Alice initiate payments for all the created invoices.
for _, payReq := range payReqs {
req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitSat: 1000000,
}
// Wait for inflight status update.
ht.SendPaymentAndAssertStatus(
alice, req, lnrpc.Payment_IN_FLIGHT,
)
}
// The payments should now show up in Alice's ListPayments, with a zero
// preimage, indicating they are not yet settled.
var zeroPreimg lntypes.Preimage
err := wait.NoError(func() error {
payments := ht.AssertNumPayments(alice, numPayments)
// Gather the payment hashes we are looking for in the
// response.
payHashes := make(map[string]struct{})
for _, preimg := range preimages {
payHashes[preimg.Hash().String()] = struct{}{}
}
for _, payment := range payments {
_, ok := payHashes[payment.PaymentHash]
if !ok {
continue
}
// The preimage should NEVER be non-zero at this point.
require.Equal(ht, zeroPreimg.String(),
payment.PaymentPreimage,
"expected zero preimage")
// We wait for the payment attempt to have been
// properly recorded in the DB.
if len(payment.Htlcs) == 0 {
return fmt.Errorf("no attempt recorded")
}
delete(payHashes, payment.PaymentHash)
}
if len(payHashes) != 0 {
return fmt.Errorf("payhash not found in response")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking alice's payments")
// Wait for all invoices to be accepted.
assertInvoiceState(lnrpc.Invoice_ACCEPTED)
// Restart alice. This to ensure she will still be able to handle
// settling the invoices after a restart.
ht.RestartNode(alice)
// Ensure the connections are made.
//
// TODO(yy): we shouldn't need these two lines since the connections
// are permanent, they'd reconnect automatically upon Alice's restart.
// However, we'd sometimes see the error `unable to gracefully close
// channel while peer is offline (try force closing it instead):
// channel link not found` from closing the channels in the end,
// indicating there's something wrong with the peer conn. We need to
// investigate and fix it in peer conn management.
ht.EnsureConnected(alice, bob)
ht.EnsureConnected(alice, carol)
// Now after a restart, we must re-track the payments. We set up a
// goroutine for each to track their status updates.
for _, preimg := range preimages {
hash := preimg.Hash()
payStream := alice.RPC.TrackPaymentV2(hash[:])
ht.ReceiveTrackPayment(payStream)
ht.AssertPaymentStatus(
alice, preimg, lnrpc.Payment_IN_FLIGHT,
)
}
// Settle invoices half the invoices, cancel the rest.
for i, preimage := range preimages {
if i%2 == 0 {
carol.RPC.SettleInvoice(preimage[:])
ht.AssertInvoiceState(
invoiceStreams[i], lnrpc.Invoice_SETTLED,
)
} else {
hash := preimage.Hash()
carol.RPC.CancelInvoice(hash[:])
ht.AssertInvoiceState(
invoiceStreams[i], lnrpc.Invoice_CANCELED,
)
}
}
// Check that Alice's invoices to be shown as settled and failed
// accordingly, and preimages matching up.
for i, preimg := range preimages {
if i%2 == 0 {
ht.AssertPaymentStatus(
alice, preimg, lnrpc.Payment_SUCCEEDED,
)
} else {
payment := ht.AssertPaymentStatus(
alice, preimg, lnrpc.Payment_FAILED,
)
require.Equal(ht, reason, payment.FailureReason,
"wrong failure reason")
}
}
// Finally, close all channels.
ht.CloseChannel(alice, chanPointBob)
ht.CloseChannel(alice, chanPointAlice)
}
-728
View File
@@ -1,728 +0,0 @@
package itest
import (
"context"
"encoding/hex"
"os"
"sort"
"strconv"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/macaroon.v2"
)
// testMacaroonAuthentication makes sure that if macaroon authentication is
// enabled on the gRPC interface, no requests with missing or invalid
// macaroons are allowed. Further, the specific access rights (read/write,
// entity based) and first-party caveats are tested as well.
func testMacaroonAuthentication(ht *lntemp.HarnessTest) {
var (
infoReq = &lnrpc.GetInfoRequest{}
newAddrReq = &lnrpc.NewAddressRequest{
Type: AddrTypeWitnessPubkeyHash,
}
testNode = ht.Alice
testClient = testNode.RPC.LN
)
testCases := []struct {
name string
run func(ctxt context.Context, t *testing.T)
}{{
// First test: Make sure we get an error if we use no macaroons
// but try to connect to a node that has macaroon authentication
// enabled.
name: "no macaroon",
run: func(ctxt context.Context, t *testing.T) {
conn, err := testNode.ConnectRPCWithMacaroon(nil)
require.NoError(t, err)
defer func() { _ = conn.Close() }()
client := lnrpc.NewLightningClient(conn)
_, err = client.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "expected 1 macaroon")
},
}, {
// Second test: Ensure that an invalid macaroon also triggers an
// error.
name: "invalid macaroon",
run: func(ctxt context.Context, t *testing.T) {
invalidMac, _ := macaroon.New(
[]byte("dummy_root_key"), []byte("0"), "itest",
macaroon.LatestVersion,
)
cleanup, client := macaroonClient(
t, testNode, invalidMac,
)
defer cleanup()
_, err := client.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot get macaroon")
},
}, {
// Third test: Try to access a write method with read-only
// macaroon.
name: "read only macaroon",
run: func(ctxt context.Context, t *testing.T) {
readonlyMac, err := testNode.ReadMacaroon(
testNode.Cfg.ReadMacPath, defaultTimeout,
)
require.NoError(t, err)
cleanup, client := macaroonClient(
t, testNode, readonlyMac,
)
defer cleanup()
_, err = client.NewAddress(ctxt, newAddrReq)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
},
}, {
// Fourth test: Check first-party caveat with timeout that
// expired 30 seconds ago.
name: "expired macaroon",
run: func(ctxt context.Context, t *testing.T) {
readonlyMac, err := testNode.ReadMacaroon(
testNode.Cfg.ReadMacPath, defaultTimeout,
)
require.NoError(t, err)
timeoutMac, err := macaroons.AddConstraints(
readonlyMac, macaroons.TimeoutConstraint(-30),
)
require.NoError(t, err)
cleanup, client := macaroonClient(
t, testNode, timeoutMac,
)
defer cleanup()
_, err = client.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "macaroon has expired")
},
}, {
// Fifth test: Check first-party caveat with invalid IP address.
name: "invalid IP macaroon",
run: func(ctxt context.Context, t *testing.T) {
readonlyMac, err := testNode.ReadMacaroon(
testNode.Cfg.ReadMacPath, defaultTimeout,
)
require.NoError(t, err)
invalidIPAddrMac, err := macaroons.AddConstraints(
readonlyMac, macaroons.IPLockConstraint(
"1.1.1.1",
),
)
require.NoError(t, err)
cleanup, client := macaroonClient(
t, testNode, invalidIPAddrMac,
)
defer cleanup()
_, err = client.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "different IP address")
},
}, {
// Sixth test: Make sure that if we do everything correct and
// send the admin macaroon with first-party caveats that we can
// satisfy, we get a correct answer.
name: "correct macaroon",
run: func(ctxt context.Context, t *testing.T) {
adminMac, err := testNode.ReadMacaroon(
testNode.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(t, err)
adminMac, err = macaroons.AddConstraints(
adminMac, macaroons.TimeoutConstraint(30),
macaroons.IPLockConstraint("127.0.0.1"),
)
require.NoError(t, err)
cleanup, client := macaroonClient(t, testNode, adminMac)
defer cleanup()
res, err := client.NewAddress(ctxt, newAddrReq)
require.NoError(t, err, "get new address")
assert.Contains(t, res.Address, "bcrt1")
},
}, {
// Seventh test: Bake a macaroon that can only access exactly
// two RPCs and make sure it works as expected.
name: "custom URI permissions",
run: func(ctxt context.Context, t *testing.T) {
entity := macaroons.PermissionEntityCustomURI
req := &lnrpc.BakeMacaroonRequest{
Permissions: []*lnrpc.MacaroonPermission{{
Entity: entity,
Action: "/lnrpc.Lightning/GetInfo",
}, {
Entity: entity,
Action: "/lnrpc.Lightning/List" +
"Permissions",
}},
}
bakeRes, err := testClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
// Create a connection that uses the custom macaroon.
customMacBytes, err := hex.DecodeString(
bakeRes.Macaroon,
)
require.NoError(t, err)
customMac := &macaroon.Macaroon{}
err = customMac.UnmarshalBinary(customMacBytes)
require.NoError(t, err)
cleanup, client := macaroonClient(
t, testNode, customMac,
)
defer cleanup()
// Call GetInfo which should succeed.
_, err = client.GetInfo(ctxt, infoReq)
require.NoError(t, err)
// Call ListPermissions which should also succeed.
permReq := &lnrpc.ListPermissionsRequest{}
permRes, err := client.ListPermissions(ctxt, permReq)
require.NoError(t, err)
require.Greater(
t, len(permRes.MethodPermissions), 10,
"permissions",
)
// Try NewAddress which should be denied.
_, err = client.NewAddress(ctxt, newAddrReq)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
},
}, {
// Eighth test: check that with the CheckMacaroonPermissions
// RPC, we can check that a macaroon follows (or doesn't)
// permissions and constraints.
name: "unknown permissions",
run: func(ctxt context.Context, t *testing.T) {
// A test macaroon created with permissions from pool,
// to make sure CheckMacaroonPermissions RPC accepts
// them.
rootKeyID := uint64(4200)
req := &lnrpc.BakeMacaroonRequest{
RootKeyId: rootKeyID,
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "account",
Action: "read",
}, {
Entity: "recommendation",
Action: "read",
}},
AllowExternalPermissions: true,
}
bakeResp, err := testClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
macBytes, err := hex.DecodeString(bakeResp.Macaroon)
require.NoError(t, err)
checkReq := &lnrpc.CheckMacPermRequest{
Macaroon: macBytes,
Permissions: req.Permissions,
}
// Test that CheckMacaroonPermissions accurately
// characterizes macaroon as valid, even if the
// permissions are not native to LND.
checkResp, err := testClient.CheckMacaroonPermissions(
ctxt, checkReq,
)
require.NoError(t, err)
require.Equal(t, checkResp.Valid, true)
mac, err := readMacaroonFromHex(bakeResp.Macaroon)
require.NoError(t, err)
// Test that CheckMacaroonPermissions responds that the
// macaroon is invalid if timed out.
timeoutMac, err := macaroons.AddConstraints(
mac, macaroons.TimeoutConstraint(-30),
)
require.NoError(t, err)
timeoutMacBytes, err := timeoutMac.MarshalBinary()
require.NoError(t, err)
checkReq.Macaroon = timeoutMacBytes
_, err = testClient.CheckMacaroonPermissions(
ctxt, checkReq,
)
require.Error(t, err)
require.Contains(t, err.Error(), "macaroon has expired")
// Test that CheckMacaroonPermissions labels macaroon
// input with wrong permissions as invalid.
wrongPermissions := []*lnrpc.MacaroonPermission{{
Entity: "invoice",
Action: "read",
}}
checkReq.Permissions = wrongPermissions
checkReq.Macaroon = macBytes
_, err = testClient.CheckMacaroonPermissions(
ctxt, checkReq,
)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
},
}}
for _, tc := range testCases {
tc := tc
ht.Run(tc.name, func(tt *testing.T) {
ctxt, cancel := context.WithTimeout(
ht.Context(), defaultTimeout,
)
defer cancel()
tc.run(ctxt, tt)
})
}
}
// testBakeMacaroon checks that when creating macaroons, the permissions param
// in the request must be set correctly, and the baked macaroon has the intended
// permissions.
func testBakeMacaroon(ht *lntemp.HarnessTest) {
var testNode = ht.Alice
testCases := []struct {
name string
run func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient)
}{{
// First test: when the permission list is empty in the request,
// an error should be returned.
name: "no permission list",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
req := &lnrpc.BakeMacaroonRequest{}
_, err := adminClient.BakeMacaroon(ctxt, req)
require.Error(t, err)
assert.Contains(
t, err.Error(), "permission list cannot be "+
"empty",
)
},
}, {
// Second test: when the action in the permission list is not
// valid, an error should be returned.
name: "invalid permission list",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
req := &lnrpc.BakeMacaroonRequest{
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "macaroon",
Action: "invalid123",
}},
}
_, err := adminClient.BakeMacaroon(ctxt, req)
require.Error(t, err)
assert.Contains(
t, err.Error(), "invalid permission action",
)
},
}, {
// Third test: when the entity in the permission list is not
// valid, an error should be returned.
name: "invalid permission entity",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
req := &lnrpc.BakeMacaroonRequest{
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "invalid123",
Action: "read",
}},
}
_, err := adminClient.BakeMacaroon(ctxt, req)
require.Error(t, err)
assert.Contains(
t, err.Error(), "invalid permission entity",
)
},
}, {
// Fourth test: check that when no root key ID is specified, the
// default root keyID is used.
name: "default root key ID",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
req := &lnrpc.BakeMacaroonRequest{
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "macaroon",
Action: "read",
}},
}
_, err := adminClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
listReq := &lnrpc.ListMacaroonIDsRequest{}
resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
require.NoError(t, err)
require.Equal(t, resp.RootKeyIds[0], uint64(0))
},
}, {
// Fifth test: create a macaroon use a non-default root key ID.
name: "custom root key ID",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
rootKeyID := uint64(4200)
req := &lnrpc.BakeMacaroonRequest{
RootKeyId: rootKeyID,
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "macaroon",
Action: "read",
}},
}
_, err := adminClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
listReq := &lnrpc.ListMacaroonIDsRequest{}
resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
require.NoError(t, err)
// the ListMacaroonIDs should give a list of two IDs,
// the default ID 0, and the newly created ID. The
// returned response is sorted to guarantee the order so
// that we can compare them one by one.
sort.Slice(resp.RootKeyIds, func(i, j int) bool {
return resp.RootKeyIds[i] < resp.RootKeyIds[j]
})
require.Equal(t, resp.RootKeyIds[0], uint64(0))
require.Equal(t, resp.RootKeyIds[1], rootKeyID)
},
}, {
// Sixth test: check the baked macaroon has the intended
// permissions. It should succeed in reading, and fail to write
// a macaroon.
name: "custom macaroon permissions",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
rootKeyID := uint64(4200)
req := &lnrpc.BakeMacaroonRequest{
RootKeyId: rootKeyID,
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "macaroon",
Action: "read",
}},
}
bakeResp, err := adminClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
newMac, err := readMacaroonFromHex(bakeResp.Macaroon)
require.NoError(t, err)
cleanup, readOnlyClient := macaroonClient(
t, testNode, newMac,
)
defer cleanup()
// BakeMacaroon requires a write permission, so this
// call should return an error.
_, err = readOnlyClient.BakeMacaroon(ctxt, req)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
// ListMacaroon requires a read permission, so this call
// should succeed.
listReq := &lnrpc.ListMacaroonIDsRequest{}
_, err = readOnlyClient.ListMacaroonIDs(ctxt, listReq)
require.NoError(t, err)
// Current macaroon can only work on entity macaroon, so
// a GetInfo request will fail.
infoReq := &lnrpc.GetInfoRequest{}
_, err = readOnlyClient.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
},
}, {
// Seventh test: check that if the allow_external_permissions
// flag is set, we are able to feed BakeMacaroons permissions
// that LND is not familiar with.
name: "allow external macaroon permissions",
run: func(ctxt context.Context, t *testing.T,
adminClient lnrpc.LightningClient) {
// We'll try permissions from Pool to test that the
// allow_external_permissions flag properly allows it.
rootKeyID := uint64(4200)
req := &lnrpc.BakeMacaroonRequest{
RootKeyId: rootKeyID,
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "account",
Action: "read",
}},
AllowExternalPermissions: true,
}
resp, err := adminClient.BakeMacaroon(ctxt, req)
require.NoError(t, err)
// We'll also check that the external permission was
// successfully added to the macaroon.
macBytes, err := hex.DecodeString(resp.Macaroon)
require.NoError(t, err)
mac := &macaroon.Macaroon{}
err = mac.UnmarshalBinary(macBytes)
require.NoError(t, err)
rawID := mac.Id()
decodedID := &lnrpc.MacaroonId{}
idProto := rawID[1:]
err = proto.Unmarshal(idProto, decodedID)
require.NoError(t, err)
require.Equal(t, "account", decodedID.Ops[0].Entity)
require.Equal(t, "read", decodedID.Ops[0].Actions[0])
},
}}
for _, tc := range testCases {
tc := tc
ht.Run(tc.name, func(tt *testing.T) {
ctxt, cancel := context.WithTimeout(
ht.Context(), defaultTimeout,
)
defer cancel()
adminMac, err := testNode.ReadMacaroon(
testNode.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(tt, err)
cleanup, client := macaroonClient(tt, testNode, adminMac)
defer cleanup()
tc.run(ctxt, tt, client)
})
}
}
// testDeleteMacaroonID checks that when deleting a macaroon ID, it removes the
// specified ID and invalidates all macaroons derived from the key with that ID.
// Also, it checks deleting the reserved marcaroon ID, DefaultRootKeyID or is
// forbidden.
func testDeleteMacaroonID(ht *lntemp.HarnessTest) {
var (
ctxb = ht.Context()
testNode = ht.Alice
)
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// Use admin macaroon to create a connection.
adminMac, err := testNode.ReadMacaroon(
testNode.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(ht, err)
cleanup, client := macaroonClient(ht.T, testNode, adminMac)
defer cleanup()
// Record the number of macaroon IDs before creation.
listReq := &lnrpc.ListMacaroonIDsRequest{}
listResp, err := client.ListMacaroonIDs(ctxt, listReq)
require.NoError(ht, err)
numMacIDs := len(listResp.RootKeyIds)
// Create macaroons for testing.
rootKeyIDs := []uint64{1, 2, 3}
macList := make([]string, 0, len(rootKeyIDs))
for _, id := range rootKeyIDs {
req := &lnrpc.BakeMacaroonRequest{
RootKeyId: id,
Permissions: []*lnrpc.MacaroonPermission{{
Entity: "macaroon",
Action: "read",
}},
}
resp, err := client.BakeMacaroon(ctxt, req)
require.NoError(ht, err)
macList = append(macList, resp.Macaroon)
}
// Check that the creation is successful.
listReq = &lnrpc.ListMacaroonIDsRequest{}
listResp, err = client.ListMacaroonIDs(ctxt, listReq)
require.NoError(ht, err)
// The number of macaroon IDs should be increased by len(rootKeyIDs).
require.Equal(ht, numMacIDs+len(rootKeyIDs), len(listResp.RootKeyIds))
// First test: check deleting the DefaultRootKeyID returns an error.
defaultID, _ := strconv.ParseUint(
string(macaroons.DefaultRootKeyID), 10, 64,
)
req := &lnrpc.DeleteMacaroonIDRequest{
RootKeyId: defaultID,
}
_, err = client.DeleteMacaroonID(ctxt, req)
require.Error(ht, err)
require.Contains(ht, err.Error(),
macaroons.ErrDeletionForbidden.Error())
// Second test: check deleting the customized ID returns success.
req = &lnrpc.DeleteMacaroonIDRequest{
RootKeyId: rootKeyIDs[0],
}
resp, err := client.DeleteMacaroonID(ctxt, req)
require.NoError(ht, err)
require.True(ht, resp.Deleted)
// Check that the deletion is successful.
listReq = &lnrpc.ListMacaroonIDsRequest{}
listResp, err = client.ListMacaroonIDs(ctxt, listReq)
require.NoError(ht, err)
// The number of macaroon IDs should be decreased by 1.
require.Equal(ht, numMacIDs+len(rootKeyIDs)-1, len(listResp.RootKeyIds))
// Check that the deleted macaroon can no longer access macaroon:read.
deletedMac, err := readMacaroonFromHex(macList[0])
require.NoError(ht, err)
cleanup, client = macaroonClient(ht.T, testNode, deletedMac)
defer cleanup()
// Because the macaroon is deleted, it will be treated as an invalid
// one.
listReq = &lnrpc.ListMacaroonIDsRequest{}
_, err = client.ListMacaroonIDs(ctxt, listReq)
require.Error(ht, err)
require.Contains(ht, err.Error(), "cannot get macaroon")
}
// testStatelessInit checks that the stateless initialization of the daemon
// does not write any macaroon files to the daemon's file system and returns
// the admin macaroon in the response. It then checks that the password
// change of the wallet can also happen stateless.
func testStatelessInit(ht *lntemp.HarnessTest) {
var (
initPw = []byte("stateless")
newPw = []byte("stateless-new")
newAddrReq = &lnrpc.NewAddressRequest{
Type: AddrTypeWitnessPubkeyHash,
}
)
// First, create a new node and request it to initialize stateless.
// This should return us the binary serialized admin macaroon that we
// can then use for further calls.
carol, _, macBytes := ht.NewNodeWithSeed("Carol", nil, initPw, true)
require.NotEmpty(ht, macBytes,
"invalid macaroon returned in stateless init")
// Now make sure no macaroon files have been created by the node Carol.
_, err := os.Stat(carol.Cfg.AdminMacPath)
require.Error(ht, err)
_, err = os.Stat(carol.Cfg.ReadMacPath)
require.Error(ht, err)
_, err = os.Stat(carol.Cfg.InvoiceMacPath)
require.Error(ht, err)
// Then check that we can unmarshal the binary serialized macaroon.
adminMac := &macaroon.Macaroon{}
err = adminMac.UnmarshalBinary(macBytes)
require.NoError(ht, err)
// Find out if we can actually use the macaroon that has been returned
// to us for a RPC call.
conn, err := carol.ConnectRPCWithMacaroon(adminMac)
require.NoError(ht, err)
defer conn.Close()
adminMacClient := lnrpc.NewLightningClient(conn)
ctxt, cancel := context.WithTimeout(ht.Context(), defaultTimeout)
defer cancel()
res, err := adminMacClient.NewAddress(ctxt, newAddrReq)
require.NoError(ht, err)
if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) {
require.Fail(ht, "returned address was not a regtest address")
}
// As a second part, shut down the node and then try to change the
// password when we start it up again.
ht.RestartNodeNoUnlock(carol)
changePwReq := &lnrpc.ChangePasswordRequest{
CurrentPassword: initPw,
NewPassword: newPw,
StatelessInit: true,
}
response, err := carol.ChangePasswordAndInit(changePwReq)
require.NoError(ht, err)
// Again, make sure no macaroon files have been created by the node
// Carol.
_, err = os.Stat(carol.Cfg.AdminMacPath)
require.Error(ht, err)
_, err = os.Stat(carol.Cfg.ReadMacPath)
require.Error(ht, err)
_, err = os.Stat(carol.Cfg.InvoiceMacPath)
require.Error(ht, err)
// Then check that we can unmarshal the new binary serialized macaroon
// and that it really is a new macaroon.
err = adminMac.UnmarshalBinary(response.AdminMacaroon)
require.NoError(ht, err, "unable to unmarshal macaroon")
require.NotEqual(ht, response.AdminMacaroon, macBytes,
"expected new macaroon to be different")
// Finally, find out if we can actually use the new macaroon that has
// been returned to us for a RPC call.
conn2, err := carol.ConnectRPCWithMacaroon(adminMac)
require.NoError(ht, err)
defer conn2.Close()
adminMacClient = lnrpc.NewLightningClient(conn2)
// Changing the password takes a while, so we use the default timeout
// of 30 seconds to wait for the connection to be ready.
ctxt, cancel = context.WithTimeout(ht.Context(), defaultTimeout)
defer cancel()
res, err = adminMacClient.NewAddress(ctxt, newAddrReq)
require.NoError(ht, err)
if !strings.HasPrefix(res.Address, harnessNetParams.Bech32HRPSegwit) {
require.Fail(ht, "returned address was not a regtest address")
}
}
// readMacaroonFromHex loads a macaroon from a hex string.
func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) {
macBytes, err := hex.DecodeString(macHex)
if err != nil {
return nil, err
}
mac := &macaroon.Macaroon{}
if err := mac.UnmarshalBinary(macBytes); err != nil {
return nil, err
}
return mac, nil
}
func macaroonClient(t *testing.T, testNode *node.HarnessNode,
mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) {
t.Helper()
conn, err := testNode.ConnectRPCWithMacaroon(mac)
require.NoError(t, err, "connect to alice")
cleanup := func() {
err := conn.Close()
require.NoError(t, err, "close")
}
return cleanup, lnrpc.NewLightningClient(conn)
}
-67
View File
@@ -1,67 +0,0 @@
//go:build rpctest
// +build rpctest
package itest
import (
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lnwallet"
)
// testMaxChannelSize tests that lnd handles --maxchansize parameter correctly.
// Wumbo nodes should enforce a default soft limit of 10 BTC by default. This
// limit can be adjusted with --maxchansize config option.
func testMaxChannelSize(ht *lntemp.HarnessTest) {
// We'll make two new nodes, both wumbo but with the default limit on
// maximum channel size (10 BTC)
wumboNode := ht.NewNode(
"wumbo", []string{"--protocol.wumbo-channels"},
)
wumboNode2 := ht.NewNode(
"wumbo2", []string{"--protocol.wumbo-channels"},
)
// We'll send 11 BTC to the wumbo node so it can test the wumbo soft
// limit.
ht.FundCoins(11*btcutil.SatoshiPerBitcoin, wumboNode)
// Next we'll connect both nodes, then attempt to make a wumbo channel
// funding request, which should fail as it exceeds the default wumbo
// soft limit of 10 BTC.
ht.EnsureConnected(wumboNode, wumboNode2)
chanAmt := funding.MaxBtcFundingAmountWumbo + 1
// The test should show failure due to the channel exceeding our max
// size.
expectedErr := lnwallet.ErrChanTooLarge(
chanAmt, funding.MaxBtcFundingAmountWumbo,
)
ht.OpenChannelAssertErr(
wumboNode, wumboNode2,
lntemp.OpenChannelParams{Amt: chanAmt}, expectedErr,
)
// We'll now make another wumbo node with appropriate maximum channel
// size to accept our wumbo channel funding.
wumboNode3 := ht.NewNode(
"wumbo3", []string{
"--protocol.wumbo-channels",
fmt.Sprintf(
"--maxchansize=%v",
int64(funding.MaxBtcFundingAmountWumbo+1),
),
},
)
// Creating a wumbo channel between these two nodes should succeed.
ht.EnsureConnected(wumboNode, wumboNode3)
chanPoint := ht.OpenChannel(
wumboNode, wumboNode3, lntemp.OpenChannelParams{Amt: chanAmt},
)
ht.CloseChannel(wumboNode, chanPoint)
}
-158
View File
@@ -1,158 +0,0 @@
package itest
import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
// testMaxHtlcPathfind tests the case where we try to send a payment over a
// channel where we have already reached the limit of the number of htlcs that
// we may add to the remote party's commitment. This test asserts that we do
// not attempt to use the full channel at all in our pathfinding.
func testMaxHtlcPathfind(ht *lntemp.HarnessTest) {
// Setup a channel between Alice and Bob where Alice will only allow
// Bob to add a maximum of 5 htlcs to her commitment.
maxHtlcs := 5
alice, bob := ht.Alice, ht.Bob
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: 1000000,
PushAmt: 800000,
RemoteMaxHtlcs: uint16(maxHtlcs),
},
)
// Alice and bob should have one channel open with each other now.
ht.AssertNodeNumChannels(alice, 1)
ht.AssertNodeNumChannels(bob, 1)
// Send our maximum number of htlcs from Bob -> Alice so that we get
// to a point where Alice won't accept any more htlcs on the channel.
subscriptions := make([]*holdSubscription, maxHtlcs)
for i := 0; i < maxHtlcs; i++ {
subscriptions[i] = acceptHoldInvoice(ht, i, bob, alice)
}
ht.AssertNumActiveHtlcs(alice, maxHtlcs)
ht.AssertNumActiveHtlcs(bob, maxHtlcs)
// Now we send a payment from Alice -> Bob to sanity check that our
// commitment limit is not applied in the opposite direction.
aliceBobSub := acceptHoldInvoice(ht, maxHtlcs, alice, bob)
ht.AssertNumActiveHtlcs(alice, maxHtlcs+1)
ht.AssertNumActiveHtlcs(bob, maxHtlcs+1)
// Now, we're going to try to send another payment from Bob -> Alice.
// We've hit our max remote htlcs, so we expect this payment to spin
// out dramatically with pathfinding.
sendReq := &routerrpc.SendPaymentRequest{
Amt: 1000,
Dest: alice.PubKey[:],
TimeoutSeconds: 60,
FeeLimitSat: 1000000,
MaxParts: 10,
Amp: true,
}
ht.SendPaymentAndAssertStatus(bob, sendReq, lnrpc.Payment_FAILED)
// Now that we're done, we cancel all our pending htlcs so that we
// can cleanup the channel with a coop close.
for _, sub := range subscriptions {
sub.cancel(ht)
}
aliceBobSub.cancel(ht)
ht.AssertNumActiveHtlcs(alice, 0)
ht.AssertNumActiveHtlcs(bob, 0)
ht.CloseChannel(alice, chanPoint)
}
type holdSubscription struct {
recipient *node.HarnessNode
hash lntypes.Hash
invSubscription invoicesrpc.Invoices_SubscribeSingleInvoiceClient
paymentSubscription routerrpc.Router_SendPaymentV2Client
}
// cancel updates a hold invoice to cancel from the recipient and consumes
// updates from the payer until it has reached a final, failed state.
func (h *holdSubscription) cancel(ht *lntemp.HarnessTest) {
h.recipient.RPC.CancelInvoice(h.hash[:])
invUpdate := ht.ReceiveSingleInvoice(h.invSubscription)
require.Equal(ht, lnrpc.Invoice_CANCELED, invUpdate.State,
"expected invoice canceled")
// We expect one in flight update when our htlc is canceled back, and
// another when we fail the payment as a whole.
payUpdate := ht.AssertPaymentStatusFromStream(
h.paymentSubscription, lnrpc.Payment_IN_FLIGHT,
)
require.Len(ht, payUpdate.Htlcs, 1)
payUpdate = ht.AssertPaymentStatusFromStream(
h.paymentSubscription, lnrpc.Payment_FAILED,
)
require.Equal(ht, lnrpc.Payment_FAILED, payUpdate.Status,
"expected payment failed")
require.Equal(ht, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
payUpdate.FailureReason, "expected unknown details")
}
// acceptHoldInvoice adds a hold invoice to the recipient node, pays it from
// the sender and asserts that we have reached the accepted state where htlcs
// are locked in for the payment.
func acceptHoldInvoice(ht *lntemp.HarnessTest, idx int, sender,
receiver *node.HarnessNode) *holdSubscription {
hash := [lntypes.HashSize]byte{byte(idx + 1)}
req := &invoicesrpc.AddHoldInvoiceRequest{
ValueMsat: 10000,
Hash: hash[:],
}
invoice := receiver.RPC.AddHoldInvoice(req)
invStream := receiver.RPC.SubscribeSingleInvoice(hash[:])
inv := ht.ReceiveSingleInvoice(invStream)
require.Equal(ht, lnrpc.Invoice_OPEN, inv.State, "expect open")
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitSat: 1000000,
}
payStream := sender.RPC.SendPayment(sendReq)
// Finally, assert that we progress to an accepted state. We expect
// the payer to get one update for the creation of the payment, and
// another when a htlc is dispatched.
payment := ht.AssertPaymentStatusFromStream(
payStream, lnrpc.Payment_IN_FLIGHT,
)
require.Empty(ht, payment.Htlcs)
payment = ht.AssertPaymentStatusFromStream(
payStream, lnrpc.Payment_IN_FLIGHT,
)
require.Len(ht, payment.Htlcs, 1)
inv = ht.ReceiveSingleInvoice(invStream)
require.Equal(ht, lnrpc.Invoice_ACCEPTED, inv.State,
"expected accepted")
return &holdSubscription{
recipient: receiver,
hash: hash,
invSubscription: invStream,
paymentSubscription: payStream,
}
}
File diff suppressed because it is too large. Load diff
-385
View File
@@ -1,385 +0,0 @@
package itest
import (
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)
// testSendToRouteMultiPath tests that we are able to successfully route a
// payment using multiple shards across different paths, by using SendToRoute.
func testSendToRouteMultiPath(ht *lntemp.HarnessTest) {
mts := newMppTestScenario(ht)
// To ensure the payment goes through separate paths, we'll set a
// channel size that can only carry one shard at a time. We'll divide
// the payment into 3 shards.
const (
paymentAmt = btcutil.Amount(300000)
shardAmt = paymentAmt / 3
chanAmt = shardAmt * 3 / 2
)
// Set up a network with three different paths Alice <-> Bob.
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
req := &mppOpenChannelRequest{
// Since the channel Alice-> Carol will have to carry two
// shards, we make it larger.
amtAliceCarol: chanAmt + shardAmt,
amtAliceDave: chanAmt,
amtCarolBob: chanAmt,
amtCarolEve: chanAmt,
amtDaveBob: chanAmt,
amtEveBob: chanAmt,
}
mts.openChannels(req)
// Make Bob create an invoice for Alice to pay.
payReqs, rHashes, invoices := ht.CreatePayReqs(mts.bob, paymentAmt, 1)
rHash := rHashes[0]
payReq := payReqs[0]
decodeResp := mts.bob.RPC.DecodePayReq(payReq)
payAddr := decodeResp.PaymentAddr
// Subscribe the invoice.
stream := mts.bob.RPC.SubscribeSingleInvoice(rHash)
// We'll send shards along three routes from Alice.
sendRoutes := [][]*node.HarnessNode{
{mts.carol, mts.bob},
{mts.dave, mts.bob},
{mts.carol, mts.eve, mts.bob},
}
responses := make(chan *lnrpc.HTLCAttempt, len(sendRoutes))
for _, hops := range sendRoutes {
// Build a route for the specified hops.
r := mts.buildRoute(shardAmt, mts.alice, hops)
// Set the MPP records to indicate this is a payment shard.
hop := r.Hops[len(r.Hops)-1]
hop.TlvPayload = true
hop.MppRecord = &lnrpc.MPPRecord{
PaymentAddr: payAddr,
TotalAmtMsat: int64(paymentAmt * 1000),
}
// Send the shard.
sendReq := &routerrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: r,
}
// We'll send all shards in their own goroutine, since
// SendToRoute will block as long as the payment is in flight.
go func() {
resp := mts.alice.RPC.SendToRouteV2(sendReq)
responses <- resp
}()
}
// Wait for all responses to be back, and check that they all
// succeeded.
timer := time.After(defaultTimeout)
for range sendRoutes {
var resp *lnrpc.HTLCAttempt
select {
case resp = <-responses:
case <-timer:
require.Fail(ht, "response not received")
}
require.Nil(ht, resp.Failure, "received payment failure")
// All shards should come back with the preimage.
require.Equal(ht, resp.Preimage, invoices[0].RPreimage,
"preimage doesn't match")
}
// assertNumHtlcs is a helper that checks the node's latest payment,
// and asserts it was split into num shards.
assertNumHtlcs := func(hn *node.HarnessNode, num int) {
var preimage lntypes.Preimage
copy(preimage[:], invoices[0].RPreimage)
payment := ht.AssertPaymentStatus(
hn, preimage, lnrpc.Payment_SUCCEEDED,
)
htlcs := payment.Htlcs
require.NotEmpty(ht, htlcs, "no htlcs")
succeeded := 0
for _, htlc := range htlcs {
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
succeeded++
}
}
require.Equal(ht, num, succeeded, "HTLCs not matched")
}
// assertSettledInvoice checks that the invoice for the given payment
// hash is settled, and has been paid using num HTLCs.
assertSettledInvoice := func(rhash []byte, num int) {
var payHash lntypes.Hash
copy(payHash[:], rhash)
inv := ht.AssertInvoiceState(stream, lnrpc.Invoice_SETTLED)
// Assert that the amount paid to the invoice is correct.
require.EqualValues(ht, paymentAmt, inv.AmtPaidSat,
"incorrect payment amt")
require.Len(ht, inv.Htlcs, num, "wrong num of HTLCs")
}
// Finally check that the payment shows up with three settled HTLCs in
// Alice's list of payments...
assertNumHtlcs(mts.alice, 3)
// ...and in Bob's list of paid invoices.
assertSettledInvoice(rHash, 3)
// Finally, close all channels.
mts.closeChannels()
}
// mppTestScenario defines a test scenario used for testing MPP-related tests.
// It has two standby nodes, alice and bob, and three new nodes, carol, dave,
// and eve.
type mppTestScenario struct {
ht *lntemp.HarnessTest
alice, bob, carol, dave, eve *node.HarnessNode
nodes []*node.HarnessNode
// Keep a list of all our active channels.
channelPoints []*lnrpc.ChannelPoint
}
// newMppTestScenario initializes a new mpp test scenario with five funded
// nodes and connects them to have the following topology,
//
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
func newMppTestScenario(ht *lntemp.HarnessTest) *mppTestScenario {
alice, bob := ht.Alice, ht.Bob
ht.RestartNodeWithExtraArgs(bob, []string{
"--maxpendingchannels=2",
"--accept-amp",
})
// Create a five-node context consisting of Alice, Bob and three new
// nodes.
carol := ht.NewNode("carol", []string{
"--maxpendingchannels=2",
"--accept-amp",
})
dave := ht.NewNode("dave", nil)
eve := ht.NewNode("eve", nil)
// Connect nodes to ensure propagation of channels.
ht.EnsureConnected(alice, carol)
ht.EnsureConnected(alice, dave)
ht.EnsureConnected(carol, bob)
ht.EnsureConnected(carol, eve)
ht.EnsureConnected(dave, bob)
ht.EnsureConnected(eve, bob)
// Send coins to the nodes and mine 1 blocks to confirm them.
for i := 0; i < 2; i++ {
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, dave)
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, eve)
ht.MineBlocks(1)
}
mts := &mppTestScenario{
ht: ht,
alice: alice,
bob: bob,
carol: carol,
dave: dave,
eve: eve,
nodes: []*node.HarnessNode{alice, bob, carol, dave, eve},
}
return mts
}
// mppOpenChannelRequest defines the amounts used for each channel opening.
type mppOpenChannelRequest struct {
// Channel Alice=>Carol.
amtAliceCarol btcutil.Amount
// Channel Alice=>Dave.
amtAliceDave btcutil.Amount
// Channel Carol=>Bob.
amtCarolBob btcutil.Amount
// Channel Carol=>Eve.
amtCarolEve btcutil.Amount
// Channel Dave=>Bob.
amtDaveBob btcutil.Amount
// Channel Eve=>Bob.
amtEveBob btcutil.Amount
}
// openChannels is a helper to open channels that sets up a network topology
// with three different paths Alice <-> Bob as following,
//
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
// NOTE: all the channels are open together to save blocks mined.
func (m *mppTestScenario) openChannels(r *mppOpenChannelRequest) {
reqs := []*lntemp.OpenChannelRequest{
{
Local: m.alice,
Remote: m.carol,
Param: lntemp.OpenChannelParams{Amt: r.amtAliceCarol},
},
{
Local: m.alice,
Remote: m.dave,
Param: lntemp.OpenChannelParams{Amt: r.amtAliceDave},
},
{
Local: m.carol,
Remote: m.bob,
Param: lntemp.OpenChannelParams{Amt: r.amtCarolBob},
},
{
Local: m.carol,
Remote: m.eve,
Param: lntemp.OpenChannelParams{Amt: r.amtCarolEve},
},
{
Local: m.dave,
Remote: m.bob,
Param: lntemp.OpenChannelParams{Amt: r.amtDaveBob},
},
{
Local: m.eve,
Remote: m.bob,
Param: lntemp.OpenChannelParams{Amt: r.amtEveBob},
},
}
m.channelPoints = m.ht.OpenMultiChannelsAsync(reqs)
// Make sure every node has heard every channel.
for _, hn := range m.nodes {
for _, cp := range m.channelPoints {
m.ht.AssertTopologyChannelOpen(hn, cp)
}
}
}
// closeChannels closes all the open channels from `openChannels`.
func (m *mppTestScenario) closeChannels() {
if m.ht.Failed() {
m.ht.Log("Skipped closing channels for failed test")
return
}
// TODO(yy): remove the sleep once the following bug is fixed. When the
// payment is reported as settled by Alice, it's expected the
// commitment dance is finished and all subsequent states have been
// updated. Yet we'd receive the error `cannot co-op close channel with
// active htlcs` or `link failed to shutdown` if we close the channel.
// We need to investigate the order of settling the payments and
// updating commitments to understand and fix .
time.Sleep(2 * time.Second)
// Close all channels without mining the closing transactions.
m.ht.CloseChannelAssertPending(m.alice, m.channelPoints[0], false)
m.ht.CloseChannelAssertPending(m.alice, m.channelPoints[1], false)
m.ht.CloseChannelAssertPending(m.carol, m.channelPoints[2], false)
m.ht.CloseChannelAssertPending(m.carol, m.channelPoints[3], false)
m.ht.CloseChannelAssertPending(m.dave, m.channelPoints[4], false)
m.ht.CloseChannelAssertPending(m.eve, m.channelPoints[5], false)
// Now mine a block to include all the closing transactions.
m.ht.MineBlocks(1)
// Assert that the channels are closed.
for _, hn := range m.nodes {
m.ht.AssertNumWaitingClose(hn, 0)
}
}
// Helper function for Alice to build a route from pubkeys.
func (m *mppTestScenario) buildRoute(amt btcutil.Amount,
sender *node.HarnessNode, hops []*node.HarnessNode) *lnrpc.Route {
rpcHops := make([][]byte, 0, len(hops))
for _, hop := range hops {
k := hop.PubKeyStr
pubkey, err := route.NewVertexFromStr(k)
require.NoErrorf(m.ht, err, "error parsing %v: %v", k, err)
rpcHops = append(rpcHops, pubkey[:])
}
req := &routerrpc.BuildRouteRequest{
AmtMsat: int64(amt * 1000),
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
HopPubkeys: rpcHops,
}
routeResp := sender.RPC.BuildRoute(req)
return routeResp.Route
}
// updatePolicy updates a Dave's global channel policy and returns the expected
// policy for further check. It changes Dave's `FeeBaseMsat` from 1000 msat to
// 500,000 msat, and `FeeProportionalMillonths` from 1 msat to 1000 msat.
func (m *mppTestScenario) updateDaveGlobalPolicy() *lnrpc.RoutingPolicy {
const (
baseFeeMsat = 500_000
feeRate = 0.001
maxHtlcMsat = 133_650_000
)
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFeeMsat,
FeeRateMilliMsat: feeRate * testFeeBase,
TimeLockDelta: 40,
MinHtlc: 1000, // default value
MaxHtlcMsat: maxHtlcMsat,
}
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFeeMsat,
FeeRate: feeRate,
TimeLockDelta: 40,
Scope: &lnrpc.PolicyUpdateRequest_Global{Global: true},
MaxHtlcMsat: maxHtlcMsat,
}
m.dave.RPC.UpdateChannelPolicy(updateFeeReq)
return expectedPolicy
}
@@ -1,390 +0,0 @@
package itest
import (
"math"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
func testHtlcErrorPropagation(ht *lntemp.HarnessTest) {
// In this test we wish to exercise the daemon's correct parsing,
// handling, and propagation of errors that occur while processing a
// multi-hop payment.
const chanAmt = funding.MaxBtcFundingAmount
alice, bob := ht.Alice, ht.Bob
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carol := ht.NewNode("Carol", nil)
ht.ConnectNodes(bob, carol)
// Before we start sending payments, subscribe to htlc events for each
// node.
aliceEvents := alice.RPC.SubscribeHtlcEvents()
bobEvents := bob.RPC.SubscribeHtlcEvents()
carolEvents := carol.RPC.SubscribeHtlcEvents()
// Once subscribed, the first event will be UNKNOWN.
ht.AssertHtlcEventType(aliceEvents, routerrpc.HtlcEvent_UNKNOWN)
ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN)
ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_UNKNOWN)
// First establish a channel with a capacity of 0.5 BTC between Alice
// and Bob.
chanPointAlice := ht.OpenChannel(
alice, bob,
lntemp.OpenChannelParams{Amt: chanAmt},
)
// Next, we'll create a connection from Bob to Carol, and open a
// channel between them so we have the topology: Alice -> Bob -> Carol.
// The channel created will be of lower capacity that the one created
// above.
const bobChanAmt = funding.MaxBtcFundingAmount
chanPointBob := ht.OpenChannel(
bob, carol, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Ensure that Alice has Carol in her routing table before proceeding.
ht.AssertTopologyChannelOpen(alice, chanPointBob)
cType := ht.GetChannelCommitType(alice, chanPointAlice)
commitFee := calcStaticFee(cType, 0)
assertBaseBalance := func() {
// Alice has opened a channel with Bob with zero push amount,
// so it's remote balance is zero.
expBalanceAlice := &lnrpc.ChannelBalanceResponse{
LocalBalance: &lnrpc.Amount{
Sat: uint64(chanAmt - commitFee),
Msat: uint64(lnwire.NewMSatFromSatoshis(
chanAmt - commitFee,
)),
},
RemoteBalance: &lnrpc.Amount{},
UnsettledLocalBalance: &lnrpc.Amount{},
UnsettledRemoteBalance: &lnrpc.Amount{},
PendingOpenLocalBalance: &lnrpc.Amount{},
PendingOpenRemoteBalance: &lnrpc.Amount{},
// Deprecated fields.
Balance: int64(chanAmt - commitFee),
}
ht.AssertChannelBalanceResp(alice, expBalanceAlice)
// Bob has a channel with Alice and another with Carol, so it's
// local and remote balances are both chanAmt - commitFee.
expBalanceBob := &lnrpc.ChannelBalanceResponse{
LocalBalance: &lnrpc.Amount{
Sat: uint64(chanAmt - commitFee),
Msat: uint64(lnwire.NewMSatFromSatoshis(
chanAmt - commitFee,
)),
},
RemoteBalance: &lnrpc.Amount{
Sat: uint64(chanAmt - commitFee),
Msat: uint64(lnwire.NewMSatFromSatoshis(
chanAmt - commitFee,
)),
},
UnsettledLocalBalance: &lnrpc.Amount{},
UnsettledRemoteBalance: &lnrpc.Amount{},
PendingOpenLocalBalance: &lnrpc.Amount{},
PendingOpenRemoteBalance: &lnrpc.Amount{},
// Deprecated fields.
Balance: int64(chanAmt - commitFee),
}
ht.AssertChannelBalanceResp(bob, expBalanceBob)
}
// assertLinkFailure checks that the stream provided has a single link
// failure the failure detail provided.
assertLinkFailure := func(event *routerrpc.HtlcEvent,
failureDetail routerrpc.FailureDetail) {
linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
require.Truef(ht, ok, "expected forwarding failure, got: %T",
linkFail)
require.Equal(ht, failureDetail,
linkFail.LinkFailEvent.FailureDetail,
"wrong link fail detail")
}
// With the channels, open we can now start to test our multi-hop error
// scenarios. First, we'll generate an invoice from carol that we'll
// use to test some error cases.
const payAmt = 10000
invoiceReq := &lnrpc.Invoice{
Memo: "kek99",
Value: payAmt,
}
carolInvoice := carol.RPC.AddInvoice(invoiceReq)
carolPayReq := carol.RPC.DecodePayReq(carolInvoice.PaymentRequest)
// For the first scenario, we'll test the cancellation of an HTLC with
// an unknown payment hash.
sendReq := &routerrpc.SendPaymentRequest{
PaymentHash: ht.Random32Bytes(),
Dest: carol.PubKey[:],
Amt: payAmt,
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
MaxParts: 1,
}
ht.SendPaymentAssertFail(
alice, sendReq,
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
)
ht.AssertLastHTLCError(
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
)
// assertAliceAndBob is a helper closure that asserts Alice and Bob
// each has one forward and one forward fail event, and Bob has the
// final htlc fail event.
assertAliceAndBob := func() {
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForward,
)
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForwardFail,
)
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_FORWARD,
lntemp.HtlcEventForward,
)
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_FORWARD,
lntemp.HtlcEventForwardFail,
)
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_UNKNOWN,
lntemp.HtlcEventFinal,
)
}
// We expect alice and bob to each have one forward and one forward
// fail event at this stage.
assertAliceAndBob()
// Carol should have a link failure because the htlc failed on her
// incoming link.
event := ht.AssertHtlcEventType(
carolEvents, routerrpc.HtlcEvent_RECEIVE,
)
assertLinkFailure(event, routerrpc.FailureDetail_UNKNOWN_INVOICE)
// There's also a final htlc event that gives the final outcome of the
// htlc.
ht.AssertHtlcEventTypes(
carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
)
// The balances of all parties should be the same as initially since
// the HTLC was canceled.
assertBaseBalance()
// Next, we'll test the case of a recognized payHash but, an incorrect
// value on the extended HTLC.
htlcAmt := lnwire.NewMSatFromSatoshis(1000)
sendReq = &routerrpc.SendPaymentRequest{
PaymentHash: carolInvoice.RHash,
Dest: carol.PubKey[:],
// 10k satoshis are expected.
Amt: int64(htlcAmt.ToSatoshis()),
FinalCltvDelta: int32(carolPayReq.CltvExpiry),
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
MaxParts: 1,
}
ht.SendPaymentAssertFail(
alice, sendReq,
lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, //nolint:lll
)
ht.AssertLastHTLCError(
alice, lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
)
// We expect alice and bob to each have one forward and one forward
// fail event at this stage.
assertAliceAndBob()
// Carol should have a link failure because the htlc failed on her
// incoming link.
event = ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_RECEIVE)
assertLinkFailure(event, routerrpc.FailureDetail_INVOICE_UNDERPAID)
// There's also a final htlc event that gives the final outcome of the
// htlc.
ht.AssertHtlcEventTypes(
carolEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
)
// The balances of all parties should be the same as initially since
// the HTLC was canceled.
assertBaseBalance()
// Next we'll test an error that occurs mid-route due to an outgoing
// link having insufficient capacity. In order to do so, we'll first
// need to unbalance the link connecting Bob<->Carol.
//
// To do so, we'll push most of the funds in the channel over to
// Alice's side, leaving on 10k satoshis of available balance for bob.
// There's a max payment amount, so we'll have to do this
// incrementally.
chanReserve := int64(chanAmt / 100)
amtToSend := int64(chanAmt) - chanReserve - 20000
amtSent := int64(0)
for amtSent != amtToSend {
// We'll send in chunks of the max payment amount. If we're
// about to send too much, then we'll only send the amount
// remaining.
toSend := int64(math.MaxUint32)
if toSend+amtSent > amtToSend {
toSend = amtToSend - amtSent
}
invoiceReq = &lnrpc.Invoice{
Value: toSend,
}
carolInvoice2 := carol.RPC.AddInvoice(invoiceReq)
req := &routerrpc.SendPaymentRequest{
PaymentRequest: carolInvoice2.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
MaxParts: 1,
}
ht.SendPaymentAndAssertStatus(bob, req, lnrpc.Payment_SUCCEEDED)
// For each send bob makes, we need to check that bob has a
// forward and settle event for his send, and carol has a
// settle event and a final htlc event for her receive.
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForward,
)
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventSettle,
)
ht.AssertHtlcEventTypes(
carolEvents, routerrpc.HtlcEvent_RECEIVE,
lntemp.HtlcEventSettle,
)
ht.AssertHtlcEventTypes(
carolEvents, routerrpc.HtlcEvent_UNKNOWN,
lntemp.HtlcEventFinal,
)
amtSent += toSend
}
// At this point, Alice has 50mil satoshis on her side of the channel,
// but Bob only has 10k available on his side of the channel. So a
// payment from Alice to Carol worth 100k satoshis should fail.
invoiceReq = &lnrpc.Invoice{
Value: 100000,
}
carolInvoice3 := carol.RPC.AddInvoice(invoiceReq)
sendReq = &routerrpc.SendPaymentRequest{
PaymentRequest: carolInvoice3.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
MaxParts: 1,
}
ht.SendPaymentAssertFail(
alice, sendReq,
lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
)
ht.AssertLastHTLCError(
alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
)
// Alice should have a forwarding event and a forwarding failure.
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForward,
)
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForwardFail,
)
// Bob should have a link failure because the htlc failed on his
// outgoing link.
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
assertLinkFailure(event, routerrpc.FailureDetail_INSUFFICIENT_BALANCE)
// There's also a final htlc event that gives the final outcome of the
// htlc.
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
)
// Generate new invoice to not pay same invoice twice.
carolInvoice = carol.RPC.AddInvoice(invoiceReq)
// For our final test, we'll ensure that if a target link isn't
// available for what ever reason then the payment fails accordingly.
//
// We'll attempt to complete the original invoice we created with Carol
// above, but before we do so, Carol will go offline, resulting in a
// failed payment.
ht.Shutdown(carol)
// Reset mission control to forget the temporary channel failure above.
alice.RPC.ResetMissionControl()
req := &routerrpc.SendPaymentRequest{
PaymentRequest: carolInvoice.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
MaxParts: 1,
}
ht.SendPaymentAssertFail(
alice, req, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
)
ht.AssertLastHTLCError(alice, lnrpc.Failure_UNKNOWN_NEXT_PEER)
// Alice should have a forwarding event and subsequent fail.
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForward,
)
ht.AssertHtlcEventTypes(
aliceEvents, routerrpc.HtlcEvent_SEND,
lntemp.HtlcEventForwardFail,
)
// Bob should have a link failure because he could not find the next
// peer.
event = ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_FORWARD)
assertLinkFailure(event, routerrpc.FailureDetail_NO_DETAIL)
// There's also a final htlc event that gives the final outcome of the
// htlc.
ht.AssertHtlcEventTypes(
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntemp.HtlcEventFinal,
)
// 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.
ht.CloseChannel(alice, chanPointAlice)
// Force close Bob's final channel.
ht.ForceCloseChannel(bob, chanPointBob)
}
-254
View File
@@ -1,254 +0,0 @@
package itest
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/stretchr/testify/require"
)
func testMultiHopPayments(ht *lntemp.HarnessTest) {
const chanAmt = btcutil.Amount(100000)
// As preliminary setup, we'll create two new nodes: Carol and Dave,
// such that we now have a 4 node, 3 channel topology. Dave will make a
// channel with Alice, and Carol with Dave. After this setup, the
// network topology should now look like:
// Carol -> Dave -> Alice -> Bob
alice, bob := ht.Alice, ht.Bob
daveArgs := []string{"--protocol.legacy.onion"}
dave := ht.NewNode("Dave", daveArgs)
carol := ht.NewNode("Carol", nil)
// Subscribe events early so we don't miss it out.
aliceEvents := alice.RPC.SubscribeHtlcEvents()
bobEvents := bob.RPC.SubscribeHtlcEvents()
carolEvents := carol.RPC.SubscribeHtlcEvents()
daveEvents := dave.RPC.SubscribeHtlcEvents()
// Once subscribed, the first event will be UNKNOWN.
ht.AssertHtlcEventType(aliceEvents, routerrpc.HtlcEvent_UNKNOWN)
ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN)
ht.AssertHtlcEventType(carolEvents, routerrpc.HtlcEvent_UNKNOWN)
ht.AssertHtlcEventType(daveEvents, routerrpc.HtlcEvent_UNKNOWN)
// Connect the nodes.
ht.ConnectNodes(dave, alice)
ht.ConnectNodes(carol, dave)
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
chanPointAlice := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// We'll create Dave and establish a channel to Alice. Dave will be
// running an older node that requires the legacy onion payload.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
chanPointDave := ht.OpenChannel(
dave, alice, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Next, we'll create Carol and establish a channel to from her to
// Dave.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
chanPointCarol := ht.OpenChannel(
carol, dave, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Create 5 invoices for Bob, which expect a payment from Carol for 1k
// satoshis with a different preimage each time.
const numPayments = 5
const paymentAmt = 1000
payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numPayments)
// Set the fee policies of the Alice -> Bob and the Dave -> Alice
// channel edges to relatively large non default values. This makes it
// possible to pick up more subtle fee calculation errors.
maxHtlc := calculateMaxHtlc(chanAmt)
const aliceBaseFeeSat = 1
const aliceFeeRatePPM = 100000
updateChannelPolicy(
ht, alice, chanPointAlice, aliceBaseFeeSat*1000,
aliceFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta,
maxHtlc, carol,
)
const daveBaseFeeSat = 5
const daveFeeRatePPM = 150000
updateChannelPolicy(
ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol,
)
// Using Carol as the source, pay to the 5 invoices from Bob created
// above.
ht.CompletePaymentRequests(carol, payReqs)
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Bob, the sink within the
// payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Carol->David->Alice->Bob, order is Bob,
// Alice, David, Carol.
// The final node bob expects to get paid five times 1000 sat.
expectedAmountPaidAtoB := int64(numPayments * paymentAmt)
ht.AssertAmountPaid("Alice(local) => Bob(remote)", bob,
chanPointAlice, int64(0), expectedAmountPaidAtoB)
ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice,
chanPointAlice, expectedAmountPaidAtoB, int64(0))
// To forward a payment of 1000 sat, Alice is charging a fee of
// 1 sat + 10% = 101 sat.
const aliceFeePerPayment = aliceBaseFeeSat +
(paymentAmt * aliceFeeRatePPM / 1_000_000)
const expectedFeeAlice = numPayments * aliceFeePerPayment
// Dave needs to pay what Alice pays plus Alice's fee.
expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice
ht.AssertAmountPaid("Dave(local) => Alice(remote)", alice,
chanPointDave, int64(0), expectedAmountPaidDtoA)
ht.AssertAmountPaid("Dave(local) => Alice(remote)", dave,
chanPointDave, expectedAmountPaidDtoA, int64(0))
// To forward a payment of 1101 sat, Dave is charging a fee of
// 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170.
const davePaymentAmt = paymentAmt + aliceFeePerPayment
const daveFeePerPayment = daveBaseFeeSat +
(davePaymentAmt * daveFeeRatePPM / 1_000_000)
const expectedFeeDave = numPayments * daveFeePerPayment
// Carol needs to pay what Dave pays plus Dave's fee.
expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave
ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave,
chanPointCarol, int64(0), expectedAmountPaidCtoD)
ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol,
chanPointCarol, expectedAmountPaidCtoD, int64(0))
// Now that we know all the balances have been settled out properly,
// we'll ensure that our internal record keeping for completed circuits
// was properly updated.
// First, check that the FeeReport response shows the proper fees
// accrued over each time range. Dave should've earned 170 satoshi for
// each of the forwarded payments.
ht.AssertFeeReport(
dave, expectedFeeDave, expectedFeeDave, expectedFeeDave,
)
// Next, ensure that if we issue the vanilla query for the forwarding
// history, it returns 5 values, and each entry is formatted properly.
// From David's perspective he receives a payement from Carol and
// forwards it to Alice. So let's ensure that the forwarding history
// returns Carol's peer alias as inbound and Alice's alias as outbound.
info := carol.RPC.GetInfo()
carolAlias := info.Alias
info = alice.RPC.GetInfo()
aliceAlias := info.Alias
fwdingHistory := dave.RPC.ForwardingHistory(nil)
require.Len(ht, fwdingHistory.ForwardingEvents, numPayments)
expectedForwardingFee := uint64(expectedFeeDave / numPayments)
for _, event := range fwdingHistory.ForwardingEvents {
// Each event should show a fee of 170 satoshi.
require.Equal(ht, expectedForwardingFee, event.Fee)
// Check that peer aliases are empty since the
// ForwardingHistoryRequest did not specify the PeerAliasLookup
// flag.
require.Empty(ht, event.PeerAliasIn)
require.Empty(ht, event.PeerAliasOut)
}
// Lookup the forwarding history again but this time also lookup the
// peers' alias names.
fwdingHistory = dave.RPC.ForwardingHistory(
&lnrpc.ForwardingHistoryRequest{
PeerAliasLookup: true,
},
)
require.Len(ht, fwdingHistory.ForwardingEvents, numPayments)
for _, event := range fwdingHistory.ForwardingEvents {
// Each event should show a fee of 170 satoshi.
require.Equal(ht, expectedForwardingFee, event.Fee)
// Check that peer aliases adhere to payment flow, namely
// Carol->Dave->Alice.
require.Equal(ht, carolAlias, event.PeerAliasIn)
require.Equal(ht, aliceAlias, event.PeerAliasOut)
}
// We expect Carol to have successful forwards and settles for
// her sends.
ht.AssertHtlcEvents(
carolEvents, numPayments, 0, numPayments,
routerrpc.HtlcEvent_SEND,
)
// Dave and Alice should both have forwards and settles for
// their role as forwarding nodes.
ht.AssertHtlcEvents(
daveEvents, numPayments, 0, numPayments,
routerrpc.HtlcEvent_FORWARD,
)
ht.AssertHtlcEvents(
aliceEvents, numPayments, 0, numPayments,
routerrpc.HtlcEvent_FORWARD,
)
// Bob should only have settle events for his receives.
ht.AssertHtlcEvents(
bobEvents, 0, 0, numPayments, routerrpc.HtlcEvent_RECEIVE,
)
// Finally, close all channels.
ht.CloseChannel(alice, chanPointAlice)
ht.CloseChannel(dave, chanPointDave)
ht.CloseChannel(carol, chanPointCarol)
}
// updateChannelPolicy updates the channel policy of node to the given fees and
// timelock delta. This function blocks until listenerNode has received the
// policy update.
//
// NOTE: only used in current test.
func updateChannelPolicy(ht *lntemp.HarnessTest, hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint, baseFee int64,
feeRate int64, timeLockDelta uint32,
maxHtlc uint64, listenerNode *node.HarnessNode) {
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: baseFee,
FeeRateMilliMsat: feeRate,
TimeLockDelta: timeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: maxHtlc,
}
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate) / testFeeBase,
TimeLockDelta: timeLockDelta,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
},
MaxHtlcMsat: maxHtlc,
}
hn.RPC.UpdateChannelPolicy(updateFeeReq)
// Wait for listener node to receive the channel update from node.
ht.AssertChannelPolicyUpdate(
listenerNode, hn, expectedPolicy, chanPoint, false,
)
}
File diff suppressed because it is too large. Load diff
-240
View File
@@ -1,240 +0,0 @@
package itest
import (
"fmt"
"net"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testNetworkConnectionTimeout checks that the connectiontimeout is taking
// effect. It creates a node with a small connection timeout value, and
// connects it to a non-routable IP address.
func testNetworkConnectionTimeout(ht *lntemp.HarnessTest) {
var (
// testPub is a random public key for testing only.
testPub = "0332bda7da70fefe4b6ab92f53b3c4f4ee7999" +
"f312284a8e89c8670bb3f67dbee2"
// testHost is a non-routable IP address. It's used to cause a
// connection timeout.
testHost = "10.255.255.255"
)
// First, test the global timeout settings.
// Create Carol with a connection timeout of 1 millisecond.
carol := ht.NewNode("Carol", []string{"--connectiontimeout=1ms"})
// Try to connect Carol to a non-routable IP address, which should give
// us a timeout error.
req := &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: testPub,
Host: testHost,
},
}
// assertTimeoutError asserts that a connection timeout error is
// raised. A context with a default timeout is used to make the
// request. If our customized connection timeout is less than the
// default, we won't see the request context times out, instead a
// network connection timeout will be returned.
assertTimeoutError := func(hn *node.HarnessNode,
req *lnrpc.ConnectPeerRequest) {
err := hn.RPC.ConnectPeerAssertErr(req)
// Check that the network returns a timeout error.
require.Containsf(ht, err.Error(), "i/o timeout",
"expected to get a timeout error, instead got: %v", err)
}
assertTimeoutError(carol, req)
// Second, test timeout on the connect peer request.
// Create Dave with the default timeout setting.
dave := ht.NewNode("Dave", nil)
// Try to connect Dave to a non-routable IP address, using a timeout
// value of 1ms, which should give us a timeout error immediately.
req = &lnrpc.ConnectPeerRequest{
Addr: &lnrpc.LightningAddress{
Pubkey: testPub,
Host: testHost,
},
Timeout: 1,
}
assertTimeoutError(dave, req)
}
// testReconnectAfterIPChange verifies that if a persistent inbound node changes
// its listening address then it's peer will still be able to reconnect to it.
func testReconnectAfterIPChange(ht *lntemp.HarnessTest) {
// In this test, the following network will be set up. A single
// dash line represents a peer connection and a double dash line
// represents a channel.
// Charlie will create a connection to Dave so that Dave is the inbound
// peer. This will be made a persistent connection for Charlie so that
// Charlie will attempt to reconnect to Dave if Dave restarts.
// A channel will be opened between Dave and Alice to ensure that any
// NodeAnnouncements that Dave sends will reach Alice.
// The connection between Alice and Charlie ensures that Charlie
// receives all of Dave's NodeAnnouncements.
// The desired behaviour is that if Dave changes his P2P IP address then
// Charlie should still be able to reconnect to him.
//
// /------- Charlie <-----\
// | |
// v |
// Dave <===============> Alice
// The first thing we will test is the case where Dave advertises two
// external IP addresses and then switches from the first one listed
// to the second one listed. The desired behaviour is that Charlie will
// attempt both of Dave's advertised addresses when attempting to
// reconnect.
// Create a new node, Charlie.
charlie := ht.NewNode("Charlie", nil)
// We derive an extra port for Dave, and we initialise his node with
// the port advertised as `--externalip` arguments.
ip2 := lntest.NextAvailablePort()
// Create a new node, Dave, which will initialize a P2P port for him.
daveArgs := []string{fmt.Sprintf("--externalip=127.0.0.1:%d", ip2)}
dave := ht.NewNode("Dave", daveArgs)
// We now have two ports, the initial P2P port from creating the node,
// and the `externalip` specified above.
advertisedAddrs := []string{
fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort),
fmt.Sprintf("127.0.0.1:%d", ip2),
}
// Connect Alice to Dave and Charlie.
alice := ht.Alice
ht.ConnectNodes(alice, dave)
ht.ConnectNodes(alice, charlie)
// We'll then go ahead and open a channel between Alice and Dave. This
// ensures that Charlie receives the node announcement from Alice as
// part of the announcement broadcast.
chanPoint := ht.OpenChannel(
alice, dave, lntemp.OpenChannelParams{Amt: 1000000},
)
// waitForNodeAnnouncement is a closure used to wait on the given graph
// subscription for a node announcement from a node with the given
// public key. It also waits for the node announcement that advertises
// a particular set of addresses.
waitForNodeAnnouncement := func(nodePubKey string, addrs []string) {
err := wait.NoError(func() error {
// Expect to have at least 1 node announcement now.
updates := ht.AssertNumNodeAnns(charlie, nodePubKey, 1)
// Get latest node update from the node.
update := updates[len(updates)-1]
addrMap := make(map[string]bool)
for _, addr := range update.NodeAddresses {
addrMap[addr.GetAddr()] = true
}
// Check that our wanted addresses can be found from
// the node update.
for _, addr := range addrs {
if !addrMap[addr] {
return fmt.Errorf("address %s not "+
"found", addr)
}
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking node ann")
}
// Wait for Charlie to receive Dave's initial NodeAnnouncement.
waitForNodeAnnouncement(dave.PubKeyStr, advertisedAddrs)
// Now create a persistent connection between Charlie and Dave with no
// channels. Charlie is the outbound node and Dave is the inbound node.
ht.ConnectNodesPerm(charlie, dave)
// Change Dave's P2P port to the second IP address that he advertised
// and restart his node.
dave.Cfg.P2PPort = ip2
ht.RestartNode(dave)
// assert that Dave and Charlie reconnect successfully after Dave
// changes to his second advertised address.
ht.AssertConnected(dave, charlie)
// Next we test the case where Dave changes his listening address to one
// that was not listed in his original advertised addresses. The desired
// behaviour is that Charlie will update his connection requests to Dave
// when he receives the Node Announcement from Dave with his updated
// address.
// Change Dave's listening port and restart.
dave.Cfg.P2PPort = lntest.NextAvailablePort()
dave.Cfg.ExtraArgs = []string{
fmt.Sprintf(
"--externalip=127.0.0.1:%d", dave.Cfg.P2PPort,
),
}
ht.RestartNode(dave)
// Show that Charlie does receive Dave's new listening address in
// a Node Announcement.
waitForNodeAnnouncement(
dave.PubKeyStr,
[]string{fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort)},
)
// assert that Dave and Charlie do reconnect after Dave changes his P2P
// address to one not listed in Dave's original advertised list of
// addresses.
ht.AssertConnected(dave, charlie)
// Finally, close the channel.
ht.CloseChannel(alice, chanPoint)
}
// testAddPeerConfig tests that the "--addpeer" config flag successfully adds
// a new peer.
func testAddPeerConfig(ht *lntemp.HarnessTest) {
alice := ht.Alice
info := alice.RPC.GetInfo()
alicePeerAddress := info.Uris[0]
// Create a new node (Carol) with Alice as a peer.
args := []string{fmt.Sprintf("--addpeer=%v", alicePeerAddress)}
carol := ht.NewNode("Carol", args)
ht.EnsureConnected(alice, carol)
// If we list Carol's peers, Alice should already be
// listed as one, since we specified her using the
// addpeer flag.
listPeersResp := carol.RPC.ListPeers()
parsedPeerAddr, err := lncfg.ParseLNAddressString(
alicePeerAddress, "9735", net.ResolveTCPAddr,
)
require.NoError(ht, err)
parsedKeyStr := fmt.Sprintf(
"%x", parsedPeerAddr.IdentityKey.SerializeCompressed(),
)
require.Equal(ht, parsedKeyStr, listPeersResp.Peers[0].PubKey)
}
-33
View File
@@ -1,33 +0,0 @@
package itest
import (
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
)
// testNeutrino checks that the neutrino sub-server can fetch compact
// block filters, server status and connect to a connected peer.
func testNeutrino(ht *lntemp.HarnessTest) {
if ht.ChainBackendName() != lntest.NeutrinoBackendName {
ht.Skipf("skipping test for non neutrino backends")
}
// Check if the neutrino sub server is running.
statusRes := ht.Alice.RPC.Status(nil)
require.True(ht, statusRes.Active)
require.Len(ht, statusRes.Peers, 1, "unable to find a peer")
// Request the compact block filter of the best block.
cFilterReq := &neutrinorpc.GetCFilterRequest{
Hash: statusRes.GetBlockHash(),
}
ht.Alice.RPC.GetCFilter(cFilterReq)
// Try to reconnect to a connected peer.
addPeerReq := &neutrinorpc.AddPeerRequest{
PeerAddrs: statusRes.Peers[0],
}
ht.Alice.RPC.AddPeer(addPeerReq)
}
@@ -1,10 +0,0 @@
//go:build !kvdb_etcd
// +build !kvdb_etcd
package itest
import "github.com/lightningnetwork/lnd/lntemp"
// testEtcdFailover is an empty itest when LND is not compiled with etcd
// support.
func testEtcdFailover(ht *lntemp.HarnessTest) {}
-135
View File
@@ -1,135 +0,0 @@
package itest
import (
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/stretchr/testify/require"
)
func testNonstdSweep(ht *lntemp.HarnessTest) {
p2shAddr, err := btcutil.NewAddressScriptHash(
make([]byte, 1), harnessNetParams,
)
require.NoError(ht, err)
p2pkhAddr, err := btcutil.NewAddressPubKeyHash(
make([]byte, 20), harnessNetParams,
)
require.NoError(ht, err)
p2wshAddr, err := btcutil.NewAddressWitnessScriptHash(
make([]byte, 32), harnessNetParams,
)
require.NoError(ht, err)
p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash(
make([]byte, 20), harnessNetParams,
)
require.NoError(ht, err)
p2trAddr, err := btcutil.NewAddressTaproot(
make([]byte, 32), harnessNetParams,
)
require.NoError(ht, err)
tests := []struct {
name string
address string
}{
{
name: "p2sh SendCoins standardness",
address: p2shAddr.EncodeAddress(),
},
{
name: "p2pkh SendCoins standardness",
address: p2pkhAddr.EncodeAddress(),
},
{
name: "p2wsh SendCoins standardness",
address: p2wshAddr.EncodeAddress(),
},
{
name: "p2wkh SendCoins standardness",
address: p2wkhAddr.EncodeAddress(),
},
{
name: "p2tr SendCoins standardness",
address: p2trAddr.EncodeAddress(),
},
}
for _, test := range tests {
test := test
success := ht.Run(test.name, func(t *testing.T) {
st := ht.Subtest(t)
testNonStdSweepInner(st, test.address)
})
if !success {
break
}
}
}
func testNonStdSweepInner(ht *lntemp.HarnessTest, address string) {
carol := ht.NewNode("carol", nil)
// Give Carol a UTXO so SendCoins will behave as expected.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Set the fee estimate to 1sat/vbyte.
ht.SetFeeEstimate(250)
// Make Carol call SendCoins with the SendAll flag and the created
// address.
sendReq := &lnrpc.SendCoinsRequest{
Addr: address,
SatPerVbyte: 1,
SendAll: true,
}
// If a non-standard transaction was created, then this SendCoins call
// will fail.
carol.RPC.SendCoins(sendReq)
// Fetch the txid so we can grab the raw transaction.
txid := ht.Miner.AssertNumTxsInMempool(1)[0]
tx := ht.Miner.GetRawTransaction(txid)
msgTx := tx.MsgTx()
// Fetch the fee of the transaction.
var (
inputVal int
outputVal int
fee int
)
for _, inp := range msgTx.TxIn {
// Fetch the previous outpoint's value.
prevOut := inp.PreviousOutPoint
ptx := ht.Miner.GetRawTransaction(&prevOut.Hash)
pout := ptx.MsgTx().TxOut[prevOut.Index]
inputVal += int(pout.Value)
}
for _, outp := range msgTx.TxOut {
outputVal += int(outp.Value)
}
fee = inputVal - outputVal
// Fetch the vsize of the transaction so we can determine if the
// transaction pays >= 1 sat/vbyte.
rawTx := ht.Miner.GetRawTransactionVerbose(txid)
// Require fee >= vbytes.
require.True(ht, fee >= int(rawTx.Vsize))
// Mine a block to keep the mempool clean.
ht.MineBlocksAndAssertNumTxes(1, 1)
}
-569
View File
@@ -1,569 +0,0 @@
package itest
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
"github.com/stretchr/testify/require"
)
// testChainKit tests ChainKit RPC endpoints.
func testChainKit(ht *lntemp.HarnessTest) {
// Test functions registered as test cases spin up separate nodes
// during execution. By calling sub-test functions as seen below we
// avoid the need to start separate nodes.
testChainKitGetBlock(ht)
testChainKitGetBlockHash(ht)
}
// testChainKitGetBlock ensures that given a block hash, the RPC endpoint
// returns the correct target block.
func testChainKitGetBlock(ht *lntemp.HarnessTest) {
// Get best block hash.
bestBlockRes := ht.Alice.RPC.GetBestBlock(nil)
var bestBlockHash chainhash.Hash
err := bestBlockHash.SetBytes(bestBlockRes.BlockHash)
require.NoError(ht, err)
// Retrieve the best block by hash.
getBlockReq := &chainrpc.GetBlockRequest{
BlockHash: bestBlockHash[:],
}
getBlockRes := ht.Alice.RPC.GetBlock(getBlockReq)
// Deserialize the block which was retrieved by hash.
msgBlock := &wire.MsgBlock{}
blockReader := bytes.NewReader(getBlockRes.RawBlock)
err = msgBlock.Deserialize(blockReader)
require.NoError(ht, err)
// Ensure best block hash is the same as retrieved block hash.
expected := bestBlockHash
actual := msgBlock.BlockHash()
require.Equal(ht, expected, actual)
}
// testChainKitGetBlockHash ensures that given a block height, the RPC endpoint
// returns the correct target block hash.
func testChainKitGetBlockHash(ht *lntemp.HarnessTest) {
// Get best block hash.
bestBlockRes := ht.Alice.RPC.GetBestBlock(nil)
// Retrieve the block hash at best block height.
req := &chainrpc.GetBlockHashRequest{
BlockHeight: int64(bestBlockRes.BlockHeight),
}
getBlockHashRes := ht.Alice.RPC.GetBlockHash(req)
// Ensure best block hash is the same as retrieved block hash.
expected := bestBlockRes.BlockHash
actual := getBlockHashRes.BlockHash
require.Equal(ht, expected, actual)
}
// testCPFP ensures that the daemon can bump an unconfirmed transaction's fee
// rate by broadcasting a Child-Pays-For-Parent (CPFP) transaction.
//
// TODO(wilmer): Add RBF case once btcd supports it.
func testCPFP(ht *lntemp.HarnessTest) {
runCPFP(ht, ht.Alice, ht.Bob)
}
// runCPFP ensures that the daemon can bump an unconfirmed transaction's fee
// rate by broadcasting a Child-Pays-For-Parent (CPFP) transaction.
func runCPFP(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode) {
// Skip this test for neutrino, as it's not aware of mempool
// transactions.
if ht.IsNeutrinoBackend() {
ht.Skipf("skipping CPFP test for neutrino backend")
}
// We'll start the test by sending Alice some coins, which she'll use
// to send to Bob.
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
// Create an address for Bob to send the coins to.
req := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
}
resp := bob.RPC.NewAddress(req)
// Send the coins from Alice to Bob. We should expect a transaction to
// be broadcast and seen in the mempool.
sendReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
Amount: btcutil.SatoshiPerBitcoin,
}
alice.RPC.SendCoins(sendReq)
txid := ht.Miner.AssertNumTxsInMempool(1)[0]
// We'll then extract the raw transaction from the mempool in order to
// determine the index of Bob's output.
tx := ht.Miner.GetRawTransaction(txid)
bobOutputIdx := -1
for i, txOut := range tx.MsgTx().TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, ht.Miner.ActiveNet,
)
require.NoErrorf(ht, err, "unable to extract address "+
"from pkScript=%x: %v", txOut.PkScript, err)
if addrs[0].String() == resp.Address {
bobOutputIdx = i
}
}
require.NotEqual(ht, -1, bobOutputIdx, "bob's output was not found "+
"within the transaction")
// Wait until bob has seen the tx and considers it as owned.
op := &lnrpc.OutPoint{
TxidBytes: txid[:],
OutputIndex: uint32(bobOutputIdx),
}
ht.AssertUTXOInWallet(bob, op, "")
// We'll attempt to bump the fee of this transaction by performing a
// CPFP from Alice's point of view.
bumpFeeReq := &walletrpc.BumpFeeRequest{
Outpoint: op,
SatPerVbyte: uint64(
sweep.DefaultMaxFeeRate.FeePerKVByte() / 2000,
),
}
bob.RPC.BumpFee(bumpFeeReq)
// We should now expect to see two transactions within the mempool, a
// parent and its child.
ht.Miner.AssertNumTxsInMempool(2)
// We should also expect to see the output being swept by the
// UtxoSweeper. We'll ensure it's using the fee rate specified.
pendingSweepsResp := bob.RPC.PendingSweeps()
require.Len(ht, pendingSweepsResp.PendingSweeps, 1,
"expected to find 1 pending sweep")
pendingSweep := pendingSweepsResp.PendingSweeps[0]
require.Equal(ht, pendingSweep.Outpoint.TxidBytes, op.TxidBytes,
"output txid not matched")
require.Equal(ht, pendingSweep.Outpoint.OutputIndex, op.OutputIndex,
"output index not matched")
require.Equal(ht, pendingSweep.SatPerVbyte, bumpFeeReq.SatPerVbyte,
"sweep sat per vbyte not matched")
// Mine a block to clean up the unconfirmed transactions.
ht.MineBlocksAndAssertNumTxes(1, 2)
// The input used to CPFP should no longer be pending.
err := wait.NoError(func() error {
resp := bob.RPC.PendingSweeps()
if len(resp.PendingSweeps) != 0 {
return fmt.Errorf("expected 0 pending sweeps, found %d",
len(resp.PendingSweeps))
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking bob's pending sweeps")
}
// testAnchorReservedValue tests that we won't allow sending transactions when
// that would take the value we reserve for anchor fee bumping out of our
// wallet.
func testAnchorReservedValue(ht *lntemp.HarnessTest) {
// Start two nodes supporting anchor channels.
args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
// NOTE: we cannot reuse the standby node here as the test requires the
// node to start with no UTXOs.
alice := ht.NewNode("Alice", args)
bob := ht.Bob
ht.RestartNodeWithExtraArgs(bob, args)
ht.ConnectNodes(alice, bob)
// Send just enough coins for Alice to open a channel without a change
// output.
const (
chanAmt = 1000000
feeEst = 8000
)
ht.FundCoins(chanAmt+feeEst, alice)
// wallet, without a change output. This should not be allowed.
ht.OpenChannelAssertErr(
alice, bob, lntemp.OpenChannelParams{
Amt: chanAmt,
}, lnwallet.ErrReservedValueInvalidated,
)
// Alice opens a smaller channel. This works since it will have a
// change output.
chanPoint1 := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4},
)
// If Alice tries to open another anchor channel to Bob, Bob should not
// reject it as he is not contributing any funds.
chanPoint2 := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4},
)
// Similarly, if Alice tries to open a legacy channel to Bob, Bob
// should not reject it as he is not contributing any funds. We'll
// restart Bob to remove his support for anchors.
ht.RestartNode(bob)
// Before opening the channel, make sure the nodes are connected.
ht.EnsureConnected(alice, bob)
chanPoint3 := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt / 4},
)
chanPoints := []*lnrpc.ChannelPoint{chanPoint1, chanPoint2, chanPoint3}
// Alice tries to send all coins to an internal address. This is
// allowed, since the final wallet balance will still be above the
// reserved value.
req := &lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
}
resp := alice.RPC.NewAddress(req)
sweepReq := &lnrpc.SendCoinsRequest{
Addr: resp.Address,
SendAll: true,
}
alice.RPC.SendCoins(sweepReq)
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
assertNumTxInAndTxOut := func(tx *wire.MsgTx, in, out int) {
require.Len(ht, tx.TxIn, in, "num inputs not matched")
require.Len(ht, tx.TxOut, out, "num outputs not matched")
}
// The sweep transaction should have exactly one input, the change from
// the previous SendCoins call.
sweepTx := block.Transactions[1]
// It should have a single output.
assertNumTxInAndTxOut(sweepTx, 1, 1)
// Wait for Alice to see her balance as confirmed.
waitForConfirmedBalance := func() int64 {
var balance int64
err := wait.NoError(func() error {
resp := alice.RPC.WalletBalance()
if resp.TotalBalance == 0 {
return fmt.Errorf("no balance")
}
if resp.UnconfirmedBalance > 0 {
return fmt.Errorf("unconfirmed balance")
}
balance = resp.TotalBalance
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking alice's balance")
return balance
}
waitForConfirmedBalance()
// Alice tries to send all funds to an external address, the reserved
// value must stay in her wallet.
minerAddr := ht.Miner.NewMinerAddress()
sweepReq = &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
}
alice.RPC.SendCoins(sweepReq)
// We'll mine a block which should include the sweep transaction we
// generated above.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The sweep transaction should have exactly one inputs as we only had
// the single output from above in the wallet.
sweepTx = block.Transactions[1]
// It should have two outputs, one being the miner address, the other
// one being the reserve going back to our wallet.
assertNumTxInAndTxOut(sweepTx, 1, 2)
// The reserved value is now back in Alice's wallet.
aliceBalance := waitForConfirmedBalance()
// Alice closes channel, should now be allowed to send everything to an
// external address.
for _, chanPoint := range chanPoints {
ht.CloseChannel(alice, chanPoint)
}
newBalance := waitForConfirmedBalance()
require.Greater(ht, newBalance, aliceBalance,
"Alice's balance did not increase after channel close")
// Assert there are no open or pending channels anymore.
ht.AssertNumWaitingClose(alice, 0)
ht.AssertNodeNumChannels(alice, 0)
// We'll wait for the balance to reflect that the channel has been
// closed and the funds are in the wallet.
sweepReq = &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
}
alice.RPC.SendCoins(sweepReq)
// We'll mine a block which should include the sweep transaction we
// generated above.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The sweep transaction should have four inputs, the change output from
// the previous sweep, and the outputs from the coop closed channels.
sweepTx = block.Transactions[1]
// It should have a single output.
assertNumTxInAndTxOut(sweepTx, 4, 1)
}
// testAnchorThirdPartySpend tests that if we force close a channel, but then
// don't sweep the anchor in time and a 3rd party spends it, that we remove any
// transactions that are a descendent of that sweep.
func testAnchorThirdPartySpend(ht *lntemp.HarnessTest) {
// First, we'll create two new nodes that both default to anchor
// channels.
//
// NOTE: The itests differ here as anchors is default off vs the normal
// lnd binary.
args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
alice := ht.NewNode("Alice", args)
defer ht.Shutdown(alice)
bob := ht.NewNode("Bob", args)
defer ht.Shutdown(bob)
ht.EnsureConnected(alice, bob)
// We'll fund our Alice with coins, as she'll be opening the channel.
// We'll fund her with *just* enough coins to open the channel and
// sweep the anchor.
const (
firstChanSize = 1_000_000
anchorFeeBuffer = 500_000
)
ht.FundCoins(firstChanSize+anchorFeeBuffer, alice)
// Open the channel between the two nodes and wait for it to confirm
// fully.
aliceChanPoint1 := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: firstChanSize,
},
)
// Send another UTXO if this is a neutrino backend. When sweeping
// anchors, there are two transactions created, `local_sweep_tx` for
// sweeping Alice's anchor on the local commitment, `remote_sweep_tx`
// for sweeping her anchor on the remote commitment. Whenever the force
// close transaction is published, Alice will always create these two
// transactions to sweep her anchor.
// On the other hand, when creating the sweep txes, the anchor itself
// is not able to cover the fee, so another wallet UTXO is needed. In
// our test case, there's a change output that can be used from the
// above funding process. And it's used by both sweep txes - when `lnd`
// happens to create the `remote_sweep_tx` first, it will receive an
// error since its parent tx, the remote commitment, is not known,
// hence freeing the change output to be used by `local_sweep_tx`.
// For neutrino client, however, it will consider the transaction which
// sweeps the remote anchor as an orphan tx, and it will neither send
// it to the mempool nor return an error to free the change output.
// Thus, if the change output is already used in `remote_sweep_tx`, we
// won't have UTXO to create `local_sweep_tx`.
//
// NOTE: the order of the sweep requests for the two anchors cannot be
// guaranteed. If the sweeper happens to sweep the remote anchor first,
// then the test won't pass without the extra UTXO, which is the source
// of the flakeness.
//
// TODO(yy): make a RPC server for sweeper so we can explicitly check
// and control its state.
if ht.IsNeutrinoBackend() {
ht.FundCoins(anchorFeeBuffer, alice)
}
// With the channel open, we'll actually immediately force close it. We
// don't care about network announcements here since there's no routing
// in this test.
ht.CloseChannelAssertPending(alice, aliceChanPoint1, true)
// Now that the channel has been force closed, it should show up in the
// PendingChannels RPC under the waiting close section.
waitingClose := ht.AssertChannelWaitingClose(alice, aliceChanPoint1)
// At this point, the channel is waiting close, and we have both the
// commitment transaction and anchor sweep in the mempool.
const expectedTxns = 2
sweepTxns := ht.Miner.GetNumTxsFromMempool(expectedTxns)
aliceCloseTx := waitingClose.Commitments.LocalTxid
_, aliceAnchor := ht.FindCommitAndAnchor(sweepTxns, aliceCloseTx)
// We'll now mine _only_ the commitment force close transaction, as we
// want the anchor sweep to stay unconfirmed.
forceCloseTxID, _ := chainhash.NewHashFromStr(aliceCloseTx)
commitTxn := ht.Miner.GetRawTransaction(forceCloseTxID)
ht.Miner.MineBlockWithTxes([]*btcutil.Tx{commitTxn})
// With the anchor output located, and the main commitment mined we'll
// instruct the wallet to send all coins in the wallet to a new address
// (to the miner), including unconfirmed change.
minerAddr := ht.Miner.NewMinerAddress()
sweepReq := &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
SendAll: true,
MinConfs: 0,
SpendUnconfirmed: true,
}
sweepAllResp := alice.RPC.SendCoins(sweepReq)
// Both the original anchor sweep transaction, as well as the
// transaction we created to sweep all the coins from Alice's wallet
// should be found in her transaction store.
sweepAllTxID, _ := chainhash.NewHashFromStr(sweepAllResp.Txid)
ht.AssertTransactionInWallet(alice, aliceAnchor.SweepTx.TxHash())
ht.AssertTransactionInWallet(alice, *sweepAllTxID)
// Next, we'll shutdown Alice, and allow 16 blocks to pass so that the
// anchor output can be swept by anyone. Rather than use the normal API
// call, we'll generate a series of _empty_ blocks here.
aliceRestart := ht.SuspendNode(alice)
const anchorCsv = 16
ht.MineEmptyBlocks(anchorCsv)
// Before we sweep the anchor, we'll restart Alice.
require.NoErrorf(ht, aliceRestart(), "unable to restart alice")
// Now that the channel has been closed, and Alice has an unconfirmed
// transaction spending the output produced by her anchor sweep, we'll
// mine a transaction that double spends the output.
thirdPartyAnchorSweep := genAnchorSweep(ht, aliceAnchor, anchorCsv)
ht.Miner.MineBlockWithTxes([]*btcutil.Tx{thirdPartyAnchorSweep})
// At this point, we should no longer find Alice's transaction that
// tried to sweep the anchor in her wallet.
ht.AssertTransactionNotInWallet(alice, aliceAnchor.SweepTx.TxHash())
// In addition, the transaction she sent to sweep all her coins to the
// miner also should no longer be found.
ht.AssertTransactionNotInWallet(alice, *sweepAllTxID)
// The anchor should now show as being "lost", while the force close
// response is still present.
assertAnchorOutputLost(ht, alice, aliceChanPoint1)
// At this point Alice's CSV output should already be fully spent and
// the channel marked as being resolved. We mine a block first, as so
// far we've been generating custom blocks this whole time.
commitSweepOp := wire.OutPoint{
Hash: *forceCloseTxID,
Index: 1,
}
ht.Miner.AssertOutpointInMempool(commitSweepOp)
ht.MineBlocks(1)
ht.AssertNumWaitingClose(alice, 0)
}
// assertAnchorOutputLost asserts that the anchor output for the given channel
// has the state of being lost.
func assertAnchorOutputLost(ht *lntemp.HarnessTest, hn *node.HarnessNode,
chanPoint *lnrpc.ChannelPoint) {
cp := ht.OutPointFromChannelPoint(chanPoint)
expected := lnrpc.PendingChannelsResponse_ForceClosedChannel_LOST
err := wait.NoError(func() error {
resp := hn.RPC.PendingChannels()
channels := resp.PendingForceClosingChannels
for _, c := range channels {
// Not the wanted channel, skipped.
if c.Channel.ChannelPoint != cp.String() {
continue
}
// Found the channel, check the anchor state.
if c.Anchor == expected {
return nil
}
return fmt.Errorf("unexpected anchor state, want %v, "+
"got %v", expected, c.Anchor)
}
return fmt.Errorf("channel not found using cp=%v", cp)
}, defaultTimeout)
require.NoError(ht, err, "anchor doesn't show as being lost")
}
// genAnchorSweep generates a "3rd party" anchor sweeping from an existing one.
// In practice, we just re-use the existing witness, and track on our own
// output producing a 1-in-1-out transaction.
func genAnchorSweep(ht *lntemp.HarnessTest,
aliceAnchor *lntemp.SweptOutput, anchorCsv uint32) *btcutil.Tx {
// At this point, we have the transaction that Alice used to try to
// sweep her anchor. As this is actually just something anyone can
// spend, just need to find the input spending the anchor output, then
// we can swap the output address.
aliceAnchorTxIn := func() wire.TxIn {
sweepCopy := aliceAnchor.SweepTx.Copy()
for _, txIn := range sweepCopy.TxIn {
if txIn.PreviousOutPoint == aliceAnchor.OutPoint {
return *txIn
}
}
require.FailNow(ht, "anchor op not found")
return wire.TxIn{}
}()
// We'll set the signature on the input to nil, and then set the
// sequence to 16 (the anchor CSV period).
aliceAnchorTxIn.Witness[0] = nil
aliceAnchorTxIn.Sequence = anchorCsv
minerAddr := ht.Miner.NewMinerAddress()
addrScript, err := txscript.PayToAddrScript(minerAddr)
require.NoError(ht, err, "unable to gen addr script")
// Now that we have the txIn, we can just make a new transaction that
// uses a different script for the output.
tx := wire.NewMsgTx(2)
tx.AddTxIn(&aliceAnchorTxIn)
tx.AddTxOut(&wire.TxOut{
PkScript: addrScript,
Value: anchorSize - 1,
})
return btcutil.NewTx(tx)
}
-548
View File
@@ -1,548 +0,0 @@
package itest
import (
"fmt"
"testing"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntemp/rpc"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testOpenChannelAfterReorg tests that in the case where we have an open
// channel where the funding tx gets reorged out, the channel will no
// longer be present in the node's routing table.
func testOpenChannelAfterReorg(ht *lntemp.HarnessTest) {
// Skip test for neutrino, as we cannot disconnect the miner at will.
// TODO(halseth): remove when either can disconnect at will, or restart
// node with connection to new miner.
if ht.IsNeutrinoBackend() {
ht.Skipf("skipping reorg test for neutrino backend")
}
temp := "temp"
// Set up a new miner that we can use to cause a reorg.
tempLogDir := ".tempminerlogs"
logFilename := "output-open_channel_reorg-temp_miner.log"
tempMiner := lntemp.NewTempMiner(
ht.Context(), ht.T, tempLogDir, logFilename,
)
defer tempMiner.Stop()
// Setup the temp miner
require.NoError(ht, tempMiner.SetUp(false, 0),
"unable to set up mining node")
miner := ht.Miner
alice, bob := ht.Alice, ht.Bob
// We start by connecting the new miner to our original miner,
// such that it will sync to our original chain.
err := miner.Client.Node(
btcjson.NConnect, tempMiner.P2PAddress(), &temp,
)
require.NoError(ht, err, "unable to connect miners")
nodeSlice := []*rpctest.Harness{miner.Harness, tempMiner.Harness}
err = rpctest.JoinNodes(nodeSlice, rpctest.Blocks)
require.NoError(ht, err, "unable to join node on blocks")
// The two miners should be on the same blockheight.
assertMinerBlockHeightDelta(ht, miner, tempMiner, 0)
// We disconnect the two miners, such that we can mine two different
// chains and can cause a reorg later.
err = miner.Client.Node(
btcjson.NDisconnect, tempMiner.P2PAddress(), &temp,
)
require.NoError(ht, err, "unable to disconnect miners")
// Create a new channel that requires 1 confs before it's considered
// open, then broadcast the funding transaction
params := lntemp.OpenChannelParams{
Amt: funding.MaxBtcFundingAmount,
Private: true,
}
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, params)
// Wait for miner to have seen the funding tx. The temporary miner is
// disconnected, and won't see the transaction.
ht.Miner.AssertNumTxsInMempool(1)
// At this point, the channel's funding transaction will have been
// broadcast, but not confirmed, and the channel should be pending.
ht.AssertNodesNumPendingOpenChannels(alice, bob, 1)
fundingTxID, err := chainhash.NewHash(pendingUpdate.Txid)
require.NoError(ht, err, "convert funding txid into chainhash failed")
// We now cause a fork, by letting our original miner mine 10 blocks,
// and our new miner mine 15. This will also confirm our pending
// channel on the original miner's chain, which should be considered
// open.
block := ht.MineBlocks(10)[0]
ht.Miner.AssertTxInBlock(block, fundingTxID)
_, err = tempMiner.Client.Generate(15)
require.NoError(ht, err, "unable to generate blocks")
// Ensure the chain lengths are what we expect, with the temp miner
// being 5 blocks ahead.
assertMinerBlockHeightDelta(ht, miner, tempMiner, 5)
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: pendingUpdate.Txid,
},
OutputIndex: pendingUpdate.OutputIndex,
}
// Ensure channel is no longer pending.
ht.AssertNodesNumPendingOpenChannels(alice, bob, 0)
// Wait for Alice and Bob to recognize and advertise the new channel
// generated above.
ht.AssertTopologyChannelOpen(alice, chanPoint)
ht.AssertTopologyChannelOpen(bob, chanPoint)
// Alice should now have 1 edge in her graph.
ht.AssertNumEdges(alice, 1, true)
// Now we disconnect Alice's chain backend from the original miner, and
// connect the two miners together. Since the temporary miner knows
// about a longer chain, both miners should sync to that chain.
ht.DisconnectMiner()
// Connecting to the temporary miner should now cause our original
// chain to be re-orged out.
err = miner.Client.Node(btcjson.NConnect, tempMiner.P2PAddress(), &temp)
require.NoError(ht, err, "unable to connect temp miner")
nodes := []*rpctest.Harness{tempMiner.Harness, miner.Harness}
err = rpctest.JoinNodes(nodes, rpctest.Blocks)
require.NoError(ht, err, "unable to join node on blocks")
// Once again they should be on the same chain.
assertMinerBlockHeightDelta(ht, miner, tempMiner, 0)
// Now we disconnect the two miners, and connect our original miner to
// our chain backend once again.
err = miner.Client.Node(
btcjson.NDisconnect, tempMiner.P2PAddress(), &temp,
)
require.NoError(ht, err, "unable to disconnect temp miner")
ht.ConnectMiner()
// This should have caused a reorg, and Alice should sync to the longer
// chain, where the funding transaction is not confirmed.
_, tempMinerHeight, err := tempMiner.Client.GetBestBlock()
require.NoError(ht, err, "unable to get current blockheight")
ht.WaitForNodeBlockHeight(alice, tempMinerHeight)
// Since the fundingtx was reorged out, Alice should now have no edges
// in her graph.
ht.AssertNumEdges(alice, 0, true)
// Cleanup by mining the funding tx again, then closing the channel.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
ht.Miner.AssertTxInBlock(block, fundingTxID)
ht.CloseChannel(alice, chanPoint)
}
// testOpenChannelFeePolicy checks if different channel fee scenarios
// are correctly handled when the optional channel fee parameters
// baseFee and feeRate are provided. If the OpenChannelRequest is not
// provided with a value for baseFee/feeRate the expectation is that the
// default baseFee/feeRate is applied.
// 1.) no params provided to OpenChannelRequest
// ChannelUpdate --> defaultBaseFee, defaultFeeRate
// 2.) only baseFee provided to OpenChannelRequest
// ChannelUpdate --> provided baseFee, defaultFeeRate
// 3.) only feeRate provided to OpenChannelRequest
// ChannelUpdate --> defaultBaseFee, provided FeeRate
// 4.) baseFee and feeRate provided to OpenChannelRequest
// ChannelUpdate --> provided baseFee, provided feeRate.
func testOpenChannelUpdateFeePolicy(ht *lntemp.HarnessTest) {
const (
defaultBaseFee = 1000
defaultFeeRate = 1
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
defaultMinHtlc = 1000
optionalBaseFee = 1337
optionalFeeRate = 1337
)
defaultMaxHtlc := calculateMaxHtlc(funding.MaxBtcFundingAmount)
chanAmt := funding.MaxBtcFundingAmount
pushAmt := chanAmt / 2
feeScenarios := []lntemp.OpenChannelParams{
{
Amt: chanAmt,
PushAmt: pushAmt,
UseBaseFee: false,
UseFeeRate: false,
},
{
Amt: chanAmt,
PushAmt: pushAmt,
BaseFee: optionalBaseFee,
UseBaseFee: true,
UseFeeRate: false,
},
{
Amt: chanAmt,
PushAmt: pushAmt,
FeeRate: optionalFeeRate,
UseBaseFee: false,
UseFeeRate: true,
},
{
Amt: chanAmt,
PushAmt: pushAmt,
BaseFee: optionalBaseFee,
FeeRate: optionalFeeRate,
UseBaseFee: true,
UseFeeRate: true,
},
}
expectedPolicies := []lnrpc.RoutingPolicy{
{
FeeBaseMsat: defaultBaseFee,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
},
{
FeeBaseMsat: optionalBaseFee,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
},
{
FeeBaseMsat: defaultBaseFee,
FeeRateMilliMsat: optionalFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
},
{
FeeBaseMsat: optionalBaseFee,
FeeRateMilliMsat: optionalFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
},
}
bobExpectedPolicy := lnrpc.RoutingPolicy{
FeeBaseMsat: defaultBaseFee,
FeeRateMilliMsat: defaultFeeRate,
TimeLockDelta: defaultTimeLockDelta,
MinHtlc: defaultMinHtlc,
MaxHtlcMsat: defaultMaxHtlc,
}
alice, bob := ht.Alice, ht.Bob
runTestCase := func(ht *lntemp.HarnessTest,
fs lntemp.OpenChannelParams,
alicePolicy, bobPolicy *lnrpc.RoutingPolicy) {
// Create a channel Alice->Bob.
chanPoint := ht.OpenChannel(alice, bob, fs)
defer ht.CloseChannel(alice, chanPoint)
// We add all the nodes' update channels to a slice, such that
// we can make sure they all receive the expected updates.
nodes := []*node.HarnessNode{alice, bob}
// Alice and Bob should see each other's ChannelUpdates,
// advertising the preferred routing policies.
assertNodesPolicyUpdate(
ht, nodes, alice, alicePolicy, chanPoint,
)
assertNodesPolicyUpdate(ht, nodes, bob, bobPolicy, chanPoint)
// They should now know about the default policies.
for _, node := range nodes {
ht.AssertChannelPolicy(
node, alice.PubKeyStr, alicePolicy, chanPoint,
)
ht.AssertChannelPolicy(
node, bob.PubKeyStr, bobPolicy, chanPoint,
)
}
}
for i, feeScenario := range feeScenarios {
ht.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
st := ht.Subtest(t)
ht.EnsureConnected(alice, bob)
runTestCase(
st, feeScenario,
&expectedPolicies[i], &bobExpectedPolicy,
)
})
}
}
// testBasicChannelCreationAndUpdates tests multiple channel opening and
// closing, and ensures that if a node is subscribed to channel updates they
// will be received correctly for both cooperative and force closed channels.
func testBasicChannelCreationAndUpdates(ht *lntemp.HarnessTest) {
runBasicChannelCreationAndUpdates(ht, ht.Alice, ht.Bob)
}
// runBasicChannelCreationAndUpdates tests multiple channel opening and closing,
// and ensures that if a node is subscribed to channel updates they will be
// received correctly for both cooperative and force closed channels.
func runBasicChannelCreationAndUpdates(ht *lntemp.HarnessTest,
alice, bob *node.HarnessNode) {
const (
numChannels = 2
amount = funding.MaxBtcFundingAmount
)
// Subscribe Bob and Alice to channel event notifications.
bobChanSub := bob.RPC.SubscribeChannelEvents()
aliceChanSub := alice.RPC.SubscribeChannelEvents()
// Open the channels between Alice and Bob, asserting that the channels
// have been properly opened on-chain.
chanPoints := make([]*lnrpc.ChannelPoint, numChannels)
for i := 0; i < numChannels; i++ {
chanPoints[i] = ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: amount,
},
)
}
// Since each of the channels just became open, Bob and Alice should
// each receive an open and an active notification for each channel.
const numExpectedOpenUpdates = 3 * numChannels
verifyOpenUpdatesReceived := func(sub rpc.ChannelEventsClient) error {
for i := 0; i < numExpectedOpenUpdates; i++ {
update := ht.ReceiveChannelEvent(sub)
switch update.Type {
case lnrpc.ChannelEventUpdate_PENDING_OPEN_CHANNEL:
if i%3 == 0 {
continue
}
return fmt.Errorf("expected open or active" +
"channel ntfn, got pending open " +
"channel ntfn instead")
case lnrpc.ChannelEventUpdate_OPEN_CHANNEL:
if i%3 == 1 {
continue
}
return fmt.Errorf("expected pending open or " +
"active channel ntfn, got open" +
"channel ntfn instead")
case lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL:
if i%3 == 2 {
continue
}
return fmt.Errorf("expected pending open or " +
"open channel ntfn, got active " +
"channel ntfn instead")
default:
return fmt.Errorf("update type mismatch: "+
"expected open or active channel "+
"notification, got: %v", update.Type)
}
}
return nil
}
require.NoError(ht, verifyOpenUpdatesReceived(bobChanSub),
"bob open channels")
require.NoError(ht, verifyOpenUpdatesReceived(aliceChanSub),
"alice open channels")
// Close the channels between Alice and Bob, asserting that the
// channels have been properly closed on-chain.
for i, chanPoint := range chanPoints {
// Force close the first of the two channels.
force := i%2 == 0
if force {
ht.ForceCloseChannel(alice, chanPoint)
} else {
ht.CloseChannel(alice, chanPoint)
}
}
// verifyCloseUpdatesReceived is used to verify that Alice and Bob
// receive the correct channel updates in order.
const numExpectedCloseUpdates = 3 * numChannels
verifyCloseUpdatesReceived := func(sub rpc.ChannelEventsClient,
forceType lnrpc.ChannelCloseSummary_ClosureType,
closeInitiator lnrpc.Initiator) error {
// Ensure one inactive and one closed notification is received
// for each closed channel.
for i := 0; i < numExpectedCloseUpdates; i++ {
expectedCloseType := lnrpc.
ChannelCloseSummary_COOPERATIVE_CLOSE
// Every other channel should be force closed. If this
// channel was force closed, set the expected close type
// to the type passed in.
force := (i/3)%2 == 0
if force {
expectedCloseType = forceType
}
chanUpdate := ht.ReceiveChannelEvent(sub)
err := verifyCloseUpdate(
chanUpdate, expectedCloseType,
closeInitiator,
)
if err != nil {
return err
}
}
return nil
}
// Verify Bob receives all closed channel notifications. He should
// receive a remote force close notification for force closed channels.
// All channels (cooperatively and force closed) should have a remote
// close initiator because Alice closed the channels.
require.NoError(
ht, verifyCloseUpdatesReceived(
bobChanSub,
lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE,
lnrpc.Initiator_INITIATOR_REMOTE,
), "verifying bob close updates",
)
// Verify Alice receives all closed channel notifications. She should
// receive a remote force close notification for force closed channels.
// All channels (cooperatively and force closed) should have a local
// close initiator because Alice closed the channels.
require.NoError(
ht, verifyCloseUpdatesReceived(
aliceChanSub,
lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE,
lnrpc.Initiator_INITIATOR_LOCAL,
), "verifying alice close updates",
)
}
// assertMinerBlockHeightDelta ensures that tempMiner is 'delta' blocks ahead
// of miner.
func assertMinerBlockHeightDelta(ht *lntemp.HarnessTest,
miner, tempMiner *lntemp.HarnessMiner, delta int32) {
// Ensure the chain lengths are what we expect.
err := wait.NoError(func() error {
_, tempMinerHeight, err := tempMiner.Client.GetBestBlock()
if err != nil {
return fmt.Errorf("unable to get current "+
"blockheight %v", err)
}
_, minerHeight, err := miner.Client.GetBestBlock()
if err != nil {
return fmt.Errorf("unable to get current "+
"blockheight %v", err)
}
if tempMinerHeight != minerHeight+delta {
return fmt.Errorf("expected new miner(%d) to be %d "+
"blocks ahead of original miner(%d)",
tempMinerHeight, delta, minerHeight)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "failed to assert block height delta")
}
// verifyCloseUpdate is used to verify that a closed channel update is of the
// expected type.
func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate,
closeType lnrpc.ChannelCloseSummary_ClosureType,
closeInitiator lnrpc.Initiator) error {
// We should receive one inactive and one closed notification
// for each channel.
switch update := chanUpdate.Channel.(type) {
case *lnrpc.ChannelEventUpdate_InactiveChannel:
if chanUpdate.Type !=
lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL {
return fmt.Errorf("update type mismatch: "+
"expected %v, got %v",
lnrpc.ChannelEventUpdate_INACTIVE_CHANNEL,
chanUpdate.Type)
}
case *lnrpc.ChannelEventUpdate_ClosedChannel:
if chanUpdate.Type !=
lnrpc.ChannelEventUpdate_CLOSED_CHANNEL {
return fmt.Errorf("update type mismatch: "+
"expected %v, got %v",
lnrpc.ChannelEventUpdate_CLOSED_CHANNEL,
chanUpdate.Type)
}
if update.ClosedChannel.CloseType != closeType {
return fmt.Errorf("channel closure type "+
"mismatch: expected %v, got %v",
closeType,
update.ClosedChannel.CloseType)
}
if update.ClosedChannel.CloseInitiator != closeInitiator {
return fmt.Errorf("expected close intiator: %v, "+
"got: %v", closeInitiator,
update.ClosedChannel.CloseInitiator)
}
case *lnrpc.ChannelEventUpdate_FullyResolvedChannel:
if chanUpdate.Type !=
lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL {
return fmt.Errorf("update type mismatch: "+
"expected %v, got %v",
lnrpc.ChannelEventUpdate_FULLY_RESOLVED_CHANNEL,
chanUpdate.Type)
}
default:
return fmt.Errorf("channel update channel of wrong type, "+
"expected closed channel, got %T",
update)
}
return nil
}
-582
View File
@@ -1,582 +0,0 @@
package itest
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
func testListPayments(ht *lntemp.HarnessTest) {
alice, bob := ht.Alice, ht.Bob
// Check that there are no payments before test.
ht.AssertNumPayments(alice, 0)
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
chanAmt := btcutil.Amount(100000)
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Get the number of invoices Bob already has.
//
// TODO(yy): we can remove this check once the `DeleteAllInvoices` rpc
// is added.
invResp := bob.RPC.ListInvoices(nil)
numOldInvoices := len(invResp.Invoices)
// Now that the channel is open, create an invoice for Bob which
// expects a payment of 1000 satoshis from Alice paid via a particular
// preimage.
const paymentAmt = 1000
preimage := ht.Random32Bytes()
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
invoiceResp := bob.RPC.AddInvoice(invoice)
// Check that Bob has added the invoice.
numInvoices := numOldInvoices + 1
ht.AssertNumInvoices(bob, 1)
// With the invoice for Bob added, send a payment towards Alice paying
// to the above generated invoice.
payReqs := []string{invoiceResp.PaymentRequest}
ht.CompletePaymentRequests(alice, payReqs)
// Grab Alice's list of payments, she should show the existence of
// exactly one payment.
p := ht.AssertNumPayments(alice, 1)[0]
path := p.Htlcs[len(p.Htlcs)-1].Route.Hops
// Ensure that the stored path shows a direct payment to Bob with no
// other nodes in-between.
require.Len(ht, path, 1, "wrong number of routes in path")
require.Equal(ht, bob.PubKeyStr, path[0].PubKey, "wrong pub key")
// The payment amount should also match our previous payment directly.
require.EqualValues(ht, paymentAmt, p.ValueSat, "incorrect sat amount")
require.EqualValues(ht, paymentAmt*1000, p.ValueMsat,
"incorrect msat amount")
// The payment hash (or r-hash) should have been stored correctly.
correctRHash := hex.EncodeToString(invoiceResp.RHash)
require.Equal(ht, correctRHash, p.PaymentHash, "incorrect RHash")
// As we made a single-hop direct payment, there should have been no
// fee applied.
require.Zero(ht, p.FeeSat, "fee should be 0")
require.Zero(ht, p.FeeMsat, "fee should be 0")
// Now verify that the payment request returned by the rpc matches the
// invoice that we paid.
require.Equal(ht, invoiceResp.PaymentRequest, p.PaymentRequest,
"incorrect payreq")
// We now check the timestamp filters in `ListPayments`.
//
// Use a start date long time ago should return us the payment.
req := &lnrpc.ListPaymentsRequest{
CreationDateStart: 1227035905,
}
resp := alice.RPC.ListPayments(req)
require.Len(ht, resp.Payments, 1)
// Use an end date long time ago should return us nothing.
req = &lnrpc.ListPaymentsRequest{
CreationDateEnd: 1227035905,
}
resp = alice.RPC.ListPayments(req)
require.Empty(ht, resp.Payments)
// Use a start date far in the future should return us nothing.
req = &lnrpc.ListPaymentsRequest{
CreationDateStart: 5392552705,
}
resp = alice.RPC.ListPayments(req)
require.Empty(ht, resp.Payments)
// Use an end date far in the future should return us the payment.
req = &lnrpc.ListPaymentsRequest{
CreationDateEnd: 5392552705,
}
resp = alice.RPC.ListPayments(req)
require.Len(ht, resp.Payments, 1)
// We now do the same check for `ListInvoices`
//
// Use a start date long time ago should return us the invoice.
invReq := &lnrpc.ListInvoiceRequest{
CreationDateStart: 1227035905,
}
invResp = bob.RPC.ListInvoices(invReq)
require.Len(ht, invResp.Invoices, numInvoices)
// Use an end date long time ago should return us nothing.
invReq = &lnrpc.ListInvoiceRequest{
CreationDateEnd: 1227035905,
}
invResp = bob.RPC.ListInvoices(invReq)
require.Empty(ht, invResp.Invoices)
// Use a start date far in the future should return us nothing.
invReq = &lnrpc.ListInvoiceRequest{
CreationDateStart: 5392552705,
}
invResp = bob.RPC.ListInvoices(invReq)
require.Empty(ht, invResp.Invoices)
// Use an end date far in the future should return us the invoice.
invReq = &lnrpc.ListInvoiceRequest{
CreationDateEnd: 5392552705,
}
invResp = bob.RPC.ListInvoices(invReq)
require.Len(ht, invResp.Invoices, numInvoices)
// Delete all payments from Alice. DB should have no payments.
alice.RPC.DeleteAllPayments()
// Check that there are no payments after test.
ht.AssertNumPayments(alice, 0)
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
// Close the channel.
ht.CloseChannel(alice, chanPoint)
}
// testPaymentFollowingChannelOpen tests that the channel transition from
// 'pending' to 'open' state does not cause any inconsistencies within other
// subsystems trying to update the channel state in the db. We follow this
// transition with a payment that updates the commitment state and verify that
// the pending state is up to date.
func testPaymentFollowingChannelOpen(ht *lntemp.HarnessTest) {
const paymentAmt = btcutil.Amount(100)
channelCapacity := paymentAmt * 1000
// We first establish a channel between Alice and Bob.
alice, bob := ht.Alice, ht.Bob
p := lntemp.OpenChannelParams{
Amt: channelCapacity,
}
pendingUpdate := ht.OpenChannelAssertPending(alice, bob, p)
// At this point, the channel's funding transaction will have been
// broadcast, but not confirmed. Alice and Bob's nodes
// should reflect this when queried via RPC.
ht.AssertNodesNumPendingOpenChannels(alice, bob, 1)
// We are restarting Bob's node to let the link be created for the
// pending channel.
ht.RestartNode(bob)
// We ensure that Bob reconnects to Alice.
ht.EnsureConnected(bob, alice)
// We mine six blocks for the channel to be confirmed.
ht.MineBlocksAndAssertNumTxes(6, 1)
// We verify that the channel is open from both nodes point of view.
chanPoint := lntemp.ChanPointFromPendingUpdate(pendingUpdate)
ht.AssertNodesNumPendingOpenChannels(alice, bob, 0)
ht.AssertChannelExists(alice, chanPoint)
ht.AssertChannelExists(bob, chanPoint)
// With the channel open, we'll create invoices for Bob that Alice will
// pay to in order to advance the state of the channel.
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
// Send payment to Bob so that a channel update to disk will be
// executed.
ht.CompletePaymentRequests(alice, []string{bobPayReqs[0]})
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
// 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.
ht.CloseChannel(alice, chanPoint)
}
// testAsyncPayments tests the performance of the async payments.
func testAsyncPayments(ht *lntemp.HarnessTest) {
// We use new nodes here as the benchmark test creates lots of data
// which can be costly to be carried on.
alice := ht.NewNode("Alice", []string{"--pending-commit-interval=3m"})
bob := ht.NewNode("Bob", []string{"--pending-commit-interval=3m"})
ht.EnsureConnected(alice, bob)
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
runAsyncPayments(ht, alice, bob)
}
// runAsyncPayments tests the performance of the async payments.
func runAsyncPayments(ht *lntemp.HarnessTest, alice, bob *node.HarnessNode) {
const paymentAmt = 100
// First establish a channel with a capacity equals to the overall
// amount of payments, between Alice and Bob, at the end of the test
// Alice should send all money from her side to Bob.
channelCapacity := btcutil.Amount(paymentAmt * 2000)
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: channelCapacity},
)
info := ht.QueryChannelByChanPoint(alice, chanPoint)
// We'll create a number of invoices equal the max number of HTLCs that
// can be carried in one direction. The number on the commitment will
// likely be lower, but we can't guarantee that any more HTLCs will
// succeed due to the limited path diversity and inability of the router
// to retry via another path.
numInvoices := int(input.MaxHTLCNumber / 2)
bobAmt := int64(numInvoices * paymentAmt)
aliceAmt := info.LocalBalance - bobAmt
// With the channel open, we'll create invoices for Bob that Alice
// will pay to in order to advance the state of the channel.
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices)
// Simultaneously send payments from Alice to Bob using of Bob's
// payment hashes generated above.
now := time.Now()
settled := make(chan struct{})
defer close(settled)
timeout := lntest.AsyncBenchmarkTimeout * 2
for i := 0; i < numInvoices; i++ {
payReq := bobPayReqs[i]
go func() {
req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: int32(timeout.Seconds()),
FeeLimitMsat: noFeeLimitMsat,
}
// AssertPaymentStatusWithTimeout will assert that the
// payment is settled.
stream := alice.RPC.SendPayment(req)
ht.AssertPaymentSucceedWithTimeout(stream, timeout)
settled <- struct{}{}
}()
}
// Wait until all the payments have settled.
timer := time.After(timeout)
for i := 0; i < numInvoices; i++ {
select {
case <-settled:
case <-timer:
require.Fail(ht, "timeout", "wait payment failed")
}
}
// All payments have been sent, mark the finish time.
timeTaken := time.Since(now)
// Wait for the revocation to be received so alice no longer has
// pending htlcs listed and has correct balances. This is needed due to
// the fact that we now pipeline the settles.
assertChannelState(ht, alice, chanPoint, aliceAmt, bobAmt)
// Wait for Bob to receive revocation from Alice.
assertChannelState(ht, bob, chanPoint, bobAmt, aliceAmt)
ht.Log("\tBenchmark info: Elapsed time: ", timeTaken)
ht.Log("\tBenchmark info: TPS: ",
float64(numInvoices)/timeTaken.Seconds())
// 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.
ht.CloseChannel(alice, chanPoint)
}
// testBidirectionalAsyncPayments tests that nodes are able to send the
// payments to each other in async manner without blocking.
func testBidirectionalAsyncPayments(ht *lntemp.HarnessTest) {
const paymentAmt = 1000
// We use new nodes here as the benchmark test creates lots of data
// which can be costly to be carried on.
alice := ht.NewNode("Alice", []string{"--pending-commit-interval=3m"})
bob := ht.NewNode("Bob", []string{"--pending-commit-interval=3m"})
ht.EnsureConnected(alice, bob)
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
// First establish a channel with a capacity equals to the overall
// amount of payments, between Alice and Bob, at the end of the test
// Alice should send all money from her side to Bob.
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: paymentAmt * 2000,
PushAmt: paymentAmt * 1000,
},
)
info := ht.QueryChannelByChanPoint(alice, chanPoint)
// We'll create a number of invoices equal the max number of HTLCs that
// can be carried in one direction. The number on the commitment will
// likely be lower, but we can't guarantee that any more HTLCs will
// succeed due to the limited path diversity and inability of the router
// to retry via another path.
numInvoices := int(input.MaxHTLCNumber / 2)
// Nodes should exchange the same amount of money and because of this
// at the end balances should remain the same.
aliceAmt := info.LocalBalance
bobAmt := info.RemoteBalance
// With the channel open, we'll create invoices for Bob that Alice
// will pay to in order to advance the state of the channel.
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices)
// With the channel open, we'll create invoices for Alice that Bob
// will pay to in order to advance the state of the channel.
alicePayReqs, _, _ := ht.CreatePayReqs(alice, paymentAmt, numInvoices)
// Reset mission control to prevent previous payment results from
// interfering with this test. A new channel has been opened, but
// mission control operates on node pairs.
alice.RPC.ResetMissionControl()
// Send payments from Alice to Bob and from Bob to Alice in async
// manner.
settled := make(chan struct{})
defer close(settled)
timeout := lntest.AsyncBenchmarkTimeout * 4
send := func(node *node.HarnessNode, payReq string) {
req := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: int32(timeout.Seconds()),
FeeLimitMsat: noFeeLimitMsat,
}
// AssertPaymentStatusWithTimeout will assert that the
// payment is settled.
stream := node.RPC.SendPayment(req)
ht.AssertPaymentSucceedWithTimeout(stream, timeout)
settled <- struct{}{}
}
for i := 0; i < numInvoices; i++ {
go send(bob, alicePayReqs[i])
go send(alice, bobPayReqs[i])
}
// Expect all payments to succeed.
timer := time.After(timeout)
for i := 0; i < 2*numInvoices; i++ {
select {
case <-settled:
case <-timer:
require.Fail(ht, "timeout", "wait payment failed")
}
}
// Wait for Alice and Bob to receive revocations messages, and update
// states, i.e. balance info.
assertChannelState(ht, alice, chanPoint, aliceAmt, bobAmt)
// Next query for Bob's and Alice's channel states, in order to confirm
// that all payment have been successful transmitted.
assertChannelState(ht, bob, chanPoint, bobAmt, aliceAmt)
// 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.
ht.CloseChannel(alice, chanPoint)
}
func testInvoiceSubscriptions(ht *lntemp.HarnessTest) {
const chanAmt = btcutil.Amount(500000)
alice, bob := ht.Alice, ht.Bob
// Create a new invoice subscription client for Bob, the notification
// should be dispatched shortly below.
req := &lnrpc.InvoiceSubscription{}
bobInvoiceSubscription := bob.RPC.SubscribeInvoices(req)
// Open a channel with 500k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
chanPoint := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Next create a new invoice for Bob requesting 1k satoshis.
const paymentAmt = 1000
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: ht.Random32Bytes(),
Value: paymentAmt,
}
invoiceResp := bob.RPC.AddInvoice(invoice)
lastAddIndex := invoiceResp.AddIndex
// With the above invoice added, we should receive an update event.
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
require.NotEqual(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
"invoice should not be settled")
// With the assertion above set up, send a payment from Alice to Bob
// which should finalize and settle the invoice.
ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})
// The invoice update should exactly match the invoice created
// above, but should now be settled and have SettleDate
invoiceUpdate = ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
"invoice not settled but should be")
require.NotZero(ht, invoiceUpdate.SettleDate,
"invoice should have non zero settle date, but doesn't")
require.Equal(ht, invoice.RPreimage, invoiceUpdate.RPreimage,
"payment preimages don't match")
require.NotZero(ht, invoiceUpdate.SettleIndex,
"invoice should have settle index")
settleIndex := invoiceUpdate.SettleIndex
// We'll now add 3 more invoices to Bob's invoice registry.
const numInvoices = 3
payReqs, _, newInvoices := ht.CreatePayReqs(
bob, paymentAmt, numInvoices,
)
// Now that the set of invoices has been added, we'll re-register for
// streaming invoice notifications for Bob, this time specifying the
// add invoice of the last prior invoice.
req = &lnrpc.InvoiceSubscription{AddIndex: lastAddIndex}
bobInvoiceSubscription = bob.RPC.SubscribeInvoices(req)
// Since we specified a value of the prior add index above, we should
// now immediately get the invoices we just added as we should get the
// backlog of notifications.
for i := 0; i < numInvoices; i++ {
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
// We should now get the ith invoice we added, as they should
// be returned in order.
require.NotEqual(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
"should have only received add events")
originalInvoice := newInvoices[i]
rHash := sha256.Sum256(originalInvoice.RPreimage)
require.Equal(ht, rHash[:], invoiceUpdate.RHash,
"invoices have mismatched payment hashes")
}
// We'll now have Bob settle out the remainder of these invoices so we
// can test that all settled invoices are properly notified.
ht.CompletePaymentRequests(alice, payReqs)
// With the set of invoices paid, we'll now cancel the old
// subscription, and create a new one for Bob, this time using the
// settle index to obtain the backlog of settled invoices.
req = &lnrpc.InvoiceSubscription{
SettleIndex: settleIndex,
}
bobInvoiceSubscription = bob.RPC.SubscribeInvoices(req)
// As we specified the index of the past settle index, we should now
// receive notifications for the three HTLCs that we just settled. As
// the order that the HTLCs will be settled in is partially randomized,
// we'll use a map to assert that the proper set has been settled.
settledInvoices := make(map[[32]byte]struct{})
for _, invoice := range newInvoices {
rHash := sha256.Sum256(invoice.RPreimage)
settledInvoices[rHash] = struct{}{}
}
for i := 0; i < numInvoices; i++ {
invoiceUpdate := ht.ReceiveInvoiceUpdate(bobInvoiceSubscription)
// We should now get the ith invoice we added, as they should
// be returned in order.
require.Equal(ht, lnrpc.Invoice_SETTLED, invoiceUpdate.State,
"should have only received settle events")
var rHash [32]byte
copy(rHash[:], invoiceUpdate.RHash)
require.Contains(ht, settledInvoices, rHash,
"unknown invoice settled")
delete(settledInvoices, rHash)
}
// At this point, all the invoices should be fully settled.
require.Empty(ht, settledInvoices, "not all invoices settled")
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
ht.CloseChannel(alice, chanPoint)
}
// assertChannelState asserts the channel state by checking the values in
// fields, LocalBalance, RemoteBalance and num of PendingHtlcs.
func assertChannelState(ht *lntemp.HarnessTest, hn *node.HarnessNode,
cp *lnrpc.ChannelPoint, localBalance, remoteBalance int64) {
// Get the funding point.
err := wait.NoError(func() error {
// Find the target channel first.
target := ht.GetChannelByChanPoint(hn, cp)
if len(target.PendingHtlcs) != 0 {
return fmt.Errorf("pending htlcs is "+
"incorrect, got %v, expected %v",
len(target.PendingHtlcs), 0)
}
if target.LocalBalance != localBalance {
return fmt.Errorf("local balance is "+
"incorrect, got %v, expected %v",
target.LocalBalance, localBalance)
}
if target.RemoteBalance != remoteBalance {
return fmt.Errorf("remote balance is "+
"incorrect, got %v, expected %v",
target.RemoteBalance, remoteBalance)
}
return nil
}, lntemp.DefaultTimeout)
require.NoError(ht, err, "timeout while chekcing for balance")
}
File diff suppressed because it is too large. Load diff
-443
View File
@@ -1,443 +0,0 @@
package itest
import (
"bytes"
"fmt"
"math"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/aezeed"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/stretchr/testify/require"
)
// testGetRecoveryInfo checks whether lnd gives the right information about
// the wallet recovery process.
func testGetRecoveryInfo(ht *lntemp.HarnessTest) {
// First, create a new node with strong passphrase and grab the mnemonic
// used for key derivation. This will bring up Carol with an empty
// wallet, and such that she is synced up.
password := []byte("The Magic Words are Squeamish Ossifrage")
carol, mnemonic, _ := ht.NewNodeWithSeed("Carol", nil, password, false)
checkInfo := func(expectedRecoveryMode, expectedRecoveryFinished bool,
expectedProgress float64, recoveryWindow int32) {
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node := ht.RestoreNodeWithSeed(
carol.Name(), nil, password, mnemonic, "",
recoveryWindow, nil,
)
// Query carol for her current wallet recovery progress.
err := wait.NoError(func() error {
// Verify that recovery info gives the right response.
resp := node.RPC.GetRecoveryInfo(nil)
mode := resp.RecoveryMode
finished := resp.RecoveryFinished
progress := resp.Progress
if mode != expectedRecoveryMode {
return fmt.Errorf("expected recovery mode %v "+
"got %v", expectedRecoveryMode, mode)
}
if finished != expectedRecoveryFinished {
return fmt.Errorf("expected finished %v "+
"got %v", expectedRecoveryFinished,
finished)
}
if progress != expectedProgress {
return fmt.Errorf("expected progress %v"+
"got %v", expectedProgress, progress)
}
return nil
}, defaultTimeout)
require.NoError(ht, err)
// Lastly, shutdown this Carol so we can move on to the next
// restoration.
ht.Shutdown(node)
}
// Restore Carol with a recovery window of 0. Since it's not in recovery
// mode, the recovery info will give a response with recoveryMode=false,
// recoveryFinished=false, and progress=0
checkInfo(false, false, 0, 0)
// Change the recovery windown to be 1 to turn on recovery mode. Since
// the current chain height is the same as the birthday height, it
// should indicate the recovery process is finished.
checkInfo(true, true, 1, 1)
// We now go ahead 5 blocks. Because the wallet's syncing process is
// controlled by a goroutine in the background, it will catch up
// quickly. This makes the recovery progress back to 1.
ht.MineBlocks(5)
checkInfo(true, true, 1, 1)
}
// testOnchainFundRecovery checks lnd's ability to rescan for onchain outputs
// when providing a valid aezeed that owns outputs on the chain. This test
// performs multiple restorations using the same seed and various recovery
// windows to ensure we detect funds properly.
func testOnchainFundRecovery(ht *lntemp.HarnessTest) {
// First, create a new node with strong passphrase and grab the mnemonic
// used for key derivation. This will bring up Carol with an empty
// wallet, and such that she is synced up.
password := []byte("The Magic Words are Squeamish Ossifrage")
carol, mnemonic, _ := ht.NewNodeWithSeed("Carol", nil, password, false)
// As long as the mnemonic is non-nil and the extended key is empty, the
// closure below will always restore the node from the seed. The tests
// need to manually overwrite this value to change that behavior.
rootKey := ""
// Create a closure for testing the recovery of Carol's wallet. This
// method takes the expected value of Carol's balance when using the
// given recovery window. Additionally, the caller can specify an action
// to perform on the restored node before the node is shutdown.
restoreCheckBalance := func(expAmount int64, expectedNumUTXOs uint32,
recoveryWindow int32, fn func(*node.HarnessNode)) {
ht.Helper()
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node := ht.RestoreNodeWithSeed(
carol.Name(), nil, password, mnemonic, rootKey,
recoveryWindow, nil,
)
// Query carol for her current wallet balance, and also that we
// gain the expected number of UTXOs.
var (
currBalance int64
currNumUTXOs uint32
)
err := wait.NoError(func() error {
resp := node.RPC.WalletBalance()
currBalance = resp.ConfirmedBalance
req := &walletrpc.ListUnspentRequest{
Account: "",
MaxConfs: math.MaxInt32,
MinConfs: 0,
}
utxoResp := node.RPC.ListUnspent(req)
currNumUTXOs = uint32(len(utxoResp.Utxos))
// Verify that Carol's balance and number of UTXOs
// matches what's expected.
if expAmount != currBalance {
return fmt.Errorf("balance not matched, want "+
"%d, got %d", expAmount, currBalance)
}
if currNumUTXOs != expectedNumUTXOs {
return fmt.Errorf("num of UTXOs not matched, "+
"want %d, got %d", expectedNumUTXOs,
currNumUTXOs)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking Carol")
// If the user provided a callback, execute the commands against
// the restored Carol.
if fn != nil {
fn(node)
}
// Lastly, shutdown this Carol so we can move on to the next
// restoration.
ht.Shutdown(node)
}
// Create a closure-factory for building closures that can generate and
// skip a configurable number of addresses, before finally sending coins
// to a next generated address. The returned closure will apply the same
// behavior to both default P2WKH and NP2WKH scopes.
skipAndSend := func(nskip int) func(*node.HarnessNode) {
return func(node *node.HarnessNode) {
ht.Helper()
// Generate and skip the number of addresses requested.
for i := 0; i < nskip; i++ {
req := &lnrpc.NewAddressRequest{}
req.Type = AddrTypeWitnessPubkeyHash
node.RPC.NewAddress(req)
req.Type = AddrTypeNestedPubkeyHash
node.RPC.NewAddress(req)
req.Type = AddrTypeTaprootPubkey
node.RPC.NewAddress(req)
}
// Send one BTC to the next P2WKH address.
ht.FundCoins(btcutil.SatoshiPerBitcoin, node)
// And another to the next NP2WKH address.
ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, node)
// Add another whole coin to the P2TR address.
ht.FundCoinsP2TR(btcutil.SatoshiPerBitcoin, node)
}
}
// Restore Carol with a recovery window of 0. Since no coins have been
// sent, her balance should be zero.
//
// After, one BTC is sent to both her first external P2WKH and NP2WKH
// addresses.
restoreCheckBalance(0, 0, 0, skipAndSend(0))
// Check that restoring without a look-ahead results in having no funds
// in the wallet, even though they exist on-chain.
restoreCheckBalance(0, 0, 0, nil)
// Now, check that using a look-ahead of 1 recovers the balance from
// the two transactions above. We should also now have 2 UTXOs in the
// wallet at the end of the recovery attempt.
//
// After, we will generate and skip 9 P2WKH, NP2WKH and P2TR addresses,
// and send another BTC to the subsequent 10th address in each
// derivation path.
restoreCheckBalance(3*btcutil.SatoshiPerBitcoin, 3, 1, skipAndSend(9))
// Check that using a recovery window of 9 does not find the two most
// recent txns.
restoreCheckBalance(3*btcutil.SatoshiPerBitcoin, 3, 9, nil)
// Extending our recovery window to 10 should find the most recent
// transactions, leaving the wallet with 6 BTC total. We should also
// learn of the two additional UTXOs created above.
//
// After, we will skip 19 more addrs, sending to the 20th address past
// our last found address, and repeat the same checks.
restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 10, skipAndSend(19))
// Check that recovering with a recovery window of 19 fails to find the
// most recent transactions.
restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 19, nil)
// Ensure that using a recovery window of 20 succeeds with all UTXOs
// found and the final balance reflected.
// After these checks are done, we'll want to make sure we can also
// recover change address outputs. This is mainly motivated by a now
// fixed bug in the wallet in which change addresses could at times be
// created outside of the default key scopes. Recovery only used to be
// performed on the default key scopes, so ideally this test case
// would've caught the bug earlier. Carol has received 9 BTC so far from
// the miner, we'll send 8 back to ensure all of her UTXOs get spent to
// avoid fee discrepancies and a change output is formed.
const minerAmt = 8 * btcutil.SatoshiPerBitcoin
const finalBalance = 9 * btcutil.SatoshiPerBitcoin
promptChangeAddr := func(node *node.HarnessNode) {
ht.Helper()
minerAddr := ht.Miner.NewMinerAddress()
req := &lnrpc.SendCoinsRequest{
Addr: minerAddr.String(),
Amount: minerAmt,
}
resp := node.RPC.SendCoins(req)
txid := ht.Miner.AssertNumTxsInMempool(1)[0]
require.Equal(ht, txid.String(), resp.Txid)
block := ht.MineBlocks(1)[0]
ht.Miner.AssertTxInBlock(block, txid)
}
restoreCheckBalance(finalBalance, 9, 20, promptChangeAddr)
// We should expect a static fee of 36400 satoshis for spending 9
// inputs (3 P2WPKH, 3 NP2WPKH, 3 P2TR) to two P2TR outputs. Carol
// should therefore only have one UTXO present (the change output) of
// 9 - 8 - fee BTC.
const fee = 37000
restoreCheckBalance(finalBalance-minerAmt-fee, 1, 21, nil)
// Last of all, make sure we can also restore a node from the extended
// master root key directly instead of the seed.
var seedMnemonic aezeed.Mnemonic
copy(seedMnemonic[:], mnemonic)
cipherSeed, err := seedMnemonic.ToCipherSeed(password)
require.NoError(ht, err)
extendedRootKey, err := hdkeychain.NewMaster(
cipherSeed.Entropy[:], harnessNetParams,
)
require.NoError(ht, err)
rootKey = extendedRootKey.String()
mnemonic = nil
restoreCheckBalance(finalBalance-minerAmt-fee, 1, 21, nil)
}
// testRescanAddressDetection makes sure that addresses created from internal
// (m/1017' scope) keys aren't detected as UTXOs when re-scanning the wallet
// with --reset-wallet-transactions to avoid showing them as un-spent ghost
// UTXOs even if they are being spent. This is to test a fix in the wallet that
// addresses the following scenario:
// 1. A key is derived from the internal 1017' scope with a custom key family
// and a p2wkh address is derived from that key.
// 2. Funds are sent to the address created above in a way that also creates a
// change output. The change output is recognized as belonging to the
// wallet, which is correct.
// 3. The funds on the address created in step 1 are fully spent (without
// creating a change output) into an output that doesn't belong to the
// wallet (e.g. a channel funding output).
// 4. At some point the user re-scans their wallet by using the
// --reset-wallet-transactions flag.
// 5. The wallet re-scan detects the change output created in step 2 and flags
// the transaction as relevant.
// 6. While adding the relevant TX to the wallet DB, the wallet also detects
// the address from step 1 as belonging to the wallet (because the internal
// key scope is defined as having the address type p2wkh) and adds that
// output as an UTXO as well (<- this is the bug). The wallet now has two
// UTXOs in its database.
// 7. The transaction that spends the UTXO of the address from step 1 is not
// detected by the wallet as belonging to it (because the output is a
// channel output and the input (correctly) isn't recognized as belonging to
// the wallet in that part of the code, it is never marked as spent and
// stays in the wallet as a ghost UTXO forever.
//
// The fix in the wallet is simple: In step 6, don't detect addresses from
// internal scopes while re-scanning to be in line with the logic in other areas
// of the wallet code.
func testRescanAddressDetection(ht *lntemp.HarnessTest) {
// We start off by creating a new node with the wallet re-scan flag
// enabled. This won't have any effect on the first startup but will
// come into effect after we re-start the node.
walletPassword := []byte("some-password")
carol, _, _ := ht.NewNodeWithSeed(
"carol", []string{"--reset-wallet-transactions"},
walletPassword, false,
)
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Create an address generated from internal keys.
keyDesc := carol.RPC.DeriveNextKey(&walletrpc.KeyReq{KeyFamily: 123})
pubKeyHash := btcutil.Hash160(keyDesc.RawKeyBytes)
ghostUtxoAddr, err := btcutil.NewAddressWitnessPubKeyHash(
pubKeyHash, harnessNetParams,
)
require.NoError(ht, err)
// Send funds to the (p2wkh!) address generated from the internal
// (m/1017') key scope. Because the internal key scope is defined as
// p2wkh address type, this might be incorrectly detected by the wallet
// in some situations (which this test makes sure is fixed).
const ghostUtxoAmount = 456_000
carol.RPC.SendCoins(&lnrpc.SendCoinsRequest{
Addr: ghostUtxoAddr.String(),
Amount: ghostUtxoAmount,
SatPerVbyte: 1,
})
ht.MineBlocksAndAssertNumTxes(1, 1)
// Make sure we see the change output in our list of unspent outputs.
// We _don't_ expect to see the ghost UTXO here as in this step it's
// ignored as an internal address correctly.
ht.AssertNumUTXOsConfirmed(carol, 1)
unspent := carol.RPC.ListUnspent(&walletrpc.ListUnspentRequest{
MinConfs: 1,
})
// Which one was the change output and which one the ghost UTXO output?
var ghostUtxoIndex uint32
if unspent.Utxos[0].Outpoint.OutputIndex == 0 {
ghostUtxoIndex = 1
}
ghostUtxoHash, err := chainhash.NewHash(
unspent.Utxos[0].Outpoint.TxidBytes,
)
require.NoError(ht, err)
burnScript, _ := ht.CreateBurnAddr(AddrTypeWitnessPubkeyHash)
// Create fee estimation for a p2wkh input and p2wkh output.
feeRate := chainfee.SatPerKWeight(12500)
estimator := input.TxWeightEstimator{}
estimator.AddP2WKHInput()
estimator.AddP2WKHOutput()
estimatedWeight := int64(estimator.Weight())
requiredFee := feeRate.FeeForWeight(estimatedWeight)
tx := wire.NewMsgTx(2)
tx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: *ghostUtxoHash,
Index: ghostUtxoIndex,
},
}}
value := int64(ghostUtxoAmount - requiredFee)
tx.TxOut = []*wire.TxOut{{
PkScript: burnScript,
Value: value,
}}
var buf bytes.Buffer
require.NoError(ht, tx.Serialize(&buf))
ghostUtxoScript := ht.PayToAddrScript(ghostUtxoAddr)
utxoInfo := []*signrpc.TxOut{{
PkScript: ghostUtxoScript,
Value: ghostUtxoAmount,
}}
// Let's sign the input now.
signResp := carol.RPC.SignOutputRaw(&signrpc.SignReq{
RawTxBytes: buf.Bytes(),
SignDescs: []*signrpc.SignDescriptor{{
Output: utxoInfo[0],
InputIndex: 0,
KeyDesc: keyDesc,
Sighash: uint32(txscript.SigHashAll),
WitnessScript: utxoInfo[0].PkScript,
}},
})
// Add the witness to the input and publish the tx.
tx.TxIn[0].Witness = wire.TxWitness{
append(signResp.RawSigs[0], byte(txscript.SigHashAll)),
keyDesc.RawKeyBytes,
}
buf.Reset()
require.NoError(ht, tx.Serialize(&buf))
carol.RPC.PublishTransaction(&walletrpc.Transaction{
TxHex: buf.Bytes(),
})
// Wait until the spending tx is found and mine a block to confirm it.
ht.Miner.AssertNumTxsInMempool(1)
ht.MineBlocks(1)
// The wallet should still just see a single UTXO of the change output
// created earlier.
ht.AssertNumUTXOsConfirmed(carol, 1)
// Let's now re-start the node, causing it to do the wallet re-scan.
ht.RestartNode(carol)
// There should now still only be a single UTXO from the change output
// instead of two (the ghost UTXO should be missing if the fix works).
ht.AssertNumUTXOsConfirmed(carol, 1)
}
-308
View File
@@ -1,308 +0,0 @@
package itest
import (
"fmt"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/stretchr/testify/require"
)
var (
rootKey = "tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3" +
"PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg"
nodePubKey = "033f55d436d4f7d24aeffb1b976647380f22ebf9e74390e8c76dcff" +
"9fea0093b7a"
accounts = []*lnrpc.WatchOnlyAccount{{
Purpose: waddrmgr.KeyScopeBIP0049Plus.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDXEYWvGCTytEF6hBog9p4qr2QBUvJhh4P2wM4qHHv9N489khk" +
"QoGkBXDVoquuiyBf8SKBwrYseYdtq9j2v2nttPpE8qbuW3sE2MCk" +
"FPhTq",
}, {
Purpose: waddrmgr.KeyScopeBIP0084.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDWAWrSLRSFrG1KdqXMQQyTKYGSKLKaY7gxpvK7RdV3e3Dkhvu" +
"W2GgsFvsPN4RGmuoYtUgZ1LHZE8oftz7T4mzc1BxGt5rt8zJcVQi" +
"KTPPV",
}, {
Purpose: waddrmgr.KeyScopeBIP0086.Purpose,
// We always use the mainnet coin type for our BIP49/84/86
// addresses!
CoinType: 0,
Account: 0,
Xpub: "tpubDDtdXpdJFU2zFKWHJwe5M2WtYtcV7qSWtKohT9VP9zarNSwKnm" +
"kwDQawsu1vUf9xwXhUDYXbdUqpcrRTn9bLyW4BAVRimZ4K7r5o1J" +
"S924u",
}}
)
// testRemoteSigner tests that a watch-only wallet can use a remote signing
// wallet to perform any signing or ECDH operations.
func testRemoteSigner(ht *lntemp.HarnessTest) {
type testCase struct {
name string
randomSeed bool
sendCoins bool
fn func(tt *lntemp.HarnessTest,
wo, carol *node.HarnessNode)
}
subTests := []testCase{{
name: "random seed",
randomSeed: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
// Nothing more to test here.
},
}, {
name: "account import",
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runWalletImportAccountScenario(
tt, walletrpc.AddressType_WITNESS_PUBKEY_HASH,
carol, wo,
)
},
}, {
name: "basic channel open close",
sendCoins: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runBasicChannelCreationAndUpdates(tt, wo, carol)
},
}, {
name: "channel funding input types",
sendCoins: false,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runChannelFundingInputTypes(tt, carol, wo)
},
}, {
name: "async payments",
sendCoins: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runAsyncPayments(tt, wo, carol)
},
}, {
name: "shared key",
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runDeriveSharedKey(tt, wo)
},
}, {
name: "cpfp",
sendCoins: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runCPFP(tt, wo, carol)
},
}, {
name: "psbt",
randomSeed: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runPsbtChanFunding(tt, carol, wo)
runSignPsbtSegWitV0P2WKH(tt, wo)
runSignPsbtSegWitV1KeySpendBip86(tt, wo)
runSignPsbtSegWitV1KeySpendRootHash(tt, wo)
runSignPsbtSegWitV1ScriptSpend(tt, wo)
// The above tests all make sure we can sign for keys
// that aren't in the wallet. But we also want to make
// sure we can fund and then sign PSBTs from our wallet.
runFundAndSignPsbt(ht, wo)
},
}, {
name: "sign output raw",
sendCoins: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runSignOutputRaw(tt, wo)
},
}, {
name: "sign verify msg",
sendCoins: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
runSignVerifyMessage(tt, wo)
},
}, {
name: "taproot",
sendCoins: true,
randomSeed: true,
fn: func(tt *lntemp.HarnessTest, wo, carol *node.HarnessNode) {
testTaprootSendCoinsKeySpendBip86(tt, wo)
testTaprootComputeInputScriptKeySpendBip86(tt, wo)
testTaprootSignOutputRawScriptSpend(tt, wo)
testTaprootSignOutputRawKeySpendBip86(tt, wo)
testTaprootSignOutputRawKeySpendRootHash(tt, wo)
muSig2Versions := []signrpc.MuSig2Version{
signrpc.MuSig2Version_MUSIG2_VERSION_V040,
signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
}
for _, version := range muSig2Versions {
testTaprootMuSig2KeySpendRootHash(
tt, wo, version,
)
testTaprootMuSig2ScriptSpend(tt, wo, version)
testTaprootMuSig2KeySpendBip86(tt, wo, version)
testTaprootMuSig2CombinedLeafKeySpend(
tt, wo, version,
)
}
},
}}
prepareTest := func(st *lntemp.HarnessTest,
subTest testCase) (*node.HarnessNode,
*node.HarnessNode, *node.HarnessNode) {
// Signer is our signing node and has the wallet with the full
// master private key. We test that we can create the watch-only
// wallet from the exported accounts but also from a static key
// to make sure the derivation of the account public keys is
// correct in both cases.
password := []byte("itestpassword")
var (
signerNodePubKey = nodePubKey
watchOnlyAccounts = deriveCustomScopeAccounts(ht.T)
signer *node.HarnessNode
err error
)
if !subTest.randomSeed {
signer = st.RestoreNodeWithSeed(
"Signer", nil, password, nil, rootKey, 0, nil,
)
} else {
signer = st.NewNode("Signer", nil)
signerNodePubKey = signer.PubKeyStr
rpcAccts := signer.RPC.ListAccounts(
&walletrpc.ListAccountsRequest{},
)
watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly(
rpcAccts.Accounts,
)
require.NoError(st, err)
}
// WatchOnly is the node that has a watch-only wallet and uses
// the Signer node for any operation that requires access to
// private keys.
watchOnly := st.NewNodeRemoteSigner(
"WatchOnly", []string{
"--remotesigner.enable",
fmt.Sprintf(
"--remotesigner.rpchost=localhost:%d",
signer.Cfg.RPCPort,
),
fmt.Sprintf(
"--remotesigner.tlscertpath=%s",
signer.Cfg.TLSCertPath,
),
fmt.Sprintf(
"--remotesigner.macaroonpath=%s",
signer.Cfg.AdminMacPath,
),
}, password, &lnrpc.WatchOnly{
MasterKeyBirthdayTimestamp: 0,
MasterKeyFingerprint: nil,
Accounts: watchOnlyAccounts,
},
)
resp := watchOnly.RPC.GetInfo()
require.Equal(st, signerNodePubKey, resp.IdentityPubkey)
if subTest.sendCoins {
st.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly)
ht.AssertWalletAccountBalance(
watchOnly, "default",
btcutil.SatoshiPerBitcoin, 0,
)
}
carol := st.NewNode("carol", nil)
st.EnsureConnected(watchOnly, carol)
return signer, watchOnly, carol
}
for _, testCase := range subTests {
subTest := testCase
success := ht.Run(subTest.name, func(tt *testing.T) {
// Skip the cleanup here as no standby node is used.
st := ht.Subtest(tt)
_, watchOnly, carol := prepareTest(st, subTest)
subTest.fn(st, watchOnly, carol)
})
if !success {
return
}
}
}
// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd
// internal key scope.
func deriveCustomScopeAccounts(t *testing.T) []*lnrpc.WatchOnlyAccount {
allAccounts := make([]*lnrpc.WatchOnlyAccount, 0, 255+len(accounts))
allAccounts = append(allAccounts, accounts...)
extendedRootKey, err := hdkeychain.NewKeyFromString(rootKey)
require.NoError(t, err)
path := []uint32{
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
harnessNetParams.HDCoinType + hdkeychain.HardenedKeyStart,
}
coinTypeKey, err := derivePath(extendedRootKey, path)
require.NoError(t, err)
for idx := uint32(0); idx <= 255; idx++ {
accountPath := []uint32{idx + hdkeychain.HardenedKeyStart}
accountKey, err := derivePath(coinTypeKey, accountPath)
require.NoError(t, err)
accountXPub, err := accountKey.Neuter()
require.NoError(t, err)
allAccounts = append(allAccounts, &lnrpc.WatchOnlyAccount{
Purpose: keychain.BIP0043Purpose,
CoinType: harnessNetParams.HDCoinType,
Account: idx,
Xpub: accountXPub.String(),
})
}
return allAccounts
}
// derivePath derives the given path from an extended key.
func derivePath(key *hdkeychain.ExtendedKey, path []uint32) (
*hdkeychain.ExtendedKey, error) {
var (
currentKey = key
err error
)
for _, pathPart := range path {
currentKey, err = currentKey.Derive(pathPart)
if err != nil {
return nil, err
}
}
return currentKey, nil
}
-99
View File
@@ -1,99 +0,0 @@
package itest
import (
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/stretchr/testify/require"
)
// testResHandoff tests that the contractcourt is able to properly hand-off
// resolution messages to the switch.
func testResHandoff(ht *lntemp.HarnessTest) {
const (
chanAmt = btcutil.Amount(1000000)
paymentAmt = 50000
)
alice, bob := ht.Alice, ht.Bob
// First we'll create a channel between Alice and Bob.
ht.EnsureConnected(alice, bob)
params := lntemp.OpenChannelParams{Amt: chanAmt}
chanPointAlice := ht.OpenChannel(alice, bob, params)
// Create a new node Carol that will be in hodl mode. This is used to
// trigger the behavior of checkRemoteDanglingActions in the
// contractcourt. This will cause Bob to fail the HTLC back to Alice.
carol := ht.NewNode("Carol", []string{"--hodl.commit"})
defer ht.Shutdown(carol)
// Connect Bob to Carol.
ht.ConnectNodes(bob, carol)
// Open a channel between Bob and Carol.
chanPointCarol := ht.OpenChannel(bob, carol, params)
// Wait for Alice to see the channel edge in the graph.
ht.AssertTopologyChannelOpen(alice, chanPointCarol)
// We'll create an invoice for Carol that Alice will attempt to pay.
// Since Carol is in hodl.commit mode, she won't send back any commit
// sigs.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, 1)
// Alice will now attempt to fulfill the invoice.
ht.CompletePaymentRequestsNoWait(alice, carolPayReqs, chanPointAlice)
// Wait until Carol has received the Add, CommitSig from Bob, and has
// responded with a RevokeAndAck. We expect NumUpdates to be 1 meaning
// Carol's CommitHeight is 1.
ht.AssertChannelCommitHeight(carol, chanPointCarol, 1)
// Before we shutdown Alice, we'll assert that she only has 1 update.
ht.AssertChannelCommitHeight(alice, chanPointAlice, 1)
// We'll shutdown Alice so that Bob can't connect to her.
restartAlice := ht.SuspendNode(alice)
// Bob will now force close his channel with Carol such that resolution
// messages are created and forwarded backwards to Alice.
ht.CloseChannelAssertPending(bob, chanPointCarol, true)
// The channel should be listed in the PendingChannels result.
ht.AssertNumWaitingClose(bob, 1)
// Mine a block to confirm the closing tx.
ht.MineBlocks(1)
// We sleep here so we can be sure that the hand-off has occurred from
// Bob's contractcourt to Bob's htlcswitch. This sleep could be removed
// if there was some feedback (i.e. API in switch) that allowed for
// querying the state of resolution messages.
time.Sleep(10 * time.Second)
// Mine blocks until Bob has no waiting close channels. This tests that
// the circuit-deletion logic is skipped if a resolution message
// exists.
ht.CleanupForceClose(bob, chanPointCarol)
// We will now restart Bob so that we can test whether the resolution
// messages are re-forwarded on start-up.
ht.RestartNode(bob)
// We'll now also restart Alice and connect her with Bob.
require.NoError(ht, restartAlice())
ht.EnsureConnected(alice, bob)
// We'll assert that Alice has received the failure resolution message.
ht.AssertChannelCommitHeight(alice, chanPointAlice, 2)
// Assert that Alice's payment failed.
ht.AssertFirstHTLCError(alice, lnrpc.Failure_PERMANENT_CHANNEL_FAILURE)
ht.CloseChannel(alice, chanPointAlice)
}
-796
View File
@@ -1,796 +0,0 @@
package itest
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"strings"
"testing"
"time"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/gorilla/websocket"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/stretchr/testify/require"
)
var (
insecureTransport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
restClient = &http.Client{
Transport: insecureTransport,
}
jsonMarshaler = &jsonpb.Marshaler{
EmitDefaults: true,
OrigName: true,
Indent: " ",
}
urlEnc = base64.URLEncoding
webSocketDialer = &websocket.Dialer{
HandshakeTimeout: time.Second,
TLSClientConfig: insecureTransport.TLSClientConfig,
}
resultPattern = regexp.MustCompile("{\"result\":(.*)}")
closeMsg = websocket.FormatCloseMessage(
websocket.CloseNormalClosure, "done",
)
pingInterval = time.Millisecond * 200
pongWait = time.Millisecond * 50
)
// testRestAPI tests that the most important features of the REST API work
// correctly.
func testRestAPI(ht *lntemp.HarnessTest) {
testCases := []struct {
name string
run func(*testing.T, *node.HarnessNode, *node.HarnessNode)
}{{
name: "simple GET",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
// Check that the parsing into the response proto
// message works.
resp := &lnrpc.GetInfoResponse{}
err := invokeGET(a, "/v1/getinfo", resp)
require.Nil(t, err, "getinfo")
require.Equal(t, "#3399ff", resp.Color, "node color")
// Make sure we get the correct field names (snake
// case).
_, resp2, err := makeRequest(
a, "/v1/getinfo", "GET", nil, nil,
)
require.Nil(t, err, "getinfo")
require.Contains(
t, string(resp2), "best_header_timestamp",
"getinfo",
)
},
}, {
name: "simple POST and GET with query param",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
// Add an invoice, testing POST in the process.
req := &lnrpc.Invoice{Value: 1234}
resp := &lnrpc.AddInvoiceResponse{}
err := invokePOST(a, "/v1/invoices", req, resp)
require.Nil(t, err, "add invoice")
require.Equal(t, 32, len(resp.RHash), "invoice rhash")
// Make sure we can call a GET endpoint with a hex
// encoded URL part.
url := fmt.Sprintf("/v1/invoice/%x", resp.RHash)
resp2 := &lnrpc.Invoice{}
err = invokeGET(a, url, resp2)
require.Nil(t, err, "query invoice")
require.Equal(t, int64(1234), resp2.Value,
"invoice amt")
},
}, {
name: "GET with base64 encoded byte slice in path",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
url := "/v2/router/mc/probability/%s/%s/%d"
url = fmt.Sprintf(
url, urlEnc.EncodeToString(a.PubKey[:]),
urlEnc.EncodeToString(b.PubKey[:]), 1234,
)
resp := &routerrpc.QueryProbabilityResponse{}
err := invokeGET(a, url, resp)
require.Nil(t, err, "query probability")
require.Zero(t, resp.Probability)
},
}, {
name: "GET with map type query param",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
// Use a fake address.
addr := "bcrt1qlutnwklt4u2548cufrjmsjclewugr9lcpnkzag"
// Create the full URL with the map query param.
url := "/v1/transactions/fee?target_conf=%d&" +
"AddrToAmount[%s]=%d"
url = fmt.Sprintf(url, 2, addr, 50000)
resp := &lnrpc.EstimateFeeResponse{}
err := invokeGET(a, url, resp)
require.Nil(t, err, "estimate fee")
require.Greater(t, resp.FeeSat, int64(253), "fee")
},
}, {
name: "sub RPC servers REST support",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
// Query autopilot status.
res1 := &autopilotrpc.StatusResponse{}
err := invokeGET(a, "/v2/autopilot/status", res1)
require.Nil(t, err, "autopilot status")
require.Equal(t, false, res1.Active, "autopilot status")
// Query the version RPC.
res2 := &verrpc.Version{}
err = invokeGET(a, "/v2/versioner/version", res2)
require.Nil(t, err, "version")
require.Greater(
t, res2.AppMinor, uint32(0), "lnd minor version",
)
// Request a new external address from the wallet kit.
req1 := &walletrpc.AddrRequest{}
res3 := &walletrpc.AddrResponse{}
err = invokePOST(
a, "/v2/wallet/address/next", req1, res3,
)
require.Nil(t, err, "address")
require.NotEmpty(t, res3.Addr, "address")
},
}, {
name: "CORS headers",
run: func(t *testing.T, a, b *node.HarnessNode) {
t.Helper()
// Alice allows all origins. Make sure we get the same
// value back in the CORS header that we send in the
// Origin header.
reqHeaders := make(http.Header)
reqHeaders.Add("Origin", "https://foo.bar:9999")
resHeaders, body, err := makeRequest(
a, "/v1/getinfo", "OPTIONS", nil, reqHeaders,
)
require.Nil(t, err, "getinfo")
require.Equal(
t, "https://foo.bar:9999",
resHeaders.Get("Access-Control-Allow-Origin"),
"CORS header",
)
require.Equal(t, 0, len(body))
// Make sure that we don't get a value set for Bob which
// doesn't allow any CORS origin.
resHeaders, body, err = makeRequest(
b, "/v1/getinfo", "OPTIONS", nil, reqHeaders,
)
require.Nil(t, err, "getinfo")
require.Equal(
t, "",
resHeaders.Get("Access-Control-Allow-Origin"),
"CORS header",
)
require.Equal(t, 0, len(body))
},
}}
wsTestCases := []struct {
name string
run func(ht *lntemp.HarnessTest)
}{{
name: "websocket subscription",
run: wsTestCaseSubscription,
}, {
name: "websocket subscription with macaroon in protocol",
run: wsTestCaseSubscriptionMacaroon,
}, {
name: "websocket bi-directional subscription",
run: wsTestCaseBiDirectionalSubscription,
}, {
name: "websocket ping and pong timeout",
run: wsTestPingPongTimeout,
}}
// Make sure Alice allows all CORS origins. Bob will keep the default.
// We also make sure the ping/pong messages are sent very often, so we
// can test them without waiting half a minute.
alice, bob := ht.Alice, ht.Bob
alice.Cfg.ExtraArgs = append(
alice.Cfg.ExtraArgs, "--restcors=\"*\"",
fmt.Sprintf("--ws-ping-interval=%s", pingInterval),
fmt.Sprintf("--ws-pong-wait=%s", pongWait),
)
ht.RestartNode(alice)
for _, tc := range testCases {
tc := tc
ht.Run(tc.name, func(t *testing.T) {
tc.run(t, alice, bob)
})
}
for _, tc := range wsTestCases {
tc := tc
ht.Run(tc.name, func(t *testing.T) {
st := ht.Subtest(t)
tc.run(st)
})
}
}
func wsTestCaseSubscription(ht *lntemp.HarnessTest) {
// Find out the current best block so we can subscribe to the next one.
hash, height := ht.Miner.GetBestBlock()
// Create a new subscription to get block epoch events.
req := &chainrpc.BlockEpoch{
Hash: hash.CloneBytes(),
Height: uint32(height),
}
url := "/v2/chainnotifier/register/blocks"
c, err := openWebSocket(ht.Alice, url, "POST", req, nil)
require.NoError(ht, err, "websocket")
defer func() {
err := c.WriteMessage(websocket.CloseMessage, closeMsg)
require.NoError(ht, err)
_ = c.Close()
}()
msgChan := make(chan *chainrpc.BlockEpoch)
errChan := make(chan error)
timeout := time.After(defaultTimeout)
// We want to read exactly one message.
go func() {
defer close(msgChan)
_, msg, err := c.ReadMessage()
if err != nil {
errChan <- err
return
}
// The chunked/streamed responses come wrapped in either a
// {"result":{}} or {"error":{}} wrapper which we'll get rid of
// here.
msgStr := string(msg)
if !strings.Contains(msgStr, "\"result\":") {
errChan <- fmt.Errorf("invalid msg: %s", msgStr)
return
}
msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
// Make sure we can parse the unwrapped message into the
// expected proto message.
protoMsg := &chainrpc.BlockEpoch{}
err = jsonpb.UnmarshalString(msgStr, protoMsg)
if err != nil {
errChan <- err
return
}
select {
case msgChan <- protoMsg:
case <-timeout:
}
}()
// Mine a block and make sure we get a message for it.
blockHashes := ht.Miner.GenerateBlocks(1)
select {
case msg := <-msgChan:
require.Equal(
ht, blockHashes[0].CloneBytes(), msg.Hash,
"block hash",
)
case err := <-errChan:
ht.Fatalf("Received error from WS: %v", err)
case <-timeout:
ht.Fatalf("Timeout before message was received")
}
}
func wsTestCaseSubscriptionMacaroon(ht *lntemp.HarnessTest) {
// Find out the current best block so we can subscribe to the next one.
hash, height := ht.Miner.GetBestBlock()
// Create a new subscription to get block epoch events.
req := &chainrpc.BlockEpoch{
Hash: hash.CloneBytes(),
Height: uint32(height),
}
url := "/v2/chainnotifier/register/blocks"
// This time we send the macaroon in the special header
// Sec-Websocket-Protocol which is the only header field available to
// browsers when opening a WebSocket.
alice := ht.Alice
mac, err := alice.ReadMacaroon(
alice.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(ht, err, "read admin mac")
macBytes, err := mac.MarshalBinary()
require.NoError(ht, err, "marshal admin mac")
customHeader := make(http.Header)
customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
))
c, err := openWebSocket(alice, url, "POST", req, customHeader)
require.Nil(ht, err, "websocket")
defer func() {
err := c.WriteMessage(websocket.CloseMessage, closeMsg)
require.NoError(ht, err)
_ = c.Close()
}()
msgChan := make(chan *chainrpc.BlockEpoch)
errChan := make(chan error)
timeout := time.After(defaultTimeout)
// We want to read exactly one message.
go func() {
defer close(msgChan)
_, msg, err := c.ReadMessage()
if err != nil {
errChan <- err
return
}
// The chunked/streamed responses come wrapped in either a
// {"result":{}} or {"error":{}} wrapper which we'll get rid of
// here.
msgStr := string(msg)
if !strings.Contains(msgStr, "\"result\":") {
errChan <- fmt.Errorf("invalid msg: %s", msgStr)
return
}
msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
// Make sure we can parse the unwrapped message into the
// expected proto message.
protoMsg := &chainrpc.BlockEpoch{}
err = jsonpb.UnmarshalString(msgStr, protoMsg)
if err != nil {
errChan <- err
return
}
select {
case msgChan <- protoMsg:
case <-timeout:
}
}()
// Mine a block and make sure we get a message for it.
blockHashes := ht.Miner.GenerateBlocks(1)
select {
case msg := <-msgChan:
require.Equal(
ht, blockHashes[0].CloneBytes(), msg.Hash,
"block hash",
)
case err := <-errChan:
ht.Fatalf("Received error from WS: %v", err)
case <-timeout:
ht.Fatalf("Timeout before message was received")
}
}
func wsTestCaseBiDirectionalSubscription(ht *lntemp.HarnessTest) {
initialRequest := &lnrpc.ChannelAcceptResponse{}
url := "/v1/channels/acceptor"
// This time we send the macaroon in the special header
// Sec-Websocket-Protocol which is the only header field available to
// browsers when opening a WebSocket.
alice := ht.Alice
mac, err := alice.ReadMacaroon(
alice.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(ht, err, "read admin mac")
macBytes, err := mac.MarshalBinary()
require.NoError(ht, err, "marshal admin mac")
customHeader := make(http.Header)
customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
))
conn, err := openWebSocket(
alice, url, "POST", initialRequest, customHeader,
)
require.Nil(ht, err, "websocket")
defer func() {
err := conn.WriteMessage(websocket.CloseMessage, closeMsg)
_ = conn.Close()
require.NoError(ht, err)
}()
// Buffer the message channel to make sure we're always blocking on
// conn.ReadMessage() to allow the ping/pong mechanism to work.
msgChan := make(chan *lnrpc.ChannelAcceptResponse, 1)
errChan := make(chan error)
done := make(chan struct{})
timeout := time.After(defaultTimeout)
// We want to read messages over and over again. We just accept any
// channels that are opened.
defer close(done)
go func() {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
// The chunked/streamed responses come wrapped in either
// a {"result":{}} or {"error":{}} wrapper which we'll
// get rid of here.
msgStr := string(msg)
if !strings.Contains(msgStr, "\"result\":") {
select {
case errChan <- fmt.Errorf("invalid msg: %s",
msgStr):
case <-done:
}
return
}
msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
// Make sure we can parse the unwrapped message into the
// expected proto message.
protoMsg := &lnrpc.ChannelAcceptRequest{}
err = jsonpb.UnmarshalString(msgStr, protoMsg)
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
// Send the response that we accept the channel.
res := &lnrpc.ChannelAcceptResponse{
Accept: true,
PendingChanId: protoMsg.PendingChanId,
}
resMsg, err := jsonMarshaler.MarshalToString(res)
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
err = conn.WriteMessage(
websocket.TextMessage, []byte(resMsg),
)
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
// Also send the message on our message channel to make
// sure we count it as successful.
msgChan <- res
// Are we done or should there be more messages?
select {
case <-done:
return
default:
}
}
}()
// Before we start opening channels, make sure the two nodes are
// connected.
bob := ht.Bob
ht.EnsureConnected(alice, bob)
// Open 3 channels to make sure multiple requests and responses can be
// sent over the web socket.
const numChannels = 3
for i := 0; i < numChannels; i++ {
chanPoint := ht.OpenChannel(
bob, alice, lntemp.OpenChannelParams{Amt: 500000},
)
defer ht.CloseChannel(bob, chanPoint)
select {
case <-msgChan:
case err := <-errChan:
ht.Fatalf("Received error from WS: %v", err)
case <-timeout:
ht.Fatalf("Timeout before message was received")
}
}
}
func wsTestPingPongTimeout(ht *lntemp.HarnessTest) {
initialRequest := &lnrpc.InvoiceSubscription{
AddIndex: 1, SettleIndex: 1,
}
url := "/v1/invoices/subscribe"
// This time we send the macaroon in the special header
// Sec-Websocket-Protocol which is the only header field available to
// browsers when opening a WebSocket.
alice := ht.Alice
mac, err := alice.ReadMacaroon(
alice.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(ht, err, "read admin mac")
macBytes, err := mac.MarshalBinary()
require.NoError(ht, err, "marshal admin mac")
customHeader := make(http.Header)
customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf(
"Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes),
))
conn, err := openWebSocket(
alice, url, "GET", initialRequest, customHeader,
)
require.Nil(ht, err, "websocket")
defer func() {
err := conn.WriteMessage(websocket.CloseMessage, closeMsg)
_ = conn.Close()
require.NoError(ht, err)
}()
// We want to be able to read invoices for a long time, making sure we
// can continue to read even after we've gone through several ping/pong
// cycles.
invoices := make(chan *lnrpc.Invoice, 1)
errChan := make(chan error)
done := make(chan struct{})
timeout := time.After(defaultTimeout)
defer close(done)
go func() {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
// The chunked/streamed responses come wrapped in either
// a {"result":{}} or {"error":{}} wrapper which we'll
// get rid of here.
msgStr := string(msg)
if !strings.Contains(msgStr, "\"result\":") {
select {
case errChan <- fmt.Errorf("invalid msg: %s",
msgStr):
case <-done:
}
return
}
msgStr = resultPattern.ReplaceAllString(msgStr, "${1}")
// Make sure we can parse the unwrapped message into the
// expected proto message.
protoMsg := &lnrpc.Invoice{}
err = jsonpb.UnmarshalString(msgStr, protoMsg)
if err != nil {
select {
case errChan <- err:
case <-done:
}
return
}
invoices <- protoMsg
// Make sure we exit the loop once we've sent through
// all expected test messages.
select {
case <-done:
return
default:
}
}
}()
// The SubscribeInvoices call returns immediately after the gRPC/REST
// connection is established. But it can happen that the goroutine in
// lnd that actually registers the subscriber in the invoice backend
// didn't get any CPU time just yet. So we can run into the situation
// where we add our first invoice _before_ the subscription client is
// registered. If that happens, we'll never get notified about the
// invoice in question. So all we really can do is wait a bit here to
// make sure the subscription is registered correctly.
time.Sleep(500 * time.Millisecond)
// Let's create five invoices and wait for them to arrive. We'll wait
// for at least one ping/pong cycle between each invoice.
const numInvoices = 5
const value = 123
const memo = "websocket"
for i := 0; i < numInvoices; i++ {
invoice := &lnrpc.Invoice{
Value: value,
Memo: memo,
}
alice.RPC.AddInvoice(invoice)
select {
case streamMsg := <-invoices:
require.Equal(ht, int64(value), streamMsg.Value)
require.Equal(ht, memo, streamMsg.Memo)
case err := <-errChan:
require.Fail(ht, "Error reading invoice: %v", err)
case <-timeout:
require.Fail(ht, "No invoice msg received in time")
}
// Let's wait for at least a whole ping/pong cycle to happen, so
// we can be sure the read/write deadlines are set correctly.
// We double the pong wait just to add some extra margin.
time.Sleep(pingInterval + 2*pongWait)
}
}
// invokeGET calls the given URL with the GET method and appropriate macaroon
// header fields then tries to unmarshal the response into the given response
// proto message.
func invokeGET(node *node.HarnessNode, url string, resp proto.Message) error {
_, rawResp, err := makeRequest(node, url, "GET", nil, nil)
if err != nil {
return err
}
return jsonpb.Unmarshal(bytes.NewReader(rawResp), resp)
}
// invokePOST calls the given URL with the POST method, request body and
// appropriate macaroon header fields then tries to unmarshal the response into
// the given response proto message.
func invokePOST(node *node.HarnessNode, url string, req,
resp proto.Message) error {
// Marshal the request to JSON using the jsonpb marshaler to get correct
// field names.
var buf bytes.Buffer
if err := jsonMarshaler.Marshal(&buf, req); err != nil {
return err
}
_, rawResp, err := makeRequest(node, url, "POST", &buf, nil)
if err != nil {
return err
}
return jsonpb.Unmarshal(bytes.NewReader(rawResp), resp)
}
// makeRequest calls the given URL with the given method, request body and
// appropriate macaroon header fields and returns the raw response body.
func makeRequest(node *node.HarnessNode, url, method string,
request io.Reader, additionalHeaders http.Header) (http.Header, []byte,
error) {
// Assemble the full URL from the node's listening address then create
// the request so we can set the macaroon on it.
fullURL := fmt.Sprintf("https://%s%s", node.Cfg.RESTAddr(), url)
req, err := http.NewRequest(method, fullURL, request)
if err != nil {
return nil, nil, err
}
if err := addAdminMacaroon(node, req.Header); err != nil {
return nil, nil, err
}
for key, values := range additionalHeaders {
for _, value := range values {
req.Header.Add(key, value)
}
}
// Do the actual call with the completed request object now.
resp, err := restClient.Do(req)
if err != nil {
return nil, nil, err
}
defer func() { _ = resp.Body.Close() }()
data, err := ioutil.ReadAll(resp.Body)
return resp.Header, data, err
}
// openWebSocket opens a new WebSocket connection to the given URL with the
// appropriate macaroon headers and sends the request message over the socket.
func openWebSocket(node *node.HarnessNode, url, method string,
req proto.Message, customHeader http.Header) (*websocket.Conn, error) {
// Prepare our macaroon headers and assemble the full URL from the
// node's listening address. WebSockets always work over GET so we need
// to append the target request method as a query parameter.
header := customHeader
if header == nil {
header = make(http.Header)
if err := addAdminMacaroon(node, header); err != nil {
return nil, err
}
}
fullURL := fmt.Sprintf(
"wss://%s%s?method=%s", node.Cfg.RESTAddr(), url, method,
)
conn, resp, err := webSocketDialer.Dial(fullURL, header)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
// Send the given request message as the first message on the socket.
reqMsg, err := jsonMarshaler.MarshalToString(req)
if err != nil {
return nil, err
}
err = conn.WriteMessage(websocket.TextMessage, []byte(reqMsg))
if err != nil {
return nil, err
}
return conn, nil
}
// addAdminMacaroon reads the admin macaroon from the node and appends it to
// the HTTP header fields.
func addAdminMacaroon(node *node.HarnessNode, header http.Header) error {
mac, err := node.ReadMacaroon(node.Cfg.AdminMacPath, defaultTimeout)
if err != nil {
return err
}
macBytes, err := mac.MarshalBinary()
if err != nil {
return err
}
header.Set("Grpc-Metadata-Macaroon", hex.EncodeToString(macBytes))
return nil
}
-879
View File
@@ -1,879 +0,0 @@
package itest
import (
"bytes"
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
// testRevokedCloseRetribution tests that Carol is able carry out
// retribution in the event that she fails immediately after detecting Bob's
// breach txn in the mempool.
func testRevokedCloseRetribution(ht *lntemp.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
)
// Carol will be the breached party. We set --nolisten to ensure Bob
// won't be able to connect to her and trigger the channel data
// protection logic automatically. We also can't have Carol
// automatically re-connect too early, otherwise DLP would be initiated
// instead of the breach we want to provoke.
carol := ht.NewNode(
"Carol",
[]string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"},
)
// We must let Bob communicate with Carol before they are able to open
// channel, so we connect Bob and Carol,
bob := ht.Bob
ht.ConnectNodes(carol, bob)
// Before we make a channel, we'll load up Carol with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// In order to test Carol's response to an uncooperative channel
// closure by Bob, we'll first open up a channel between them with a
// 0.5 BTC value.
chanPoint := ht.OpenChannel(
carol, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// With the channel open, we'll create a few invoices for Bob that
// Carol will pay to in order to advance the state of the channel.
bobPayReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, numInvoices)
// Send payments from Carol to Bob using 3 of Bob's payment hashes
// generated above.
ht.CompletePaymentRequests(carol, bobPayReqs[:numInvoices/2])
// Next query for Bob's channel state, as we sent 3 payments of 10k
// satoshis each, Bob should now see his balance as being 30k satoshis.
bobChan := ht.AssertChannelLocalBalance(bob, chanPoint, 30_000)
// Grab Bob's current commitment height (update number), we'll later
// revert him to this state after additional updates to force him to
// broadcast this soon to be revoked state.
bobStateNumPreCopy := bobChan.NumUpdates
// With the temporary file created, copy Bob's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(bob)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(carol, bob)
// Because Bob has been restarted, we need to make sure Carol has
// remarked the channel as active after that.
ht.AssertChannelExists(carol, chanPoint)
// Finally, send payments from Carol to Bob, consuming Bob's remaining
// payment hashes.
ht.CompletePaymentRequests(carol, bobPayReqs[numInvoices/2:])
// Now we shutdown Bob, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Bob to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(bob)
// Now query for Bob's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(bob, chanPoint, int(bobStateNumPreCopy))
// Now force Bob to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Carol's retribution.
_, breachTXID := ht.CloseChannelAssertPending(bob, chanPoint, true)
// Here, Carol sees Bob's breach transaction in the mempool, but is
// waiting for it to confirm before continuing her retribution. We
// restart Carol to ensure that she is persisting her retribution state
// and continues watching for the breach transaction to confirm even
// after her node restarts.
ht.RestartNode(carol)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
ht.Miner.AssertTxInBlock(block, breachTXID)
// Query the mempool for Carol's justice transaction, this should be
// broadcast as Bob's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Bob's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
// We restart Carol here to ensure that she persists her retribution
// state and successfully continues exacting retribution after
// restarting. At this point, Carol has broadcast the justice
// transaction, but it hasn't been confirmed yet; when Carol restarts,
// she should start waiting for the justice transaction to confirm
// again.
ht.RestartNode(carol)
// Now mine a block, this transaction should include Carol's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
ht.AssertNodeNumChannels(carol, 0)
// Mine enough blocks for Bob's channel arbitrator to wrap up the
// references to the breached channel. The chanarb waits for commitment
// tx's confHeight+CSV-1 blocks and since we've already mined one that
// included the justice tx we only need to mine extra DefaultCSV-2
// blocks to unlock it.
ht.MineBlocks(lntest.DefaultCSV - 2)
ht.AssertNumPendingForceClose(bob, 0)
}
// testRevokedCloseRetributionZeroValueRemoteOutput tests that Dave is able
// carry out retribution in the event that he fails in state where the remote
// commitment output has zero-value.
func testRevokedCloseRetributionZeroValueRemoteOutput(ht *lntemp.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically. We also can't have Dave automatically
// re-connect too early, otherwise DLP would be initiated instead of the
// breach we want to provoke.
dave := ht.NewNode(
"Dave",
[]string{"--hodl.exit-settle", "--nolisten", "--minbackoff=1h"},
)
// We must let Dave have an open channel before he can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
chanPoint := ht.OpenChannel(
dave, carol, lntemp.OpenChannelParams{Amt: chanAmt},
)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should now see her balance as being 0 satoshis.
carolChan := ht.AssertChannelLocalBalance(carol, chanPoint, 0)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force her to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
// Now we shutdown Carol, copying over the her temporary database state
// which has the *prior* channel state over her current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that she's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting her current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so she'll soon
// feel the wrath of Dave's retribution.
stream, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Here, Dave receives a confirmation of Carol's breach transaction.
// We restart Dave to ensure that he is persisting his retribution
// state and continues exacting justice after his node restarts.
ht.RestartNode(dave)
// The breachTXID should match the above closeTxID.
breachTXID := ht.WaitForChannelCloseEvent(stream)
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo ")
}
// We restart Dave here to ensure that he persists his retribution state
// and successfully continues exacting retribution after restarting. At
// this point, Dave has broadcast the justice transaction, but it hasn't
// been confirmed yet; when Dave restarts, he should start waiting for
// the justice transaction to confirm again.
ht.RestartNode(dave)
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
ht.AssertNodeNumChannels(dave, 0)
}
// testRevokedCloseRetributionRemoteHodl tests that Dave properly responds to a
// channel breach made by the remote party, specifically in the case that the
// remote party breaches before settling extended HTLCs.
func testRevokedCloseRetributionRemoteHodl(ht *lntemp.HarnessTest) {
const (
chanAmt = funding.MaxBtcFundingAmount
pushAmt = 200000
paymentAmt = 10000
numInvoices = 6
)
// Since this test will result in the counterparty being left in a
// weird state, we will introduce another node into our test network:
// Carol.
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
// We'll also create a new node Dave, who will have a channel with
// Carol, and also use similar settings so we can broadcast a commit
// with active HTLCs. Dave will be the breached party. We set
// --nolisten to ensure Carol won't be able to connect to him and
// trigger the channel data protection logic automatically.
dave := ht.NewNode(
"Dave",
[]string{"--hodl.exit-settle", "--nolisten"},
)
// We must let Dave communicate with Carol before they are able to open
// channel, so we connect Dave and Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// In order to test Dave's response to an uncooperative channel closure
// by Carol, we'll first open up a channel between them with a
// funding.MaxBtcFundingAmount (2^24) satoshis value.
chanPoint := ht.OpenChannel(
dave, carol, lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
},
)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// We'll introduce an closure to validate that Carol's current
// number of updates is at least as large as the provided minimum
// number.
checkCarolNumUpdatesAtLeast := func(carolChan *lnrpc.Channel,
minimum int) {
require.GreaterOrEqual(ht, int(carolChan.NumUpdates), minimum,
"carol's numupdates is incorrect")
}
// Ensure that carol's balance starts with the amount we pushed to her.
ht.AssertChannelLocalBalance(carol, chanPoint, pushAmt)
// Send payments from Dave to Carol using 3 of Carol's payment hashes
// generated above.
ht.CompletePaymentRequestsNoWait(
dave, carolPayReqs[:numInvoices/2], chanPoint,
)
// At this point, we'll also send over a set of HTLC's from Carol to
// Dave. This ensures that the final revoked transaction has HTLC's in
// both directions.
davePayReqs, _, _ := ht.CreatePayReqs(dave, paymentAmt, numInvoices)
// Send payments from Carol to Dave using 3 of Dave's payment hashes
// generated above.
ht.CompletePaymentRequestsNoWait(
carol, davePayReqs[:numInvoices/2], chanPoint,
)
// Next query for Carol's channel state, as we sent 3 payments of 10k
// satoshis each, however Carol should now see her balance as being
// equal to the push amount in satoshis since she has not settled.
// Ensure that carol's balance still reflects the original amount we
// pushed to her, minus the HTLCs she just sent to Dave.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, pushAmt-3*paymentAmt,
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force her to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// Since Carol has not settled, she should only see at least one update
// to her channel.
checkCarolNumUpdatesAtLeast(carolChan, 1)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(
dave, carolPayReqs[numInvoices/2:], chanPoint,
)
// Ensure that carol's balance still shows the amount we originally
// pushed to her (minus the HTLCs she sent to Bob), and that at least
// one more update has occurred.
carolChan = ht.AssertChannelLocalBalance(
carol, chanPoint, pushAmt-3*paymentAmt,
)
checkCarolNumUpdatesAtLeast(carolChan, carolStateNumPreCopy+1)
// Suspend Dave, such that Carol won't reconnect at startup, triggering
// the data loss protection.
restartDave := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the her temporary database state
// which has the *prior* channel state over her current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Ensure that Carol's view of the channel is consistent with the state
// of the channel just before it was snapshotted.
carolChan = ht.AssertChannelLocalBalance(
carol, chanPoint, pushAmt-3*paymentAmt,
)
checkCarolNumUpdatesAtLeast(carolChan, 1)
// Now query for Carol's channel state, it should show that she's at a
// state number in the past, *not* the latest state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting her current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so she'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Generate a single block to mine the breach transaction.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// We resurrect Dave to ensure he will be exacting justice after his
// node restarts.
require.NoError(ht, restartDave(), "unable to restart Dave's node")
// Finally, wait for the final close status update, then ensure that
// the closing transaction was included in the block.
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
require.Equal(ht, closeTxID[:], breachTXID[:],
"expected breach ID to be equal to close ID")
ht.Miner.AssertTxInBlock(block, breachTXID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above. Since Carol might have had the time to take some of the HTLC
// outputs to the second level before Dave broadcasts his justice tx,
// we'll search through the mempool for a tx that matches the number of
// expected inputs in the justice tx.
var justiceTxid *chainhash.Hash
errNotFound := errors.New("justice tx not found")
findJusticeTx := func() (*chainhash.Hash, error) {
mempool := ht.Miner.GetRawMempool()
for _, txid := range mempool {
// Check that the justice tx has the appropriate number
// of inputs.
tx := ht.Miner.GetRawTransaction(txid)
exNumInputs := 2 + numInvoices
if len(tx.MsgTx().TxIn) == exNumInputs {
return txid, nil
}
}
return nil, errNotFound
}
err := wait.NoError(func() error {
txid, err := findJusticeTx()
if err != nil {
return err
}
justiceTxid = txid
return nil
}, defaultTimeout)
if err != nil && errors.Is(err, errNotFound) {
// If Dave is unable to broadcast his justice tx on first
// attempt because of the second layer transactions, he will
// wait until the next block epoch before trying again. Because
// of this, we'll mine a block if we cannot find the justice tx
// immediately. Since we cannot tell for sure how many
// transactions will be in the mempool at this point, we pass 0
// as the last argument, indicating we don't care what's in the
// mempool.
ht.MineBlocks(1)
err = wait.NoError(func() error {
txid, err := findJusticeTx()
if err != nil {
return err
}
justiceTxid = txid
return nil
}, defaultTimeout)
}
require.NoError(ht, err, "timeout finding justice tx")
justiceTx := ht.Miner.GetRawTransaction(justiceTxid)
// isSecondLevelSpend checks that the passed secondLevelTxid is a
// potentitial second level spend spending from the commit tx.
isSecondLevelSpend := func(commitTxid,
secondLevelTxid *chainhash.Hash) bool {
secondLevel := ht.Miner.GetRawTransaction(secondLevelTxid)
// A second level spend should have only one input, and one
// output.
if len(secondLevel.MsgTx().TxIn) != 1 {
return false
}
if len(secondLevel.MsgTx().TxOut) != 1 {
return false
}
// The sole input should be spending from the commit tx.
txIn := secondLevel.MsgTx().TxIn[0]
return bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:])
}
// Check that all the inputs of this transaction are spending outputs
// generated by Carol's breach transaction above.
for _, txIn := range justiceTx.MsgTx().TxIn {
if bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) {
continue
}
// If the justice tx is spending from an output that was not on
// the breach tx, Carol might have had the time to take an
// output to the second level. In that case, check that the
// justice tx is spending this second level output.
if isSecondLevelSpend(breachTXID, &txIn.PreviousOutPoint.Hash) {
continue
}
require.Fail(ht, "justice tx not spending commitment utxo "+
"instead is: %v", txIn.PreviousOutPoint)
}
// We restart Dave here to ensure that he persists he retribution state
// and successfully continues exacting retribution after restarting. At
// this point, Dave has broadcast the justice transaction, but it
// hasn't been confirmed yet; when Dave restarts, he should start
// waiting for the justice transaction to confirm again.
ht.RestartNode(dave)
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}
// testRevokedCloseRetributionAltruistWatchtower establishes a channel between
// Carol and Dave, where Carol is using a third node Willy as her watchtower.
// After sending some payments, Dave reverts his state and force closes to
// trigger a breach. Carol is kept offline throughout the process and the test
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntemp.HarnessTest) {
testCases := []struct {
name string
anchors bool
}{{
name: "anchors",
anchors: true,
}, {
name: "legacy",
anchors: false,
}}
for _, tc := range testCases {
tc := tc
testFunc := func(ht *lntemp.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, tc.anchors,
)
}
success := ht.Run(tc.name, func(tt *testing.T) {
st := ht.Subtest(tt)
st.RunTestCase(&lntemp.TestCase{
Name: tc.name,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntemp.HarnessTest,
anchors bool) {
const (
chanAmt = funding.MaxBtcFundingAmount
paymentAmt = 10000
numInvoices = 6
externalIP = "1.2.3.4"
)
// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := []string{"--hodl.exit-settle"}
if anchors {
carolArgs = append(carolArgs, "--protocol.anchors")
}
carol := ht.NewNode("Carol", carolArgs)
// Willy the watchtower will protect Dave from Carol's breach. He will
// remain online in order to punish Carol on Dave's behalf, since the
// breach will happen while Dave is offline.
willy := ht.NewNode(
"Willy", []string{"--watchtower.active",
"--watchtower.externalip=" + externalIP},
)
willyInfo := willy.RPC.GetInfoWatchtower()
// Assert that Willy has one listener and it is 0.0.0.0:9911 or
// [::]:9911. Since no listener is explicitly specified, one of these
// should be the default depending on whether the host supports IPv6 or
// not.
require.Len(ht, willyInfo.Listeners, 1, "Willy should have 1 listener")
listener := willyInfo.Listeners[0]
if listener != "0.0.0.0:9911" && listener != "[::]:9911" {
ht.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+
"got %v", listener)
}
// Assert the Willy's URIs properly display the chosen external IP.
require.Len(ht, willyInfo.Uris, 1, "Willy should have 1 uri")
require.Contains(ht, willyInfo.Uris[0], externalIP)
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := []string{
"--nolisten",
"--wtclient.active",
}
if anchors {
daveArgs = append(daveArgs, "--protocol.anchors")
}
dave := ht.NewNode("Dave", daveArgs)
addTowerReq := &wtclientrpc.AddTowerRequest{
Pubkey: willyInfo.Pubkey,
Address: listener,
}
dave.RPC.AddTower(addTowerReq)
// We must let Dave have an open channel before she can send a node
// announcement, so we open a channel with Carol,
ht.ConnectNodes(dave, carol)
// Before we make a channel, we'll load up Dave with some coins sent
// directly from the miner.
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Send one more UTXOs if this is a neutrino backend.
if ht.IsNeutrinoBackend() {
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
}
// In order to test Dave's response to an uncooperative channel
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntemp.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
}
chanPoint := ht.OpenChannel(dave, carol, params)
// With the channel open, we'll create a few invoices for Carol that
// Dave will pay to in order to advance the state of the channel.
carolPayReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numInvoices)
// Next query for Carol's channel state, as we sent 0 payments, Carol
// should still see her balance as the push amount, which is 1/4 of the
// capacity.
carolChan := ht.AssertChannelLocalBalance(
carol, chanPoint, int64(chanAmt/4),
)
// Grab Carol's current commitment height (update number), we'll later
// revert her to this state after additional updates to force him to
// broadcast this soon to be revoked state.
carolStateNumPreCopy := int(carolChan.NumUpdates)
// With the temporary file created, copy Carol's current state into the
// temporary file we created above. Later after more updates, we'll
// restore this state.
ht.BackupDB(carol)
// Reconnect the peers after the restart that was needed for the db
// backup.
ht.EnsureConnected(dave, carol)
// Finally, send payments from Dave to Carol, consuming Carol's
// remaining payment hashes.
ht.CompletePaymentRequestsNoWait(dave, carolPayReqs, chanPoint)
daveBalResp := dave.RPC.WalletBalance()
davePreSweepBalance := daveBalResp.ConfirmedBalance
// Wait until the backup has been accepted by the watchtower before
// shutting down Dave.
err := wait.NoError(func() error {
bkpStats := dave.RPC.WatchtowerStats()
if bkpStats == nil {
return errors.New("no active backup sessions")
}
if bkpStats.NumBackups == 0 {
return errors.New("no backups accepted")
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "unable to verify backup task completed")
// Shutdown Dave to simulate going offline for an extended period of
// time. Once he's not watching, Carol will try to breach the channel.
restart := ht.SuspendNode(dave)
// Now we shutdown Carol, copying over the his temporary database state
// which has the *prior* channel state over his current most up to date
// state. With this, we essentially force Carol to travel back in time
// within the channel's history.
ht.RestartNodeAndRestoreDB(carol)
// Now query for Carol's channel state, it should show that he's at a
// state number in the past, not the *latest* state.
ht.AssertChannelCommitHeight(carol, chanPoint, carolStateNumPreCopy)
// Now force Carol to execute a *force* channel closure by unilaterally
// broadcasting his current channel state. This is actually the
// commitment transaction of a prior *revoked* state, so he'll soon
// feel the wrath of Dave's retribution.
closeUpdates, closeTxID := ht.CloseChannelAssertPending(
carol, chanPoint, true,
)
// Finally, generate a single block, wait for the final close status
// update, then ensure that the closing transaction was included in the
// block.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
breachTXID := ht.WaitForChannelCloseEvent(closeUpdates)
ht.Miner.AssertTxInBlock(block, breachTXID)
// The breachTXID should match the above closeTxID.
require.EqualValues(ht, breachTXID, closeTxID)
// Query the mempool for Dave's justice transaction, this should be
// broadcast as Carol's contract breaching transaction gets confirmed
// above.
justiceTXID := ht.Miner.AssertNumTxsInMempool(1)[0]
// Query for the mempool transaction found above. Then assert that all
// the inputs of this transaction are spending outputs generated by
// Carol's breach transaction above.
justiceTx := ht.Miner.GetRawTransaction(justiceTXID)
for _, txIn := range justiceTx.MsgTx().TxIn {
require.Equal(ht, breachTXID[:], txIn.PreviousOutPoint.Hash[:],
"justice tx not spending commitment utxo")
}
willyBalResp := willy.RPC.WalletBalance()
require.Zero(ht, willyBalResp.ConfirmedBalance,
"willy should have 0 balance before mining justice transaction")
// Now mine a block, this transaction should include Dave's justice
// transaction which was just accepted into the mempool.
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The block should have exactly *two* transactions, one of which is
// the justice transaction.
require.Len(ht, block.Transactions, 2, "transaction wasn't mined")
justiceSha := block.Transactions[1].TxHash()
require.Equal(ht, justiceTx.Hash()[:], justiceSha[:],
"justice tx wasn't mined")
// Ensure that Willy doesn't get any funds, as he is acting as an
// altruist watchtower.
err = wait.NoError(func() error {
willyBalResp := willy.RPC.WalletBalance()
if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("Expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
return nil
}, time.Second*5)
require.NoError(ht, err, "timeout checking willy's balance")
// Before restarting Dave, shutdown Carol so Dave won't sync with her.
// Otherwise, during the restart, Dave will realize Carol is falling
// behind and return `ErrCommitSyncRemoteDataLoss`, thus force closing
// the channel. Although this force close tx will be later replaced by
// the breach tx, it will create two anchor sweeping txes for neutrino
// backend, causing the confirmed wallet balance to be zero later on
// because the utxos are used in sweeping.
ht.Shutdown(carol)
// Restart Dave, who will still think his channel with Carol is open.
// We should him to detect the breach, but realize that the funds have
// then been swept to his wallet by Willy.
require.NoError(ht, restart(), "unable to restart dave")
err = wait.NoError(func() error {
daveBalResp := dave.RPC.ChannelBalance()
if daveBalResp.LocalBalance.Sat != 0 {
return fmt.Errorf("Dave should end up with zero "+
"channel balance, instead has %d",
daveBalResp.LocalBalance.Sat)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's channel balance")
ht.AssertNumPendingForceClose(dave, 0)
// If this is an anchor channel, Dave would sweep the anchor.
if anchors {
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// Check that Dave's wallet balance is increased.
err = wait.NoError(func() error {
daveBalResp := dave.RPC.WalletBalance()
if daveBalResp.ConfirmedBalance <= davePreSweepBalance {
return fmt.Errorf("Dave should have more than %d "+
"after sweep, instead has %d",
davePreSweepBalance,
daveBalResp.ConfirmedBalance)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "timeout checking dave's wallet balance")
// Dave should have no open channels.
ht.AssertNodeNumChannels(dave, 0)
}
File diff suppressed because it is too large. Load diff
@@ -1,870 +0,0 @@
package itest
import (
"context"
"fmt"
"testing"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"gopkg.in/macaroon.v2"
)
// testRPCMiddlewareInterceptor tests that the RPC middleware interceptor can
// be used correctly and in a safe way.
func testRPCMiddlewareInterceptor(ht *lntemp.HarnessTest) {
// Let's first enable the middleware interceptor.
//
// NOTE: we cannot use standby nodes here as the test messes with
// middleware interceptor. Thus we also skip the calling of cleanup of
// each of the following subtests because no standby nodes are used.
alice := ht.NewNode("alice", []string{"--rpcmiddleware.enable"})
bob := ht.NewNode("bob", nil)
// Let's set up a channel between Alice and Bob, just to get some useful
// data to inspect when doing RPC calls to Alice later.
ht.EnsureConnected(alice, bob)
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
ht.OpenChannel(alice, bob, lntemp.OpenChannelParams{Amt: 1_234_567})
// Load or bake the macaroons that the simulated users will use to
// access the RPC.
readonlyMac, err := alice.ReadMacaroon(
alice.Cfg.ReadMacPath, defaultTimeout,
)
require.NoError(ht, err)
adminMac, err := alice.ReadMacaroon(
alice.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(ht, err)
customCaveatReadonlyMac, err := macaroons.SafeCopyMacaroon(readonlyMac)
require.NoError(ht, err)
addConstraint := macaroons.CustomConstraint(
"itest-caveat", "itest-value",
)
require.NoError(ht, addConstraint(customCaveatReadonlyMac))
customCaveatAdminMac, err := macaroons.SafeCopyMacaroon(adminMac)
require.NoError(ht, err)
require.NoError(ht, addConstraint(customCaveatAdminMac))
// Run all sub-tests now. We can't run anything in parallel because that
// would cause the main test function to exit and the nodes being
// cleaned up.
ht.Run("registration restrictions", func(tt *testing.T) {
middlewareRegistrationRestrictionTests(tt, alice)
})
ht.Run("read-only intercept", func(tt *testing.T) {
registration := registerMiddleware(
tt, alice, &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor-1",
ReadOnlyMode: true,
}, true,
)
defer registration.cancel()
middlewareInterceptionTest(
tt, alice, bob, registration, readonlyMac,
customCaveatReadonlyMac, true,
)
})
// We've manually disconnected Bob from Alice in the previous test, make
// sure they're connected again.
//
// NOTE: we may get an error here saying "interceptor RPC client quit"
// as it takes some time for the interceptor to fully quit. Thus we
// restart the node here to make sure the old interceptor is removed
// from registration.
ht.RestartNode(alice)
ht.EnsureConnected(alice, bob)
ht.Run("encumbered macaroon intercept", func(tt *testing.T) {
registration := registerMiddleware(
tt, alice, &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor-2",
CustomMacaroonCaveatName: "itest-caveat",
}, true,
)
defer registration.cancel()
middlewareInterceptionTest(
tt, alice, bob, registration,
customCaveatReadonlyMac, readonlyMac, false,
)
})
// Next, run the response manipulation tests.
//
// NOTE: we may get an error here saying "interceptor RPC client quit"
// as it takes some time for the interceptor to fully quit. Thus we
// restart the node here to make sure the old interceptor is removed
// from registration.
ht.RestartNode(alice)
ht.EnsureConnected(alice, bob)
ht.Run("read-only not allowed to manipulate", func(tt *testing.T) {
registration := registerMiddleware(
tt, alice, &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor-3",
ReadOnlyMode: true,
}, true,
)
defer registration.cancel()
middlewareRequestManipulationTest(
tt, alice, registration, adminMac, true,
)
middlewareResponseManipulationTest(
tt, alice, bob, registration, readonlyMac, true,
)
})
// NOTE: we may get an error here saying "interceptor RPC client quit"
// as it takes some time for the interceptor to fully quit. Thus we
// restart the node here to make sure the old interceptor is removed
// from registration.
ht.RestartNode(alice)
ht.EnsureConnected(alice, bob)
ht.Run("encumbered macaroon manipulate", func(tt *testing.T) {
registration := registerMiddleware(
tt, alice, &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor-4",
CustomMacaroonCaveatName: "itest-caveat",
}, true,
)
defer registration.cancel()
middlewareRequestManipulationTest(
tt, alice, registration, customCaveatAdminMac, false,
)
middlewareResponseManipulationTest(
tt, alice, bob, registration,
customCaveatReadonlyMac, false,
)
})
// And finally make sure mandatory middleware is always checked for any
// RPC request.
ht.Run("mandatory middleware", func(tt *testing.T) {
st := ht.Subtest(tt)
middlewareMandatoryTest(st, alice)
})
}
// middlewareRegistrationRestrictionTests tests all restrictions that apply to
// registering a middleware.
func middlewareRegistrationRestrictionTests(t *testing.T,
node *node.HarnessNode) {
testCases := []struct {
registration *lnrpc.MiddlewareRegistration
expectedErr string
}{{
registration: &lnrpc.MiddlewareRegistration{
MiddlewareName: "foo",
},
expectedErr: "invalid middleware name",
}, {
registration: &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor",
CustomMacaroonCaveatName: "foo",
},
expectedErr: "custom caveat name of at least",
}, {
registration: &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor",
CustomMacaroonCaveatName: "itest-caveat",
ReadOnlyMode: true,
},
expectedErr: "cannot set read-only and custom caveat name",
}}
for idx, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("%d", idx), func(tt *testing.T) {
invalidName := registerMiddleware(
tt, node, tc.registration, false,
)
_, err := invalidName.stream.Recv()
require.Error(tt, err)
require.Contains(tt, err.Error(), tc.expectedErr)
invalidName.cancel()
})
}
}
// middlewareInterceptionTest tests that unary and streaming requests can be
// intercepted. It also makes sure that depending on the mode (read-only or
// custom macaroon caveat) a middleware only gets access to the requests it
// should be allowed access to.
func middlewareInterceptionTest(t *testing.T,
node, peer *node.HarnessNode, registration *middlewareHarness,
userMac *macaroon.Macaroon,
disallowedMac *macaroon.Macaroon, readOnly bool) {
t.Helper()
// Everything we test here should be executed in a matter of
// milliseconds, so we can use one single timeout context for all calls.
ctxb := context.Background()
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// Create a client connection that we'll use to simulate user requests
// to lnd with.
cleanup, client := macaroonClient(t, node, userMac)
defer cleanup()
// We're going to send a simple RPC request to list all channels.
// We need to invoke the intercept logic in a goroutine because we'd
// block the execution of the main task otherwise.
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
go registration.interceptUnary(
"/lnrpc.Lightning/ListChannels", req, nil, readOnly, false,
)
// Do the actual call now and wait for the interceptor to do its thing.
resp, err := client.ListChannels(ctxc, req)
require.NoError(t, err)
// Did we receive the correct intercept message?
assertInterceptedType(t, resp, <-registration.responsesChan)
// Also try the interception of a request that is expected to result in
// an error being returned from lnd.
expectedErr := "either `active_only` or `inactive_only` can be set"
invalidReq := &lnrpc.ListChannelsRequest{
ActiveOnly: true,
InactiveOnly: true,
}
go registration.interceptUnary(
"/lnrpc.Lightning/ListChannels", invalidReq, nil, readOnly,
false,
)
// Do the actual call now and wait for the interceptor to do its thing.
_, err = client.ListChannels(ctxc, invalidReq)
require.Error(t, err)
require.Contains(t, err.Error(), expectedErr)
// Did we receive the correct intercept message with the error?
assertInterceptedErr(t, expectedErr, <-registration.responsesChan)
// Let's test the same for a streaming endpoint.
req2 := &lnrpc.PeerEventSubscription{}
go registration.interceptStream(
"/lnrpc.Lightning/SubscribePeerEvents", req2, nil, readOnly,
)
// Do the actual call now and wait for the interceptor to do its thing.
peerCtx, peerCancel := context.WithCancel(ctxb)
resp2, err := client.SubscribePeerEvents(peerCtx, req2)
require.NoError(t, err)
// Disconnect Bob to trigger a peer event without using Alice's RPC
// interface itself.
peer.RPC.DisconnectPeer(node.PubKeyStr)
peerEvent, err := resp2.Recv()
require.NoError(t, err)
require.Equal(t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType())
// Stop the peer stream again, otherwise we'll produce more events.
peerCancel()
// Did we receive the correct intercept message?
assertInterceptedType(t, peerEvent, <-registration.responsesChan)
// Make sure that with the other macaroon we aren't allowed to access
// the interceptor. If we registered for read-only access then there is
// no middleware that handles the custom macaroon caveat. If we
// registered for a custom caveat then there is no middleware that
// handles unencumbered read-only access.
cleanup, client = macaroonClient(t, node, disallowedMac)
defer cleanup()
// We need to make sure we don't get any interception messages for
// requests made with the disallowed macaroon.
var (
errChan = make(chan error, 1)
msgChan = make(chan *lnrpc.RPCMiddlewareRequest, 1)
)
go func() {
req, err := registration.stream.Recv()
if err != nil {
errChan <- err
return
}
msgChan <- req
}()
// Let's invoke the same request again but with the other macaroon.
resp, err = client.ListChannels(ctxc, req)
// Depending on what mode we're in, we expect something different. If we
// are in read-only mode then an encumbered macaroon cannot be used
// since there is no middleware registered for it. If we registered for
// a custom macaroon caveat and a request with anon-encumbered macaroon
// comes in, we expect to just not get any intercept messages.
if readOnly {
require.Error(t, err)
require.Contains(
t, err.Error(), "cannot accept macaroon with custom "+
"caveat 'itest-caveat', no middleware "+
"registered",
)
} else {
require.NoError(t, err)
// We disconnected Bob so there should be no active channels.
require.Len(t, resp.Channels, 0)
}
// There should be neither an error nor any interception messages in the
// channels.
select {
case err := <-errChan:
t.Fatalf("Unexpected error, not expecting messages: %v", err)
case msg := <-msgChan:
t.Fatalf("Unexpected intercept message: %v", msg)
case <-time.After(time.Second):
// Nothing came in for a second, we're fine.
}
}
// middlewareResponseManipulationTest tests that unary and streaming responses
// can be intercepted and also manipulated, at least if the middleware didn't
// register for read-only access.
func middlewareResponseManipulationTest(t *testing.T,
node, peer *node.HarnessNode, registration *middlewareHarness,
userMac *macaroon.Macaroon, readOnly bool) {
t.Helper()
// Everything we test here should be executed in a matter of
// milliseconds, so we can use one single timeout context for all calls.
ctxb := context.Background()
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// Create a client connection that we'll use to simulate user requests
// to lnd with.
cleanup, client := macaroonClient(t, node, userMac)
defer cleanup()
// We're going to attempt to replace the response with our own. But
// since we only registered for read-only access, our replacement should
// just be ignored.
replacementResponse := &lnrpc.ListChannelsResponse{
Channels: []*lnrpc.Channel{{
ChannelPoint: "f000:0",
}, {
ChannelPoint: "f000:1",
}},
}
// We're going to send a simple RPC request to list all channels.
// We need to invoke the intercept logic in a goroutine because we'd
// block the execution of the main task otherwise.
req := &lnrpc.ListChannelsRequest{ActiveOnly: true}
go registration.interceptUnary(
"/lnrpc.Lightning/ListChannels", req, replacementResponse,
readOnly, false,
)
// Do the actual call now and wait for the interceptor to do its thing.
resp, err := client.ListChannels(ctxc, req)
require.NoError(t, err)
// Did we get the manipulated response (2 fake channels) or the original
// one (1 channel)?
if readOnly {
require.Len(t, resp.Channels, 1)
} else {
require.Len(t, resp.Channels, 2)
}
// Let's see if we can also replace an error message sent out by lnd
// with a custom one.
betterError := fmt.Errorf("yo, this request is no good")
invalidReq := &lnrpc.ListChannelsRequest{
ActiveOnly: true,
InactiveOnly: true,
}
go registration.interceptUnary(
"/lnrpc.Lightning/ListChannels", invalidReq, betterError,
readOnly, false,
)
// Do the actual call now and wait for the interceptor to do its thing.
_, err = client.ListChannels(ctxc, invalidReq)
require.Error(t, err)
// Did we get the manipulated error or the original one?
if readOnly {
require.Contains(
t, err.Error(), "either `active_only` or "+
"`inactive_only` can be set",
)
} else {
require.Contains(t, err.Error(), betterError.Error())
}
// Let's test the same for a streaming endpoint.
replacementResponse2 := &lnrpc.PeerEvent{
Type: lnrpc.PeerEvent_PEER_ONLINE,
PubKey: "foo",
}
req2 := &lnrpc.PeerEventSubscription{}
go registration.interceptStream(
"/lnrpc.Lightning/SubscribePeerEvents", req2,
replacementResponse2, readOnly,
)
// Do the actual call now and wait for the interceptor to do its thing.
peerCtx, peerCancel := context.WithCancel(ctxb)
resp2, err := client.SubscribePeerEvents(peerCtx, req2)
require.NoError(t, err)
// Disconnect Bob to trigger a peer event without using Alice's RPC
// interface itself.
peer.RPC.DisconnectPeer(node.PubKeyStr)
peerEvent, err := resp2.Recv()
require.NoError(t, err)
// Did we get the correct, original response?
if readOnly {
require.Equal(
t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType(),
)
require.Equal(t, peer.PubKeyStr, peerEvent.PubKey)
} else {
require.Equal(
t, lnrpc.PeerEvent_PEER_ONLINE, peerEvent.GetType(),
)
require.Equal(t, "foo", peerEvent.PubKey)
}
// Stop the peer stream again, otherwise we'll produce more events.
peerCancel()
}
// middlewareRequestManipulationTest tests that unary and streaming requests
// can be intercepted and also manipulated, at least if the middleware didn't
// register for read-only access.
func middlewareRequestManipulationTest(t *testing.T, node *node.HarnessNode,
registration *middlewareHarness, userMac *macaroon.Macaroon,
readOnly bool) {
t.Helper()
// Everything we test here should be executed in a matter of
// milliseconds, so we can use one single timeout context for all calls.
ctxb := context.Background()
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// Create a client connection that we'll use to simulate user requests
// to lnd with.
cleanup, client := macaroonClient(t, node, userMac)
defer cleanup()
// We're going to attempt to replace the request with our own. But since
// we only registered for read-only access, our replacement should just
// be ignored.
replacementRequest := &lnrpc.Invoice{
Memo: "This is the replaced memo",
}
// We're going to send a simple RPC request to add an invoice. We need
// to invoke the intercept logic in a goroutine because we'd block the
// execution of the main task otherwise.
req := &lnrpc.Invoice{
Memo: "Plz pay me",
Value: 123456,
}
go registration.interceptUnary(
"/lnrpc.Lightning/AddInvoice", req, replacementRequest,
readOnly, true,
)
// Do the actual call now and wait for the interceptor to do its thing.
resp, err := client.AddInvoice(ctxc, req)
require.NoError(t, err)
// Did we get the manipulated response or the original one?
invoice, err := zpay32.Decode(resp.PaymentRequest, harnessNetParams)
require.NoError(t, err)
if readOnly {
require.Equal(t, req.Memo, *invoice.Description)
} else {
require.Equal(t, replacementRequest.Memo, *invoice.Description)
}
// Read the message from the registration, otherwise we cannot check the
// next one.
<-registration.responsesChan
// Make sure we also get errors intercepted for stream events. We do
// this in the request manipulation test because only here we have a
// macaroon with sufficient permissions (admin macaroon) to attempt to
// call a streaming RPC that actually accepts request parameters that we
// can use to provoke an error message.
expectedErr := "channel is too small"
invalidReq := &lnrpc.OpenChannelRequest{
LocalFundingAmount: 5,
}
go registration.interceptStream(
"/lnrpc.Lightning/OpenChannel", invalidReq, nil, readOnly,
)
// Do the actual call now and wait for the interceptor to do its thing.
// We don't receive the error on the RPC call directly, because that
// only returns the stream. We have to attempt to read a response first
// to get the expected error.
responseStream, err := client.OpenChannel(ctxc, invalidReq)
require.NoError(t, err)
_, err = responseStream.Recv()
require.Error(t, err)
require.Contains(t, err.Error(), expectedErr)
assertInterceptedErr(t, expectedErr, <-registration.responsesChan)
}
// middlewareMandatoryTest tests that all RPC requests are blocked if there is
// a mandatory middleware declared that's currently not registered.
func middlewareMandatoryTest(ht *lntemp.HarnessTest, node *node.HarnessNode) {
// Let's declare our itest interceptor as mandatory but don't register
// it just yet. That should cause all RPC requests to fail, except for
// the registration itself.
node.Cfg.SkipUnlock = true
ht.RestartNodeWithExtraArgs(node, []string{
"--noseedbackup", "--rpcmiddleware.enable",
"--rpcmiddleware.addmandatory=itest-interceptor",
})
// The "wait for node to start" flag of the above restart does too much
// and has a call to GetInfo built in, which will fail in this special
// test case. So we need to do the wait and client setup manually here.
conn, err := node.ConnectRPC()
require.NoError(ht, err)
node.Initialize(conn)
err = node.WaitUntilServerActive()
require.NoError(ht, err)
ctxb := context.Background()
ctxc, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// Test a unary request first.
_, err = node.RPC.LN.ListChannels(ctxc, &lnrpc.ListChannelsRequest{})
require.Contains(ht, err.Error(), "middleware 'itest-interceptor' is "+
"currently not registered")
// Then a streaming one.
stream := node.RPC.SubscribeInvoices(&lnrpc.InvoiceSubscription{})
_, err = stream.Recv()
require.Error(ht, err)
require.Contains(ht, err.Error(), "middleware 'itest-interceptor' is "+
"currently not registered")
// Now let's register the middleware and try again.
registration := registerMiddleware(
ht.T, node, &lnrpc.MiddlewareRegistration{
MiddlewareName: "itest-interceptor",
CustomMacaroonCaveatName: "itest-caveat",
}, true,
)
defer registration.cancel()
// Both the unary and streaming requests should now be allowed.
time.Sleep(500 * time.Millisecond)
node.RPC.ListChannels(&lnrpc.ListChannelsRequest{})
node.RPC.SubscribeInvoices(&lnrpc.InvoiceSubscription{})
// We now shut down the node manually to prevent the test from failing
// because we can't call the stop RPC if we unregister the middleware
// in the defer statement above.
ht.KillNode(node)
}
// assertInterceptedType makes sure that the intercept message sent by the RPC
// interceptor is correct for a proto message that was sent or received over the
// RPC interface.
func assertInterceptedType(t *testing.T, rpcMessage proto.Message,
interceptMessage *lnrpc.RPCMessage) {
t.Helper()
require.Equal(
t, string(proto.MessageName(rpcMessage)),
interceptMessage.TypeName,
)
rawRequest, err := proto.Marshal(rpcMessage)
require.NoError(t, err)
// Make sure we don't trip over nil vs. empty slice in the equality
// check below.
if len(rawRequest) == 0 {
rawRequest = nil
}
require.Equal(t, rawRequest, interceptMessage.Serialized)
}
// assertInterceptedErr makes sure that the intercept message sent by the RPC
// interceptor contains an error message instead of a serialized proto message.
func assertInterceptedErr(t *testing.T, errString string,
interceptMessage *lnrpc.RPCMessage) {
t.Helper()
require.Equal(t, "error", interceptMessage.TypeName)
require.True(t, interceptMessage.IsError)
require.Contains(t, string(interceptMessage.Serialized), errString)
}
// middlewareStream is a type alias to shorten the long definition.
type middlewareStream lnrpc.Lightning_RegisterRPCMiddlewareClient
// middlewareHarness is a test harness that holds one instance of a simulated
// middleware.
type middlewareHarness struct {
t *testing.T
cancel func()
stream middlewareStream
responsesChan chan *lnrpc.RPCMessage
}
// registerMiddleware creates a new middleware harness and sends the initial
// register message to the RPC server.
func registerMiddleware(t *testing.T, node *node.HarnessNode,
registration *lnrpc.MiddlewareRegistration,
waitForRegister bool) *middlewareHarness {
t.Helper()
middlewareStream, cancel := node.RPC.RegisterRPCMiddleware()
errChan := make(chan error)
go func() {
msg := &lnrpc.RPCMiddlewareResponse_Register{
Register: registration,
}
err := middlewareStream.Send(&lnrpc.RPCMiddlewareResponse{
MiddlewareMessage: msg,
})
errChan <- err
}()
select {
case <-time.After(defaultTimeout):
require.Fail(t, "registerMiddleware send timeout")
case err := <-errChan:
require.NoError(t, err, "registerMiddleware send failed")
}
mh := &middlewareHarness{
t: t,
cancel: cancel,
stream: middlewareStream,
responsesChan: make(chan *lnrpc.RPCMessage),
}
if !waitForRegister {
return mh
}
// Wait for the registration complete message.
msg := make(chan *lnrpc.RPCMiddlewareRequest)
go func() {
regCompleteMsg, err := middlewareStream.Recv()
require.NoError(t, err, "registerMiddleware recv failed")
msg <- regCompleteMsg
}()
select {
case <-time.After(defaultTimeout):
require.Fail(t, "registerMiddleware recv timeout")
case m := <-msg:
require.True(t, m.GetRegComplete())
}
return mh
}
// interceptUnary intercepts a unary call, optionally requesting to replace the
// response sent to the client. A unary call is expected to receive one
// intercept message for the request and one for the response.
//
// NOTE: Must be called in a goroutine as this will block until the response is
// read from the response channel.
func (h *middlewareHarness) interceptUnary(methodURI string,
expectedRequest proto.Message, responseReplacement interface{},
readOnly bool, replaceRequest bool) {
// Read intercept message and make sure it's for an RPC request.
reqIntercept, err := h.stream.Recv()
require.NoError(h.t, err)
// Make sure the custom condition is populated correctly (if we're using
// a macaroon with a custom condition).
if !readOnly {
require.Equal(
h.t, "itest-value", reqIntercept.CustomCaveatCondition,
)
}
req := reqIntercept.GetRequest()
require.NotNil(h.t, req)
// We know the request we're going to send so make sure we get the right
// type and content from the interceptor.
require.Equal(h.t, methodURI, req.MethodFullUri)
assertInterceptedType(h.t, expectedRequest, req)
// We need to accept the request.
if replaceRequest {
h.sendAccept(reqIntercept.MsgId, responseReplacement)
} else {
h.sendAccept(reqIntercept.MsgId, nil)
}
// Now read the intercept message for the response.
respIntercept, err := h.stream.Recv()
require.NoError(h.t, err)
res := respIntercept.GetResponse()
require.NotNil(h.t, res)
// We expect the request ID to be the same for the request intercept
// and the response intercept messages. But the message IDs must be
// different/unique.
require.Equal(h.t, reqIntercept.RequestId, respIntercept.RequestId)
require.NotEqual(h.t, reqIntercept.MsgId, respIntercept.MsgId)
// We need to accept the response as well.
if replaceRequest {
h.sendAccept(respIntercept.MsgId, nil)
} else {
h.sendAccept(respIntercept.MsgId, responseReplacement)
}
h.responsesChan <- res
}
// interceptStream intercepts a streaming call, optionally requesting to replace
// the (first) response sent to the client. A streaming call is expected to
// receive one intercept message for the stream authentication, one for the
// first request and one for the first response.
//
// NOTE: Must be called in a goroutine as this will block until the first
// response is read from the response channel.
func (h *middlewareHarness) interceptStream(methodURI string,
expectedRequest proto.Message, responseReplacement proto.Message,
readOnly bool) {
// Read intercept message and make sure it's for an RPC stream auth.
authIntercept, err := h.stream.Recv()
require.NoError(h.t, err)
// Make sure the custom condition is populated correctly (if we're using
// a macaroon with a custom condition).
if !readOnly {
require.Equal(
h.t, "itest-value", authIntercept.CustomCaveatCondition,
)
}
auth := authIntercept.GetStreamAuth()
require.NotNil(h.t, auth)
// This is just the authentication, so we can only look at the URI.
require.Equal(h.t, methodURI, auth.MethodFullUri)
// We need to accept the auth.
h.sendAccept(authIntercept.MsgId, nil)
// Read intercept message and make sure it's for an RPC request.
reqIntercept, err := h.stream.Recv()
require.NoError(h.t, err)
req := reqIntercept.GetRequest()
require.NotNil(h.t, req)
// We know the request we're going to send so make sure we get the right
// type and content from the interceptor.
require.Equal(h.t, methodURI, req.MethodFullUri)
assertInterceptedType(h.t, expectedRequest, req)
// We need to accept the request.
h.sendAccept(reqIntercept.MsgId, nil)
// Now read the intercept message for the response.
respIntercept, err := h.stream.Recv()
require.NoError(h.t, err)
res := respIntercept.GetResponse()
require.NotNil(h.t, res)
// We expect the request ID to be the same for the auth intercept,
// request intercept and the response intercept messages. But the
// message IDs must be different/unique.
require.Equal(h.t, authIntercept.RequestId, respIntercept.RequestId)
require.Equal(h.t, reqIntercept.RequestId, respIntercept.RequestId)
require.NotEqual(h.t, authIntercept.MsgId, reqIntercept.MsgId)
require.NotEqual(h.t, authIntercept.MsgId, respIntercept.MsgId)
require.NotEqual(h.t, reqIntercept.MsgId, respIntercept.MsgId)
// We need to accept the response as well.
h.sendAccept(respIntercept.MsgId, responseReplacement)
h.responsesChan <- res
}
// sendAccept sends an accept feedback to the RPC server.
func (h *middlewareHarness) sendAccept(msgID uint64,
responseReplacement interface{}) {
var replacementBytes []byte
if responseReplacement != nil {
var err error
switch t := responseReplacement.(type) {
case proto.Message:
replacementBytes, err = proto.Marshal(t)
require.NoError(h.t, err)
case error:
replacementBytes = []byte(t.Error())
default:
require.Fail(h.t, "invalid replacement type")
}
}
err := h.stream.Send(&lnrpc.RPCMiddlewareResponse{
MiddlewareMessage: &lnrpc.RPCMiddlewareResponse_Feedback{
Feedback: &lnrpc.InterceptFeedback{
ReplaceResponse: len(replacementBytes) > 0,
ReplacementSerialized: replacementBytes,
},
},
RefMsgId: msgID,
})
require.NoError(h.t, err)
}
@@ -1,108 +0,0 @@
package itest
import (
"encoding/hex"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/stretchr/testify/require"
)
// testSendMultiPathPayment tests that we are able to successfully route a
// payment using multiple shards across different paths.
func testSendMultiPathPayment(ht *lntemp.HarnessTest) {
mts := newMppTestScenario(ht)
const paymentAmt = btcutil.Amount(300000)
// Set up a network with three different paths Alice <-> Bob. Channel
// capacities are set such that the payment can only succeed if (at
// least) three paths are used.
//
// _ Eve _
// / \
// Alice -- Carol ---- Bob
// \ /
// \__ Dave ____/
//
req := &mppOpenChannelRequest{
amtAliceCarol: 235000,
amtAliceDave: 135000,
amtCarolBob: 135000,
amtCarolEve: 135000,
amtDaveBob: 135000,
amtEveBob: 135000,
}
mts.openChannels(req)
chanPointAliceDave := mts.channelPoints[1]
// Increase Dave's fee to make the test deterministic. Otherwise it
// would be unpredictable whether pathfinding would go through Charlie
// or Dave for the first shard.
expectedPolicy := mts.updateDaveGlobalPolicy()
// Make sure Alice has heard it.
ht.AssertChannelPolicyUpdate(
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)
// Our first test will be Alice paying Bob using a SendPayment call.
// Let Bob create an invoice for Alice to pay.
payReqs, rHashes, invoices := ht.CreatePayReqs(mts.bob, paymentAmt, 1)
rHash := rHashes[0]
payReq := payReqs[0]
sendReq := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
MaxParts: 10,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
payment := ht.SendPaymentAssertSettled(mts.alice, sendReq)
// Make sure we got the preimage.
require.Equal(ht, hex.EncodeToString(invoices[0].RPreimage),
payment.PaymentPreimage, "preimage doesn't match")
// Check that Alice split the payment in at least three shards. Because
// the hand-off of the htlc to the link is asynchronous (via a mailbox),
// there is some non-determinism in the process. Depending on whether
// the new pathfinding round is started before or after the htlc is
// locked into the channel, different sharding may occur. Therefore we
// can only check if the number of shards isn't below the theoretical
// minimum.
succeeded := 0
for _, htlc := range payment.Htlcs {
if htlc.Status == lnrpc.HTLCAttempt_SUCCEEDED {
succeeded++
}
}
const minExpectedShards = 3
require.GreaterOrEqual(ht, succeeded, minExpectedShards,
"expected shards not reached")
// Make sure Bob show the invoice as settled for the full amount.
inv := mts.bob.RPC.LookupInvoice(rHash)
require.EqualValues(ht, paymentAmt, inv.AmtPaidSat,
"incorrect payment amt")
require.Equal(ht, lnrpc.Invoice_SETTLED, inv.State,
"Invoice not settled")
settled := 0
for _, htlc := range inv.Htlcs {
if htlc.State == lnrpc.InvoiceHTLCState_SETTLED {
settled++
}
}
require.Equal(ht, succeeded, settled,
"num of HTLCs wrong")
// Finally, close all channels.
mts.closeChannels()
}
-461
View File
@@ -1,461 +0,0 @@
package itest
import (
"bytes"
"crypto/sha256"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/stretchr/testify/require"
)
// testDeriveSharedKey checks the ECDH performed by the endpoint
// DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with
// the node's pubkey and a customized public key to check the validity of the
// result.
func testDeriveSharedKey(ht *lntemp.HarnessTest) {
runDeriveSharedKey(ht, ht.Alice)
}
// runDeriveSharedKey checks the ECDH performed by the endpoint
// DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with
// the node's pubkey and a customized public key to check the validity of the
// result.
func runDeriveSharedKey(ht *lntemp.HarnessTest, alice *node.HarnessNode) {
// Create an ephemeral key, extracts its public key, and make a
// PrivKeyECDH using the ephemeral key.
ephemeralPriv, err := btcec.NewPrivateKey()
require.NoError(ht, err, "failed to create ephemeral key")
ephemeralPubBytes := ephemeralPriv.PubKey().SerializeCompressed()
privKeyECDH := &keychain.PrivKeyECDH{PrivKey: ephemeralPriv}
// assertECDHMatch checks the correctness of the ECDH between the
// ephemeral key and the given public key.
assertECDHMatch := func(pub *btcec.PublicKey,
req *signrpc.SharedKeyRequest) {
resp := alice.RPC.DeriveSharedKey(req)
sharedKey, _ := privKeyECDH.ECDH(pub)
require.Equal(ht, sharedKey[:], resp.SharedKey,
"failed to derive the expected key")
}
nodePub, err := btcec.ParsePubKey(alice.PubKey[:])
require.NoError(ht, err, "failed to parse node pubkey")
customizedKeyFamily := int32(keychain.KeyFamilyMultiSig)
customizedIndex := int32(1)
// Derive a customized key.
deriveReq := &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
KeyIndex: customizedIndex,
}
resp := alice.RPC.DeriveKey(deriveReq)
customizedPub, err := btcec.ParsePubKey(resp.RawKeyBytes)
require.NoError(ht, err, "failed to parse node pubkey")
// Test DeriveSharedKey with no optional arguments. It will result in
// performing an ECDH between the ephemeral key and the node's pubkey.
req := &signrpc.SharedKeyRequest{EphemeralPubkey: ephemeralPubBytes}
assertECDHMatch(nodePub, req)
// Test DeriveSharedKey with a KeyLoc which points to the node's pubkey.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keychain.KeyFamilyNodeKey),
KeyIndex: 0,
},
}
assertECDHMatch(nodePub, req)
// Test DeriveSharedKey with a KeyLoc being set in KeyDesc. The KeyLoc
// points to the node's pubkey.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keychain.KeyFamilyNodeKey),
KeyIndex: 0,
},
},
}
assertECDHMatch(nodePub, req)
// Test DeriveSharedKey with RawKeyBytes set in KeyDesc. The RawKeyBytes
// is the node's pubkey bytes, and the KeyFamily is KeyFamilyNodeKey.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: alice.PubKey[:],
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keychain.KeyFamilyNodeKey),
},
},
}
assertECDHMatch(nodePub, req)
// Test DeriveSharedKey with a KeyLoc which points to the customized
// public key.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
KeyIndex: customizedIndex,
},
}
assertECDHMatch(customizedPub, req)
// Test DeriveSharedKey with a KeyLoc being set in KeyDesc. The KeyLoc
// points to the customized public key.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
KeyIndex: customizedIndex,
},
},
}
assertECDHMatch(customizedPub, req)
// Test DeriveSharedKey with RawKeyBytes set in KeyDesc. The RawKeyBytes
// is the customized public key. The KeyLoc is also set with the family
// being the customizedKeyFamily.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: customizedPub.SerializeCompressed(),
KeyLoc: &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
},
},
}
assertECDHMatch(customizedPub, req)
// assertErrorMatch checks when calling DeriveSharedKey with invalid
// params, the expected error is returned.
assertErrorMatch := func(match string, req *signrpc.SharedKeyRequest) {
err := alice.RPC.DeriveSharedKeyErr(req)
require.Contains(ht, err.Error(), match, "error not match")
}
// Test that EphemeralPubkey must be supplied.
req = &signrpc.SharedKeyRequest{}
assertErrorMatch("must provide ephemeral pubkey", req)
// Test that cannot use both KeyDesc and KeyLoc.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: customizedPub.SerializeCompressed(),
},
KeyLoc: &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
KeyIndex: 0,
},
}
assertErrorMatch("use either key_desc or key_loc", req)
// Test when KeyDesc is used, KeyLoc must be set.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: alice.PubKey[:],
},
}
assertErrorMatch("key_desc.key_loc must also be set", req)
// Test that cannot use both RawKeyBytes and KeyIndex.
req = &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubBytes,
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: customizedPub.SerializeCompressed(),
KeyLoc: &signrpc.KeyLocator{
KeyFamily: customizedKeyFamily,
KeyIndex: 1,
},
},
}
assertErrorMatch("use either raw_key_bytes or key_index", req)
}
// testSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all
// custom ways of specifying the signing key in the key descriptor/locator.
func testSignOutputRaw(ht *lntemp.HarnessTest) {
runSignOutputRaw(ht, ht.Alice)
}
// runSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all
// custom ways of specifying the signing key in the key descriptor/locator.
func runSignOutputRaw(ht *lntemp.HarnessTest, alice *node.HarnessNode) {
// For the next step, we need a public key. Let's use a special family
// for this. We want this to be an index of zero.
const testCustomKeyFamily = 44
req := &walletrpc.KeyReq{
KeyFamily: testCustomKeyFamily,
}
keyDesc := alice.RPC.DeriveNextKey(req)
require.Equal(ht, int32(0), keyDesc.KeyLoc.KeyIndex)
targetPubKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
require.NoError(ht, err)
// First, try with a key descriptor that only sets the public key.
assertSignOutputRaw(
ht, alice, targetPubKey, &signrpc.KeyDescriptor{
RawKeyBytes: keyDesc.RawKeyBytes,
}, txscript.SigHashAll,
)
// Now try again, this time only with the (0 index!) key locator.
assertSignOutputRaw(
ht, alice, targetPubKey, &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: keyDesc.KeyLoc.KeyFamily,
KeyIndex: keyDesc.KeyLoc.KeyIndex,
},
}, txscript.SigHashAll,
)
// And now test everything again with a new key where we know the index
// is not 0.
req = &walletrpc.KeyReq{
KeyFamily: testCustomKeyFamily,
}
keyDesc = alice.RPC.DeriveNextKey(req)
require.Equal(ht, int32(1), keyDesc.KeyLoc.KeyIndex)
targetPubKey, err = btcec.ParsePubKey(keyDesc.RawKeyBytes)
require.NoError(ht, err)
// First, try with a key descriptor that only sets the public key.
assertSignOutputRaw(
ht, alice, targetPubKey, &signrpc.KeyDescriptor{
RawKeyBytes: keyDesc.RawKeyBytes,
}, txscript.SigHashAll,
)
// Now try again, this time only with the key locator.
assertSignOutputRaw(
ht, alice, targetPubKey, &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: keyDesc.KeyLoc.KeyFamily,
KeyIndex: keyDesc.KeyLoc.KeyIndex,
},
}, txscript.SigHashAll,
)
// Finally, we'll try again, but this time with a non-default sighash.
assertSignOutputRaw(
ht, alice, targetPubKey, &signrpc.KeyDescriptor{
KeyLoc: &signrpc.KeyLocator{
KeyFamily: keyDesc.KeyLoc.KeyFamily,
KeyIndex: keyDesc.KeyLoc.KeyIndex,
},
}, txscript.SigHashSingle,
)
}
// assertSignOutputRaw sends coins to a p2wkh address derived from the given
// target public key and then tries to spend that output again by invoking the
// SignOutputRaw RPC with the key descriptor provided.
func assertSignOutputRaw(ht *lntemp.HarnessTest,
alice *node.HarnessNode, targetPubKey *btcec.PublicKey,
keyDesc *signrpc.KeyDescriptor,
sigHash txscript.SigHashType) {
pubKeyHash := btcutil.Hash160(targetPubKey.SerializeCompressed())
targetAddr, err := btcutil.NewAddressWitnessPubKeyHash(
pubKeyHash, harnessNetParams,
)
require.NoError(ht, err)
targetScript, err := txscript.PayToAddrScript(targetAddr)
require.NoError(ht, err)
// Send some coins to the generated p2wpkh address.
req := &lnrpc.SendCoinsRequest{
Addr: targetAddr.String(),
Amount: 800_000,
}
alice.RPC.SendCoins(req)
// Wait until the TX is found in the mempool.
txid := ht.Miner.AssertNumTxsInMempool(1)[0]
targetOutputIndex := ht.GetOutputIndex(txid, targetAddr.String())
// Clear the mempool.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Try to spend the output now to a new p2wkh address.
addrReq := &lnrpc.NewAddressRequest{Type: AddrTypeWitnessPubkeyHash}
p2wkhResp := alice.RPC.NewAddress(addrReq)
p2wkhAdrr, err := btcutil.DecodeAddress(
p2wkhResp.Address, harnessNetParams,
)
require.NoError(ht, err)
p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAdrr)
require.NoError(ht, err)
tx := wire.NewMsgTx(2)
tx.TxIn = []*wire.TxIn{{
PreviousOutPoint: wire.OutPoint{
Hash: *txid,
Index: uint32(targetOutputIndex),
},
}}
value := int64(800_000 - 200)
tx.TxOut = []*wire.TxOut{{
PkScript: p2wkhPkScript,
Value: value,
}}
var buf bytes.Buffer
require.NoError(ht, tx.Serialize(&buf))
signReq := &signrpc.SignReq{
RawTxBytes: buf.Bytes(),
SignDescs: []*signrpc.SignDescriptor{{
Output: &signrpc.TxOut{
PkScript: targetScript,
Value: 800_000,
},
InputIndex: 0,
KeyDesc: keyDesc,
Sighash: uint32(sigHash),
WitnessScript: targetScript,
}},
}
signResp := alice.RPC.SignOutputRaw(signReq)
tx.TxIn[0].Witness = wire.TxWitness{
append(signResp.RawSigs[0], byte(sigHash)),
targetPubKey.SerializeCompressed(),
}
buf.Reset()
require.NoError(ht, tx.Serialize(&buf))
alice.RPC.PublishTransaction(&walletrpc.Transaction{
TxHex: buf.Bytes(),
})
// Wait until the spending tx is found.
txid = ht.Miner.AssertNumTxsInMempool(1)[0]
p2wkhOutputIndex := ht.GetOutputIndex(txid, p2wkhAdrr.String())
op := &lnrpc.OutPoint{
TxidBytes: txid[:],
OutputIndex: uint32(p2wkhOutputIndex),
}
ht.AssertUTXOInWallet(alice, op, "")
// Mine another block to clean up the mempool and to make sure the
// spend tx is actually included in a block.
ht.MineBlocksAndAssertNumTxes(1, 1)
}
// testSignVerifyMessage makes sure that the SignMessage RPC can be used with
// all custom flags by verifying with VerifyMessage. Tests both ECDSA and
// Schnorr signatures.
func testSignVerifyMessage(ht *lntemp.HarnessTest) {
runSignVerifyMessage(ht, ht.Alice)
}
// runSignVerifyMessage makes sure that the SignMessage RPC can be used with
// all custom flags by verifying with VerifyMessage. Tests both ECDSA and
// Schnorr signatures.
func runSignVerifyMessage(ht *lntemp.HarnessTest, alice *node.HarnessNode) {
aliceMsg := []byte("alice msg")
keyLoc := &signrpc.KeyLocator{
KeyFamily: int32(keychain.KeyFamilyNodeKey),
KeyIndex: 1,
}
// Sign a message with the default ECDSA.
signMsgReq := &signrpc.SignMessageReq{
Msg: aliceMsg,
KeyLoc: keyLoc,
SchnorrSig: false,
}
signMsgResp := alice.RPC.SignMessageSigner(signMsgReq)
deriveCustomizedKey := func() *btcec.PublicKey {
resp := alice.RPC.DeriveKey(keyLoc)
pub, err := btcec.ParsePubKey(resp.RawKeyBytes)
require.NoError(ht, err, "failed to parse node pubkey")
return pub
}
customPubKey := deriveCustomizedKey()
verifyReq := &signrpc.VerifyMessageReq{
Msg: aliceMsg,
Signature: signMsgResp.Signature,
Pubkey: customPubKey.SerializeCompressed(),
IsSchnorrSig: false,
}
verifyResp := alice.RPC.VerifyMessageSigner(verifyReq)
require.True(ht, verifyResp.Valid, "failed to verify message")
// Use a different key locator.
keyLoc = &signrpc.KeyLocator{
KeyFamily: int32(keychain.KeyFamilyNodeKey),
KeyIndex: 2,
}
// Sign a message with Schnorr signature.
signMsgReq = &signrpc.SignMessageReq{
Msg: aliceMsg,
KeyLoc: keyLoc,
SchnorrSig: true,
}
signMsgResp = alice.RPC.SignMessageSigner(signMsgReq)
customPubKey = deriveCustomizedKey()
// Verify the Schnorr signature.
verifyReq = &signrpc.VerifyMessageReq{
Msg: aliceMsg,
Signature: signMsgResp.Signature,
Pubkey: schnorr.SerializePubKey(customPubKey),
IsSchnorrSig: true,
}
verifyResp = alice.RPC.VerifyMessageSigner(verifyReq)
require.True(ht, verifyResp.Valid, "failed to verify message")
// Also test that we can tweak a private key and verify the message
// against the tweaked public key.
tweakBytes := sha256.Sum256([]byte("some text"))
tweakedPubKey := txscript.ComputeTaprootOutputKey(
customPubKey, tweakBytes[:],
)
signMsgReq.SchnorrSigTapTweak = tweakBytes[:]
signMsgResp = alice.RPC.SignMessageSigner(signMsgReq)
verifyReq = &signrpc.VerifyMessageReq{
Msg: aliceMsg,
Signature: signMsgResp.Signature,
Pubkey: schnorr.SerializePubKey(tweakedPubKey),
IsSchnorrSig: true,
}
verifyResp = alice.RPC.VerifyMessageSigner(verifyReq)
require.True(ht, verifyResp.Valid, "failed to verify message")
}
-141
View File
@@ -1,141 +0,0 @@
package itest
import (
"bytes"
"encoding/hex"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/stretchr/testify/require"
)
func testSingleHopInvoice(ht *lntemp.HarnessTest) {
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel.
chanAmt := btcutil.Amount(100000)
alice, bob := ht.Alice, ht.Bob
cp := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// assertAmountPaid is a helper closure that asserts the amount paid by
// Alice and received by Bob are expected.
assertAmountPaid := func(expected int64) {
ht.AssertAmountPaid("alice -> bob", alice, cp, expected, 0)
ht.AssertAmountPaid("bob <- alice", bob, cp, 0, expected)
}
// Now that the channel is open, create an invoice for Bob which
// expects a payment of 1000 satoshis from Alice paid via a particular
// preimage.
const paymentAmt = 1000
preimage := bytes.Repeat([]byte("A"), 32)
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
invoiceResp := bob.RPC.AddInvoice(invoice)
// With the invoice for Bob added, send a payment towards Alice paying
// to the above generated invoice.
ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})
// Bob's invoice should now be found and marked as settled.
dbInvoice := bob.RPC.LookupInvoice(invoiceResp.RHash)
require.Equal(ht, lnrpc.Invoice_SETTLED, dbInvoice.State,
"bob's invoice should be marked as settled")
// With the payment completed all balance related stats should be
// properly updated.
assertAmountPaid(paymentAmt)
// Create another invoice for Bob, this time leaving off the preimage
// to one will be randomly generated. We'll test the proper
// encoding/decoding of the zpay32 payment requests.
invoice = &lnrpc.Invoice{
Memo: "test3",
Value: paymentAmt,
}
invoiceResp = bob.RPC.AddInvoice(invoice)
// Next send another payment, but this time using a zpay32 encoded
// invoice rather than manually specifying the payment details.
ht.CompletePaymentRequests(alice, []string{invoiceResp.PaymentRequest})
// The second payment should also have succeeded, with the balances
// being update accordingly.
assertAmountPaid(paymentAmt * 2)
// Next send a keysend payment.
keySendPreimage := lntypes.Preimage{3, 4, 5, 11}
keySendHash := keySendPreimage.Hash()
req := &routerrpc.SendPaymentRequest{
Dest: bob.PubKey[:],
Amt: paymentAmt,
FinalCltvDelta: 40,
PaymentHash: keySendHash[:],
DestCustomRecords: map[uint64][]byte{
record.KeySendType: keySendPreimage[:],
},
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
ht.SendPaymentAssertSettled(alice, req)
// The keysend payment should also have succeeded, with the balances
// being update accordingly.
assertAmountPaid(paymentAmt * 3)
// Assert that the invoice has the proper AMP fields set, since the
// legacy keysend payment should have been promoted into an AMP payment
// internally.
keysendInvoice := bob.RPC.LookupInvoice(keySendHash[:])
require.Len(ht, keysendInvoice.Htlcs, 1)
htlc := keysendInvoice.Htlcs[0]
require.Zero(ht, htlc.MppTotalAmtMsat)
require.Nil(ht, htlc.Amp)
// Now create an invoice and specify routing hints.
// We will test that the routing hints are encoded properly.
hintChannel := lnwire.ShortChannelID{BlockHeight: 10}
bobPubKey := hex.EncodeToString(bob.PubKey[:])
hint := &lnrpc.HopHint{
NodeId: bobPubKey,
ChanId: hintChannel.ToUint64(),
FeeBaseMsat: 1,
FeeProportionalMillionths: 1000000,
CltvExpiryDelta: 20,
}
hints := []*lnrpc.RouteHint{{HopHints: []*lnrpc.HopHint{hint}}}
invoice = &lnrpc.Invoice{
Memo: "hints",
Value: paymentAmt,
RouteHints: hints,
}
invoiceResp = bob.RPC.AddInvoice(invoice)
payreq := bob.RPC.DecodePayReq(invoiceResp.PaymentRequest)
require.Len(ht, payreq.RouteHints, 1, "expected one routing hint")
routingHint := payreq.RouteHints[0]
require.Len(ht, routingHint.HopHints, 1, "expected one hop hint")
hopHint := routingHint.HopHints[0]
require.EqualValues(ht, 1000000, hopHint.FeeProportionalMillionths,
"wrong FeeProportionalMillionths")
require.Equal(ht, bobPubKey, hopHint.NodeId, "wrong NodeId")
require.Equal(ht, hintChannel.ToUint64(), hopHint.ChanId,
"wrong ChanId")
require.EqualValues(ht, 1, hopHint.FeeBaseMsat, "wrong FeeBaseMsat")
require.EqualValues(ht, 20, hopHint.CltvExpiryDelta,
"wrong CltvExpiryDelta")
ht.CloseChannel(alice, cp)
}
-506
View File
@@ -1,506 +0,0 @@
package itest
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/stretchr/testify/require"
)
const (
numPayments = 5
paymentAmt = 1000
baseFee = 1
)
// testSwitchCircuitPersistence creates a multihop network to ensure the sender
// and intermediaries are persisting their open payment circuits. After
// forwarding a packet via an outgoing link, all are restarted, and expected to
// forward a response back from the receiver once back online.
//
// The general flow of this test:
// 1. Carol --> Dave --> Alice --> Bob forward payment
// 2. -------X X X Bob restart sender and intermediaries
// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate
//
//nolint:dupword
func testSwitchCircuitPersistence(ht *lntemp.HarnessTest) {
// Setup our test scenario. We should now have four nodes running with
// three channels.
s := setupScenarioFourNodes(ht)
defer s.cleanUp()
// Restart the intermediaries and the sender.
ht.RestartNode(s.dave)
ht.RestartNode(s.alice)
ht.RestartNode(s.bob)
// Ensure all of the intermediate links are reconnected.
ht.EnsureConnected(s.alice, s.dave)
ht.EnsureConnected(s.bob, s.alice)
// Ensure all nodes in the network still have 5 outstanding htlcs.
s.assertHTLCs(ht, numPayments)
// Now restart carol without hodl mode, to settle back the outstanding
// payments.
s.carol.SetExtraArgs(nil)
ht.RestartNode(s.carol)
ht.EnsureConnected(s.dave, s.carol)
// After the payments settle, there should be no active htlcs on any of
// the nodes in the network.
s.assertHTLCs(ht, 0)
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Carol, the sink within
// the payment flow generated above. The order of asserts corresponds
// to increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
// David, Alice, Bob.
var amountPaid = int64(5000)
s.assertAmoutPaid(ht, amountPaid, numPayments)
// Lastly, we will send one more payment to ensure all channels are
// still functioning properly.
finalInvoice := &lnrpc.Invoice{
Memo: "testing",
Value: paymentAmt,
}
resp := s.carol.RPC.AddInvoice(finalInvoice)
payReqs := []string{resp.PaymentRequest}
// Using Carol as the source, pay to the 5 invoices from Bob created
// above.
ht.CompletePaymentRequests(s.bob, payReqs)
amountPaid = int64(6000)
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
}
// testSwitchOfflineDelivery constructs a set of multihop payments, and tests
// that the returning payments are not lost if a peer on the backwards path is
// offline when the settle/fails are received. We expect the payments to be
// buffered in memory, and transmitted as soon as the disconnect link comes back
// online.
//
// The general flow of this test:
// 1. Carol --> Dave --> Alice --> Bob forward payment
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
// 3. Carol --- Dave X Alice <-- Bob settle last hop
// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate
func testSwitchOfflineDelivery(ht *lntemp.HarnessTest) {
// Setup our test scenario. We should now have four nodes running with
// three channels.
s := setupScenarioFourNodes(ht)
defer s.cleanUp()
// First, disconnect Dave and Alice so that their link is broken.
ht.DisconnectNodes(s.dave, s.alice)
// Then, reconnect them to ensure Dave doesn't just fail back the htlc.
ht.ConnectNodes(s.dave, s.alice)
// Wait to ensure that the payment remain are not failed back after
// reconnecting. All node should report the number payments initiated
// for the duration of the interval.
s.assertHTLCs(ht, numPayments)
// Now, disconnect Dave from Alice again before settling back the
// payment.
ht.DisconnectNodes(s.dave, s.alice)
// Now restart carol without hodl mode, to settle back the outstanding
// payments.
s.carol.SetExtraArgs(nil)
ht.RestartNode(s.carol)
// Wait for Carol to report no outstanding htlcs.
ht.AssertNumActiveHtlcs(s.carol, 0)
// Now that the settles have reached Dave, reconnect him with Alice,
// allowing the settles to return to the sender.
ht.EnsureConnected(s.dave, s.alice)
// Wait until all outstanding htlcs in the network have been settled.
s.assertHTLCs(ht, 0)
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
const baseFee = 1
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Carol, the sink within the
// payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
// David, Alice, Bob.
var amountPaid = int64(5000)
s.assertAmoutPaid(ht, amountPaid, numPayments)
// Lastly, we will send one more payment to ensure all channels are
// still functioning properly.
finalInvoice := &lnrpc.Invoice{
Memo: "testing",
Value: paymentAmt,
}
resp := s.carol.RPC.AddInvoice(finalInvoice)
payReqs := []string{resp.PaymentRequest}
// Using Carol as the source, pay to the 5 invoices from Bob created
// above.
ht.CompletePaymentRequests(s.bob, payReqs)
amountPaid = int64(6000)
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
}
// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments,
// and tests that the returning payments are not lost if a peer on the backwards
// path is offline when the settle/fails are received AND the peer buffering the
// responses is completely restarts. We expect the payments to be reloaded from
// disk, and transmitted as soon as the intermediaries are reconnected.
//
// The general flow of this test:
// 1. Carol --> Dave --> Alice --> Bob forward payment
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
// 3. Carol --- Dave X Alice <-- Bob settle last hop
// 4. Carol --- Dave X X Bob restart Alice
// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate
func testSwitchOfflineDeliveryPersistence(ht *lntemp.HarnessTest) {
// Setup our test scenario. We should now have four nodes running with
// three channels.
s := setupScenarioFourNodes(ht)
defer s.cleanUp()
// Disconnect the two intermediaries, Alice and Dave, by shutting down
// Alice.
restartAlice := ht.SuspendNode(s.alice)
// Now restart carol without hodl mode, to settle back the outstanding
// payments.
s.carol.SetExtraArgs(nil)
ht.RestartNode(s.carol)
// Make Carol and Dave are reconnected before waiting for the htlcs to
// clear.
ht.EnsureConnected(s.dave, s.carol)
// Wait for Carol to report no outstanding htlcs, and also for Dave to
// receive all the settles from Carol.
ht.AssertNumActiveHtlcs(s.carol, 0)
// As an intermediate node, Dave should now have zero outgoing HTLCs
// and 5 incoming HTLCs from Alice.
ht.AssertNumActiveHtlcs(s.dave, numPayments)
// Finally, restart dave who received the settles, but was unable to
// deliver them to Alice since they were disconnected.
ht.RestartNode(s.dave)
require.NoError(ht, restartAlice(), "restart alice failed")
// Force Dave and Alice to reconnect before waiting for the htlcs to
// clear.
ht.EnsureConnected(s.dave, s.alice)
// After reconnection succeeds, the settles should be propagated all
// the way back to the sender. All nodes should report no active htlcs.
s.assertHTLCs(ht, 0)
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
// At this point all the channels within our proto network should be
// shifted by 5k satoshis in the direction of Carol, the sink within the
// payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Bob->Alice->David->Carol, order is Carol,
// David, Alice, Bob.
var amountPaid = int64(5000)
s.assertAmoutPaid(ht, amountPaid, numPayments)
// Lastly, we will send one more payment to ensure all channels are
// still functioning properly.
finalInvoice := &lnrpc.Invoice{
Memo: "testing",
Value: paymentAmt,
}
resp := s.carol.RPC.AddInvoice(finalInvoice)
payReqs := []string{resp.PaymentRequest}
// Before completing the final payment request, ensure that the
// connection between Dave and Carol has been healed.
ht.EnsureConnected(s.dave, s.carol)
// Using Carol as the source, pay to the 5 invoices from Bob created
// above.
ht.CompletePaymentRequests(s.bob, payReqs)
amountPaid = int64(6000)
s.assertAmoutPaid(ht, amountPaid, numPayments+1)
}
// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop
// payments, and tests that the returning payments are not lost if a peer on
// the backwards path is offline when the settle/fails are received AND the
// peer buffering the responses is completely restarts. We expect the payments
// to be reloaded from disk, and transmitted as soon as the intermediaries are
// reconnected.
//
// The general flow of this test:
// 1. Carol --> Dave --> Alice --> Bob forward payment
// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries
// 3. Carol --- Dave X Alice <-- Bob settle last hop
// 4. Carol --- Dave X X shutdown Bob, restart Alice
// 5. Carol <-- Dave <-- Alice X expect settle to propagate
func testSwitchOfflineDeliveryOutgoingOffline(ht *lntemp.HarnessTest) {
// Setup our test scenario. We should now have four nodes running with
// three channels. Note that we won't call the cleanUp function here as
// we will manually stop the node Carol and her channel.
s := setupScenarioFourNodes(ht)
// Disconnect the two intermediaries, Alice and Dave, so that when carol
// restarts, the response will be held by Dave.
restartAlice := ht.SuspendNode(s.alice)
// Now restart carol without hodl mode, to settle back the outstanding
// payments.
s.carol.SetExtraArgs(nil)
ht.RestartNode(s.carol)
// Wait for Carol to report no outstanding htlcs.
ht.AssertNumActiveHtlcs(s.carol, 0)
// As an intermediate node, Dave should now have zero outgoing HTLCs
// and 5 incoming HTLCs from Alice.
ht.AssertNumActiveHtlcs(s.dave, numPayments)
// Now check that the total amount was transferred from Dave to Carol.
// The amount transferred should be exactly equal to the invoice total
// payment amount, 5k satsohis.
const amountPaid = int64(5000)
ht.AssertAmountPaid(
"Dave(local) => Carol(remote)", s.carol,
s.chanPointCarolDave, int64(0), amountPaid,
)
ht.AssertAmountPaid(
"Dave(local) => Carol(remote)", s.dave,
s.chanPointCarolDave, amountPaid, int64(0),
)
// Shutdown carol and leave her offline for the rest of the test. This
// is critical, as we wish to see if Dave can propragate settles even if
// the outgoing link is never revived.
ht.Shutdown(s.carol)
// Now restart Dave, ensuring he is both persisting the settles, and is
// able to reforward them to Alice after recovering from a restart.
ht.RestartNode(s.dave)
require.NoErrorf(ht, restartAlice(), "restart alice failed")
// Ensure that Dave is reconnected to Alice before waiting for the
// htlcs to clear.
ht.EnsureConnected(s.dave, s.alice)
// Since Carol has been shutdown permanently, we will wait until all
// other nodes in the network report no active htlcs.
ht.AssertNumActiveHtlcs(s.alice, 0)
ht.AssertNumActiveHtlcs(s.bob, 0)
ht.AssertNumActiveHtlcs(s.dave, 0)
// When asserting the amount of satoshis moved, we'll factor in the
// default base fee, as we didn't modify the fee structure when
// creating the seed nodes in the network.
// At this point, all channels (minus Carol, who is shutdown) should
// show a shift of 5k satoshis towards Carol. The order of asserts
// corresponds to increasing of time is needed to embed the HTLC in
// commitment transaction, in channel Bob->Alice->David, order is
// David, Alice, Bob.
ht.AssertAmountPaid(
"Alice(local) => Dave(remote)", s.dave, s.chanPointDaveAlice,
int64(0), amountPaid+(baseFee*numPayments),
)
ht.AssertAmountPaid(
"Alice(local) => Dave(remote)", s.alice, s.chanPointDaveAlice,
amountPaid+(baseFee*numPayments), int64(0),
)
ht.AssertAmountPaid(
"Bob(local) => Alice(remote)", s.alice, s.chanPointAliceBob,
int64(0), amountPaid+((baseFee*numPayments)*2),
)
ht.AssertAmountPaid(
"Bob(local) => Alice(remote)", s.bob, s.chanPointAliceBob,
amountPaid+(baseFee*numPayments)*2, int64(0),
)
ht.CloseChannel(s.alice, s.chanPointAliceBob)
ht.CloseChannel(s.dave, s.chanPointDaveAlice)
}
// scenarioFourNodes specifies a scenario which we have a topology that has
// four nodes and three channels.
type scenarioFourNodes struct {
alice *node.HarnessNode
bob *node.HarnessNode
carol *node.HarnessNode
dave *node.HarnessNode
chanPointAliceBob *lnrpc.ChannelPoint
chanPointCarolDave *lnrpc.ChannelPoint
chanPointDaveAlice *lnrpc.ChannelPoint
cleanUp func()
}
// setupScenarioFourNodes creates a topology for switch tests. It will create
// two new nodes: Carol and Dave, such that there will be a 4 nodes, 3 channel
// topology. Dave will make a channel with Alice, and Carol with Dave. After
// this setup, the network topology should now look like:
//
// Carol -> Dave -> Alice -> Bob
//
// Once the network is created, Carol will generate 5 invoices and Bob will pay
// them using the above path.
//
// NOTE: caller needs to call cleanUp to clean the nodes and channels created
// from this setup.
func setupScenarioFourNodes(ht *lntemp.HarnessTest) *scenarioFourNodes {
const (
chanAmt = btcutil.Amount(1000000)
pushAmt = btcutil.Amount(900000)
)
params := lntemp.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
}
// Grab the standby nodes.
alice, bob := ht.Alice, ht.Bob
// As preliminary setup, we'll create two new nodes: Carol and Dave,
// such that we now have a 4 node, 3 channel topology. Dave will make
// a channel with Alice, and Carol with Dave. After this setup, the
// network topology should now look like:
// Carol -> Dave -> Alice -> Bob
//
// First, we'll create Dave and establish a channel to Alice.
dave := ht.NewNode("Dave", nil)
ht.ConnectNodes(dave, alice)
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
// Next, we'll create Carol and establish a channel to from her to
// Dave. Carol is started in htlchodl mode so that we can disconnect
// the intermediary hops before starting the settle.
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
ht.ConnectNodes(carol, dave)
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Open channels in batch to save blocks mined.
reqs := []*lntemp.OpenChannelRequest{
{Local: alice, Remote: bob, Param: params},
{Local: dave, Remote: alice, Param: params},
{Local: carol, Remote: dave, Param: params},
}
resp := ht.OpenMultiChannelsAsync(reqs)
// Wait for all nodes to have seen all channels.
nodes := []*node.HarnessNode{alice, bob, carol, dave}
for _, chanPoint := range resp {
for _, node := range nodes {
ht.AssertTopologyChannelOpen(node, chanPoint)
}
}
chanPointAliceBob := resp[0]
chanPointDaveAlice := resp[1]
chanPointCarolDave := resp[2]
// Create 5 invoices for Carol, which expect a payment from Bob for 1k
// satoshis with a different preimage each time.
payReqs, _, _ := ht.CreatePayReqs(carol, paymentAmt, numPayments)
// Using Carol as the source, pay to the 5 invoices from Bob created
// above.
ht.CompletePaymentRequestsNoWait(bob, payReqs, chanPointAliceBob)
// Create a cleanUp to wipe the states.
cleanUp := func() {
if ht.Failed() {
ht.Skip("Skipped cleanup for failed test")
return
}
ht.CloseChannel(alice, chanPointAliceBob)
ht.CloseChannel(dave, chanPointDaveAlice)
ht.CloseChannel(carol, chanPointCarolDave)
}
s := &scenarioFourNodes{
alice, bob, carol, dave, chanPointAliceBob,
chanPointCarolDave, chanPointDaveAlice, cleanUp,
}
// Wait until all nodes in the network have 5 outstanding htlcs.
s.assertHTLCs(ht, numPayments)
return s
}
// assertHTLCs is a helper function which asserts the desired num of
// HTLCs has been seen in the nodes.
func (s *scenarioFourNodes) assertHTLCs(ht *lntemp.HarnessTest, num int) {
// Alice should have both the same number of outgoing and
// incoming HTLCs.
ht.AssertNumActiveHtlcs(s.alice, num*2)
// Bob should have num of incoming HTLCs.
ht.AssertNumActiveHtlcs(s.bob, num)
// Dave should have both the same number of outgoing and
// incoming HTLCs.
ht.AssertNumActiveHtlcs(s.dave, num*2)
// Carol should have the num of outgoing HTLCs.
ht.AssertNumActiveHtlcs(s.carol, num)
}
// assertAmoutPaid is a helper method which takes a given paid amount
// and number of payments and asserts the desired payments are made in
// the four nodes.
func (s *scenarioFourNodes) assertAmoutPaid(ht *lntemp.HarnessTest,
amt int64, num int64) {
ht.AssertAmountPaid(
"Dave(local) => Carol(remote)", s.carol,
s.chanPointCarolDave, int64(0), amt,
)
ht.AssertAmountPaid(
"Dave(local) => Carol(remote)", s.dave,
s.chanPointCarolDave, amt, int64(0),
)
ht.AssertAmountPaid(
"Alice(local) => Dave(remote)", s.dave,
s.chanPointDaveAlice,
int64(0), amt+(baseFee*num),
)
ht.AssertAmountPaid(
"Alice(local) => Dave(remote)", s.alice,
s.chanPointDaveAlice,
amt+(baseFee*num), int64(0),
)
ht.AssertAmountPaid(
"Bob(local) => Alice(remote)", s.alice,
s.chanPointAliceBob,
int64(0), amt+((baseFee*num)*2),
)
ht.AssertAmountPaid(
"Bob(local) => Alice(remote)", s.bob,
s.chanPointAliceBob,
amt+(baseFee*num)*2, int64(0),
)
}
File diff suppressed because it is too large. Load diff
-197
View File
@@ -1,197 +0,0 @@
package itest
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/grpclog"
)
const (
// defaultSplitTranches is the default number of tranches we split the
// test cases into.
defaultSplitTranches uint = 1
// defaultRunTranche is the default index of the test cases tranche that
// we run.
defaultRunTranche uint = 0
)
var (
// testCasesSplitParts is the number of tranches the test cases should
// be split into. By default this is set to 1, so no splitting happens.
// If this value is increased, then the -runtranche flag must be
// specified as well to indicate which part should be run in the current
// invocation.
testCasesSplitTranches = flag.Uint(
"splittranches", defaultSplitTranches, "split the test cases "+
"in this many tranches and run the tranche at "+
"0-based index specified by the -runtranche flag",
)
// testCasesRunTranche is the 0-based index of the split test cases
// tranche to run in the current invocation.
testCasesRunTranche = flag.Uint(
"runtranche", defaultRunTranche, "run the tranche of the "+
"split test cases with the given (0-based) index",
)
// dbBackendFlag specifies the backend to use.
dbBackendFlag = flag.String("dbbackend", "bbolt", "Database backend "+
"(bbolt, etcd, postgres)")
)
// TestLightningNetworkDaemon performs a series of integration tests amongst a
// programmatically driven network of lnd nodes.
func TestLightningNetworkDaemon(t *testing.T) {
// If no tests are registered, then we can exit early.
if len(allTestCases) == 0 {
t.Skip("integration tests not selected with flag 'rpctest'")
}
// Get the test cases to be run in this tranche.
testCases, trancheIndex, trancheOffset := getTestCaseSplitTranche()
lntest.ApplyPortOffset(uint32(trancheIndex) * 1000)
// Create a simple fee service.
feeService := lntemp.NewFeeService(t)
// Get the binary path and setup the harness test.
binary := getLndBinary(t)
harnessTest := lntemp.SetupHarness(
t, binary, *dbBackendFlag, feeService,
)
defer harnessTest.Stop()
// Setup standby nodes, Alice and Bob, which will be alive and shared
// among all the test cases.
harnessTest.SetupStandbyNodes()
// Run the subset of the test cases selected in this tranche.
for idx, testCase := range testCases {
testCase := testCase
name := fmt.Sprintf("tranche%02d/%02d-of-%d/%s/%s",
trancheIndex, trancheOffset+uint(idx)+1,
len(allTestCases), harnessTest.ChainBackendName(),
testCase.Name)
success := t.Run(name, func(t1 *testing.T) {
// Create a separate harness test for the testcase to
// avoid overwriting the external harness test that is
// tied to the parent test.
ht := harnessTest.Subtest(t1)
// TODO(yy): split log files.
cleanTestCaseName := strings.ReplaceAll(
testCase.Name, " ", "_",
)
ht.SetTestName(cleanTestCaseName)
logLine := fmt.Sprintf(
"STARTING ============ %v ============\n",
testCase.Name,
)
ht.Alice.AddToLogf(logLine)
ht.Bob.AddToLogf(logLine)
ht.EnsureConnected(ht.Alice, ht.Bob)
ht.RunTestCase(testCase)
})
// Stop at the first failure. Mimic behavior of original test
// framework.
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
t.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
_, height := harnessTest.Miner.GetBestBlock()
t.Logf("=========> tests finished for tranche: %v, tested %d "+
"cases, end height: %d\n", trancheIndex, len(testCases), height)
}
// getTestCaseSplitTranche returns the sub slice of the test cases that should
// be run as the current split tranche as well as the index and slice offset of
// the tranche.
func getTestCaseSplitTranche() ([]*lntemp.TestCase, uint, uint) {
numTranches := defaultSplitTranches
if testCasesSplitTranches != nil {
numTranches = *testCasesSplitTranches
}
runTranche := defaultRunTranche
if testCasesRunTranche != nil {
runTranche = *testCasesRunTranche
}
// There's a special flake-hunt mode where we run the same test multiple
// times in parallel. In that case the tranche index is equal to the
// thread ID, but we need to actually run all tests for the regex
// selection to work.
threadID := runTranche
if numTranches == 1 {
runTranche = 0
}
numCases := uint(len(allTestCases))
testsPerTranche := numCases / numTranches
trancheOffset := runTranche * testsPerTranche
trancheEnd := trancheOffset + testsPerTranche
if trancheEnd > numCases || runTranche == numTranches-1 {
trancheEnd = numCases
}
return allTestCases[trancheOffset:trancheEnd], threadID,
trancheOffset
}
func getLndBinary(t *testing.T) string {
binary := itestLndBinary
lndExec := ""
if lndExecutable != nil && *lndExecutable != "" {
lndExec = *lndExecutable
}
if lndExec == "" && runtime.GOOS == "windows" {
// Windows (even in a bash like environment like git bash as on
// Travis) doesn't seem to like relative paths to exe files...
currentDir, err := os.Getwd()
require.NoError(t, err, "unable to get working directory")
targetPath := filepath.Join(currentDir, "../../lnd-itest.exe")
binary, err = filepath.Abs(targetPath)
require.NoError(t, err, "unable to get absolute path")
} else if lndExec != "" {
binary = lndExec
}
return binary
}
func init() {
// Before we start any node, we need to make sure that any btcd node
// that is started through the RPC harness uses a unique port as well
// to avoid any port collisions.
rpctest.ListenAddressGenerator = lntest.GenerateBtcdListenerAddresses
// Swap out grpc's default logger with out fake logger which drops the
// statements on the floor.
fakeLogger := grpclog.NewLoggerV2(io.Discard, io.Discard, io.Discard)
grpclog.SetLoggerV2(fakeLogger)
}
-89
View File
@@ -1,89 +0,0 @@
package itest
import (
"encoding/hex"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/stretchr/testify/require"
)
// testTrackPayments tests whether a client that calls the TrackPayments api
// receives payment updates.
func testTrackPayments(ht *lntemp.HarnessTest) {
// Open a channel between alice and bob.
alice, bob := ht.Alice, ht.Bob
channel := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{
Amt: btcutil.Amount(300000),
},
)
// Call the TrackPayments api to listen for payment updates.
req := &routerrpc.TrackPaymentsRequest{
NoInflightUpdates: false,
}
tracker := alice.RPC.TrackPayments(req)
// Create an invoice from bob.
var amountMsat int64 = 1000
invoiceResp := bob.RPC.AddInvoice(
&lnrpc.Invoice{
ValueMsat: amountMsat,
},
)
invoice := bob.RPC.LookupInvoice(invoiceResp.RHash)
// Send payment from alice to bob.
paymentClient := alice.RPC.SendPayment(
&routerrpc.SendPaymentRequest{
PaymentRequest: invoice.PaymentRequest,
TimeoutSeconds: 60,
},
)
// Make sure the payment doesn't error due to invalid parameters or so.
_, err := paymentClient.Recv()
require.NoError(ht, err, "unable to get payment update.")
// Assert the first payment update is an inflight update.
update1, err := tracker.Recv()
require.NoError(ht, err, "unable to receive payment update 1.")
require.Equal(
ht, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
update1.FailureReason,
)
require.Equal(ht, lnrpc.Payment_IN_FLIGHT, update1.Status)
require.Equal(ht, invoice.PaymentRequest, update1.PaymentRequest)
require.Equal(ht, amountMsat, update1.ValueMsat)
// Assert the second payment update is a payment success update.
update2, err := tracker.Recv()
require.NoError(ht, err, "unable to receive payment update 2.")
require.Equal(
ht, lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
update2.FailureReason,
)
require.Equal(ht, lnrpc.Payment_SUCCEEDED, update2.Status)
require.Equal(ht, invoice.PaymentRequest, update2.PaymentRequest)
require.Equal(ht, amountMsat, update2.ValueMsat)
require.Equal(
ht, hex.EncodeToString(invoice.RPreimage),
update2.PaymentPreimage,
)
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
ht.CloseChannel(alice, channel)
}
-781
View File
@@ -1,781 +0,0 @@
package itest
import (
"bytes"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntemp/node"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/stretchr/testify/require"
)
const (
defaultAccount = lnwallet.DefaultAccountName
defaultImportedAccount = waddrmgr.ImportedAddrAccountName
)
// walletToLNAddrType maps walletrpc.AddressType to lnrpc.AddressType.
func walletToLNAddrType(t *testing.T,
addrType walletrpc.AddressType) lnrpc.AddressType {
switch addrType {
case walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH,
walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
return lnrpc.AddressType_NESTED_PUBKEY_HASH
case walletrpc.AddressType_WITNESS_PUBKEY_HASH:
return lnrpc.AddressType_WITNESS_PUBKEY_HASH
case walletrpc.AddressType_TAPROOT_PUBKEY:
return lnrpc.AddressType_TAPROOT_PUBKEY
default:
t.Fatalf("unhandled addr type %v", addrType)
return 0
}
}
// newExternalAddr generates a new external address of an imported account for a
// pair of nodes, where one acts as the funder and the other as the signer.
func newExternalAddr(ht *lntemp.HarnessTest, funder, signer *node.HarnessNode,
importedAccount string, addrType walletrpc.AddressType) string {
// We'll generate a new address for Carol from Dave's node to receive
// and fund a new channel.
req := &lnrpc.NewAddressRequest{
Type: walletToLNAddrType(ht.T, addrType),
Account: importedAccount,
}
funderResp := funder.RPC.NewAddress(req)
// Carol also needs to generate the address for the sake of this test
// to be able to sign the channel funding input.
req = &lnrpc.NewAddressRequest{
Type: walletToLNAddrType(ht.T, addrType),
}
signerResp := signer.RPC.NewAddress(req)
// Sanity check that the generated addresses match.
require.Equal(ht, funderResp.Address, signerResp.Address)
assertExternalAddrType(ht.T, funderResp.Address, addrType)
return funderResp.Address
}
// assertExternalAddrType asserts that an external address generated for an
// imported account is of the expected type.
func assertExternalAddrType(t *testing.T, addrStr string,
accountAddrType walletrpc.AddressType) {
addr, err := btcutil.DecodeAddress(addrStr, harnessNetParams)
require.NoError(t, err)
switch accountAddrType {
case walletrpc.AddressType_WITNESS_PUBKEY_HASH:
require.IsType(t, addr, &btcutil.AddressWitnessPubKeyHash{})
case walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH,
walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
require.IsType(t, addr, &btcutil.AddressScriptHash{})
case walletrpc.AddressType_TAPROOT_PUBKEY:
require.IsType(t, addr, &btcutil.AddressTaproot{})
default:
t.Fatalf("unsupported account addr type %v", accountAddrType)
}
}
// assertOutputScriptType asserts that a transaction's output, indicated by the
// output with the given amount, has a script of the expected type. This assumes
// all transaction outputs have unique amounts.
func assertOutputScriptType(t *testing.T, expType txscript.ScriptClass,
tx *wire.MsgTx, outputAmt int64) {
for _, txOut := range tx.TxOut {
if txOut.Value != outputAmt {
continue
}
pkScript, err := txscript.ParsePkScript(txOut.PkScript)
require.NoError(t, err)
require.Equal(t, expType, pkScript.Class())
return
}
// No output with the given amount was found.
t.Fatalf("output with amount %v not found in transaction %v", outputAmt,
spew.Sdump(tx))
}
// psbtSendFromImportedAccount attempts to fund a PSBT from the given imported
// account, originating from the source node to the destination.
func psbtSendFromImportedAccount(ht *lntemp.HarnessTest, srcNode, destNode,
signer *node.HarnessNode, account string,
accountAddrType walletrpc.AddressType) {
balanceResp := srcNode.RPC.WalletBalance()
require.Contains(ht, balanceResp.AccountBalance, account)
confBalance := balanceResp.AccountBalance[account].ConfirmedBalance
destAmt := confBalance - 10000
destAddrResp := destNode.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
})
fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Raw{
Raw: &walletrpc.TxTemplate{
Outputs: map[string]uint64{
destAddrResp.Address: uint64(destAmt),
},
},
},
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: 1,
},
Account: account,
}
fundResp := srcNode.RPC.FundPsbt(fundReq)
// Have Carol sign the PSBT input since Dave doesn't have any private
// key information.
finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt,
}
finalizeResp := signer.RPC.FinalizePsbt(finalizeReq)
// With the PSBT signed, we can broadcast the resulting transaction.
publishReq := &walletrpc.Transaction{
TxHex: finalizeResp.RawFinalTx,
}
srcNode.RPC.PublishTransaction(publishReq)
// Carol's balance from Dave's perspective should update accordingly.
var (
expTxFee int64
expChangeScriptType txscript.ScriptClass
)
switch accountAddrType {
case walletrpc.AddressType_WITNESS_PUBKEY_HASH:
expTxFee = 141
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
case walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH:
if account != defaultImportedAccount {
expTxFee = 165
expChangeScriptType = txscript.ScriptHashTy
break
}
// Spends from the default NP2WKH imported account have the same
// fee rate as the hybrid address type since a NP2WKH input is
// spent and a P2WKH change output is created.
fallthrough
case walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
expTxFee = 164
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
case walletrpc.AddressType_TAPROOT_PUBKEY:
if account != defaultImportedAccount {
expTxFee = 143
expChangeScriptType = txscript.WitnessV1TaprootTy
break
}
// Spends from the default imported account fall back to a P2WKH
// change. We'll want to change that, but in a separate PR.
expTxFee = 131
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
default:
ht.Fatalf("unsupported addr type %v", accountAddrType)
}
changeUtxoAmt := confBalance - destAmt - expTxFee
// If the transaction was created from the default imported account,
// then any change produced is moved to the default wallet account.
accountWithBalance := account
if account == defaultImportedAccount {
accountWithBalance = defaultAccount
}
ht.AssertWalletAccountBalance(
srcNode, accountWithBalance, 0, changeUtxoAmt,
)
ht.MineBlocksAndAssertNumTxes(1, 1)
ht.AssertWalletAccountBalance(
srcNode, accountWithBalance, changeUtxoAmt, 0,
)
// Finally, assert that the transaction has the expected change address
// type based on the account.
var tx wire.MsgTx
err := tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx))
require.NoError(ht, err)
assertOutputScriptType(ht.T, expChangeScriptType, &tx, changeUtxoAmt)
}
// fundChanAndCloseFromImportedAccount attempts to a fund a channel from the
// given imported account, originating from the source node to the destination
// node. To ensure the channel is operational before closing it, a test payment
// is made. Several balance assertions are made along the way for the sake of
// correctness.
func fundChanAndCloseFromImportedAccount(ht *lntemp.HarnessTest, srcNode,
destNode, signer *node.HarnessNode, account string,
accountAddrType walletrpc.AddressType, utxoAmt, chanSize int64) {
// Retrieve the current confirmed balance to make some assertions later
// on.
balanceResp := srcNode.RPC.WalletBalance()
require.Contains(ht, balanceResp.AccountBalance, account)
accountConfBalance := balanceResp.
AccountBalance[account].ConfirmedBalance
defaultAccountConfBalance := balanceResp.
AccountBalance[defaultAccount].ConfirmedBalance
// Now, start the channel funding process. We'll need to connect both
// nodes first.
ht.EnsureConnected(srcNode, destNode)
// The source node will then fund the channel through a PSBT shim.
pendingChanID := ht.Random32Bytes()
chanUpdates, rawPsbt := ht.OpenChannelPsbt(
srcNode, destNode, lntemp.OpenChannelParams{
Amt: btcutil.Amount(chanSize),
FundingShim: &lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{
PendingChanId: pendingChanID,
},
},
},
},
)
fundReq := &walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_Psbt{
Psbt: rawPsbt,
},
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: 1,
},
Account: account,
}
fundResp := srcNode.RPC.FundPsbt(fundReq)
srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
PsbtVerify: &lnrpc.FundingPsbtVerify{
PendingChanId: pendingChanID,
FundedPsbt: fundResp.FundedPsbt,
},
},
})
// Now that we have a PSBT to fund the channel, our signer needs to sign
// it.
finalizeReq := &walletrpc.FinalizePsbtRequest{
FundedPsbt: fundResp.FundedPsbt,
}
finalizeResp := signer.RPC.FinalizePsbt(finalizeReq)
// The source node can then submit the signed PSBT and complete the
// channel funding process.
srcNode.RPC.FundingStateStep(&lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtFinalize{
PsbtFinalize: &lnrpc.FundingPsbtFinalize{
PendingChanId: pendingChanID,
SignedPsbt: finalizeResp.SignedPsbt,
},
},
})
// We should receive a notification of the channel funding transaction
// being broadcast.
updateResp := ht.ReceiveOpenChannelUpdate(chanUpdates)
upd, ok := updateResp.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
require.True(ht, ok)
// Mine enough blocks to announce the channel to the network, making
// balance assertions along the way.
var (
expChanTxFee int64
expChangeScriptType txscript.ScriptClass
)
switch accountAddrType {
case walletrpc.AddressType_WITNESS_PUBKEY_HASH:
expChanTxFee = 153
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
case walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH:
if account != defaultImportedAccount {
expChanTxFee = 177
expChangeScriptType = txscript.ScriptHashTy
break
}
// Spends from the default NP2WKH imported account have the same
// fee rate as the hybrid address type since a NP2WKH input is
// spent and a P2WKH change output is created.
fallthrough
case walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH:
expChanTxFee = 176
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
case walletrpc.AddressType_TAPROOT_PUBKEY:
if account != defaultImportedAccount {
expChanTxFee = 155
expChangeScriptType = txscript.WitnessV1TaprootTy
break
}
// Spends from the default imported account fall back to a P2WKH
// change. We'll want to change that, but in a separate PR.
expChanTxFee = 143
expChangeScriptType = txscript.WitnessV0PubKeyHashTy
default:
ht.Fatalf("unsupported addr type %v", accountAddrType)
}
chanChangeUtxoAmt := utxoAmt - chanSize - expChanTxFee
txHash, err := chainhash.NewHash(upd.ChanPending.Txid)
require.NoError(ht, err)
// If we're spending from the default imported account, then any change
// outputs produced are moved to the default wallet account, so we
// should expect to see balances there.
var confBalanceAfterChan int64
if account == defaultImportedAccount {
confBalanceAfterChan = defaultAccountConfBalance
ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
ht.AssertWalletAccountBalance(
srcNode, defaultAccount, defaultAccountConfBalance,
chanChangeUtxoAmt,
)
block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
ht.Miner.AssertTxInBlock(block, txHash)
confBalanceAfterChan += chanChangeUtxoAmt
ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
ht.AssertWalletAccountBalance(
srcNode, defaultAccount, confBalanceAfterChan, 0,
)
} else {
// Otherwise, all interactions remain within Carol's imported
// account.
confBalanceAfterChan = accountConfBalance - utxoAmt
ht.AssertWalletAccountBalance(
srcNode, account, confBalanceAfterChan,
chanChangeUtxoAmt,
)
block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
ht.Miner.AssertTxInBlock(block, txHash)
confBalanceAfterChan += chanChangeUtxoAmt
ht.AssertWalletAccountBalance(
srcNode, account, confBalanceAfterChan, 0,
)
}
// Assert that the transaction has the expected change address type
// based on the account.
var tx wire.MsgTx
err = tx.Deserialize(bytes.NewReader(finalizeResp.RawFinalTx))
require.NoError(ht, err)
assertOutputScriptType(
ht.T, expChangeScriptType, &tx, chanChangeUtxoAmt,
)
// Wait for the channel to be announced by both parties.
chanPoint := &lnrpc.ChannelPoint{
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
FundingTxidBytes: upd.ChanPending.Txid,
},
OutputIndex: upd.ChanPending.OutputIndex,
}
ht.AssertTopologyChannelOpen(srcNode, chanPoint)
ht.AssertTopologyChannelOpen(destNode, chanPoint)
// Send a test payment to ensure the channel is operating as normal.
const invoiceAmt = 100000
invoice := &lnrpc.Invoice{
Memo: "psbt import chan",
Value: invoiceAmt,
}
resp := destNode.RPC.AddInvoice(invoice)
ht.CompletePaymentRequests(srcNode, []string{resp.PaymentRequest})
// TODO(yy): remove the sleep once the following bug is fixed. When the
// payment is reported as settled by srcNode, it's expected the
// commitment dance is finished and all subsequent states have been
// updated. Yet we'd receive the error `cannot co-op close channel with
// active htlcs` or `link failed to shutdown` if we close the channel.
// We need to investigate the order of settling the payments and
// updating commitments to understand and fix .
time.Sleep(2 * time.Second)
// Now that we've confirmed the opened channel works, we'll close it.
ht.CloseChannel(srcNode, chanPoint)
// Since the channel still had funds left on the source node's side,
// they must've been redeemed after the close. Without a pre-negotiated
// close address, the funds will go into the source node's wallet
// instead of the imported account.
const chanCloseTxFee = 9650
balanceFromClosedChan := chanSize - invoiceAmt - chanCloseTxFee
if account == defaultImportedAccount {
ht.AssertWalletAccountBalance(srcNode, account, 0, 0)
ht.AssertWalletAccountBalance(
srcNode, defaultAccount,
confBalanceAfterChan+balanceFromClosedChan, 0,
)
} else {
ht.AssertWalletAccountBalance(
srcNode, account, confBalanceAfterChan, 0,
)
ht.AssertWalletAccountBalance(
srcNode, defaultAccount, balanceFromClosedChan, 0,
)
}
}
// testWalletImportAccount tests that an imported account can fund transactions
// and channels through PSBTs, by having one node (the one with the imported
// account) craft the transactions and another node act as the signer.
func testWalletImportAccount(ht *lntemp.HarnessTest) {
testCases := []struct {
name string
addrType walletrpc.AddressType
}{
{
name: "standard BIP-0049",
addrType: walletrpc.
AddressType_NESTED_WITNESS_PUBKEY_HASH,
},
{
name: "lnd BIP-0049 variant",
addrType: walletrpc.
AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH,
},
{
name: "standard BIP-0084",
addrType: walletrpc.AddressType_WITNESS_PUBKEY_HASH,
},
{
name: "standard BIP-0086",
addrType: walletrpc.AddressType_TAPROOT_PUBKEY,
},
}
for _, tc := range testCases {
tc := tc
success := ht.Run(tc.name, func(tt *testing.T) {
testFunc := func(ht *lntemp.HarnessTest) {
testWalletImportAccountScenario(
ht, tc.addrType,
)
}
st := ht.Subtest(tt)
st.RunTestCase(&lntemp.TestCase{
Name: tc.name,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testWalletImportAccountScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType) {
// We'll start our test by having two nodes, Carol and Dave. Carol's
// default wallet account will be imported into Dave's node.
//
// NOTE: we won't use standby nodes here since the test will change
// each of the node's wallet state.
carol := ht.NewNode("carol", nil)
dave := ht.NewNode("dave", nil)
runWalletImportAccountScenario(ht, addrType, carol, dave)
}
func runWalletImportAccountScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType, carol, dave *node.HarnessNode) {
const utxoAmt int64 = btcutil.SatoshiPerBitcoin
listReq := &walletrpc.ListAccountsRequest{
Name: "default",
AddressType: addrType,
}
listResp := carol.RPC.ListAccounts(listReq)
require.Len(ht, listResp.Accounts, 1)
carolAccount := listResp.Accounts[0]
const importedAccount = "carol"
importReq := &walletrpc.ImportAccountRequest{
Name: importedAccount,
ExtendedPublicKey: carolAccount.ExtendedPublicKey,
AddressType: addrType,
}
dave.RPC.ImportAccount(importReq)
// We'll generate an address for Carol from Dave's node to receive some
// funds.
externalAddr := newExternalAddr(
ht, dave, carol, importedAccount, addrType,
)
// Send coins to Carol's address and confirm them, making sure the
// balance updates accordingly.
alice := ht.Alice
req := &lnrpc.SendCoinsRequest{
Addr: externalAddr,
Amount: utxoAmt,
SatPerByte: 1,
}
alice.RPC.SendCoins(req)
ht.AssertWalletAccountBalance(dave, importedAccount, 0, utxoAmt)
ht.MineBlocksAndAssertNumTxes(1, 1)
ht.AssertWalletAccountBalance(dave, importedAccount, utxoAmt, 0)
// To ensure that Dave can use Carol's account as watch-only, we'll
// construct a PSBT that sends funds to Alice, which we'll then hand
// over to Carol to sign.
psbtSendFromImportedAccount(
ht, dave, alice, carol, importedAccount, addrType,
)
// We'll generate a new address for Carol from Dave's node to receive
// and fund a new channel.
externalAddr = newExternalAddr(
ht, dave, carol, importedAccount, addrType,
)
// Retrieve the current confirmed balance of the imported account for
// some assertions we'll make later on.
balanceResp := dave.RPC.WalletBalance()
require.Contains(ht, balanceResp.AccountBalance, importedAccount)
confBalance := balanceResp.AccountBalance[importedAccount].
ConfirmedBalance
// Send coins to Carol's address and confirm them, making sure the
// balance updates accordingly.
req = &lnrpc.SendCoinsRequest{
Addr: externalAddr,
Amount: utxoAmt,
SatPerByte: 1,
}
alice.RPC.SendCoins(req)
ht.AssertWalletAccountBalance(
dave, importedAccount, confBalance, utxoAmt,
)
ht.MineBlocksAndAssertNumTxes(1, 1)
ht.AssertWalletAccountBalance(
dave, importedAccount, confBalance+utxoAmt, 0,
)
// Now that we have enough funds, it's time to fund the channel, make a
// test payment, and close it. This contains several balance assertions
// along the way.
fundChanAndCloseFromImportedAccount(
ht, dave, alice, carol, importedAccount, addrType, utxoAmt,
int64(funding.MaxBtcFundingAmount),
)
}
// testWalletImportPubKey tests that an imported public keys can fund
// transactions and channels through PSBTs, by having one node (the one with the
// imported account) craft the transactions and another node act as the signer.
func testWalletImportPubKey(ht *lntemp.HarnessTest) {
testCases := []struct {
name string
addrType walletrpc.AddressType
}{
{
name: "BIP-0049",
addrType: walletrpc.
AddressType_NESTED_WITNESS_PUBKEY_HASH,
},
{
name: "BIP-0084",
addrType: walletrpc.AddressType_WITNESS_PUBKEY_HASH,
},
{
name: "BIP-0086",
addrType: walletrpc.AddressType_TAPROOT_PUBKEY,
},
}
for _, tc := range testCases {
tc := tc
success := ht.Run(tc.name, func(tt *testing.T) {
testFunc := func(ht *lntemp.HarnessTest) {
testWalletImportPubKeyScenario(
ht, tc.addrType,
)
}
st := ht.Subtest(tt)
st.RunTestCase(&lntemp.TestCase{
Name: tc.name,
TestFunc: testFunc,
})
})
if !success {
// Log failure time to help relate the lnd logs to the
// failure.
ht.Logf("Failure time: %v", time.Now().Format(
"2006-01-02 15:04:05.000",
))
break
}
}
}
func testWalletImportPubKeyScenario(ht *lntemp.HarnessTest,
addrType walletrpc.AddressType) {
const utxoAmt int64 = btcutil.SatoshiPerBitcoin
alice := ht.Alice
// We'll start our test by having two nodes, Carol and Dave.
//
// NOTE: we won't use standby nodes here since the test will change
// each of the node's wallet state.
carol := ht.NewNode("carol", nil)
dave := ht.NewNode("dave", nil)
// We'll define a helper closure that we'll use throughout the test to
// generate a new address of the given type from Carol's perspective,
// import it into Dave's wallet, and fund it.
importPubKey := func(keyIndex uint32, prevConfBalance,
prevUnconfBalance int64) {
// Retrieve Carol's account public key for the corresponding
// address type.
listReq := &walletrpc.ListAccountsRequest{
Name: "default",
AddressType: addrType,
}
listResp := carol.RPC.ListAccounts(listReq)
require.Len(ht, listResp.Accounts, 1)
p2wkhAccount := listResp.Accounts[0]
// Derive the external address at the given index.
accountPubKey, err := hdkeychain.NewKeyFromString(
p2wkhAccount.ExtendedPublicKey,
)
require.NoError(ht, err)
externalAccountExtKey, err := accountPubKey.Derive(0)
require.NoError(ht, err)
externalAddrExtKey, err := externalAccountExtKey.Derive(
keyIndex,
)
require.NoError(ht, err)
externalAddrPubKey, err := externalAddrExtKey.ECPubKey()
require.NoError(ht, err)
// Serialize as 32-byte x-only pubkey for Taproot addresses.
serializedPubKey := externalAddrPubKey.SerializeCompressed()
if addrType == walletrpc.AddressType_TAPROOT_PUBKEY {
serializedPubKey = schnorr.SerializePubKey(
externalAddrPubKey,
)
}
// Import the public key into Dave.
importReq := &walletrpc.ImportPublicKeyRequest{
PublicKey: serializedPubKey,
AddressType: addrType,
}
dave.RPC.ImportPublicKey(importReq)
// We'll also generate the same address for Carol, as it'll be
// required later when signing.
carolAddrResp := carol.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: walletToLNAddrType(ht.T, addrType),
})
// Send coins to Carol's address and confirm them, making sure
// the balance updates accordingly.
req := &lnrpc.SendCoinsRequest{
Addr: carolAddrResp.Address,
Amount: utxoAmt,
SatPerByte: 1,
}
alice.RPC.SendCoins(req)
ht.AssertWalletAccountBalance(
dave, defaultImportedAccount, prevConfBalance,
prevUnconfBalance+utxoAmt,
)
ht.MineBlocksAndAssertNumTxes(1, 1)
ht.AssertWalletAccountBalance(
dave, defaultImportedAccount,
prevConfBalance+utxoAmt, prevUnconfBalance,
)
}
// We'll have Carol generate a new external address, which we'll import
// into Dave.
importPubKey(0, 0, 0)
// To ensure that Dave can use Carol's public key as watch-only, we'll
// construct a PSBT that sends funds to Alice, which we'll then hand
// over to Carol to sign.
psbtSendFromImportedAccount(
ht, dave, alice, carol, defaultImportedAccount, addrType,
)
// We'll now attempt to fund a channel.
//
// We'll have Carol generate another external address, which we'll
// import into Dave again.
balanceResp := dave.RPC.WalletBalance()
require.Contains(ht, balanceResp.AccountBalance, defaultImportedAccount)
confBalance := balanceResp.
AccountBalance[defaultImportedAccount].ConfirmedBalance
importPubKey(1, confBalance, 0)
// Now that we have enough funds, it's time to fund the channel, make a
// test payment, and close it. This contains several balance assertions
// along the way.
fundChanAndCloseFromImportedAccount(
ht, dave, alice, carol, defaultImportedAccount, addrType,
utxoAmt, int64(funding.MaxBtcFundingAmount),
)
}
-124
View File
@@ -1,124 +0,0 @@
package itest
import (
"time"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/stretchr/testify/require"
)
type pendingChan *lnrpc.PendingChannelsResponse_PendingChannel
// testWipeForwardingPackagesLocal tests that when a channel is closed, either
// through local force close, remote close, or coop close, all the forwarding
// packages of that particular channel are deleted. The test creates a
// topology as Alice -> Bob -> Carol, and sends payments from Alice to Carol
// through Bob. It then performs the test in two steps,
// - Bob force closes the channel Alice->Bob, and checks from both Bob's
// PoV(local force close) and Alice's Pov(remote close) that the forwarding
// packages are wiped.
// - Bob coop closes the channel Bob->Carol, and checks from both Bob PoVs
// that the forwarding packages are wiped.
func testWipeForwardingPackages(ht *lntemp.HarnessTest) {
const (
chanAmt = 10e6
paymentAmt = 10e4
finalCTLVDelta = chainreg.DefaultBitcoinTimeLockDelta
numInvoices = 3
)
// Grab Alice and Bob from HarnessTest.
alice, bob := ht.Alice, ht.Bob
// Create a new node Carol, which will create invoices that require
// Alice to pay.
carol := ht.NewNode("Carol", nil)
// Connect Bob to Carol.
ht.ConnectNodes(bob, carol)
// Open a channel between Alice and Bob.
chanPointAB := ht.OpenChannel(
alice, bob, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Open a channel between Bob and Carol.
chanPointBC := ht.OpenChannel(
bob, carol, lntemp.OpenChannelParams{Amt: chanAmt},
)
// Before we continue, make sure Alice has seen the channel between Bob
// and Carol.
ht.AssertTopologyChannelOpen(alice, chanPointBC)
// Alice sends several payments to Carol through Bob, which triggers
// Bob to create forwarding packages.
for i := 0; i < numInvoices; i++ {
// Add an invoice for Carol.
invoice := &lnrpc.Invoice{Memo: "testing", Value: paymentAmt}
resp := carol.RPC.AddInvoice(invoice)
// Alice sends a payment to Carol through Bob.
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
}
// TODO(yy): remove the sleep once the following bug is fixed.
// When the invoice is reported settled, the commitment dance is not
// yet finished, which can cause an error when closing the channel,
// saying there's active HTLCs. We need to investigate this issue and
// reverse the order to, first finish the commitment dance, then report
// the invoice as settled.
time.Sleep(2 * time.Second)
// Firstly, Bob force closes the channel.
ht.CloseChannelAssertPending(bob, chanPointAB, true)
// Now that the channel has been force closed, it should show up in
// bob's PendingChannels RPC under the waiting close section.
pendingAB := ht.AssertChannelWaitingClose(bob, chanPointAB).Channel
// Check that Bob has created forwarding packages. We don't care the
// exact number here as long as these packages are deleted when the
// channel is closed.
require.NotZero(ht, pendingAB.NumForwardingPackages)
// Secondly, Bob coop closes the channel.
ht.CloseChannelAssertPending(bob, chanPointBC, false)
// Now that the channel has been coop closed, it should show up in
// bob's PendingChannels RPC under the waiting close section.
pendingBC := ht.AssertChannelWaitingClose(bob, chanPointBC).Channel
// Check that Bob has created forwarding packages. We don't care the
// exact number here as long as these packages are deleted when the
// channel is closed.
require.NotZero(ht, pendingBC.NumForwardingPackages)
// Since it's a coop close, Carol should see the waiting close channel
// too.
pendingBC = ht.AssertChannelWaitingClose(carol, chanPointBC).Channel
require.NotZero(ht, pendingBC.NumForwardingPackages)
// Mine 1 block to get the two closing transactions confirmed.
ht.MineBlocksAndAssertNumTxes(1, 2)
// Now that the closing transaction is confirmed, the above waiting
// close channel should now become pending force closed channel.
pendingAB = ht.AssertChannelPendingForceClose(bob, chanPointAB).Channel
// Check the forwarding pacakges are deleted.
require.Zero(ht, pendingAB.NumForwardingPackages)
// For Alice, the forwarding packages should have been wiped too.
pending := ht.AssertChannelPendingForceClose(alice, chanPointAB)
pendingAB = pending.Channel
require.Zero(ht, pendingAB.NumForwardingPackages)
// Mine 1 block to get Alice's sweeping tx confirmed.
ht.MineBlocksAndAssertNumTxes(1, 1)
// Clean up the force closed channel.
ht.CleanupForceClose(bob, chanPointAB)
}
-52
View File
@@ -1,52 +0,0 @@
package itest
import (
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lnwallet"
)
// testWumboChannels tests that only a node that signals wumbo channel
// acceptances will allow a wumbo channel to be created. Additionally, if a
// node is running with mini channels only enabled, then they should reject any
// inbound wumbo channel requests.
func testWumboChannels(ht *lntemp.HarnessTest) {
// With all the channel types exercised, we'll now make sure the wumbo
// signalling support works properly.
//
// We'll make two new nodes, with one of them signalling support for
// wumbo channels while the other doesn't.
wumboNode := ht.NewNode("wumbo", []string{"--protocol.wumbo-channels"})
miniNode := ht.NewNode("mini", nil)
// We'll send coins to the wumbo node, as it'll be the one imitating
// the channel funding.
ht.FundCoins(btcutil.SatoshiPerBitcoin, wumboNode)
// Next we'll connect both nodes, then attempt to make a wumbo channel
// funding request to the mini node we created above. The wumbo request
// should fail as the node isn't advertising wumbo channels.
ht.EnsureConnected(wumboNode, miniNode)
chanAmt := funding.MaxBtcFundingAmount + 1
// The test should indicate a failure due to the channel being too
// large.
ht.OpenChannelAssertErr(
wumboNode, miniNode, lntemp.OpenChannelParams{Amt: chanAmt},
lnwallet.ErrChanTooLarge(chanAmt, funding.MaxBtcFundingAmount),
)
// We'll now make another wumbo node to accept our wumbo channel
// funding.
wumboNode2 := ht.NewNode(
"wumbo2", []string{"--protocol.wumbo-channels"},
)
// Creating a wumbo channel between these two nodes should succeed.
ht.EnsureConnected(wumboNode, wumboNode2)
chanPoint := ht.OpenChannel(
wumboNode, wumboNode2, lntemp.OpenChannelParams{Amt: chanAmt},
)
ht.CloseChannel(wumboNode, chanPoint)
}
File diff suppressed because it is too large. Load diff
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
BASEDIR=$(dirname "$0")
echo ""
# Filter all log files for errors, substitute variable data and match against whitelist.
find $BASEDIR -name "*.log" | xargs grep -h "\[ERR\]" | \
sed -r -f $BASEDIR/log_substitutions.txt | \
sort | uniq | \
grep -Fvi -f $BASEDIR/log_error_whitelist.txt
# If something shows up (not on whitelist) exit with error code 1.
if [[ $? -eq 0 ]]; then
echo ""
echo "In the itest logs, the log line (patterns) above were detected."
echo "[ERR] lines are generally reserved for internal errors."
echo "Resolve the issue by either changing the log level or adding an "
echo "exception to log_error_whitelist.txt"
echo ""
exit 1
fi
echo "No itest errors detected."
echo ""
-302
View File
@@ -1,302 +0,0 @@
<time> [ERR] BRAR: Unable to broadcast justice tx: Transaction rejected: output already spent
<time> [ERR] BRAR: Unable to check for spentness of outpoint=<chan_point>: TxNotifier is exiting
<time> [ERR] BRAR: Unable to register for conf for txid(<hex>): TxNotifier is exiting
<time> [ERR] BRAR: Unable to register for block notifications: chainntnfs: system interrupt while attempting to register for block epoch notification.
<time> [ERR] BTCN: Broadcast attempt failed: rejected by <ip>: replacement transaction <hex> has an insufficient absolute fee: needs <amt>, has <amt>
<time> [ERR] BTCN: Broadcast attempt failed: rejected by <ip>: replacement transaction <hex> has an insufficient fee rate: needs more than <amt>, has <amt>
<time> [ERR] BTCN: Broadcast attempt failed: rejected by <ip>: transaction already exists
<time> [ERR] BTCN: Can't accept connection: unable to accept connection from <ip>: chacha20poly1305: message authentication failed
<time> [ERR] BTCN: Can't accept connection: unable to accept connection from <ip>: EOF
<time> [ERR] BTCN: Can't accept connection: unable to accept connection from <ip>: read tcp <ip>-><ip>: i/o timeout
<time> [ERR] BTCN: Query failed with 0 out of 1 filters received
<time> [ERR] BTCN: unable to get filter for hash=<hex>, retrying: neutrino shutting down
<time> [ERR] BTCN: unable to get filter for hash=<hex>, retrying: unable to fetch cfilter
<time> [ERR] BTCN: Unable to process block connected (height=<height>, hash=<hex>): out of order block <hex>: expected PrevBlock <hex>, got <hex>
<time> [ERR] BTCN: Unknown connid=<id>
<time> [ERR] CHAC: Received an error: rpc error: code = Canceled desc = context canceled, shutting down
<time> [ERR] CHFT: Close channel <chan_point> unknown to store
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to advance state: channel not found
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to broadcast close tx: Transaction rejected: output already spent
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to force close: channel not found
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to mark commitment broadcasted: channel not found
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.commitSweepResolver: remote party swept utxo
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.commitSweepResolver: chainntnfs: system interrupt while attempting to register for block epoch notification.
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcOutgoingContestResolver: chain notifier shutting down
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcOutgoingContestResolver: chainntnfs: system interrupt while attempting to register for block epoch notification.
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcOutgoingContestResolver: resolver canceled
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcOutgoingContestResolver: the client has been shutdown
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcOutgoingContestResolver: unable to create pre-image from witness: invalid preimage length of 33, want 32
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcSuccessResolver: Transaction rejected: output already spent
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcTimeoutResolver: htlcswitch shutting down
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unable to progress *contractcourt.htlcTimeoutResolver: TxNotifier is exiting
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unexpected local commitment confirmed while in StateDefault
<time> [ERR] CNCT: ChannelArbitrator(<chan_point>): unexpected local on-chain channel close
<time> [ERR] CNCT: *contractcourt.commitSweepResolver(<chan_point>): unable to sweep input: remote party swept utxo
<time> [ERR] CNCT: Unable to advance state: channel not found
<time> [ERR] CNCT: unable to hand breached contract off to breachArbiter: server is shutting down
<time> [ERR] CNCT: unable to handle channel breach for chan_point=<chan_point>: server is shutting down
<time> [ERR] CNCT: Unable to handle known remote state: unable to handle channel breach for chan_point=<chan_point>: server is shutting down
<time> [ERR] CRTR: Channel update of ourselves received
<time> [ERR] CRTR: Error collecting result for shard <number> for payment <hex>: shard handler exiting
<time> [ERR] CRTR: Error encountered during rescan: rescan exited
<time> [ERR] CRTR: Failed sending attempt <number> for payment <hex> to switch: could not add downstream htlc
<time> [ERR] CRTR: Failed sending attempt <number> for payment <hex> to switch: insufficient bandwidth to route htlc
<time> [ERR] CRTR: Failed sending attempt <number> for payment <hex> to switch: UnknownNextPeer
<time> [ERR] CRTR: out of order block: expecting height=<height>, got height=<height>
<time> [ERR] CRTR: Payment <hex> failed: error
<time> [ERR] CRTR: Payment <hex> failed: incorrect_payment_details
<time> [ERR] CRTR: Payment <hex> failed: insufficient_balance
<time> [ERR] CRTR: Payment <hex> failed: no_route
<time> [ERR] CRTR: Payment <hex> failed: router shutting down
<time> [ERR] CRTR: Payment <hex> failed: timeout
<time> [ERR] CRTR: Resuming payment <hex> failed: error.
<time> [ERR] CRTR: Resuming payment <hex> failed: incorrect_payment_details.
<time> [ERR] CRTR: Resuming payment <hex> failed: no_route.
<time> [ERR] CRTR: Resuming payment <hex> failed: router shutting down.
<time> [ERR] CRTR: unable to add channel: edge not found
<time> [ERR] CRTR: Unable to retrieve channel by id: edge not found
<time> [ERR] DISC: channel announcement proof for short_chan_id=<cid> isn't valid: can't verify first bitcoin signature
<time> [ERR] DISC: router shutting down
<time> [ERR] DISC: unable add proof to the channel chanID=<hex>: edge marked as zombie
<time> [ERR] DISC: unable add proof to the channel chanID=<hex>: edge not found
<time> [ERR] DISC: unable to add channel: edge not found
<time> [ERR] DISC: Unable to reply to peer query: set tcp <ip>: use of closed network connection
<time> [ERR] DISC: Unable to reply to peer query: write tcp <ip>-><ip>: use of closed network connection
<time> [ERR] DISC: Unable to reply to peer query: write tcp <ip>-><ip>: write: broken pipe
<time> [ERR] DISC: Unable to reply to peer query: write tcp <ip>-><ip>: write: connection reset by peer
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=Number of pending channels exceed maximum
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=Synchronizing blockchain
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.001 BTC is below min chan size of 0.002 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=funding failed due to internal error
<time> [ERR] FNDG: Unable to add new channel <chan_point> with peer <hex>: canceled adding new channel
<time> [ERR] FNDG: Unable to add new channel <chan_point> with peer <hex>: peer exiting
<time> [ERR] FNDG: Unable to add new channel <chan_point> with peer <hex>: unable to get best block: the client has been shutdown
<time> [ERR] FNDG: Unable to advance pending state of ChannelPoint(<chan_point>): error waiting for funding confirmation for ChannelPoint(<chan_point>): epoch client shutting down
<time> [ERR] FNDG: Unable to advance pending state of ChannelPoint(<chan_point>): error waiting for funding confirmation for ChannelPoint(<chan_point>): funding manager shutting down
<time> [ERR] FNDG: Unable to advance pending state of ChannelPoint(<chan_point>): error waiting for funding confirmation for ChannelPoint(<chan_point>): waiting for fundingconfirmation failed
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: ChainNotifier shutting down, cannot complete funding flow for ChannelPoint(<chan_point>)
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: channel announcement proof for short_chan_id=<cid> isn't valid: can't verify first bitcoin signature
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: funding manager shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: gossiper is shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: router shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: unable add proof to the channel chanID=<hex>: edge marked as zombie
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: channel announcement failed: unable add proof to the channel chanID=<hex>: edge not found
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: unable to register for confirmation of ChannelPoint(<chan_point>): chain notifier shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): error sending channel announcement: unable to register for confirmation of ChannelPoint(<chan_point>): TxNotifier is exiting
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: error sending channel announcement: gossiper is shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: error sending channel announcement: router shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: error sending channel update: router shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed adding to router graph: funding manager shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): failed sending fundingLocked: funding manager shutting down
<time> [ERR] FNDG: Unable to advance state(<chan_point>): funding manager shutting down
<time> [ERR] FNDG: unable to cancel reservation: no active reservations for peer(<hex>)
<time> [ERR] FNDG: Unable to handle funding accept message for peer_key=<hex>, pending_chan_id=<hex>: aborting PSBT flow: user canceled funding
<time> [ERR] FNDG: unable to report short chan id: link <hex> not found
<time> [ERR] FNDG: Unable to send channel proof: channel announcement proof for short_chan_id=<cid> isn't valid: can't verify first bitcoin signature
<time> [ERR] FNDG: Unable to send channel proof: gossiper is shutting down
<time> [ERR] FNDG: Unable to send channel proof: unable add proof to the channel chanID=<hex>: edge marked as zombie
<time> [ERR] FNDG: Unable to send channel proof: unable add proof to the channel chanID=<hex>: edge not found
<time> [ERR] FNDG: Unable to send node announcement: gossiper is shutting down
<time> [ERR] FNDG: Unable to send node announcement: router shutting down
<time> [ERR] FNDG: unable to open channel to NodeKey(<hex>): remote canceled funding, possibly timed out: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.001 BTC is below min chan size of 0.002 BTC
<time> [ERR] HSWC: AmountBelowMinimum(amt=<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: ChannelPoint(<chan_point>): received error from peer: chan_id=<hex>, err=internal error with error: remote error
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: ChannelPoint(<chan_point>): received error from peer: chan_id=<hex>, err=invalid update with error: remote error
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: ChannelPoint(<chan_point>): received error from peer: chan_id=<hex>, err=sync error with error: remote error
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: ChannelPoint(<chan_point>): received error from peer: chan_id=<hex>, err=unable to resume channel, recovery required with error: remote error
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to handle upstream settle HTLC: Invalid payment preimage <hex> for hash <hex> with error: invalid update
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: ChannelPoint(<chan_point>) with CommitPoint(<hex>) had possible local commitment state data loss with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: possible remote commitment state data loss with error: sync error
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: Unable to send chan sync message for ChannelPoint(<chan_point>): peer exiting with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: unable to send chan sync message for ChannelPoint(<chan_point>): set tcp <ip>: use of closed network connection with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: unable to send chan sync message for ChannelPoint(<chan_point>): set tcp <ip>: use of closed network connection with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: Unable to send chan sync message for ChannelPoint(<chan_point>): write tcp <ip>-><ip>: use of closed network connection with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: Unable to send chan sync message for ChannelPoint(<chan_point>): write tcp <ip>-><ip>: write: broken pipe with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to synchronize channel states: unable to send chan sync message for ChannelPoint(<chan_point>): write tcp <ip>-><ip>: write: connection reset by peer with error: unable to resume channel, recovery required
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: unable to update commitment: link shutting down with error: internal error
<time> [ERR] HSWC: ChannelLink(<chan_point>): link failed, exiting htlcManager
<time> [ERR] HSWC: ChannelLink(<chan_point>): unable to cancel incoming HTLC for circuit-key=(Chan ID=<chan>, HTLC ID=0): HTLC with ID 0 has already been failed
<time> [ERR] HSWC: ChannelLink(<chan_point>): unable to decode onion hop iterator: TemporaryChannelFailure
<time> [ERR] HSWC: ChannelLink(<chan_point>): unable to update signals
<time> [ERR] HSWC: ChannelLink(<chan_point>): unhandled error while forwarding htlc packet over htlcswitch: AmountBelowMinimum(amt=4000 mSAT, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: ChannelLink(<chan_point>): unhandled error while forwarding htlc packet over htlcswitch: circuit has already been closed
<time> [ERR] HSWC: ChannelLink(<chan_point>): unhandled error while forwarding htlc packet over htlcswitch: insufficient bandwidth to route htlc
<time> [ERR] HSWC: ChannelLink(<chan_point>): unhandled error while forwarding htlc packet over htlcswitch: node configured to disallow forwards
<time> [ERR] HSWC: ChannelLink(<chan_point>): unhandled error while forwarding htlc packet over htlcswitch: UnknownNextPeer
<time> [ERR] HSWC: FeeInsufficient(htlc_amt==<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: insufficient bandwidth to route htlc
<time> [ERR] HSWC: Link <chan> not found
<time> [ERR] HSWC: Link <chan> policy for local forward not satisfied
<time> [ERR] HSWC: node configured to disallow forwards
<time> [ERR] HSWC: unable to de-obfuscate onion failure (hash=<hex>, pid=<pid>): invalid error length: expected 292 got 0
<time> [ERR] HSWC: unable to find target channel for HTLC fail: channel ID = <chan>, HTLC ID = <id>
<time> [ERR] HSWC: Unable to forward resolution msg: unable to find target channel for HTLC fail: channel ID = <chan>, HTLC ID = <id>
<time> [ERR] HSWC: unable to process onion packet: sphinx packet replay attempted
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: AmountBelowMinimum(amt=<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: circuit has already been closed
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: FeeInsufficient(htlc_amt==<amt>, update=(lnwire.ChannelUpdate) {
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: insufficient bandwidth to route htlc
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: node configured to disallow forwards
<time> [ERR] HSWC: Unhandled error while reforwarding htlc settle/fail over htlcswitch: UnknownNextPeer
<time> [ERR] HSWC: UnknownNextPeer
<time> [ERR] LNWL: ChannelPoint(<chan_point>): sync failed: remote believes our tail height is <height>, while we have <height>!
<time> [ERR] LNWL: ChannelPoint(<chan_point>): sync failed: remote's next commit height is <height>, while we believe it is <height>!
<time> [ERR] LNWL: ChannelPoint(<chan_point>): sync failed with local data loss: remote believes our tail height is <height>, while we have <height>!
<time> [ERR] LNWL: Neutrino rescan ended with error: rescan exited
<time> [ERR] LNWL: Notifying unmined tx notification (<hex>) while creating notification for blocks
<time> [ERR] LNWL: Rescan for <num> addresses failed: the client has been shutdown
<time> [ERR] LTND: Unable to lookup witness: no witnesses
<time> [ERR] NANN: Unable to retrieve chan status for Channel(<chan_point>): edge not found
<time> [ERR] NANN: Unable to retrieve chan status for Channel(<chan_point>): unable to extract ChannelUpdate for channel <chan_point>
<time> [ERR] NANN: Unable to sign update disabling channel(<chan_point>): edge not found
<time> [ERR] NTFN: chain notifier shutting down
<time> [ERR] NTFN: Error during rescan: rescan exited
<time> [ERR] NTFN: Failed getting UTXO: get utxo request cancelled
<time> [ERR] NTFN: Rescan to determine the spend details of <chan_point> failed: the client has been shutdown
<time> [ERR] NTFN: Unable to fetch block header: the client has been shutdown
<time> [ERR] NTFN: unable to find blockhash for height=<height>: -1: Block number out of range
<time> [ERR] NTFN: unable to get block: Post "http://<ip>": dial tcp <ip>: connect: connection refused
<time> [ERR] NTFN: unable to get block: Post "http://<ip>": dial tcp <ip>: connect: connection reset by peer
<time> [ERR] NTFN: unable to get block: Post "http://<ip>": read tcp <ip>-><ip>: read: connection reset by peer
<time> [ERR] NTFN: unable to get block: the client has been shutdown
<time> [ERR] NTFN: unable to get hash from block with height 790
<time> [ERR] NTFN: unable to get missed blocks: starting height <height> is greater than ending height <height>
<time> [ERR] NTFN: Unable to rewind chain from height <height> to height <height>: unable to find blockhash for disconnected height=<height>: -1: Block number out of range
<time> [ERR] NTFN: Unable to rewind chain from height <height> to height <height>: unable to find blockhash for disconnected height=<height>: -8: Block height out of range
<time> [ERR] NTNF: unable to get hash from block with height <height>
<time> [ERR] PEER: Allowed test error from <ip> (inbound): ReadMessage: unhandled command [sendaddrv2]
<time> [ERR] PEER: resend failed: unable to fetch channel sync messages for peer <hex>@<ip>: unable to find closed channel summary
<time> [ERR] PEER: unable to close channel, ChannelID(<hex>) is unknown
<time> [ERR] PEER: unable to force close link(<chan>): ChainArbitrator exiting
<time> [ERR] PEER: unable to force close link(<chan>): channel not found
<time> [ERR] PEER: unable to force close link(<chan>): unable to find arbitrator
<time> [ERR] PEER: unable to get best block: the client has been shutdown
<time> [ERR] PEER: unable to send msg to remote peer: peer exiting
<time> [ERR] PEER: unable to send msg to remote peer: write tcp <ip>-><ip>: write: broken pipe
<time> [ERR] PEER: unable to send msg to remote peer: write tcp <ip>-><ip>: write: connection reset by peer
<time> [ERR] RPCS: [/chainrpc.ChainNotifier/RegisterBlockEpochNtfn]: chain notifier shutting down
<time> [ERR] RPCS: [/chainrpc.ChainNotifier/RegisterBlockEpochNtfn]: context canceled
<time> [ERR] RPCS: [closechannel] unable to close ChannelPoint(<chan_point>): chain notifier shutting down
<time> [ERR] RPCS: [connectpeer]: error connecting to peer: already connected to peer: <hex>@<ip>
<time> [ERR] RPCS: [connectpeer]: error connecting to peer: dial tcp <ip>: i/o timeout
<time> [ERR] RPCS: [connectpeer]: error connecting to peer: dial tcp <ip>: i/o timeout
<time> [ERR] RPCS: [connectpeer]: error connecting to peer: read tcp <ip>-><ip>: i/o timeout
<time> [ERR] RPCS: Failed receiving from stream: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: Failed receiving from stream: rpc error: code = DeadlineExceeded desc = context deadline exceeded
<time> [ERR] RPCS: Failed sending error response: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: Failed sending error response: rpc error: code = Internal desc = transport: transport: the stream is done or WriteHeader was already called
<time> [ERR] RPCS: Failed sending response: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: Failed sending response: rpc error: code = Internal desc = transport: transport: the stream is done or WriteHeader was already called
<time> [ERR] RPCS: [/invoicesrpc.Invoices/SubscribeSingleInvoice]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/BatchOpenChannel]: batch funding failed: error batch opening channel, initial negotiation failed: remote canceled funding, possibly timed out: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.001 BTC is below min chan size of 0.002 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission action. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: invalid permission entity. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: permission list cannot be empty. specify at least one action/entity pair. supported actions are [read write generate], supported entities are [onchain offchain address message peers info invoices signer macaroon uri]
<time> [ERR] RPCS: [/lnrpc.Lightning/ChannelAcceptor]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot close channel with state: ChanStatusRestored
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=3059, (current_height=3055)
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: cannot co-op close frozen channel as initiator until height=<height>, (current_height=<height>)
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: chain notifier shutting down
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: must specify channel point in close channel
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: rpc error: code = DeadlineExceeded desc = context deadline exceeded
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: server is still in the process of starting
<time> [ERR] RPCS: [/lnrpc.Lightning/ConnectPeer]: already connected to peer: <hex>@<ip>
<time> [ERR] RPCS: [/lnrpc.Lightning/ConnectPeer]: dial tcp <ip>: i/o timeout
<time> [ERR] RPCS: [/lnrpc.Lightning/ConnectPeer]: dial tcp <ip>: i/o timeout
<time> [ERR] RPCS: [/lnrpc.Lightning/ConnectPeer]: read tcp <ip>-><ip>: i/o timeout
<time> [ERR] RPCS: [/lnrpc.Lightning/ConnectPeer]: server is still in the process of starting
<time> [ERR] RPCS: [/lnrpc.Lightning/DeleteMacaroonID]: the specified ID cannot be deleted
<time> [ERR] RPCS: [/lnrpc.Lightning/FundingStateStep]: pendingChanID(<hex>) already has intent registered
<time> [ERR] RPCS: [/lnrpc.Lightning/GetChanInfo]: edge marked as zombie
<time> [ERR] RPCS: [/lnrpc.Lightning/GetChanInfo]: edge not found
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: channels cannot be created before the wallet is fully synced
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=Number of pending channels exceed maximum
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=Synchronizing blockchain
<time> [ERR] RPCS: [/lnrpc.Lightning/PendingChannels]: unable to find arbitrator
<time> [ERR] RPCS: [/lnrpc.Lightning/SendCoins]: address: tb1qfc8fusa98jx8uvnhzavxccqlzvg749tvjw82tg is not valid for this network: regtest
<time> [ERR] RPCS: [/lnrpc.Lightning/SendCoins]: amount set while SendAll is active
<time> [ERR] RPCS: [/lnrpc.Lightning/SendCoins]: cannot send coins to pubkeys
<time> [ERR] RPCS: [/lnrpc.Lightning/SendCoins]: unknown address type
<time> [ERR] RPCS: [/lnrpc.Lightning/SendPayment]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SendPayment]: rpc error: code = DeadlineExceeded desc = context deadline exceeded
<time> [ERR] RPCS: [/lnrpc.Lightning/SendPayment]: rpc server shutting down
<time> [ERR] RPCS: [/lnrpc.Lightning/SendToRoute]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SendToRoute]: rpc error: code = DeadlineExceeded desc = context deadline exceeded
<time> [ERR] RPCS: [/lnrpc.Lightning/SendToRoute]: rpc server shutting down
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelEvents]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: ChannelRouter shutting down
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: router not started
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: rpc error: code = DeadlineExceeded desc = context deadline exceeded
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: rpc error: code = Internal desc = transport: transport: the stream is done or WriteHeader was already called
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeInvoices]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/routerrpc.Router/HtlcInterceptor]: rpc error: code = Canceled desc = context canceled
<time> [ERR] RPCS: [/routerrpc.Router/SendPayment]: routerrpc server shutting down
<time> [ERR] RPCS: [/routerrpc.Router/SendPaymentV2]: context canceled
<time> [ERR] RPCS: [/routerrpc.Router/SendPaymentV2]: context deadline exceeded
<time> [ERR] RPCS: [/routerrpc.Router/SendPaymentV2]: routerrpc server shutting down
<time> [ERR] RPCS: [/routerrpc.Router/SubscribeHtlcEvents]: context canceled
<time> [ERR] RPCS: [/routerrpc.Router/SubscribeHtlcEvents]: htlc event subscription terminated
<time> [ERR] RPCS: [/routerrpc.Router/SubscribeHtlcEvents]: context deadline exceeded
<time> [ERR] RPCS: [/routerrpc.Route<time> [INF] LTND: Listening on the p2p interface is disabled!
<time> [ERR] RPCS: [/signrpc.Signer/DeriveSharedKey]: must provide ephemeral pubkey
<time> [ERR] RPCS: [/signrpc.Signer/DeriveSharedKey]: use either key_desc or key_loc
<time> [ERR] RPCS: [/signrpc.Signer/DeriveSharedKey]: use either raw_key_bytes or key_index
<time> [ERR] RPCS: [/signrpc.Signer/DeriveSharedKey]: when setting key_desc the field key_desc.key_loc must also be set
<time> [ERR] RPCS: [/lnrpc.Lightning/BakeMacaroon]: permission denied
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: cannot retrieve macaroon: cannot get macaroon: root key with id doesn't exist
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: caveat "ipaddr 1.1.1.1" not satisfied: macaroon locked to different IP address
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: caveat "time-before <time>" not satisfied: macaroon has expired
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: expected 1 macaroon, got 0
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: permission denied
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: the RPC server is in the process of starting up, but not yet ready to accept calls
<time> [ERR] RPCS: [/lnrpc.Lightning/GetInfo]: wallet locked, unlock it to enable full RPC access
<time> [ERR] RPCS: [/lnrpc.Lightning/ListMacaroonIDs]: cannot retrieve macaroon: cannot get macaroon: root key with id 1 doesn't exist
<time> [ERR] RPCS: [/lnrpc.Lightning/NewAddress]: permission denied
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=Number of pending channels exceed maximum
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=Synchronizing blockchain
<time> [ERR] RPCS: [/walletrpc.WalletKit/LabelTransaction]: cannot label transaction with empty label
<time> [ERR] RPCS: [/walletrpc.WalletKit/LabelTransaction]: transaction already labelled
<time> [ERR] RPCS: Websocket receive error from <ip>: read tcp4 <ip>-><ip>: use of closed network connection
<time> [ERR] RPCS: Websocket receive error from <ip>: websocket: close 1006 unexpected EOF
<time> [ERR] RPCS: WS: error closing upgraded conn: write tcp4 <ip>-><ip>: write: connection reset by peer
<time> [ERR] SRVR: Unable to connect to <hex>@<ip>: dial tcp <ip>: i/o timeout
<time> [ERR] SRVR: Unable to connect to <hex>@<ip>: dial tcp <ip>: i/o timeout
<time> [ERR] SRVR: Unable to connect to <hex>@<ip>: read tcp <ip>-><ip>: i/o timeout
<time> [ERR] SRVR: Unable to retrieve advertised address for node <hex>: no advertised addresses found
<time> [ERR] SRVR: Unable to retrieve advertised address for node <hex>: unable to find node
<time> [ERR] UTXN: error while graduating class at height=<height>: TxNotifier is exiting
<time> [ERR] UTXN: Failed to sweep first-stage HTLC (CLTV-delayed) output <chan_point>
<time> [ERR] UTXN: Notification chan closed, can't advance output <chan_point>
<time> [ERR] DISC: Unable to rebroadcast stale announcements: unable to retrieve outgoing channels: channel from self node has no policy
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: reserved wallet balance invalidated: transaction would leave insufficient funds for fee bumping anchor channel closings (see debug log for details)
<time> [ERR] RPCS: [/lnrpc.Lightning/SendCoins]: reserved wallet balance invalidated: transaction would leave insufficient funds for fee bumping anchor channel closings (see debug log for details)
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): reserved wallet balance invalidated: transaction would leave insufficient funds for fee bumping anchor channel closings (see debug log for details)
<time> [ERR] NANN: Unable to retrieve chan status for Channel(<chan_point>): unable to extract ChannelUpdate
<time> [ERR] NTFN: Unable to rewind chain from height 1 to height -1: unable to find blockhash for disconnected height=<height>: -8: Block height out of range
<time> [ERR] RPCS: WS: error writing message: websocket: close sent
<time> [ERR] RPCS: [/routerrpc.Router/XImportMissionControl]: pair: <hex> -> <hex>: invalid failure: msat: <amt> and sat: 0.0000002 BTC values not equal
<time> [ERR] BTCN: utxo scan failed: neutrino shutting down
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeInvoices]: context canceled
<time> [ERR] RPCS: [/lnrpc.Lightning/SubscribeChannelGraph]: context deadline exceeded
<time> [ERR] RPCS: [/invoicesrpc.Invoices/SubscribeSingleInvoice]: context canceled
<time> [ERR] RPCS: [/lnrpc.State/SubscribeState]: context canceled
<time> [ERR] NTFN: Failed to update rescan progress: database not open
<time> [ERR] HSWC: ChannelLink(<chan_point>): failing link: process hodl queue: unable to update commitment: link shutting down with error: internal error
<time> [ERR] INVC: SettleHodlInvoice with preimage <hex>: invoice already canceled
<time> [ERR] RPCS: [/invoicesrpc.Invoices/SettleInvoice]: invoice already canceled
<time> [ERR] HSWC: ChannelLink(<chan_point>): outgoing htlc(<hex>) has insufficient fee: expected 33000, got 1020
<time> [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: rpc error: code = Canceled desc = context canceled
-23
View File
@@ -1,23 +0,0 @@
s/[0-9a-f]{64}\:[[:digit:]]+/<chan_point>/g
s/short_chan_id=[[:digit:]]+/short_chan_id=<cid>/g
s/[0-9a-f]{16,}/<hex>/g
s/[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+\:[[:digit:]]+/<ip>/g
s/[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\.[[:digit:]]{3}/<time>/g
s/[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}T[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\.[[:digit:]]{1,18}Z/<time>/g
s/[[:digit:]]+\:[[:digit:]]+\:[[:digit:]]+/<chan>/g
s/[[:digit:]]+ mSAT/<amt>/g
s/HTLC ID = [[:digit:]]+/HTLC ID = <id>/g
s/height=[[:digit:]]+/height=<height>/g
s/collecting result for shard [[:digit:]]+/collecting result for shard <number>/g
s/sending attempt [[:digit:]]+/sending attempt <number>/g
s/Unable to rewind chain from height [[:digit:]]+ to height -?[[:digit:]]+/Unable to rewind chain from height <height> to height <height>/g
s/NTFN: unable to get missed blocks: starting height [[:digit:]]+ is greater than ending height [[:digit:]]+/NTFN: unable to get missed blocks: starting height <height> is greater than ending height <height>/g
s/BTCN: Broadcast attempt failed: rejected by <ip>: replacement transaction <hex> has an insufficient fee rate: needs more than [[:digit:]]+, has [[:digit:]]+/BTCN: Broadcast attempt failed: rejected by <ip>: replacement transaction <hex> has an insufficient fee rate: needs more than <amt>, has <amt>/g
s/pid=[[:digit:]]+/pid=<pid>/g
s/connid=[[:digit:]]+/connid=<id>/g
s/sync failed with local data loss: remote believes our tail height is [[:digit:]]+, while we have [[:digit:]]+/sync failed with local data loss: remote believes our tail height is <height>, while we have <height>/g
s/sync failed: remote believes our tail height is [[:digit:]]+, while we have [[:digit:]]+/sync failed: remote believes our tail height is <height>, while we have <height>/g
s/sync failed: remote's next commit height is [[:digit:]]+, while we believe it is [[:digit:]]+/sync failed: remote's next commit height is <height>, while we believe it is <height>/g
s/replacement transaction <hex> has an insufficient absolute fee: needs [[:digit:]]+, has [[:digit:]]+/replacement transaction <hex> has an insufficient absolute fee: needs <amt>, has <amt>/g
s/Rescan for [[:digit:]]+ addresses/Rescan for <num> addresses/g
s/unable to get hash from block with height [[:digit:]]+/unable to get hash from block with height <height>/g
-351
View File
@@ -1,351 +0,0 @@
package itest
import (
"bytes"
"context"
"flag"
"fmt"
"math"
"os"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/stretchr/testify/require"
)
var (
harnessNetParams = &chaincfg.RegressionNetParams
// lndExecutable is the full path to the lnd binary.
lndExecutable = flag.String(
"lndexec", itestLndBinary, "full path to lnd binary",
)
slowMineDelay = 20 * time.Millisecond
)
const (
testFeeBase = 1e+6
defaultCSV = lntest.DefaultCSV
defaultTimeout = lntest.DefaultTimeout
minerMempoolTimeout = lntest.MinerMempoolTimeout
channelCloseTimeout = lntest.ChannelCloseTimeout
itestLndBinary = "../../lnd-itest"
anchorSize = 330
noFeeLimitMsat = math.MaxInt64
AddrTypeWitnessPubkeyHash = lnrpc.AddressType_WITNESS_PUBKEY_HASH
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
AddrTypeTaprootPubkey = lnrpc.AddressType_TAPROOT_PUBKEY
)
// harnessTest wraps a regular testing.T providing enhanced error detection
// and propagation. All error will be augmented with a full stack-trace in
// order to aid in debugging. Additionally, any panics caused by active
// test cases will also be handled and represented as fatals.
type harnessTest struct {
t *testing.T
// testCase is populated during test execution and represents the
// current test case.
testCase *testCase
// lndHarness is a reference to the current network harness. Will be
// nil if not yet set up.
lndHarness *lntest.NetworkHarness
}
// newHarnessTest creates a new instance of a harnessTest from a regular
// testing.T instance.
func newHarnessTest(t *testing.T, net *lntest.NetworkHarness) *harnessTest {
return &harnessTest{t, nil, net}
}
// Skipf calls the underlying testing.T's Skip method, causing the current test
// to be skipped.
func (h *harnessTest) Skipf(format string, args ...interface{}) {
h.t.Skipf(format, args...)
}
// Fatalf causes the current active test case to fail with a fatal error. All
// integration tests should mark test failures solely with this method due to
// the error stack traces it produces.
func (h *harnessTest) Fatalf(format string, a ...interface{}) {
if h.lndHarness != nil {
h.lndHarness.SaveProfilesPages(h.t)
}
stacktrace := errors.Wrap(fmt.Sprintf(format, a...), 1).ErrorStack()
if h.testCase != nil {
h.t.Fatalf("Failed: (%v): exited with error: \n"+
"%v", h.testCase.name, stacktrace)
} else {
h.t.Fatalf("Error outside of test: %v", stacktrace)
}
}
// RunTestCase executes a harness test case. Any errors or panics will be
// represented as fatal.
func (h *harnessTest) RunTestCase(testCase *testCase) {
h.testCase = testCase
defer func() {
h.testCase = nil
}()
defer func() {
if err := recover(); err != nil {
description := errors.Wrap(err, 2).ErrorStack()
h.t.Fatalf("Failed: (%v) panicked with: \n%v",
h.testCase.name, description)
}
}()
testCase.test(h.lndHarness, h)
}
func (h *harnessTest) Logf(format string, args ...interface{}) {
h.t.Logf(format, args...)
}
func (h *harnessTest) Log(args ...interface{}) {
h.t.Log(args...)
}
func (h *harnessTest) getLndBinary() string {
binary := itestLndBinary
lndExec := ""
if lndExecutable != nil && *lndExecutable != "" {
lndExec = *lndExecutable
}
if lndExec == "" && runtime.GOOS == "windows" {
// Windows (even in a bash like environment like git bash as on
// Travis) doesn't seem to like relative paths to exe files...
currentDir, err := os.Getwd()
if err != nil {
h.Fatalf("unable to get working directory: %v", err)
}
targetPath := filepath.Join(currentDir, "../../lnd-itest.exe")
binary, err = filepath.Abs(targetPath)
if err != nil {
h.Fatalf("unable to get absolute path: %v", err)
}
} else if lndExec != "" {
binary = lndExec
}
return binary
}
type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest)
}
// waitForTxInMempool polls until finding one transaction in the provided
// miner's mempool. An error is returned if *one* transaction isn't found within
// the given timeout.
func waitForTxInMempool(miner *rpcclient.Client,
timeout time.Duration) (*chainhash.Hash, error) {
txs, err := waitForNTxsInMempool(miner, 1, timeout)
if err != nil {
return nil, err
}
return txs[0], err
}
// waitForNTxsInMempool polls until finding the desired number of transactions
// in the provided miner's mempool. An error is returned if this number is not
// met after the given timeout.
func waitForNTxsInMempool(miner *rpcclient.Client, n int,
timeout time.Duration) ([]*chainhash.Hash, error) {
breakTimeout := time.After(timeout)
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
var err error
var mempool []*chainhash.Hash
for {
select {
case <-breakTimeout:
return nil, fmt.Errorf("wanted %v, found %v txs "+
"in mempool: %v", n, len(mempool), mempool)
case <-ticker.C:
mempool, err = miner.GetRawMempool()
if err != nil {
return nil, err
}
if len(mempool) == n {
return mempool, nil
}
}
}
}
// mineBlocks mine 'num' of blocks and check that blocks are present in
// node blockchain. numTxs should be set to the number of transactions
// (excluding the coinbase) we expect to be included in the first mined block.
func mineBlocksFast(t *harnessTest, net *lntest.NetworkHarness,
num uint32, numTxs int) []*wire.MsgBlock {
// If we expect transactions to be included in the blocks we'll mine,
// we wait here until they are seen in the miner's mempool.
var txids []*chainhash.Hash
var err error
if numTxs > 0 {
txids, err = waitForNTxsInMempool(
net.Miner.Client, numTxs, minerMempoolTimeout,
)
if err != nil {
t.Fatalf("unable to find txns in mempool: %v", err)
}
}
blocks := make([]*wire.MsgBlock, num)
blockHashes, err := net.Miner.Client.Generate(num)
if err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
for i, blockHash := range blockHashes {
block, err := net.Miner.Client.GetBlock(blockHash)
if err != nil {
t.Fatalf("unable to get block: %v", err)
}
blocks[i] = block
}
// Finally, assert that all the transactions were included in the first
// block.
for _, txid := range txids {
assertTxInBlock(t, blocks[0], txid)
}
return blocks
}
// mineBlocksSlow mines 'num' of blocks and checks that blocks are present in
// the mining node's blockchain. numTxs should be set to the number of
// transactions (excluding the coinbase) we expect to be included in the first
// mined block. Between each mined block an artificial delay is introduced to
// give all network participants time to catch up.
//
// NOTE: This function currently is just an alias for mineBlocksSlow.
func mineBlocks(t *harnessTest, net *lntest.NetworkHarness,
num uint32, numTxs int) []*wire.MsgBlock {
return mineBlocksSlow(t, net, num, numTxs)
}
// mineBlocksSlow mines 'num' of blocks and checks that blocks are present in
// the mining node's blockchain. numTxs should be set to the number of
// transactions (excluding the coinbase) we expect to be included in the first
// mined block. Between each mined block an artificial delay is introduced to
// give all network participants time to catch up.
func mineBlocksSlow(t *harnessTest, net *lntest.NetworkHarness,
num uint32, numTxs int) []*wire.MsgBlock {
t.t.Helper()
// If we expect transactions to be included in the blocks we'll mine,
// we wait here until they are seen in the miner's mempool.
var txids []*chainhash.Hash
var err error
if numTxs > 0 {
txids, err = waitForNTxsInMempool(
net.Miner.Client, numTxs, minerMempoolTimeout,
)
require.NoError(t.t, err, "unable to find txns in mempool")
}
blocks := make([]*wire.MsgBlock, num)
blockHashes := make([]*chainhash.Hash, 0, num)
for i := uint32(0); i < num; i++ {
generatedHashes, err := net.Miner.Client.Generate(1)
require.NoError(t.t, err, "generate blocks")
blockHashes = append(blockHashes, generatedHashes...)
time.Sleep(slowMineDelay)
}
for i, blockHash := range blockHashes {
block, err := net.Miner.Client.GetBlock(blockHash)
require.NoError(t.t, err, "get blocks")
blocks[i] = block
}
// Finally, assert that all the transactions were included in the first
// block.
for _, txid := range txids {
assertTxInBlock(t, blocks[0], txid)
}
return blocks
}
func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) {
for _, tx := range block.Transactions {
sha := tx.TxHash()
if bytes.Equal(txid[:], sha[:]) {
return
}
}
t.Fatalf("tx was not included in block")
}
func assertWalletUnspent(t *harnessTest, node *lntest.HarnessNode,
out *lnrpc.OutPoint, account string) {
t.t.Helper()
ctxb := context.Background()
err := wait.NoError(func() error {
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
unspent, err := node.WalletKitClient.ListUnspent(
ctxt, &walletrpc.ListUnspentRequest{
Account: account,
},
)
if err != nil {
return err
}
err = errors.New("tx with wanted txhash never found")
for _, utxo := range unspent.Utxos {
if !bytes.Equal(utxo.Outpoint.TxidBytes, out.TxidBytes) {
continue
}
err = errors.New("wanted output is not a wallet utxo")
if utxo.Outpoint.OutputIndex != out.OutputIndex {
continue
}
return nil
}
return err
}, defaultTimeout)
require.NoError(t.t, err)
}
-468
View File
@@ -1,468 +0,0 @@
package itest
import (
"context"
"crypto/rand"
"fmt"
"io"
"time"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
"github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// completePaymentRequests sends payments from a lightning node to complete all
// payment requests. If the awaitResponse parameter is true, this function
// does not return until all payments successfully complete without errors.
func completePaymentRequests(client lnrpc.LightningClient,
routerClient routerrpc.RouterClient, paymentRequests []string,
awaitResponse bool) error {
ctxb := context.Background()
ctx, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
// We start by getting the current state of the client's channels. This
// is needed to ensure the payments actually have been committed before
// we return.
req := &lnrpc.ListChannelsRequest{}
listResp, err := client.ListChannels(ctx, req)
if err != nil {
return err
}
// send sends a payment and returns an error if it doesn't succeeded.
send := func(payReq string) error {
ctxc, cancel := context.WithCancel(ctx)
defer cancel()
payStream, err := routerClient.SendPaymentV2(
ctxc,
&routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
},
)
if err != nil {
return err
}
resp, err := getPaymentResult(payStream)
if err != nil {
return err
}
if resp.Status != lnrpc.Payment_SUCCEEDED {
return errors.New(resp.FailureReason)
}
return nil
}
// Launch all payments simultaneously.
results := make(chan error)
for _, payReq := range paymentRequests {
go func(payReq string) {
err := send(payReq)
if awaitResponse {
results <- err
}
}(payReq)
}
// If awaiting a response, verify that all payments succeeded.
if awaitResponse {
for range paymentRequests {
err := <-results
if err != nil {
return err
}
}
return nil
}
// We are not waiting for feedback in the form of a response, but we
// should still wait long enough for the server to receive and handle
// the send before cancelling the request. We wait for the number of
// updates to one of our channels has increased before we return.
err = wait.Predicate(func() bool {
newListResp, err := client.ListChannels(ctx, req)
if err != nil {
return false
}
// If the number of open channels is now lower than before
// attempting the payments, it means one of the payments
// triggered a force closure (for example, due to an incorrect
// preimage). Return early since it's clear the payment was
// attempted.
if len(newListResp.Channels) < len(listResp.Channels) {
return true
}
for _, c1 := range listResp.Channels {
for _, c2 := range newListResp.Channels {
if c1.ChannelPoint != c2.ChannelPoint {
continue
}
// If this channel has an increased numbr of
// updates, we assume the payments are
// committed, and we can return.
if c2.NumUpdates > c1.NumUpdates {
return true
}
}
}
return false
}, defaultTimeout)
if err != nil {
return err
}
return nil
}
// makeFakePayHash creates random pre image hash.
func makeFakePayHash(t *harnessTest) []byte {
randBuf := make([]byte, 32)
if _, err := rand.Read(randBuf); err != nil {
t.Fatalf("internal error, cannot generate random string: %v", err)
}
return randBuf
}
// createPayReqs is a helper method that will create a slice of payment
// requests for the given node.
func createPayReqs(node *lntest.HarnessNode, paymentAmt btcutil.Amount,
numInvoices int) ([]string, [][]byte, []*lnrpc.Invoice, error) {
payReqs := make([]string, numInvoices)
rHashes := make([][]byte, numInvoices)
invoices := make([]*lnrpc.Invoice, numInvoices)
for i := 0; i < numInvoices; i++ {
preimage := make([]byte, 32)
_, err := rand.Read(preimage)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to generate "+
"preimage: %v", err)
}
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: int64(paymentAmt),
}
ctxt, _ := context.WithTimeout(
context.Background(), defaultTimeout,
)
resp, err := node.AddInvoice(ctxt, invoice)
if err != nil {
return nil, nil, nil, fmt.Errorf("unable to add "+
"invoice: %v", err)
}
// Set the payment address in the invoice so the caller can
// properly use it.
invoice.PaymentAddr = resp.PaymentAddr
payReqs[i] = resp.PaymentRequest
rHashes[i] = resp.RHash
invoices[i] = invoice
}
return payReqs, rHashes, invoices, nil
}
// getChanInfo is a helper method for getting channel info for a node's sole
// channel.
func getChanInfo(node *lntest.HarnessNode) (*lnrpc.Channel, error) {
ctxb := context.Background()
ctx, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
req := &lnrpc.ListChannelsRequest{}
channelInfo, err := node.ListChannels(ctx, req)
if err != nil {
return nil, err
}
if len(channelInfo.Channels) != 1 {
return nil, fmt.Errorf("node should only have a single "+
"channel, instead it has %v", len(channelInfo.Channels))
}
return channelInfo.Channels[0], nil
}
// commitTypeHasAnchors returns whether commitType uses anchor outputs.
func commitTypeHasAnchors(commitType lnrpc.CommitmentType) bool {
switch commitType {
case lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return true
default:
return false
}
}
// nodeArgsForCommitType returns the command line flag to supply to enable this
// commitment type.
func nodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
switch commitType {
case lnrpc.CommitmentType_LEGACY:
return []string{"--protocol.legacy.committweak"}
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
return []string{}
case lnrpc.CommitmentType_ANCHORS:
return []string{"--protocol.anchors"}
case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE:
return []string{
"--protocol.anchors",
"--protocol.script-enforced-lease",
}
}
return nil
}
// calcStaticFee calculates appropriate fees for commitment transactions. This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
func calcStaticFee(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
const htlcWeight = input.HTLCWeight
var (
feePerKw = chainfee.SatPerKWeight(
lntemp.DefaultFeeRateSatPerKw,
)
commitWeight = input.CommitWeight
anchors = btcutil.Amount(0)
)
// The anchor commitment type is slightly heavier, and we must also add
// the value of the two anchors to the resulting fee the initiator
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
if commitTypeHasAnchors(c) {
feePerKw = chainfee.SatPerKVByte(
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
).FeePerKWeight()
commitWeight = input.AnchorCommitWeight
anchors = 2 * anchorSize
}
return feePerKw.FeeForWeight(int64(commitWeight+htlcWeight*numHTLCs)) +
anchors
}
// channelCommitType retrieves the active channel commitment type for the given
// chan point.
func channelCommitType(node *lntest.HarnessNode,
chanPoint *lnrpc.ChannelPoint) (lnrpc.CommitmentType, error) {
ctxb := context.Background()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
req := &lnrpc.ListChannelsRequest{}
channels, err := node.ListChannels(ctxt, req)
if err != nil {
return 0, fmt.Errorf("listchannels failed: %v", err)
}
for _, c := range channels.Channels {
if c.ChannelPoint == txStr(chanPoint) {
return c.CommitmentType, nil
}
}
return 0, fmt.Errorf("channel point %v not found", chanPoint)
}
// calculateMaxHtlc re-implements the RequiredRemoteChannelReserve of the
// funding manager's config, which corresponds to the maximum MaxHTLC value we
// allow users to set when updating a channel policy.
func calculateMaxHtlc(chanCap btcutil.Amount) uint64 {
reserve := lnwire.NewMSatFromSatoshis(chanCap / 100)
max := lnwire.NewMSatFromSatoshis(chanCap) - reserve
return uint64(max)
}
// waitForNodeBlockHeight queries the node for its current block height until
// it reaches the passed height.
func waitForNodeBlockHeight(node *lntest.HarnessNode, height int32) error {
ctxb := context.Background()
ctx, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
var predErr error
err := wait.Predicate(func() bool {
info, err := node.GetInfo(ctx, &lnrpc.GetInfoRequest{})
if err != nil {
predErr = err
return false
}
if int32(info.BlockHeight) != height {
predErr = fmt.Errorf("expected block height to "+
"be %v, was %v", height, info.BlockHeight)
return false
}
return true
}, defaultTimeout)
if err != nil {
return predErr
}
return nil
}
// getNTxsFromMempool polls until finding the desired number of transactions in
// the provided miner's mempool and returns the full transactions to the caller.
func getNTxsFromMempool(miner *rpcclient.Client, n int,
timeout time.Duration) ([]*wire.MsgTx, error) {
txids, err := waitForNTxsInMempool(miner, n, timeout)
if err != nil {
return nil, err
}
var txes []*wire.MsgTx
for _, txid := range txids {
tx, err := miner.GetRawTransaction(txid)
if err != nil {
return nil, err
}
txes = append(txes, tx.MsgTx())
}
return txes, nil
}
// getTxFee retrieves parent transactions and reconstructs the fee paid.
func getTxFee(miner *rpcclient.Client, tx *wire.MsgTx) (btcutil.Amount, error) {
var balance btcutil.Amount
for _, in := range tx.TxIn {
parentHash := in.PreviousOutPoint.Hash
rawTx, err := miner.GetRawTransaction(&parentHash)
if err != nil {
return 0, err
}
parent := rawTx.MsgTx()
balance += btcutil.Amount(
parent.TxOut[in.PreviousOutPoint.Index].Value,
)
}
for _, out := range tx.TxOut {
balance -= btcutil.Amount(out.Value)
}
return balance, nil
}
// channelSubscription houses the proxied update and error chans for a node's
// channel subscriptions.
type channelSubscription struct {
updateChan chan *lnrpc.ChannelEventUpdate
errChan chan error
quit chan struct{}
}
// subscribeChannelNotifications subscribes to channel updates and launches a
// goroutine that forwards these to the returned channel.
func subscribeChannelNotifications(ctxb context.Context, t *harnessTest,
node *lntest.HarnessNode) channelSubscription {
// We'll first start by establishing a notification client which will
// send us notifications upon channels becoming active, inactive or
// closed.
req := &lnrpc.ChannelEventSubscription{}
ctx, cancelFunc := context.WithCancel(ctxb)
chanUpdateClient, err := node.SubscribeChannelEvents(ctx, req)
if err != nil {
t.Fatalf("unable to create channel update client: %v", err)
}
// We'll launch a goroutine that will be responsible for proxying all
// notifications recv'd from the client into the channel below.
errChan := make(chan error, 1)
quit := make(chan struct{})
chanUpdates := make(chan *lnrpc.ChannelEventUpdate, 20)
go func() {
defer cancelFunc()
for {
select {
case <-quit:
return
default:
chanUpdate, err := chanUpdateClient.Recv()
select {
case <-quit:
return
default:
}
if err == io.EOF {
return
} else if err != nil {
select {
case errChan <- err:
case <-quit:
}
return
}
select {
case chanUpdates <- chanUpdate:
case <-quit:
return
}
}
}
}()
return channelSubscription{
updateChan: chanUpdates,
errChan: errChan,
quit: quit,
}
}
// findTxAtHeight gets all of the transactions that a node's wallet has a record
// of at the target height, and finds and returns the tx with the target txid,
// failing if it is not found.
func findTxAtHeight(t *harnessTest, height int32,
target string, node *lntest.HarnessNode) *lnrpc.Transaction {
ctxb := context.Background()
ctx, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
txns, err := node.LightningClient.GetTransactions(
ctx, &lnrpc.GetTransactionsRequest{
StartHeight: height,
EndHeight: height,
},
)
require.NoError(t.t, err, "could not get transactions")
for _, tx := range txns.Transactions {
if tx.TxHash == target {
return tx
}
}
return nil
}