Skip to content

Commit fbe645f

Browse files
committed
itests: add FundPsbt custom lock ID and duration test
1 parent 8dab512 commit fbe645f

File tree

4 files changed

+133
-0
lines changed

4 files changed

+133
-0
lines changed

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ var allTestCases = []*lntest.TestCase{
273273
Name: "fund psbt",
274274
TestFunc: testFundPsbt,
275275
},
276+
{
277+
Name: "fund psbt custom lock",
278+
TestFunc: testFundPsbtCustomLock,
279+
},
276280
{
277281
Name: "resolution handoff",
278282
TestFunc: testResHandoff,

itest/lnd_psbt_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/hex"
66
"testing"
7+
"time"
78

89
"github.com/btcsuite/btcd/btcec/v2"
910
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
@@ -1919,3 +1920,113 @@ func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
19191920
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
19201921
ht.AssertTxInBlock(block, txHash)
19211922
}
1923+
1924+
// testFundPsbtCustomLock verifies that FundPsbt correctly locks inputs
1925+
// using a custom lock ID and expiration time.
1926+
func testFundPsbtCustomLock(ht *lntest.HarnessTest) {
1927+
alice := ht.NewNodeWithCoins("Alice", nil)
1928+
1929+
// Define a custom lock ID and a short expiration for testing.
1930+
customLockID := ht.Random32Bytes()
1931+
lockDurationSeconds := uint64(30)
1932+
1933+
ht.Logf("Using custom lock ID: %x with expiration: %d seconds",
1934+
customLockID, lockDurationSeconds)
1935+
1936+
// Generate an address for the output.
1937+
aliceAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
1938+
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
1939+
})
1940+
outputs := map[string]uint64{
1941+
aliceAddr.Address: 100_000,
1942+
}
1943+
1944+
// Build the FundPsbt request using custom lock parameters.
1945+
req := &walletrpc.FundPsbtRequest{
1946+
Template: &walletrpc.FundPsbtRequest_Raw{
1947+
Raw: &walletrpc.TxTemplate{Outputs: outputs},
1948+
},
1949+
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
1950+
SatPerVbyte: 2,
1951+
},
1952+
MinConfs: 1,
1953+
CustomLockId: customLockID,
1954+
LockExpirationSeconds: lockDurationSeconds,
1955+
}
1956+
1957+
// Capture the current time for later expiration validation.
1958+
callTime := time.Now()
1959+
1960+
// Execute the FundPsbt call and validate the response.
1961+
fundResp := alice.RPC.FundPsbt(req)
1962+
require.NotEmpty(ht, fundResp.FundedPsbt)
1963+
1964+
// Ensure the response includes at least one locked UTXO.
1965+
require.GreaterOrEqual(ht, len(fundResp.LockedUtxos), 1)
1966+
1967+
// Parse the PSBT and map locked outpoints for quick lookup.
1968+
fundedPacket, err := psbt.NewFromRawBytes(
1969+
bytes.NewReader(fundResp.FundedPsbt), false,
1970+
)
1971+
require.NoError(ht, err)
1972+
1973+
lockedOutpointsMap := make(map[string]struct{})
1974+
for _, utxo := range fundResp.LockedUtxos {
1975+
lockedOutpointsMap[lntest.LnrpcOutpointToStr(utxo.Outpoint)] =
1976+
struct{}{}
1977+
}
1978+
1979+
// Check that all PSBT inputs are among the locked UTXOs.
1980+
require.Len(ht, fundedPacket.UnsignedTx.TxIn, len(lockedOutpointsMap))
1981+
for _, txIn := range fundedPacket.UnsignedTx.TxIn {
1982+
_, ok := lockedOutpointsMap[txIn.PreviousOutPoint.String()]
1983+
require.True(
1984+
ht, ok, "Missing locked input: %v",
1985+
txIn.PreviousOutPoint,
1986+
)
1987+
}
1988+
1989+
// Verify leases via ListLeases call.
1990+
ht.Logf("Verifying leases via ListLeases...")
1991+
leasesResp := alice.RPC.ListLeases()
1992+
require.NoError(ht, err)
1993+
require.Len(ht, leasesResp.LockedUtxos, len(lockedOutpointsMap))
1994+
1995+
for _, lease := range leasesResp.LockedUtxos {
1996+
// Validate that the lease matches our locked UTXOs.
1997+
require.Contains(
1998+
ht, lockedOutpointsMap,
1999+
lntest.LnrpcOutpointToStr(lease.Outpoint),
2000+
)
2001+
2002+
// Confirm lock ID and expiration.
2003+
require.EqualValues(ht, customLockID, lease.Id)
2004+
2005+
expectedExpiration := callTime.Unix() +
2006+
int64(lockDurationSeconds)
2007+
2008+
// Validate that the expiration time is within a small delta (5
2009+
// seconds) of the expected value. This accounts for any latency
2010+
// in the RPC call or processing time (to avoid flakes in CI).
2011+
const leaseExpirationDelta = 5.0
2012+
require.InDelta(
2013+
ht, expectedExpiration, lease.Expiration,
2014+
leaseExpirationDelta,
2015+
)
2016+
}
2017+
2018+
// We use this extra wait time to ensure the lock is released after the
2019+
// expiration time.
2020+
const extraWaitSeconds = 2
2021+
2022+
// Wait for the lock to expire, then confirm it's released.
2023+
waitDuration := time.Duration(
2024+
lockDurationSeconds+extraWaitSeconds,
2025+
) * time.Second
2026+
ht.Logf("Waiting %v for lock to expire...", waitDuration)
2027+
time.Sleep(waitDuration)
2028+
2029+
ht.Logf("Verifying lease expiration...")
2030+
leasesRespAfter := alice.RPC.ListLeases()
2031+
require.Empty(ht, leasesRespAfter.LockedUtxos)
2032+
}

lntest/rpc/wallet_kit.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,16 @@ func (h *HarnessRPC) RequiredReserve(
379379

380380
return resp
381381
}
382+
383+
// ListLeases makes a ListLeases RPC call to the node's WalletKit client.
384+
func (h *HarnessRPC) ListLeases() *walletrpc.ListLeasesResponse {
385+
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
386+
defer cancel()
387+
388+
resp, err := h.WalletKit.ListLeases(
389+
ctxt, &walletrpc.ListLeasesRequest{},
390+
)
391+
h.NoError(err, "ListLeases")
392+
393+
return resp
394+
}

lntest/utils.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,8 @@ func CustomRecordsWithUnendorsed(
294294
}},
295295
)
296296
}
297+
298+
// LnrpcOutpointToStr returns a string representation of an lnrpc.OutPoint.
299+
func LnrpcOutpointToStr(outpoint *lnrpc.OutPoint) string {
300+
return fmt.Sprintf("%s:%d", outpoint.TxidStr, outpoint.OutputIndex)
301+
}

0 commit comments

Comments
 (0)