mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
Merge pull request #2354 from joostjager/invoice-state
channeldb+lnrpc: invoice state
This commit is contained in:
commit
2e2d5fcf54
@ -106,7 +106,7 @@ func TestInvoiceWorkflow(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to fetch invoice: %v", err)
|
||||
}
|
||||
if !dbInvoice2.Terms.Settled {
|
||||
if dbInvoice2.Terms.State != ContractSettled {
|
||||
t.Fatalf("invoice should now be settled but isn't")
|
||||
}
|
||||
if dbInvoice2.SettleDate.IsZero() {
|
||||
@ -348,7 +348,7 @@ func TestDuplicateSettleInvoice(t *testing.T) {
|
||||
// We'll update what we expect the settle invoice to be so that our
|
||||
// comparison below has the correct assumption.
|
||||
invoice.SettleIndex = 1
|
||||
invoice.Terms.Settled = true
|
||||
invoice.Terms.State = ContractSettled
|
||||
invoice.AmtPaid = amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
|
||||
|
@ -74,6 +74,30 @@ const (
|
||||
MaxPaymentRequestSize = 4096
|
||||
)
|
||||
|
||||
// ContractState describes the state the invoice is in.
|
||||
type ContractState uint8
|
||||
|
||||
const (
|
||||
// ContractOpen means the invoice has only been created.
|
||||
ContractOpen ContractState = 0
|
||||
|
||||
// ContractSettled means the htlc is settled and the invoice has been
|
||||
// paid.
|
||||
ContractSettled ContractState = 1
|
||||
)
|
||||
|
||||
// String returns a human readable identifier for the ContractState type.
|
||||
func (c ContractState) String() string {
|
||||
switch c {
|
||||
case ContractOpen:
|
||||
return "Open"
|
||||
case ContractSettled:
|
||||
return "Settled"
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// ContractTerm is a companion struct to the Invoice struct. This struct houses
|
||||
// the necessary conditions required before the invoice can be considered fully
|
||||
// settled by the payee.
|
||||
@ -87,9 +111,8 @@ type ContractTerm struct {
|
||||
// which can be satisfied by the above preimage.
|
||||
Value lnwire.MilliSatoshi
|
||||
|
||||
// Settled indicates if this particular contract term has been fully
|
||||
// settled by the payer.
|
||||
Settled bool
|
||||
// State describes the state the invoice is in.
|
||||
State ContractState
|
||||
}
|
||||
|
||||
// Invoice is a payment invoice generated by a payee in order to request
|
||||
@ -380,7 +403,9 @@ func (d *DB) FetchAllInvoices(pendingOnly bool) ([]Invoice, error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if pendingOnly && invoice.Terms.Settled {
|
||||
if pendingOnly &&
|
||||
invoice.Terms.State == ContractSettled {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -528,7 +553,9 @@ func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) {
|
||||
|
||||
// Skip any settled invoices if the caller is only
|
||||
// interested in unsettled.
|
||||
if q.PendingOnly && invoice.Terms.Settled {
|
||||
if q.PendingOnly &&
|
||||
invoice.Terms.State == ContractSettled {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@ -773,7 +800,7 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, byteOrder, i.Terms.Settled); err != nil {
|
||||
if err := binary.Write(w, byteOrder, i.Terms.State); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -845,7 +872,7 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
|
||||
}
|
||||
invoice.Terms.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
|
||||
|
||||
if err := binary.Read(r, byteOrder, &invoice.Terms.Settled); err != nil {
|
||||
if err := binary.Read(r, byteOrder, &invoice.Terms.State); err != nil {
|
||||
return invoice, err
|
||||
}
|
||||
|
||||
@ -872,7 +899,7 @@ func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
|
||||
|
||||
// Add idempotency to duplicate settles, return here to avoid
|
||||
// overwriting the previous info.
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == ContractSettled {
|
||||
return &invoice, nil
|
||||
}
|
||||
|
||||
@ -891,7 +918,7 @@ func settleInvoice(invoices, settleIndex *bbolt.Bucket, invoiceNum []byte,
|
||||
}
|
||||
|
||||
invoice.AmtPaid = amtPaid
|
||||
invoice.Terms.Settled = true
|
||||
invoice.Terms.State = ContractSettled
|
||||
invoice.SettleDate = time.Now()
|
||||
invoice.SettleIndex = nextSettleSeqNo
|
||||
|
||||
|
@ -189,7 +189,7 @@ func migrateInvoiceTimeSeries(tx *bbolt.Tx) error {
|
||||
// Next, we'll check if the invoice has been settled or not. If
|
||||
// so, then we'll also add it to the settle index.
|
||||
var nextSettleSeqNo uint64
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == ContractSettled {
|
||||
nextSettleSeqNo, err = settleIndex.NextSequence()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -2334,7 +2334,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
||||
// TODO(conner): track ownership of settlements to
|
||||
// properly recover from failures? or add batch invoice
|
||||
// settlement
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractOpen {
|
||||
log.Warnf("Accepting duplicate payment for "+
|
||||
"hash=%x", pd.RHash[:])
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ func TestChannelLinkSingleHopPayment(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if !invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatal("alice invoice wasn't settled")
|
||||
}
|
||||
|
||||
@ -467,7 +467,7 @@ func TestChannelLinkMultiHopPayment(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if !invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice haven't been settled")
|
||||
}
|
||||
|
||||
@ -818,7 +818,7 @@ func TestUpdateForwardingPolicy(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if !invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice haven't been settled")
|
||||
}
|
||||
|
||||
@ -937,7 +937,7 @@ func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice have been settled")
|
||||
}
|
||||
|
||||
@ -1026,7 +1026,7 @@ func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) {
|
||||
|
||||
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||
// links hasn't been changed.
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == channeldb.ContractSettled {
|
||||
t.Fatal("alice invoice was settled")
|
||||
}
|
||||
|
||||
@ -1112,7 +1112,7 @@ func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice have been settled")
|
||||
}
|
||||
|
||||
@ -1227,7 +1227,7 @@ func TestChannelLinkMultiHopDecodeError(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice have been settled")
|
||||
}
|
||||
|
||||
@ -3332,7 +3332,7 @@ func TestChannelRetransmission(t *testing.T) {
|
||||
err = errors.Errorf("unable to get invoice: %v", err)
|
||||
continue
|
||||
}
|
||||
if !invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractSettled {
|
||||
err = errors.Errorf("alice invoice haven't been settled")
|
||||
continue
|
||||
}
|
||||
@ -3828,7 +3828,7 @@ func TestChannelLinkAcceptOverpay(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get invoice: %v", err)
|
||||
}
|
||||
if !invoice.Terms.Settled {
|
||||
if invoice.Terms.State != channeldb.ContractSettled {
|
||||
t.Fatal("carol invoice haven't been settled")
|
||||
}
|
||||
|
||||
|
@ -720,11 +720,11 @@ func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash,
|
||||
return fmt.Errorf("can't find mock invoice: %x", rhash[:])
|
||||
}
|
||||
|
||||
if invoice.Terms.Settled {
|
||||
if invoice.Terms.State == channeldb.ContractSettled {
|
||||
return nil
|
||||
}
|
||||
|
||||
invoice.Terms.Settled = true
|
||||
invoice.Terms.State = channeldb.ContractSettled
|
||||
invoice.AmtPaid = amt
|
||||
i.invoices[rhash] = invoice
|
||||
|
||||
|
@ -89,8 +89,7 @@ func (i *invoiceRegistry) Stop() {
|
||||
// Only two event types are currently supported: newly created invoices, and
|
||||
// instance where invoices are settled.
|
||||
type invoiceEvent struct {
|
||||
isSettle bool
|
||||
|
||||
state channeldb.ContractState
|
||||
invoice *channeldb.Invoice
|
||||
}
|
||||
|
||||
@ -143,27 +142,27 @@ func (i *invoiceRegistry) invoiceEventNotifier() {
|
||||
switch {
|
||||
// If we've already sent this settle event to
|
||||
// the client, then we can skip this.
|
||||
case event.isSettle &&
|
||||
case event.state == channeldb.ContractSettled &&
|
||||
client.settleIndex >= invoice.SettleIndex:
|
||||
continue
|
||||
|
||||
// Similarly, if we've already sent this add to
|
||||
// the client then we can skip this one.
|
||||
case !event.isSettle &&
|
||||
case event.state == channeldb.ContractOpen &&
|
||||
client.addIndex >= invoice.AddIndex:
|
||||
continue
|
||||
|
||||
// These two states should never happen, but we
|
||||
// log them just in case so we can detect this
|
||||
// instance.
|
||||
case !event.isSettle &&
|
||||
case event.state == channeldb.ContractOpen &&
|
||||
client.addIndex+1 != invoice.AddIndex:
|
||||
ltndLog.Warnf("client=%v for invoice "+
|
||||
"notifications missed an update, "+
|
||||
"add_index=%v, new add event index=%v",
|
||||
clientID, client.addIndex,
|
||||
invoice.AddIndex)
|
||||
case event.isSettle &&
|
||||
case event.state == channeldb.ContractSettled &&
|
||||
client.settleIndex+1 != invoice.SettleIndex:
|
||||
ltndLog.Warnf("client=%v for invoice "+
|
||||
"notifications missed an update, "+
|
||||
@ -174,8 +173,8 @@ func (i *invoiceRegistry) invoiceEventNotifier() {
|
||||
|
||||
select {
|
||||
case client.ntfnQueue.ChanIn() <- &invoiceEvent{
|
||||
isSettle: event.isSettle,
|
||||
invoice: invoice,
|
||||
state: event.state,
|
||||
invoice: invoice,
|
||||
}:
|
||||
case <-i.quit:
|
||||
return
|
||||
@ -187,10 +186,14 @@ func (i *invoiceRegistry) invoiceEventNotifier() {
|
||||
// don't send a notification twice, which can
|
||||
// happen if a new event is added while we're
|
||||
// catching up a new client.
|
||||
if event.isSettle {
|
||||
switch event.state {
|
||||
case channeldb.ContractSettled:
|
||||
client.settleIndex = invoice.SettleIndex
|
||||
} else {
|
||||
case channeldb.ContractOpen:
|
||||
client.addIndex = invoice.AddIndex
|
||||
default:
|
||||
ltndLog.Errorf("unknown invoice "+
|
||||
"state: %v", event.state)
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,8 +228,8 @@ func (i *invoiceRegistry) deliverBacklogEvents(client *invoiceSubscription) erro
|
||||
|
||||
select {
|
||||
case client.ntfnQueue.ChanIn() <- &invoiceEvent{
|
||||
isSettle: false,
|
||||
invoice: &addEvent,
|
||||
state: channeldb.ContractOpen,
|
||||
invoice: &addEvent,
|
||||
}:
|
||||
case <-i.quit:
|
||||
return fmt.Errorf("registry shutting down")
|
||||
@ -239,8 +242,8 @@ func (i *invoiceRegistry) deliverBacklogEvents(client *invoiceSubscription) erro
|
||||
|
||||
select {
|
||||
case client.ntfnQueue.ChanIn() <- &invoiceEvent{
|
||||
isSettle: true,
|
||||
invoice: &settleEvent,
|
||||
state: channeldb.ContractSettled,
|
||||
invoice: &settleEvent,
|
||||
}:
|
||||
case <-i.quit:
|
||||
return fmt.Errorf("registry shutting down")
|
||||
@ -296,7 +299,7 @@ func (i *invoiceRegistry) AddInvoice(invoice *channeldb.Invoice) (uint64, error)
|
||||
|
||||
// Now that we've added the invoice, we'll send dispatch a message to
|
||||
// notify the clients of this new invoice.
|
||||
i.notifyClients(invoice, false)
|
||||
i.notifyClients(invoice, channeldb.ContractOpen)
|
||||
|
||||
return addIndex, nil
|
||||
}
|
||||
@ -365,17 +368,19 @@ func (i *invoiceRegistry) SettleInvoice(rHash chainhash.Hash,
|
||||
|
||||
ltndLog.Infof("Payment received: %v", spew.Sdump(invoice))
|
||||
|
||||
i.notifyClients(invoice, true)
|
||||
i.notifyClients(invoice, channeldb.ContractSettled)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// notifyClients notifies all currently registered invoice notification clients
|
||||
// of a newly added/settled invoice.
|
||||
func (i *invoiceRegistry) notifyClients(invoice *channeldb.Invoice, settle bool) {
|
||||
func (i *invoiceRegistry) notifyClients(invoice *channeldb.Invoice,
|
||||
state channeldb.ContractState) {
|
||||
|
||||
event := &invoiceEvent{
|
||||
isSettle: settle,
|
||||
invoice: invoice,
|
||||
state: state,
|
||||
invoice: invoice,
|
||||
}
|
||||
|
||||
select {
|
||||
@ -483,9 +488,17 @@ func (i *invoiceRegistry) SubscribeNotifications(addIndex, settleIndex uint64) *
|
||||
case ntfn := <-client.ntfnQueue.ChanOut():
|
||||
invoiceEvent := ntfn.(*invoiceEvent)
|
||||
|
||||
targetChan := client.NewInvoices
|
||||
if invoiceEvent.isSettle {
|
||||
var targetChan chan *channeldb.Invoice
|
||||
switch invoiceEvent.state {
|
||||
case channeldb.ContractOpen:
|
||||
targetChan = client.NewInvoices
|
||||
case channeldb.ContractSettled:
|
||||
targetChan = client.SettledInvoices
|
||||
default:
|
||||
ltndLog.Errorf("unknown invoice "+
|
||||
"state: %v", invoiceEvent.state)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
|
1117
lnrpc/rpc.pb.go
1117
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -1699,7 +1699,7 @@ message Invoice {
|
||||
int64 value = 5 [json_name = "value"];
|
||||
|
||||
/// Whether this invoice has been fulfilled
|
||||
bool settled = 6 [json_name = "settled"];
|
||||
bool settled = 6 [json_name = "settled", deprecated = true];
|
||||
|
||||
/// When this invoice was created
|
||||
int64 creation_date = 7 [json_name = "creation_date"];
|
||||
@ -1777,7 +1777,18 @@ message Invoice {
|
||||
here as well.
|
||||
*/
|
||||
int64 amt_paid_msat = 20 [json_name = "amt_paid_msat"];
|
||||
|
||||
enum InvoiceState {
|
||||
OPEN = 0;
|
||||
SETTLED = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
The state the invoice is in.
|
||||
*/
|
||||
InvoiceState state = 21 [json_name = "state"];
|
||||
}
|
||||
|
||||
message AddInvoiceResponse {
|
||||
bytes r_hash = 1 [json_name = "r_hash"];
|
||||
|
||||
|
@ -1127,6 +1127,14 @@
|
||||
],
|
||||
"default": "COOPERATIVE_CLOSE"
|
||||
},
|
||||
"InvoiceInvoiceState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"OPEN",
|
||||
"SETTLED"
|
||||
],
|
||||
"default": "OPEN"
|
||||
},
|
||||
"PendingChannelsResponseClosedChannel": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2125,6 +2133,10 @@
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "*\nThe amount that was accepted for this invoice, in millisatoshis. This will\nONLY be set if this invoice has been settled. We provide this field as if\nthe invoice was created with a zero value, then we need to record what\namount was ultimately accepted. Additionally, it's possible that the sender\npaid MORE that was specified in the original invoice. So we'll record that\nhere as well."
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/InvoiceInvoiceState",
|
||||
"description": "*\nThe state the invoice is in."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
15
rpcserver.go
15
rpcserver.go
@ -3303,6 +3303,18 @@ func createRPCInvoice(invoice *channeldb.Invoice) (*lnrpc.Invoice, error) {
|
||||
satAmt := invoice.Terms.Value.ToSatoshis()
|
||||
satAmtPaid := invoice.AmtPaid.ToSatoshis()
|
||||
|
||||
isSettled := invoice.Terms.State == channeldb.ContractSettled
|
||||
|
||||
var state lnrpc.Invoice_InvoiceState
|
||||
switch invoice.Terms.State {
|
||||
case channeldb.ContractOpen:
|
||||
state = lnrpc.Invoice_OPEN
|
||||
case channeldb.ContractSettled:
|
||||
state = lnrpc.Invoice_SETTLED
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown invoice state")
|
||||
}
|
||||
|
||||
return &lnrpc.Invoice{
|
||||
Memo: string(invoice.Memo[:]),
|
||||
Receipt: invoice.Receipt[:],
|
||||
@ -3311,7 +3323,7 @@ func createRPCInvoice(invoice *channeldb.Invoice) (*lnrpc.Invoice, error) {
|
||||
Value: int64(satAmt),
|
||||
CreationDate: invoice.CreationDate.Unix(),
|
||||
SettleDate: settleDate,
|
||||
Settled: invoice.Terms.Settled,
|
||||
Settled: isSettled,
|
||||
PaymentRequest: paymentRequest,
|
||||
DescriptionHash: descHash,
|
||||
Expiry: expiry,
|
||||
@ -3324,6 +3336,7 @@ func createRPCInvoice(invoice *channeldb.Invoice) (*lnrpc.Invoice, error) {
|
||||
AmtPaidSat: int64(satAmtPaid),
|
||||
AmtPaidMsat: int64(invoice.AmtPaid),
|
||||
AmtPaid: int64(invoice.AmtPaid),
|
||||
State: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user