invoices: optionally hold and auto-cancel keysend payments
Adds a new configuration flag to lnd that will keep keysend payments in the accepted state. An application can then inspect the payment parameters and decide whether to settle or cancel. The on-the-fly inserted keysend invoices get a configurable expiry time. This is a safeguard in case the application that should decide on the keysend payments isn't active.
This commit is contained in:
6 files changed
+128
-10
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestSettleInvoice tests settling of an invoice and related notifications.
|
||||
@@ -780,6 +781,105 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
|
||||
checkSubscription()
|
||||
}
|
||||
|
||||
// TestHoldKeysend tests receiving a spontaneous payment that is held.
|
||||
func TestHoldKeysend(t *testing.T) {
|
||||
t.Run("settle", func(t *testing.T) {
|
||||
testHoldKeysend(t, false)
|
||||
})
|
||||
t.Run("timeout", func(t *testing.T) {
|
||||
testHoldKeysend(t, true)
|
||||
})
|
||||
}
|
||||
|
||||
// testHoldKeysend is the inner test function that tests hold-keysend.
|
||||
func testHoldKeysend(t *testing.T, timeoutKeysend bool) {
|
||||
defer timeout()()
|
||||
|
||||
const holdDuration = time.Minute
|
||||
|
||||
ctx := newTestContext(t)
|
||||
defer ctx.cleanup()
|
||||
|
||||
ctx.registry.cfg.AcceptKeySend = true
|
||||
ctx.registry.cfg.KeysendHoldTime = holdDuration
|
||||
|
||||
allSubscriptions, err := ctx.registry.SubscribeNotifications(0, 0)
|
||||
assert.Nil(t, err)
|
||||
defer allSubscriptions.Cancel()
|
||||
|
||||
hodlChan := make(chan interface{}, 1)
|
||||
|
||||
amt := lnwire.MilliSatoshi(1000)
|
||||
expiry := uint32(testCurrentHeight + 20)
|
||||
|
||||
// Create key for keysend.
|
||||
preimage := lntypes.Preimage{1, 2, 3}
|
||||
hash := preimage.Hash()
|
||||
|
||||
// Try to settle invoice with a valid keysend htlc.
|
||||
keysendPayload := &mockPayload{
|
||||
customRecords: map[uint64][]byte{
|
||||
record.KeySendType: preimage[:],
|
||||
},
|
||||
}
|
||||
|
||||
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||
hash, amt, expiry,
|
||||
testCurrentHeight, getCircuitKey(10), hodlChan, keysendPayload,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// No immediate resolution is expected.
|
||||
require.Nil(t, resolution, "expected hold resolution")
|
||||
|
||||
// We expect a new invoice notification to be sent out.
|
||||
newInvoice := <-allSubscriptions.NewInvoices
|
||||
if newInvoice.State != channeldb.ContractOpen {
|
||||
t.Fatalf("expected state ContractOpen, but got %v",
|
||||
newInvoice.State)
|
||||
}
|
||||
|
||||
// We expect no further invoice notifications yet (on the all invoices
|
||||
// subscription).
|
||||
select {
|
||||
case <-allSubscriptions.NewInvoices:
|
||||
t.Fatalf("no invoice update expected")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
if timeoutKeysend {
|
||||
// Advance the clock to just past the hold duration.
|
||||
ctx.clock.SetTime(ctx.clock.Now().Add(
|
||||
holdDuration + time.Millisecond),
|
||||
)
|
||||
|
||||
// Expect the keysend payment to be failed.
|
||||
res := <-hodlChan
|
||||
failResolution, ok := res.(*HtlcFailResolution)
|
||||
require.Truef(
|
||||
t, ok, "expected fail resolution, got: %T",
|
||||
resolution,
|
||||
)
|
||||
require.Equal(
|
||||
t, ResultCanceled, failResolution.Outcome,
|
||||
"expected keysend payment to be failed",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Settle keysend payment manually.
|
||||
require.Nil(t, ctx.registry.SettleHodlInvoice(
|
||||
*newInvoice.Terms.PaymentPreimage,
|
||||
))
|
||||
|
||||
// We expect a settled notification to be sent out.
|
||||
settledInvoice := <-allSubscriptions.SettledInvoices
|
||||
assert.Equal(t, settledInvoice.State, channeldb.ContractSettled)
|
||||
}
|
||||
|
||||
// TestMppPayment tests settling of an invoice with multiple partial payments.
|
||||
// It covers the case where there is a mpp timeout before the whole invoice is
|
||||
// paid and the case where the invoice is settled in time.
|
||||
|
||||
Reference in new issue
Block a user