itests: add FundPsbt custom lock ID and duration test
This commit is contained in:
4 files changed
+133
No files matched your search
@@ -273,6 +273,10 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "fund psbt",
|
Name: "fund psbt",
|
||||||
TestFunc: testFundPsbt,
|
TestFunc: testFundPsbt,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "fund psbt custom lock",
|
||||||
|
TestFunc: testFundPsbtCustomLock,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "resolution handoff",
|
Name: "resolution handoff",
|
||||||
TestFunc: testResHandoff,
|
TestFunc: testResHandoff,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2"
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
||||||
@@ -1919,3 +1920,113 @@ func testPsbtChanFundingWithUnstableUtxos(ht *lntest.HarnessTest) {
|
|||||||
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
block = ht.MineBlocksAndAssertNumTxes(1, 1)[0]
|
||||||
ht.AssertTxInBlock(block, txHash)
|
ht.AssertTxInBlock(block, txHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testFundPsbtCustomLock verifies that FundPsbt correctly locks inputs
|
||||||
|
// using a custom lock ID and expiration time.
|
||||||
|
func testFundPsbtCustomLock(ht *lntest.HarnessTest) {
|
||||||
|
alice := ht.NewNodeWithCoins("Alice", nil)
|
||||||
|
|
||||||
|
// Define a custom lock ID and a short expiration for testing.
|
||||||
|
customLockID := ht.Random32Bytes()
|
||||||
|
lockDurationSeconds := uint64(30)
|
||||||
|
|
||||||
|
ht.Logf("Using custom lock ID: %x with expiration: %d seconds",
|
||||||
|
customLockID, lockDurationSeconds)
|
||||||
|
|
||||||
|
// Generate an address for the output.
|
||||||
|
aliceAddr := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
|
||||||
|
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
|
||||||
|
})
|
||||||
|
outputs := map[string]uint64{
|
||||||
|
aliceAddr.Address: 100_000,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the FundPsbt request using custom lock parameters.
|
||||||
|
req := &walletrpc.FundPsbtRequest{
|
||||||
|
Template: &walletrpc.FundPsbtRequest_Raw{
|
||||||
|
Raw: &walletrpc.TxTemplate{Outputs: outputs},
|
||||||
|
},
|
||||||
|
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
|
||||||
|
SatPerVbyte: 2,
|
||||||
|
},
|
||||||
|
MinConfs: 1,
|
||||||
|
CustomLockId: customLockID,
|
||||||
|
LockExpirationSeconds: lockDurationSeconds,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture the current time for later expiration validation.
|
||||||
|
callTime := time.Now()
|
||||||
|
|
||||||
|
// Execute the FundPsbt call and validate the response.
|
||||||
|
fundResp := alice.RPC.FundPsbt(req)
|
||||||
|
require.NotEmpty(ht, fundResp.FundedPsbt)
|
||||||
|
|
||||||
|
// Ensure the response includes at least one locked UTXO.
|
||||||
|
require.GreaterOrEqual(ht, len(fundResp.LockedUtxos), 1)
|
||||||
|
|
||||||
|
// Parse the PSBT and map locked outpoints for quick lookup.
|
||||||
|
fundedPacket, err := psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader(fundResp.FundedPsbt), false,
|
||||||
|
)
|
||||||
|
require.NoError(ht, err)
|
||||||
|
|
||||||
|
lockedOutpointsMap := make(map[string]struct{})
|
||||||
|
for _, utxo := range fundResp.LockedUtxos {
|
||||||
|
lockedOutpointsMap[lntest.LnrpcOutpointToStr(utxo.Outpoint)] =
|
||||||
|
struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all PSBT inputs are among the locked UTXOs.
|
||||||
|
require.Len(ht, fundedPacket.UnsignedTx.TxIn, len(lockedOutpointsMap))
|
||||||
|
for _, txIn := range fundedPacket.UnsignedTx.TxIn {
|
||||||
|
_, ok := lockedOutpointsMap[txIn.PreviousOutPoint.String()]
|
||||||
|
require.True(
|
||||||
|
ht, ok, "Missing locked input: %v",
|
||||||
|
txIn.PreviousOutPoint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify leases via ListLeases call.
|
||||||
|
ht.Logf("Verifying leases via ListLeases...")
|
||||||
|
leasesResp := alice.RPC.ListLeases()
|
||||||
|
require.NoError(ht, err)
|
||||||
|
require.Len(ht, leasesResp.LockedUtxos, len(lockedOutpointsMap))
|
||||||
|
|
||||||
|
for _, lease := range leasesResp.LockedUtxos {
|
||||||
|
// Validate that the lease matches our locked UTXOs.
|
||||||
|
require.Contains(
|
||||||
|
ht, lockedOutpointsMap,
|
||||||
|
lntest.LnrpcOutpointToStr(lease.Outpoint),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Confirm lock ID and expiration.
|
||||||
|
require.EqualValues(ht, customLockID, lease.Id)
|
||||||
|
|
||||||
|
expectedExpiration := callTime.Unix() +
|
||||||
|
int64(lockDurationSeconds)
|
||||||
|
|
||||||
|
// Validate that the expiration time is within a small delta (5
|
||||||
|
// seconds) of the expected value. This accounts for any latency
|
||||||
|
// in the RPC call or processing time (to avoid flakes in CI).
|
||||||
|
const leaseExpirationDelta = 5.0
|
||||||
|
require.InDelta(
|
||||||
|
ht, expectedExpiration, lease.Expiration,
|
||||||
|
leaseExpirationDelta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use this extra wait time to ensure the lock is released after the
|
||||||
|
// expiration time.
|
||||||
|
const extraWaitSeconds = 2
|
||||||
|
|
||||||
|
// Wait for the lock to expire, then confirm it's released.
|
||||||
|
waitDuration := time.Duration(
|
||||||
|
lockDurationSeconds+extraWaitSeconds,
|
||||||
|
) * time.Second
|
||||||
|
ht.Logf("Waiting %v for lock to expire...", waitDuration)
|
||||||
|
time.Sleep(waitDuration)
|
||||||
|
|
||||||
|
ht.Logf("Verifying lease expiration...")
|
||||||
|
leasesRespAfter := alice.RPC.ListLeases()
|
||||||
|
require.Empty(ht, leasesRespAfter.LockedUtxos)
|
||||||
|
}
|
||||||
@@ -379,3 +379,16 @@ func (h *HarnessRPC) RequiredReserve(
|
|||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListLeases makes a ListLeases RPC call to the node's WalletKit client.
|
||||||
|
func (h *HarnessRPC) ListLeases() *walletrpc.ListLeasesResponse {
|
||||||
|
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resp, err := h.WalletKit.ListLeases(
|
||||||
|
ctxt, &walletrpc.ListLeasesRequest{},
|
||||||
|
)
|
||||||
|
h.NoError(err, "ListLeases")
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
@@ -294,3 +294,8 @@ func CustomRecordsWithUnendorsed(
|
|||||||
}},
|
}},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LnrpcOutpointToStr returns a string representation of an lnrpc.OutPoint.
|
||||||
|
func LnrpcOutpointToStr(outpoint *lnrpc.OutPoint) string {
|
||||||
|
return fmt.Sprintf("%s:%d", outpoint.TxidStr, outpoint.OutputIndex)
|
||||||
|
}
|
||||||
Reference in new issue
Block a user