invoices: add processAMP
This commit is contained in:
parent
3fb70dd936
commit
88b72ab398
@ -781,6 +781,67 @@ func (i *InvoiceRegistry) processKeySend(ctx invoiceUpdateCtx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processAMP just-in-time inserts an invoice if this htlc is a keysend
|
||||||
|
// htlc.
|
||||||
|
func (i *InvoiceRegistry) processAMP(ctx invoiceUpdateCtx) error {
|
||||||
|
// AMP payments MUST also include an MPP record.
|
||||||
|
if ctx.mpp == nil {
|
||||||
|
return errors.New("no MPP record for AMP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an invoice for the total amount expected, provided in the MPP
|
||||||
|
// record.
|
||||||
|
amt := ctx.mpp.TotalMsat()
|
||||||
|
|
||||||
|
// Set the TLV and MPP optional features on the invoice. We'll also make
|
||||||
|
// the AMP features required so that it can't be paid by legacy or MPP
|
||||||
|
// htlcs.
|
||||||
|
rawFeatures := lnwire.NewRawFeatureVector(
|
||||||
|
lnwire.TLVOnionPayloadOptional,
|
||||||
|
lnwire.PaymentAddrOptional,
|
||||||
|
lnwire.AMPRequired,
|
||||||
|
)
|
||||||
|
features := lnwire.NewFeatureVector(rawFeatures, lnwire.Features)
|
||||||
|
|
||||||
|
// Use the minimum block delta that we require for settling htlcs.
|
||||||
|
finalCltvDelta := i.cfg.FinalCltvRejectDelta
|
||||||
|
|
||||||
|
// Pre-check expiry here to prevent inserting an invoice that will not
|
||||||
|
// be settled.
|
||||||
|
if ctx.expiry < uint32(ctx.currentHeight+finalCltvDelta) {
|
||||||
|
return errors.New("final expiry too soon")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll use the sender-generated payment address provided in the HTLC
|
||||||
|
// to create our AMP invoice.
|
||||||
|
payAddr := ctx.mpp.PaymentAddr()
|
||||||
|
|
||||||
|
// Create placeholder invoice.
|
||||||
|
invoice := &channeldb.Invoice{
|
||||||
|
CreationDate: i.cfg.Clock.Now(),
|
||||||
|
Terms: channeldb.ContractTerm{
|
||||||
|
FinalCltvDelta: finalCltvDelta,
|
||||||
|
Value: amt,
|
||||||
|
PaymentPreimage: nil,
|
||||||
|
PaymentAddr: payAddr,
|
||||||
|
Features: features,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert invoice into database. Ignore duplicates payment hashes and
|
||||||
|
// payment addrs, this may be a replay or a different HTLC for the AMP
|
||||||
|
// invoice.
|
||||||
|
_, err := i.AddInvoice(invoice, ctx.hash)
|
||||||
|
switch {
|
||||||
|
case err == channeldb.ErrDuplicateInvoice:
|
||||||
|
return nil
|
||||||
|
case err == channeldb.ErrDuplicatePayAddr:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NotifyExitHopHtlc attempts to mark an invoice as settled. The return value
|
// NotifyExitHopHtlc attempts to mark an invoice as settled. The return value
|
||||||
// describes how the htlc should be resolved.
|
// describes how the htlc should be resolved.
|
||||||
//
|
//
|
||||||
@ -819,6 +880,16 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
// AddInvoice obtains its own lock. This is no problem, because the
|
// AddInvoice obtains its own lock. This is no problem, because the
|
||||||
// operation is idempotent.
|
// operation is idempotent.
|
||||||
if i.cfg.AcceptKeySend {
|
if i.cfg.AcceptKeySend {
|
||||||
|
if ctx.amp != nil {
|
||||||
|
err := i.processAMP(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.log(fmt.Sprintf("amp error: %v", err))
|
||||||
|
|
||||||
|
return NewFailResolution(
|
||||||
|
circuitKey, currentHeight, ResultAmpError,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
err := i.processKeySend(ctx)
|
err := i.processKeySend(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.log(fmt.Sprintf("keysend error: %v", err))
|
ctx.log(fmt.Sprintf("keysend error: %v", err))
|
||||||
@ -828,6 +899,7 @@ func (i *InvoiceRegistry) NotifyExitHopHtlc(rHash lntypes.Hash,
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Execute locked notify exit hop logic.
|
// Execute locked notify exit hop logic.
|
||||||
i.Lock()
|
i.Lock()
|
||||||
|
@ -1273,3 +1273,36 @@ func TestSettleInvoicePaymentAddrRequiredOptionalGrace(t *testing.T) {
|
|||||||
t.Fatal("no update received")
|
t.Fatal("no update received")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAMPWithoutMPPPayload asserts that we correctly reject an AMP HTLC that
|
||||||
|
// does not include an MPP record.
|
||||||
|
func TestAMPWithoutMPPPayload(t *testing.T) {
|
||||||
|
defer timeout()()
|
||||||
|
|
||||||
|
ctx := newTestContext(t)
|
||||||
|
defer ctx.cleanup()
|
||||||
|
|
||||||
|
ctx.registry.cfg.AcceptKeySend = true
|
||||||
|
|
||||||
|
const (
|
||||||
|
shardAmt = lnwire.MilliSatoshi(10)
|
||||||
|
expiry = uint32(testCurrentHeight + 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create payload with missing MPP field.
|
||||||
|
payload := &mockPayload{
|
||||||
|
amp: record.NewAMP([32]byte{}, [32]byte{}, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
hodlChan := make(chan interface{}, 1)
|
||||||
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
|
lntypes.Hash{}, shardAmt, expiry,
|
||||||
|
testCurrentHeight, getCircuitKey(uint64(10)), hodlChan,
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// We should receive the ResultAmpError failure.
|
||||||
|
require.NotNil(t, resolution)
|
||||||
|
checkFailResolution(t, resolution, ResultAmpError)
|
||||||
|
}
|
||||||
|
@ -105,6 +105,9 @@ const (
|
|||||||
// ResultMppInProgress is returned when we are busy receiving a mpp
|
// ResultMppInProgress is returned when we are busy receiving a mpp
|
||||||
// payment.
|
// payment.
|
||||||
ResultMppInProgress
|
ResultMppInProgress
|
||||||
|
|
||||||
|
// ResultAmpError is returned when we receive invalid AMP parameters.
|
||||||
|
ResultAmpError
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a string representation of the result.
|
// String returns a string representation of the result.
|
||||||
@ -162,6 +165,9 @@ func (f FailResolutionResult) FailureString() string {
|
|||||||
case ResultMppInProgress:
|
case ResultMppInProgress:
|
||||||
return "mpp reception in progress"
|
return "mpp reception in progress"
|
||||||
|
|
||||||
|
case ResultAmpError:
|
||||||
|
return "invalid amp parameters"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return "unknown failure resolution result"
|
return "unknown failure resolution result"
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,16 @@ func (p *mockPayload) CustomRecords() record.CustomSet {
|
|||||||
return p.customRecords
|
return p.customRecords
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testHtlcExpiry = uint32(5)
|
||||||
|
|
||||||
|
testInvoiceCltvDelta = uint32(4)
|
||||||
|
|
||||||
|
testFinalCltvRejectDelta = int32(4)
|
||||||
|
|
||||||
|
testCurrentHeight = int32(1)
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testTimeout = 5 * time.Second
|
testTimeout = 5 * time.Second
|
||||||
|
|
||||||
@ -55,14 +65,6 @@ var (
|
|||||||
|
|
||||||
testInvoicePaymentHash = testInvoicePreimage.Hash()
|
testInvoicePaymentHash = testInvoicePreimage.Hash()
|
||||||
|
|
||||||
testHtlcExpiry = uint32(5)
|
|
||||||
|
|
||||||
testInvoiceCltvDelta = uint32(4)
|
|
||||||
|
|
||||||
testFinalCltvRejectDelta = int32(4)
|
|
||||||
|
|
||||||
testCurrentHeight = int32(1)
|
|
||||||
|
|
||||||
testPrivKeyBytes, _ = hex.DecodeString(
|
testPrivKeyBytes, _ = hex.DecodeString(
|
||||||
"e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
|
"e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user