Skip to content

Commit

Permalink
invoices/invoiceregistry: generate random payaddr for keysend
Browse files Browse the repository at this point in the history
This commit automatically generates a random payment address for any new
keysend payments. With the addition of the payment address index, we now
enforce uniqueness of payment addresses across all invoices. As a
result, if you run master currently you can only accept one keysend
payment since they all share a zero-value payment address.
  • Loading branch information
cfromknecht committed Jun 2, 2020
1 parent 9f32942 commit 7d1c528
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 17 deletions.
12 changes: 12 additions & 0 deletions invoices/invoiceregistry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package invoices

import (
"crypto/rand"
"errors"
"fmt"
"sync"
Expand Down Expand Up @@ -676,13 +677,24 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
return errors.New("final expiry too soon")
}

// Generate a random payment address for this invoice. The invoice
// databse is indexed by payment address, so we must ensure that the
// generated keysend invoices also have a unique identifier, otherwise
// insertion will fail on the second attempt to insert a keysend invoice
// or if this context is being replayed after a restart.
var payAddr [32]byte
if _, err := rand.Read(payAddr[:]); err != nil {
return err
}

// Create placeholder invoice.
invoice := &channeldb.Invoice{
CreationDate: i.cfg.Clock.Now(),
Terms: channeldb.ContractTerm{
FinalCltvDelta: finalCltvDelta,
Value: amt,
PaymentPreimage: &preimage,
PaymentAddr: payAddr,
Features: features,
},
}
Expand Down
64 changes: 47 additions & 17 deletions invoices/invoiceregistry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,29 +725,59 @@ func testKeySend(t *testing.T, keySendEnabled bool) {
return
}

// Otherwise we expect no error and a settle resolution for the htlc.
settleResolution, ok := resolution.(*HtlcSettleResolution)
if !ok {
t.Fatalf("expected settle resolution, got: %T",
resolution)
checkResolution := func(res HtlcResolution, pimg lntypes.Preimage) {
// Otherwise we expect no error and a settle res for the htlc.
settleResolution, ok := res.(*HtlcSettleResolution)
assert.True(t, ok)
assert.Equal(t, settleResolution.Preimage, pimg)
}
if settleResolution.Preimage != preimage {
t.Fatalf("expected settle with matching preimage")
checkSubscription := func() {
// We expect a new invoice notification to be sent out.
newInvoice := <-allSubscriptions.NewInvoices
assert.Equal(t, newInvoice.State, channeldb.ContractOpen)

// We expect a settled notification to be sent out.
settledInvoice := <-allSubscriptions.SettledInvoices
assert.Equal(t, settledInvoice.State, channeldb.ContractSettled)
}

// 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)
checkResolution(resolution, preimage)
checkSubscription()

// Replay the same keysend payment. We expect an identical resolution,
// but no event should be generated.
resolution, err = ctx.registry.NotifyExitHopHtlc(
hash, amt, expiry,
testCurrentHeight, getCircuitKey(10), hodlChan, keySendPayload,
)
assert.Nil(t, err)
checkResolution(resolution, preimage)

select {
case <-allSubscriptions.NewInvoices:
t.Fatalf("replayed keysend should not generate event")
case <-time.After(time.Second):
}

// We expect a settled notification to be sent out.
settledInvoice := <-allSubscriptions.SettledInvoices
if settledInvoice.State != channeldb.ContractSettled {
t.Fatalf("expected state ContractOpen, but got %v",
settledInvoice.State)
// Finally, test that we can properly fulfill a second keysend payment
// with a unique preiamge.
preimage2 := lntypes.Preimage{1, 2, 3, 4}
hash2 := preimage2.Hash()

keySendPayload2 := &mockPayload{
customRecords: map[uint64][]byte{
record.KeySendType: preimage2[:],
},
}

resolution, err = ctx.registry.NotifyExitHopHtlc(
hash2, amt, expiry,
testCurrentHeight, getCircuitKey(20), hodlChan, keySendPayload2,
)
assert.Nil(t, err)

checkResolution(resolution, preimage2)
checkSubscription()
}

// TestMppPayment tests settling of an invoice with multiple partial payments.
Expand Down

0 comments on commit 7d1c528

Please sign in to comment.