mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-05-29 17:19:33 +02:00
Merge pull request #9368 from lightningnetwork/yy-waiting-on-merge
Fix itest re new behaviors introduced by `blockbeat`
This commit is contained in:
commit
a388c1f39d
112
.github/workflows/main.yml
vendored
112
.github/workflows/main.yml
vendored
@ -23,7 +23,14 @@ defaults:
|
||||
env:
|
||||
BITCOIN_VERSION: "28"
|
||||
|
||||
TRANCHES: 8
|
||||
# TRANCHES defines the number of tranches used in the itests.
|
||||
TRANCHES: 16
|
||||
|
||||
# SMALL_TRANCHES defines the number of tranches used in the less stable itest
|
||||
# builds
|
||||
#
|
||||
# TODO(yy): remove this value and use TRANCHES.
|
||||
SMALL_TRANCHES: 8
|
||||
|
||||
# If you change this please also update GO_VERSION in Makefile (then run
|
||||
# `make lint` to see where else it needs to be updated as well).
|
||||
@ -262,10 +269,10 @@ jobs:
|
||||
|
||||
|
||||
########################
|
||||
# run ubuntu integration tests
|
||||
# run integration tests with TRANCHES
|
||||
########################
|
||||
ubuntu-integration-test:
|
||||
name: run ubuntu itests
|
||||
basic-integration-test:
|
||||
name: basic itests
|
||||
runs-on: ubuntu-latest
|
||||
if: '!contains(github.event.pull_request.labels.*.name, ''no-itest'')'
|
||||
strategy:
|
||||
@ -279,18 +286,6 @@ jobs:
|
||||
args: backend=bitcoind cover=1
|
||||
- name: bitcoind-notxindex
|
||||
args: backend="bitcoind notxindex"
|
||||
- name: bitcoind-rpcpolling
|
||||
args: backend="bitcoind rpcpolling" cover=1
|
||||
- name: bitcoind-etcd
|
||||
args: backend=bitcoind dbbackend=etcd
|
||||
- name: bitcoind-postgres
|
||||
args: backend=bitcoind dbbackend=postgres
|
||||
- name: bitcoind-sqlite
|
||||
args: backend=bitcoind dbbackend=sqlite
|
||||
- name: bitcoind-postgres-nativesql
|
||||
args: backend=bitcoind dbbackend=postgres nativesql=true
|
||||
- name: bitcoind-sqlite-nativesql
|
||||
args: backend=bitcoind dbbackend=sqlite nativesql=true
|
||||
- name: neutrino
|
||||
args: backend=neutrino cover=1
|
||||
steps:
|
||||
@ -316,7 +311,7 @@ jobs:
|
||||
run: ./scripts/install_bitcoind.sh $BITCOIN_VERSION
|
||||
|
||||
- name: run ${{ matrix.name }}
|
||||
run: make itest-parallel tranches=${{ env.TRANCHES }} ${{ matrix.args }}
|
||||
run: make itest-parallel tranches=${{ env.TRANCHES }} ${{ matrix.args }} shuffleseed=${{ github.run_id }}${{ strategy.job-index }}
|
||||
|
||||
- name: Send coverage
|
||||
if: ${{ contains(matrix.args, 'cover=1') }}
|
||||
@ -339,12 +334,79 @@ jobs:
|
||||
path: logs-itest-${{ matrix.name }}.zip
|
||||
retention-days: 5
|
||||
|
||||
########################
|
||||
# run integration tests with SMALL_TRANCHES
|
||||
########################
|
||||
integration-test:
|
||||
name: itests
|
||||
runs-on: ubuntu-latest
|
||||
if: '!contains(github.event.pull_request.labels.*.name, ''no-itest'')'
|
||||
strategy:
|
||||
# Allow other tests in the matrix to continue if one fails.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: bitcoind-rpcpolling
|
||||
args: backend="bitcoind rpcpolling"
|
||||
- name: bitcoind-etcd
|
||||
args: backend=bitcoind dbbackend=etcd
|
||||
- name: bitcoind-sqlite
|
||||
args: backend=bitcoind dbbackend=sqlite
|
||||
- name: bitcoind-sqlite-nativesql
|
||||
args: backend=bitcoind dbbackend=sqlite nativesql=true
|
||||
- name: bitcoind-postgres
|
||||
args: backend=bitcoind dbbackend=postgres
|
||||
- name: bitcoind-postgres-nativesql
|
||||
args: backend=bitcoind dbbackend=postgres nativesql=true
|
||||
steps:
|
||||
- name: git checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: fetch and rebase on ${{ github.base_ref }}
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: ./.github/actions/rebase
|
||||
|
||||
- name: setup go ${{ env.GO_VERSION }}
|
||||
uses: ./.github/actions/setup-go
|
||||
with:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: install bitcoind
|
||||
run: ./scripts/install_bitcoind.sh $BITCOIN_VERSION
|
||||
|
||||
- name: run ${{ matrix.name }}
|
||||
run: make itest-parallel tranches=${{ env.SMALL_TRANCHES }} ${{ matrix.args }} shuffleseed=${{ github.run_id }}${{ strategy.job-index }}
|
||||
|
||||
- name: Send coverage
|
||||
if: ${{ contains(matrix.args, 'cover=1') }}
|
||||
uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
path-to-profile: coverage.txt
|
||||
flag-name: 'itest-${{ matrix.name }}'
|
||||
parallel: true
|
||||
|
||||
- name: Zip log files on failure
|
||||
if: ${{ failure() }}
|
||||
timeout-minutes: 5 # timeout after 5 minute
|
||||
run: 7z a logs-itest-${{ matrix.name }}.zip itest/**/*.log
|
||||
|
||||
- name: Upload log files on failure
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: logs-itest-${{ matrix.name }}
|
||||
path: logs-itest-${{ matrix.name }}.zip
|
||||
retention-days: 5
|
||||
|
||||
|
||||
########################
|
||||
# run windows integration test
|
||||
########################
|
||||
windows-integration-test:
|
||||
name: run windows itest
|
||||
name: windows itest
|
||||
runs-on: windows-latest
|
||||
if: '!contains(github.event.pull_request.labels.*.name, ''no-itest'')'
|
||||
steps:
|
||||
@ -364,7 +426,7 @@ jobs:
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: run itest
|
||||
run: make itest-parallel tranches=${{ env.TRANCHES }} windows=1
|
||||
run: make itest-parallel tranches=${{ env.SMALL_TRANCHES }} windows=1 shuffleseed=${{ github.run_id }}
|
||||
|
||||
- name: kill any remaining lnd processes
|
||||
if: ${{ failure() }}
|
||||
@ -388,7 +450,7 @@ jobs:
|
||||
# run macOS integration test
|
||||
########################
|
||||
macos-integration-test:
|
||||
name: run macOS itest
|
||||
name: macOS itest
|
||||
runs-on: macos-14
|
||||
if: '!contains(github.event.pull_request.labels.*.name, ''no-itest'')'
|
||||
steps:
|
||||
@ -407,14 +469,8 @@ jobs:
|
||||
go-version: '${{ env.GO_VERSION }}'
|
||||
key-prefix: integration-test
|
||||
|
||||
- name: install bitcoind
|
||||
run: |
|
||||
wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}.0/bitcoin-${BITCOIN_VERSION}.0-arm64-apple-darwin.tar.gz
|
||||
tar zxvf bitcoin-${BITCOIN_VERSION}.0-arm64-apple-darwin.tar.gz
|
||||
mv bitcoin-${BITCOIN_VERSION}.0 /tmp/bitcoin
|
||||
|
||||
- name: run itest
|
||||
run: PATH=$PATH:/tmp/bitcoin/bin make itest-parallel tranches=${{ env.TRANCHES }} backend=bitcoind
|
||||
run: make itest-parallel tranches=${{ env.SMALL_TRANCHES }} shuffleseed=${{ github.run_id }}
|
||||
|
||||
- name: Zip log files on failure
|
||||
if: ${{ failure() }}
|
||||
@ -473,7 +529,7 @@ jobs:
|
||||
# Notify about the completion of all coverage collecting jobs.
|
||||
finish:
|
||||
if: ${{ always() }}
|
||||
needs: [unit-test, ubuntu-integration-test]
|
||||
needs: [unit-test, basic-integration-test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: ziggie1984/actions-goveralls@c440f43938a4032b627d2b03d61d4ae1a2ba2b5c
|
||||
|
4
Makefile
4
Makefile
@ -220,7 +220,7 @@ clean-itest-logs:
|
||||
itest-only: clean-itest-logs db-instance
|
||||
@$(call print, "Running integration tests with ${backend} backend.")
|
||||
date
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_part.sh 0 1 $(TEST_FLAGS) $(ITEST_FLAGS) -test.v
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_part.sh 0 1 $(SHUFFLE_SEED) $(TEST_FLAGS) $(ITEST_FLAGS) -test.v
|
||||
$(COLLECT_ITEST_COVERAGE)
|
||||
|
||||
#? itest: Build and run integration tests
|
||||
@ -233,7 +233,7 @@ itest-race: build-itest-race itest-only
|
||||
itest-parallel: clean-itest-logs build-itest db-instance
|
||||
@$(call print, "Running tests")
|
||||
date
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_parallel.sh $(ITEST_PARALLELISM) $(NUM_ITEST_TRANCHES) $(TEST_FLAGS) $(ITEST_FLAGS)
|
||||
EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_parallel.sh $(ITEST_PARALLELISM) $(NUM_ITEST_TRANCHES) $(SHUFFLE_SEED) $(TEST_FLAGS) $(ITEST_FLAGS)
|
||||
$(COLLECT_ITEST_COVERAGE)
|
||||
|
||||
#? itest-clean: Kill all running itest processes
|
||||
|
@ -221,6 +221,10 @@ The underlying functionality between those two options remain the same.
|
||||
estimator provided by bitcoind or btcd in regtest and simnet modes instead of
|
||||
static fee estimator if feeurl is not provided.
|
||||
|
||||
* The integration tests CI have been optimized to run faster and all flakes are
|
||||
now documented and
|
||||
[fixed](https://github.com/lightningnetwork/lnd/pull/9368).
|
||||
|
||||
## Database
|
||||
|
||||
* [Migrate the mission control
|
||||
|
111
itest/list_exclude_test.go
Normal file
111
itest/list_exclude_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
//go:build integration
|
||||
|
||||
package itest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
)
|
||||
|
||||
// excludedTestsWindows is a list of tests that are flaky on Windows and should
|
||||
// be excluded from the test suite atm.
|
||||
//
|
||||
// TODO(yy): fix these tests and remove them from this list.
|
||||
var excludedTestsWindows = []string{
|
||||
"batch channel funding",
|
||||
"zero conf channel open",
|
||||
"open channel with unstable utxos",
|
||||
"funding flow persistence",
|
||||
|
||||
// Gives "channel link not found" error.
|
||||
"zero conf-channel policy update public zero conf",
|
||||
|
||||
"listsweeps",
|
||||
"sweep htlcs",
|
||||
"sweep cpfp anchor incoming timeout",
|
||||
"payment succeeded htlc remote swept",
|
||||
"3rd party anchor spend",
|
||||
|
||||
"send payment amp",
|
||||
"async payments benchmark",
|
||||
"async bidirectional payments",
|
||||
|
||||
"multihop-htlc aggregation leased",
|
||||
"multihop-htlc aggregation leased zero conf",
|
||||
"multihop-htlc aggregation anchor",
|
||||
"multihop-htlc aggregation anchor zero conf",
|
||||
"multihop-htlc aggregation simple taproot",
|
||||
"multihop-htlc aggregation simple taproot zero conf",
|
||||
|
||||
"channel force closure anchor",
|
||||
"channel force closure simple taproot",
|
||||
"channel backup restore force close",
|
||||
"wipe forwarding packages",
|
||||
|
||||
"coop close with htlcs",
|
||||
"coop close with external delivery",
|
||||
|
||||
"forward interceptor restart",
|
||||
"forward interceptor dedup htlcs",
|
||||
"invoice HTLC modifier basic",
|
||||
"lookup htlc resolution",
|
||||
|
||||
"remote signer-taproot",
|
||||
"remote signer-account import",
|
||||
"remote signer-bump fee",
|
||||
"remote signer-funding input types",
|
||||
"remote signer-funding async payments taproot",
|
||||
"remote signer-funding async payments",
|
||||
"remote signer-random seed",
|
||||
"remote signer-verify msg",
|
||||
"remote signer-channel open",
|
||||
"remote signer-shared key",
|
||||
"remote signer-psbt",
|
||||
"remote signer-sign output raw",
|
||||
|
||||
"on chain to blinded",
|
||||
"query blinded route",
|
||||
|
||||
"data loss protection",
|
||||
}
|
||||
|
||||
// filterWindowsFlakyTests filters out the flaky tests that are excluded from
|
||||
// the test suite on Windows.
|
||||
func filterWindowsFlakyTests() []*lntest.TestCase {
|
||||
// filteredTestCases is a substest of allTestCases that excludes the
|
||||
// above flaky tests.
|
||||
filteredTestCases := make([]*lntest.TestCase, 0, len(allTestCases))
|
||||
|
||||
// Create a set for the excluded test cases for fast lookup.
|
||||
excludedSet := fn.NewSet(excludedTestsWindows...)
|
||||
|
||||
// Remove the tests from the excludedSet if it's found in the list of
|
||||
// all test cases. This is done to ensure the excluded tests are not
|
||||
// pointing to a test case that doesn't exist.
|
||||
for _, tc := range allTestCases {
|
||||
if excludedSet.Contains(tc.Name) {
|
||||
excludedSet.Remove(tc.Name)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
filteredTestCases = append(filteredTestCases, tc)
|
||||
}
|
||||
|
||||
// Exit early if all the excluded tests are found in allTestCases.
|
||||
if excludedSet.IsEmpty() {
|
||||
return filteredTestCases
|
||||
}
|
||||
|
||||
// Otherwise, print out the tests that are not found in allTestCases.
|
||||
errStr := "\nThe following tests are not found, please make sure the " +
|
||||
"test names are correct in `excludedTestsWindows`.\n"
|
||||
for _, name := range excludedSet.ToSlice() {
|
||||
errStr += fmt.Sprintf("Test not found in test suite: %v\n",
|
||||
name)
|
||||
}
|
||||
|
||||
panic(errStr)
|
||||
}
|
@ -2,33 +2,25 @@
|
||||
|
||||
package itest
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lntest"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
)
|
||||
|
||||
var allTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "update channel status",
|
||||
TestFunc: testUpdateChanStatus,
|
||||
},
|
||||
{
|
||||
Name: "basic funding flow",
|
||||
TestFunc: testBasicChannelFunding,
|
||||
},
|
||||
{
|
||||
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,
|
||||
@ -178,8 +170,12 @@ var allTestCases = []*lntest.TestCase{
|
||||
TestFunc: testListPayments,
|
||||
},
|
||||
{
|
||||
Name: "send direct payment",
|
||||
TestFunc: testSendDirectPayment,
|
||||
Name: "send direct payment anchor",
|
||||
TestFunc: testSendDirectPaymentAnchor,
|
||||
},
|
||||
{
|
||||
Name: "send direct payment simple taproot",
|
||||
TestFunc: testSendDirectPaymentSimpleTaproot,
|
||||
},
|
||||
{
|
||||
Name: "immediate payment after channel opened",
|
||||
@ -281,10 +277,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "open channel reorg test",
|
||||
TestFunc: testOpenChannelAfterReorg,
|
||||
},
|
||||
{
|
||||
Name: "psbt channel funding",
|
||||
TestFunc: testPsbtChanFunding,
|
||||
},
|
||||
{
|
||||
Name: "sign psbt",
|
||||
TestFunc: testSignPsbt,
|
||||
@ -314,10 +306,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "revoked uncooperative close retribution remote hodl",
|
||||
TestFunc: testRevokedCloseRetributionRemoteHodl,
|
||||
},
|
||||
{
|
||||
Name: "single-hop send to route",
|
||||
TestFunc: testSingleHopSendToRoute,
|
||||
},
|
||||
{
|
||||
Name: "multi-hop send to route",
|
||||
TestFunc: testMultiHopSendToRoute,
|
||||
@ -454,10 +442,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "option scid alias",
|
||||
TestFunc: testOptionScidAlias,
|
||||
},
|
||||
{
|
||||
Name: "scid alias channel update",
|
||||
TestFunc: testUpdateChannelPolicyScidAlias,
|
||||
},
|
||||
{
|
||||
Name: "scid alias upgrade",
|
||||
TestFunc: testOptionScidUpgrade,
|
||||
@ -491,17 +475,21 @@ var allTestCases = []*lntest.TestCase{
|
||||
TestFunc: testBumpForceCloseFee,
|
||||
},
|
||||
{
|
||||
Name: "taproot",
|
||||
TestFunc: testTaproot,
|
||||
Name: "taproot spend",
|
||||
TestFunc: testTaprootSpend,
|
||||
},
|
||||
{
|
||||
Name: "taproot musig2",
|
||||
TestFunc: testTaprootMuSig2,
|
||||
},
|
||||
{
|
||||
Name: "taproot import scripts",
|
||||
TestFunc: testTaprootImportScripts,
|
||||
},
|
||||
{
|
||||
Name: "simple taproot channel activation",
|
||||
TestFunc: testSimpleTaprootChannelActivation,
|
||||
},
|
||||
{
|
||||
Name: "wallet import account",
|
||||
TestFunc: testWalletImportAccount,
|
||||
},
|
||||
{
|
||||
Name: "wallet import pubkey",
|
||||
TestFunc: testWalletImportPubKey,
|
||||
@ -510,10 +498,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "async payments benchmark",
|
||||
TestFunc: testAsyncPayments,
|
||||
},
|
||||
{
|
||||
Name: "remote signer",
|
||||
TestFunc: testRemoteSigner,
|
||||
},
|
||||
{
|
||||
Name: "taproot coop close",
|
||||
TestFunc: testTaprootCoopClose,
|
||||
@ -526,10 +510,6 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "trackpayments compatible",
|
||||
TestFunc: testTrackPaymentsCompatible,
|
||||
},
|
||||
{
|
||||
Name: "open channel fee policy",
|
||||
TestFunc: testOpenChannelUpdateFeePolicy,
|
||||
},
|
||||
{
|
||||
Name: "custom message",
|
||||
TestFunc: testCustomMessage,
|
||||
@ -551,8 +531,16 @@ var allTestCases = []*lntest.TestCase{
|
||||
TestFunc: testLookupHtlcResolution,
|
||||
},
|
||||
{
|
||||
Name: "channel fundmax",
|
||||
TestFunc: testChannelFundMax,
|
||||
Name: "channel fundmax error",
|
||||
TestFunc: testChannelFundMaxError,
|
||||
},
|
||||
{
|
||||
Name: "channel fundmax wallet amount",
|
||||
TestFunc: testChannelFundMaxWalletAmount,
|
||||
},
|
||||
{
|
||||
Name: "channel fundmax anchor reserve",
|
||||
TestFunc: testChannelFundMaxAnchorReserve,
|
||||
},
|
||||
{
|
||||
Name: "htlc timeout resolver extract preimage remote",
|
||||
@ -567,12 +555,12 @@ var allTestCases = []*lntest.TestCase{
|
||||
TestFunc: testCustomFeatures,
|
||||
},
|
||||
{
|
||||
Name: "utxo selection funding",
|
||||
TestFunc: testChannelUtxoSelection,
|
||||
Name: "update pending open channels on funder side",
|
||||
TestFunc: testUpdateOnFunderPendingOpenChannels,
|
||||
},
|
||||
{
|
||||
Name: "update pending open channels",
|
||||
TestFunc: testUpdateOnPendingOpenChannels,
|
||||
Name: "update pending open channels on fundee side",
|
||||
TestFunc: testUpdateOnFundeePendingOpenChannels,
|
||||
},
|
||||
{
|
||||
Name: "blinded payment htlc re-forward",
|
||||
@ -666,6 +654,10 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "payment failed htlc local swept",
|
||||
TestFunc: testPaymentFailedHTLCLocalSwept,
|
||||
},
|
||||
{
|
||||
Name: "payment failed htlc local swept resumed",
|
||||
TestFunc: testPaymentFailedHTLCLocalSweptResumed,
|
||||
},
|
||||
{
|
||||
Name: "payment succeeded htlc remote swept",
|
||||
TestFunc: testPaymentSucceededHTLCRemoteSwept,
|
||||
@ -674,6 +666,10 @@ var allTestCases = []*lntest.TestCase{
|
||||
Name: "send to route failed htlc timeout",
|
||||
TestFunc: testSendToRouteFailHTLCTimeout,
|
||||
},
|
||||
{
|
||||
Name: "send to route failed htlc timeout resumed",
|
||||
TestFunc: testSendToRouteFailHTLCTimeoutResumed,
|
||||
},
|
||||
{
|
||||
Name: "debuglevel show",
|
||||
TestFunc: testDebuglevelShow,
|
||||
@ -688,8 +684,71 @@ var allTestCases = []*lntest.TestCase{
|
||||
},
|
||||
}
|
||||
|
||||
// appendPrefixed is used to add a prefix to each test name in the subtests
|
||||
// before appending them to the main test cases.
|
||||
func appendPrefixed(prefix string, testCases,
|
||||
subtestCases []*lntest.TestCase) []*lntest.TestCase {
|
||||
|
||||
for _, tc := range subtestCases {
|
||||
name := fmt.Sprintf("%s-%s", prefix, tc.Name)
|
||||
testCases = append(testCases, &lntest.TestCase{
|
||||
Name: name,
|
||||
TestFunc: tc.TestFunc,
|
||||
})
|
||||
}
|
||||
|
||||
return testCases
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register subtests.
|
||||
allTestCases = append(allTestCases, multiHopForceCloseTestCases...)
|
||||
allTestCases = append(allTestCases, watchtowerTestCases...)
|
||||
allTestCases = appendPrefixed(
|
||||
"multihop", allTestCases, multiHopForceCloseTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"watchtower", allTestCases, watchtowerTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"psbt", allTestCases, psbtFundingTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"remote signer", allTestCases, remoteSignerTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"channel backup", allTestCases, channelRestoreTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"utxo selection", allTestCases, fundUtxoSelectionTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"zero conf", allTestCases, zeroConfPolicyTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"channel fee policy", allTestCases, channelFeePolicyTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"wallet import account", allTestCases,
|
||||
walletImportAccountTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"funding", allTestCases, basicFundingTestCases,
|
||||
)
|
||||
allTestCases = appendPrefixed(
|
||||
"send to route", allTestCases, sendToRouteTestCases,
|
||||
)
|
||||
|
||||
// Prepare the test cases for windows to exclude some of the flaky
|
||||
// ones.
|
||||
//
|
||||
// NOTE: We need to run this before the isWindowsOS check to make sure
|
||||
// the excluded tests are found in allTestCases. Otherwise, if a
|
||||
// non-existing test is included in excludedTestsWindows, we won't be
|
||||
// able to find it until it's pushed to the CI, which creates a much
|
||||
// longer feedback loop.
|
||||
windowsTestCases := filterWindowsFlakyTests()
|
||||
|
||||
// If this is Windows, we'll skip running some of the flaky tests.
|
||||
if isWindowsOS() {
|
||||
allTestCases = windowsTestCases
|
||||
}
|
||||
}
|
||||
|
@ -47,8 +47,6 @@ func testSendPaymentAMPInvoiceCase(ht *lntest.HarnessTest,
|
||||
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.
|
||||
@ -59,15 +57,8 @@ func testSendPaymentAMPInvoiceCase(ht *lntest.HarnessTest,
|
||||
// \ /
|
||||
// \__ Dave ____/
|
||||
//
|
||||
mppReq := &mppOpenChannelRequest{
|
||||
amtAliceCarol: 285000,
|
||||
amtAliceDave: 155000,
|
||||
amtCarolBob: 200000,
|
||||
amtCarolEve: 155000,
|
||||
amtDaveBob: 155000,
|
||||
amtEveBob: 155000,
|
||||
}
|
||||
mts.openChannels(mppReq)
|
||||
paymentAmt := mts.setupSendPaymentCase()
|
||||
|
||||
chanPointAliceDave := mts.channelPoints[1]
|
||||
chanPointDaveBob := mts.channelPoints[4]
|
||||
|
||||
@ -373,7 +364,6 @@ func testSendPaymentAMPInvoiceRepeat(ht *lntest.HarnessTest) {
|
||||
// destination using SendPaymentV2.
|
||||
func testSendPaymentAMP(ht *lntest.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
|
||||
@ -385,15 +375,8 @@ func testSendPaymentAMP(ht *lntest.HarnessTest) {
|
||||
// \ /
|
||||
// \__ Dave ____/
|
||||
//
|
||||
mppReq := &mppOpenChannelRequest{
|
||||
amtAliceCarol: 285000,
|
||||
amtAliceDave: 155000,
|
||||
amtCarolBob: 200000,
|
||||
amtCarolEve: 155000,
|
||||
amtDaveBob: 155000,
|
||||
amtEveBob: 155000,
|
||||
}
|
||||
mts.openChannels(mppReq)
|
||||
paymentAmt := mts.setupSendPaymentCase()
|
||||
|
||||
chanPointAliceDave := mts.channelPoints[1]
|
||||
|
||||
// Increase Dave's fee to make the test deterministic. Otherwise, it
|
||||
@ -497,12 +480,6 @@ func testSendPaymentAMP(ht *lntest.HarnessTest) {
|
||||
|
||||
func testSendToRouteAMP(ht *lntest.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{}
|
||||
@ -515,20 +492,10 @@ func testSendToRouteAMP(ht *lntest.HarnessTest) {
|
||||
// \ /
|
||||
// \__ 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)
|
||||
paymentAmt, shardAmt := mts.setupSendToRouteCase()
|
||||
|
||||
// We'll send shards along three routes from Alice.
|
||||
sendRoutes := [numShards][]*node.HarnessNode{
|
||||
sendRoutes := [][]*node.HarnessNode{
|
||||
{mts.carol, mts.bob},
|
||||
{mts.dave, mts.bob},
|
||||
{mts.carol, mts.eve, mts.bob},
|
||||
@ -662,7 +629,7 @@ func testSendToRouteAMP(ht *lntest.HarnessTest) {
|
||||
|
||||
// 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))
|
||||
require.Equal(ht, 3, len(rpcInvoice.Htlcs))
|
||||
for _, htlc := range rpcInvoice.Htlcs {
|
||||
require.NotNil(ht, htlc.Amp)
|
||||
require.Equal(ht, setID, htlc.Amp.SetId)
|
||||
|
@ -23,6 +23,90 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// channelRestoreTestCases contains the test cases for the channel restore
|
||||
// scenario.
|
||||
var channelRestoreTestCases = []*lntest.TestCase{
|
||||
{
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for anchor commitment channels.
|
||||
Name: "restore anchor",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, lnrpc.CommitmentType_ANCHORS, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for script-enforced leased channels.
|
||||
Name: "restore leased",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, leasedType, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for zero-conf anchor channels.
|
||||
Name: "restore anchor zero conf",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, lnrpc.CommitmentType_ANCHORS, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface for a zero-conf script-enforced leased channel.
|
||||
Name: "restore leased zero conf",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, leasedType, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Restore a channel back up of a taproot channel that was
|
||||
// confirmed.
|
||||
Name: "restore simple taproot",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, lnrpc.CommitmentType_SIMPLE_TAPROOT, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
// Restore a channel back up of an unconfirmed taproot channel.
|
||||
Name: "restore simple taproot zero conf",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
ht, lnrpc.CommitmentType_SIMPLE_TAPROOT, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "restore from rpc",
|
||||
TestFunc: testChannelBackupRestoreFromRPC,
|
||||
},
|
||||
{
|
||||
Name: "restore from file",
|
||||
TestFunc: testChannelBackupRestoreFromFile,
|
||||
},
|
||||
{
|
||||
Name: "restore during creation",
|
||||
TestFunc: testChannelBackupRestoreDuringCreation,
|
||||
},
|
||||
{
|
||||
Name: "restore during unlock",
|
||||
TestFunc: testChannelBackupRestoreDuringUnlock,
|
||||
},
|
||||
{
|
||||
Name: "restore twice",
|
||||
TestFunc: testChannelBackupRestoreTwice,
|
||||
},
|
||||
}
|
||||
|
||||
type (
|
||||
// nodeRestorer is a function closure that allows each test case to
|
||||
// control exactly *how* the prior node is restored. This might be
|
||||
@ -234,202 +318,167 @@ func (c *chanRestoreScenario) testScenario(ht *lntest.HarnessTest,
|
||||
)
|
||||
}
|
||||
|
||||
// testChannelBackupRestore tests that we're able to recover from, and initiate
|
||||
// the DLP protocol via: the RPC restore command, restoring on unlock, and
|
||||
// restoring from initial wallet creation. We'll also alternate between
|
||||
// restoring form the on disk file, and restoring from the exported RPC command
|
||||
// as well.
|
||||
func testChannelBackupRestoreBasic(ht *lntest.HarnessTest) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
restoreMethod restoreMethodType
|
||||
}{
|
||||
// Restore from backups obtained via the RPC interface. Dave
|
||||
// was the initiator, of the non-advertised channel.
|
||||
{
|
||||
name: "restore from RPC backup",
|
||||
restoreMethod: func(st *lntest.HarnessTest,
|
||||
oldNode *node.HarnessNode,
|
||||
backupFilePath string,
|
||||
password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
// testChannelBackupRestoreFromRPC tests that we're able to recover from, and
|
||||
// initiate the DLP protocol via the RPC restore command.
|
||||
func testChannelBackupRestoreFromRPC(ht *lntest.HarnessTest) {
|
||||
// Restore from backups obtained via the RPC interface. Dave was the
|
||||
// initiator, of the non-advertised channel.
|
||||
restoreMethod := func(st *lntest.HarnessTest, oldNode *node.HarnessNode,
|
||||
backupFilePath string, password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// For this restoration method, we'll grab the
|
||||
// current multi-channel backup from the old
|
||||
// node, and use it to restore a new node
|
||||
// within the closure.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
// For this restoration method, we'll grab the current
|
||||
// multi-channel backup from the old node, and use it to
|
||||
// restore a new node within the closure.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
|
||||
multi := chanBackup.MultiChanBackup.
|
||||
MultiChanBackup
|
||||
multi := chanBackup.MultiChanBackup.
|
||||
MultiChanBackup
|
||||
|
||||
// In our nodeRestorer function, we'll restore
|
||||
// the node from seed, then manually recover
|
||||
// the channel backup.
|
||||
return chanRestoreViaRPC(
|
||||
st, password, mnemonic, multi,
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface.
|
||||
{
|
||||
name: "restore from backup file",
|
||||
restoreMethod: func(st *lntest.HarnessTest,
|
||||
oldNode *node.HarnessNode,
|
||||
backupFilePath string,
|
||||
password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// Read the entire Multi backup stored within
|
||||
// this node's channel.backup file.
|
||||
multi, err := os.ReadFile(backupFilePath)
|
||||
require.NoError(st, err)
|
||||
|
||||
// Now that we have Dave's backup file, we'll
|
||||
// create a new nodeRestorer that will restore
|
||||
// using the on-disk channel.backup.
|
||||
return chanRestoreViaRPC(
|
||||
st, password, mnemonic, multi,
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup as part of node initialization with the
|
||||
// prior mnemonic and new backup seed.
|
||||
{
|
||||
name: "restore during creation",
|
||||
restoreMethod: func(st *lntest.HarnessTest,
|
||||
oldNode *node.HarnessNode,
|
||||
backupFilePath string,
|
||||
password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// First, fetch the current backup state as is,
|
||||
// to obtain our latest Multi.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
backupSnapshot := &lnrpc.ChanBackupSnapshot{
|
||||
MultiChanBackup: chanBackup.
|
||||
MultiChanBackup,
|
||||
}
|
||||
|
||||
// Create a new nodeRestorer that will restore
|
||||
// the node using the Multi backup we just
|
||||
// obtained above.
|
||||
return func() *node.HarnessNode {
|
||||
return st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow,
|
||||
backupSnapshot,
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup once the node has already been
|
||||
// re-created, using the Unlock call.
|
||||
{
|
||||
name: "restore during unlock",
|
||||
restoreMethod: func(st *lntest.HarnessTest,
|
||||
oldNode *node.HarnessNode,
|
||||
backupFilePath string,
|
||||
password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// First, fetch the current backup state as is,
|
||||
// to obtain our latest Multi.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
backupSnapshot := &lnrpc.ChanBackupSnapshot{
|
||||
MultiChanBackup: chanBackup.
|
||||
MultiChanBackup,
|
||||
}
|
||||
|
||||
// Create a new nodeRestorer that will restore
|
||||
// the node with its seed, but no channel
|
||||
// backup, shutdown this initialized node, then
|
||||
// restart it again using Unlock.
|
||||
return func() *node.HarnessNode {
|
||||
newNode := st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow, nil,
|
||||
)
|
||||
st.RestartNodeWithChanBackups(
|
||||
newNode, backupSnapshot,
|
||||
)
|
||||
|
||||
return newNode
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file a second time to
|
||||
// make sure imports can be canceled and later resumed.
|
||||
{
|
||||
name: "restore from backup file twice",
|
||||
restoreMethod: func(st *lntest.HarnessTest,
|
||||
oldNode *node.HarnessNode,
|
||||
backupFilePath string,
|
||||
password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// Read the entire Multi backup stored within
|
||||
// this node's channel.backup file.
|
||||
multi, err := os.ReadFile(backupFilePath)
|
||||
require.NoError(st, err)
|
||||
|
||||
// Now that we have Dave's backup file, we'll
|
||||
// create a new nodeRestorer that will restore
|
||||
// using the on-disk channel.backup.
|
||||
//
|
||||
//nolint:ll
|
||||
backup := &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
|
||||
MultiChanBackup: multi,
|
||||
}
|
||||
|
||||
return func() *node.HarnessNode {
|
||||
newNode := st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow, nil,
|
||||
)
|
||||
|
||||
req := &lnrpc.RestoreChanBackupRequest{
|
||||
Backup: backup,
|
||||
}
|
||||
res := newNode.RPC.RestoreChanBackups(
|
||||
req,
|
||||
)
|
||||
require.EqualValues(
|
||||
st, 1, res.NumRestored,
|
||||
)
|
||||
|
||||
req = &lnrpc.RestoreChanBackupRequest{
|
||||
Backup: backup,
|
||||
}
|
||||
res = newNode.RPC.RestoreChanBackups(
|
||||
req,
|
||||
)
|
||||
require.EqualValues(
|
||||
st, 0, res.NumRestored,
|
||||
)
|
||||
|
||||
return newNode
|
||||
}
|
||||
},
|
||||
},
|
||||
// In our nodeRestorer function, we'll restore the node from
|
||||
// seed, then manually recover the channel backup.
|
||||
return chanRestoreViaRPC(
|
||||
st, password, mnemonic, multi,
|
||||
)
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
tc := testCase
|
||||
success := ht.Run(tc.name, func(t *testing.T) {
|
||||
h := ht.Subtest(t)
|
||||
runChanRestoreScenarioBasic(ht, restoreMethod)
|
||||
}
|
||||
|
||||
runChanRestoreScenarioBasic(h, tc.restoreMethod)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
// testChannelBackupRestoreFromFile tests that we're able to recover from, and
|
||||
// initiate the DLP protocol via the backup file.
|
||||
func testChannelBackupRestoreFromFile(ht *lntest.HarnessTest) {
|
||||
// Restore the backup from the on-disk file, using the RPC interface.
|
||||
restoreMethod := func(st *lntest.HarnessTest, oldNode *node.HarnessNode,
|
||||
backupFilePath string, password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// Read the entire Multi backup stored within this node's
|
||||
// channel.backup file.
|
||||
multi, err := os.ReadFile(backupFilePath)
|
||||
require.NoError(st, err)
|
||||
|
||||
// Now that we have Dave's backup file, we'll create a new
|
||||
// nodeRestorer that will restore using the on-disk
|
||||
// channel.backup.
|
||||
return chanRestoreViaRPC(
|
||||
st, password, mnemonic, multi,
|
||||
)
|
||||
}
|
||||
|
||||
runChanRestoreScenarioBasic(ht, restoreMethod)
|
||||
}
|
||||
|
||||
// testChannelBackupRestoreFromFile tests that we're able to recover from, and
|
||||
// initiate the DLP protocol via restoring from initial wallet creation.
|
||||
func testChannelBackupRestoreDuringCreation(ht *lntest.HarnessTest) {
|
||||
// Restore the backup as part of node initialization with the prior
|
||||
// mnemonic and new backup seed.
|
||||
restoreMethod := func(st *lntest.HarnessTest, oldNode *node.HarnessNode,
|
||||
backupFilePath string, password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// First, fetch the current backup state as is, to obtain our
|
||||
// latest Multi.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
backupSnapshot := &lnrpc.ChanBackupSnapshot{
|
||||
MultiChanBackup: chanBackup.
|
||||
MultiChanBackup,
|
||||
}
|
||||
|
||||
// Create a new nodeRestorer that will restore the node using
|
||||
// the Multi backup we just obtained above.
|
||||
return func() *node.HarnessNode {
|
||||
return st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow,
|
||||
backupSnapshot,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
runChanRestoreScenarioBasic(ht, restoreMethod)
|
||||
}
|
||||
|
||||
// testChannelBackupRestoreFromFile tests that we're able to recover from, and
|
||||
// initiate the DLP protocol via restoring on unlock.
|
||||
func testChannelBackupRestoreDuringUnlock(ht *lntest.HarnessTest) {
|
||||
// Restore the backup once the node has already been re-created, using
|
||||
// the Unlock call.
|
||||
restoreMethod := func(st *lntest.HarnessTest, oldNode *node.HarnessNode,
|
||||
backupFilePath string, password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// First, fetch the current backup state as is, to obtain our
|
||||
// latest Multi.
|
||||
chanBackup := oldNode.RPC.ExportAllChanBackups()
|
||||
backupSnapshot := &lnrpc.ChanBackupSnapshot{
|
||||
MultiChanBackup: chanBackup.
|
||||
MultiChanBackup,
|
||||
}
|
||||
|
||||
// Create a new nodeRestorer that will restore the node with
|
||||
// its seed, but no channel backup, shutdown this initialized
|
||||
// node, then restart it again using Unlock.
|
||||
return func() *node.HarnessNode {
|
||||
newNode := st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow, nil,
|
||||
)
|
||||
st.RestartNodeWithChanBackups(
|
||||
newNode, backupSnapshot,
|
||||
)
|
||||
|
||||
return newNode
|
||||
}
|
||||
}
|
||||
|
||||
runChanRestoreScenarioBasic(ht, restoreMethod)
|
||||
}
|
||||
|
||||
// testChannelBackupRestoreTwice tests that we're able to recover from, and
|
||||
// initiate the DLP protocol twice by alternating between restoring form the on
|
||||
// disk file, and restoring from the exported RPC command.
|
||||
func testChannelBackupRestoreTwice(ht *lntest.HarnessTest) {
|
||||
// Restore the backup from the on-disk file a second time to make sure
|
||||
// imports can be canceled and later resumed.
|
||||
restoreMethod := func(st *lntest.HarnessTest, oldNode *node.HarnessNode,
|
||||
backupFilePath string, password []byte,
|
||||
mnemonic []string) nodeRestorer {
|
||||
|
||||
// Read the entire Multi backup stored within this node's
|
||||
// channel.backup file.
|
||||
multi, err := os.ReadFile(backupFilePath)
|
||||
require.NoError(st, err)
|
||||
|
||||
// Now that we have Dave's backup file, we'll create a new
|
||||
// nodeRestorer that will restore using the on-disk
|
||||
// channel.backup.
|
||||
backup := &lnrpc.RestoreChanBackupRequest_MultiChanBackup{
|
||||
MultiChanBackup: multi,
|
||||
}
|
||||
|
||||
return func() *node.HarnessNode {
|
||||
newNode := st.RestoreNodeWithSeed(
|
||||
"dave", nil, password, mnemonic,
|
||||
"", revocationWindow, nil,
|
||||
)
|
||||
|
||||
req := &lnrpc.RestoreChanBackupRequest{
|
||||
Backup: backup,
|
||||
}
|
||||
newNode.RPC.RestoreChanBackups(req)
|
||||
|
||||
req = &lnrpc.RestoreChanBackupRequest{
|
||||
Backup: backup,
|
||||
}
|
||||
newNode.RPC.RestoreChanBackups(req)
|
||||
|
||||
return newNode
|
||||
}
|
||||
}
|
||||
|
||||
runChanRestoreScenarioBasic(ht, restoreMethod)
|
||||
}
|
||||
|
||||
// runChanRestoreScenarioBasic executes a given test case from end to end,
|
||||
@ -540,79 +589,6 @@ func runChanRestoreScenarioUnConfirmed(ht *lntest.HarnessTest, useFile bool) {
|
||||
crs.testScenario(ht, restoredNodeFunc)
|
||||
}
|
||||
|
||||
// testChannelBackupRestoreCommitTypes tests that we're able to recover from,
|
||||
// and initiate the DLP protocol for different channel commitment types and
|
||||
// zero-conf channel.
|
||||
func testChannelBackupRestoreCommitTypes(ht *lntest.HarnessTest) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
ct lnrpc.CommitmentType
|
||||
zeroConf bool
|
||||
}{
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for anchor commitment channels.
|
||||
{
|
||||
name: "restore from backup file anchors",
|
||||
ct: lnrpc.CommitmentType_ANCHORS,
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for script-enforced leased channels.
|
||||
{
|
||||
name: "restore from backup file script " +
|
||||
"enforced lease",
|
||||
ct: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface, for zero-conf anchor channels.
|
||||
{
|
||||
name: "restore from backup file for zero-conf " +
|
||||
"anchors channel",
|
||||
ct: lnrpc.CommitmentType_ANCHORS,
|
||||
zeroConf: true,
|
||||
},
|
||||
|
||||
// Restore the backup from the on-disk file, using the RPC
|
||||
// interface for a zero-conf script-enforced leased channel.
|
||||
{
|
||||
name: "restore from backup file zero-conf " +
|
||||
"script-enforced leased channel",
|
||||
ct: lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
|
||||
zeroConf: true,
|
||||
},
|
||||
|
||||
// Restore a channel back up of a taproot channel that was
|
||||
// confirmed.
|
||||
{
|
||||
name: "restore from backup taproot",
|
||||
ct: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
zeroConf: false,
|
||||
},
|
||||
|
||||
// Restore a channel back up of an unconfirmed taproot channel.
|
||||
{
|
||||
name: "restore from backup taproot zero conf",
|
||||
ct: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
zeroConf: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
tc := testCase
|
||||
success := ht.Run(tc.name, func(t *testing.T) {
|
||||
h := ht.Subtest(t)
|
||||
|
||||
runChanRestoreScenarioCommitTypes(
|
||||
h, tc.ct, tc.zeroConf,
|
||||
)
|
||||
})
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runChanRestoreScenarioCommitTypes tests that the DLP is applied for
|
||||
// different channel commitment types and zero-conf channel.
|
||||
func runChanRestoreScenarioCommitTypes(ht *lntest.HarnessTest,
|
||||
@ -844,7 +820,7 @@ func runChanRestoreScenarioForceClose(ht *lntest.HarnessTest, zeroConf bool) {
|
||||
// and the on-disk channel.backup are updated each time a channel is
|
||||
// opened/closed.
|
||||
func testChannelBackupUpdates(ht *lntest.HarnessTest) {
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
// First, we'll make a temp directory that we'll use to store our
|
||||
// backup file, so we can check in on it during the test easily.
|
||||
@ -1052,7 +1028,7 @@ func testExportChannelBackup(ht *lntest.HarnessTest) {
|
||||
|
||||
// With Carol up, we'll now connect her to Alice, and open a channel
|
||||
// between them.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.ConnectNodes(carol, alice)
|
||||
|
||||
// Next, we'll open two channels between Alice and Carol back to back.
|
||||
@ -1523,7 +1499,6 @@ func assertTimeLockSwept(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
||||
ht.AssertNumPendingSweeps(dave, 2)
|
||||
|
||||
// Mine a block to trigger the sweeps.
|
||||
ht.MineEmptyBlocks(1)
|
||||
daveSweep := ht.AssertNumTxsInMempool(1)[0]
|
||||
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||
ht.AssertTxInBlock(block, daveSweep)
|
||||
|
@ -48,7 +48,8 @@ func testChannelBalance(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
// Before beginning, make sure alice and bob are connected.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
chanPoint := ht.OpenChannel(
|
||||
@ -62,10 +63,6 @@ func testChannelBalance(ht *lntest.HarnessTest) {
|
||||
|
||||
// Ensure Bob currently has no available balance within the channel.
|
||||
checkChannelBalance(bob, 0, amount-lntest.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
|
||||
@ -118,7 +115,7 @@ func testChannelUnsettledBalance(ht *lntest.HarnessTest) {
|
||||
carol := ht.NewNode("Carol", []string{"--hodl.exit-settle"})
|
||||
|
||||
// Connect Alice to Carol.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.ConnectNodes(alice, carol)
|
||||
|
||||
// Open a channel between Alice and Carol.
|
||||
@ -207,7 +204,4 @@ func testChannelUnsettledBalance(ht *lntest.HarnessTest) {
|
||||
// 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)
|
||||
}
|
||||
|
@ -340,6 +340,22 @@ func runChannelForceClosureTest(ht *lntest.HarnessTest,
|
||||
"sweep transaction not spending from commit")
|
||||
}
|
||||
|
||||
// For neutrino backend, due to it has no mempool, we need to check the
|
||||
// sweep tx has already been saved to db before restarting. This is due
|
||||
// to the possible race,
|
||||
// - the fee bumper returns a TxPublished event, which is received by
|
||||
// the sweeper and the sweep tx is saved to db.
|
||||
// - the sweeper receives a shutdown signal before it receives the
|
||||
// above event.
|
||||
//
|
||||
// TODO(yy): fix the above race.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
// Check that we can find the commitment sweep in our set of
|
||||
// known sweeps, using the simple transaction id ListSweeps
|
||||
// output.
|
||||
ht.AssertSweepFound(alice, sweepingTXID.String(), false, 0)
|
||||
}
|
||||
|
||||
// Restart Alice to ensure that she resumes watching the finalized
|
||||
// commitment sweep txid.
|
||||
ht.RestartNode(alice)
|
||||
@ -771,7 +787,7 @@ func testFailingChannel(ht *lntest.HarnessTest) {
|
||||
// totally unrelated preimage.
|
||||
carol := ht.NewNode("Carol", []string{"--hodl.bogus-settle"})
|
||||
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.ConnectNodes(alice, carol)
|
||||
|
||||
// Let Alice connect and open a channel to Carol,
|
||||
|
@ -50,17 +50,14 @@ type chanFundMaxTestCase struct {
|
||||
private bool
|
||||
}
|
||||
|
||||
// testChannelFundMax checks various channel funding scenarios where the user
|
||||
// instructed the wallet to use all remaining funds.
|
||||
func testChannelFundMax(ht *lntest.HarnessTest) {
|
||||
// testChannelFundMaxError checks various error channel funding scenarios where
|
||||
// the user instructed the wallet to use all remaining funds.
|
||||
func testChannelFundMaxError(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
defer ht.Shutdown(alice)
|
||||
|
||||
bob := ht.NewNode("Bob", args)
|
||||
defer ht.Shutdown(bob)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
@ -95,22 +92,6 @@ func testChannelFundMax(ht *lntest.HarnessTest) {
|
||||
expectedErrStr: "available funds(0.00017877 BTC) " +
|
||||
"below the minimum amount(0.00020000 BTC)",
|
||||
},
|
||||
{
|
||||
name: "wallet amount > min chan " +
|
||||
"size (37000sat)",
|
||||
initialWalletBalance: 37_000,
|
||||
// The transaction fee to open the channel must be
|
||||
// subtracted from Alice's balance.
|
||||
// (since wallet balance < max-chan-size)
|
||||
expectedBalanceAlice: btcutil.Amount(37_000) -
|
||||
fundingFee(1, false),
|
||||
},
|
||||
{
|
||||
name: "wallet amount > max chan size " +
|
||||
"(20000000sat)",
|
||||
initialWalletBalance: 20_000_000,
|
||||
expectedBalanceAlice: lnd.MaxFundingAmount,
|
||||
},
|
||||
// Expects, that if the maximum funding amount for a channel is
|
||||
// pushed to the remote side, then the funding flow is failing
|
||||
// because the push amount has to be less than the local channel
|
||||
@ -140,6 +121,63 @@ func testChannelFundMax(ht *lntest.HarnessTest) {
|
||||
expectedErrStr: "funder balance too small (-8050000) " +
|
||||
"with fee=9050 sat, minimum=708 sat required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
success := ht.Run(
|
||||
testCase.name, func(tt *testing.T) {
|
||||
runFundMaxTestCase(
|
||||
ht, alice, bob, testCase, reserveAmount,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Stop at the first failure. Mimic behavior of original test
|
||||
// framework.
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testChannelFundMaxWalletAmount checks various channel funding scenarios
|
||||
// where the user instructed the wallet to use all remaining funds and succeed.
|
||||
func testChannelFundMaxWalletAmount(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
var testCases = []*chanFundMaxTestCase{
|
||||
{
|
||||
name: "wallet amount > min chan " +
|
||||
"size (37000sat)",
|
||||
initialWalletBalance: 37_000,
|
||||
// The transaction fee to open the channel must be
|
||||
// subtracted from Alice's balance.
|
||||
// (since wallet balance < max-chan-size)
|
||||
expectedBalanceAlice: btcutil.Amount(37_000) -
|
||||
fundingFee(1, false),
|
||||
},
|
||||
{
|
||||
name: "wallet amount > max chan size " +
|
||||
"(20000000sat)",
|
||||
initialWalletBalance: 20_000_000,
|
||||
expectedBalanceAlice: lnd.MaxFundingAmount,
|
||||
},
|
||||
{
|
||||
name: "wallet amount > max chan size, " +
|
||||
"push amount 16766000",
|
||||
@ -147,7 +185,48 @@ func testChannelFundMax(ht *lntest.HarnessTest) {
|
||||
pushAmt: 16_766_000,
|
||||
expectedBalanceAlice: lnd.MaxFundingAmount - 16_766_000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
success := ht.Run(
|
||||
testCase.name, func(tt *testing.T) {
|
||||
runFundMaxTestCase(
|
||||
ht, alice, bob, testCase, reserveAmount,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// Stop at the first failure. Mimic behavior of original test
|
||||
// framework.
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testChannelFundMaxAnchorReserve checks various channel funding scenarios
|
||||
// where the user instructed the wallet to use all remaining funds and its
|
||||
// impact on anchor reserve.
|
||||
func testChannelFundMaxAnchorReserve(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
var testCases = []*chanFundMaxTestCase{
|
||||
{
|
||||
name: "anchor reserved value",
|
||||
initialWalletBalance: 100_000,
|
||||
@ -229,13 +308,12 @@ func runFundMaxTestCase(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
|
||||
|
||||
// Otherwise, if we expect to open a channel use the helper function.
|
||||
chanPoint := ht.OpenChannel(alice, bob, chanParams)
|
||||
cType := ht.GetChannelCommitType(alice, chanPoint)
|
||||
|
||||
// Close the channel between Alice and Bob, asserting
|
||||
// that the channel has been properly closed on-chain.
|
||||
defer ht.CloseChannel(alice, chanPoint)
|
||||
|
||||
cType := ht.GetChannelCommitType(alice, chanPoint)
|
||||
|
||||
// Alice's balance should be her amount subtracted by the commitment
|
||||
// transaction fee.
|
||||
checkChannelBalance(
|
||||
|
@ -15,6 +15,37 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var fundUtxoSelectionTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "funding error",
|
||||
TestFunc: testChannelUtxoSelectionError,
|
||||
},
|
||||
{
|
||||
Name: "selected valid chan size",
|
||||
TestFunc: testUtxoSelectionSelectedValidChanSize,
|
||||
},
|
||||
{
|
||||
Name: "selected valid chan reserve",
|
||||
TestFunc: testUtxoSelectionSelectedValidChanReserve,
|
||||
},
|
||||
{
|
||||
Name: "selected reserve from selected",
|
||||
TestFunc: testUtxoSelectionReserveFromSelected,
|
||||
},
|
||||
{
|
||||
Name: "fundmax",
|
||||
TestFunc: testUtxoSelectionFundmax,
|
||||
},
|
||||
{
|
||||
Name: "fundmax reserve",
|
||||
TestFunc: testUtxoSelectionFundmaxReserve,
|
||||
},
|
||||
{
|
||||
Name: "reused utxo",
|
||||
TestFunc: testUtxoSelectionReuseUTXO,
|
||||
},
|
||||
}
|
||||
|
||||
type chanFundUtxoSelectionTestCase struct {
|
||||
// name is the name of the target test case.
|
||||
name string
|
||||
@ -57,17 +88,15 @@ type chanFundUtxoSelectionTestCase struct {
|
||||
reuseUtxo bool
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testChannelUtxoSelection(ht *lntest.HarnessTest) {
|
||||
// testChannelUtxoSelectionError checks various channel funding error scenarios
|
||||
// where the user instructed the wallet to use a selection funds available in
|
||||
// the wallet.
|
||||
func testChannelUtxoSelectionError(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
defer ht.Shutdown(alice)
|
||||
|
||||
bob := ht.NewNode("Bob", args)
|
||||
defer ht.Shutdown(bob)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
@ -118,73 +147,6 @@ func testChannelUtxoSelection(ht *lntest.HarnessTest) {
|
||||
"create funding transaction, need 0.00210337 " +
|
||||
"BTC only have 0.00100000 BTC available",
|
||||
},
|
||||
// We are spending two selected coins partially out of three
|
||||
// available in the wallet and expect a change output and the
|
||||
// unselected coin as remaining wallet balance.
|
||||
{
|
||||
name: "selected, local amount > " +
|
||||
"min chan size",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, 50_000, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{
|
||||
200_000, 100_000,
|
||||
},
|
||||
localAmt: btcutil.Amount(250_000),
|
||||
expectedBalance: btcutil.Amount(250_000),
|
||||
remainingWalletBalance: btcutil.Amount(350_000) -
|
||||
btcutil.Amount(250_000) - fundingFee(2, true),
|
||||
},
|
||||
// We are spending the entirety of two selected coins out of
|
||||
// three available in the wallet and expect no change output and
|
||||
// the unselected coin as remaining wallet balance.
|
||||
{
|
||||
name: "fundmax, local amount > min " +
|
||||
"chan size",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, 100_000, 50_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{
|
||||
200_000, 50_000,
|
||||
},
|
||||
expectedBalance: btcutil.Amount(200_000) +
|
||||
btcutil.Amount(50_000) - fundingFee(2, false),
|
||||
remainingWalletBalance: btcutil.Amount(100_000),
|
||||
},
|
||||
// Select all coins in wallet and use the maximum available
|
||||
// local amount to fund an anchor channel.
|
||||
{
|
||||
name: "selected, local amount leaves sufficient " +
|
||||
"reserve",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000, 100_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
localAmt: btcutil.Amount(300_000) -
|
||||
reserveAmount - fundingFee(2, true),
|
||||
expectedBalance: btcutil.Amount(300_000) -
|
||||
reserveAmount - fundingFee(2, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
},
|
||||
// Select all coins in wallet towards local amount except for an
|
||||
// anchor reserve portion. Because the UTXOs are sorted by size
|
||||
// by default, the reserve amount is just left in the wallet.
|
||||
{
|
||||
name: "selected, reserve from selected",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount, 100_000,
|
||||
},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
localAmt: btcutil.Amount(300_000) -
|
||||
fundingFee(2, true),
|
||||
expectedBalance: btcutil.Amount(300_000) -
|
||||
fundingFee(2, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
},
|
||||
// Select all coins in wallet and use more than the maximum
|
||||
// available local amount to fund an anchor channel.
|
||||
{
|
||||
@ -203,43 +165,6 @@ func testChannelUtxoSelection(ht *lntest.HarnessTest) {
|
||||
"insufficient funds for fee bumping anchor " +
|
||||
"channel closings",
|
||||
},
|
||||
// We fund an anchor channel with a single coin and just keep
|
||||
// enough funds in the wallet to cover for the anchor reserve.
|
||||
{
|
||||
name: "fundmax, sufficient reserve",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
expectedBalance: btcutil.Amount(200_000) -
|
||||
fundingFee(1, false),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
},
|
||||
// We fund an anchor channel with a single coin and expect the
|
||||
// reserve amount left in the wallet.
|
||||
{
|
||||
name: "fundmax, sufficient reserve from channel " +
|
||||
"balance carve out",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
expectedBalance: btcutil.Amount(200_000) -
|
||||
reserveAmount - fundingFee(1, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
},
|
||||
// Confirm that already spent outputs can't be reused to fund
|
||||
// another channel.
|
||||
{
|
||||
name: "output already spent",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
reuseUtxo: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
@ -258,24 +183,258 @@ func testChannelUtxoSelection(ht *lntest.HarnessTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionSelectedValidChanSize(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// We are spending two selected coins partially out of three available
|
||||
// in the wallet and expect a change output and the unselected coin as
|
||||
// remaining wallet balance.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "selected, local amount > min chan size",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, 50_000, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{
|
||||
200_000, 100_000,
|
||||
},
|
||||
localAmt: btcutil.Amount(250_000),
|
||||
expectedBalance: btcutil.Amount(250_000),
|
||||
remainingWalletBalance: btcutil.Amount(350_000) -
|
||||
btcutil.Amount(250_000) - fundingFee(2, true),
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionSelectedValidChanReserve(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// Select all coins in wallet and use the maximum available
|
||||
// local amount to fund an anchor channel.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "selected, local amount leaves sufficient reserve",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000, 100_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
localAmt: btcutil.Amount(300_000) -
|
||||
reserveAmount - fundingFee(2, true),
|
||||
expectedBalance: btcutil.Amount(300_000) -
|
||||
reserveAmount - fundingFee(2, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionReserveFromSelected(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// Select all coins in wallet towards local amount except for an anchor
|
||||
// reserve portion. Because the UTXOs are sorted by size by default,
|
||||
// the reserve amount is just left in the wallet.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "selected, reserve from selected",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount, 100_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount, 100_000,
|
||||
},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
localAmt: btcutil.Amount(300_000) -
|
||||
fundingFee(2, true),
|
||||
expectedBalance: btcutil.Amount(300_000) -
|
||||
fundingFee(2, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionFundmax(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// We fund an anchor channel with a single coin and just keep enough
|
||||
// funds in the wallet to cover for the anchor reserve.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "fundmax, sufficient reserve",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000, reserveAmount,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
expectedBalance: btcutil.Amount(200_000) -
|
||||
fundingFee(1, false),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionFundmaxReserve(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// We fund an anchor channel with a single coin and expect the reserve
|
||||
// amount left in the wallet.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "fundmax, sufficient reserve from channel " +
|
||||
"balance carve out",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
expectedBalance: btcutil.Amount(200_000) -
|
||||
reserveAmount - fundingFee(1, true),
|
||||
remainingWalletBalance: reserveAmount,
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// testChannelUtxoSelection checks various channel funding scenarios where the
|
||||
// user instructed the wallet to use a selection funds available in the wallet.
|
||||
func testUtxoSelectionReuseUTXO(ht *lntest.HarnessTest) {
|
||||
// Create two new nodes that open a channel between each other for these
|
||||
// tests.
|
||||
args := lntest.NodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
|
||||
alice := ht.NewNode("Alice", args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// Ensure both sides are connected so the funding flow can be properly
|
||||
// executed.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Calculate reserve amount for one channel.
|
||||
reserveResp, _ := alice.RPC.WalletKit.RequiredReserve(
|
||||
context.Background(), &walletrpc.RequiredReserveRequest{
|
||||
AdditionalPublicChannels: 1,
|
||||
},
|
||||
)
|
||||
|
||||
reserveAmount := btcutil.Amount(reserveResp.RequiredReserve)
|
||||
|
||||
// Confirm that already spent outputs can't be reused to fund another
|
||||
// channel.
|
||||
tc := &chanFundUtxoSelectionTestCase{
|
||||
name: "output already spent",
|
||||
initialCoins: []btcutil.Amount{
|
||||
200_000,
|
||||
},
|
||||
selectedCoins: []btcutil.Amount{200_000},
|
||||
reuseUtxo: true,
|
||||
}
|
||||
|
||||
runUtxoSelectionTestCase(ht, alice, bob, tc, reserveAmount)
|
||||
}
|
||||
|
||||
// runUtxoSelectionTestCase runs a single test case asserting that test
|
||||
// conditions are met.
|
||||
func runUtxoSelectionTestCase(ht *lntest.HarnessTest, alice,
|
||||
bob *node.HarnessNode, tc *chanFundUtxoSelectionTestCase,
|
||||
reserveAmount btcutil.Amount) {
|
||||
|
||||
// fund initial coins
|
||||
// Fund initial coins.
|
||||
for _, initialCoin := range tc.initialCoins {
|
||||
ht.FundCoins(initialCoin, alice)
|
||||
}
|
||||
defer func() {
|
||||
// Fund additional coins to sweep in case the wallet contains
|
||||
// dust.
|
||||
ht.FundCoins(100_000, alice)
|
||||
|
||||
// Remove all funds from Alice.
|
||||
sweepNodeWalletAndAssert(ht, alice)
|
||||
}()
|
||||
|
||||
// Create an outpoint lookup for each unique amount.
|
||||
lookup := make(map[int64]*lnrpc.OutPoint)
|
||||
@ -317,9 +476,14 @@ func runUtxoSelectionTestCase(ht *lntest.HarnessTest, alice,
|
||||
// successful, simply check for an error.
|
||||
if tc.chanOpenShouldFail {
|
||||
expectedErr := errors.New(tc.expectedErrStr)
|
||||
ht.OpenChannelAssertErr(
|
||||
alice, bob, chanParams, expectedErr,
|
||||
)
|
||||
ht.OpenChannelAssertErr(alice, bob, chanParams, expectedErr)
|
||||
|
||||
// Fund additional coins to sweep in case the wallet contains
|
||||
// dust.
|
||||
ht.FundCoins(100_000, alice)
|
||||
|
||||
// Remove all funds from Alice.
|
||||
sweepNodeWalletAndAssert(ht, alice)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -218,7 +218,9 @@ func testUpdateChanStatus(ht *lntest.HarnessTest) {
|
||||
// describeGraph RPC request unless explicitly asked for.
|
||||
func testUnannouncedChannels(ht *lntest.HarnessTest) {
|
||||
amount := funding.MaxBtcFundingAmount
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Open a channel between Alice and Bob, ensuring the
|
||||
// channel has been opened properly.
|
||||
@ -232,23 +234,20 @@ func testUnannouncedChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
// One block is enough to make the channel ready for use, since the
|
||||
// nodes have defaultNumConfs=1 set.
|
||||
fundingChanPoint := ht.WaitForChannelOpenEvent(chanOpenUpdate)
|
||||
ht.WaitForChannelOpenEvent(chanOpenUpdate)
|
||||
|
||||
// Alice should have 1 edge in her graph.
|
||||
ht.AssertNumActiveEdges(alice, 1, true)
|
||||
ht.AssertNumEdges(alice, 1, true)
|
||||
|
||||
// Channels should not be announced yet, hence Alice should have no
|
||||
// announced edges in her graph.
|
||||
ht.AssertNumActiveEdges(alice, 0, false)
|
||||
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.AssertNumActiveEdges(alice, 1, false)
|
||||
|
||||
// Close the channel used during the test.
|
||||
ht.CloseChannel(alice, fundingChanPoint)
|
||||
ht.AssertNumEdges(alice, 1, false)
|
||||
}
|
||||
|
||||
func testGraphTopologyNotifications(ht *lntest.HarnessTest) {
|
||||
@ -267,14 +266,10 @@ func testGraphTopologyNtfns(ht *lntest.HarnessTest, pinned bool) {
|
||||
|
||||
// Spin up Bob first, since we will need to grab his pubkey when
|
||||
// starting Alice to test pinned syncing.
|
||||
bob := ht.Bob
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
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
|
||||
@ -285,8 +280,7 @@ func testGraphTopologyNtfns(ht *lntest.HarnessTest, pinned bool) {
|
||||
}
|
||||
}
|
||||
|
||||
alice := ht.Alice
|
||||
ht.RestartNodeWithExtraArgs(alice, aliceArgs)
|
||||
alice := ht.NewNodeWithCoins("Alice", aliceArgs)
|
||||
|
||||
// Connect Alice and Bob.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
@ -370,16 +364,15 @@ func testGraphTopologyNtfns(ht *lntest.HarnessTest, pinned bool) {
|
||||
// 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 *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
advertisedAddrs := []string{
|
||||
"192.168.1.1:8333",
|
||||
@ -403,7 +396,7 @@ func testNodeAnnouncement(ht *lntest.HarnessTest) {
|
||||
// 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(
|
||||
ht.OpenChannel(
|
||||
bob, dave, lntest.OpenChannelParams{Amt: 1000000},
|
||||
)
|
||||
|
||||
@ -425,16 +418,15 @@ func testNodeAnnouncement(ht *lntest.HarnessTest) {
|
||||
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 broadcast
|
||||
// with the right information after updating our node.
|
||||
func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
var lndArgs []string
|
||||
|
||||
@ -530,7 +522,7 @@ func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
|
||||
// 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(
|
||||
ht.OpenChannel(
|
||||
bob, dave, lntest.OpenChannelParams{
|
||||
Amt: 1000000,
|
||||
},
|
||||
@ -660,9 +652,6 @@ func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
|
||||
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.
|
||||
|
@ -30,19 +30,16 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
// Create a channel Alice->Bob.
|
||||
chanPoint := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil}, lntest.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, bob := nodes[0], nodes[1]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
// Alice and Bob should see each other's ChannelUpdates, advertising the
|
||||
// default routing policies. We do not currently set any inbound fees.
|
||||
@ -423,11 +420,6 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
|
||||
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
|
||||
@ -441,7 +433,8 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
|
||||
func testSendUpdateDisableChannel(ht *lntest.HarnessTest) {
|
||||
const chanAmt = 100000
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
|
||||
// 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
|
||||
@ -678,7 +671,9 @@ func testUpdateChannelPolicyForPrivateChannel(ht *lntest.HarnessTest) {
|
||||
|
||||
// We'll create the following topology first,
|
||||
// Alice <--public:100k--> Bob <--private:100k--> Carol
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob.
|
||||
chanPointAliceBob := ht.OpenChannel(
|
||||
@ -773,10 +768,6 @@ func testUpdateChannelPolicyForPrivateChannel(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
@ -787,16 +778,14 @@ func testUpdateChannelPolicyFeeRateAccuracy(ht *lntest.HarnessTest) {
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
// Create a channel Alice -> Bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
chanPoint := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil}, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
},
|
||||
)
|
||||
|
||||
// Nodes that we need to make sure receive the channel updates.
|
||||
nodes := []*node.HarnessNode{alice, bob}
|
||||
alice := nodes[0]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
baseFee := int64(1500)
|
||||
timeLockDelta := uint32(66)
|
||||
@ -847,8 +836,6 @@ func testUpdateChannelPolicyFeeRateAccuracy(ht *lntest.HarnessTest) {
|
||||
|
||||
// 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
|
||||
|
@ -1,11 +1,13 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -58,7 +60,8 @@ func testCoopCloseWithExternalDelivery(ht *lntest.HarnessTest) {
|
||||
func testCoopCloseWithExternalDeliveryImpl(ht *lntest.HarnessTest,
|
||||
upfrontShutdown bool, deliveryAddressType lnrpc.AddressType) {
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("bob", nil)
|
||||
ht.ConnectNodes(alice, bob)
|
||||
|
||||
// Here we generate a final delivery address in bob's wallet but set by
|
||||
@ -113,11 +116,20 @@ func testCoopCloseWithExternalDeliveryImpl(ht *lntest.HarnessTest,
|
||||
// assertion. We want to ensure that even though alice's delivery
|
||||
// address is set to an address in bob's wallet, we should still show
|
||||
// the balance as settled.
|
||||
closed := alice.RPC.ClosedChannels(&lnrpc.ClosedChannelsRequest{
|
||||
Cooperative: true,
|
||||
})
|
||||
err = wait.NoError(func() error {
|
||||
closed := alice.RPC.ClosedChannels(&lnrpc.ClosedChannelsRequest{
|
||||
Cooperative: true,
|
||||
})
|
||||
|
||||
// The settled balance should never be zero at this point.
|
||||
require.NotZero(ht, len(closed.Channels))
|
||||
require.NotZero(ht, closed.Channels[0].SettledBalance)
|
||||
if len(closed.Channels) == 0 {
|
||||
return fmt.Errorf("expected closed channel not found")
|
||||
}
|
||||
|
||||
if closed.Channels[0].SettledBalance == 0 {
|
||||
return fmt.Errorf("expected settled balance to be zero")
|
||||
}
|
||||
|
||||
return nil
|
||||
}, defaultTimeout)
|
||||
require.NoError(ht, err, "timeout checking closed channels")
|
||||
}
|
||||
|
@ -37,7 +37,8 @@ func testCoopCloseWithHtlcs(ht *lntest.HarnessTest) {
|
||||
// channel party initiates a channel shutdown while an HTLC is still pending on
|
||||
// the channel.
|
||||
func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("bob", nil)
|
||||
ht.ConnectNodes(alice, bob)
|
||||
|
||||
// Here we set up a channel between Alice and Bob, beginning with a
|
||||
@ -128,7 +129,8 @@ func coopCloseWithHTLCs(ht *lntest.HarnessTest) {
|
||||
// process continues as expected even if a channel re-establish happens after
|
||||
// one party has already initiated the shutdown.
|
||||
func coopCloseWithHTLCsWithRestart(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("bob", nil)
|
||||
ht.ConnectNodes(alice, bob)
|
||||
|
||||
// Open a channel between Alice and Bob with the balance split equally.
|
||||
|
@ -27,20 +27,18 @@ func testCustomFeatures(ht *lntest.HarnessTest) {
|
||||
fmt.Sprintf("--protocol.custom-nodeann=%v", customNodeAnn),
|
||||
fmt.Sprintf("--protocol.custom-invoice=%v", customInvoice),
|
||||
}
|
||||
ht.RestartNodeWithExtraArgs(ht.Alice, extraArgs)
|
||||
cfgs := [][]string{extraArgs, nil}
|
||||
|
||||
// Connect nodes and open a channel so that Alice will be included
|
||||
// in Bob's graph.
|
||||
ht.ConnectNodes(ht.Alice, ht.Bob)
|
||||
chanPoint := ht.OpenChannel(
|
||||
ht.Alice, ht.Bob, lntest.OpenChannelParams{Amt: 1000000},
|
||||
_, nodes := ht.CreateSimpleNetwork(
|
||||
cfgs, lntest.OpenChannelParams{Amt: 1000000},
|
||||
)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// Check that Alice's custom feature bit was sent to Bob in her init
|
||||
// message.
|
||||
peers := ht.Bob.RPC.ListPeers()
|
||||
peers := bob.RPC.ListPeers()
|
||||
require.Len(ht, peers.Peers, 1)
|
||||
require.Equal(ht, peers.Peers[0].PubKey, ht.Alice.PubKeyStr)
|
||||
require.Equal(ht, peers.Peers[0].PubKey, alice.PubKeyStr)
|
||||
|
||||
_, customInitSet := peers.Peers[0].Features[customInit]
|
||||
require.True(ht, customInitSet)
|
||||
@ -51,7 +49,7 @@ func testCustomFeatures(ht *lntest.HarnessTest) {
|
||||
|
||||
// Assert that Alice's custom feature bit is contained in the node
|
||||
// announcement sent to Bob.
|
||||
updates := ht.AssertNumNodeAnns(ht.Bob, ht.Alice.PubKeyStr, 1)
|
||||
updates := ht.AssertNumNodeAnns(bob, alice.PubKeyStr, 1)
|
||||
features := updates[len(updates)-1].Features
|
||||
_, customFeature := features[customNodeAnn]
|
||||
require.True(ht, customFeature)
|
||||
@ -60,8 +58,8 @@ func testCustomFeatures(ht *lntest.HarnessTest) {
|
||||
)
|
||||
|
||||
// Assert that Alice's custom feature bit is included in invoices.
|
||||
invoice := ht.Alice.RPC.AddInvoice(&lnrpc.Invoice{})
|
||||
payReq := ht.Alice.RPC.DecodePayReq(invoice.PaymentRequest)
|
||||
invoice := alice.RPC.AddInvoice(&lnrpc.Invoice{})
|
||||
payReq := alice.RPC.DecodePayReq(invoice.PaymentRequest)
|
||||
_, customInvoiceSet := payReq.Features[customInvoice]
|
||||
require.True(ht, customInvoiceSet)
|
||||
assertFeatureNotInSet(
|
||||
@ -79,9 +77,7 @@ func testCustomFeatures(ht *lntest.HarnessTest) {
|
||||
},
|
||||
},
|
||||
}
|
||||
ht.Alice.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
|
||||
|
||||
ht.CloseChannel(ht.Alice, chanPoint)
|
||||
alice.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
|
||||
}
|
||||
|
||||
// assertFeatureNotInSet checks that the features provided aren't contained in
|
||||
|
@ -14,8 +14,6 @@ import (
|
||||
// types (within the message type range usually reserved for protocol messages)
|
||||
// via the send and subscribe custom message APIs.
|
||||
func testCustomMessage(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
var (
|
||||
overrideType1 uint32 = 554
|
||||
overrideType2 uint32 = 555
|
||||
@ -27,7 +25,8 @@ func testCustomMessage(ht *lntest.HarnessTest) {
|
||||
extraArgs := []string{
|
||||
fmt.Sprintf(msgOverrideArg, overrideType1),
|
||||
}
|
||||
ht.RestartNodeWithExtraArgs(alice, extraArgs)
|
||||
alice := ht.NewNode("Alice", extraArgs)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
|
||||
// Subscribe Alice to custom messages before we send any, so that we
|
||||
// don't miss any.
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -93,23 +94,19 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
// added to the invoice always have enough liquidity, but here we check
|
||||
// that the prober uses the more expensive route.
|
||||
ht.EnsureConnected(mts.bob, paula)
|
||||
channelPointBobPaula := ht.OpenChannel(
|
||||
mts.bob, paula, lntest.OpenChannelParams{
|
||||
Private: true,
|
||||
Amt: 90_000,
|
||||
PushAmt: 69_000,
|
||||
},
|
||||
)
|
||||
ht.OpenChannel(mts.bob, paula, lntest.OpenChannelParams{
|
||||
Private: true,
|
||||
Amt: 90_000,
|
||||
PushAmt: 69_000,
|
||||
})
|
||||
|
||||
ht.EnsureConnected(mts.eve, paula)
|
||||
channelPointEvePaula := ht.OpenChannel(
|
||||
mts.eve, paula, lntest.OpenChannelParams{
|
||||
Private: true,
|
||||
Amt: 1_000_000,
|
||||
},
|
||||
)
|
||||
ht.OpenChannel(mts.eve, paula, lntest.OpenChannelParams{
|
||||
Private: true,
|
||||
Amt: 1_000_000,
|
||||
})
|
||||
|
||||
bobsPrivChannels := ht.Bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{
|
||||
bobsPrivChannels := mts.bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{
|
||||
PrivateOnly: true,
|
||||
})
|
||||
require.Len(ht, bobsPrivChannels.Channels, 1)
|
||||
@ -242,6 +239,8 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
locktime := initialBlockHeight + defaultTimelock +
|
||||
int64(routing.BlockPadding)
|
||||
|
||||
noChanNode := ht.NewNode("ImWithoutChannels", nil)
|
||||
|
||||
var testCases = []*estimateRouteFeeTestCase{
|
||||
// Single hop payment is free.
|
||||
{
|
||||
@ -303,10 +302,8 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
{
|
||||
name: "single hop hint, destination " +
|
||||
"without channels",
|
||||
probing: true,
|
||||
destination: ht.NewNode(
|
||||
"ImWithoutChannels", nil,
|
||||
),
|
||||
probing: true,
|
||||
destination: noChanNode,
|
||||
routeHints: singleRouteHint,
|
||||
expectedRoutingFeesMsat: feeACBP,
|
||||
expectedCltvDelta: locktime + deltaACBP,
|
||||
@ -356,12 +353,6 @@ func testEstimateRouteFee(ht *lntest.HarnessTest) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
mts.ht.CloseChannelAssertPending(mts.bob, channelPointBobPaula, false)
|
||||
mts.ht.CloseChannelAssertPending(mts.eve, channelPointEvePaula, false)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||
|
||||
mts.closeChannels()
|
||||
}
|
||||
|
||||
// runTestCase runs a single test case asserting that test conditions are met.
|
||||
@ -376,7 +367,7 @@ func runFeeEstimationTestCase(ht *lntest.HarnessTest,
|
||||
)
|
||||
feeReq = &routerrpc.RouteFeeRequest{
|
||||
PaymentRequest: payReqs[0],
|
||||
Timeout: 10,
|
||||
Timeout: uint32(wait.PaymentTimeout.Seconds()),
|
||||
}
|
||||
} else {
|
||||
feeReq = &routerrpc.RouteFeeRequest{
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntest/rpc"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
@ -24,36 +25,18 @@ func testExperimentalEndorsement(ht *lntest.HarnessTest) {
|
||||
// testEndorsement sets up a 5 hop network and tests propagation of
|
||||
// experimental endorsement signals.
|
||||
func testEndorsement(ht *lntest.HarnessTest, aliceEndorse bool) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
carol := ht.NewNode(
|
||||
"carol", []string{"--protocol.no-experimental-endorsement"},
|
||||
cfg := node.CfgAnchor
|
||||
carolCfg := append(
|
||||
[]string{"--protocol.no-experimental-endorsement"}, cfg...,
|
||||
)
|
||||
dave := ht.NewNode("dave", nil)
|
||||
eve := ht.NewNode("eve", nil)
|
||||
cfgs := [][]string{cfg, cfg, carolCfg, cfg, cfg}
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
ht.EnsureConnected(bob, carol)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
ht.EnsureConnected(dave, eve)
|
||||
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
// Open and wait for channels.
|
||||
const chanAmt = btcutil.Amount(300000)
|
||||
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.OpenChannelRequest{
|
||||
{Local: alice, Remote: bob, Param: p},
|
||||
{Local: bob, Remote: carol, Param: p},
|
||||
{Local: carol, Remote: dave, Param: p},
|
||||
{Local: dave, Remote: eve, Param: p},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC, cpCD, cpDE := resp[0], resp[1], resp[2], resp[3]
|
||||
|
||||
// Make sure Alice is aware of Bob=>Carol=>Dave=>Eve channels.
|
||||
ht.AssertChannelInGraph(alice, cpBC)
|
||||
ht.AssertChannelInGraph(alice, cpCD)
|
||||
ht.AssertChannelInGraph(alice, cpDE)
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol, dave, eve := nodes[0], nodes[1], nodes[2], nodes[3],
|
||||
nodes[4]
|
||||
|
||||
bobIntercept, cancelBob := bob.RPC.HtlcInterceptor()
|
||||
defer cancelBob()
|
||||
@ -94,11 +77,6 @@ func testEndorsement(ht *lntest.HarnessTest, aliceEndorse bool) {
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], invoice.RPreimage)
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
ht.CloseChannel(carol, cpCD)
|
||||
ht.CloseChannel(dave, cpDE)
|
||||
}
|
||||
|
||||
func validateEndorsedAndResume(ht *lntest.HarnessTest,
|
||||
|
@ -42,23 +42,24 @@ type interceptorTestCase struct {
|
||||
// testForwardInterceptorDedupHtlc tests that upon reconnection, duplicate
|
||||
// HTLCs aren't re-notified using the HTLC interceptor API.
|
||||
func testForwardInterceptorDedupHtlc(ht *lntest.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 := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.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.AssertChannelInGraph(alice, cpBC)
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
cpAB := chanPoints[0]
|
||||
|
||||
// Init the scenario.
|
||||
ts := &interceptorTestScenario{
|
||||
ht: ht,
|
||||
alice: alice,
|
||||
bob: bob,
|
||||
carol: carol,
|
||||
}
|
||||
|
||||
// Connect the interceptor.
|
||||
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -177,10 +178,6 @@ func testForwardInterceptorDedupHtlc(ht *lntest.HarnessTest) {
|
||||
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.
|
||||
@ -194,22 +191,24 @@ func testForwardInterceptorDedupHtlc(ht *lntest.HarnessTest) {
|
||||
// 4. When Interceptor disconnects it resumes all held htlcs, which result in
|
||||
// valid payment (invoice is settled).
|
||||
func testForwardInterceptorBasic(ht *lntest.HarnessTest) {
|
||||
ts := newInterceptorTestScenario(ht)
|
||||
|
||||
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
||||
|
||||
// Open and wait for channels.
|
||||
const chanAmt = btcutil.Amount(300000)
|
||||
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.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.AssertChannelInGraph(alice, cpBC)
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
cpAB := chanPoints[0]
|
||||
|
||||
// Init the scenario.
|
||||
ts := &interceptorTestScenario{
|
||||
ht: ht,
|
||||
alice: alice,
|
||||
bob: bob,
|
||||
carol: carol,
|
||||
}
|
||||
|
||||
// Connect the interceptor.
|
||||
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -345,32 +344,28 @@ func testForwardInterceptorBasic(ht *lntest.HarnessTest) {
|
||||
case <-time.After(defaultTimeout):
|
||||
require.Fail(ht, "timeout waiting for interceptor error")
|
||||
}
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
}
|
||||
|
||||
// testForwardInterceptorModifiedHtlc tests that the interceptor can modify the
|
||||
// amount and custom records of an intercepted HTLC and resume it.
|
||||
func testForwardInterceptorModifiedHtlc(ht *lntest.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 := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.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.AssertChannelInGraph(alice, cpBC)
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
|
||||
// Init the scenario.
|
||||
ts := &interceptorTestScenario{
|
||||
ht: ht,
|
||||
alice: alice,
|
||||
bob: bob,
|
||||
carol: carol,
|
||||
}
|
||||
|
||||
// Connect an interceptor to Bob's node.
|
||||
bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -451,34 +446,21 @@ func testForwardInterceptorModifiedHtlc(ht *lntest.HarnessTest) {
|
||||
var preimage lntypes.Preimage
|
||||
copy(preimage[:], invoice.RPreimage)
|
||||
ht.AssertPaymentStatus(alice, preimage, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
}
|
||||
|
||||
// testForwardInterceptorWireRecords tests that the interceptor can read any
|
||||
// wire custom records provided by the sender of a payment as part of the
|
||||
// update_add_htlc message.
|
||||
func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
ts := newInterceptorTestScenario(ht)
|
||||
|
||||
alice, bob, carol, dave := ts.alice, ts.bob, ts.carol, ts.dave
|
||||
|
||||
// Open and wait for channels.
|
||||
const chanAmt = btcutil.Amount(300000)
|
||||
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.OpenChannelRequest{
|
||||
{Local: alice, Remote: bob, Param: p},
|
||||
{Local: bob, Remote: carol, Param: p},
|
||||
{Local: carol, Remote: dave, Param: p},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC, cpCD := resp[0], resp[1], resp[2]
|
||||
|
||||
// Make sure Alice is aware of channel Bob=>Carol.
|
||||
ht.AssertChannelInGraph(alice, cpBC)
|
||||
// Initialize the test context with 4 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol, dave := nodes[0], nodes[1], nodes[2], nodes[3]
|
||||
|
||||
// Connect an interceptor to Bob's node.
|
||||
bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -579,11 +561,6 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
ht.CloseChannel(carol, cpCD)
|
||||
}
|
||||
|
||||
// testForwardInterceptorRestart tests that the interceptor can read any wire
|
||||
@ -591,25 +568,15 @@ func testForwardInterceptorWireRecords(ht *lntest.HarnessTest) {
|
||||
// update_add_htlc message and that those records are persisted correctly and
|
||||
// re-sent on node restart.
|
||||
func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
ts := newInterceptorTestScenario(ht)
|
||||
|
||||
alice, bob, carol, dave := ts.alice, ts.bob, ts.carol, ts.dave
|
||||
|
||||
// Open and wait for channels.
|
||||
const chanAmt = btcutil.Amount(300000)
|
||||
p := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.OpenChannelRequest{
|
||||
{Local: alice, Remote: bob, Param: p},
|
||||
{Local: bob, Remote: carol, Param: p},
|
||||
{Local: carol, Remote: dave, Param: p},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC, cpCD := resp[0], resp[1], resp[2]
|
||||
|
||||
// Make sure Alice is aware of channels Bob=>Carol and Carol=>Dave.
|
||||
ht.AssertChannelInGraph(alice, cpBC)
|
||||
ht.AssertChannelInGraph(alice, cpCD)
|
||||
// Initialize the test context with 4 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol, dave := nodes[0], nodes[1], nodes[2], nodes[3]
|
||||
|
||||
// Connect an interceptor to Bob's node.
|
||||
bobInterceptor, cancelBobInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -742,47 +709,13 @@ func testForwardInterceptorRestart(ht *lntest.HarnessTest) {
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
ht.CloseChannel(carol, cpCD)
|
||||
}
|
||||
|
||||
// interceptorTestScenario is a helper struct to hold the test context and
|
||||
// provide the needed functionality.
|
||||
type interceptorTestScenario struct {
|
||||
ht *lntest.HarnessTest
|
||||
alice, bob, carol, dave *node.HarnessNode
|
||||
}
|
||||
|
||||
// newInterceptorTestScenario initializes a new test scenario with three nodes
|
||||
// and connects them to have the following topology,
|
||||
//
|
||||
// Alice --> Bob --> Carol --> Dave
|
||||
//
|
||||
// Among them, Alice and Bob are standby nodes and Carol is a new node.
|
||||
func newInterceptorTestScenario(
|
||||
ht *lntest.HarnessTest) *interceptorTestScenario {
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
carol := ht.NewNode("carol", nil)
|
||||
dave := ht.NewNode("dave", nil)
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
ht.EnsureConnected(bob, carol)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
|
||||
// So that carol can open channels.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
return &interceptorTestScenario{
|
||||
ht: ht,
|
||||
alice: alice,
|
||||
bob: bob,
|
||||
carol: carol,
|
||||
dave: dave,
|
||||
}
|
||||
ht *lntest.HarnessTest
|
||||
alice, bob, carol *node.HarnessNode
|
||||
}
|
||||
|
||||
// prepareTestCases prepares 4 tests:
|
||||
|
@ -20,169 +20,233 @@ import (
|
||||
"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 *lntest.HarnessTest) {
|
||||
// Run through the test with combinations of all the different
|
||||
// commitment types.
|
||||
allTypes := []lnrpc.CommitmentType{
|
||||
lnrpc.CommitmentType_STATIC_REMOTE_KEY,
|
||||
lnrpc.CommitmentType_ANCHORS,
|
||||
lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
}
|
||||
// basicFundingTestCases defines the test cases for the basic funding test.
|
||||
var basicFundingTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "basic flow static key remote",
|
||||
TestFunc: testBasicChannelFundingStaticRemote,
|
||||
},
|
||||
{
|
||||
Name: "basic flow anchor",
|
||||
TestFunc: testBasicChannelFundingAnchor,
|
||||
},
|
||||
{
|
||||
Name: "basic flow simple taproot",
|
||||
TestFunc: testBasicChannelFundingSimpleTaproot,
|
||||
},
|
||||
}
|
||||
|
||||
// testFunding is a function closure that takes Carol and Dave's
|
||||
// commitment types and test the funding flow.
|
||||
testFunding := func(ht *lntest.HarnessTest, carolCommitType,
|
||||
daveCommitType lnrpc.CommitmentType) {
|
||||
// allFundingTypes defines the channel types to test for the basic funding
|
||||
// test.
|
||||
var allFundingTypes = []lnrpc.CommitmentType{
|
||||
lnrpc.CommitmentType_STATIC_REMOTE_KEY,
|
||||
lnrpc.CommitmentType_ANCHORS,
|
||||
lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
}
|
||||
|
||||
// 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 := lntest.NodeArgsForCommitType(carolCommitType)
|
||||
carol := ht.NewNode("Carol", carolArgs)
|
||||
// testBasicChannelFundingStaticRemote performs a test exercising expected
|
||||
// behavior from a basic funding workflow. The test creates a new channel
|
||||
// between Carol and Dave, with Carol using the static remote key commitment
|
||||
// type, and Dave using allFundingTypes.
|
||||
func testBasicChannelFundingStaticRemote(ht *lntest.HarnessTest) {
|
||||
carolCommitType := lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||
|
||||
// Each time, we'll send Carol a new set of coins in order to
|
||||
// fund the channel.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
daveArgs := lntest.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)
|
||||
|
||||
var privateChan bool
|
||||
|
||||
// If this is to be a taproot channel type, then it needs to be
|
||||
// private, otherwise it'll be rejected by Dave.
|
||||
//
|
||||
// TODO(roasbeef): lift after gossip 1.75
|
||||
if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
privateChan = true
|
||||
}
|
||||
|
||||
// If carol wants taproot, but dave wants something
|
||||
// else, then we'll assert that the channel negotiation
|
||||
// attempt fails.
|
||||
if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT &&
|
||||
daveCommitType != lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
|
||||
expectedErr := fmt.Errorf("requested channel type " +
|
||||
"not supported")
|
||||
amt := funding.MaxBtcFundingAmount
|
||||
ht.OpenChannelAssertErr(
|
||||
carol, dave, lntest.OpenChannelParams{
|
||||
Private: privateChan,
|
||||
Amt: amt,
|
||||
CommitmentType: carolCommitType,
|
||||
}, expectedErr,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
carolChan, daveChan, closeChan := basicChannelFundingTest(
|
||||
ht, carol, dave, nil, privateChan, &carolCommitType,
|
||||
)
|
||||
|
||||
// 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 taproot, type will be what Carol supports.
|
||||
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
|
||||
// Dave supports anchors, type will be what Carol supports.
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
// However if Alice wants taproot chans, then we
|
||||
// downgrade to anchors as this is still using implicit
|
||||
// negotiation.
|
||||
if expType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
expType = lnrpc.CommitmentType_ANCHORS
|
||||
}
|
||||
|
||||
// Dave only supports tweakless, channel will be downgraded to
|
||||
// this type if Carol supports anchors.
|
||||
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
||||
switch expType {
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||
}
|
||||
|
||||
// Dave only supports 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: //nolint:ll
|
||||
|
||||
case expType == lnrpc.CommitmentType_LEGACY &&
|
||||
chansCommitType == lnrpc.CommitmentType_LEGACY:
|
||||
|
||||
case expType == lnrpc.CommitmentType_SIMPLE_TAPROOT &&
|
||||
chansCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
|
||||
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
|
||||
for _, daveCommitType := range allFundingTypes {
|
||||
cc := carolCommitType
|
||||
dc := daveCommitType
|
||||
|
||||
testName := fmt.Sprintf(
|
||||
"carol_commit=%v,dave_commit=%v", cc, dc,
|
||||
)
|
||||
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)
|
||||
})
|
||||
success := ht.Run(testName, func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runBasicFundingTest(st, cc, dc)
|
||||
})
|
||||
|
||||
if !success {
|
||||
break test
|
||||
}
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testBasicChannelFundingAnchor performs a test exercising expected behavior
|
||||
// from a basic funding workflow. The test creates a new channel between Carol
|
||||
// and Dave, with Carol using the anchor commitment type, and Dave using
|
||||
// allFundingTypes.
|
||||
func testBasicChannelFundingAnchor(ht *lntest.HarnessTest) {
|
||||
carolCommitType := lnrpc.CommitmentType_ANCHORS
|
||||
|
||||
// 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 _, daveCommitType := range allFundingTypes {
|
||||
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)
|
||||
runBasicFundingTest(st, cc, dc)
|
||||
})
|
||||
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testBasicChannelFundingSimpleTaproot performs a test exercising expected
|
||||
// behavior from a basic funding workflow. The test creates a new channel
|
||||
// between Carol and Dave, with Carol using the simple taproot commitment type,
|
||||
// and Dave using allFundingTypes.
|
||||
func testBasicChannelFundingSimpleTaproot(ht *lntest.HarnessTest) {
|
||||
carolCommitType := lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
// 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 _, daveCommitType := range allFundingTypes {
|
||||
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)
|
||||
runBasicFundingTest(st, cc, dc)
|
||||
})
|
||||
|
||||
if !success {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runBasicFundingTest is a helper function that takes Carol and Dave's
|
||||
// commitment types and test the funding flow.
|
||||
func runBasicFundingTest(ht *lntest.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 := lntest.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 := lntest.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)
|
||||
|
||||
var privateChan bool
|
||||
|
||||
// If this is to be a taproot channel type, then it needs to be
|
||||
// private, otherwise it'll be rejected by Dave.
|
||||
//
|
||||
// TODO(roasbeef): lift after gossip 1.75
|
||||
if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
privateChan = true
|
||||
}
|
||||
|
||||
// If carol wants taproot, but dave wants something else, then we'll
|
||||
// assert that the channel negotiation attempt fails.
|
||||
if carolCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT &&
|
||||
daveCommitType != lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
|
||||
expectedErr := fmt.Errorf("requested channel type " +
|
||||
"not supported")
|
||||
amt := funding.MaxBtcFundingAmount
|
||||
ht.OpenChannelAssertErr(
|
||||
carol, dave, lntest.OpenChannelParams{
|
||||
Private: privateChan,
|
||||
Amt: amt,
|
||||
CommitmentType: carolCommitType,
|
||||
}, expectedErr,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
carolChan, daveChan := basicChannelFundingTest(
|
||||
ht, carol, dave, nil, privateChan, &carolCommitType,
|
||||
)
|
||||
|
||||
// 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 taproot, type will be what Carol supports.
|
||||
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
|
||||
// Dave supports anchors, type will be what Carol supports.
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
// However if Alice wants taproot chans, then we downgrade to
|
||||
// anchors as this is still using implicit negotiation.
|
||||
if expType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
expType = lnrpc.CommitmentType_ANCHORS
|
||||
}
|
||||
|
||||
// Dave only supports tweakless, channel will be downgraded to this
|
||||
// type if Carol supports anchors.
|
||||
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
|
||||
switch expType {
|
||||
case lnrpc.CommitmentType_ANCHORS:
|
||||
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||
case lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
|
||||
}
|
||||
|
||||
// Dave only supports 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:
|
||||
|
||||
case expType == lnrpc.CommitmentType_SIMPLE_TAPROOT &&
|
||||
chansCommitType == lnrpc.CommitmentType_SIMPLE_TAPROOT:
|
||||
|
||||
default:
|
||||
ht.Fatalf("expected nodes to signal commit type %v, instead "+
|
||||
"got %v", expType, chansCommitType)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -190,7 +254,7 @@ test:
|
||||
func basicChannelFundingTest(ht *lntest.HarnessTest,
|
||||
alice, bob *node.HarnessNode, fundingShim *lnrpc.FundingShim,
|
||||
privateChan bool, commitType *lnrpc.CommitmentType) (*lnrpc.Channel,
|
||||
*lnrpc.Channel, func()) {
|
||||
*lnrpc.Channel) {
|
||||
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := btcutil.Amount(100000)
|
||||
@ -262,14 +326,7 @@ func basicChannelFundingTest(ht *lntest.HarnessTest,
|
||||
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
|
||||
return aliceChannel, bobChannel
|
||||
}
|
||||
|
||||
// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can
|
||||
@ -282,8 +339,7 @@ func testUnconfirmedChannelFunding(ht *lntest.HarnessTest) {
|
||||
|
||||
// We'll start off by creating a node for Carol.
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// We'll send her some unconfirmed funds.
|
||||
ht.FundCoinsUnconfirmed(2*chanAmt, carol)
|
||||
@ -378,38 +434,27 @@ func testUnconfirmedChannelFunding(ht *lntest.HarnessTest) {
|
||||
// spend and the funding tx.
|
||||
ht.MineBlocksAndAssertNumTxes(6, 2)
|
||||
|
||||
chanPoint := ht.WaitForChannelOpenEvent(chanOpenUpdate)
|
||||
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 *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// 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)
|
||||
ht.ConnectNodes(carol, alice)
|
||||
|
||||
runChannelFundingInputTypes(ht, ht.Alice, carol)
|
||||
runChannelFundingInputTypes(ht, alice, carol)
|
||||
}
|
||||
|
||||
// runChannelFundingInputTypes tests that any type of supported input type can
|
||||
@ -601,7 +646,7 @@ func runExternalFundingScriptEnforced(ht *lntest.HarnessTest) {
|
||||
// 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(
|
||||
carolChan, daveChan := basicChannelFundingTest(
|
||||
ht, carol, dave, fundingShim2, false, nil,
|
||||
)
|
||||
|
||||
@ -717,7 +762,7 @@ func runExternalFundingTaproot(ht *lntest.HarnessTest) {
|
||||
// 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(
|
||||
carolChan, daveChan := basicChannelFundingTest(
|
||||
ht, carol, dave, fundingShim2, true, &commitmentType,
|
||||
)
|
||||
|
||||
@ -839,7 +884,7 @@ func testChannelFundingPersistence(ht *lntest.HarnessTest) {
|
||||
}
|
||||
carol := ht.NewNode("Carol", carolArgs)
|
||||
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.ConnectNodes(alice, carol)
|
||||
|
||||
// Create a new channel that requires 5 confs before it's considered
|
||||
@ -930,11 +975,6 @@ func testChannelFundingPersistence(ht *lntest.HarnessTest) {
|
||||
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
|
||||
@ -955,8 +995,8 @@ func testBatchChanFunding(ht *lntest.HarnessTest) {
|
||||
}
|
||||
eve := ht.NewNode("eve", scidAliasArgs)
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
ht.RestartNodeWithExtraArgs(alice, scidAliasArgs)
|
||||
alice := ht.NewNodeWithCoins("Alice", scidAliasArgs)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// 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).
|
||||
@ -1127,15 +1167,6 @@ func testBatchChanFunding(ht *lntest.HarnessTest) {
|
||||
chainreg.DefaultBitcoinBaseFeeMSat,
|
||||
chainreg.DefaultBitcoinFeeRate,
|
||||
)
|
||||
|
||||
// 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)
|
||||
ht.CloseChannel(alice, chanPoint4)
|
||||
}
|
||||
|
||||
// ensurePolicy ensures that the peer sees alice's channel fee settings.
|
||||
@ -1209,13 +1240,12 @@ func testChannelFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
|
||||
// Open a channel to dave with an unconfirmed utxo. Although this utxo
|
||||
// is unconfirmed it can be used to open a channel because it did not
|
||||
// originated from the sweeper subsystem.
|
||||
update := ht.OpenChannelAssertPending(carol, dave,
|
||||
ht.OpenChannelAssertPending(carol, dave,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanSize,
|
||||
SpendUnconfirmed: true,
|
||||
CommitmentType: cType,
|
||||
})
|
||||
chanPoint1 := lntest.ChanPointFromPendingUpdate(update)
|
||||
|
||||
// Verify that both nodes know about the channel.
|
||||
ht.AssertNumPendingOpenChannels(carol, 1)
|
||||
@ -1227,7 +1257,7 @@ func testChannelFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
|
||||
// so unconfirmed utxos originated from prior channel opening are safe
|
||||
// to use because channel opening should not be RBFed, at least not for
|
||||
// now.
|
||||
update = ht.OpenChannelAssertPending(carol, dave,
|
||||
update := ht.OpenChannelAssertPending(carol, dave,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanSize,
|
||||
SpendUnconfirmed: true,
|
||||
@ -1346,20 +1376,16 @@ func testChannelFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
|
||||
|
||||
// Now after the sweep utxo is confirmed it is stable and can be used
|
||||
// for channel openings again.
|
||||
update = ht.OpenChannelAssertPending(carol, dave,
|
||||
ht.OpenChannelAssertPending(carol, dave,
|
||||
lntest.OpenChannelParams{
|
||||
Amt: chanSize,
|
||||
SpendUnconfirmed: true,
|
||||
CommitmentType: cType,
|
||||
})
|
||||
chanPoint4 := lntest.ChanPointFromPendingUpdate(update)
|
||||
|
||||
// Verify that both nodes know about the channel.
|
||||
ht.AssertNumPendingOpenChannels(carol, 1)
|
||||
ht.AssertNumPendingOpenChannels(dave, 1)
|
||||
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
ht.CloseChannel(carol, chanPoint1)
|
||||
ht.CloseChannel(carol, chanPoint4)
|
||||
}
|
||||
|
@ -17,10 +17,11 @@ import (
|
||||
// would otherwise trigger force closes when they expire.
|
||||
func testHoldInvoiceForceClose(ht *lntest.HarnessTest) {
|
||||
// Open a channel between alice and bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
chanPoint := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: 300000},
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil}, lntest.OpenChannelParams{Amt: 300000},
|
||||
)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
// Create a non-dust hold invoice for bob.
|
||||
var (
|
||||
@ -29,7 +30,7 @@ func testHoldInvoiceForceClose(ht *lntest.HarnessTest) {
|
||||
)
|
||||
invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{
|
||||
Value: 30000,
|
||||
CltvExpiry: 40,
|
||||
CltvExpiry: finalCltvDelta,
|
||||
Hash: payHash[:],
|
||||
}
|
||||
bobInvoice := bob.RPC.AddHoldInvoice(invoiceReq)
|
||||
@ -139,7 +140,4 @@ func testHoldInvoiceForceClose(ht *lntest.HarnessTest) {
|
||||
// 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)
|
||||
}
|
||||
|
@ -28,12 +28,14 @@ func testHoldInvoicePersistence(ht *lntest.HarnessTest) {
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
|
||||
// Connect Alice to Carol.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, 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(
|
||||
ht.OpenChannel(
|
||||
alice, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
Private: true,
|
||||
@ -220,8 +222,4 @@ func testHoldInvoicePersistence(ht *lntest.HarnessTest) {
|
||||
"wrong failure reason")
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, close all channels.
|
||||
ht.CloseChannel(alice, chanPointBob)
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
func testLookupHtlcResolution(ht *lntest.HarnessTest) {
|
||||
const chanAmt = btcutil.Amount(1000000)
|
||||
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
carol := ht.NewNode("Carol", []string{
|
||||
"--store-final-htlc-resolutions",
|
||||
})
|
||||
@ -24,7 +24,6 @@ func testLookupHtlcResolution(ht *lntest.HarnessTest) {
|
||||
cp := ht.OpenChannel(
|
||||
alice, carol, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
defer ht.CloseChannel(alice, cp)
|
||||
|
||||
// Channel should be ready for payments.
|
||||
const payAmt = 100
|
||||
|
@ -32,7 +32,7 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) {
|
||||
{Local: bob, Remote: carol, Param: p},
|
||||
}
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
cpAB, cpBC := resp[0], resp[1]
|
||||
cpBC := resp[1]
|
||||
|
||||
// Make sure Alice is aware of channel Bob=>Carol.
|
||||
ht.AssertChannelInGraph(alice, cpBC)
|
||||
@ -208,10 +208,6 @@ func testInvoiceHtlcModifierBasic(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
cancelModifier()
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
}
|
||||
|
||||
// acceptorTestCase is a helper struct to hold test case data.
|
||||
@ -251,7 +247,8 @@ type acceptorTestScenario struct {
|
||||
//
|
||||
// Among them, Alice and Bob are standby nodes and Carol is a new node.
|
||||
func newAcceptorTestScenario(ht *lntest.HarnessTest) *acceptorTestScenario {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("bob", nil)
|
||||
carol := ht.NewNode("carol", nil)
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
@ -29,7 +29,7 @@ func testMacaroonAuthentication(ht *lntest.HarnessTest) {
|
||||
newAddrReq = &lnrpc.NewAddressRequest{
|
||||
Type: AddrTypeWitnessPubkeyHash,
|
||||
}
|
||||
testNode = ht.Alice
|
||||
testNode = ht.NewNode("Alice", nil)
|
||||
testClient = testNode.RPC.LN
|
||||
)
|
||||
|
||||
@ -295,7 +295,7 @@ func testMacaroonAuthentication(ht *lntest.HarnessTest) {
|
||||
// in the request must be set correctly, and the baked macaroon has the intended
|
||||
// permissions.
|
||||
func testBakeMacaroon(ht *lntest.HarnessTest) {
|
||||
var testNode = ht.Alice
|
||||
var testNode = ht.NewNode("Alice", nil)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -521,7 +521,7 @@ func testBakeMacaroon(ht *lntest.HarnessTest) {
|
||||
func testDeleteMacaroonID(ht *lntest.HarnessTest) {
|
||||
var (
|
||||
ctxb = ht.Context()
|
||||
testNode = ht.Alice
|
||||
testNode = ht.NewNode("Alice", nil)
|
||||
)
|
||||
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
|
||||
defer cancel()
|
||||
|
@ -56,9 +56,7 @@ func testMaxChannelSize(ht *lntest.HarnessTest) {
|
||||
|
||||
// Creating a wumbo channel between these two nodes should succeed.
|
||||
ht.EnsureConnected(wumboNode, wumboNode3)
|
||||
chanPoint := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
wumboNode, wumboNode3, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
ht.CloseChannel(wumboNode, chanPoint)
|
||||
}
|
||||
|
@ -19,25 +19,20 @@ func testMaxHtlcPathfind(ht *lntest.HarnessTest) {
|
||||
// Bob to add a maximum of 5 htlcs to her commitment.
|
||||
maxHtlcs := 5
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
// Restart nodes with the new flag so they understand the new payment
|
||||
// Create nodes with the new flag so they understand the new payment
|
||||
// status.
|
||||
ht.RestartNodeWithExtraArgs(alice, []string{
|
||||
"--routerrpc.usestatusinitiated",
|
||||
})
|
||||
ht.RestartNodeWithExtraArgs(bob, []string{
|
||||
"--routerrpc.usestatusinitiated",
|
||||
})
|
||||
cfg := []string{"--routerrpc.usestatusinitiated"}
|
||||
cfgs := [][]string{cfg, cfg}
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
chanPoint := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
// Create a channel Alice->Bob.
|
||||
_, nodes := ht.CreateSimpleNetwork(
|
||||
cfgs, lntest.OpenChannelParams{
|
||||
Amt: 1000000,
|
||||
PushAmt: 800000,
|
||||
RemoteMaxHtlcs: uint16(maxHtlcs),
|
||||
},
|
||||
)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// Alice and bob should have one channel open with each other now.
|
||||
ht.AssertNodeNumChannels(alice, 1)
|
||||
@ -82,8 +77,6 @@ func testMaxHtlcPathfind(ht *lntest.HarnessTest) {
|
||||
|
||||
ht.AssertNumActiveHtlcs(alice, 0)
|
||||
ht.AssertNumActiveHtlcs(bob, 0)
|
||||
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
type holdSubscription struct {
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/funding"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
@ -39,9 +38,8 @@ func testDisconnectingTargetPeer(ht *lntest.HarnessTest) {
|
||||
"--maxbackoff=1m",
|
||||
}
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
ht.RestartNodeWithExtraArgs(alice, args)
|
||||
ht.RestartNodeWithExtraArgs(bob, args)
|
||||
alice := ht.NewNodeWithCoins("Alice", args)
|
||||
bob := ht.NewNodeWithCoins("Bob", args)
|
||||
|
||||
// Start by connecting Alice and Bob with no channels.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
@ -157,7 +155,6 @@ func testSphinxReplayPersistence(ht *lntest.HarnessTest) {
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
defer ht.CloseChannel(fred, chanPointFC)
|
||||
|
||||
// Now that the channel is open, create an invoice for Dave which
|
||||
// expects a payment of 1000 satoshis from Carol paid via a particular
|
||||
@ -226,9 +223,6 @@ func testSphinxReplayPersistence(ht *lntest.HarnessTest) {
|
||||
// unaltered.
|
||||
ht.AssertAmountPaid("carol => dave", carol, chanPoint, 0, 0)
|
||||
ht.AssertAmountPaid("dave <= carol", dave, chanPoint, 0, 0)
|
||||
|
||||
// Cleanup by mining the force close and sweep transaction.
|
||||
ht.ForceCloseChannel(carol, chanPoint)
|
||||
}
|
||||
|
||||
// testListChannels checks that the response from ListChannels is correct. It
|
||||
@ -239,17 +233,11 @@ func testListChannels(ht *lntest.HarnessTest) {
|
||||
const aliceRemoteMaxHtlcs = 50
|
||||
const bobRemoteMaxHtlcs = 100
|
||||
|
||||
// Get the standby nodes and open a channel between them.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
args := []string{fmt.Sprintf(
|
||||
"--default-remote-max-htlcs=%v",
|
||||
bobRemoteMaxHtlcs,
|
||||
)}
|
||||
ht.RestartNodeWithExtraArgs(bob, args)
|
||||
|
||||
// Connect Alice to Bob.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
cfgs := [][]string{nil, args}
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel. The minial HTLC amount is set
|
||||
@ -264,8 +252,10 @@ func testListChannels(ht *lntest.HarnessTest) {
|
||||
MinHtlc: customizedMinHtlc,
|
||||
RemoteMaxHtlcs: aliceRemoteMaxHtlcs,
|
||||
}
|
||||
chanPoint := ht.OpenChannel(alice, bob, p)
|
||||
defer ht.CloseChannel(alice, chanPoint)
|
||||
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
// Alice should have one channel opened with Bob.
|
||||
ht.AssertNodeNumChannels(alice, 1)
|
||||
@ -369,7 +359,7 @@ func testMaxPendingChannels(ht *lntest.HarnessTest) {
|
||||
}
|
||||
carol := ht.NewNode("Carol", args)
|
||||
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.ConnectNodes(alice, carol)
|
||||
|
||||
carolBalance := btcutil.Amount(maxPendingChannels) * amount
|
||||
@ -425,12 +415,6 @@ func testMaxPendingChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
chanPoints[i] = fundingChanPoint
|
||||
}
|
||||
|
||||
// Next, close the channel between Alice and Carol, asserting that the
|
||||
// channel has been properly closed on-chain.
|
||||
for _, chanPoint := range chanPoints {
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
}
|
||||
|
||||
// testGarbageCollectLinkNodes tests that we properly garbage collect link
|
||||
@ -439,7 +423,9 @@ func testMaxPendingChannels(ht *lntest.HarnessTest) {
|
||||
func testGarbageCollectLinkNodes(ht *lntest.HarnessTest) {
|
||||
const chanAmt = 1000000
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Open a channel between Alice and Bob which will later be
|
||||
// cooperatively closed.
|
||||
@ -467,7 +453,7 @@ func testGarbageCollectLinkNodes(ht *lntest.HarnessTest) {
|
||||
dave := ht.NewNode("Dave", nil)
|
||||
|
||||
ht.ConnectNodes(alice, dave)
|
||||
persistentChanPoint := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
alice, dave, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
@ -519,12 +505,6 @@ func testGarbageCollectLinkNodes(ht *lntest.HarnessTest) {
|
||||
// close the channel instead.
|
||||
ht.ForceCloseChannel(alice, forceCloseChanPoint)
|
||||
|
||||
// We'll need to mine some blocks in order to mark the channel fully
|
||||
// closed.
|
||||
ht.MineBlocks(
|
||||
chainreg.DefaultBitcoinTimeLockDelta - defaultCSV,
|
||||
)
|
||||
|
||||
// Before we test reconnection, we'll ensure that the channel has been
|
||||
// fully cleaned up for both Carol and Alice.
|
||||
ht.AssertNumPendingForceClose(alice, 0)
|
||||
@ -540,9 +520,6 @@ func testGarbageCollectLinkNodes(ht *lntest.HarnessTest) {
|
||||
"did not expect to find bob in the channel graph, but did")
|
||||
require.NotContains(ht, channelGraph.Nodes, carol.PubKeyStr,
|
||||
"did not expect to find carol in the channel graph, but did")
|
||||
|
||||
// Now that the test is done, we can also close the persistent link.
|
||||
ht.CloseChannel(alice, persistentChanPoint)
|
||||
}
|
||||
|
||||
// testRejectHTLC tests that a node can be created with the flag --rejecthtlc.
|
||||
@ -553,7 +530,8 @@ func testRejectHTLC(ht *lntest.HarnessTest) {
|
||||
// Alice ------> Carol ------> Bob
|
||||
//
|
||||
const chanAmt = btcutil.Amount(1000000)
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// Create Carol with reject htlc flag.
|
||||
carol := ht.NewNode("Carol", []string{"--rejecthtlc"})
|
||||
@ -568,14 +546,14 @@ func testRejectHTLC(ht *lntest.HarnessTest) {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
// Open a channel between Alice and Carol.
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
alice, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
|
||||
// Open a channel between Carol and Bob.
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
carol, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
@ -638,10 +616,6 @@ func testRejectHTLC(ht *lntest.HarnessTest) {
|
||||
)
|
||||
|
||||
ht.AssertLastHTLCError(alice, lnrpc.Failure_CHANNEL_DISABLED)
|
||||
|
||||
// Close all channels.
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
ht.CloseChannel(carol, chanPointCarol)
|
||||
}
|
||||
|
||||
// testNodeSignVerify checks that only connected nodes are allowed to perform
|
||||
@ -649,15 +623,15 @@ func testRejectHTLC(ht *lntest.HarnessTest) {
|
||||
func testNodeSignVerify(ht *lntest.HarnessTest) {
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := btcutil.Amount(100000)
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
p := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
}
|
||||
|
||||
// Create a channel between alice and bob.
|
||||
aliceBobCh := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
},
|
||||
)
|
||||
cfgs := [][]string{nil, nil}
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// alice signs "alice msg" and sends her signature to bob.
|
||||
aliceMsg := []byte("alice msg")
|
||||
@ -685,23 +659,23 @@ func testNodeSignVerify(ht *lntest.HarnessTest) {
|
||||
require.False(ht, verifyResp.Valid, "carol's signature didn't validate")
|
||||
require.Equal(ht, verifyResp.Pubkey, carol.PubKeyStr,
|
||||
"carol's signature doesn't contain alice's pubkey.")
|
||||
|
||||
// Close the channel between alice and bob.
|
||||
ht.CloseChannel(alice, aliceBobCh)
|
||||
}
|
||||
|
||||
// testAbandonChannel abandons a channel and asserts that it is no longer open
|
||||
// and not in one of the pending closure states. It also verifies that the
|
||||
// abandoned channel is reported as closed with close type 'abandoned'.
|
||||
func testAbandonChannel(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
// First establish a channel between Alice and Bob.
|
||||
channelParam := lntest.OpenChannelParams{
|
||||
Amt: funding.MaxBtcFundingAmount,
|
||||
PushAmt: btcutil.Amount(100000),
|
||||
}
|
||||
chanPoint := ht.OpenChannel(alice, bob, channelParam)
|
||||
|
||||
// Create a channel between alice and bob.
|
||||
cfgs := [][]string{nil, nil}
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, channelParam)
|
||||
alice := nodes[0]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
// Now that the channel is open, we'll obtain its channel ID real quick
|
||||
// so we can use it to query the graph below.
|
||||
@ -753,16 +727,13 @@ func testAbandonChannel(ht *lntest.HarnessTest) {
|
||||
// Calling AbandonChannel again, should result in no new errors, as the
|
||||
// channel has already been removed.
|
||||
alice.RPC.AbandonChannel(abandonChannelRequest)
|
||||
|
||||
// Now that we're done with the test, the channel can be closed. This
|
||||
// is necessary to avoid unexpected outcomes of other tests that use
|
||||
// Bob's lnd instance.
|
||||
ht.ForceCloseChannel(bob, chanPoint)
|
||||
}
|
||||
|
||||
// testSendAllCoins tests that we're able to properly sweep all coins from the
|
||||
// wallet into a single target address at the specified fee rate.
|
||||
func testSendAllCoins(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
// First, we'll make a new node, Ainz who'll we'll use to test wallet
|
||||
// sweeping.
|
||||
//
|
||||
@ -789,7 +760,7 @@ func testSendAllCoins(ht *lntest.HarnessTest) {
|
||||
|
||||
// Ensure that we can't send coins to another user's Pubkey.
|
||||
err = ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
|
||||
Addr: ht.Alice.RPC.GetInfo().IdentityPubkey,
|
||||
Addr: alice.RPC.GetInfo().IdentityPubkey,
|
||||
SendAll: true,
|
||||
Label: sendCoinsLabel,
|
||||
TargetConf: 6,
|
||||
@ -1160,7 +1131,8 @@ func assertChannelConstraintsEqual(ht *lntest.HarnessTest,
|
||||
// on a message with a provided address.
|
||||
func testSignVerifyMessageWithAddr(ht *lntest.HarnessTest) {
|
||||
// Using different nodes to sign the message and verify the signature.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNode("Alice,", nil)
|
||||
bob := ht.NewNode("Bob,", nil)
|
||||
|
||||
// Test an lnd wallet created P2WKH address.
|
||||
respAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||
@ -1277,7 +1249,7 @@ func testSignVerifyMessageWithAddr(ht *lntest.HarnessTest) {
|
||||
// up with native SQL enabled, as we don't currently support migration of KV
|
||||
// invoices to the new SQL schema.
|
||||
func testNativeSQLNoMigration(ht *lntest.HarnessTest) {
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Make sure we run the test with SQLite or Postgres.
|
||||
if alice.Cfg.DBBackend != node.BackendSqlite &&
|
||||
|
@ -1,6 +1,8 @@
|
||||
package itest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
@ -9,11 +11,107 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/routing/route"
|
||||
"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 *lntest.HarnessTest) {
|
||||
mts := newMppTestScenario(ht)
|
||||
|
||||
// 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 ____/
|
||||
//
|
||||
paymentAmt := mts.setupSendPaymentCase()
|
||||
|
||||
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 := &lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: 500_000,
|
||||
FeeRateMilliMsat: int64(0.001 * 1_000_000),
|
||||
TimeLockDelta: 40,
|
||||
MinHtlc: 1000, // default value
|
||||
MaxHtlcMsat: 133_650_000,
|
||||
}
|
||||
mts.dave.UpdateGlobalPolicy(expectedPolicy)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// testSendToRouteMultiPath tests that we are able to successfully route a
|
||||
// payment using multiple shards across different paths, by using SendToRoute.
|
||||
func testSendToRouteMultiPath(ht *lntest.HarnessTest) {
|
||||
@ -22,11 +120,6 @@ func testSendToRouteMultiPath(ht *lntest.HarnessTest) {
|
||||
// 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 _
|
||||
@ -35,17 +128,7 @@ func testSendToRouteMultiPath(ht *lntest.HarnessTest) {
|
||||
// \ /
|
||||
// \__ 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)
|
||||
paymentAmt, shardAmt := mts.setupSendToRouteCase()
|
||||
|
||||
// Make Bob create an invoice for Alice to pay.
|
||||
payReqs, rHashes, invoices := ht.CreatePayReqs(mts.bob, paymentAmt, 1)
|
||||
@ -179,9 +262,12 @@ type mppTestScenario struct {
|
||||
// Alice -- Carol ---- Bob
|
||||
// \ /
|
||||
// \__ Dave ____/
|
||||
//
|
||||
// The scenario is setup in a way that when sending a payment from Alice to
|
||||
// Bob, (at least) three routes must be tried to succeed.
|
||||
func newMppTestScenario(ht *lntest.HarnessTest) *mppTestScenario {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
ht.RestartNodeWithExtraArgs(bob, []string{
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", []string{
|
||||
"--maxpendingchannels=2",
|
||||
"--accept-amp",
|
||||
})
|
||||
@ -246,6 +332,136 @@ type mppOpenChannelRequest struct {
|
||||
amtEveBob btcutil.Amount
|
||||
}
|
||||
|
||||
// setupSendPaymentCase opens channels between the nodes for testing the
|
||||
// `SendPaymentV2` case, where a payment amount of 300,000 sats is used and it
|
||||
// tests sending three attempts: the first has 150,000 sats, the rest two have
|
||||
// 75,000 sats. It returns the payment amt.
|
||||
func (c *mppTestScenario) setupSendPaymentCase() btcutil.Amount {
|
||||
// To ensure the payment goes through separate paths, we'll set a
|
||||
// channel size that can only carry one HTLC attempt at a time. We'll
|
||||
// divide the payment into 3 attempts.
|
||||
//
|
||||
// Set the payment amount to be 300,000 sats. When a route cannot be
|
||||
// found for a given payment amount, we will halven the amount and try
|
||||
// the pathfinding again, which means we need to see the following
|
||||
// three attempts to succeed:
|
||||
// 1. 1st attempt: 150,000 sats.
|
||||
// 2. 2nd attempt: 75,000 sats.
|
||||
// 3. 3rd attempt: 75,000 sats.
|
||||
paymentAmt := btcutil.Amount(300_000)
|
||||
|
||||
// Prepare to open channels between the nodes. Given our expected
|
||||
// topology,
|
||||
//
|
||||
// _ Eve _
|
||||
// / \
|
||||
// Alice -- Carol ---- Bob
|
||||
// \ /
|
||||
// \__ Dave ___/
|
||||
//
|
||||
// There are three routes from Alice to Bob:
|
||||
// 1. Alice -> Carol -> Bob
|
||||
// 2. Alice -> Dave -> Bob
|
||||
// 3. Alice -> Carol -> Eve -> Bob
|
||||
// We now use hardcoded amounts so it's easier to reason about the
|
||||
// test.
|
||||
req := &mppOpenChannelRequest{
|
||||
amtAliceCarol: 285_000,
|
||||
amtAliceDave: 155_000,
|
||||
amtCarolBob: 200_000,
|
||||
amtCarolEve: 155_000,
|
||||
amtDaveBob: 155_000,
|
||||
amtEveBob: 155_000,
|
||||
}
|
||||
|
||||
// Given the above setup, the only possible routes to send each of the
|
||||
// attempts are:
|
||||
// - 1st attempt(150,000 sats): Alice->Carol->Bob: 200,000 sats.
|
||||
// - 2nd attempt(75,000 sats): Alice->Dave->Bob: 155,000 sats.
|
||||
// - 3rd attempt(75,000 sats): Alice->Carol->Eve->Bob: 155,000 sats.
|
||||
//
|
||||
// There is a case where the payment will fail due to the channel
|
||||
// bandwidth not being updated in the graph, which has been seen many
|
||||
// times:
|
||||
// 1. the 1st attempt (150,000 sats) is sent via
|
||||
// Alice->Carol->Eve->Bob, after which the capacity in Carol->Eve
|
||||
// should decrease.
|
||||
// 2. the 2nd attempt (75,000 sats) is sent via Alice->Carol->Eve->Bob,
|
||||
// which shouldn't happen because the capacity in Carol->Eve is
|
||||
// depleted. However, since the HTLCs are sent in parallel, the 2nd
|
||||
// attempt can be sent before the capacity is updated in the graph.
|
||||
// 3. if the 2nd attempt succeeds, the 1st attempt will fail and be
|
||||
// split into two attempts, each holding 75,000 sats. At this point,
|
||||
// we have three attempts to send, but only two routes are
|
||||
// available, causing the payment to be failed.
|
||||
// 4. In addition, with recent fee buffer addition, the attempts will
|
||||
// fail even earlier without being further split.
|
||||
//
|
||||
// To avoid this case, we now increase the channel capacity of the
|
||||
// route Carol->Eve->Bob and Carol->Bob such that even the above case
|
||||
// happened, we can still send the HTLCs.
|
||||
//
|
||||
// TODO(yy): we should properly fix this in the router. Atm we only
|
||||
// perform this hack to unblock the CI.
|
||||
req.amtCarolBob = 285_000
|
||||
req.amtEveBob = 285_000
|
||||
req.amtCarolEve = 285_000
|
||||
|
||||
// Open the channels as described above.
|
||||
c.openChannels(req)
|
||||
|
||||
return paymentAmt
|
||||
}
|
||||
|
||||
// setupSendToRouteCase opens channels between the nodes for testing the
|
||||
// `SendToRouteV2` case, where a payment amount of 300,000 sats is used and it
|
||||
// tests sending three attempts each holding 100,000 sats. It returns the
|
||||
// payment amount and attempt amount.
|
||||
func (c *mppTestScenario) setupSendToRouteCase() (btcutil.Amount,
|
||||
btcutil.Amount) {
|
||||
|
||||
// To ensure the payment goes through separate paths, we'll set a
|
||||
// channel size that can only carry one HTLC attempt at a time. We'll
|
||||
// divide the payment into 3 attempts, each holding 100,000 sats.
|
||||
paymentAmt := btcutil.Amount(300_000)
|
||||
attemptAmt := btcutil.Amount(100_000)
|
||||
|
||||
// Prepare to open channels between the nodes. Given our expected
|
||||
// topology,
|
||||
//
|
||||
// _ Eve _
|
||||
// / \
|
||||
// Alice -- Carol ---- Bob
|
||||
// \ /
|
||||
// \__ Dave ___/
|
||||
//
|
||||
// There are three routes from Alice to Bob:
|
||||
// 1. Alice -> Carol -> Bob
|
||||
// 2. Alice -> Dave -> Bob
|
||||
// 3. Alice -> Carol -> Eve -> Bob
|
||||
// We now use hardcoded amounts so it's easier to reason about the
|
||||
// test.
|
||||
req := &mppOpenChannelRequest{
|
||||
amtAliceCarol: 250_000,
|
||||
amtAliceDave: 150_000,
|
||||
amtCarolBob: 150_000,
|
||||
amtCarolEve: 150_000,
|
||||
amtDaveBob: 150_000,
|
||||
amtEveBob: 150_000,
|
||||
}
|
||||
|
||||
// Given the above setup, the only possible routes to send each of the
|
||||
// attempts are:
|
||||
// - 1st attempt(100,000 sats): Alice->Carol->Bob: 150,000 sats.
|
||||
// - 2nd attempt(100,000 sats): Alice->Dave->Bob: 150,000 sats.
|
||||
// - 3rd attempt(100,000 sats): Alice->Carol->Eve->Bob: 150,000 sats.
|
||||
//
|
||||
// Open the channels as described above.
|
||||
c.openChannels(req)
|
||||
|
||||
return paymentAmt, attemptAmt
|
||||
}
|
||||
|
||||
// openChannels is a helper to open channels that sets up a network topology
|
||||
// with three different paths Alice <-> Bob as following,
|
||||
//
|
||||
@ -299,7 +515,7 @@ func (m *mppTestScenario) openChannels(r *mppOpenChannelRequest) {
|
||||
}
|
||||
|
||||
// Each node should have exactly 6 edges.
|
||||
m.ht.AssertNumActiveEdges(hn, len(m.channelPoints), false)
|
||||
m.ht.AssertNumEdges(hn, len(m.channelPoints), false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,7 +560,35 @@ func (m *mppTestScenario) buildRoute(amt btcutil.Amount,
|
||||
FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
|
||||
HopPubkeys: rpcHops,
|
||||
}
|
||||
routeResp := sender.RPC.BuildRoute(req)
|
||||
|
||||
return routeResp.Route
|
||||
// We should be able to call `sender.RPC.BuildRoute` directly, but
|
||||
// sometimes we will get a RPC-level error saying we cannot find the
|
||||
// node index:
|
||||
// - no matching outgoing channel available for node index 1
|
||||
// This happens because the `getEdgeUnifiers` cannot find a policy for
|
||||
// one of the hops,
|
||||
// - [ERR] CRTR router.go:1689: Cannot find policy for node ...
|
||||
// However, by the time we get here, we have already checked that all
|
||||
// nodes have heard all channels, so this indicates a bug in our
|
||||
// pathfinding, specifically in the edge unifier.
|
||||
//
|
||||
// TODO(yy): Remove the following wait and use the direct call, then
|
||||
// investigate the bug in the edge unifier.
|
||||
var route *lnrpc.Route
|
||||
err := wait.NoError(func() error {
|
||||
routeResp, err := sender.RPC.Router.BuildRoute(
|
||||
m.ht.Context(), req,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build route for %v "+
|
||||
"using hops=%v: %v", sender.Name(), hops, err)
|
||||
}
|
||||
|
||||
route = routeResp.Route
|
||||
|
||||
return nil
|
||||
}, defaultTimeout)
|
||||
require.NoError(m.ht, err, "build route timeout")
|
||||
|
||||
return route
|
||||
}
|
||||
|
@ -15,7 +15,9 @@ func testHtlcErrorPropagation(ht *lntest.HarnessTest) {
|
||||
// multi-hop payment.
|
||||
const chanAmt = funding.MaxBtcFundingAmount
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Since we'd like to test some multi-hop failure scenarios, we'll
|
||||
// introduce another node into our test network: Carol.
|
||||
@ -363,12 +365,4 @@ func testHtlcErrorPropagation(ht *lntest.HarnessTest) {
|
||||
ht.AssertHtlcEventTypes(
|
||||
bobEvents, routerrpc.HtlcEvent_UNKNOWN, lntest.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)
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
|
||||
daveArgs := []string{"--protocol.legacy.onion"}
|
||||
dave := ht.NewNode("Dave", daveArgs)
|
||||
@ -37,6 +38,7 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
||||
ht.AssertHtlcEventType(daveEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
|
||||
// Connect the nodes.
|
||||
ht.ConnectNodes(alice, bob)
|
||||
ht.ConnectNodes(dave, alice)
|
||||
ht.ConnectNodes(carol, dave)
|
||||
|
||||
@ -233,11 +235,6 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
||||
ht.AssertHtlcEvents(
|
||||
bobEvents, 0, 0, numPayments, 0, 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
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -126,16 +126,14 @@ func testReconnectAfterIPChange(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
// Connect Alice to Dave and Charlie.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
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, lntest.OpenChannelParams{Amt: 1000000},
|
||||
)
|
||||
ht.OpenChannel(alice, dave, lntest.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
|
||||
@ -210,15 +208,12 @@ func testReconnectAfterIPChange(ht *lntest.HarnessTest) {
|
||||
// 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 *lntest.HarnessTest) {
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
info := alice.RPC.GetInfo()
|
||||
|
||||
alicePeerAddress := info.Uris[0]
|
||||
|
@ -13,8 +13,10 @@ func testNeutrino(ht *lntest.HarnessTest) {
|
||||
ht.Skipf("skipping test for non neutrino backends")
|
||||
}
|
||||
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Check if the neutrino sub server is running.
|
||||
statusRes := ht.Alice.RPC.Status(nil)
|
||||
statusRes := alice.RPC.Status(nil)
|
||||
require.True(ht, statusRes.Active)
|
||||
require.Len(ht, statusRes.Peers, 1, "unable to find a peer")
|
||||
|
||||
@ -22,11 +24,11 @@ func testNeutrino(ht *lntest.HarnessTest) {
|
||||
cFilterReq := &neutrinorpc.GetCFilterRequest{
|
||||
Hash: statusRes.GetBlockHash(),
|
||||
}
|
||||
ht.Alice.RPC.GetCFilter(cFilterReq)
|
||||
alice.RPC.GetCFilter(cFilterReq)
|
||||
|
||||
// Try to reconnect to a connected peer.
|
||||
addPeerReq := &neutrinorpc.AddPeerRequest{
|
||||
PeerAddrs: statusRes.Peers[0],
|
||||
}
|
||||
ht.Alice.RPC.AddPeer(addPeerReq)
|
||||
alice.RPC.AddPeer(addPeerReq)
|
||||
}
|
||||
|
@ -33,8 +33,10 @@ func testChainKit(ht *lntest.HarnessTest) {
|
||||
// testChainKitGetBlock ensures that given a block hash, the RPC endpoint
|
||||
// returns the correct target block.
|
||||
func testChainKitGetBlock(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Get best block hash.
|
||||
bestBlockRes := ht.Alice.RPC.GetBestBlock(nil)
|
||||
bestBlockRes := alice.RPC.GetBestBlock(nil)
|
||||
|
||||
var bestBlockHash chainhash.Hash
|
||||
err := bestBlockHash.SetBytes(bestBlockRes.BlockHash)
|
||||
@ -44,7 +46,7 @@ func testChainKitGetBlock(ht *lntest.HarnessTest) {
|
||||
getBlockReq := &chainrpc.GetBlockRequest{
|
||||
BlockHash: bestBlockHash[:],
|
||||
}
|
||||
getBlockRes := ht.Alice.RPC.GetBlock(getBlockReq)
|
||||
getBlockRes := alice.RPC.GetBlock(getBlockReq)
|
||||
|
||||
// Deserialize the block which was retrieved by hash.
|
||||
msgBlock := &wire.MsgBlock{}
|
||||
@ -61,8 +63,10 @@ func testChainKitGetBlock(ht *lntest.HarnessTest) {
|
||||
// testChainKitGetBlockHeader ensures that given a block hash, the RPC endpoint
|
||||
// returns the correct target block header.
|
||||
func testChainKitGetBlockHeader(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Get best block hash.
|
||||
bestBlockRes := ht.Alice.RPC.GetBestBlock(nil)
|
||||
bestBlockRes := alice.RPC.GetBestBlock(nil)
|
||||
|
||||
var (
|
||||
bestBlockHash chainhash.Hash
|
||||
@ -76,7 +80,7 @@ func testChainKitGetBlockHeader(ht *lntest.HarnessTest) {
|
||||
getBlockReq := &chainrpc.GetBlockRequest{
|
||||
BlockHash: bestBlockHash[:],
|
||||
}
|
||||
getBlockRes := ht.Alice.RPC.GetBlock(getBlockReq)
|
||||
getBlockRes := alice.RPC.GetBlock(getBlockReq)
|
||||
|
||||
// Deserialize the block which was retrieved by hash.
|
||||
blockReader := bytes.NewReader(getBlockRes.RawBlock)
|
||||
@ -87,7 +91,7 @@ func testChainKitGetBlockHeader(ht *lntest.HarnessTest) {
|
||||
getBlockHeaderReq := &chainrpc.GetBlockHeaderRequest{
|
||||
BlockHash: bestBlockHash[:],
|
||||
}
|
||||
getBlockHeaderRes := ht.Alice.RPC.GetBlockHeader(getBlockHeaderReq)
|
||||
getBlockHeaderRes := alice.RPC.GetBlockHeader(getBlockHeaderReq)
|
||||
|
||||
// Deserialize the block header which was retrieved by hash.
|
||||
blockHeaderReader := bytes.NewReader(getBlockHeaderRes.RawBlockHeader)
|
||||
@ -104,14 +108,16 @@ func testChainKitGetBlockHeader(ht *lntest.HarnessTest) {
|
||||
// testChainKitGetBlockHash ensures that given a block height, the RPC endpoint
|
||||
// returns the correct target block hash.
|
||||
func testChainKitGetBlockHash(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Get best block hash.
|
||||
bestBlockRes := ht.Alice.RPC.GetBestBlock(nil)
|
||||
bestBlockRes := 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)
|
||||
getBlockHashRes := alice.RPC.GetBlockHash(req)
|
||||
|
||||
// Ensure best block hash is the same as retrieved block hash.
|
||||
expected := bestBlockRes.BlockHash
|
||||
@ -128,8 +134,7 @@ func testChainKitSendOutputsAnchorReserve(ht *lntest.HarnessTest) {
|
||||
// NOTE: we cannot reuse the standby node here as the test requires the
|
||||
// node to start with no UTXOs.
|
||||
charlie := ht.NewNode("Charlie", args)
|
||||
bob := ht.Bob
|
||||
ht.RestartNodeWithExtraArgs(bob, args)
|
||||
bob := ht.NewNode("Bob", args)
|
||||
|
||||
// We'll start the test by sending Charlie some coins.
|
||||
fundingAmount := btcutil.Amount(100_000)
|
||||
@ -148,7 +153,7 @@ func testChainKitSendOutputsAnchorReserve(ht *lntest.HarnessTest) {
|
||||
// Charlie opens an anchor channel and keeps twice the amount of the
|
||||
// anchor reserve in her wallet.
|
||||
chanAmt := fundingAmount - 2*btcutil.Amount(reserve.RequiredReserve)
|
||||
outpoint := ht.OpenChannel(charlie, bob, lntest.OpenChannelParams{
|
||||
ht.OpenChannel(charlie, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
SatPerVByte: 1,
|
||||
@ -202,11 +207,7 @@ func testChainKitSendOutputsAnchorReserve(ht *lntest.HarnessTest) {
|
||||
|
||||
// This second transaction should be published correctly.
|
||||
charlie.RPC.SendOutputs(req)
|
||||
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Clean up our test setup.
|
||||
ht.CloseChannel(charlie, outpoint)
|
||||
}
|
||||
|
||||
// testAnchorReservedValue tests that we won't allow sending transactions when
|
||||
@ -216,12 +217,8 @@ func testAnchorReservedValue(ht *lntest.HarnessTest) {
|
||||
// Start two nodes supporting anchor channels.
|
||||
args := lntest.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)
|
||||
|
||||
bob := ht.NewNode("Bob", args)
|
||||
ht.ConnectNodes(alice, bob)
|
||||
|
||||
// Send just enough coins for Alice to open a channel without a change
|
||||
|
@ -3,8 +3,6 @@ package itest
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
@ -19,6 +17,31 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// channelFeePolicyTestCases defines a set of tests to check the update channel
|
||||
// policy fee behavior.
|
||||
var channelFeePolicyTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "default",
|
||||
TestFunc: testChannelFeePolicyDefault,
|
||||
},
|
||||
{
|
||||
Name: "base fee",
|
||||
TestFunc: testChannelFeePolicyBaseFee,
|
||||
},
|
||||
{
|
||||
Name: "fee rate",
|
||||
TestFunc: testChannelFeePolicyFeeRate,
|
||||
},
|
||||
{
|
||||
Name: "base fee and fee rate",
|
||||
TestFunc: testChannelFeePolicyBaseFeeAndFeeRate,
|
||||
},
|
||||
{
|
||||
Name: "low base fee and fee rate",
|
||||
TestFunc: testChannelFeePolicyLowBaseFeeAndFeeRate,
|
||||
},
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -30,11 +53,16 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) {
|
||||
ht.Skipf("skipping reorg test for neutrino backend")
|
||||
}
|
||||
|
||||
// Create a temp miner.
|
||||
tempMiner := ht.SpawnTempMiner()
|
||||
|
||||
miner := ht.Miner()
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Create a temp miner after the creation of Alice.
|
||||
//
|
||||
// NOTE: this is needed since NewNodeWithCoins will mine a block and
|
||||
// the temp miner needs to sync up.
|
||||
tempMiner := ht.SpawnTempMiner()
|
||||
|
||||
// Create a new channel that requires 1 confs before it's considered
|
||||
// open, then broadcast the funding transaction
|
||||
@ -84,7 +112,7 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) {
|
||||
ht.AssertChannelInGraph(bob, chanPoint)
|
||||
|
||||
// Alice should now have 1 edge in her graph.
|
||||
ht.AssertNumActiveEdges(alice, 1, true)
|
||||
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
|
||||
@ -112,41 +140,21 @@ func testOpenChannelAfterReorg(ht *lntest.HarnessTest) {
|
||||
|
||||
// Since the fundingtx was reorged out, Alice should now have no edges
|
||||
// in her graph.
|
||||
ht.AssertNumActiveEdges(alice, 0, true)
|
||||
ht.AssertNumEdges(alice, 0, true)
|
||||
|
||||
// Cleanup by mining the funding tx again, then closing the channel.
|
||||
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||
ht.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
|
||||
// 5. Both baseFee and feeRate are set to a value lower than the default:
|
||||
// ChannelUpdate --> provided baseFee, provided feeRate
|
||||
func testOpenChannelUpdateFeePolicy(ht *lntest.HarnessTest) {
|
||||
// testChannelFeePolicyDefault check when no params provided to
|
||||
// OpenChannelRequest: ChannelUpdate --> defaultBaseFee, defaultFeeRate.
|
||||
func testChannelFeePolicyDefault(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
defaultBaseFee = 1000
|
||||
defaultFeeRate = 1
|
||||
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||
defaultMinHtlc = 1000
|
||||
optionalBaseFee = 1337
|
||||
optionalFeeRate = 1337
|
||||
lowBaseFee = 0
|
||||
lowFeeRate = 900
|
||||
)
|
||||
|
||||
defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||
@ -154,81 +162,19 @@ func testOpenChannelUpdateFeePolicy(ht *lntest.HarnessTest) {
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
feeScenarios := []lntest.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,
|
||||
},
|
||||
{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
BaseFee: lowBaseFee,
|
||||
FeeRate: lowFeeRate,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: true,
|
||||
},
|
||||
feeScenario := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
UseBaseFee: false,
|
||||
UseFeeRate: false,
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
{
|
||||
FeeBaseMsat: lowBaseFee,
|
||||
FeeRateMilliMsat: lowFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
},
|
||||
expectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||
@ -239,83 +185,259 @@ func testOpenChannelUpdateFeePolicy(ht *lntest.HarnessTest) {
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
// In this basic test, we'll need a third node, Carol, so we can forward
|
||||
// a payment through the channel we'll open with the different fee
|
||||
// policies.
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
runChannelFeePolicyTest(
|
||||
ht, feeScenario, &expectedPolicy, &bobExpectedPolicy,
|
||||
)
|
||||
}
|
||||
|
||||
// testChannelFeePolicyBaseFee checks only baseFee provided to
|
||||
// OpenChannelRequest: ChannelUpdate --> provided baseFee, defaultFeeRate.
|
||||
func testChannelFeePolicyBaseFee(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
defaultBaseFee = 1000
|
||||
defaultFeeRate = 1
|
||||
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||
defaultMinHtlc = 1000
|
||||
optionalBaseFee = 1337
|
||||
)
|
||||
|
||||
defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
feeScenario := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
BaseFee: optionalBaseFee,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: false,
|
||||
}
|
||||
|
||||
expectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: optionalBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
runChannelFeePolicyTest(
|
||||
ht, feeScenario, &expectedPolicy, &bobExpectedPolicy,
|
||||
)
|
||||
}
|
||||
|
||||
// testChannelFeePolicyFeeRate checks if only feeRate provided to
|
||||
// OpenChannelRequest: ChannelUpdate --> defaultBaseFee, provided FeeRate.
|
||||
func testChannelFeePolicyFeeRate(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
defaultBaseFee = 1000
|
||||
defaultFeeRate = 1
|
||||
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||
defaultMinHtlc = 1000
|
||||
optionalFeeRate = 1337
|
||||
)
|
||||
|
||||
defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
feeScenario := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
FeeRate: optionalFeeRate,
|
||||
UseBaseFee: false,
|
||||
UseFeeRate: true,
|
||||
}
|
||||
|
||||
expectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: optionalFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
runChannelFeePolicyTest(
|
||||
ht, feeScenario, &expectedPolicy, &bobExpectedPolicy,
|
||||
)
|
||||
}
|
||||
|
||||
// testChannelFeePolicyBaseFeeAndFeeRate checks if baseFee and feeRate provided
|
||||
// to OpenChannelRequest: ChannelUpdate --> provided baseFee, provided feeRate.
|
||||
func testChannelFeePolicyBaseFeeAndFeeRate(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
defaultBaseFee = 1000
|
||||
defaultFeeRate = 1
|
||||
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||
defaultMinHtlc = 1000
|
||||
optionalBaseFee = 1337
|
||||
optionalFeeRate = 1337
|
||||
)
|
||||
|
||||
defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
feeScenario := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
BaseFee: optionalBaseFee,
|
||||
FeeRate: optionalFeeRate,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: true,
|
||||
}
|
||||
|
||||
expectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: optionalBaseFee,
|
||||
FeeRateMilliMsat: optionalFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
runChannelFeePolicyTest(
|
||||
ht, feeScenario, &expectedPolicy, &bobExpectedPolicy,
|
||||
)
|
||||
}
|
||||
|
||||
// testChannelFeePolicyLowBaseFeeAndFeeRate checks if both baseFee and feeRate
|
||||
// are set to a value lower than the default: ChannelUpdate --> provided
|
||||
// baseFee, provided feeRate.
|
||||
func testChannelFeePolicyLowBaseFeeAndFeeRate(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
defaultBaseFee = 1000
|
||||
defaultFeeRate = 1
|
||||
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||
defaultMinHtlc = 1000
|
||||
lowBaseFee = 0
|
||||
lowFeeRate = 900
|
||||
)
|
||||
|
||||
defaultMaxHtlc := lntest.CalculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||
|
||||
chanAmt := funding.MaxBtcFundingAmount
|
||||
pushAmt := chanAmt / 2
|
||||
|
||||
feeScenario := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: pushAmt,
|
||||
BaseFee: lowBaseFee,
|
||||
FeeRate: lowFeeRate,
|
||||
UseBaseFee: true,
|
||||
UseFeeRate: true,
|
||||
}
|
||||
|
||||
expectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: lowBaseFee,
|
||||
FeeRateMilliMsat: lowFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: defaultBaseFee,
|
||||
FeeRateMilliMsat: defaultFeeRate,
|
||||
TimeLockDelta: defaultTimeLockDelta,
|
||||
MinHtlc: defaultMinHtlc,
|
||||
MaxHtlcMsat: defaultMaxHtlc,
|
||||
}
|
||||
|
||||
runChannelFeePolicyTest(
|
||||
ht, feeScenario, &expectedPolicy, &bobExpectedPolicy,
|
||||
)
|
||||
}
|
||||
|
||||
// runChannelFeePolicyTest 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.
|
||||
func runChannelFeePolicyTest(ht *lntest.HarnessTest,
|
||||
chanParams lntest.OpenChannelParams,
|
||||
alicePolicy, bobPolicy *lnrpc.RoutingPolicy) {
|
||||
|
||||
// In this basic test, we'll need a third node, Carol, so we can
|
||||
// forward a payment through the channel we'll open with the different
|
||||
// fee policies.
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
carol := ht.NewNodeWithCoins("Carol", nil)
|
||||
|
||||
ht.EnsureConnected(alice, bob)
|
||||
ht.EnsureConnected(alice, carol)
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
nodes := []*node.HarnessNode{alice, bob, carol}
|
||||
|
||||
runTestCase := func(ht *lntest.HarnessTest,
|
||||
chanParams lntest.OpenChannelParams,
|
||||
alicePolicy, bobPolicy *lnrpc.RoutingPolicy) {
|
||||
// Create a channel Alice->Bob.
|
||||
chanPoint := ht.OpenChannel(alice, bob, chanParams)
|
||||
|
||||
// Create a channel Alice->Bob.
|
||||
chanPoint := ht.OpenChannel(alice, bob, chanParams)
|
||||
defer ht.CloseChannel(alice, chanPoint)
|
||||
// Create a channel Carol->Alice.
|
||||
ht.OpenChannel(
|
||||
carol, alice, lntest.OpenChannelParams{
|
||||
Amt: 500000,
|
||||
},
|
||||
)
|
||||
|
||||
// Create a channel Carol->Alice.
|
||||
chanPoint2 := ht.OpenChannel(
|
||||
carol, alice, lntest.OpenChannelParams{
|
||||
Amt: 500000,
|
||||
},
|
||||
// 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 _, n := range nodes {
|
||||
ht.AssertChannelPolicy(
|
||||
n, alice.PubKeyStr, alicePolicy, chanPoint,
|
||||
)
|
||||
defer ht.CloseChannel(carol, chanPoint2)
|
||||
|
||||
// Alice and Bob should see each other's ChannelUpdates,
|
||||
// advertising the preferred routing policies.
|
||||
assertNodesPolicyUpdate(
|
||||
ht, nodes, alice, alicePolicy, chanPoint,
|
||||
ht.AssertChannelPolicy(
|
||||
n, bob.PubKeyStr, bobPolicy, chanPoint,
|
||||
)
|
||||
assertNodesPolicyUpdate(ht, nodes, bob, bobPolicy, chanPoint)
|
||||
|
||||
// They should now know about the default policies.
|
||||
for _, n := range nodes {
|
||||
ht.AssertChannelPolicy(
|
||||
n, alice.PubKeyStr, alicePolicy, chanPoint,
|
||||
)
|
||||
ht.AssertChannelPolicy(
|
||||
n, bob.PubKeyStr, bobPolicy, chanPoint,
|
||||
)
|
||||
}
|
||||
|
||||
// We should be able to forward a payment from Carol to Bob
|
||||
// through the new channel we opened.
|
||||
payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
|
||||
ht.CompletePaymentRequests(carol, payReqs)
|
||||
}
|
||||
|
||||
for i, feeScenario := range feeScenarios {
|
||||
ht.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
st.EnsureConnected(alice, bob)
|
||||
|
||||
st.RestartNode(carol)
|
||||
|
||||
// Because we're using ht.Subtest(), we need to restart
|
||||
// any node we have to refresh its runtime context.
|
||||
// Otherwise, we'll get a "context canceled" error on
|
||||
// RPC calls.
|
||||
st.EnsureConnected(alice, carol)
|
||||
|
||||
// Send Carol enough coins to be able to open a channel
|
||||
// to Alice.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
runTestCase(
|
||||
st, feeScenario,
|
||||
&expectedPolicies[i], &bobExpectedPolicy,
|
||||
)
|
||||
})
|
||||
}
|
||||
// We should be able to forward a payment from Carol to Bob
|
||||
// through the new channel we opened.
|
||||
payReqs, _, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
|
||||
ht.CompletePaymentRequests(carol, payReqs)
|
||||
}
|
||||
|
||||
// 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 *lntest.HarnessTest) {
|
||||
runBasicChannelCreationAndUpdates(ht, ht.Alice, ht.Bob)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
runBasicChannelCreationAndUpdates(ht, alice, bob)
|
||||
}
|
||||
|
||||
// runBasicChannelCreationAndUpdates tests multiple channel opening and closing,
|
||||
@ -491,34 +613,13 @@ func runBasicChannelCreationAndUpdates(ht *lntest.HarnessTest,
|
||||
)
|
||||
}
|
||||
|
||||
// testUpdateOnPendingOpenChannels checks that `update_add_htlc` followed by
|
||||
// `channel_ready` is properly handled. In specific, when a node is in a state
|
||||
// that it's still processing a remote `channel_ready` message, meanwhile an
|
||||
// `update_add_htlc` is received, this HTLC message is cached and settled once
|
||||
// processing `channel_ready` is complete.
|
||||
func testUpdateOnPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Test funder's behavior. Funder sees the channel pending, but fundee
|
||||
// sees it active and sends an HTLC.
|
||||
ht.Run("pending on funder side", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
testUpdateOnFunderPendingOpenChannels(st)
|
||||
})
|
||||
|
||||
// Test fundee's behavior. Fundee sees the channel pending, but funder
|
||||
// sees it active and sends an HTLC.
|
||||
ht.Run("pending on fundee side", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
testUpdateOnFundeePendingOpenChannels(st)
|
||||
})
|
||||
}
|
||||
|
||||
// testUpdateOnFunderPendingOpenChannels checks that when the fundee sends an
|
||||
// `update_add_htlc` followed by `channel_ready` while the funder is still
|
||||
// processing the fundee's `channel_ready`, the HTLC will be cached and
|
||||
// eventually settled.
|
||||
func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Grab the channel participants.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
|
||||
// Restart Alice with the config so she won't process Bob's
|
||||
// channel_ready msg immediately.
|
||||
@ -535,13 +636,8 @@ func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
Amt: funding.MaxBtcFundingAmount,
|
||||
PushAmt: funding.MaxBtcFundingAmount / 2,
|
||||
}
|
||||
pendingChan := ht.OpenChannelAssertPending(alice, bob, params)
|
||||
chanPoint := &lnrpc.ChannelPoint{
|
||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||
FundingTxidBytes: pendingChan.Txid,
|
||||
},
|
||||
OutputIndex: pendingChan.OutputIndex,
|
||||
}
|
||||
pending := ht.OpenChannelAssertPending(alice, bob, params)
|
||||
chanPoint := lntest.ChanPointFromPendingUpdate(pending)
|
||||
|
||||
// Alice and Bob should both consider the channel pending open.
|
||||
ht.AssertNumPendingOpenChannels(alice, 1)
|
||||
@ -559,6 +655,7 @@ func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Bob will consider the channel open as there's no wait time to send
|
||||
// and receive Alice's channel_ready message.
|
||||
ht.AssertNumPendingOpenChannels(bob, 0)
|
||||
ht.AssertChannelInGraph(bob, chanPoint)
|
||||
|
||||
// Alice and Bob now have different view of the channel. For Bob,
|
||||
// since the channel_ready messages are processed, he will have a
|
||||
@ -591,9 +688,6 @@ func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Once Alice sees the channel as active, she will process the cached
|
||||
// premature `update_add_htlc` and settles the payment.
|
||||
ht.AssertPaymentStatusFromStream(bobStream, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
// Close the channel.
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
// testUpdateOnFundeePendingOpenChannels checks that when the funder sends an
|
||||
@ -601,8 +695,8 @@ func testUpdateOnFunderPendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// processing the funder's `channel_ready`, the HTLC will be cached and
|
||||
// eventually settled.
|
||||
func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Grab the channel participants.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
|
||||
// Restart Bob with the config so he won't process Alice's
|
||||
// channel_ready msg immediately.
|
||||
@ -618,13 +712,8 @@ func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: funding.MaxBtcFundingAmount,
|
||||
}
|
||||
pendingChan := ht.OpenChannelAssertPending(alice, bob, params)
|
||||
chanPoint := &lnrpc.ChannelPoint{
|
||||
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{
|
||||
FundingTxidBytes: pendingChan.Txid,
|
||||
},
|
||||
OutputIndex: pendingChan.OutputIndex,
|
||||
}
|
||||
pending := ht.OpenChannelAssertPending(alice, bob, params)
|
||||
chanPoint := lntest.ChanPointFromPendingUpdate(pending)
|
||||
|
||||
// Alice and Bob should both consider the channel pending open.
|
||||
ht.AssertNumPendingOpenChannels(alice, 1)
|
||||
@ -636,6 +725,7 @@ func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Alice will consider the channel open as there's no wait time to send
|
||||
// and receive Bob's channel_ready message.
|
||||
ht.AssertNumPendingOpenChannels(alice, 0)
|
||||
ht.AssertChannelInGraph(alice, chanPoint)
|
||||
|
||||
// TODO(yy): we've prematurely marked the channel as open before
|
||||
// processing channel ready messages. We need to mark it as open after
|
||||
@ -674,9 +764,6 @@ func testUpdateOnFundeePendingOpenChannels(ht *lntest.HarnessTest) {
|
||||
// Once Bob sees the channel as active, he will process the cached
|
||||
// premature `update_add_htlc` and settles the payment.
|
||||
ht.AssertPaymentStatusFromStream(aliceStream, lnrpc.Payment_SUCCEEDED)
|
||||
|
||||
// Close the channel.
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
// verifyCloseUpdate is used to verify that a closed channel update is of the
|
||||
@ -744,9 +831,12 @@ func verifyCloseUpdate(chanUpdate *lnrpc.ChannelEventUpdate,
|
||||
// before the funding transaction is confirmed, that the FundingExpiryBlocks
|
||||
// field of a PendingChannels decreases.
|
||||
func testFundingExpiryBlocksOnPending(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
param := lntest.OpenChannelParams{Amt: 100000}
|
||||
update := ht.OpenChannelAssertPending(alice, bob, param)
|
||||
ht.OpenChannelAssertPending(alice, bob, param)
|
||||
|
||||
// At this point, the channel's funding transaction will have been
|
||||
// broadcast, but not confirmed. Alice and Bob's nodes should reflect
|
||||
@ -767,20 +857,6 @@ func testFundingExpiryBlocksOnPending(ht *lntest.HarnessTest) {
|
||||
// Mine 1 block to confirm the funding transaction, and then close the
|
||||
// channel.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
chanPoint := lntest.ChanPointFromPendingUpdate(update)
|
||||
|
||||
// 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)
|
||||
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
// testSimpleTaprootChannelActivation ensures that a simple taproot channel is
|
||||
@ -793,9 +869,7 @@ func testSimpleTaprootChannelActivation(ht *lntest.HarnessTest) {
|
||||
|
||||
// Make the new set of participants.
|
||||
alice := ht.NewNode("alice", simpleTaprootChanArgs)
|
||||
defer ht.Shutdown(alice)
|
||||
bob := ht.NewNode("bob", simpleTaprootChanArgs)
|
||||
defer ht.Shutdown(bob)
|
||||
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||
|
||||
@ -832,9 +906,6 @@ func testSimpleTaprootChannelActivation(ht *lntest.HarnessTest) {
|
||||
|
||||
// Verify that Alice sees an active channel to Bob.
|
||||
ht.AssertChannelActive(alice, chanPoint)
|
||||
|
||||
// Our test is done and Alice closes her channel to Bob.
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
// testOpenChannelLockedBalance tests that when a funding reservation is
|
||||
@ -842,7 +913,6 @@ func testSimpleTaprootChannelActivation(ht *lntest.HarnessTest) {
|
||||
// up as locked balance in the WalletBalance response.
|
||||
func testOpenChannelLockedBalance(ht *lntest.HarnessTest) {
|
||||
var (
|
||||
bob = ht.Bob
|
||||
req *lnrpc.ChannelAcceptRequest
|
||||
err error
|
||||
)
|
||||
@ -850,6 +920,7 @@ func testOpenChannelLockedBalance(ht *lntest.HarnessTest) {
|
||||
// Create a new node so we can assert exactly how much fund has been
|
||||
// locked later.
|
||||
alice := ht.NewNode("alice", nil)
|
||||
bob := ht.NewNode("bob", nil)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||
|
||||
// Connect the nodes.
|
||||
|
@ -179,21 +179,19 @@ func testPaymentSucceededHTLCRemoteSwept(ht *lntest.HarnessTest) {
|
||||
// out and claimed onchain via the timeout path, the payment will be marked as
|
||||
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
||||
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
||||
// time out. Alice will also be restarted to make sure resumed payments are
|
||||
// also marked as failed.
|
||||
// time out.
|
||||
func testPaymentFailedHTLCLocalSwept(ht *lntest.HarnessTest) {
|
||||
success := ht.Run("fail payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runTestPaymentHTLCTimeout(st, false)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
runTestPaymentHTLCTimeout(ht, false)
|
||||
}
|
||||
|
||||
ht.Run("fail resumed payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runTestPaymentHTLCTimeout(st, true)
|
||||
})
|
||||
// testPaymentFailedHTLCLocalSweptResumed checks that when an outgoing HTLC is
|
||||
// timed out and claimed onchain via the timeout path, the payment will be
|
||||
// marked as failed. This test creates a topology from Alice -> Bob, and let
|
||||
// Alice send payments to Bob. Bob then goes offline, such that Alice's
|
||||
// outgoing HTLC will time out. Alice will be restarted to make sure resumed
|
||||
// payments are also marked as failed.
|
||||
func testPaymentFailedHTLCLocalSweptResumed(ht *lntest.HarnessTest) {
|
||||
runTestPaymentHTLCTimeout(ht, true)
|
||||
}
|
||||
|
||||
// runTestPaymentHTLCTimeout is the helper function that actually runs the
|
||||
@ -341,132 +339,104 @@ func runTestPaymentHTLCTimeout(ht *lntest.HarnessTest, restartAlice bool) {
|
||||
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
|
||||
}
|
||||
|
||||
// testSendDirectPayment creates a topology Alice->Bob and then tests that
|
||||
// Alice can send a direct payment to Bob. This test modifies the fee estimator
|
||||
// to return floor fee rate(1 sat/vb).
|
||||
func testSendDirectPayment(ht *lntest.HarnessTest) {
|
||||
// Grab Alice and Bob's nodes for convenience.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
// runSendDirectPayment opens a channel between Alice and Bob using the
|
||||
// specified params. It then sends a payment from Alice to Bob and asserts it
|
||||
// being successful.
|
||||
func runSendDirectPayment(ht *lntest.HarnessTest, cfgs [][]string,
|
||||
params lntest.OpenChannelParams) {
|
||||
|
||||
// Create a list of commitment types we want to test.
|
||||
commitmentTypes := []lnrpc.CommitmentType{
|
||||
lnrpc.CommitmentType_ANCHORS,
|
||||
lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
// Set the fee estimate to 1sat/vbyte.
|
||||
ht.SetFeeEstimate(250)
|
||||
|
||||
// Create a two-hop network: Alice -> Bob.
|
||||
_, nodes := ht.CreateSimpleNetwork(cfgs, params)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// 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{
|
||||
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.
|
||||
payReqs := []string{invoiceResp.PaymentRequest}
|
||||
ht.CompletePaymentRequests(alice, payReqs)
|
||||
|
||||
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 pubkey")
|
||||
|
||||
// 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 hash")
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// testSendDirectPaymentAnchor creates a topology Alice->Bob using anchor
|
||||
// channel and then tests that Alice can send a direct payment to Bob.
|
||||
func testSendDirectPaymentAnchor(ht *lntest.HarnessTest) {
|
||||
// Create a two-hop network: Alice -> Bob using anchor channel.
|
||||
//
|
||||
// Prepare params.
|
||||
params := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
cfg := node.CfgAnchor
|
||||
cfgs := [][]string{cfg, cfg}
|
||||
|
||||
runSendDirectPayment(ht, cfgs, params)
|
||||
}
|
||||
|
||||
// testSendDirectPaymentSimpleTaproot creates a topology Alice->Bob using
|
||||
// simple taproot channel and then tests that Alice can send a direct payment
|
||||
// to Bob.
|
||||
func testSendDirectPaymentSimpleTaproot(ht *lntest.HarnessTest) {
|
||||
c := lnrpc.CommitmentType_SIMPLE_TAPROOT
|
||||
|
||||
// Create a two-hop network: Alice -> Bob using simple taproot channel.
|
||||
//
|
||||
// Prepare params.
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
CommitmentType: c,
|
||||
Private: true,
|
||||
}
|
||||
|
||||
// testSendPayment opens a channel between Alice and Bob using the
|
||||
// specified params. It then sends a payment from Alice to Bob and
|
||||
// asserts it being successful.
|
||||
testSendPayment := func(ht *lntest.HarnessTest,
|
||||
params lntest.OpenChannelParams) {
|
||||
cfg := node.CfgSimpleTaproot
|
||||
cfgs := [][]string{cfg, cfg}
|
||||
|
||||
// Check that there are no payments before test.
|
||||
chanPoint := ht.OpenChannel(alice, bob, params)
|
||||
|
||||
// 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{
|
||||
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.
|
||||
payReqs := []string{invoiceResp.PaymentRequest}
|
||||
ht.CompletePaymentRequests(alice, payReqs)
|
||||
|
||||
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 pubkey")
|
||||
|
||||
// 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 hash")
|
||||
|
||||
// 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")
|
||||
|
||||
// Delete all payments from Alice. DB should have no payments.
|
||||
alice.RPC.DeleteAllPayments()
|
||||
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.
|
||||
//
|
||||
// NOTE: This implicitly tests that the channel link is active
|
||||
// before closing this channel. The above payment will trigger
|
||||
// a commitment dance in both of the nodes. If the node fails
|
||||
// to update the commitment state, we will fail to close the
|
||||
// channel as the link won't be active.
|
||||
ht.CloseChannel(alice, chanPoint)
|
||||
}
|
||||
|
||||
// Run the test cases.
|
||||
for _, ct := range commitmentTypes {
|
||||
ht.Run(ct.String(), func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
// Set the fee estimate to 1sat/vbyte.
|
||||
st.SetFeeEstimate(250)
|
||||
|
||||
// Restart the nodes with the specified commitment type.
|
||||
args := lntest.NodeArgsForCommitType(ct)
|
||||
st.RestartNodeWithExtraArgs(alice, args)
|
||||
st.RestartNodeWithExtraArgs(bob, args)
|
||||
|
||||
// Make sure they are connected.
|
||||
st.EnsureConnected(alice, bob)
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and
|
||||
// Bob with Alice being the sole funder of the channel.
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: 100_000,
|
||||
CommitmentType: ct,
|
||||
}
|
||||
|
||||
// Open private channel for taproot channels.
|
||||
if ct == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
params.Private = true
|
||||
}
|
||||
|
||||
testSendPayment(st, params)
|
||||
})
|
||||
}
|
||||
runSendDirectPayment(ht, cfgs, params)
|
||||
}
|
||||
|
||||
func testListPayments(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Check that there are no payments before test.
|
||||
ht.AssertNumPayments(alice, 0)
|
||||
@ -474,9 +444,7 @@ func testListPayments(ht *lntest.HarnessTest) {
|
||||
// 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, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
ht.OpenChannel(alice, bob, lntest.OpenChannelParams{Amt: chanAmt})
|
||||
|
||||
// Now that the channel is open, create an invoice for Bob which
|
||||
// expects a payment of 1000 satoshis from Alice paid via a particular
|
||||
@ -635,17 +603,6 @@ func testListPayments(ht *lntest.HarnessTest) {
|
||||
|
||||
// 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
|
||||
@ -658,7 +615,10 @@ func testPaymentFollowingChannelOpen(ht *lntest.HarnessTest) {
|
||||
channelCapacity := paymentAmt * 1000
|
||||
|
||||
// We first establish a channel between Alice and Bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
p := lntest.OpenChannelParams{
|
||||
Amt: channelCapacity,
|
||||
}
|
||||
@ -692,19 +652,6 @@ func testPaymentFollowingChannelOpen(ht *lntest.HarnessTest) {
|
||||
// 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.
|
||||
@ -812,11 +759,6 @@ func runAsyncPayments(ht *lntest.HarnessTest, alice, bob *node.HarnessNode,
|
||||
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
|
||||
@ -924,17 +866,14 @@ func testBidirectionalAsyncPayments(ht *lntest.HarnessTest) {
|
||||
// Next query for Bob's and Alice's channel states, in order to confirm
|
||||
// that all payment have been successfully 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 *lntest.HarnessTest) {
|
||||
const chanAmt = btcutil.Amount(500000)
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Create a new invoice subscription client for Bob, the notification
|
||||
// should be dispatched shortly below.
|
||||
@ -943,9 +882,7 @@ func testInvoiceSubscriptions(ht *lntest.HarnessTest) {
|
||||
|
||||
// Open a channel with 500k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
chanPoint := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
ht.OpenChannel(alice, bob, lntest.OpenChannelParams{Amt: chanAmt})
|
||||
|
||||
// Next create a new invoice for Bob requesting 1k satoshis.
|
||||
const paymentAmt = 1000
|
||||
@ -1047,16 +984,6 @@ func testInvoiceSubscriptions(ht *lntest.HarnessTest) {
|
||||
|
||||
// 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
|
||||
@ -1104,23 +1031,16 @@ func assertChannelState(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
||||
// 5.) Alice observes a failed OR succeeded payment with failure reason
|
||||
// FAILURE_REASON_CANCELED which suppresses further payment attempts.
|
||||
func testPaymentFailureReasonCanceled(ht *lntest.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 := lntest.OpenChannelParams{Amt: chanAmt}
|
||||
reqs := []*lntest.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.AssertChannelInGraph(alice, cpBC)
|
||||
// Initialize the test context with 3 connected nodes.
|
||||
cfgs := [][]string{nil, nil, nil}
|
||||
|
||||
// Open and wait for channels.
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, p)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
cpAB := chanPoints[0]
|
||||
|
||||
// Connect the interceptor.
|
||||
interceptor, cancelInterceptor := bob.RPC.HtlcInterceptor()
|
||||
@ -1130,7 +1050,8 @@ func testPaymentFailureReasonCanceled(ht *lntest.HarnessTest) {
|
||||
// htlc even though the payment context was canceled before invoice
|
||||
// settlement.
|
||||
sendPaymentInterceptAndCancel(
|
||||
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_RESUME,
|
||||
ht, alice, bob, carol, cpAB,
|
||||
routerrpc.ResolveHoldForwardAction_RESUME,
|
||||
lnrpc.Payment_SUCCEEDED, interceptor,
|
||||
)
|
||||
|
||||
@ -1140,24 +1061,18 @@ func testPaymentFailureReasonCanceled(ht *lntest.HarnessTest) {
|
||||
// Note that we'd have to reset Alice's mission control if we tested the
|
||||
// htlc fail case before the htlc resume case.
|
||||
sendPaymentInterceptAndCancel(
|
||||
ht, ts, cpAB, routerrpc.ResolveHoldForwardAction_FAIL,
|
||||
ht, alice, bob, carol, cpAB,
|
||||
routerrpc.ResolveHoldForwardAction_FAIL,
|
||||
lnrpc.Payment_FAILED, interceptor,
|
||||
)
|
||||
|
||||
// Finally, close channels.
|
||||
ht.CloseChannel(alice, cpAB)
|
||||
ht.CloseChannel(bob, cpBC)
|
||||
}
|
||||
|
||||
func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
||||
ts *interceptorTestScenario, cpAB *lnrpc.ChannelPoint,
|
||||
alice, bob, carol *node.HarnessNode, cpAB *lnrpc.ChannelPoint,
|
||||
interceptorAction routerrpc.ResolveHoldForwardAction,
|
||||
expectedPaymentStatus lnrpc.Payment_PaymentStatus,
|
||||
interceptor rpc.InterceptorClient) {
|
||||
|
||||
// Prepare the test cases.
|
||||
alice, bob, carol := ts.alice, ts.bob, ts.carol
|
||||
|
||||
// Prepare the test cases.
|
||||
addResponse := carol.RPC.AddInvoice(&lnrpc.Invoice{
|
||||
ValueMsat: 1000,
|
||||
@ -1227,21 +1142,21 @@ func sendPaymentInterceptAndCancel(ht *lntest.HarnessTest,
|
||||
// out and claimed onchain via the timeout path, the payment will be marked as
|
||||
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
||||
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
||||
// time out. Alice will also be restarted to make sure resumed payments are
|
||||
// also marked as failed.
|
||||
// time out.
|
||||
func testSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest) {
|
||||
success := ht.Run("fail payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runSendToRouteFailHTLCTimeout(st, false)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
runSendToRouteFailHTLCTimeout(ht, false)
|
||||
}
|
||||
|
||||
ht.Run("fail resumed payment", func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
runTestPaymentHTLCTimeout(st, true)
|
||||
})
|
||||
// testSendToRouteFailHTLCTimeout is similar to
|
||||
// testPaymentFailedHTLCLocalSwept. The only difference is the `SendPayment` is
|
||||
// replaced with `SendToRouteV2`. It checks that when an outgoing HTLC is timed
|
||||
// out and claimed onchain via the timeout path, the payment will be marked as
|
||||
// failed. This test creates a topology from Alice -> Bob, and let Alice send
|
||||
// payments to Bob. Bob then goes offline, such that Alice's outgoing HTLC will
|
||||
// time out. Alice will be restarted to make sure resumed payments are also
|
||||
// marked as failed.
|
||||
func testSendToRouteFailHTLCTimeoutResumed(ht *lntest.HarnessTest) {
|
||||
runTestPaymentHTLCTimeout(ht, true)
|
||||
}
|
||||
|
||||
// runSendToRouteFailHTLCTimeout is the helper function that actually runs the
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||
@ -27,126 +26,92 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testPsbtChanFunding makes sure a channel can be opened between carol and dave
|
||||
// by using a Partially Signed Bitcoin Transaction that funds the channel
|
||||
// multisig funding output.
|
||||
func testPsbtChanFunding(ht *lntest.HarnessTest) {
|
||||
const (
|
||||
burnAddr = "bcrt1qxsnqpdc842lu8c0xlllgvejt6rhy49u6fmpgyz"
|
||||
)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
commitmentType lnrpc.CommitmentType
|
||||
private bool
|
||||
}{
|
||||
{
|
||||
name: "anchors",
|
||||
commitmentType: lnrpc.CommitmentType_ANCHORS,
|
||||
private: false,
|
||||
// psbtFundingTestCases contains the test cases for funding via PSBT.
|
||||
var psbtFundingTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "psbt funding anchor",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFunding(
|
||||
ht, false, lnrpc.CommitmentType_ANCHORS,
|
||||
)
|
||||
},
|
||||
{
|
||||
name: "simple taproot",
|
||||
commitmentType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
|
||||
// Set this to true once simple taproot channels can be
|
||||
// announced to the network.
|
||||
private: true,
|
||||
},
|
||||
{
|
||||
Name: "psbt external funding anchor",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFundingExternal(
|
||||
ht, false, lnrpc.CommitmentType_ANCHORS,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
success := ht.T.Run(tc.name, func(tt *testing.T) {
|
||||
st := ht.Subtest(tt)
|
||||
|
||||
args := lntest.NodeArgsForCommitType(tc.commitmentType)
|
||||
|
||||
// First, we'll create two new nodes that we'll use to
|
||||
// open channels between for this test. Dave gets some
|
||||
// coins that will be used to fund the PSBT, just to
|
||||
// make sure that Carol has an empty wallet.
|
||||
carol := st.NewNode("carol", args)
|
||||
dave := st.NewNode("dave", args)
|
||||
|
||||
// We just send enough funds to satisfy the anchor
|
||||
// channel reserve for 5 channels (50k sats).
|
||||
st.FundCoins(50_000, carol)
|
||||
st.FundCoins(50_000, dave)
|
||||
|
||||
st.RunTestCase(&lntest.TestCase{
|
||||
Name: tc.name,
|
||||
TestFunc: func(sst *lntest.HarnessTest) {
|
||||
runPsbtChanFunding(
|
||||
sst, carol, dave, tc.private,
|
||||
tc.commitmentType,
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// Empty out the wallets so there aren't any lingering
|
||||
// coins.
|
||||
sendAllCoinsConfirm(st, carol, burnAddr)
|
||||
sendAllCoinsConfirm(st, dave, burnAddr)
|
||||
|
||||
// Now we test the second scenario. Again, we just send
|
||||
// enough funds to satisfy the anchor channel reserve
|
||||
// for 5 channels (50k sats).
|
||||
st.FundCoins(50_000, carol)
|
||||
st.FundCoins(50_000, dave)
|
||||
|
||||
st.RunTestCase(&lntest.TestCase{
|
||||
Name: tc.name,
|
||||
TestFunc: func(sst *lntest.HarnessTest) {
|
||||
runPsbtChanFundingExternal(
|
||||
sst, carol, dave, tc.private,
|
||||
tc.commitmentType,
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
// Empty out the wallets a last time, so there aren't
|
||||
// any lingering coins.
|
||||
sendAllCoinsConfirm(st, carol, burnAddr)
|
||||
sendAllCoinsConfirm(st, dave, burnAddr)
|
||||
|
||||
// The last test case tests the anchor channel reserve
|
||||
// itself, so we need empty wallets.
|
||||
st.RunTestCase(&lntest.TestCase{
|
||||
Name: tc.name,
|
||||
TestFunc: func(sst *lntest.HarnessTest) {
|
||||
runPsbtChanFundingSingleStep(
|
||||
sst, carol, dave, tc.private,
|
||||
tc.commitmentType,
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Name: "psbt single step funding anchor",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFundingSingleStep(
|
||||
ht, false, lnrpc.CommitmentType_ANCHORS,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "psbt funding simple taproot",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFunding(
|
||||
ht, true, lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "psbt external funding simple taproot",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFundingExternal(
|
||||
ht, true, lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "psbt single step funding simple taproot",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
runPsbtChanFundingSingleStep(
|
||||
ht, true, lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// runPsbtChanFunding makes sure a channel can be opened between carol and dave
|
||||
// by using a Partially Signed Bitcoin Transaction that funds the channel
|
||||
// multisig funding output.
|
||||
func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
||||
private bool, commitType lnrpc.CommitmentType) {
|
||||
func runPsbtChanFunding(ht *lntest.HarnessTest, private bool,
|
||||
commitType lnrpc.CommitmentType) {
|
||||
|
||||
args := lntest.NodeArgsForCommitType(commitType)
|
||||
|
||||
// First, we'll create two new nodes that we'll use to open channels
|
||||
// between for this test. Dave gets some coins that will be used to
|
||||
// fund the PSBT, just to make sure that Carol has an empty wallet.
|
||||
carol := ht.NewNode("carol", args)
|
||||
dave := ht.NewNode("dave", args)
|
||||
|
||||
// We just send enough funds to satisfy the anchor channel reserve for
|
||||
// 5 channels (50k sats).
|
||||
ht.FundCoins(50_000, carol)
|
||||
ht.FundCoins(50_000, dave)
|
||||
|
||||
runPsbtChanFundingWithNodes(ht, carol, dave, private, commitType)
|
||||
}
|
||||
|
||||
// runPsbtChanFundingWithNodes run a test case to make sure a channel can be
|
||||
// opened between carol and dave by using a PSBT that funds the channel
|
||||
// multisig funding output.
|
||||
func runPsbtChanFundingWithNodes(ht *lntest.HarnessTest, carol,
|
||||
dave *node.HarnessNode, private bool, commitType lnrpc.CommitmentType) {
|
||||
|
||||
const chanSize = funding.MaxBtcFundingAmount
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
|
||||
// Before we start the test, we'll ensure both sides are connected so
|
||||
// the funding flow can be properly executed.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
ht.EnsureConnected(carol, alice)
|
||||
|
||||
@ -307,6 +272,9 @@ func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
||||
txHash := finalTx.TxHash()
|
||||
block := ht.MineBlocksAndAssertNumTxes(6, 1)[0]
|
||||
ht.AssertTxInBlock(block, txHash)
|
||||
|
||||
ht.AssertChannelActive(carol, chanPoint)
|
||||
ht.AssertChannelActive(carol, chanPoint2)
|
||||
ht.AssertChannelInGraph(carol, chanPoint)
|
||||
ht.AssertChannelInGraph(carol, chanPoint2)
|
||||
|
||||
@ -324,27 +292,33 @@ func runPsbtChanFunding(ht *lntest.HarnessTest, carol, dave *node.HarnessNode,
|
||||
}
|
||||
resp := dave.RPC.AddInvoice(invoice)
|
||||
ht.CompletePaymentRequests(carol, []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(carol, chanPoint)
|
||||
ht.CloseChannel(carol, chanPoint2)
|
||||
}
|
||||
|
||||
// runPsbtChanFundingExternal makes sure a channel can be opened between carol
|
||||
// and dave by using a Partially Signed Bitcoin Transaction that funds the
|
||||
// channel multisig funding output and is fully funded by an external third
|
||||
// party.
|
||||
func runPsbtChanFundingExternal(ht *lntest.HarnessTest, carol,
|
||||
dave *node.HarnessNode, private bool, commitType lnrpc.CommitmentType) {
|
||||
func runPsbtChanFundingExternal(ht *lntest.HarnessTest, private bool,
|
||||
commitType lnrpc.CommitmentType) {
|
||||
|
||||
args := lntest.NodeArgsForCommitType(commitType)
|
||||
|
||||
// First, we'll create two new nodes that we'll use to open channels
|
||||
// between for this test. Dave gets some coins that will be used to
|
||||
// fund the PSBT, just to make sure that Carol has an empty wallet.
|
||||
carol := ht.NewNode("carol", args)
|
||||
dave := ht.NewNode("dave", args)
|
||||
|
||||
// We just send enough funds to satisfy the anchor channel reserve for
|
||||
// 5 channels (50k sats).
|
||||
ht.FundCoins(50_000, carol)
|
||||
ht.FundCoins(50_000, dave)
|
||||
|
||||
const chanSize = funding.MaxBtcFundingAmount
|
||||
|
||||
// Before we start the test, we'll ensure both sides are connected so
|
||||
// the funding flow can be properly executed.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
ht.EnsureConnected(carol, alice)
|
||||
|
||||
@ -499,26 +473,25 @@ func runPsbtChanFundingExternal(ht *lntest.HarnessTest, carol,
|
||||
}
|
||||
resp := dave.RPC.AddInvoice(invoice)
|
||||
ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})
|
||||
|
||||
// To conclude, we'll close the newly created channel between Carol and
|
||||
// Dave. This function will also block until the channels are closed and
|
||||
// will additionally assert the relevant channel closing post
|
||||
// conditions.
|
||||
ht.CloseChannel(carol, chanPoint)
|
||||
ht.CloseChannel(carol, chanPoint2)
|
||||
}
|
||||
|
||||
// runPsbtChanFundingSingleStep checks whether PSBT funding works also when
|
||||
// the wallet of both nodes are empty and one of them uses PSBT and an external
|
||||
// wallet to fund the channel while creating reserve output in the same
|
||||
// transaction.
|
||||
func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, carol,
|
||||
dave *node.HarnessNode, private bool, commitType lnrpc.CommitmentType) {
|
||||
func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, private bool,
|
||||
commitType lnrpc.CommitmentType) {
|
||||
|
||||
args := lntest.NodeArgsForCommitType(commitType)
|
||||
|
||||
// First, we'll create two new nodes that we'll use to open channels
|
||||
// between for this test.
|
||||
carol := ht.NewNode("carol", args)
|
||||
dave := ht.NewNode("dave", args)
|
||||
|
||||
const chanSize = funding.MaxBtcFundingAmount
|
||||
|
||||
alice := ht.Alice
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
// Get new address for anchor reserve.
|
||||
req := &lnrpc.NewAddressRequest{
|
||||
@ -650,12 +623,6 @@ func runPsbtChanFundingSingleStep(ht *lntest.HarnessTest, carol,
|
||||
}
|
||||
resp := dave.RPC.AddInvoice(invoice)
|
||||
ht.CompletePaymentRequests(carol, []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(carol, chanPoint)
|
||||
}
|
||||
|
||||
// testSignPsbt tests that the SignPsbt RPC works correctly.
|
||||
@ -697,7 +664,8 @@ func testSignPsbt(ht *lntest.HarnessTest) {
|
||||
for _, tc := range psbtTestRunners {
|
||||
succeed := ht.Run(tc.name, func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
tc.runner(st, st.Alice)
|
||||
alice := st.NewNodeWithCoins("Alice", nil)
|
||||
tc.runner(st, alice)
|
||||
})
|
||||
|
||||
// Abort the test if failed.
|
||||
@ -1088,6 +1056,9 @@ func runFundAndSignPsbt(ht *lntest.HarnessTest, alice *node.HarnessNode) {
|
||||
// a PSBT that already specifies an input but where the user still wants the
|
||||
// wallet to perform coin selection.
|
||||
func testFundPsbt(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// We test a pay-join between Alice and Bob. Bob wants to send Alice
|
||||
// 5 million Satoshis in a non-obvious way. So Bob selects a UTXO that's
|
||||
// bigger than 5 million Satoshis and expects the change minus the send
|
||||
@ -1095,20 +1066,20 @@ func testFundPsbt(ht *lntest.HarnessTest) {
|
||||
// combines her change with the 5 million Satoshis from Bob. With this
|
||||
// Alice ends up paying the fees for a transfer to her.
|
||||
const sendAmount = 5_000_000
|
||||
aliceAddr := ht.Alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||
aliceAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
||||
})
|
||||
bobAddr := ht.Bob.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||
bobAddr := bob.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||
Type: lnrpc.AddressType_TAPROOT_PUBKEY,
|
||||
})
|
||||
|
||||
ht.Alice.UpdateState()
|
||||
ht.Bob.UpdateState()
|
||||
aliceStartBalance := ht.Alice.State.Wallet.TotalBalance
|
||||
bobStartBalance := ht.Bob.State.Wallet.TotalBalance
|
||||
alice.UpdateState()
|
||||
bob.UpdateState()
|
||||
aliceStartBalance := alice.State.Wallet.TotalBalance
|
||||
bobStartBalance := bob.State.Wallet.TotalBalance
|
||||
|
||||
var bobUtxo *lnrpc.Utxo
|
||||
bobUnspent := ht.Bob.RPC.ListUnspent(&walletrpc.ListUnspentRequest{})
|
||||
bobUnspent := bob.RPC.ListUnspent(&walletrpc.ListUnspentRequest{})
|
||||
for _, utxo := range bobUnspent.Utxos {
|
||||
if utxo.AmountSat > sendAmount {
|
||||
bobUtxo = utxo
|
||||
@ -1145,7 +1116,7 @@ func testFundPsbt(ht *lntest.HarnessTest) {
|
||||
require.NoError(ht, err)
|
||||
|
||||
derivation, trDerivation := getAddressBip32Derivation(
|
||||
ht, bobUtxo.Address, ht.Bob,
|
||||
ht, bobUtxo.Address, bob,
|
||||
)
|
||||
|
||||
bobUtxoPkScript, _ := hex.DecodeString(bobUtxo.PkScript)
|
||||
@ -1165,31 +1136,31 @@ func testFundPsbt(ht *lntest.HarnessTest) {
|
||||
// We have the template now. Bob basically funds the 5 million Sats to
|
||||
// send to Alice and Alice now only needs to coin select to pay for the
|
||||
// fees.
|
||||
fundedPacket := fundPsbtCoinSelect(ht, ht.Alice, packet, 1)
|
||||
fundedPacket := fundPsbtCoinSelect(ht, alice, packet, 1)
|
||||
txFee, err := fundedPacket.GetTxFee()
|
||||
require.NoError(ht, err)
|
||||
|
||||
// We now let Bob sign the transaction.
|
||||
signedPacket := signPacket(ht, ht.Bob, fundedPacket)
|
||||
signedPacket := signPacket(ht, bob, fundedPacket)
|
||||
|
||||
// And then Alice, which should give us a fully signed TX.
|
||||
signedPacket = signPacket(ht, ht.Alice, signedPacket)
|
||||
signedPacket = signPacket(ht, alice, signedPacket)
|
||||
|
||||
// We should be able to finalize the PSBT and extract the final TX now.
|
||||
extractPublishAndMine(ht, ht.Alice, signedPacket)
|
||||
extractPublishAndMine(ht, alice, signedPacket)
|
||||
|
||||
// Make sure the new wallet balances are reflected correctly.
|
||||
ht.AssertActiveNodesSynced()
|
||||
ht.Alice.UpdateState()
|
||||
ht.Bob.UpdateState()
|
||||
alice.UpdateState()
|
||||
bob.UpdateState()
|
||||
|
||||
require.Equal(
|
||||
ht, aliceStartBalance+sendAmount-int64(txFee),
|
||||
ht.Alice.State.Wallet.TotalBalance,
|
||||
alice.State.Wallet.TotalBalance,
|
||||
)
|
||||
require.Equal(
|
||||
ht, bobStartBalance-sendAmount,
|
||||
ht.Bob.State.Wallet.TotalBalance,
|
||||
bob.State.Wallet.TotalBalance,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1596,6 +1567,9 @@ func sendAllCoinsToAddrType(ht *lntest.HarnessTest,
|
||||
// the channel opening. The psbt funding flow is used to simulate this behavior
|
||||
// because we can easily let the remote peer run into the timeout.
|
||||
func testPsbtChanFundingFailFlow(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
const chanSize = funding.MaxBtcFundingAmount
|
||||
|
||||
// Decrease the timeout window for the remote peer to accelerate the
|
||||
@ -1604,12 +1578,10 @@ func testPsbtChanFundingFailFlow(ht *lntest.HarnessTest) {
|
||||
"--dev.reservationtimeout=1s",
|
||||
"--dev.zombiesweeperinterval=1s",
|
||||
}
|
||||
ht.RestartNodeWithExtraArgs(ht.Bob, args)
|
||||
ht.RestartNodeWithExtraArgs(bob, args)
|
||||
|
||||
// Before we start the test, we'll ensure both sides are connected so
|
||||
// the funding flow can be properly executed.
|
||||
alice := ht.Alice
|
||||
bob := ht.Bob
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// At this point, we can begin our PSBT channel funding workflow. We'll
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -16,12 +17,14 @@ import (
|
||||
// NOTE FOR REVIEW: this could be improved by blasting the channel with HTLC
|
||||
// traffic on both sides to increase the surface area of the change under test.
|
||||
func testQuiescence(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
cfg := node.CfgAnchor
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{cfg, cfg}, lntest.OpenChannelParams{
|
||||
Amt: btcutil.Amount(1000000),
|
||||
})
|
||||
|
||||
chanPoint := ht.OpenChannel(bob, alice, lntest.OpenChannelParams{
|
||||
Amt: btcutil.Amount(1000000),
|
||||
})
|
||||
defer ht.CloseChannel(bob, chanPoint)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
chanPoint := chanPoints[0]
|
||||
|
||||
res := alice.RPC.Quiesce(&devrpc.QuiescenceRequest{
|
||||
ChanId: chanPoint,
|
||||
@ -30,7 +33,7 @@ func testQuiescence(ht *lntest.HarnessTest) {
|
||||
require.True(ht, res.Initiator)
|
||||
|
||||
req := &routerrpc.SendPaymentRequest{
|
||||
Dest: ht.Alice.PubKey[:],
|
||||
Dest: alice.PubKey[:],
|
||||
Amt: 100,
|
||||
PaymentHash: ht.Random32Bytes(),
|
||||
FinalCltvDelta: finalCltvDelta,
|
||||
@ -39,7 +42,7 @@ func testQuiescence(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
ht.SendPaymentAssertFail(
|
||||
ht.Bob, req,
|
||||
bob, req,
|
||||
// This fails with insufficient balance because the bandwidth
|
||||
// manager reports 0 bandwidth if a link is not eligible for
|
||||
// forwarding, which is the case during quiescence.
|
||||
|
@ -16,6 +16,59 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// remoteSignerTestCases defines a set of test cases to run against the remote
|
||||
// signer.
|
||||
var remoteSignerTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "random seed",
|
||||
TestFunc: testRemoteSignerRadomSeed,
|
||||
},
|
||||
{
|
||||
Name: "account import",
|
||||
TestFunc: testRemoteSignerAccountImport,
|
||||
},
|
||||
{
|
||||
Name: "channel open",
|
||||
TestFunc: testRemoteSignerChannelOpen,
|
||||
},
|
||||
{
|
||||
Name: "funding input types",
|
||||
TestFunc: testRemoteSignerChannelFundingInputTypes,
|
||||
},
|
||||
{
|
||||
Name: "funding async payments",
|
||||
TestFunc: testRemoteSignerAsyncPayments,
|
||||
},
|
||||
{
|
||||
Name: "funding async payments taproot",
|
||||
TestFunc: testRemoteSignerAsyncPaymentsTaproot,
|
||||
},
|
||||
{
|
||||
Name: "shared key",
|
||||
TestFunc: testRemoteSignerSharedKey,
|
||||
},
|
||||
{
|
||||
Name: "bump fee",
|
||||
TestFunc: testRemoteSignerBumpFee,
|
||||
},
|
||||
{
|
||||
Name: "psbt",
|
||||
TestFunc: testRemoteSignerPSBT,
|
||||
},
|
||||
{
|
||||
Name: "sign output raw",
|
||||
TestFunc: testRemoteSignerSignOutputRaw,
|
||||
},
|
||||
{
|
||||
Name: "verify msg",
|
||||
TestFunc: testRemoteSignerSignVerifyMsg,
|
||||
},
|
||||
{
|
||||
Name: "taproot",
|
||||
TestFunc: testRemoteSignerTaproot,
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
rootKey = "tprv8ZgxMBicQKsPe6jS4vDm2n7s42Q6MpvghUQqMmSKG7bTZvGKtjrcU3" +
|
||||
"PGzMNG37yzxywrcdvgkwrr8eYXJmbwdvUNVT4Ucv7ris4jvA7BUmg"
|
||||
@ -53,25 +106,115 @@ var (
|
||||
}}
|
||||
)
|
||||
|
||||
// testRemoteSigner tests that a watch-only wallet can use a remote signing
|
||||
// wallet to perform any signing or ECDH operations.
|
||||
func testRemoteSigner(ht *lntest.HarnessTest) {
|
||||
type testCase struct {
|
||||
name string
|
||||
randomSeed bool
|
||||
sendCoins bool
|
||||
commitType lnrpc.CommitmentType
|
||||
fn func(tt *lntest.HarnessTest,
|
||||
wo, carol *node.HarnessNode)
|
||||
// remoteSignerTestCase defines a test case for the remote signer test suite.
|
||||
type remoteSignerTestCase struct {
|
||||
name string
|
||||
randomSeed bool
|
||||
sendCoins bool
|
||||
commitType lnrpc.CommitmentType
|
||||
fn func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode)
|
||||
}
|
||||
|
||||
// prepareRemoteSignerTest prepares a test case for the remote signer test
|
||||
// suite by creating three nodes.
|
||||
func prepareRemoteSignerTest(ht *lntest.HarnessTest, tc remoteSignerTestCase) (
|
||||
*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 !tc.randomSeed {
|
||||
signer = ht.RestoreNodeWithSeed(
|
||||
"Signer", nil, password, nil, rootKey, 0, nil,
|
||||
)
|
||||
} else {
|
||||
signer = ht.NewNode("Signer", nil)
|
||||
signerNodePubKey = signer.PubKeyStr
|
||||
|
||||
rpcAccts := signer.RPC.ListAccounts(
|
||||
&walletrpc.ListAccountsRequest{},
|
||||
)
|
||||
|
||||
watchOnlyAccounts, err = walletrpc.AccountsToWatchOnly(
|
||||
rpcAccts.Accounts,
|
||||
)
|
||||
require.NoError(ht, err)
|
||||
}
|
||||
|
||||
subTests := []testCase{{
|
||||
var commitArgs []string
|
||||
if tc.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
commitArgs = lntest.NodeArgsForCommitType(
|
||||
tc.commitType,
|
||||
)
|
||||
}
|
||||
|
||||
// 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 := ht.NewNodeRemoteSigner(
|
||||
"WatchOnly", append([]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,
|
||||
),
|
||||
}, commitArgs...),
|
||||
password, &lnrpc.WatchOnly{
|
||||
MasterKeyBirthdayTimestamp: 0,
|
||||
MasterKeyFingerprint: nil,
|
||||
Accounts: watchOnlyAccounts,
|
||||
},
|
||||
)
|
||||
|
||||
resp := watchOnly.RPC.GetInfo()
|
||||
require.Equal(ht, signerNodePubKey, resp.IdentityPubkey)
|
||||
|
||||
if tc.sendCoins {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, watchOnly)
|
||||
ht.AssertWalletAccountBalance(
|
||||
watchOnly, "default",
|
||||
btcutil.SatoshiPerBitcoin, 0,
|
||||
)
|
||||
}
|
||||
|
||||
carol := ht.NewNode("carol", commitArgs)
|
||||
ht.EnsureConnected(watchOnly, carol)
|
||||
|
||||
return signer, watchOnly, carol
|
||||
}
|
||||
|
||||
// testRemoteSignerRadomSeed tests that a watch-only wallet can use a remote
|
||||
// signing wallet to perform any signing or ECDH operations.
|
||||
func testRemoteSignerRadomSeed(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "random seed",
|
||||
randomSeed: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
// Nothing more to test here.
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerAccountImport(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "account import",
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runWalletImportAccountScenario(
|
||||
@ -79,25 +222,53 @@ func testRemoteSigner(ht *lntest.HarnessTest) {
|
||||
carol, wo,
|
||||
)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerChannelOpen(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "basic channel open close",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runBasicChannelCreationAndUpdates(tt, wo, carol)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerChannelFundingInputTypes(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "channel funding input types",
|
||||
sendCoins: false,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runChannelFundingInputTypes(tt, carol, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerAsyncPayments(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "async payments",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runAsyncPayments(tt, wo, carol, nil)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerAsyncPaymentsTaproot(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "async payments taproot",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
@ -108,22 +279,43 @@ func testRemoteSigner(ht *lntest.HarnessTest) {
|
||||
)
|
||||
},
|
||||
commitType: lnrpc.CommitmentType_SIMPLE_TAPROOT,
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerSharedKey(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "shared key",
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runDeriveSharedKey(tt, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerBumpFee(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "bumpfee",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runBumpFee(tt, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerPSBT(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "psbt",
|
||||
randomSeed: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runPsbtChanFunding(
|
||||
runPsbtChanFundingWithNodes(
|
||||
tt, carol, wo, false,
|
||||
lnrpc.CommitmentType_LEGACY,
|
||||
)
|
||||
@ -137,19 +329,40 @@ func testRemoteSigner(ht *lntest.HarnessTest) {
|
||||
// sure we can fund and then sign PSBTs from our wallet.
|
||||
runFundAndSignPsbt(ht, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerSignOutputRaw(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "sign output raw",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runSignOutputRaw(tt, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerSignVerifyMsg(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "sign verify msg",
|
||||
sendCoins: true,
|
||||
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
|
||||
runSignVerifyMessage(tt, wo)
|
||||
},
|
||||
}, {
|
||||
}
|
||||
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
func testRemoteSignerTaproot(ht *lntest.HarnessTest) {
|
||||
tc := remoteSignerTestCase{
|
||||
name: "taproot",
|
||||
sendCoins: true,
|
||||
randomSeed: true,
|
||||
@ -175,107 +388,10 @@ func testRemoteSigner(ht *lntest.HarnessTest) {
|
||||
)
|
||||
}
|
||||
},
|
||||
}}
|
||||
|
||||
prepareTest := func(st *lntest.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)
|
||||
}
|
||||
|
||||
var commitArgs []string
|
||||
if subTest.commitType == lnrpc.CommitmentType_SIMPLE_TAPROOT {
|
||||
commitArgs = lntest.NodeArgsForCommitType(
|
||||
subTest.commitType,
|
||||
)
|
||||
}
|
||||
|
||||
// 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", append([]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,
|
||||
),
|
||||
}, commitArgs...),
|
||||
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", commitArgs)
|
||||
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
|
||||
}
|
||||
}
|
||||
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
|
||||
tc.fn(ht, watchOnly, carol)
|
||||
}
|
||||
|
||||
// deriveCustomScopeAccounts derives the first 255 default accounts of the custom lnd
|
||||
|
@ -17,7 +17,8 @@ func testResHandoff(ht *lntest.HarnessTest) {
|
||||
paymentAmt = 50000
|
||||
)
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// First we'll create a channel between Alice and Bob.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
@ -93,6 +94,4 @@ func testResHandoff(ht *lntest.HarnessTest) {
|
||||
|
||||
// Assert that Alice's payment failed.
|
||||
ht.AssertFirstHTLCError(alice, lnrpc.Failure_PERMANENT_CHANNEL_FAILURE)
|
||||
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
}
|
||||
|
@ -212,13 +212,13 @@ func testRestAPI(ht *lntest.HarnessTest) {
|
||||
// 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=\"*\"",
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
args := []string{
|
||||
"--restcors=\"*\"",
|
||||
fmt.Sprintf("--ws-ping-interval=%s", pingInterval),
|
||||
fmt.Sprintf("--ws-pong-wait=%s", pongWait),
|
||||
)
|
||||
ht.RestartNode(alice)
|
||||
}
|
||||
alice := ht.NewNodeWithCoins("Alice", args)
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
@ -237,6 +237,8 @@ func testRestAPI(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
func wsTestCaseSubscription(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
// Find out the current best block so we can subscribe to the next one.
|
||||
hash, height := ht.GetBestBlock()
|
||||
|
||||
@ -246,7 +248,7 @@ func wsTestCaseSubscription(ht *lntest.HarnessTest) {
|
||||
Height: uint32(height),
|
||||
}
|
||||
url := "/v2/chainnotifier/register/blocks"
|
||||
c, err := openWebSocket(ht.Alice, url, "POST", req, nil)
|
||||
c, err := openWebSocket(alice, url, "POST", req, nil)
|
||||
require.NoError(ht, err, "websocket")
|
||||
defer func() {
|
||||
err := c.WriteMessage(websocket.CloseMessage, closeMsg)
|
||||
@ -326,7 +328,7 @@ func wsTestCaseSubscriptionMacaroon(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
mac, err := alice.ReadMacaroon(
|
||||
alice.Cfg.AdminMacPath, defaultTimeout,
|
||||
)
|
||||
@ -411,7 +413,7 @@ func wsTestCaseBiDirectionalSubscription(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
mac, err := alice.ReadMacaroon(
|
||||
alice.Cfg.AdminMacPath, defaultTimeout,
|
||||
)
|
||||
@ -438,7 +440,6 @@ func wsTestCaseBiDirectionalSubscription(ht *lntest.HarnessTest) {
|
||||
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.
|
||||
@ -504,6 +505,7 @@ func wsTestCaseBiDirectionalSubscription(ht *lntest.HarnessTest) {
|
||||
}
|
||||
return
|
||||
}
|
||||
ht.Logf("Finish writing message %s", resMsg)
|
||||
|
||||
// Also send the message on our message channel to make
|
||||
// sure we count it as successful.
|
||||
@ -520,27 +522,30 @@ func wsTestCaseBiDirectionalSubscription(ht *lntest.HarnessTest) {
|
||||
|
||||
// Before we start opening channels, make sure the two nodes are
|
||||
// connected.
|
||||
bob := ht.Bob
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
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, lntest.OpenChannelParams{Amt: 500000},
|
||||
)
|
||||
defer ht.CloseChannel(bob, chanPoint)
|
||||
|
||||
assertMsgReceived := func() {
|
||||
select {
|
||||
case <-msgChan:
|
||||
case err := <-errChan:
|
||||
ht.Fatalf("Received error from WS: %v", err)
|
||||
|
||||
case <-timeout:
|
||||
case <-time.After(defaultTimeout):
|
||||
ht.Fatalf("Timeout before message was received")
|
||||
}
|
||||
}
|
||||
|
||||
// Open 3 channels to make sure multiple requests and responses can be
|
||||
// sent over the web socket.
|
||||
ht.OpenChannel(bob, alice, lntest.OpenChannelParams{Amt: 500000})
|
||||
assertMsgReceived()
|
||||
|
||||
ht.OpenChannel(bob, alice, lntest.OpenChannelParams{Amt: 500000})
|
||||
assertMsgReceived()
|
||||
|
||||
ht.OpenChannel(bob, alice, lntest.OpenChannelParams{Amt: 500000})
|
||||
assertMsgReceived()
|
||||
}
|
||||
|
||||
func wsTestPingPongTimeout(ht *lntest.HarnessTest) {
|
||||
@ -552,7 +557,7 @@ func wsTestPingPongTimeout(ht *lntest.HarnessTest) {
|
||||
// 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
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
mac, err := alice.ReadMacaroon(
|
||||
alice.Cfg.AdminMacPath, defaultTimeout,
|
||||
)
|
||||
|
@ -612,7 +612,7 @@ func revokedCloseRetributionRemoteHodlCase(ht *lntest.HarnessTest,
|
||||
// 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)
|
||||
ht.MineEmptyBlocks(1)
|
||||
err = wait.NoError(func() error {
|
||||
txid, err := findJusticeTx()
|
||||
if err != nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
@ -19,6 +20,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// toLocalCSV is the CSV delay for the node's to_local output. We use a small
|
||||
// value to save us from mining blocks.
|
||||
var toLocalCSV = 2
|
||||
|
||||
// testQueryBlindedRoutes tests querying routes to blinded routes. To do this,
|
||||
// it sets up a nework of Alice - Bob - Carol and creates a mock blinded route
|
||||
// that uses Carol as the introduction node (plus dummy hops to cover multiple
|
||||
@ -26,11 +31,9 @@ import (
|
||||
// expected. It also includes the edge case of a single-hop blinded route,
|
||||
// which indicates that the introduction node is the recipient.
|
||||
func testQueryBlindedRoutes(ht *lntest.HarnessTest) {
|
||||
var (
|
||||
// Convenience aliases.
|
||||
alice = ht.Alice
|
||||
bob = ht.Bob
|
||||
)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Setup a two hop channel network: Alice -- Bob -- Carol.
|
||||
// We set our proportional fee for these channels to zero, so that
|
||||
@ -311,13 +314,12 @@ func testQueryBlindedRoutes(ht *lntest.HarnessTest) {
|
||||
require.Len(ht, resp.Routes, 1)
|
||||
require.Len(ht, resp.Routes[0].Hops, 2)
|
||||
require.Equal(ht, resp.Routes[0].TotalTimeLock, sendToIntroTimelock)
|
||||
|
||||
ht.CloseChannel(alice, chanPointAliceBob)
|
||||
ht.CloseChannel(bob, chanPointBobCarol)
|
||||
}
|
||||
|
||||
type blindedForwardTest struct {
|
||||
ht *lntest.HarnessTest
|
||||
alice *node.HarnessNode
|
||||
bob *node.HarnessNode
|
||||
carol *node.HarnessNode
|
||||
dave *node.HarnessNode
|
||||
channels []*lnrpc.ChannelPoint
|
||||
@ -349,11 +351,28 @@ func newBlindedForwardTest(ht *lntest.HarnessTest) (context.Context,
|
||||
func (b *blindedForwardTest) setupNetwork(ctx context.Context,
|
||||
withInterceptor bool) {
|
||||
|
||||
carolArgs := []string{"--bitcoin.timelockdelta=18"}
|
||||
carolArgs := []string{
|
||||
"--bitcoin.timelockdelta=18",
|
||||
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
|
||||
}
|
||||
if withInterceptor {
|
||||
carolArgs = append(carolArgs, "--requireinterceptor")
|
||||
}
|
||||
b.carol = b.ht.NewNode("Carol", carolArgs)
|
||||
|
||||
daveArgs := []string{
|
||||
"--bitcoin.timelockdelta=18",
|
||||
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", toLocalCSV),
|
||||
}
|
||||
cfgs := [][]string{nil, nil, carolArgs, daveArgs}
|
||||
param := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
}
|
||||
|
||||
// Creates a network with the following topology and liquidity:
|
||||
// Alice (100k)----- Bob (100k) ----- Carol (100k) ----- Dave
|
||||
chanPoints, nodes := b.ht.CreateSimpleNetwork(cfgs, param)
|
||||
b.channels = chanPoints
|
||||
b.alice, b.bob, b.carol, b.dave = nodes[0], nodes[1], nodes[2], nodes[3]
|
||||
|
||||
if withInterceptor {
|
||||
var err error
|
||||
@ -362,10 +381,6 @@ func (b *blindedForwardTest) setupNetwork(ctx context.Context,
|
||||
)
|
||||
require.NoError(b.ht, err, "interceptor")
|
||||
}
|
||||
|
||||
b.dave = b.ht.NewNode("Dave", []string{"--bitcoin.timelockdelta=18"})
|
||||
|
||||
b.channels = setupFourHopNetwork(b.ht, b.carol, b.dave)
|
||||
}
|
||||
|
||||
// buildBlindedPath returns a blinded route from Bob -> Carol -> Dave, with Bob
|
||||
@ -395,7 +410,7 @@ func (b *blindedForwardTest) buildBlindedPath() *lnrpc.BlindedPaymentPath {
|
||||
require.Len(b.ht, payReq.BlindedPaths, 1)
|
||||
path := payReq.BlindedPaths[0].BlindedPath
|
||||
require.Len(b.ht, path.BlindedHops, 3)
|
||||
require.EqualValues(b.ht, path.IntroductionNode, b.ht.Bob.PubKey[:])
|
||||
require.EqualValues(b.ht, path.IntroductionNode, b.bob.PubKey[:])
|
||||
|
||||
return payReq.BlindedPaths[0]
|
||||
}
|
||||
@ -403,10 +418,6 @@ func (b *blindedForwardTest) buildBlindedPath() *lnrpc.BlindedPaymentPath {
|
||||
// cleanup tears down all channels created by the test and cancels the top
|
||||
// level context used in the test.
|
||||
func (b *blindedForwardTest) cleanup() {
|
||||
b.ht.CloseChannel(b.ht.Alice, b.channels[0])
|
||||
b.ht.CloseChannel(b.ht.Bob, b.channels[1])
|
||||
b.ht.CloseChannel(b.carol, b.channels[2])
|
||||
|
||||
b.cancel()
|
||||
}
|
||||
|
||||
@ -431,7 +442,7 @@ func (b *blindedForwardTest) createRouteToBlinded(paymentAmt int64,
|
||||
},
|
||||
}
|
||||
|
||||
resp := b.ht.Alice.RPC.QueryRoutes(req)
|
||||
resp := b.alice.RPC.QueryRoutes(req)
|
||||
require.Greater(b.ht, len(resp.Routes), 0, "no routes")
|
||||
require.Len(b.ht, resp.Routes[0].Hops, 3, "unexpected route length")
|
||||
|
||||
@ -452,7 +463,7 @@ func (b *blindedForwardTest) sendBlindedPayment(ctx context.Context,
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Hour)
|
||||
go func() {
|
||||
_, err := b.ht.Alice.RPC.Router.SendToRouteV2(ctx, sendReq)
|
||||
_, err := b.alice.RPC.Router.SendToRouteV2(ctx, sendReq)
|
||||
|
||||
// We may get a context canceled error when the test is
|
||||
// finished.
|
||||
@ -481,7 +492,7 @@ func (b *blindedForwardTest) sendToRoute(route *lnrpc.Route,
|
||||
|
||||
// Let Alice send to the blinded payment path and assert that it
|
||||
// succeeds/fails.
|
||||
htlcAttempt := b.ht.Alice.RPC.SendToRouteV2(sendReq)
|
||||
htlcAttempt := b.alice.RPC.SendToRouteV2(sendReq)
|
||||
if assertSuccess {
|
||||
require.Nil(b.ht, htlcAttempt.Failure)
|
||||
require.Equal(b.ht, htlcAttempt.Status,
|
||||
@ -498,7 +509,7 @@ func (b *blindedForwardTest) sendToRoute(route *lnrpc.Route,
|
||||
require.NoError(b.ht, err)
|
||||
|
||||
pmt := b.ht.AssertPaymentStatus(
|
||||
b.ht.Alice, preimage, lnrpc.Payment_FAILED,
|
||||
b.alice, preimage, lnrpc.Payment_FAILED,
|
||||
)
|
||||
require.Len(b.ht, pmt.Htlcs, 1)
|
||||
|
||||
@ -520,7 +531,7 @@ func (b *blindedForwardTest) drainCarolLiquidity(incoming bool) {
|
||||
receivingNode := b.dave
|
||||
|
||||
if incoming {
|
||||
sendingNode = b.ht.Bob
|
||||
sendingNode = b.bob
|
||||
receivingNode = b.carol
|
||||
}
|
||||
|
||||
@ -548,62 +559,6 @@ func (b *blindedForwardTest) drainCarolLiquidity(incoming bool) {
|
||||
b.ht.AssertPaymentStatusFromStream(pmtClient, lnrpc.Payment_SUCCEEDED)
|
||||
}
|
||||
|
||||
// setupFourHopNetwork creates a network with the following topology and
|
||||
// liquidity:
|
||||
// Alice (100k)----- Bob (100k) ----- Carol (100k) ----- Dave
|
||||
//
|
||||
// The funding outpoint for AB / BC / CD are returned in-order.
|
||||
func setupFourHopNetwork(ht *lntest.HarnessTest,
|
||||
carol, dave *node.HarnessNode) []*lnrpc.ChannelPoint {
|
||||
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
var networkChans []*lnrpc.ChannelPoint
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
ht.Alice, ht.Bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
networkChans = append(networkChans, chanPointAlice)
|
||||
|
||||
// Create a channel between bob and carol.
|
||||
ht.EnsureConnected(ht.Bob, carol)
|
||||
chanPointBob := ht.OpenChannel(
|
||||
ht.Bob, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
networkChans = append(networkChans, chanPointBob)
|
||||
|
||||
// Fund carol and connect her and dave so that she can create a channel
|
||||
// between them.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
ht.EnsureConnected(carol, dave)
|
||||
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
carol, dave, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
},
|
||||
)
|
||||
networkChans = append(networkChans, chanPointCarol)
|
||||
|
||||
// Wait for all nodes to have seen all channels.
|
||||
nodes := []*node.HarnessNode{ht.Alice, ht.Bob, carol, dave}
|
||||
for _, chanPoint := range networkChans {
|
||||
for _, node := range nodes {
|
||||
ht.AssertChannelInGraph(node, chanPoint)
|
||||
}
|
||||
}
|
||||
|
||||
return []*lnrpc.ChannelPoint{
|
||||
chanPointAlice,
|
||||
chanPointBob,
|
||||
chanPointCarol,
|
||||
}
|
||||
}
|
||||
|
||||
// testBlindedRouteInvoices tests lnd's ability to create a blinded payment path
|
||||
// which it then inserts into an invoice, sending to an invoice with a blinded
|
||||
// path and forward payments in a blinded route and finally, receiving the
|
||||
@ -616,6 +571,8 @@ func testBlindedRouteInvoices(ht *lntest.HarnessTest) {
|
||||
// blinded path that uses Bob as an introduction node.
|
||||
testCase.setupNetwork(ctx, false)
|
||||
|
||||
alice := testCase.alice
|
||||
|
||||
// Let Dave add a blinded invoice.
|
||||
// Add restrictions so that he only ever creates a single blinded path
|
||||
// from Bob to himself.
|
||||
@ -634,7 +591,7 @@ func testBlindedRouteInvoices(ht *lntest.HarnessTest) {
|
||||
})
|
||||
|
||||
// Now let Alice pay the invoice.
|
||||
ht.CompletePaymentRequests(ht.Alice, []string{invoice.PaymentRequest})
|
||||
ht.CompletePaymentRequests(alice, []string{invoice.PaymentRequest})
|
||||
|
||||
// Let Dave add a blinded invoice.
|
||||
// Once again let Dave create a blinded invoice.
|
||||
@ -661,7 +618,7 @@ func testBlindedRouteInvoices(ht *lntest.HarnessTest) {
|
||||
require.EqualValues(ht, path.IntroductionNode, testCase.dave.PubKey[:])
|
||||
|
||||
// Now let Alice pay the invoice.
|
||||
ht.CompletePaymentRequests(ht.Alice, []string{invoice.PaymentRequest})
|
||||
ht.CompletePaymentRequests(alice, []string{invoice.PaymentRequest})
|
||||
}
|
||||
|
||||
// testReceiverBlindedError tests handling of errors from the receiving node in
|
||||
@ -739,6 +696,8 @@ func testIntroductionNodeError(ht *lntest.HarnessTest) {
|
||||
blindedPaymentPath := testCase.buildBlindedPath()
|
||||
route := testCase.createRouteToBlinded(10_000_000, blindedPaymentPath)
|
||||
|
||||
bob := testCase.bob
|
||||
|
||||
// Before we send our payment, drain all of Carol's incoming liquidity
|
||||
// so that she can't receive the forward from Bob, causing a failure
|
||||
// at the introduction node.
|
||||
@ -746,7 +705,7 @@ func testIntroductionNodeError(ht *lntest.HarnessTest) {
|
||||
|
||||
// Subscribe to Bob's HTLC events so that we can observe the payment
|
||||
// coming in.
|
||||
bobEvents := ht.Bob.RPC.SubscribeHtlcEvents()
|
||||
bobEvents := bob.RPC.SubscribeHtlcEvents()
|
||||
|
||||
// Once subscribed, the first event will be UNKNOWN.
|
||||
ht.AssertHtlcEventType(bobEvents, routerrpc.HtlcEvent_UNKNOWN)
|
||||
@ -773,11 +732,13 @@ func testDisableIntroductionNode(ht *lntest.HarnessTest) {
|
||||
blindedPaymentPath := testCase.buildBlindedPath()
|
||||
route := testCase.createRouteToBlinded(10_000_000, blindedPaymentPath)
|
||||
|
||||
alice, bob := testCase.alice, testCase.bob
|
||||
|
||||
// Now, disable route blinding for Bob, then re-connect to Alice.
|
||||
ht.RestartNodeWithExtraArgs(ht.Bob, []string{
|
||||
ht.RestartNodeWithExtraArgs(bob, []string{
|
||||
"--protocol.no-route-blinding",
|
||||
})
|
||||
ht.EnsureConnected(ht.Alice, ht.Bob)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Assert that this fails.
|
||||
testCase.sendToRoute(route, false)
|
||||
@ -801,14 +762,16 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
|
||||
50_000_000, blindedPaymentPath,
|
||||
)
|
||||
|
||||
alice, bob := testCase.alice, testCase.bob
|
||||
|
||||
// Once our interceptor is set up, we can send the blinded payment.
|
||||
cancelPmt := testCase.sendBlindedPayment(ctx, blindedRoute)
|
||||
defer cancelPmt()
|
||||
|
||||
// Wait for the HTLC to be active on Alice and Bob's channels.
|
||||
hash := sha256.Sum256(testCase.preimage[:])
|
||||
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(alice, testCase.channels[0], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(bob, testCase.channels[1], hash[:])
|
||||
|
||||
// Intercept the forward on Carol's link, but do not take any action
|
||||
// so that we have the chance to force close with this HTLC in flight.
|
||||
@ -817,46 +780,47 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
|
||||
|
||||
// Force close Bob <-> Carol.
|
||||
closeStream, _ := ht.CloseChannelAssertPending(
|
||||
ht.Bob, testCase.channels[1], true,
|
||||
bob, testCase.channels[1], true,
|
||||
)
|
||||
|
||||
ht.AssertStreamChannelForceClosed(
|
||||
ht.Bob, testCase.channels[1], false, closeStream,
|
||||
bob, testCase.channels[1], false, closeStream,
|
||||
)
|
||||
|
||||
// SuspendCarol so that she can't interfere with the resolution of the
|
||||
// HTLC from now on.
|
||||
restartCarol := ht.SuspendNode(testCase.carol)
|
||||
ht.SuspendNode(testCase.carol)
|
||||
|
||||
// Mine blocks so that Bob will claim his CSV delayed local commitment,
|
||||
// we've already mined 1 block so we need one less than our CSV.
|
||||
ht.MineBlocks(node.DefaultCSV - 1)
|
||||
ht.AssertNumPendingSweeps(ht.Bob, 1)
|
||||
ht.MineBlocks(toLocalCSV - 1)
|
||||
ht.AssertNumPendingSweeps(bob, 1)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Restart bob so that we can test that he's able to recover everything
|
||||
// he needs to claim a blinded HTLC.
|
||||
ht.RestartNode(ht.Bob)
|
||||
ht.RestartNode(bob)
|
||||
|
||||
// Mine enough blocks for Bob to trigger timeout of his outgoing HTLC.
|
||||
// Carol's incoming expiry height is Bob's outgoing so we can use this
|
||||
// value.
|
||||
info := ht.Bob.RPC.GetInfo()
|
||||
info := bob.RPC.GetInfo()
|
||||
target := carolHTLC.IncomingExpiry - info.BlockHeight
|
||||
ht.Log(carolHTLC.IncomingExpiry, info.BlockHeight, target)
|
||||
ht.MineBlocks(int(target))
|
||||
|
||||
// Wait for Bob's timeout transaction in the mempool, since we've
|
||||
// suspended Carol we don't need to account for her commitment output
|
||||
// claim.
|
||||
ht.AssertNumPendingSweeps(ht.Bob, 0)
|
||||
ht.AssertNumPendingSweeps(bob, 0)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Assert that the HTLC has cleared.
|
||||
ht.AssertHTLCNotActive(ht.Bob, testCase.channels[0], hash[:])
|
||||
ht.AssertHTLCNotActive(ht.Alice, testCase.channels[0], hash[:])
|
||||
ht.AssertHTLCNotActive(bob, testCase.channels[0], hash[:])
|
||||
ht.AssertHTLCNotActive(alice, testCase.channels[0], hash[:])
|
||||
|
||||
// Wait for the HTLC to reflect as failed for Alice.
|
||||
paymentStream := ht.Alice.RPC.TrackPaymentV2(hash[:])
|
||||
paymentStream := alice.RPC.TrackPaymentV2(hash[:])
|
||||
htlcs := ht.ReceiveTrackPayment(paymentStream).Htlcs
|
||||
require.Len(ht, htlcs, 1)
|
||||
require.NotNil(ht, htlcs[0].Failure)
|
||||
@ -865,28 +829,9 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
|
||||
lnrpc.Failure_INVALID_ONION_BLINDING,
|
||||
)
|
||||
|
||||
// Clean up the rest of our force close: mine blocks so that Bob's CSV
|
||||
// expires to trigger his sweep and then mine it.
|
||||
ht.MineBlocks(node.DefaultCSV)
|
||||
ht.AssertNumPendingSweeps(ht.Bob, 1)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Bring carol back up so that we can close out the rest of our
|
||||
// channels cooperatively. She requires an interceptor to start up
|
||||
// so we just re-register our interceptor.
|
||||
require.NoError(ht, restartCarol())
|
||||
_, err = testCase.carol.RPC.Router.HtlcInterceptor(ctx)
|
||||
require.NoError(ht, err, "interceptor")
|
||||
|
||||
// Assert that Carol has started up and reconnected to dave so that
|
||||
// we can close out channels cooperatively.
|
||||
ht.EnsureConnected(testCase.carol, testCase.dave)
|
||||
|
||||
// Manually close out the rest of our channels and cancel (don't use
|
||||
// built in cleanup which will try close the already-force-closed
|
||||
// channel).
|
||||
ht.CloseChannel(ht.Alice, testCase.channels[0])
|
||||
ht.CloseChannel(testCase.carol, testCase.channels[2])
|
||||
testCase.cancel()
|
||||
}
|
||||
|
||||
@ -907,8 +852,8 @@ func testErrorHandlingOnChainFailure(ht *lntest.HarnessTest) {
|
||||
func testMPPToSingleBlindedPath(ht *lntest.HarnessTest) {
|
||||
// Create a five-node context consisting of Alice, Bob and three new
|
||||
// nodes.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
dave := ht.NewNode("dave", nil)
|
||||
carol := ht.NewNode("carol", nil)
|
||||
eve := ht.NewNode("eve", nil)
|
||||
@ -922,10 +867,12 @@ func testMPPToSingleBlindedPath(ht *lntest.HarnessTest) {
|
||||
|
||||
// Send coins to the nodes and mine 1 blocks to confirm them.
|
||||
for i := 0; i < 2; i++ {
|
||||
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, alice)
|
||||
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, bob)
|
||||
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, carol)
|
||||
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, dave)
|
||||
ht.FundCoinsUnconfirmed(btcutil.SatoshiPerBitcoin, eve)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 3)
|
||||
ht.MineBlocksAndAssertNumTxes(1, 5)
|
||||
}
|
||||
|
||||
const paymentAmt = btcutil.Amount(300000)
|
||||
@ -986,7 +933,7 @@ func testMPPToSingleBlindedPath(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
// Each node should have exactly numPublic edges.
|
||||
ht.AssertNumActiveEdges(hn, numPublic, false)
|
||||
ht.AssertNumEdges(hn, numPublic, false)
|
||||
}
|
||||
|
||||
// Make Dave create an invoice with a blinded path for Alice to pay.
|
||||
@ -1097,11 +1044,11 @@ func testMPPToSingleBlindedPath(ht *lntest.HarnessTest) {
|
||||
// between him and the introduction node. So we expect that Carol is chosen as
|
||||
// the intro node and that one dummy hops is appended.
|
||||
func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
// Disable route blinding for Bob so that he is never chosen as the
|
||||
// introduction node.
|
||||
ht.RestartNodeWithExtraArgs(bob, []string{
|
||||
bob := ht.NewNodeWithCoins("Bob", []string{
|
||||
"--protocol.no-route-blinding",
|
||||
})
|
||||
|
||||
@ -1157,7 +1104,7 @@ func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
// Each node should have exactly 5 edges.
|
||||
ht.AssertNumActiveEdges(hn, len(channelPoints), false)
|
||||
ht.AssertNumEdges(hn, len(channelPoints), false)
|
||||
}
|
||||
|
||||
// Make Dave create an invoice with a blinded path for Alice to pay.
|
||||
@ -1191,7 +1138,7 @@ func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
|
||||
|
||||
// Now let Alice pay the invoice.
|
||||
ht.CompletePaymentRequests(
|
||||
ht.Alice, []string{invoiceResp.PaymentRequest},
|
||||
alice, []string{invoiceResp.PaymentRequest},
|
||||
)
|
||||
|
||||
// Make sure Dave show the invoice as settled.
|
||||
@ -1233,7 +1180,7 @@ func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
|
||||
|
||||
// Now let Alice pay the invoice.
|
||||
ht.CompletePaymentRequests(
|
||||
ht.Alice, []string{invoiceResp.PaymentRequest},
|
||||
alice, []string{invoiceResp.PaymentRequest},
|
||||
)
|
||||
|
||||
// Make sure Dave show the invoice as settled.
|
||||
@ -1268,7 +1215,8 @@ func testBlindedRouteDummyHops(ht *lntest.HarnessTest) {
|
||||
// \ /
|
||||
// --- Carol ---
|
||||
func testMPPToMultipleBlindedPaths(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// Create a four-node context consisting of Alice, Bob and three new
|
||||
// nodes.
|
||||
@ -1326,7 +1274,7 @@ func testMPPToMultipleBlindedPaths(ht *lntest.HarnessTest) {
|
||||
}
|
||||
|
||||
// Each node should have exactly 5 edges.
|
||||
ht.AssertNumActiveEdges(hn, len(channelPoints), false)
|
||||
ht.AssertNumEdges(hn, len(channelPoints), false)
|
||||
}
|
||||
|
||||
// Ok now make a payment that must be split to succeed.
|
||||
@ -1437,6 +1385,8 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
|
||||
// Set up network with carol interceptor.
|
||||
testCase.setupNetwork(ctx, true)
|
||||
|
||||
alice, bob := testCase.alice, testCase.bob
|
||||
|
||||
// Let dave create invoice.
|
||||
blindedPaymentPath := testCase.buildBlindedPath()
|
||||
route := testCase.createRouteToBlinded(10_000_000, blindedPaymentPath)
|
||||
@ -1453,7 +1403,7 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
|
||||
go func() {
|
||||
defer close(done)
|
||||
|
||||
htlcAttempt, err := testCase.ht.Alice.RPC.Router.SendToRouteV2(
|
||||
htlcAttempt, err := testCase.alice.RPC.Router.SendToRouteV2(
|
||||
ctx, sendReq,
|
||||
)
|
||||
require.NoError(testCase.ht, err)
|
||||
@ -1464,8 +1414,8 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
|
||||
}()
|
||||
|
||||
// Wait for the HTLC to be active on Alice and Bob's channels.
|
||||
ht.AssertOutgoingHTLCActive(ht.Alice, testCase.channels[0], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(ht.Bob, testCase.channels[1], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(alice, testCase.channels[0], hash[:])
|
||||
ht.AssertOutgoingHTLCActive(bob, testCase.channels[1], hash[:])
|
||||
|
||||
// Intercept the forward on Carol's link. At this point, we know she
|
||||
// has received the HTLC and so will persist this packet.
|
||||
@ -1493,7 +1443,7 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
|
||||
|
||||
// Nodes need to be connected otherwise the forwarding of the
|
||||
// intercepted htlc will fail.
|
||||
ht.EnsureConnected(ht.Bob, testCase.carol)
|
||||
ht.EnsureConnected(bob, testCase.carol)
|
||||
ht.EnsureConnected(testCase.carol, testCase.dave)
|
||||
|
||||
// Now that carol and dave are connected signal the forwarding of the
|
||||
|
@ -20,46 +20,33 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type singleHopSendToRouteCase struct {
|
||||
name string
|
||||
|
||||
// streaming tests streaming SendToRoute if true, otherwise tests
|
||||
// synchronous SenToRoute.
|
||||
streaming bool
|
||||
|
||||
// routerrpc submits the request to the routerrpc subserver if true,
|
||||
// otherwise submits to the main rpc server.
|
||||
routerrpc bool
|
||||
}
|
||||
|
||||
var singleHopSendToRouteCases = []singleHopSendToRouteCase{
|
||||
var sendToRouteTestCases = []*lntest.TestCase{
|
||||
{
|
||||
name: "regular main sync",
|
||||
Name: "single hop with sync",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// useStream: false, routerrpc: false.
|
||||
testSingleHopSendToRouteCase(ht, false, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular main stream",
|
||||
streaming: true,
|
||||
Name: "single hop with stream",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// useStream: true, routerrpc: false.
|
||||
testSingleHopSendToRouteCase(ht, true, false)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular routerrpc sync",
|
||||
routerrpc: true,
|
||||
},
|
||||
{
|
||||
name: "mpp main sync",
|
||||
},
|
||||
{
|
||||
name: "mpp main stream",
|
||||
streaming: true,
|
||||
},
|
||||
{
|
||||
name: "mpp routerrpc sync",
|
||||
routerrpc: true,
|
||||
Name: "single hop with v2",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// useStream: false, routerrpc: true.
|
||||
testSingleHopSendToRouteCase(ht, false, true)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// testSingleHopSendToRoute tests that payments are properly processed through a
|
||||
// provided route with a single hop. We'll create the following network
|
||||
// topology:
|
||||
// testSingleHopSendToRouteCase tests that payments are properly processed
|
||||
// through a provided route with a single hop. We'll create the following
|
||||
// network topology:
|
||||
//
|
||||
// Carol --100k--> Dave
|
||||
//
|
||||
@ -67,19 +54,8 @@ var singleHopSendToRouteCases = []singleHopSendToRouteCase{
|
||||
// by feeding the route back into the various SendToRoute RPC methods. Here we
|
||||
// test all three SendToRoute endpoints, forcing each to perform both a regular
|
||||
// payment and an MPP payment.
|
||||
func testSingleHopSendToRoute(ht *lntest.HarnessTest) {
|
||||
for _, test := range singleHopSendToRouteCases {
|
||||
test := test
|
||||
|
||||
ht.Run(test.name, func(t1 *testing.T) {
|
||||
st := ht.Subtest(t1)
|
||||
testSingleHopSendToRouteCase(st, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSingleHopSendToRouteCase(ht *lntest.HarnessTest,
|
||||
test singleHopSendToRouteCase) {
|
||||
useStream, useRPC bool) {
|
||||
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
const paymentAmtSat = 1000
|
||||
@ -101,7 +77,6 @@ func testSingleHopSendToRouteCase(ht *lntest.HarnessTest,
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
carol, dave, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
defer ht.CloseChannel(carol, chanPointCarol)
|
||||
|
||||
// Create invoices for Dave, which expect a payment from Carol.
|
||||
payReqs, rHashes, _ := ht.CreatePayReqs(
|
||||
@ -200,11 +175,11 @@ func testSingleHopSendToRouteCase(ht *lntest.HarnessTest,
|
||||
// synchronously via the routerrpc's SendToRoute, or via the main RPC
|
||||
// server's SendToRoute streaming or sync calls.
|
||||
switch {
|
||||
case !test.routerrpc && test.streaming:
|
||||
case !useRPC && useStream:
|
||||
sendToRouteStream()
|
||||
case !test.routerrpc && !test.streaming:
|
||||
case !useRPC && !useStream:
|
||||
sendToRouteSync()
|
||||
case test.routerrpc && !test.streaming:
|
||||
case useRPC && !useStream:
|
||||
sendToRouteRouterRPC()
|
||||
default:
|
||||
require.Fail(ht, "routerrpc does not support "+
|
||||
@ -317,9 +292,8 @@ func runMultiHopSendToRoute(ht *lntest.HarnessTest, useGraphCache bool) {
|
||||
opts = append(opts, "--db.no-graph-cache")
|
||||
}
|
||||
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
ht.RestartNodeWithExtraArgs(alice, opts)
|
||||
|
||||
alice := ht.NewNodeWithCoins("Alice", opts)
|
||||
bob := ht.NewNodeWithCoins("Bob", opts)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
@ -329,7 +303,6 @@ func runMultiHopSendToRoute(ht *lntest.HarnessTest, useGraphCache bool) {
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
defer ht.CloseChannel(alice, chanPointAlice)
|
||||
|
||||
// Create Carol and establish a channel from Bob. Bob is the sole
|
||||
// funder of the channel with 100k satoshis. The network topology
|
||||
@ -341,7 +314,6 @@ func runMultiHopSendToRoute(ht *lntest.HarnessTest, useGraphCache bool) {
|
||||
chanPointBob := ht.OpenChannel(
|
||||
bob, carol, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
defer ht.CloseChannel(carol, chanPointBob)
|
||||
|
||||
// Make sure Alice knows the channel between Bob and Carol.
|
||||
ht.AssertChannelInGraph(alice, chanPointBob)
|
||||
@ -417,10 +389,11 @@ func testSendToRouteErrorPropagation(ht *lntest.HarnessTest) {
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
ht.OpenChannel(alice, bob, lntest.OpenChannelParams{Amt: chanAmt})
|
||||
|
||||
// Create a new nodes (Carol and Charlie), load her with some funds,
|
||||
// then establish a connection between Carol and Charlie with a channel
|
||||
@ -474,8 +447,6 @@ func testSendToRouteErrorPropagation(ht *lntest.HarnessTest) {
|
||||
require.NoError(ht, err, "payment stream has been closed but fake "+
|
||||
"route has consumed")
|
||||
require.Contains(ht, event.PaymentError, "UnknownNextPeer")
|
||||
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
}
|
||||
|
||||
// testPrivateChannels tests that a private channel can be used for
|
||||
@ -496,7 +467,10 @@ func testPrivateChannels(ht *lntest.HarnessTest) {
|
||||
// where the 100k channel between Carol and Alice is private.
|
||||
|
||||
// Open a channel with 200k satoshis between Alice and Bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNode("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: chanAmt * 2},
|
||||
)
|
||||
@ -591,18 +565,12 @@ func testPrivateChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
// Carol and Alice should know about 4, while Bob and Dave should only
|
||||
// know about 3, since one channel is private.
|
||||
ht.AssertNumActiveEdges(alice, 4, true)
|
||||
ht.AssertNumActiveEdges(alice, 3, false)
|
||||
ht.AssertNumActiveEdges(bob, 3, true)
|
||||
ht.AssertNumActiveEdges(carol, 4, true)
|
||||
ht.AssertNumActiveEdges(carol, 3, false)
|
||||
ht.AssertNumActiveEdges(dave, 3, true)
|
||||
|
||||
// Close all channels.
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
ht.CloseChannel(dave, chanPointDave)
|
||||
ht.CloseChannel(carol, chanPointCarol)
|
||||
ht.CloseChannel(carol, chanPointPrivate)
|
||||
ht.AssertNumEdges(alice, 4, true)
|
||||
ht.AssertNumEdges(alice, 3, false)
|
||||
ht.AssertNumEdges(bob, 3, true)
|
||||
ht.AssertNumEdges(carol, 4, true)
|
||||
ht.AssertNumEdges(carol, 3, false)
|
||||
ht.AssertNumEdges(dave, 3, true)
|
||||
}
|
||||
|
||||
// testInvoiceRoutingHints tests that the routing hints for an invoice are
|
||||
@ -618,7 +586,10 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
// throughout this test. We'll include a push amount since we currently
|
||||
// require channels to have enough remote balance to cover the
|
||||
// invoice's payment.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
chanPointBob := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
@ -633,7 +604,7 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
|
||||
ht.ConnectNodes(alice, carol)
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
alice, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: chanAmt / 2,
|
||||
@ -646,7 +617,7 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
// advertised, otherwise we'd end up leaking information about nodes
|
||||
// that wish to stay unadvertised.
|
||||
ht.ConnectNodes(bob, carol)
|
||||
chanPointBobCarol := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
bob, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: chanAmt / 2,
|
||||
@ -660,7 +631,7 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
dave := ht.NewNode("Dave", nil)
|
||||
|
||||
ht.ConnectNodes(alice, dave)
|
||||
chanPointDave := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
alice, dave, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
Private: true,
|
||||
@ -673,7 +644,7 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
// inactive channels.
|
||||
eve := ht.NewNode("Eve", nil)
|
||||
ht.ConnectNodes(alice, eve)
|
||||
chanPointEve := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
alice, eve, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: chanAmt / 2,
|
||||
@ -734,22 +705,13 @@ func testInvoiceRoutingHints(ht *lntest.HarnessTest) {
|
||||
Private: true,
|
||||
}
|
||||
checkInvoiceHints(invoice)
|
||||
|
||||
// Now that we've confirmed the routing hints were added correctly, we
|
||||
// can close all the channels and shut down all the nodes created.
|
||||
ht.CloseChannel(alice, chanPointBob)
|
||||
ht.CloseChannel(alice, chanPointCarol)
|
||||
ht.CloseChannel(bob, chanPointBobCarol)
|
||||
ht.CloseChannel(alice, chanPointDave)
|
||||
|
||||
// The channel between Alice and Eve should be force closed since Eve
|
||||
// is offline.
|
||||
ht.ForceCloseChannel(alice, chanPointEve)
|
||||
}
|
||||
|
||||
// testScidAliasRoutingHints tests that dynamically created aliases via the RPC
|
||||
// are properly used when routing.
|
||||
func testScidAliasRoutingHints(ht *lntest.HarnessTest) {
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
const chanAmt = btcutil.Amount(800000)
|
||||
|
||||
// Option-scid-alias is opt-in, as is anchors.
|
||||
@ -866,8 +828,8 @@ func testScidAliasRoutingHints(ht *lntest.HarnessTest) {
|
||||
})
|
||||
|
||||
// Connect the existing Bob node with Carol via a public channel.
|
||||
ht.ConnectNodes(ht.Bob, carol)
|
||||
chanPointBC := ht.OpenChannel(ht.Bob, carol, lntest.OpenChannelParams{
|
||||
ht.ConnectNodes(bob, carol)
|
||||
ht.OpenChannel(bob, carol, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
PushAmt: chanAmt / 2,
|
||||
})
|
||||
@ -902,7 +864,7 @@ func testScidAliasRoutingHints(ht *lntest.HarnessTest) {
|
||||
|
||||
// Now Alice will try to pay to that payment request.
|
||||
timeout := time.Second * 15
|
||||
stream := ht.Bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
stream := bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: payReq,
|
||||
TimeoutSeconds: int32(timeout.Seconds()),
|
||||
FeeLimitSat: math.MaxInt64,
|
||||
@ -924,15 +886,12 @@ func testScidAliasRoutingHints(ht *lntest.HarnessTest) {
|
||||
AliasMaps: ephemeralAliasMap,
|
||||
})
|
||||
payReq2 := dave.RPC.AddInvoice(invoice).PaymentRequest
|
||||
stream2 := ht.Bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
stream2 := bob.RPC.SendPayment(&routerrpc.SendPaymentRequest{
|
||||
PaymentRequest: payReq2,
|
||||
TimeoutSeconds: int32(timeout.Seconds()),
|
||||
FeeLimitSat: math.MaxInt64,
|
||||
})
|
||||
ht.AssertPaymentStatusFromStream(stream2, lnrpc.Payment_FAILED)
|
||||
|
||||
ht.CloseChannel(carol, chanPointCD)
|
||||
ht.CloseChannel(ht.Bob, chanPointBC)
|
||||
}
|
||||
|
||||
// testMultiHopOverPrivateChannels tests that private channels can be used as
|
||||
@ -946,7 +905,10 @@ func testMultiHopOverPrivateChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
// First, we'll open a private channel between Alice and Bob with Alice
|
||||
// being the funder.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
chanPointAlice := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
@ -956,7 +918,7 @@ func testMultiHopOverPrivateChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
// Next, we'll create Carol's node and open a public channel between
|
||||
// her and Bob with Bob being the funder.
|
||||
carol := ht.NewNode("Carol", nil)
|
||||
carol := ht.NewNodeWithCoins("Carol", nil)
|
||||
ht.ConnectNodes(bob, carol)
|
||||
chanPointBob := ht.OpenChannel(
|
||||
bob, carol, lntest.OpenChannelParams{
|
||||
@ -971,7 +933,6 @@ func testMultiHopOverPrivateChannels(ht *lntest.HarnessTest) {
|
||||
// him and Carol with Carol being the funder.
|
||||
dave := ht.NewNode("Dave", nil)
|
||||
ht.ConnectNodes(carol, dave)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
|
||||
|
||||
chanPointCarol := ht.OpenChannel(
|
||||
carol, dave, lntest.OpenChannelParams{
|
||||
@ -1030,12 +991,6 @@ func testMultiHopOverPrivateChannels(ht *lntest.HarnessTest) {
|
||||
// Alice should have sent 20k satoshis + fee for two hops to Bob.
|
||||
ht.AssertAmountPaid("Alice(local) [private=>] Bob(remote)", alice,
|
||||
chanPointAlice, paymentAmt+baseFee*2, 0)
|
||||
|
||||
// At this point, the payment was successful. We can now close all the
|
||||
// channels and shutdown the nodes created throughout this test.
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
ht.CloseChannel(bob, chanPointBob)
|
||||
ht.CloseChannel(carol, chanPointCarol)
|
||||
}
|
||||
|
||||
// testQueryRoutes checks the response of queryroutes.
|
||||
@ -1048,7 +1003,9 @@ func testQueryRoutes(ht *lntest.HarnessTest) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
|
||||
// Grab Alice and Bob from the standby nodes.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
// Create Carol and connect her to Bob. We also send her some coins for
|
||||
// channel opening.
|
||||
@ -1071,7 +1028,6 @@ func testQueryRoutes(ht *lntest.HarnessTest) {
|
||||
resp := ht.OpenMultiChannelsAsync(reqs)
|
||||
|
||||
// Extract channel points from the response.
|
||||
chanPointAlice := resp[0]
|
||||
chanPointBob := resp[1]
|
||||
chanPointCarol := resp[2]
|
||||
|
||||
@ -1182,12 +1138,6 @@ func testQueryRoutes(ht *lntest.HarnessTest) {
|
||||
// control import function updates appropriately.
|
||||
testMissionControlCfg(ht.T, alice)
|
||||
testMissionControlImport(ht, alice, bob.PubKey[:], carol.PubKey[:])
|
||||
|
||||
// We clean up the test case by closing channels that were created for
|
||||
// the duration of the tests.
|
||||
ht.CloseChannel(alice, chanPointAlice)
|
||||
ht.CloseChannel(bob, chanPointBob)
|
||||
ht.CloseChannel(carol, chanPointCarol)
|
||||
}
|
||||
|
||||
// testMissionControlCfg tests getting and setting of a node's mission control
|
||||
@ -1351,7 +1301,10 @@ func testRouteFeeCutoff(ht *lntest.HarnessTest) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
|
||||
// Open a channel between Alice and Bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
ht.EnsureConnected(alice, bob)
|
||||
|
||||
chanPointAliceBob := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
@ -1511,13 +1464,6 @@ func testRouteFeeCutoff(ht *lntest.HarnessTest) {
|
||||
},
|
||||
}
|
||||
testFeeCutoff(feeLimitFixed)
|
||||
|
||||
// Once we're done, close the channels and shut down the nodes created
|
||||
// throughout this test.
|
||||
ht.CloseChannel(alice, chanPointAliceBob)
|
||||
ht.CloseChannel(alice, chanPointAliceCarol)
|
||||
ht.CloseChannel(bob, chanPointBobDave)
|
||||
ht.CloseChannel(carol, chanPointCarolDave)
|
||||
}
|
||||
|
||||
// testFeeLimitAfterQueryRoutes tests that a payment's fee limit is consistent
|
||||
@ -1530,7 +1476,7 @@ func testFeeLimitAfterQueryRoutes(ht *lntest.HarnessTest) {
|
||||
cfgs, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
chanPointAliceBob, chanPointBobCarol := chanPoints[0], chanPoints[1]
|
||||
chanPointAliceBob := chanPoints[0]
|
||||
|
||||
// We set an inbound fee discount on Bob's channel to Alice to
|
||||
// effectively set the outbound fees charged to Carol to zero.
|
||||
@ -1589,10 +1535,6 @@ func testFeeLimitAfterQueryRoutes(ht *lntest.HarnessTest) {
|
||||
|
||||
// We assert that a route compatible with the fee limit is available.
|
||||
ht.SendPaymentAssertSettled(alice, sendReq)
|
||||
|
||||
// Once we're done, close the channels.
|
||||
ht.CloseChannel(alice, chanPointAliceBob)
|
||||
ht.CloseChannel(bob, chanPointBobCarol)
|
||||
}
|
||||
|
||||
// computeFee calculates the payment fee as specified in BOLT07.
|
||||
|
@ -1,115 +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/lntest"
|
||||
"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 *lntest.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: 285000,
|
||||
amtAliceDave: 155000,
|
||||
amtCarolBob: 200000,
|
||||
amtCarolEve: 155000,
|
||||
amtDaveBob: 155000,
|
||||
amtEveBob: 155000,
|
||||
}
|
||||
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 := &lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: 500_000,
|
||||
FeeRateMilliMsat: int64(0.001 * 1_000_000),
|
||||
TimeLockDelta: 40,
|
||||
MinHtlc: 1000, // default value
|
||||
MaxHtlcMsat: 133_650_000,
|
||||
}
|
||||
mts.dave.UpdateGlobalPolicy(expectedPolicy)
|
||||
|
||||
// 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()
|
||||
}
|
@ -25,7 +25,9 @@ import (
|
||||
// the node's pubkey and a customized public key to check the validity of the
|
||||
// result.
|
||||
func testDeriveSharedKey(ht *lntest.HarnessTest) {
|
||||
runDeriveSharedKey(ht, ht.Alice)
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
runDeriveSharedKey(ht, alice)
|
||||
}
|
||||
|
||||
// runDeriveSharedKey checks the ECDH performed by the endpoint
|
||||
@ -197,7 +199,9 @@ func runDeriveSharedKey(ht *lntest.HarnessTest, alice *node.HarnessNode) {
|
||||
// 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 *lntest.HarnessTest) {
|
||||
runSignOutputRaw(ht, ht.Alice)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
runSignOutputRaw(ht, alice)
|
||||
}
|
||||
|
||||
// runSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all
|
||||
@ -377,7 +381,9 @@ func assertSignOutputRaw(ht *lntest.HarnessTest,
|
||||
// all custom flags by verifying with VerifyMessage. Tests both ECDSA and
|
||||
// Schnorr signatures.
|
||||
func testSignVerifyMessage(ht *lntest.HarnessTest) {
|
||||
runSignVerifyMessage(ht, ht.Alice)
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
runSignVerifyMessage(ht, alice)
|
||||
}
|
||||
|
||||
// runSignVerifyMessage makes sure that the SignMessage RPC can be used with
|
||||
|
@ -18,10 +18,11 @@ func testSingleHopInvoice(ht *lntest.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, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil}, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
cp := chanPoints[0]
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// assertAmountPaid is a helper closure that asserts the amount paid by
|
||||
// Alice and received by Bob are expected.
|
||||
@ -136,6 +137,4 @@ func testSingleHopInvoice(ht *lntest.HarnessTest) {
|
||||
require.EqualValues(ht, 1, hopHint.FeeBaseMsat, "wrong FeeBaseMsat")
|
||||
require.EqualValues(ht, 20, hopHint.CltvExpiryDelta,
|
||||
"wrong CltvExpiryDelta")
|
||||
|
||||
ht.CloseChannel(alice, cp)
|
||||
}
|
||||
|
@ -113,6 +113,14 @@ func testSweepCPFPAnchorOutgoingTimeout(ht *lntest.HarnessTest) {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
}
|
||||
|
||||
// Bob should have enough wallet UTXOs here to sweep the HTLC in the
|
||||
// end of this test. However, due to a known issue, Bob's wallet may
|
||||
// report there's no UTXO available. For details,
|
||||
// - https://github.com/lightningnetwork/lnd/issues/8786
|
||||
//
|
||||
// TODO(yy): remove this step once the issue is resolved.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
|
||||
// Subscribe the invoice.
|
||||
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||
|
||||
@ -432,6 +440,14 @@ func testSweepCPFPAnchorIncomingTimeout(ht *lntest.HarnessTest) {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
}
|
||||
|
||||
// Bob should have enough wallet UTXOs here to sweep the HTLC in the
|
||||
// end of this test. However, due to a known issue, Bob's wallet may
|
||||
// report there's no UTXO available. For details,
|
||||
// - https://github.com/lightningnetwork/lnd/issues/8786
|
||||
//
|
||||
// TODO(yy): remove this step once the issue is resolved.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
|
||||
// Subscribe the invoice.
|
||||
streamCarol := carol.RPC.SubscribeSingleInvoice(payHash[:])
|
||||
|
||||
@ -763,16 +779,24 @@ func testSweepHTLCs(ht *lntest.HarnessTest) {
|
||||
// Bob needs two more wallet utxos:
|
||||
// - when sweeping anchors, he needs one utxo for each sweep.
|
||||
// - when sweeping HTLCs, he needs one utxo for each sweep.
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
numUTXOs := 2
|
||||
|
||||
// Bob should have enough wallet UTXOs here to sweep the HTLC in the
|
||||
// end of this test. However, due to a known issue, Bob's wallet may
|
||||
// report there's no UTXO available. For details,
|
||||
// - https://github.com/lightningnetwork/lnd/issues/8786
|
||||
//
|
||||
// TODO(yy): remove this extra UTXO once the issue is resolved.
|
||||
numUTXOs++
|
||||
|
||||
// For neutrino backend, we need two more UTXOs for Bob to create his
|
||||
// sweeping txns.
|
||||
if ht.IsNeutrinoBackend() {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, bob)
|
||||
numUTXOs += 2
|
||||
}
|
||||
|
||||
ht.FundNumCoins(bob, numUTXOs)
|
||||
|
||||
// Subscribe the invoices.
|
||||
stream1 := carol.RPC.SubscribeSingleInvoice(payHashSettled[:])
|
||||
stream2 := carol.RPC.SubscribeSingleInvoice(payHashHold[:])
|
||||
@ -1558,7 +1582,9 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
||||
// CPFP, then RBF. Along the way, we check the `BumpFee` can properly update
|
||||
// the fee function used by supplying new params.
|
||||
func testBumpFee(ht *lntest.HarnessTest) {
|
||||
runBumpFee(ht, ht.Alice)
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
runBumpFee(ht, alice)
|
||||
}
|
||||
|
||||
// runBumpFee checks the `BumpFee` RPC can properly bump the fee of a given
|
||||
|
@ -29,7 +29,6 @@ func testSwitchCircuitPersistence(ht *lntest.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)
|
||||
@ -99,13 +98,12 @@ func testSwitchOfflineDelivery(ht *lntest.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)
|
||||
ht.EnsureConnected(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
|
||||
@ -175,7 +173,6 @@ func testSwitchOfflineDeliveryPersistence(ht *lntest.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.
|
||||
@ -264,7 +261,6 @@ func testSwitchOfflineDeliveryOutgoingOffline(ht *lntest.HarnessTest) {
|
||||
// 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)
|
||||
defer s.cleanUp()
|
||||
|
||||
// Disconnect the two intermediaries, Alice and Dave, so that when carol
|
||||
// restarts, the response will be held by Dave.
|
||||
@ -355,8 +351,6 @@ type scenarioFourNodes struct {
|
||||
chanPointAliceBob *lnrpc.ChannelPoint
|
||||
chanPointCarolDave *lnrpc.ChannelPoint
|
||||
chanPointDaveAlice *lnrpc.ChannelPoint
|
||||
|
||||
cleanUp func()
|
||||
}
|
||||
|
||||
// setupScenarioFourNodes creates a topology for switch tests. It will create
|
||||
@ -383,7 +377,9 @@ func setupScenarioFourNodes(ht *lntest.HarnessTest) *scenarioFourNodes {
|
||||
}
|
||||
|
||||
// Grab the standby nodes.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
bob := ht.NewNodeWithCoins("bob", nil)
|
||||
ht.ConnectNodes(alice, 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
|
||||
@ -431,21 +427,9 @@ func setupScenarioFourNodes(ht *lntest.HarnessTest) *scenarioFourNodes {
|
||||
// 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,
|
||||
chanPointCarolDave, chanPointDaveAlice,
|
||||
}
|
||||
|
||||
// Wait until all nodes in the network have 5 outstanding htlcs.
|
||||
|
@ -46,37 +46,50 @@ var (
|
||||
))
|
||||
)
|
||||
|
||||
// testTaproot ensures that the daemon can send to and spend from taproot (p2tr)
|
||||
// outputs.
|
||||
func testTaproot(ht *lntest.HarnessTest) {
|
||||
testTaprootSendCoinsKeySpendBip86(ht, ht.Alice)
|
||||
testTaprootComputeInputScriptKeySpendBip86(ht, ht.Alice)
|
||||
testTaprootSignOutputRawScriptSpend(ht, ht.Alice)
|
||||
// testTaprootSpend ensures that the daemon can send to and spend from taproot
|
||||
// (p2tr) outputs.
|
||||
func testTaprootSpend(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
testTaprootSendCoinsKeySpendBip86(ht, alice)
|
||||
testTaprootComputeInputScriptKeySpendBip86(ht, alice)
|
||||
testTaprootSignOutputRawScriptSpend(ht, alice)
|
||||
testTaprootSignOutputRawScriptSpend(
|
||||
ht, ht.Alice, txscript.SigHashSingle,
|
||||
ht, alice, txscript.SigHashSingle,
|
||||
)
|
||||
testTaprootSignOutputRawKeySpendBip86(ht, ht.Alice)
|
||||
testTaprootSignOutputRawKeySpendBip86(ht, alice)
|
||||
testTaprootSignOutputRawKeySpendBip86(
|
||||
ht, ht.Alice, txscript.SigHashSingle,
|
||||
ht, alice, txscript.SigHashSingle,
|
||||
)
|
||||
testTaprootSignOutputRawKeySpendRootHash(ht, ht.Alice)
|
||||
testTaprootSignOutputRawKeySpendRootHash(ht, alice)
|
||||
}
|
||||
|
||||
// testTaprootMuSig2 ensures that the daemon can send to and spend from taproot
|
||||
// (p2tr) outputs using musig2.
|
||||
func testTaprootMuSig2(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
muSig2Versions := []signrpc.MuSig2Version{
|
||||
signrpc.MuSig2Version_MUSIG2_VERSION_V040,
|
||||
signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
|
||||
}
|
||||
for _, version := range muSig2Versions {
|
||||
testTaprootMuSig2KeySpendBip86(ht, ht.Alice, version)
|
||||
testTaprootMuSig2KeySpendRootHash(ht, ht.Alice, version)
|
||||
testTaprootMuSig2ScriptSpend(ht, ht.Alice, version)
|
||||
testTaprootMuSig2CombinedLeafKeySpend(ht, ht.Alice, version)
|
||||
testMuSig2CombineKey(ht, ht.Alice, version)
|
||||
testTaprootMuSig2KeySpendBip86(ht, alice, version)
|
||||
testTaprootMuSig2KeySpendRootHash(ht, alice, version)
|
||||
testTaprootMuSig2ScriptSpend(ht, alice, version)
|
||||
testTaprootMuSig2CombinedLeafKeySpend(ht, alice, version)
|
||||
testMuSig2CombineKey(ht, alice, version)
|
||||
}
|
||||
}
|
||||
|
||||
testTaprootImportTapscriptFullTree(ht, ht.Alice)
|
||||
testTaprootImportTapscriptPartialReveal(ht, ht.Alice)
|
||||
testTaprootImportTapscriptRootHashOnly(ht, ht.Alice)
|
||||
testTaprootImportTapscriptFullKey(ht, ht.Alice)
|
||||
// testTaprootImportScripts ensures that the daemon can import taproot scripts.
|
||||
func testTaprootImportScripts(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
testTaprootImportTapscriptFullTree(ht, alice)
|
||||
testTaprootImportTapscriptPartialReveal(ht, alice)
|
||||
testTaprootImportTapscriptRootHashOnly(ht, alice)
|
||||
testTaprootImportTapscriptFullKey(ht, alice)
|
||||
}
|
||||
|
||||
// testTaprootSendCoinsKeySpendBip86 tests sending to and spending from
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -20,6 +19,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lntest/port"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/rand"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
@ -61,6 +61,13 @@ var (
|
||||
"0-based index specified by the -runtranche flag",
|
||||
)
|
||||
|
||||
// shuffleSeedFlag is the source of randomness used to shuffle the test
|
||||
// cases. If not specified, the test cases won't be shuffled.
|
||||
shuffleSeedFlag = flag.Uint64(
|
||||
"shuffleseed", 0, "if set, shuffles the test cases using this "+
|
||||
"as the source of randomness",
|
||||
)
|
||||
|
||||
// testCasesRunTranche is the 0-based index of the split test cases
|
||||
// tranche to run in the current invocation.
|
||||
testCasesRunTranche = flag.Uint(
|
||||
@ -102,9 +109,8 @@ func TestLightningNetworkDaemon(t *testing.T) {
|
||||
)
|
||||
defer harnessTest.Stop()
|
||||
|
||||
// Setup standby nodes, Alice and Bob, which will be alive and shared
|
||||
// among all the test cases.
|
||||
harnessTest.SetupStandbyNodes()
|
||||
// Get the current block height.
|
||||
height := harnessTest.CurrentHeight()
|
||||
|
||||
// Run the subset of the test cases selected in this tranche.
|
||||
for idx, testCase := range testCases {
|
||||
@ -119,22 +125,7 @@ func TestLightningNetworkDaemon(t *testing.T) {
|
||||
// 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.SetTestName(testCase.Name)
|
||||
|
||||
ht.RunTestCase(testCase)
|
||||
})
|
||||
@ -151,9 +142,60 @@ func TestLightningNetworkDaemon(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
height := harnessTest.CurrentHeight()
|
||||
t.Logf("=========> tests finished for tranche: %v, tested %d "+
|
||||
"cases, end height: %d\n", trancheIndex, len(testCases), height)
|
||||
//nolint:forbidigo
|
||||
fmt.Printf("=========> tranche %v finished, tested %d cases, mined "+
|
||||
"blocks: %d\n", trancheIndex, len(testCases),
|
||||
harnessTest.CurrentHeight()-height)
|
||||
}
|
||||
|
||||
// maybeShuffleTestCases shuffles the test cases if the flag `shuffleseed` is
|
||||
// set and not 0. In parallel tests we want to shuffle the test cases so they
|
||||
// are executed in a random order. This is done to even out the blocks mined in
|
||||
// each test tranche so they can run faster.
|
||||
//
|
||||
// NOTE: Because the parallel tests are initialized with the same seed (job
|
||||
// ID), they will always have the same order.
|
||||
func maybeShuffleTestCases() {
|
||||
// Exit if not set.
|
||||
if shuffleSeedFlag == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Exit if set to 0.
|
||||
if *shuffleSeedFlag == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Init the seed and shuffle the test cases.
|
||||
rand.Seed(*shuffleSeedFlag)
|
||||
rand.Shuffle(len(allTestCases), func(i, j int) {
|
||||
allTestCases[i], allTestCases[j] =
|
||||
allTestCases[j], allTestCases[i]
|
||||
})
|
||||
}
|
||||
|
||||
// createIndices divides the number of test cases into pairs of indices that
|
||||
// specify the start and end of a tranche.
|
||||
func createIndices(numCases, numTranches uint) [][2]uint {
|
||||
// Calculate base value and remainder.
|
||||
base := numCases / numTranches
|
||||
remainder := numCases % numTranches
|
||||
|
||||
// Generate indices.
|
||||
indices := make([][2]uint, numTranches)
|
||||
start := uint(0)
|
||||
|
||||
for i := uint(0); i < numTranches; i++ {
|
||||
end := start + base
|
||||
if i < remainder {
|
||||
// Add one for the remainder.
|
||||
end++
|
||||
}
|
||||
indices[i] = [2]uint{start, end}
|
||||
start = end
|
||||
}
|
||||
|
||||
return indices
|
||||
}
|
||||
|
||||
// getTestCaseSplitTranche returns the sub slice of the test cases that should
|
||||
@ -178,13 +220,13 @@ func getTestCaseSplitTranche() ([]*lntest.TestCase, uint, uint) {
|
||||
runTranche = 0
|
||||
}
|
||||
|
||||
// Shuffle the test cases if the `shuffleseed` flag is set.
|
||||
maybeShuffleTestCases()
|
||||
|
||||
numCases := uint(len(allTestCases))
|
||||
testsPerTranche := numCases / numTranches
|
||||
trancheOffset := runTranche * testsPerTranche
|
||||
trancheEnd := trancheOffset + testsPerTranche
|
||||
if trancheEnd > numCases || runTranche == numTranches-1 {
|
||||
trancheEnd = numCases
|
||||
}
|
||||
indices := createIndices(numCases, numTranches)
|
||||
index := indices[runTranche]
|
||||
trancheOffset, trancheEnd := index[0], index[1]
|
||||
|
||||
return allTestCases[trancheOffset:trancheEnd], threadID,
|
||||
trancheOffset
|
||||
@ -212,6 +254,16 @@ func getLndBinary(t *testing.T) string {
|
||||
return binary
|
||||
}
|
||||
|
||||
// isDarwin returns true if the test is running on a macOS.
|
||||
func isDarwin() bool {
|
||||
return runtime.GOOS == "darwin"
|
||||
}
|
||||
|
||||
// isWindowsOS returns true if the test is running on a Windows OS.
|
||||
func isWindowsOS() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -2,7 +2,6 @@ package itest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
@ -14,21 +13,18 @@ import (
|
||||
// testTrackPayments tests whether a client that calls the TrackPayments api
|
||||
// receives payment updates.
|
||||
func testTrackPayments(ht *lntest.HarnessTest) {
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
|
||||
// Restart Alice with the new flag so she understands the new payment
|
||||
// Create Alice with the new flag so she understands the new payment
|
||||
// status.
|
||||
ht.RestartNodeWithExtraArgs(alice, []string{
|
||||
"--routerrpc.usestatusinitiated",
|
||||
})
|
||||
cfgAlice := []string{"--routerrpc.usestatusinitiated"}
|
||||
cfgs := [][]string{cfgAlice, nil}
|
||||
|
||||
// Open a channel between alice and bob.
|
||||
ht.EnsureConnected(alice, bob)
|
||||
channel := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
// Create a channel Alice->Bob.
|
||||
_, nodes := ht.CreateSimpleNetwork(
|
||||
cfgs, lntest.OpenChannelParams{
|
||||
Amt: btcutil.Amount(300000),
|
||||
},
|
||||
)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// Call the TrackPayments api to listen for payment updates.
|
||||
req := &routerrpc.TrackPaymentsRequest{
|
||||
@ -88,28 +84,18 @@ func testTrackPayments(ht *lntest.HarnessTest) {
|
||||
require.Equal(ht, amountMsat, update3.ValueMsat)
|
||||
require.Equal(ht, hex.EncodeToString(invoice.RPreimage),
|
||||
update3.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)
|
||||
}
|
||||
|
||||
// testTrackPaymentsCompatible checks that when `routerrpc.usestatusinitiated`
|
||||
// is not set, the new Payment_INITIATED is replaced with Payment_IN_FLIGHT.
|
||||
func testTrackPaymentsCompatible(ht *lntest.HarnessTest) {
|
||||
// Open a channel between alice and bob.
|
||||
alice, bob := ht.Alice, ht.Bob
|
||||
channel := ht.OpenChannel(
|
||||
alice, bob, lntest.OpenChannelParams{
|
||||
_, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil}, lntest.OpenChannelParams{
|
||||
Amt: btcutil.Amount(300000),
|
||||
},
|
||||
)
|
||||
alice, bob := nodes[0], nodes[1]
|
||||
|
||||
// Call the TrackPayments api to listen for payment updates.
|
||||
req := &routerrpc.TrackPaymentsRequest{
|
||||
@ -163,14 +149,4 @@ func testTrackPaymentsCompatible(ht *lntest.HarnessTest) {
|
||||
payment3, err := paymentClient.Recv()
|
||||
require.NoError(ht, err, "unable to get payment update")
|
||||
require.Equal(ht, lnrpc.Payment_SUCCEEDED, payment3.Status)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
@ -23,6 +23,47 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// walletImportAccountTestCases 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.
|
||||
//
|
||||
//nolint:ll
|
||||
var walletImportAccountTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "standard BIP-0049",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
testWalletImportAccountScenario(
|
||||
ht, walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "lnd BIP-0049 variant",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
testWalletImportAccountScenario(
|
||||
ht, walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "standard BIP-0084",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
testWalletImportAccountScenario(
|
||||
ht, walletrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "standard BIP-0086",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
testWalletImportAccountScenario(
|
||||
ht, walletrpc.AddressType_TAPROOT_PUBKEY,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
defaultAccount = lnwallet.DefaultAccountName
|
||||
defaultImportedAccount = waddrmgr.ImportedAddrAccountName
|
||||
@ -452,65 +493,6 @@ func fundChanAndCloseFromImportedAccount(ht *lntest.HarnessTest, srcNode,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 *lntest.HarnessTest) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
addrType walletrpc.AddressType
|
||||
}{
|
||||
{
|
||||
name: "standard BIP-0044",
|
||||
addrType: walletrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
},
|
||||
{
|
||||
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 *lntest.HarnessTest) {
|
||||
testWalletImportAccountScenario(
|
||||
ht, tc.addrType,
|
||||
)
|
||||
}
|
||||
|
||||
st := ht.Subtest(tt)
|
||||
|
||||
st.RunTestCase(&lntest.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 *lntest.HarnessTest,
|
||||
addrType walletrpc.AddressType) {
|
||||
|
||||
@ -582,7 +564,7 @@ func runWalletImportAccountScenario(ht *lntest.HarnessTest,
|
||||
|
||||
// Send coins to Carol's address and confirm them, making sure the
|
||||
// balance updates accordingly.
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
req := &lnrpc.SendCoinsRequest{
|
||||
Addr: externalAddr,
|
||||
Amount: utxoAmt,
|
||||
@ -694,7 +676,7 @@ func testWalletImportPubKeyScenario(ht *lntest.HarnessTest,
|
||||
addrType walletrpc.AddressType) {
|
||||
|
||||
const utxoAmt int64 = btcutil.SatoshiPerBitcoin
|
||||
alice := ht.Alice
|
||||
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||
|
||||
// We'll start our test by having two nodes, Carol and Dave.
|
||||
//
|
||||
|
@ -23,15 +23,15 @@ import (
|
||||
// watchtower client and server.
|
||||
var watchtowerTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "watchtower revoked close retribution altruist",
|
||||
Name: "revoked close retribution altruist",
|
||||
TestFunc: testRevokedCloseRetributionAltruistWatchtower,
|
||||
},
|
||||
{
|
||||
Name: "watchtower client session deletion",
|
||||
Name: "client session deletion",
|
||||
TestFunc: testTowerClientSessionDeletion,
|
||||
},
|
||||
{
|
||||
Name: "watchtower client tower and session management",
|
||||
Name: "client tower and session management",
|
||||
TestFunc: testTowerClientTowerAndSessionManagement,
|
||||
},
|
||||
}
|
||||
@ -39,6 +39,8 @@ var watchtowerTestCases = []*lntest.TestCase{
|
||||
// testTowerClientTowerAndSessionManagement tests the various control commands
|
||||
// that a user has over the client's set of active towers and sessions.
|
||||
func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
const (
|
||||
chanAmt = funding.MaxBtcFundingAmount
|
||||
externalIP = "1.2.3.4"
|
||||
@ -104,13 +106,13 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
|
||||
// Connect Dave and Alice.
|
||||
ht.ConnectNodes(dave, ht.Alice)
|
||||
ht.ConnectNodes(dave, alice)
|
||||
|
||||
// Open a channel between Dave and Alice.
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
}
|
||||
chanPoint := ht.OpenChannel(dave, ht.Alice, params)
|
||||
chanPoint := ht.OpenChannel(dave, alice, params)
|
||||
|
||||
// Show that the Wallis tower is currently seen as an active session
|
||||
// candidate.
|
||||
@ -122,7 +124,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
|
||||
// Make some back-ups and assert that they are added to a session with
|
||||
// the tower.
|
||||
generateBackups(ht, dave, ht.Alice, 4)
|
||||
generateBackups(ht, dave, alice, 4)
|
||||
|
||||
// Assert that one of the sessions now has 4 backups.
|
||||
assertNumBackups(ht, dave.RPC, wallisPk, 4, false)
|
||||
@ -139,7 +141,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
require.False(ht, info.SessionInfo[0].ActiveSessionCandidate)
|
||||
|
||||
// Back up a few more states.
|
||||
generateBackups(ht, dave, ht.Alice, 4)
|
||||
generateBackups(ht, dave, alice, 4)
|
||||
|
||||
// These should _not_ be on the tower. Therefore, the number of
|
||||
// back-ups on the tower should be the same as before.
|
||||
@ -163,7 +165,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
})
|
||||
|
||||
// Generate some more back-ups.
|
||||
generateBackups(ht, dave, ht.Alice, 4)
|
||||
generateBackups(ht, dave, alice, 4)
|
||||
|
||||
// Assert that they get added to the first tower (Wallis) and that the
|
||||
// number of sessions with Wallis has not changed - in other words, the
|
||||
@ -205,7 +207,7 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
assertNumSessions(wallisPk, 4, false)
|
||||
|
||||
// Any new back-ups should now be backed up on a different session.
|
||||
generateBackups(ht, dave, ht.Alice, 2)
|
||||
generateBackups(ht, dave, alice, 2)
|
||||
assertNumBackups(ht, dave.RPC, wallisPk, 10, false)
|
||||
findSession(wallisPk, 2)
|
||||
|
||||
@ -238,6 +240,8 @@ func testTowerClientTowerAndSessionManagement(ht *lntest.HarnessTest) {
|
||||
// testTowerClientSessionDeletion tests that sessions are correctly deleted
|
||||
// when they are deemed closable.
|
||||
func testTowerClientSessionDeletion(ht *lntest.HarnessTest) {
|
||||
alice := ht.NewNode("Alice", nil)
|
||||
|
||||
const (
|
||||
chanAmt = funding.MaxBtcFundingAmount
|
||||
numInvoices = 5
|
||||
@ -290,18 +294,18 @@ func testTowerClientSessionDeletion(ht *lntest.HarnessTest) {
|
||||
ht.FundCoins(btcutil.SatoshiPerBitcoin, dave)
|
||||
|
||||
// Connect Dave and Alice.
|
||||
ht.ConnectNodes(dave, ht.Alice)
|
||||
ht.ConnectNodes(dave, alice)
|
||||
|
||||
// Open a channel between Dave and Alice.
|
||||
params := lntest.OpenChannelParams{
|
||||
Amt: chanAmt,
|
||||
}
|
||||
chanPoint := ht.OpenChannel(dave, ht.Alice, params)
|
||||
chanPoint := ht.OpenChannel(dave, alice, params)
|
||||
|
||||
// Since there are 2 updates made for every payment and the maximum
|
||||
// number of updates per session has been set to 10, make 5 payments
|
||||
// between the pair so that the session is exhausted.
|
||||
generateBackups(ht, dave, ht.Alice, maxUpdates)
|
||||
generateBackups(ht, dave, alice, maxUpdates)
|
||||
|
||||
// Assert that one of the sessions now has 10 backups.
|
||||
assertNumBackups(ht, dave.RPC, wallisPk, 10, false)
|
||||
@ -391,7 +395,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
|
||||
// protection logic automatically.
|
||||
daveArgs := lntest.NodeArgsForCommitType(commitType)
|
||||
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
|
||||
dave := ht.NewNode("Dave", daveArgs)
|
||||
dave := ht.NewNodeWithCoins("Dave", daveArgs)
|
||||
|
||||
addTowerReq := &wtclientrpc.AddTowerRequest{
|
||||
Pubkey: willyInfoPk,
|
||||
@ -403,10 +407,6 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
|
||||
// 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)
|
||||
|
@ -27,25 +27,12 @@ func testWipeForwardingPackages(ht *lntest.HarnessTest) {
|
||||
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, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
|
||||
// Open a channel between Bob and Carol.
|
||||
chanPointBC := ht.OpenChannel(
|
||||
bob, carol, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
chanPoints, nodes := ht.CreateSimpleNetwork(
|
||||
[][]string{nil, nil, nil},
|
||||
lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
chanPointAB, chanPointBC := chanPoints[0], chanPoints[1]
|
||||
alice, bob, carol := nodes[0], nodes[1], nodes[2]
|
||||
|
||||
// Before we continue, make sure Alice has seen the channel between Bob
|
||||
// and Carol.
|
||||
@ -119,7 +106,4 @@ func testWipeForwardingPackages(ht *lntest.HarnessTest) {
|
||||
|
||||
// Mine 1 block to get Alice's sweeping tx confirmed.
|
||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
||||
|
||||
// Clean up the force closed channel.
|
||||
ht.CleanupForceClose(bob)
|
||||
}
|
||||
|
@ -45,8 +45,7 @@ func testWumboChannels(ht *lntest.HarnessTest) {
|
||||
|
||||
// Creating a wumbo channel between these two nodes should succeed.
|
||||
ht.EnsureConnected(wumboNode, wumboNode2)
|
||||
chanPoint := ht.OpenChannel(
|
||||
ht.OpenChannel(
|
||||
wumboNode, wumboNode2, lntest.OpenChannelParams{Amt: chanAmt},
|
||||
)
|
||||
ht.CloseChannel(wumboNode, chanPoint)
|
||||
}
|
||||
|
@ -19,6 +19,67 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// zeroConfPolicyTestCases checks that option-scid-alias, zero-conf
|
||||
// channel-types, and option-scid-alias feature-bit-only channels have the
|
||||
// expected graph and that payments work when updating the channel policy.
|
||||
var zeroConfPolicyTestCases = []*lntest.TestCase{
|
||||
{
|
||||
Name: "channel policy update private",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// zeroConf: false
|
||||
// scidAlias: false
|
||||
// private: true
|
||||
testPrivateUpdateAlias(
|
||||
ht, false, false, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "channel policy update private scid alias",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// zeroConf: false
|
||||
// scidAlias: true
|
||||
// private: true
|
||||
testPrivateUpdateAlias(
|
||||
ht, false, true, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "channel policy update private zero conf",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// zeroConf: true
|
||||
// scidAlias: false
|
||||
// private: true
|
||||
testPrivateUpdateAlias(
|
||||
ht, true, false, true,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "channel policy update public zero conf",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// zeroConf: true
|
||||
// scidAlias: false
|
||||
// private: false
|
||||
testPrivateUpdateAlias(
|
||||
ht, true, false, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "channel policy update public",
|
||||
TestFunc: func(ht *lntest.HarnessTest) {
|
||||
// zeroConf: false
|
||||
// scidAlias: false
|
||||
// private: false
|
||||
testPrivateUpdateAlias(
|
||||
ht, false, false, false,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// testZeroConfChannelOpen tests that opening a zero-conf channel works and
|
||||
// sending payments also works.
|
||||
func testZeroConfChannelOpen(ht *lntest.HarnessTest) {
|
||||
@ -395,61 +456,6 @@ func waitForZeroConfGraphChange(hn *node.HarnessNode,
|
||||
}, defaultTimeout)
|
||||
}
|
||||
|
||||
// testUpdateChannelPolicyScidAlias checks that option-scid-alias, zero-conf
|
||||
// channel-types, and option-scid-alias feature-bit-only channels have the
|
||||
// expected graph and that payments work when updating the channel policy.
|
||||
func testUpdateChannelPolicyScidAlias(ht *lntest.HarnessTest) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
// The option-scid-alias channel type.
|
||||
scidAliasType bool
|
||||
|
||||
// The zero-conf channel type.
|
||||
zeroConf bool
|
||||
|
||||
private bool
|
||||
}{
|
||||
{
|
||||
name: "private scid-alias chantype update",
|
||||
scidAliasType: true,
|
||||
private: true,
|
||||
},
|
||||
{
|
||||
name: "private zero-conf update",
|
||||
zeroConf: true,
|
||||
private: true,
|
||||
},
|
||||
{
|
||||
name: "public zero-conf update",
|
||||
zeroConf: true,
|
||||
},
|
||||
{
|
||||
name: "public no-chan-type update",
|
||||
},
|
||||
{
|
||||
name: "private no-chan-type update",
|
||||
private: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
success := ht.Run(test.name, func(t *testing.T) {
|
||||
st := ht.Subtest(t)
|
||||
|
||||
testPrivateUpdateAlias(
|
||||
st, test.zeroConf, test.scidAliasType,
|
||||
test.private,
|
||||
)
|
||||
})
|
||||
if !success {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testPrivateUpdateAlias(ht *lntest.HarnessTest,
|
||||
zeroConf, scidAliasType, private bool) {
|
||||
|
||||
@ -621,6 +627,9 @@ func testPrivateUpdateAlias(ht *lntest.HarnessTest,
|
||||
//
|
||||
// TODO(yy): further investigate this sleep.
|
||||
time.Sleep(time.Second * 5)
|
||||
|
||||
// Make sure Eve has heard about this public channel.
|
||||
ht.AssertChannelInGraph(eve, fundingPoint2)
|
||||
}
|
||||
|
||||
// Dave creates an invoice that Eve will pay.
|
||||
@ -753,7 +762,7 @@ func testPrivateUpdateAlias(ht *lntest.HarnessTest,
|
||||
// testOptionScidUpgrade tests that toggling the option-scid-alias feature bit
|
||||
// correctly upgrades existing channels.
|
||||
func testOptionScidUpgrade(ht *lntest.HarnessTest) {
|
||||
bob := ht.Bob
|
||||
bob := ht.NewNodeWithCoins("Bob", nil)
|
||||
|
||||
// Start carol with anchors only.
|
||||
carolArgs := []string{
|
||||
@ -854,9 +863,6 @@ func testOptionScidUpgrade(ht *lntest.HarnessTest) {
|
||||
|
||||
daveInvoice2 := dave.RPC.AddInvoice(daveParams)
|
||||
ht.CompletePaymentRequests(bob, []string{daveInvoice2.PaymentRequest})
|
||||
|
||||
// Close standby node's channels.
|
||||
ht.CloseChannel(bob, fundingPoint2)
|
||||
}
|
||||
|
||||
// acceptChannel is used to accept a single channel that comes across. This
|
||||
|
@ -94,6 +94,14 @@ func NewBackend(miner string, netParams *chaincfg.Params) (
|
||||
"--nobanning",
|
||||
// Don't disconnect if a reply takes too long.
|
||||
"--nostalldetect",
|
||||
|
||||
// The default max num of websockets is 25, but the closed
|
||||
// connections are not cleaned up immediately so we double the
|
||||
// size.
|
||||
//
|
||||
// TODO(yy): fix this in `btcd` to clean up the stale
|
||||
// connections.
|
||||
"--rpcmaxwebsockets=50",
|
||||
}
|
||||
chainBackend, err := rpctest.New(
|
||||
netParams, nil, args, node.GetBtcdBinary(),
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -67,24 +68,12 @@ type TestCase struct {
|
||||
TestFunc func(t *HarnessTest)
|
||||
}
|
||||
|
||||
// standbyNodes are a list of nodes which are created during the initialization
|
||||
// of the test and used across all test cases.
|
||||
type standbyNodes struct {
|
||||
// Alice and Bob are the initial seeder nodes that are automatically
|
||||
// created to be the initial participants of the test network.
|
||||
Alice *node.HarnessNode
|
||||
Bob *node.HarnessNode
|
||||
}
|
||||
|
||||
// HarnessTest builds on top of a testing.T with enhanced error detection. It
|
||||
// is responsible for managing the interactions among different nodes, and
|
||||
// providing easy-to-use assertions.
|
||||
type HarnessTest struct {
|
||||
*testing.T
|
||||
|
||||
// Embed the standbyNodes so we can easily access them via `ht.Alice`.
|
||||
standbyNodes
|
||||
|
||||
// miner is a reference to a running full node that can be used to
|
||||
// create new blocks on the network.
|
||||
miner *miner.HarnessMiner
|
||||
@ -271,97 +260,6 @@ func (h *HarnessTest) createAndSendOutput(target *node.HarnessNode,
|
||||
h.miner.SendOutput(output, defaultMinerFeeRate)
|
||||
}
|
||||
|
||||
// SetupRemoteSigningStandbyNodes starts the initial seeder nodes within the
|
||||
// test harness in a remote signing configuration. The initial node's wallets
|
||||
// will be funded wallets with 100x1 BTC outputs each.
|
||||
func (h *HarnessTest) SetupRemoteSigningStandbyNodes() {
|
||||
h.Log("Setting up standby nodes Alice and Bob with remote " +
|
||||
"signing configurations...")
|
||||
defer h.Log("Finished the setup, now running tests...")
|
||||
|
||||
password := []byte("itestpassword")
|
||||
|
||||
// Setup remote signing nodes for Alice and Bob.
|
||||
signerAlice := h.NewNode("SignerAlice", nil)
|
||||
signerBob := h.NewNode("SignerBob", nil)
|
||||
|
||||
// Setup watch-only nodes for Alice and Bob, each configured with their
|
||||
// own remote signing instance.
|
||||
h.Alice = h.setupWatchOnlyNode("Alice", signerAlice, password)
|
||||
h.Bob = h.setupWatchOnlyNode("Bob", signerBob, password)
|
||||
|
||||
// Fund each node with 100 BTC (using 100 separate transactions).
|
||||
const fundAmount = 1 * btcutil.SatoshiPerBitcoin
|
||||
const numOutputs = 100
|
||||
const totalAmount = fundAmount * numOutputs
|
||||
for _, node := range []*node.HarnessNode{h.Alice, h.Bob} {
|
||||
h.manager.standbyNodes[node.Cfg.NodeID] = node
|
||||
for i := 0; i < numOutputs; i++ {
|
||||
h.createAndSendOutput(
|
||||
node, fundAmount,
|
||||
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// We generate several blocks in order to give the outputs created
|
||||
// above a good number of confirmations.
|
||||
const totalTxes = 200
|
||||
h.MineBlocksAndAssertNumTxes(numBlocksSendOutput, totalTxes)
|
||||
|
||||
// Now we want to wait for the nodes to catch up.
|
||||
h.WaitForBlockchainSync(h.Alice)
|
||||
h.WaitForBlockchainSync(h.Bob)
|
||||
|
||||
// Now block until both wallets have fully synced up.
|
||||
h.WaitForBalanceConfirmed(h.Alice, totalAmount)
|
||||
h.WaitForBalanceConfirmed(h.Bob, totalAmount)
|
||||
}
|
||||
|
||||
// SetUp starts the initial seeder nodes within the test harness. The initial
|
||||
// node's wallets will be funded wallets with 10x10 BTC outputs each.
|
||||
func (h *HarnessTest) SetupStandbyNodes() {
|
||||
h.Log("Setting up standby nodes Alice and Bob...")
|
||||
defer h.Log("Finished the setup, now running tests...")
|
||||
|
||||
lndArgs := []string{
|
||||
"--default-remote-max-htlcs=483",
|
||||
"--channel-max-fee-exposure=5000000",
|
||||
}
|
||||
|
||||
// Start the initial seeder nodes within the test network.
|
||||
h.Alice = h.NewNode("Alice", lndArgs)
|
||||
h.Bob = h.NewNode("Bob", lndArgs)
|
||||
|
||||
// Load up the wallets of the seeder nodes with 100 outputs of 1 BTC
|
||||
// each.
|
||||
const fundAmount = 1 * btcutil.SatoshiPerBitcoin
|
||||
const numOutputs = 100
|
||||
const totalAmount = fundAmount * numOutputs
|
||||
for _, node := range []*node.HarnessNode{h.Alice, h.Bob} {
|
||||
h.manager.standbyNodes[node.Cfg.NodeID] = node
|
||||
for i := 0; i < numOutputs; i++ {
|
||||
h.createAndSendOutput(
|
||||
node, fundAmount,
|
||||
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// We generate several blocks in order to give the outputs created
|
||||
// above a good number of confirmations.
|
||||
const totalTxes = 200
|
||||
h.MineBlocksAndAssertNumTxes(numBlocksSendOutput, totalTxes)
|
||||
|
||||
// Now we want to wait for the nodes to catch up.
|
||||
h.WaitForBlockchainSync(h.Alice)
|
||||
h.WaitForBlockchainSync(h.Bob)
|
||||
|
||||
// Now block until both wallets have fully synced up.
|
||||
h.WaitForBalanceConfirmed(h.Alice, totalAmount)
|
||||
h.WaitForBalanceConfirmed(h.Bob, totalAmount)
|
||||
}
|
||||
|
||||
// Stop stops the test harness.
|
||||
func (h *HarnessTest) Stop() {
|
||||
// Do nothing if it's not started.
|
||||
@ -399,24 +297,6 @@ func (h *HarnessTest) RunTestCase(testCase *TestCase) {
|
||||
testCase.TestFunc(h)
|
||||
}
|
||||
|
||||
// resetStandbyNodes resets all standby nodes by attaching the new testing.T
|
||||
// and restarting them with the original config.
|
||||
func (h *HarnessTest) resetStandbyNodes(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
for _, hn := range h.manager.standbyNodes {
|
||||
// Inherit the testing.T.
|
||||
h.T = t
|
||||
|
||||
// Reset the config so the node will be using the default
|
||||
// config for the coming test. This will also inherit the
|
||||
// test's running context.
|
||||
h.RestartNodeWithExtraArgs(hn, hn.Cfg.OriginalExtraArgs)
|
||||
|
||||
hn.AddToLogf("Finished test case %v", h.manager.currentTestCase)
|
||||
}
|
||||
}
|
||||
|
||||
// Subtest creates a child HarnessTest, which inherits the harness net and
|
||||
// stand by nodes created by the parent test. It will return a cleanup function
|
||||
// which resets all the standby nodes' configs back to its original state and
|
||||
@ -428,7 +308,6 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
|
||||
T: t,
|
||||
manager: h.manager,
|
||||
miner: h.miner,
|
||||
standbyNodes: h.standbyNodes,
|
||||
feeService: h.feeService,
|
||||
lndErrorChan: make(chan error, lndErrorChanSize),
|
||||
}
|
||||
@ -439,9 +318,6 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
|
||||
// Inherit the subtest for the miner.
|
||||
st.miner.T = st.T
|
||||
|
||||
// Reset the standby nodes.
|
||||
st.resetStandbyNodes(t)
|
||||
|
||||
// Reset fee estimator.
|
||||
st.feeService.Reset()
|
||||
|
||||
@ -450,16 +326,13 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
|
||||
startHeight := int32(h.CurrentHeight())
|
||||
|
||||
st.Cleanup(func() {
|
||||
_, endHeight := h.GetBestBlock()
|
||||
|
||||
st.Logf("finished test: %s, start height=%d, end height=%d, "+
|
||||
"mined blocks=%d", st.manager.currentTestCase,
|
||||
startHeight, endHeight, endHeight-startHeight)
|
||||
// Make sure the test is not consuming too many blocks.
|
||||
st.checkAndLimitBlocksMined(startHeight)
|
||||
|
||||
// Don't bother run the cleanups if the test is failed.
|
||||
if st.Failed() {
|
||||
st.Log("test failed, skipped cleanup")
|
||||
st.shutdownAllNodes()
|
||||
st.shutdownNodesNoAssert()
|
||||
return
|
||||
}
|
||||
|
||||
@ -471,14 +344,8 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
|
||||
return
|
||||
}
|
||||
|
||||
// When we finish the test, reset the nodes' configs and take a
|
||||
// snapshot of each of the nodes' internal states.
|
||||
for _, node := range st.manager.standbyNodes {
|
||||
st.cleanupStandbyNode(node)
|
||||
}
|
||||
|
||||
// If found running nodes, shut them down.
|
||||
st.shutdownNonStandbyNodes()
|
||||
st.shutdownAllNodes()
|
||||
|
||||
// We require the mempool to be cleaned from the test.
|
||||
require.Empty(st, st.miner.GetRawMempool(), "mempool not "+
|
||||
@ -498,33 +365,57 @@ func (h *HarnessTest) Subtest(t *testing.T) *HarnessTest {
|
||||
return st
|
||||
}
|
||||
|
||||
// shutdownNonStandbyNodes will shutdown any non-standby nodes.
|
||||
func (h *HarnessTest) shutdownNonStandbyNodes() {
|
||||
h.shutdownNodes(true)
|
||||
// checkAndLimitBlocksMined asserts that the blocks mined in a single test
|
||||
// doesn't exceed 50, which implicitly discourage table-drive tests, which are
|
||||
// hard to maintain and take a long time to run.
|
||||
func (h *HarnessTest) checkAndLimitBlocksMined(startHeight int32) {
|
||||
_, endHeight := h.GetBestBlock()
|
||||
blocksMined := endHeight - startHeight
|
||||
|
||||
h.Logf("finished test: %s, start height=%d, end height=%d, mined "+
|
||||
"blocks=%d", h.manager.currentTestCase, startHeight, endHeight,
|
||||
blocksMined)
|
||||
|
||||
// If the number of blocks is less than 40, we consider the test
|
||||
// healthy.
|
||||
if blocksMined < 40 {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise log a warning if it's mining more than 40 blocks.
|
||||
desc := "!============================================!\n"
|
||||
|
||||
desc += fmt.Sprintf("Too many blocks (%v) mined in one test! Tips:\n",
|
||||
blocksMined)
|
||||
|
||||
desc += "1. break test into smaller individual tests, especially if " +
|
||||
"this is a table-drive test.\n" +
|
||||
"2. use smaller CSV via `--bitcoin.defaultremotedelay=1.`\n" +
|
||||
"3. use smaller CLTV via `--bitcoin.timelockdelta=18.`\n" +
|
||||
"4. remove unnecessary CloseChannel when test ends.\n" +
|
||||
"5. use `CreateSimpleNetwork` for efficient channel creation.\n"
|
||||
h.Log(desc)
|
||||
|
||||
// We enforce that the test should not mine more than 50 blocks, which
|
||||
// is more than enough to test a multi hop force close scenario.
|
||||
require.LessOrEqual(h, int(blocksMined), 50, "cannot mine more than "+
|
||||
"50 blocks in one test")
|
||||
}
|
||||
|
||||
// shutdownNodesNoAssert will shutdown all running nodes without assertions.
|
||||
// This is used when the test has already failed, we don't want to log more
|
||||
// errors but focusing on the original error.
|
||||
func (h *HarnessTest) shutdownNodesNoAssert() {
|
||||
for _, node := range h.manager.activeNodes {
|
||||
_ = h.manager.shutdownNode(node)
|
||||
}
|
||||
}
|
||||
|
||||
// shutdownAllNodes will shutdown all running nodes.
|
||||
func (h *HarnessTest) shutdownAllNodes() {
|
||||
h.shutdownNodes(false)
|
||||
}
|
||||
|
||||
// shutdownNodes will shutdown any non-standby nodes. If skipStandby is false,
|
||||
// all the standby nodes will be shutdown too.
|
||||
func (h *HarnessTest) shutdownNodes(skipStandby bool) {
|
||||
for nid, node := range h.manager.activeNodes {
|
||||
// If it's a standby node, skip.
|
||||
_, ok := h.manager.standbyNodes[nid]
|
||||
if ok && skipStandby {
|
||||
continue
|
||||
}
|
||||
|
||||
// The process may not be in a state to always shutdown
|
||||
// immediately, so we'll retry up to a hard limit to ensure we
|
||||
// eventually shutdown.
|
||||
err := wait.NoError(func() error {
|
||||
return h.manager.shutdownNode(node)
|
||||
}, DefaultTimeout)
|
||||
|
||||
var err error
|
||||
for _, node := range h.manager.activeNodes {
|
||||
err = h.manager.shutdownNode(node)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
@ -534,6 +425,8 @@ func (h *HarnessTest) shutdownNodes(skipStandby bool) {
|
||||
// processes.
|
||||
h.Logf("unable to shutdown %s, got err: %v", node.Name(), err)
|
||||
}
|
||||
|
||||
require.NoError(h, err, "failed to shutdown all nodes")
|
||||
}
|
||||
|
||||
// cleanupStandbyNode is a function should be called with defer whenever a
|
||||
@ -566,26 +459,14 @@ func (h *HarnessTest) cleanupStandbyNode(hn *node.HarnessNode) {
|
||||
func (h *HarnessTest) removeConnectionns(hn *node.HarnessNode) {
|
||||
resp := hn.RPC.ListPeers()
|
||||
for _, peer := range resp.Peers {
|
||||
// Skip disconnecting Alice and Bob.
|
||||
switch peer.PubKey {
|
||||
case h.Alice.PubKeyStr:
|
||||
continue
|
||||
case h.Bob.PubKeyStr:
|
||||
continue
|
||||
}
|
||||
|
||||
hn.RPC.DisconnectPeer(peer.PubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// SetTestName set the test case name.
|
||||
func (h *HarnessTest) SetTestName(name string) {
|
||||
h.manager.currentTestCase = name
|
||||
|
||||
// Overwrite the old log filename so we can create new log files.
|
||||
for _, node := range h.manager.standbyNodes {
|
||||
node.Cfg.LogFilenamePrefix = name
|
||||
}
|
||||
cleanTestCaseName := strings.ReplaceAll(name, " ", "_")
|
||||
h.manager.currentTestCase = cleanTestCaseName
|
||||
}
|
||||
|
||||
// NewNode creates a new node and asserts its creation. The node is guaranteed
|
||||
@ -600,17 +481,51 @@ func (h *HarnessTest) NewNode(name string,
|
||||
err = node.Start(h.runCtx)
|
||||
require.NoError(h, err, "failed to start node %s", node.Name())
|
||||
|
||||
// Get the miner's best block hash.
|
||||
bestBlock, err := h.miner.Client.GetBestBlockHash()
|
||||
require.NoError(h, err, "unable to get best block hash")
|
||||
|
||||
// Wait until the node's chain backend is synced to the miner's best
|
||||
// block.
|
||||
h.WaitForBlockchainSyncTo(node, *bestBlock)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// NewNodeWithCoins creates a new node and asserts its creation. The node is
|
||||
// guaranteed to have finished its initialization and all its subservers are
|
||||
// started. In addition, 5 UTXO of 1 BTC each are sent to the node.
|
||||
func (h *HarnessTest) NewNodeWithCoins(name string,
|
||||
extraArgs []string) *node.HarnessNode {
|
||||
|
||||
node := h.NewNode(name, extraArgs)
|
||||
|
||||
// Load up the wallets of the node with 5 outputs of 1 BTC each.
|
||||
const (
|
||||
numOutputs = 5
|
||||
fundAmount = 1 * btcutil.SatoshiPerBitcoin
|
||||
totalAmount = fundAmount * numOutputs
|
||||
)
|
||||
|
||||
for i := 0; i < numOutputs; i++ {
|
||||
h.createAndSendOutput(
|
||||
node, fundAmount,
|
||||
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
}
|
||||
|
||||
// Mine a block to confirm the transactions.
|
||||
h.MineBlocksAndAssertNumTxes(1, numOutputs)
|
||||
|
||||
// Now block until the wallet have fully synced up.
|
||||
h.WaitForBalanceConfirmed(node, totalAmount)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// Shutdown shuts down the given node and asserts that no errors occur.
|
||||
func (h *HarnessTest) Shutdown(node *node.HarnessNode) {
|
||||
// The process may not be in a state to always shutdown immediately, so
|
||||
// we'll retry up to a hard limit to ensure we eventually shutdown.
|
||||
err := wait.NoError(func() error {
|
||||
return h.manager.shutdownNode(node)
|
||||
}, DefaultTimeout)
|
||||
|
||||
err := h.manager.shutdownNode(node)
|
||||
require.NoErrorf(h, err, "unable to shutdown %v in %v", node.Name(),
|
||||
h.manager.currentTestCase)
|
||||
}
|
||||
@ -853,9 +768,10 @@ func (h *HarnessTest) NewNodeRemoteSigner(name string, extraArgs []string,
|
||||
|
||||
// KillNode kills the node and waits for the node process to stop.
|
||||
func (h *HarnessTest) KillNode(hn *node.HarnessNode) {
|
||||
delete(h.manager.activeNodes, hn.Cfg.NodeID)
|
||||
|
||||
h.Logf("Manually killing the node %s", hn.Name())
|
||||
require.NoErrorf(h, hn.KillAndWait(), "%s: kill got error", hn.Name())
|
||||
delete(h.manager.activeNodes, hn.Cfg.NodeID)
|
||||
}
|
||||
|
||||
// SetFeeEstimate sets a fee rate to be returned from fee estimator.
|
||||
@ -1471,7 +1387,7 @@ func (h *HarnessTest) fundCoins(amt btcutil.Amount, target *node.HarnessNode,
|
||||
}
|
||||
|
||||
// FundCoins attempts to send amt satoshis from the internal mining node to the
|
||||
// targeted lightning node using a P2WKH address. 2 blocks are mined after in
|
||||
// targeted lightning node using a P2WKH address. 1 blocks are mined after in
|
||||
// order to confirm the transaction.
|
||||
func (h *HarnessTest) FundCoins(amt btcutil.Amount, hn *node.HarnessNode) {
|
||||
h.fundCoins(amt, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH, true)
|
||||
@ -1502,6 +1418,40 @@ func (h *HarnessTest) FundCoinsP2TR(amt btcutil.Amount,
|
||||
h.fundCoins(amt, target, lnrpc.AddressType_TAPROOT_PUBKEY, true)
|
||||
}
|
||||
|
||||
// FundNumCoins attempts to send the given number of UTXOs from the internal
|
||||
// mining node to the targeted lightning node using a P2WKH address. Each UTXO
|
||||
// has an amount of 1 BTC. 1 blocks are mined to confirm the tx.
|
||||
func (h *HarnessTest) FundNumCoins(hn *node.HarnessNode, num int) {
|
||||
// Get the initial balance first.
|
||||
resp := hn.RPC.WalletBalance()
|
||||
initialBalance := btcutil.Amount(resp.ConfirmedBalance)
|
||||
|
||||
const fundAmount = 1 * btcutil.SatoshiPerBitcoin
|
||||
|
||||
// Send out the outputs from the miner.
|
||||
for i := 0; i < num; i++ {
|
||||
h.createAndSendOutput(
|
||||
hn, fundAmount, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for ListUnspent to show the correct number of unconfirmed
|
||||
// UTXOs.
|
||||
//
|
||||
// Since neutrino doesn't support unconfirmed outputs, skip this check.
|
||||
if !h.IsNeutrinoBackend() {
|
||||
h.AssertNumUTXOsUnconfirmed(hn, num)
|
||||
}
|
||||
|
||||
// Mine a block to confirm the transactions.
|
||||
h.MineBlocksAndAssertNumTxes(1, num)
|
||||
|
||||
// Now block until the wallet have fully synced up.
|
||||
totalAmount := btcutil.Amount(fundAmount * num)
|
||||
expectedBalance := initialBalance + totalAmount
|
||||
h.WaitForBalanceConfirmed(hn, expectedBalance)
|
||||
}
|
||||
|
||||
// completePaymentRequestsAssertStatus sends payments from a node to complete
|
||||
// all payment requests. This function does not return until all payments
|
||||
// have reached the specified status.
|
||||
@ -1747,9 +1697,9 @@ func (h *HarnessTest) RestartNodeAndRestoreDB(hn *node.HarnessNode) {
|
||||
// closures as the caller doesn't need to mine all the blocks to make sure the
|
||||
// mempool is empty.
|
||||
func (h *HarnessTest) CleanShutDown() {
|
||||
// First, shutdown all non-standby nodes to prevent new transactions
|
||||
// being created and fed into the mempool.
|
||||
h.shutdownNonStandbyNodes()
|
||||
// First, shutdown all nodes to prevent new transactions being created
|
||||
// and fed into the mempool.
|
||||
h.shutdownAllNodes()
|
||||
|
||||
// Now mine blocks till the mempool is empty.
|
||||
h.cleanMempool()
|
||||
@ -2288,6 +2238,13 @@ func acceptChannel(t *testing.T, zeroConf bool, stream rpc.AcceptorClient) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// nodeNames defines a slice of human-reable names for the nodes created in the
|
||||
// `createNodes` method. 8 nodes are defined here as by default we can only
|
||||
// create this many nodes in one test.
|
||||
var nodeNames = []string{
|
||||
"Alice", "Bob", "Carol", "Dave", "Eve", "Frank", "Grace", "Heidi",
|
||||
}
|
||||
|
||||
// createNodes creates the number of nodes specified by the number of configs.
|
||||
// Each node is created using the specified config, the neighbors are
|
||||
// connected.
|
||||
@ -2295,12 +2252,15 @@ func (h *HarnessTest) createNodes(nodeCfgs [][]string) []*node.HarnessNode {
|
||||
// Get the number of nodes.
|
||||
numNodes := len(nodeCfgs)
|
||||
|
||||
// Make sure we are creating a reasonable number of nodes.
|
||||
require.LessOrEqual(h, numNodes, len(nodeNames), "too many nodes")
|
||||
|
||||
// Make a slice of nodes.
|
||||
nodes := make([]*node.HarnessNode, numNodes)
|
||||
|
||||
// Create new nodes.
|
||||
for i, nodeCfg := range nodeCfgs {
|
||||
nodeName := fmt.Sprintf("Node%q", string(rune('A'+i)))
|
||||
nodeName := nodeNames[i]
|
||||
n := h.NewNode(nodeName, nodeCfg)
|
||||
nodes[i] = n
|
||||
}
|
||||
@ -2375,12 +2335,27 @@ func (h *HarnessTest) openChannelsForNodes(nodes []*node.HarnessNode,
|
||||
}
|
||||
resp := h.OpenMultiChannelsAsync(reqs)
|
||||
|
||||
// Make sure the nodes know each other's channels if they are public.
|
||||
if !p.Private {
|
||||
// If the channels are private, make sure the channel participants know
|
||||
// the relevant channels.
|
||||
if p.Private {
|
||||
for i, chanPoint := range resp {
|
||||
// Get the channel participants - for n channels we
|
||||
// would have n+1 nodes.
|
||||
nodeA, nodeB := nodes[i], nodes[i+1]
|
||||
h.AssertChannelInGraph(nodeA, chanPoint)
|
||||
h.AssertChannelInGraph(nodeB, chanPoint)
|
||||
}
|
||||
} else {
|
||||
// Make sure the all nodes know all the channels if they are
|
||||
// public.
|
||||
for _, node := range nodes {
|
||||
for _, chanPoint := range resp {
|
||||
h.AssertChannelInGraph(node, chanPoint)
|
||||
}
|
||||
|
||||
// Make sure every node has updated its cached graph
|
||||
// about the edges as indicated in `DescribeGraph`.
|
||||
h.AssertNumEdges(node, len(resp), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/fn/v2"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
@ -62,9 +61,9 @@ func (h *HarnessTest) WaitForBlockchainSync(hn *node.HarnessNode) {
|
||||
|
||||
// WaitForBlockchainSyncTo waits until the node is synced to bestBlock.
|
||||
func (h *HarnessTest) WaitForBlockchainSyncTo(hn *node.HarnessNode,
|
||||
bestBlock *wire.MsgBlock) {
|
||||
bestBlock chainhash.Hash) {
|
||||
|
||||
bestBlockHash := bestBlock.BlockHash().String()
|
||||
bestBlockHash := bestBlock.String()
|
||||
err := wait.NoError(func() error {
|
||||
resp := hn.RPC.GetInfo()
|
||||
if resp.SyncedToChain {
|
||||
@ -241,59 +240,6 @@ func (h *HarnessTest) EnsureConnected(a, b *node.HarnessNode) {
|
||||
h.AssertPeerConnected(b, a)
|
||||
}
|
||||
|
||||
// AssertNumActiveEdges checks that an expected number of active edges can be
|
||||
// found in the node specified.
|
||||
func (h *HarnessTest) AssertNumActiveEdges(hn *node.HarnessNode,
|
||||
expected int, includeUnannounced bool) []*lnrpc.ChannelEdge {
|
||||
|
||||
var edges []*lnrpc.ChannelEdge
|
||||
|
||||
old := hn.State.Edge.Public
|
||||
if includeUnannounced {
|
||||
old = hn.State.Edge.Total
|
||||
}
|
||||
|
||||
// filterDisabled is a helper closure that filters out disabled
|
||||
// channels.
|
||||
filterDisabled := func(edge *lnrpc.ChannelEdge) bool {
|
||||
if edge.Node1Policy != nil && edge.Node1Policy.Disabled {
|
||||
return false
|
||||
}
|
||||
if edge.Node2Policy != nil && edge.Node2Policy.Disabled {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
err := wait.NoError(func() error {
|
||||
req := &lnrpc.ChannelGraphRequest{
|
||||
IncludeUnannounced: includeUnannounced,
|
||||
}
|
||||
resp := hn.RPC.DescribeGraph(req)
|
||||
activeEdges := fn.Filter(resp.Edges, filterDisabled)
|
||||
total := len(activeEdges)
|
||||
|
||||
if total-old == expected {
|
||||
if expected != 0 {
|
||||
// NOTE: assume edges come in ascending order
|
||||
// that the old edges are at the front of the
|
||||
// slice.
|
||||
edges = activeEdges[old:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return errNumNotMatched(hn.Name(), "num of channel edges",
|
||||
expected, total-old, total, old)
|
||||
}, DefaultTimeout)
|
||||
|
||||
require.NoError(h, err, "timeout while checking for edges")
|
||||
|
||||
return edges
|
||||
}
|
||||
|
||||
// AssertNumEdges checks that an expected number of edges can be found in the
|
||||
// node specified.
|
||||
func (h *HarnessTest) AssertNumEdges(hn *node.HarnessNode,
|
||||
@ -1567,13 +1513,14 @@ func (h *HarnessTest) AssertNumHTLCsAndStage(hn *node.HarnessNode,
|
||||
lnutils.SpewLogClosure(target.PendingHtlcs)())
|
||||
}
|
||||
|
||||
for i, htlc := range target.PendingHtlcs {
|
||||
for _, htlc := range target.PendingHtlcs {
|
||||
if htlc.Stage == stage {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("HTLC %d got stage: %v, "+
|
||||
"want stage: %v", i, htlc.Stage, stage)
|
||||
return fmt.Errorf("HTLC %s got stage: %v, "+
|
||||
"want stage: %v", htlc.Outpoint, htlc.Stage,
|
||||
stage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -1682,7 +1629,7 @@ func (h *HarnessTest) AssertActiveNodesSynced() {
|
||||
|
||||
// AssertActiveNodesSyncedTo asserts all active nodes have synced to the
|
||||
// provided bestBlock.
|
||||
func (h *HarnessTest) AssertActiveNodesSyncedTo(bestBlock *wire.MsgBlock) {
|
||||
func (h *HarnessTest) AssertActiveNodesSyncedTo(bestBlock chainhash.Hash) {
|
||||
for _, node := range h.manager.activeNodes {
|
||||
h.WaitForBlockchainSyncTo(node, bestBlock)
|
||||
}
|
||||
@ -1912,6 +1859,18 @@ func (h *HarnessTest) AssertChannelInGraph(hn *node.HarnessNode,
|
||||
op, err)
|
||||
}
|
||||
|
||||
// Make sure the policies are populated, otherwise this edge
|
||||
// cannot be used for routing.
|
||||
if resp.Node1Policy == nil {
|
||||
return fmt.Errorf("channel %s has no policy1: %w",
|
||||
op, err)
|
||||
}
|
||||
|
||||
if resp.Node2Policy == nil {
|
||||
return fmt.Errorf("channel %s has no policy2: %w",
|
||||
op, err)
|
||||
}
|
||||
|
||||
edge = resp
|
||||
|
||||
return nil
|
||||
|
@ -41,7 +41,7 @@ func (h *HarnessTest) MineBlocks(num int) {
|
||||
// Check the block doesn't have any txns except the coinbase.
|
||||
if len(block.Transactions) <= 1 {
|
||||
// Make sure all the active nodes are synced.
|
||||
h.AssertActiveNodesSyncedTo(block)
|
||||
h.AssertActiveNodesSyncedTo(block.BlockHash())
|
||||
|
||||
// Mine the next block.
|
||||
continue
|
||||
@ -116,7 +116,7 @@ func (h *HarnessTest) MineBlocksAndAssertNumTxes(num uint32,
|
||||
|
||||
// Finally, make sure all the active nodes are synced.
|
||||
bestBlock := blocks[len(blocks)-1]
|
||||
h.AssertActiveNodesSyncedTo(bestBlock)
|
||||
h.AssertActiveNodesSyncedTo(bestBlock.BlockHash())
|
||||
|
||||
return blocks
|
||||
}
|
||||
@ -157,7 +157,7 @@ func (h *HarnessTest) cleanMempool() {
|
||||
bestBlock = blocks[len(blocks)-1]
|
||||
|
||||
// Make sure all the active nodes are synced.
|
||||
h.AssertActiveNodesSyncedTo(bestBlock)
|
||||
h.AssertActiveNodesSyncedTo(bestBlock.BlockHash())
|
||||
|
||||
return fmt.Errorf("still have %d txes in mempool", len(mem))
|
||||
}, wait.MinerMempoolTimeout)
|
||||
|
@ -40,10 +40,6 @@ type nodeManager struct {
|
||||
// {pubkey: *HarnessNode}.
|
||||
activeNodes map[uint32]*node.HarnessNode
|
||||
|
||||
// standbyNodes is a map of all the standby nodes, format:
|
||||
// {pubkey: *HarnessNode}.
|
||||
standbyNodes map[uint32]*node.HarnessNode
|
||||
|
||||
// nodeCounter is a monotonically increasing counter that's used as the
|
||||
// node's unique ID.
|
||||
nodeCounter atomic.Uint32
|
||||
@ -57,11 +53,10 @@ func newNodeManager(lndBinary string, dbBackend node.DatabaseBackend,
|
||||
nativeSQL bool) *nodeManager {
|
||||
|
||||
return &nodeManager{
|
||||
lndBinary: lndBinary,
|
||||
dbBackend: dbBackend,
|
||||
nativeSQL: nativeSQL,
|
||||
activeNodes: make(map[uint32]*node.HarnessNode),
|
||||
standbyNodes: make(map[uint32]*node.HarnessNode),
|
||||
lndBinary: lndBinary,
|
||||
dbBackend: dbBackend,
|
||||
nativeSQL: nativeSQL,
|
||||
activeNodes: make(map[uint32]*node.HarnessNode),
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,11 +112,14 @@ func (nm *nodeManager) registerNode(node *node.HarnessNode) {
|
||||
// ShutdownNode stops an active lnd process and returns when the process has
|
||||
// exited and any temporary directories have been cleaned up.
|
||||
func (nm *nodeManager) shutdownNode(node *node.HarnessNode) error {
|
||||
// Remove the node from the active nodes map even if the shutdown
|
||||
// fails as the shutdown cannot be retried in that case.
|
||||
delete(nm.activeNodes, node.Cfg.NodeID)
|
||||
|
||||
if err := node.Shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(nm.activeNodes, node.Cfg.NodeID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -636,12 +636,11 @@ func (hn *HarnessNode) cleanup() error {
|
||||
// waitForProcessExit Launch a new goroutine which that bubbles up any
|
||||
// potential fatal process errors to the goroutine running the tests.
|
||||
func (hn *HarnessNode) WaitForProcessExit() error {
|
||||
var err error
|
||||
var errReturned error
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
err = hn.cmd.Wait()
|
||||
errChan <- err
|
||||
errChan <- hn.cmd.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
@ -656,24 +655,36 @@ func (hn *HarnessNode) WaitForProcessExit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The process may have already been killed in the test, in
|
||||
// that case we will skip the error and continue processing
|
||||
// the logs.
|
||||
if strings.Contains(err.Error(), "signal: killed") {
|
||||
break
|
||||
}
|
||||
|
||||
// Otherwise, we print the error, break the select and save
|
||||
// logs.
|
||||
hn.printErrf("wait process exit got err: %v", err)
|
||||
|
||||
break
|
||||
errReturned = err
|
||||
|
||||
case <-time.After(wait.DefaultTimeout):
|
||||
hn.printErrf("timeout waiting for process to exit")
|
||||
}
|
||||
|
||||
// Make sure log file is closed and renamed if necessary.
|
||||
finalizeLogfile(hn)
|
||||
filename := finalizeLogfile(hn)
|
||||
|
||||
// Rename the etcd.log file if the node was running on embedded
|
||||
// etcd.
|
||||
// Assert the node has shut down from the log file.
|
||||
err1 := assertNodeShutdown(filename)
|
||||
if err1 != nil {
|
||||
return fmt.Errorf("[%s]: assert shutdown failed in log[%s]: %w",
|
||||
hn.Name(), filename, err1)
|
||||
}
|
||||
|
||||
// Rename the etcd.log file if the node was running on embedded etcd.
|
||||
finalizeEtcdLog(hn)
|
||||
|
||||
return err
|
||||
return errReturned
|
||||
}
|
||||
|
||||
// Stop attempts to stop the active lnd process.
|
||||
@ -700,23 +711,21 @@ func (hn *HarnessNode) Stop() error {
|
||||
|
||||
err := wait.NoError(func() error {
|
||||
_, err := hn.RPC.LN.StopDaemon(ctxt, &req)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
|
||||
// Try again if a recovery/rescan is in progress.
|
||||
case strings.Contains(
|
||||
err.Error(), "recovery in progress",
|
||||
):
|
||||
return err
|
||||
|
||||
default:
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the connection is already closed, we can exit
|
||||
// early as the node has already been shut down in the
|
||||
// test, e.g., in etcd leader health check test.
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}, wait.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("shutdown timeout: %w", err)
|
||||
}
|
||||
|
||||
// Wait for goroutines to be finished.
|
||||
@ -724,6 +733,7 @@ func (hn *HarnessNode) Stop() error {
|
||||
go func() {
|
||||
hn.Watcher.wg.Wait()
|
||||
close(done)
|
||||
hn.Watcher = nil
|
||||
}()
|
||||
|
||||
// If the goroutines fail to finish before timeout, we'll print
|
||||
@ -966,23 +976,76 @@ func getFinalizedLogFilePrefix(hn *HarnessNode) string {
|
||||
|
||||
// finalizeLogfile makes sure the log file cleanup function is initialized,
|
||||
// even if no log file is created.
|
||||
func finalizeLogfile(hn *HarnessNode) {
|
||||
func finalizeLogfile(hn *HarnessNode) string {
|
||||
// Exit early if there's no log file.
|
||||
if hn.logFile == nil {
|
||||
return
|
||||
return ""
|
||||
}
|
||||
|
||||
hn.logFile.Close()
|
||||
|
||||
// If logoutput flag is not set, return early.
|
||||
if !*logOutput {
|
||||
return
|
||||
return ""
|
||||
}
|
||||
|
||||
newFileName := fmt.Sprintf("%v.log",
|
||||
getFinalizedLogFilePrefix(hn),
|
||||
)
|
||||
newFileName := fmt.Sprintf("%v.log", getFinalizedLogFilePrefix(hn))
|
||||
renameFile(hn.filename, newFileName)
|
||||
|
||||
return newFileName
|
||||
}
|
||||
|
||||
// assertNodeShutdown asserts that the node has shut down properly by checking
|
||||
// the last lines of the log file for the shutdown message "Shutdown complete".
|
||||
func assertNodeShutdown(filename string) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read more than one line to make sure we get the last line.
|
||||
// const linesSize = 200
|
||||
//
|
||||
// NOTE: Reading 200 bytes of lines should be more than enough to find
|
||||
// the `Shutdown complete` message. However, this is only true if the
|
||||
// message is printed the last, which means `lnd` will properly wait
|
||||
// for all its subsystems to shut down before exiting. Unfortunately
|
||||
// there is at least one bug in the shutdown process where we don't
|
||||
// wait for the chain backend to fully quit first, which can be easily
|
||||
// reproduced by turning on `RPCC=trace` and use a linesSize of 200.
|
||||
//
|
||||
// TODO(yy): fix the shutdown process and remove this workaround by
|
||||
// refactoring the lnd to use only one rpcclient, which requires quite
|
||||
// some work on the btcwallet front.
|
||||
const linesSize = 1000
|
||||
|
||||
buf := make([]byte, linesSize)
|
||||
stat, statErr := file.Stat()
|
||||
if statErr != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := stat.Size() - linesSize
|
||||
_, err = file.ReadAt(buf, start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Exit early if the shutdown line is found.
|
||||
if bytes.Contains(buf, []byte("Shutdown complete")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// For etcd tests, we need to check for the line where the node is
|
||||
// blocked at wallet unlock since we are testing how such a behavior is
|
||||
// handled by etcd.
|
||||
if bytes.Contains(buf, []byte("wallet and unlock")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("node did not shut down properly: found log "+
|
||||
"lines: %s", buf)
|
||||
}
|
||||
|
||||
// finalizeEtcdLog saves the etcd log files when test ends.
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -45,7 +47,7 @@ func NextAvailablePort() int {
|
||||
defer portFileMutex.Unlock()
|
||||
|
||||
lockFile := filepath.Join(os.TempDir(), uniquePortFile+".lock")
|
||||
timeout := time.After(time.Second)
|
||||
timeout := time.After(wait.DefaultTimeout)
|
||||
|
||||
var (
|
||||
lockFileHandle *os.File
|
||||
|
@ -1,5 +1,5 @@
|
||||
//go:build !darwin && !kvdb_etcd && !kvdb_postgres
|
||||
// +build !darwin,!kvdb_etcd,!kvdb_postgres
|
||||
//go:build !darwin && !windows && !kvdb_etcd && !kvdb_postgres
|
||||
// +build !darwin,!windows,!kvdb_etcd,!kvdb_postgres
|
||||
|
||||
package wait
|
||||
|
||||
|
@ -29,7 +29,21 @@ const (
|
||||
|
||||
// NodeStartTimeout is the timeout value when waiting for a node to
|
||||
// become fully started.
|
||||
NodeStartTimeout = time.Minute * 2
|
||||
//
|
||||
// TODO(yy): There is an optimization we can do to increase the time it
|
||||
// takes to finish the initial wallet sync. Instead of finding the
|
||||
// block birthday using binary search in btcwallet, we can instead
|
||||
// search optimistically by looking at the chain tip minus X blocks to
|
||||
// get the birthday block. This way in the test the node won't attempt
|
||||
// to sync from the beginning of the chain, which is always the case
|
||||
// due to how regtest blocks are mined.
|
||||
// The other direction of optimization is to change the precision of
|
||||
// the regtest block's median time. By consensus, we need to increase
|
||||
// at least one second(?), this means in regtest when large amount of
|
||||
// blocks are mined in a short time, the block time is actually in the
|
||||
// future. We could instead allow the median time to increase by
|
||||
// microseconds for itests.
|
||||
NodeStartTimeout = time.Minute * 3
|
||||
|
||||
// SqliteBusyTimeout is the maximum time that a call to the sqlite db
|
||||
// will wait for the connection to become available.
|
||||
|
@ -29,7 +29,7 @@ const (
|
||||
|
||||
// AsyncBenchmarkTimeout is the timeout used when running the async
|
||||
// payments benchmark.
|
||||
AsyncBenchmarkTimeout = time.Minute*2 + extraTimeout
|
||||
AsyncBenchmarkTimeout = time.Minute*5 + extraTimeout
|
||||
|
||||
// NodeStartTimeout is the timeout value when waiting for a node to
|
||||
// become fully started.
|
||||
|
39
lntest/wait/timeouts_windows.go
Normal file
39
lntest/wait/timeouts_windows.go
Normal file
@ -0,0 +1,39 @@
|
||||
//go:build windows && !kvdb_etcd && !kvdb_postgres
|
||||
// +build windows,!kvdb_etcd,!kvdb_postgres
|
||||
|
||||
package wait
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
// MinerMempoolTimeout is the max time we will wait for a transaction
|
||||
// to propagate to the mining node's mempool.
|
||||
MinerMempoolTimeout = time.Minute
|
||||
|
||||
// ChannelOpenTimeout is the max time we will wait before a channel to
|
||||
// be considered opened.
|
||||
ChannelOpenTimeout = time.Second * 30
|
||||
|
||||
// ChannelCloseTimeout is the max time we will wait before a channel is
|
||||
// considered closed.
|
||||
ChannelCloseTimeout = time.Second * 30
|
||||
|
||||
// DefaultTimeout is a timeout that will be used for various wait
|
||||
// scenarios where no custom timeout value is defined.
|
||||
DefaultTimeout = time.Second * 60
|
||||
|
||||
// AsyncBenchmarkTimeout is the timeout used when running the async
|
||||
// payments benchmark.
|
||||
AsyncBenchmarkTimeout = time.Minute * 5
|
||||
|
||||
// NodeStartTimeout is the timeout value when waiting for a node to
|
||||
// become fully started.
|
||||
NodeStartTimeout = time.Minute * 3
|
||||
|
||||
// SqliteBusyTimeout is the maximum time that a call to the sqlite db
|
||||
// will wait for the connection to become available.
|
||||
SqliteBusyTimeout = time.Second * 10
|
||||
|
||||
// PaymentTimeout is the timeout used when sending payments.
|
||||
PaymentTimeout = time.Second * 120
|
||||
)
|
@ -10,6 +10,7 @@ COVER_PKG = $$(go list -deps -tags="$(DEV_TAGS)" ./... | grep '$(PKG)' | grep -v
|
||||
NUM_ITEST_TRANCHES = 4
|
||||
ITEST_PARALLELISM = $(NUM_ITEST_TRANCHES)
|
||||
POSTGRES_START_DELAY = 5
|
||||
SHUFFLE_SEED = 0
|
||||
|
||||
# If rpc option is set also add all extra RPC tags to DEV_TAGS
|
||||
ifneq ($(with-rpc),)
|
||||
@ -27,6 +28,11 @@ ifneq ($(parallel),)
|
||||
ITEST_PARALLELISM = $(parallel)
|
||||
endif
|
||||
|
||||
# Set the seed for shuffling the test cases.
|
||||
ifneq ($(shuffleseed),)
|
||||
SHUFFLE_SEED = $(shuffleseed)
|
||||
endif
|
||||
|
||||
# Windows needs to append a .exe suffix to all executable files, otherwise it
|
||||
# won't run them.
|
||||
ifneq ($(windows),)
|
||||
|
@ -348,7 +348,7 @@ func (p *paymentLifecycle) checkContext(ctx context.Context) error {
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
reason = channeldb.FailureReasonTimeout
|
||||
log.Warnf("Payment attempt not completed before "+
|
||||
"timeout, id=%s", p.identifier.String())
|
||||
"context timeout, id=%s", p.identifier.String())
|
||||
} else {
|
||||
reason = channeldb.FailureReasonCanceled
|
||||
log.Warnf("Payment attempt context canceled, id=%s",
|
||||
|
@ -3,9 +3,10 @@
|
||||
# Get all the variables.
|
||||
PROCESSES=$1
|
||||
TRANCHES=$2
|
||||
SHUFFLE_SEED=$3
|
||||
|
||||
# Here we also shift 2 times and get the rest of our flags to pass on in $@.
|
||||
shift 2
|
||||
# Here we also shift 3 times and get the rest of our flags to pass on in $@.
|
||||
shift 3
|
||||
|
||||
# Create a variable to hold the final exit code.
|
||||
exit_code=0
|
||||
@ -13,7 +14,7 @@ exit_code=0
|
||||
# Run commands using xargs in parallel and capture their PIDs
|
||||
pids=()
|
||||
for ((i=0; i<PROCESSES; i++)); do
|
||||
scripts/itest_part.sh $i $TRANCHES $@ &
|
||||
scripts/itest_part.sh $i $TRANCHES $SHUFFLE_SEED $@ &
|
||||
pids+=($!)
|
||||
done
|
||||
|
||||
|
@ -5,11 +5,11 @@ WORKDIR=$(pwd)/itest
|
||||
|
||||
TRANCHE=$1
|
||||
NUM_TRANCHES=$2
|
||||
SHUFFLE_SEED_PARAM=$3
|
||||
|
||||
# Shift the passed parameters by two, giving us all remaining testing flags in
|
||||
# Shift the passed parameters by three, giving us all remaining testing flags in
|
||||
# the $@ special variable.
|
||||
shift
|
||||
shift
|
||||
shift 3
|
||||
|
||||
# Windows insists on having the .exe suffix for an executable, we need to add
|
||||
# that here if necessary.
|
||||
@ -18,9 +18,9 @@ LND_EXEC="$WORKDIR"/lnd-itest"$EXEC_SUFFIX"
|
||||
BTCD_EXEC="$WORKDIR"/btcd-itest"$EXEC_SUFFIX"
|
||||
export GOCOVERDIR="$WORKDIR/cover"
|
||||
mkdir -p "$GOCOVERDIR"
|
||||
echo $EXEC "$@" -logoutput -logdir=.logs-tranche$TRANCHE -lndexec=$LND_EXEC -btcdexec=$BTCD_EXEC -splittranches=$NUM_TRANCHES -runtranche=$TRANCHE
|
||||
echo $EXEC "$@" -logoutput -logdir=.logs-tranche$TRANCHE -lndexec=$LND_EXEC -btcdexec=$BTCD_EXEC -splittranches=$NUM_TRANCHES -runtranche=$TRANCHE -shuffleseed=$SHUFFLE_SEED_PARAM
|
||||
|
||||
# Exit code 255 causes the parallel jobs to abort, so if one part fails the
|
||||
# other is aborted too.
|
||||
cd "$WORKDIR" || exit 255
|
||||
$EXEC "$@" -logoutput -logdir=.logs-tranche$TRANCHE -lndexec=$LND_EXEC -btcdexec=$BTCD_EXEC -splittranches=$NUM_TRANCHES -runtranche=$TRANCHE || exit 255
|
||||
$EXEC "$@" -logoutput -logdir=.logs-tranche$TRANCHE -lndexec=$LND_EXEC -btcdexec=$BTCD_EXEC -splittranches=$NUM_TRANCHES -runtranche=$TRANCHE -shuffleseed=$SHUFFLE_SEED_PARAM || exit 255
|
||||
|
Loading…
x
Reference in New Issue
Block a user