2019-01-14 12:09:46 +03:00
|
|
|
package invoices
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testTimeout = 5 * time.Second
|
|
|
|
|
|
|
|
preimage = lntypes.Preimage{
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
hash = preimage.Hash()
|
|
|
|
|
2019-08-12 18:28:36 +03:00
|
|
|
testHtlcExpiry = uint32(5)
|
2019-04-16 13:11:20 +03:00
|
|
|
|
2019-08-14 22:21:39 +03:00
|
|
|
testInvoiceCltvDelta = uint32(4)
|
|
|
|
|
2019-08-12 18:28:36 +03:00
|
|
|
testFinalCltvRejectDelta = int32(4)
|
|
|
|
|
|
|
|
testCurrentHeight = int32(1)
|
2019-01-14 12:09:46 +03:00
|
|
|
)
|
|
|
|
|
2019-02-20 14:11:15 +03:00
|
|
|
var (
|
|
|
|
testInvoice = &channeldb.Invoice{
|
|
|
|
Terms: channeldb.ContractTerm{
|
|
|
|
PaymentPreimage: preimage,
|
|
|
|
Value: lnwire.MilliSatoshi(100000),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func newTestContext(t *testing.T) (*InvoiceRegistry, func()) {
|
2019-01-14 12:09:46 +03:00
|
|
|
cdb, cleanup, err := newDB()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instantiate and start the invoice registry.
|
2019-08-20 15:54:39 +03:00
|
|
|
registry := NewRegistry(cdb, testFinalCltvRejectDelta)
|
2019-01-14 12:09:46 +03:00
|
|
|
|
|
|
|
err = registry.Start()
|
|
|
|
if err != nil {
|
2019-02-20 14:11:15 +03:00
|
|
|
cleanup()
|
2019-01-14 12:09:46 +03:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-02-20 14:11:15 +03:00
|
|
|
|
|
|
|
return registry, func() {
|
|
|
|
registry.Stop()
|
|
|
|
cleanup()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-08 16:48:31 +03:00
|
|
|
func getCircuitKey(htlcID uint64) channeldb.CircuitKey {
|
|
|
|
return channeldb.CircuitKey{
|
|
|
|
ChanID: lnwire.ShortChannelID{
|
|
|
|
BlockHeight: 1, TxIndex: 2, TxPosition: 3,
|
|
|
|
},
|
|
|
|
HtlcID: htlcID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-20 14:11:15 +03:00
|
|
|
// TestSettleInvoice tests settling of an invoice and related notifications.
|
|
|
|
func TestSettleInvoice(t *testing.T) {
|
|
|
|
registry, cleanup := newTestContext(t)
|
|
|
|
defer cleanup()
|
2019-01-14 12:09:46 +03:00
|
|
|
|
|
|
|
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
|
|
|
defer allSubscriptions.Cancel()
|
|
|
|
|
|
|
|
// Subscribe to the not yet existing invoice.
|
2019-08-09 13:22:53 +03:00
|
|
|
subscription, err := registry.SubscribeSingleInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-01-14 12:09:46 +03:00
|
|
|
defer subscription.Cancel()
|
|
|
|
|
|
|
|
if subscription.hash != hash {
|
|
|
|
t.Fatalf("expected subscription for provided hash")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the invoice.
|
2019-02-20 14:11:15 +03:00
|
|
|
addIdx, err := registry.AddInvoice(testInvoice, hash)
|
2019-01-14 12:09:46 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if addIdx != 1 {
|
|
|
|
t.Fatalf("expected addIndex to start with 1, but got %v",
|
|
|
|
addIdx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the open state to be sent to the single invoice subscriber.
|
|
|
|
select {
|
|
|
|
case update := <-subscription.Updates:
|
|
|
|
if update.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
update.Terms.State)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect a new invoice notification to be sent out.
|
|
|
|
select {
|
|
|
|
case newInvoice := <-allSubscriptions.NewInvoices:
|
|
|
|
if newInvoice.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
newInvoice.Terms.State)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
2019-02-11 14:01:05 +03:00
|
|
|
hodlChan := make(chan interface{}, 1)
|
|
|
|
|
2019-08-14 22:21:39 +03:00
|
|
|
// Try to settle invoice with an htlc that expires too soon.
|
|
|
|
event, err := registry.NotifyExitHopHtlc(
|
|
|
|
hash, testInvoice.Terms.Value,
|
|
|
|
uint32(testCurrentHeight)+testInvoiceCltvDelta-1,
|
|
|
|
testCurrentHeight, getCircuitKey(10), hodlChan, nil,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if event.Preimage != nil {
|
|
|
|
t.Fatal("expected cancel event")
|
|
|
|
}
|
|
|
|
if event.AcceptHeight != testCurrentHeight {
|
|
|
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
|
|
|
testCurrentHeight, event.AcceptHeight)
|
|
|
|
}
|
|
|
|
|
2019-01-14 12:09:46 +03:00
|
|
|
// Settle invoice with a slightly higher amount.
|
|
|
|
amtPaid := lnwire.MilliSatoshi(100500)
|
2019-04-16 13:11:20 +03:00
|
|
|
_, err = registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-01-14 12:09:46 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the settled state to be sent to the single invoice
|
|
|
|
// subscriber.
|
|
|
|
select {
|
|
|
|
case update := <-subscription.Updates:
|
|
|
|
if update.Terms.State != channeldb.ContractSettled {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
update.Terms.State)
|
|
|
|
}
|
|
|
|
if update.AmtPaid != amtPaid {
|
|
|
|
t.Fatal("invoice AmtPaid incorrect")
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect a settled notification to be sent out.
|
|
|
|
select {
|
|
|
|
case settledInvoice := <-allSubscriptions.SettledInvoices:
|
|
|
|
if settledInvoice.Terms.State != channeldb.ContractSettled {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
settledInvoice.Terms.State)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
2019-08-08 16:48:31 +03:00
|
|
|
// Try to settle again with the same htlc id. We need this idempotent
|
|
|
|
// behaviour after a restart.
|
2019-08-14 22:21:39 +03:00
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-01-14 12:09:46 +03:00
|
|
|
if err != nil {
|
2019-06-10 13:20:49 +03:00
|
|
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
|
|
|
}
|
|
|
|
if event.Preimage == nil {
|
|
|
|
t.Fatal("expected settle event")
|
2019-01-14 12:09:46 +03:00
|
|
|
}
|
|
|
|
|
2019-08-08 16:48:31 +03:00
|
|
|
// Try to settle again with a new higher-valued htlc. This payment
|
|
|
|
// should also be accepted, to prevent any change in behaviour for a
|
|
|
|
// paid invoice that may open up a probe vector.
|
2019-06-10 13:20:49 +03:00
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
2019-08-12 18:28:36 +03:00
|
|
|
hash, amtPaid+600, testHtlcExpiry, testCurrentHeight,
|
2019-08-08 16:48:31 +03:00
|
|
|
getCircuitKey(1), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-01-14 12:09:46 +03:00
|
|
|
if err != nil {
|
2019-06-10 13:20:49 +03:00
|
|
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
|
|
|
}
|
2019-08-08 17:25:25 +03:00
|
|
|
if event.Preimage == nil {
|
|
|
|
t.Fatal("expected settle event")
|
2019-06-10 13:20:49 +03:00
|
|
|
}
|
|
|
|
|
2019-08-08 17:25:25 +03:00
|
|
|
// Try to settle again with a lower amount. This should fail just as it
|
|
|
|
// would have failed if it were the first payment.
|
2019-06-10 13:20:49 +03:00
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
2019-08-12 18:28:36 +03:00
|
|
|
hash, amtPaid-600, testHtlcExpiry, testCurrentHeight,
|
2019-08-08 16:48:31 +03:00
|
|
|
getCircuitKey(2), hodlChan, nil,
|
2019-06-10 13:20:49 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected NotifyExitHopHtlc error: %v", err)
|
|
|
|
}
|
|
|
|
if event.Preimage != nil {
|
|
|
|
t.Fatal("expected cancel event")
|
2019-01-14 12:09:46 +03:00
|
|
|
}
|
|
|
|
|
2019-08-09 16:09:57 +03:00
|
|
|
// Check that settled amount is equal to the sum of values of the htlcs
|
|
|
|
// 0 and 1.
|
2019-08-20 15:54:39 +03:00
|
|
|
inv, err := registry.LookupInvoice(hash)
|
2019-01-14 12:09:46 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-08-09 16:09:57 +03:00
|
|
|
if inv.AmtPaid != amtPaid+amtPaid+600 {
|
|
|
|
t.Fatal("amount incorrect")
|
2019-01-14 12:09:46 +03:00
|
|
|
}
|
2019-01-11 13:19:16 +03:00
|
|
|
|
|
|
|
// Try to cancel.
|
|
|
|
err = registry.CancelInvoice(hash)
|
|
|
|
if err != channeldb.ErrInvoiceAlreadySettled {
|
|
|
|
t.Fatal("expected cancelation of a settled invoice to fail")
|
|
|
|
}
|
2019-02-11 14:01:05 +03:00
|
|
|
|
|
|
|
// As this is a direct sette, we expect nothing on the hodl chan.
|
|
|
|
select {
|
|
|
|
case <-hodlChan:
|
|
|
|
t.Fatal("unexpected event")
|
|
|
|
default:
|
|
|
|
}
|
2019-01-11 13:19:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestCancelInvoice tests cancelation of an invoice and related notifications.
|
|
|
|
func TestCancelInvoice(t *testing.T) {
|
2019-02-20 14:11:15 +03:00
|
|
|
registry, cleanup := newTestContext(t)
|
2019-01-11 13:19:16 +03:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
|
|
|
defer allSubscriptions.Cancel()
|
|
|
|
|
|
|
|
// Try to cancel the not yet existing invoice. This should fail.
|
2019-02-20 14:11:15 +03:00
|
|
|
err := registry.CancelInvoice(hash)
|
2019-01-11 13:19:16 +03:00
|
|
|
if err != channeldb.ErrInvoiceNotFound {
|
|
|
|
t.Fatalf("expected ErrInvoiceNotFound, but got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subscribe to the not yet existing invoice.
|
2019-08-09 13:22:53 +03:00
|
|
|
subscription, err := registry.SubscribeSingleInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-01-11 13:19:16 +03:00
|
|
|
defer subscription.Cancel()
|
|
|
|
|
|
|
|
if subscription.hash != hash {
|
|
|
|
t.Fatalf("expected subscription for provided hash")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the invoice.
|
|
|
|
amt := lnwire.MilliSatoshi(100000)
|
2019-02-20 14:11:15 +03:00
|
|
|
_, err = registry.AddInvoice(testInvoice, hash)
|
2019-01-11 13:19:16 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the open state to be sent to the single invoice subscriber.
|
|
|
|
select {
|
|
|
|
case update := <-subscription.Updates:
|
|
|
|
if update.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf(
|
|
|
|
"expected state ContractOpen, but got %v",
|
|
|
|
update.Terms.State,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect a new invoice notification to be sent out.
|
|
|
|
select {
|
|
|
|
case newInvoice := <-allSubscriptions.NewInvoices:
|
|
|
|
if newInvoice.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf(
|
|
|
|
"expected state ContractOpen, but got %v",
|
|
|
|
newInvoice.Terms.State,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel invoice.
|
|
|
|
err = registry.CancelInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the canceled state to be sent to the single invoice
|
|
|
|
// subscriber.
|
|
|
|
select {
|
|
|
|
case update := <-subscription.Updates:
|
|
|
|
if update.Terms.State != channeldb.ContractCanceled {
|
|
|
|
t.Fatalf(
|
|
|
|
"expected state ContractCanceled, but got %v",
|
|
|
|
update.Terms.State,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
case <-time.After(testTimeout):
|
|
|
|
t.Fatal("no update received")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect no cancel notification to be sent to all invoice
|
|
|
|
// subscribers (backwards compatibility).
|
|
|
|
|
|
|
|
// Try to cancel again.
|
|
|
|
err = registry.CancelInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("expected cancelation of a canceled invoice to succeed")
|
|
|
|
}
|
|
|
|
|
2019-02-20 14:11:15 +03:00
|
|
|
// Notify arrival of a new htlc paying to this invoice. This should
|
2019-08-14 22:21:39 +03:00
|
|
|
// result in a cancel event.
|
2019-02-11 14:01:05 +03:00
|
|
|
hodlChan := make(chan interface{})
|
2019-04-16 13:11:20 +03:00
|
|
|
event, err := registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amt, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-02-20 14:11:15 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal("expected settlement of a canceled invoice to succeed")
|
|
|
|
}
|
|
|
|
|
|
|
|
if event.Preimage != nil {
|
|
|
|
t.Fatal("expected cancel hodl event")
|
2019-01-11 13:19:16 +03:00
|
|
|
}
|
2019-08-14 22:21:39 +03:00
|
|
|
if event.AcceptHeight != testCurrentHeight {
|
|
|
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
|
|
|
testCurrentHeight, event.AcceptHeight)
|
|
|
|
}
|
2019-01-14 12:09:46 +03:00
|
|
|
}
|
|
|
|
|
2019-08-14 22:21:39 +03:00
|
|
|
// TestSettleHoldInvoice tests settling of a hold invoice and related
|
|
|
|
// notifications.
|
|
|
|
func TestSettleHoldInvoice(t *testing.T) {
|
2019-02-11 14:01:05 +03:00
|
|
|
defer timeout(t)()
|
|
|
|
|
|
|
|
cdb, cleanup, err := newDB()
|
2019-09-13 05:59:07 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-02-11 14:01:05 +03:00
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
// Instantiate and start the invoice registry.
|
2019-08-20 15:54:39 +03:00
|
|
|
registry := NewRegistry(cdb, testFinalCltvRejectDelta)
|
2019-02-11 14:01:05 +03:00
|
|
|
|
|
|
|
err = registry.Start()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer registry.Stop()
|
|
|
|
|
|
|
|
allSubscriptions := registry.SubscribeNotifications(0, 0)
|
|
|
|
defer allSubscriptions.Cancel()
|
|
|
|
|
|
|
|
// Subscribe to the not yet existing invoice.
|
2019-08-09 13:22:53 +03:00
|
|
|
subscription, err := registry.SubscribeSingleInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2019-02-11 14:01:05 +03:00
|
|
|
defer subscription.Cancel()
|
|
|
|
|
|
|
|
if subscription.hash != hash {
|
|
|
|
t.Fatalf("expected subscription for provided hash")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the invoice.
|
|
|
|
invoice := &channeldb.Invoice{
|
|
|
|
Terms: channeldb.ContractTerm{
|
|
|
|
PaymentPreimage: channeldb.UnknownPreimage,
|
|
|
|
Value: lnwire.MilliSatoshi(100000),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = registry.AddInvoice(invoice, hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the open state to be sent to the single invoice subscriber.
|
|
|
|
update := <-subscription.Updates
|
|
|
|
if update.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
update.Terms.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We expect a new invoice notification to be sent out.
|
|
|
|
newInvoice := <-allSubscriptions.NewInvoices
|
|
|
|
if newInvoice.Terms.State != channeldb.ContractOpen {
|
|
|
|
t.Fatalf("expected state ContractOpen, but got %v",
|
|
|
|
newInvoice.Terms.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use slightly higher amount for accept/settle.
|
|
|
|
amtPaid := lnwire.MilliSatoshi(100500)
|
|
|
|
|
|
|
|
hodlChan := make(chan interface{}, 1)
|
|
|
|
|
|
|
|
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
|
|
|
// should be possible.
|
2019-04-16 13:11:20 +03:00
|
|
|
event, err := registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-02-11 14:01:05 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
|
|
|
if event != nil {
|
2019-08-20 16:51:34 +03:00
|
|
|
t.Fatalf("expected htlc to be held")
|
2019-02-11 14:01:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test idempotency.
|
2019-04-16 13:11:20 +03:00
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
2019-02-11 14:01:05 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
|
|
|
if event != nil {
|
2019-08-20 16:51:34 +03:00
|
|
|
t.Fatalf("expected htlc to be held")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test replay at a higher height. We expect the same result because it
|
|
|
|
// is a replay.
|
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight+10,
|
2019-08-08 16:48:31 +03:00
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-08-20 16:51:34 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
|
|
|
if event != nil {
|
|
|
|
t.Fatalf("expected htlc to be held")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test a new htlc coming in that doesn't meet the final cltv delta
|
2019-08-09 16:09:57 +03:00
|
|
|
// requirement. It should be rejected.
|
2019-08-20 16:51:34 +03:00
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
|
|
|
hash, amtPaid, 1, testCurrentHeight,
|
2019-08-08 16:48:31 +03:00
|
|
|
getCircuitKey(1), hodlChan, nil,
|
2019-08-20 16:51:34 +03:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
2019-08-09 16:09:57 +03:00
|
|
|
if event == nil || event.Preimage != nil {
|
2019-10-03 18:22:43 +03:00
|
|
|
t.Fatalf("expected htlc to be canceled")
|
2019-02-11 14:01:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// We expect the accepted state to be sent to the single invoice
|
|
|
|
// subscriber. For all invoice subscribers, we don't expect an update.
|
|
|
|
// Those only get notified on settle.
|
|
|
|
update = <-subscription.Updates
|
|
|
|
if update.Terms.State != channeldb.ContractAccepted {
|
|
|
|
t.Fatalf("expected state ContractAccepted, but got %v",
|
|
|
|
update.Terms.State)
|
|
|
|
}
|
|
|
|
if update.AmtPaid != amtPaid {
|
|
|
|
t.Fatal("invoice AmtPaid incorrect")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Settling with preimage should succeed.
|
|
|
|
err = registry.SettleHodlInvoice(preimage)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("expected set preimage to succeed")
|
|
|
|
}
|
|
|
|
|
|
|
|
hodlEvent := (<-hodlChan).(HodlEvent)
|
|
|
|
if *hodlEvent.Preimage != preimage {
|
|
|
|
t.Fatal("unexpected preimage in hodl event")
|
|
|
|
}
|
2019-08-14 22:21:39 +03:00
|
|
|
if hodlEvent.AcceptHeight != testCurrentHeight {
|
|
|
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
|
|
|
testCurrentHeight, event.AcceptHeight)
|
|
|
|
}
|
2019-02-11 14:01:05 +03:00
|
|
|
|
|
|
|
// We expect a settled notification to be sent out for both all and
|
|
|
|
// single invoice subscribers.
|
|
|
|
settledInvoice := <-allSubscriptions.SettledInvoices
|
|
|
|
if settledInvoice.Terms.State != channeldb.ContractSettled {
|
|
|
|
t.Fatalf("expected state ContractSettled, but got %v",
|
|
|
|
settledInvoice.Terms.State)
|
|
|
|
}
|
2019-08-20 16:51:34 +03:00
|
|
|
if settledInvoice.AmtPaid != amtPaid {
|
|
|
|
t.Fatalf("expected amount to be %v, but got %v",
|
|
|
|
amtPaid, settledInvoice.AmtPaid)
|
|
|
|
}
|
2019-02-11 14:01:05 +03:00
|
|
|
|
|
|
|
update = <-subscription.Updates
|
|
|
|
if update.Terms.State != channeldb.ContractSettled {
|
|
|
|
t.Fatalf("expected state ContractSettled, but got %v",
|
|
|
|
update.Terms.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Idempotency.
|
|
|
|
err = registry.SettleHodlInvoice(preimage)
|
|
|
|
if err != channeldb.ErrInvoiceAlreadySettled {
|
|
|
|
t.Fatalf("expected ErrInvoiceAlreadySettled but got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try to cancel.
|
|
|
|
err = registry.CancelInvoice(hash)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected cancelation of a settled invoice to fail")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-14 22:21:39 +03:00
|
|
|
// TestCancelHoldInvoice tests canceling of a hold invoice and related
|
|
|
|
// notifications.
|
|
|
|
func TestCancelHoldInvoice(t *testing.T) {
|
|
|
|
defer timeout(t)()
|
|
|
|
|
|
|
|
cdb, cleanup, err := newDB()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
// Instantiate and start the invoice registry.
|
|
|
|
registry := NewRegistry(cdb, testFinalCltvRejectDelta)
|
|
|
|
|
|
|
|
err = registry.Start()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer registry.Stop()
|
|
|
|
|
|
|
|
// Add the invoice.
|
|
|
|
invoice := &channeldb.Invoice{
|
|
|
|
Terms: channeldb.ContractTerm{
|
|
|
|
PaymentPreimage: channeldb.UnknownPreimage,
|
|
|
|
Value: lnwire.MilliSatoshi(100000),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = registry.AddInvoice(invoice, hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
amtPaid := lnwire.MilliSatoshi(100000)
|
|
|
|
hodlChan := make(chan interface{}, 1)
|
|
|
|
|
|
|
|
// NotifyExitHopHtlc without a preimage present in the invoice registry
|
|
|
|
// should be possible.
|
|
|
|
event, err := registry.NotifyExitHopHtlc(
|
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
|
|
|
if event != nil {
|
|
|
|
t.Fatalf("expected htlc to be held")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cancel invoice.
|
|
|
|
err = registry.CancelInvoice(hash)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("cancel invoice failed")
|
|
|
|
}
|
|
|
|
|
|
|
|
hodlEvent := (<-hodlChan).(HodlEvent)
|
|
|
|
if hodlEvent.Preimage != nil {
|
|
|
|
t.Fatal("expected cancel hodl event")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Offering the same htlc again at a higher height should still result
|
|
|
|
// in a rejection. The accept height is expected to be the original
|
|
|
|
// accept height.
|
|
|
|
event, err = registry.NotifyExitHopHtlc(
|
|
|
|
hash, amtPaid, testHtlcExpiry, testCurrentHeight+1,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("expected settle to succeed but got %v", err)
|
|
|
|
}
|
|
|
|
if event.Preimage != nil {
|
|
|
|
t.Fatalf("expected htlc to be canceled")
|
|
|
|
}
|
|
|
|
if event.AcceptHeight != testCurrentHeight {
|
|
|
|
t.Fatalf("expected acceptHeight %v, but got %v",
|
|
|
|
testCurrentHeight, event.AcceptHeight)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-14 12:09:46 +03:00
|
|
|
func newDB() (*channeldb.DB, func(), error) {
|
|
|
|
// First, create a temporary directory to be used for the duration of
|
|
|
|
// this test.
|
|
|
|
tempDirName, err := ioutil.TempDir("", "channeldb")
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, create channeldb for the first time.
|
|
|
|
cdb, err := channeldb.Open(tempDirName)
|
|
|
|
if err != nil {
|
|
|
|
os.RemoveAll(tempDirName)
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanUp := func() {
|
|
|
|
cdb.Close()
|
|
|
|
os.RemoveAll(tempDirName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cdb, cleanUp, nil
|
|
|
|
}
|
2019-04-16 13:11:20 +03:00
|
|
|
|
|
|
|
// TestUnknownInvoice tests that invoice registry returns an error when the
|
|
|
|
// invoice is unknown. This is to guard against returning a cancel hodl event
|
|
|
|
// for forwarded htlcs. In the link, NotifyExitHopHtlc is only called if we are
|
|
|
|
// the exit hop, but in htlcIncomingContestResolver it is called with forwarded
|
|
|
|
// htlc hashes as well.
|
|
|
|
func TestUnknownInvoice(t *testing.T) {
|
|
|
|
registry, cleanup := newTestContext(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
// Notify arrival of a new htlc paying to this invoice. This should
|
|
|
|
// succeed.
|
|
|
|
hodlChan := make(chan interface{})
|
|
|
|
amt := lnwire.MilliSatoshi(100000)
|
|
|
|
_, err := registry.NotifyExitHopHtlc(
|
2019-08-08 16:48:31 +03:00
|
|
|
hash, amt, testHtlcExpiry, testCurrentHeight,
|
|
|
|
getCircuitKey(0), hodlChan, nil,
|
2019-04-16 13:11:20 +03:00
|
|
|
)
|
|
|
|
if err != channeldb.ErrInvoiceNotFound {
|
|
|
|
t.Fatal("expected invoice not found error")
|
|
|
|
}
|
|
|
|
}
|