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:
Joost Jager committed 2020-06-27 09:54:11 +02:00
1 parent cff52f7622
commit 24e3234dfa
6 files changed
+128 -10

No files matched your search

+100
View File
@@ -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.