2019-12-03 14:22:31 +03:00
|
|
|
package invoices
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2020-02-04 17:15:48 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2019-12-03 14:22:31 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2019-09-03 13:23:39 +03:00
|
|
|
"github.com/lightningnetwork/lnd/record"
|
2019-12-03 14:22:31 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// invoiceUpdateCtx is an object that describes the context for the invoice
|
|
|
|
// update to be carried out.
|
|
|
|
type invoiceUpdateCtx struct {
|
2020-02-04 17:15:48 +03:00
|
|
|
hash lntypes.Hash
|
2019-12-03 14:22:31 +03:00
|
|
|
circuitKey channeldb.CircuitKey
|
|
|
|
amtPaid lnwire.MilliSatoshi
|
|
|
|
expiry uint32
|
|
|
|
currentHeight int32
|
|
|
|
finalCltvRejectDelta int32
|
2019-12-12 02:01:55 +03:00
|
|
|
customRecords record.CustomSet
|
2019-09-03 13:23:39 +03:00
|
|
|
mpp *record.MPP
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
2020-05-22 01:37:10 +03:00
|
|
|
// invoiceRef returns an identifier that can be used to lookup or update the
|
|
|
|
// invoice this HTLC is targeting.
|
|
|
|
func (i *invoiceUpdateCtx) invoiceRef() channeldb.InvoiceRef {
|
2020-05-22 01:37:39 +03:00
|
|
|
if i.mpp != nil {
|
|
|
|
payAddr := i.mpp.PaymentAddr()
|
|
|
|
return channeldb.InvoiceRefByHashAndAddr(i.hash, payAddr)
|
|
|
|
}
|
2020-05-22 01:37:10 +03:00
|
|
|
return channeldb.InvoiceRefByHash(i.hash)
|
|
|
|
}
|
|
|
|
|
2020-02-04 17:17:54 +03:00
|
|
|
// log logs a message specific to this update context.
|
|
|
|
func (i *invoiceUpdateCtx) log(s string) {
|
2020-05-22 01:37:10 +03:00
|
|
|
log.Debugf("Invoice%v: %v, amt=%v, expiry=%v, circuit=%v, mpp=%v",
|
|
|
|
i.invoiceRef, s, i.amtPaid, i.expiry, i.circuitKey, i.mpp)
|
2020-02-04 17:17:54 +03:00
|
|
|
}
|
|
|
|
|
2020-02-06 20:35:10 +03:00
|
|
|
// failRes is a helper function which creates a failure resolution with
|
2020-02-06 20:35:16 +03:00
|
|
|
// the information contained in the invoiceUpdateCtx and the fail resolution
|
|
|
|
// result provided.
|
|
|
|
func (i invoiceUpdateCtx) failRes(outcome FailResolutionResult) *HtlcFailResolution {
|
2020-02-06 20:35:10 +03:00
|
|
|
return NewFailResolution(i.circuitKey, i.currentHeight, outcome)
|
|
|
|
}
|
|
|
|
|
|
|
|
// settleRes is a helper function which creates a settle resolution with
|
|
|
|
// the information contained in the invoiceUpdateCtx and the preimage and
|
2020-02-06 20:35:16 +03:00
|
|
|
// the settle resolution result provided.
|
2020-02-06 20:35:10 +03:00
|
|
|
func (i invoiceUpdateCtx) settleRes(preimage lntypes.Preimage,
|
2020-02-06 20:35:16 +03:00
|
|
|
outcome SettleResolutionResult) *HtlcSettleResolution {
|
2020-02-06 20:35:10 +03:00
|
|
|
|
|
|
|
return NewSettleResolution(
|
|
|
|
preimage, i.circuitKey, i.currentHeight, outcome,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// acceptRes is a helper function which creates an accept resolution with
|
2020-02-06 20:35:16 +03:00
|
|
|
// the information contained in the invoiceUpdateCtx and the accept resolution
|
|
|
|
// result provided.
|
|
|
|
func (i invoiceUpdateCtx) acceptRes(outcome acceptResolutionResult) *htlcAcceptResolution {
|
2020-02-06 20:35:10 +03:00
|
|
|
return newAcceptResolution(i.circuitKey, outcome)
|
|
|
|
}
|
|
|
|
|
2019-12-03 14:22:31 +03:00
|
|
|
// updateInvoice is a callback for DB.UpdateInvoice that contains the invoice
|
2020-02-06 20:35:10 +03:00
|
|
|
// settlement logic. It returns a hltc resolution that indicates what the
|
|
|
|
// outcome of the update was.
|
2019-12-03 14:22:31 +03:00
|
|
|
func updateInvoice(ctx *invoiceUpdateCtx, inv *channeldb.Invoice) (
|
2020-02-06 20:35:10 +03:00
|
|
|
*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
// Don't update the invoice when this is a replayed htlc.
|
|
|
|
htlc, ok := inv.Htlcs[ctx.circuitKey]
|
|
|
|
if ok {
|
|
|
|
switch htlc.State {
|
|
|
|
case channeldb.HtlcStateCanceled:
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultReplayToCanceled), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
case channeldb.HtlcStateAccepted:
|
2020-02-06 20:35:16 +03:00
|
|
|
return nil, ctx.acceptRes(resultReplayToAccepted), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
case channeldb.HtlcStateSettled:
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.settleRes(
|
2020-04-08 14:47:10 +03:00
|
|
|
*inv.Terms.PaymentPreimage,
|
2020-02-06 20:35:10 +03:00
|
|
|
ResultReplayToSettled,
|
|
|
|
), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
default:
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, nil, errors.New("unknown htlc state")
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-03 13:23:39 +03:00
|
|
|
if ctx.mpp == nil {
|
|
|
|
return updateLegacy(ctx, inv)
|
|
|
|
}
|
|
|
|
|
|
|
|
return updateMpp(ctx, inv)
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateMpp is a callback for DB.UpdateInvoice that contains the invoice
|
|
|
|
// settlement logic for mpp payments.
|
2020-02-06 20:35:10 +03:00
|
|
|
func updateMpp(ctx *invoiceUpdateCtx,
|
|
|
|
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc,
|
|
|
|
HtlcResolution, error) {
|
2019-09-03 13:23:39 +03:00
|
|
|
|
|
|
|
// Start building the accept descriptor.
|
|
|
|
acceptDesc := &channeldb.HtlcAcceptDesc{
|
|
|
|
Amt: ctx.amtPaid,
|
|
|
|
Expiry: ctx.expiry,
|
|
|
|
AcceptHeight: ctx.currentHeight,
|
|
|
|
MppTotalAmt: ctx.mpp.TotalMsat(),
|
|
|
|
CustomRecords: ctx.customRecords,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only accept payments to open invoices. This behaviour differs from
|
|
|
|
// non-mpp payments that are accepted even after the invoice is settled.
|
|
|
|
// Because non-mpp payments don't have a payment address, this is needed
|
|
|
|
// to thwart probing.
|
|
|
|
if inv.State != channeldb.ContractOpen {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultInvoiceNotOpen), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check the payment address that authorizes the payment.
|
|
|
|
if ctx.mpp.PaymentAddr() != inv.Terms.PaymentAddr {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultAddressMismatch), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Don't accept zero-valued sets.
|
|
|
|
if ctx.mpp.TotalMsat() == 0 {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the total amt of the htlc set is high enough. In case this
|
|
|
|
// is a zero-valued invoice, it will always be enough.
|
|
|
|
if ctx.mpp.TotalMsat() < inv.Terms.Value {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultHtlcSetTotalTooLow), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether total amt matches other htlcs in the set.
|
|
|
|
var newSetTotal lnwire.MilliSatoshi
|
|
|
|
for _, htlc := range inv.Htlcs {
|
|
|
|
// Only consider accepted mpp htlcs. It is possible that there
|
|
|
|
// are htlcs registered in the invoice database that previously
|
|
|
|
// timed out and are in the canceled state now.
|
|
|
|
if htlc.State != channeldb.HtlcStateAccepted {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if ctx.mpp.TotalMsat() != htlc.MppTotalAmt {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultHtlcSetTotalMismatch), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
newSetTotal += htlc.Amt
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add amount of new htlc.
|
|
|
|
newSetTotal += ctx.amtPaid
|
|
|
|
|
|
|
|
// Make sure the communicated set total isn't overpaid.
|
|
|
|
if newSetTotal > ctx.mpp.TotalMsat() {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultHtlcSetOverpayment), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// The invoice is still open. Check the expiry.
|
|
|
|
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Record HTLC in the invoice database.
|
|
|
|
newHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{
|
|
|
|
ctx.circuitKey: acceptDesc,
|
|
|
|
}
|
|
|
|
|
|
|
|
update := channeldb.InvoiceUpdateDesc{
|
|
|
|
AddHtlcs: newHtlcs,
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the invoice cannot be settled yet, only record the htlc.
|
|
|
|
setComplete := newSetTotal == ctx.mpp.TotalMsat()
|
|
|
|
if !setComplete {
|
2020-02-06 20:35:16 +03:00
|
|
|
return &update, ctx.acceptRes(resultPartialAccepted), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if we can settle or this is an hold invoice and
|
|
|
|
// we need to wait for the preimage.
|
2020-04-08 14:47:10 +03:00
|
|
|
if inv.HodlInvoice {
|
2019-09-03 13:23:39 +03:00
|
|
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
|
|
|
NewState: channeldb.ContractAccepted,
|
|
|
|
}
|
2020-02-06 20:35:16 +03:00
|
|
|
return &update, ctx.acceptRes(resultAccepted), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
|
|
|
NewState: channeldb.ContractSettled,
|
|
|
|
Preimage: inv.Terms.PaymentPreimage,
|
|
|
|
}
|
|
|
|
|
2020-02-06 20:35:10 +03:00
|
|
|
return &update, ctx.settleRes(
|
2020-04-08 14:47:10 +03:00
|
|
|
*inv.Terms.PaymentPreimage, ResultSettled,
|
2020-02-06 20:35:10 +03:00
|
|
|
), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// updateLegacy is a callback for DB.UpdateInvoice that contains the invoice
|
|
|
|
// settlement logic for legacy payments.
|
2020-02-06 20:35:10 +03:00
|
|
|
func updateLegacy(ctx *invoiceUpdateCtx,
|
|
|
|
inv *channeldb.Invoice) (*channeldb.InvoiceUpdateDesc, HtlcResolution, error) {
|
2019-09-03 13:23:39 +03:00
|
|
|
|
|
|
|
// If the invoice is already canceled, there is no further
|
|
|
|
// checking to do.
|
2019-12-03 14:22:31 +03:00
|
|
|
if inv.State == channeldb.ContractCanceled {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultInvoiceAlreadyCanceled), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// If an invoice amount is specified, check that enough is paid. Also
|
|
|
|
// check this for duplicate payments if the invoice is already settled
|
2019-12-03 22:41:46 +03:00
|
|
|
// or accepted. In case this is a zero-valued invoice, it will always be
|
|
|
|
// enough.
|
|
|
|
if ctx.amtPaid < inv.Terms.Value {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultAmountTooLow), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
2019-09-03 13:23:39 +03:00
|
|
|
// TODO(joostjager): Check invoice mpp required feature
|
|
|
|
// bit when feature becomes mandatory.
|
|
|
|
|
|
|
|
// Don't allow settling the invoice with an old style
|
|
|
|
// htlc if we are already in the process of gathering an
|
|
|
|
// mpp set.
|
|
|
|
for _, htlc := range inv.Htlcs {
|
|
|
|
if htlc.State == channeldb.HtlcStateAccepted &&
|
|
|
|
htlc.MppTotalAmt > 0 {
|
|
|
|
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultMppInProgress), nil
|
2019-09-03 13:23:39 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-03 14:22:31 +03:00
|
|
|
// The invoice is still open. Check the expiry.
|
|
|
|
if ctx.expiry < uint32(ctx.currentHeight+ctx.finalCltvRejectDelta) {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if ctx.expiry < uint32(ctx.currentHeight+inv.Terms.FinalCltvDelta) {
|
2020-02-06 20:35:10 +03:00
|
|
|
return nil, ctx.failRes(ResultExpiryTooSoon), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Record HTLC in the invoice database.
|
|
|
|
newHtlcs := map[channeldb.CircuitKey]*channeldb.HtlcAcceptDesc{
|
|
|
|
ctx.circuitKey: {
|
2019-11-19 15:33:05 +03:00
|
|
|
Amt: ctx.amtPaid,
|
|
|
|
Expiry: ctx.expiry,
|
|
|
|
AcceptHeight: ctx.currentHeight,
|
|
|
|
CustomRecords: ctx.customRecords,
|
2019-12-03 14:22:31 +03:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-11-27 16:19:15 +03:00
|
|
|
update := channeldb.InvoiceUpdateDesc{
|
|
|
|
AddHtlcs: newHtlcs,
|
|
|
|
}
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
// Don't update invoice state if we are accepting a duplicate payment.
|
|
|
|
// We do accept or settle the HTLC.
|
|
|
|
switch inv.State {
|
|
|
|
case channeldb.ContractAccepted:
|
2020-02-06 20:35:16 +03:00
|
|
|
return &update, ctx.acceptRes(resultDuplicateToAccepted), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
|
|
|
|
case channeldb.ContractSettled:
|
2020-02-06 20:35:10 +03:00
|
|
|
return &update, ctx.settleRes(
|
2020-04-08 14:47:10 +03:00
|
|
|
*inv.Terms.PaymentPreimage, ResultDuplicateToSettled,
|
2020-02-06 20:35:10 +03:00
|
|
|
), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if we can settle or this is an hold invoice and we need
|
|
|
|
// to wait for the preimage.
|
2020-04-08 14:47:10 +03:00
|
|
|
if inv.HodlInvoice {
|
2019-11-27 15:20:14 +03:00
|
|
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
|
|
|
NewState: channeldb.ContractAccepted,
|
|
|
|
}
|
2020-02-06 20:35:10 +03:00
|
|
|
|
2020-02-06 20:35:16 +03:00
|
|
|
return &update, ctx.acceptRes(resultAccepted), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|
|
|
|
|
2019-11-27 15:20:14 +03:00
|
|
|
update.State = &channeldb.InvoiceStateUpdateDesc{
|
|
|
|
NewState: channeldb.ContractSettled,
|
|
|
|
Preimage: inv.Terms.PaymentPreimage,
|
|
|
|
}
|
2019-12-03 14:22:31 +03:00
|
|
|
|
2020-02-06 20:35:10 +03:00
|
|
|
return &update, ctx.settleRes(
|
2020-04-08 14:47:10 +03:00
|
|
|
*inv.Terms.PaymentPreimage, ResultSettled,
|
2020-02-06 20:35:10 +03:00
|
|
|
), nil
|
2019-12-03 14:22:31 +03:00
|
|
|
}
|