diff --git a/accounting/config.go b/accounting/config.go index e48827e..490a57c 100644 --- a/accounting/config.go +++ b/accounting/config.go @@ -9,6 +9,9 @@ import ( "github.com/lightningnetwork/lnd/routing/route" ) +// decodePaymentRequest is a signature for decoding payment requests. +type decodePaymentRequest func(payReq string) (*lndclient.PaymentRequest, error) + // OffChainConfig contains all the functionality required to produce an off // chain report. type OffChainConfig struct { @@ -21,6 +24,9 @@ type OffChainConfig struct { // ListForwards lists all our forwards over out relevant period. ListForwards func() ([]lndclient.ForwardingEvent, error) + // DecodePayReq decodes a payment request. + DecodePayReq decodePaymentRequest + // StartTime is the time from which the report should be created, // inclusive. StartTime time.Time @@ -119,6 +125,11 @@ func NewOffChainConfig(ctx context.Context, lnd lndclient.LndServices, lnd.Client, ) }, + DecodePayReq: func(payReq string) (*lndclient.PaymentRequest, + error) { + + return lnd.Client.DecodePaymentRequest(ctx, payReq) + }, OwnPubKey: ownPubkey, StartTime: startTime, EndTime: endTime, diff --git a/accounting/docs.md b/accounting/docs.md index a71e993..b82fe08 100644 --- a/accounting/docs.md +++ b/accounting/docs.md @@ -77,6 +77,7 @@ A payment is an on chain transaction which was paid from our wallet and was not Known Omissions: - This entry type will include the on chain resolution of htlcs when we force close on our peers and have to settle or fail them on chain. - The current accounting package does not support accounting for payments with duplicate payment hashes, which were allowed in previous versions of lnd. Duplicate payments should be deleted or a time range that does not include them should be specified. +- Legacy payments that were made in older versions of lnd that were created without a payment request will not have any information stored about their destination. We therefore cannot identify whether these are circular payments (they will be identified as regular payments). A warning will be logged when we encounter this type of payment. ### Fee A fee entry represents the on chain fees we paid for a transaction. diff --git a/accounting/entries.go b/accounting/entries.go index e2fc64c..fd6e4f2 100644 --- a/accounting/entries.go +++ b/accounting/entries.go @@ -281,7 +281,7 @@ func paymentFeeNote(htlcs []*lnrpc.HTLCAttempt) string { // paymentEntry creates an entry for an off chain payment, including fee entries // where required. -func paymentEntry(payment settledPayment, paidToSelf bool, +func paymentEntry(payment paymentInfo, paidToSelf bool, convert msatToFiat) ([]*HarmonyEntry, error) { // It is possible to make a payment to ourselves as part of a circular diff --git a/accounting/entries_test.go b/accounting/entries_test.go index ff3b6c3..9dae5cc 100644 --- a/accounting/entries_test.go +++ b/accounting/entries_test.go @@ -133,9 +133,10 @@ var ( SequenceNumber: uint64(paymentIndex), } - settledPmt = settledPayment{ - Payment: payment, - settleTime: paymentTime, + payInfo = paymentInfo{ + Payment: payment, + destination: &otherPubkey, + settleTime: paymentTime, } forwardTs = time.Unix(1590578022, 0) @@ -613,7 +614,7 @@ func TestPaymentEntry(t *testing.T) { t.Run(test.name, func(t *testing.T) { entries, err := paymentEntry( - settledPmt, test.toSelf, mockConvert, + payInfo, test.toSelf, mockConvert, ) if err != nil { t.Fatalf("unexpected error: %v", err) diff --git a/accounting/filter.go b/accounting/filter.go index d76f8a7..8b69ff2 100644 --- a/accounting/filter.go +++ b/accounting/filter.go @@ -6,6 +6,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/routing/route" ) // inRange returns a boolean that indicates whether a timestamp lies in a @@ -77,29 +78,56 @@ func filterInvoices(startTime, endTime time.Time, return filtered } -// settledPayment contains a payment and the timestamp of the latest settled -// htlc. Payments do not have a settle time, so we have to get our settle time -// from examining each htlc. -type settledPayment struct { +// paymentInfo wraps a lndclient payment struct with a destination, if it is +// available from the information we have available, and its settle time. +// Since we now allow multi-path payments, a single payment may have multiple +// htlcs resolved over a period of time. We use the most recent settle time for +// payment because payments are not considered settled until all the htlcs are +// resolved. +type paymentInfo struct { lndclient.Payment - settleTime time.Time + destination *route.Vertex + settleTime time.Time } -// filterPayments filters out unsuccessful payments and those which did not -// occur within the range we specify. Since we now allow multi-path payments, -// a single payment may have multiple htlcs resolved over a period of time. -// We use the most recent settle time for payment to classify whether the -// payment occurred within our desired time range, because payments are not -// considered settled until all the htlcs are resolved. -func filterPayments(startTime, endTime time.Time, - payments []lndclient.Payment) []settledPayment { +// preProcessPayments takes a list of payments and gets their destination and +// settled time from their htlcs and payment request. We use the last hop in our +// stored payment attempt to identify payments to our own nodes because this +// information is readily available for the most recent payments db migration, +// and we do not need to query lnd to decode payment requests. Further, payment +// requests are not stored for keysend payments and payments that were created +// by directly specifying the amount and destination. We fallback to payment +// requests in the case where we do not have htlcs present. +func preProcessPayments(payments []lndclient.Payment, + decode decodePaymentRequest) ([]paymentInfo, error) { + + paymentList := make([]paymentInfo, len(payments)) + + for i, payment := range payments { + // Try to get our payment destination from our set of htlcs. + // If we cannot get it from our htlcs (which is the case for + // legacy payments that did not store htlcs), we try to get it + // from our payment request. This value may not be present for + // all payments, so we do not error if it is not. + destination, err := paymentHtlcDestination(payment) + if err != nil { + destination, err = paymentRequestDestination( + payment.PaymentRequest, decode, + ) + if err != nil && err != errNoPaymentRequest { + return nil, err + } + } - // nolint: prealloc - var filtered []settledPayment + pmt := paymentInfo{ + Payment: payment, + destination: destination, + } - for _, payment := range payments { - // If the payment did not succeed, we can skip it. + // If the payment did not succeed, we can add it to our list + // with its current zero settle time and continue. if payment.Status.State != lnrpc.Payment_SUCCEEDED { + paymentList[i] = pmt continue } @@ -117,20 +145,81 @@ func filterPayments(startTime, endTime time.Time, latestTimeNs = htlc.ResolveTimeNs } } + pmt.settleTime = time.Unix(0, latestTimeNs) + paymentList[i] = pmt + } + + return paymentList, nil +} + +// paymentHtlcDestination examines the htlcs in a payment to determine whether a +// payment was made to our own node. +func paymentHtlcDestination(payment lndclient.Payment) (*route.Vertex, error) { + // If our payment has no htlcs and it is settled, it is either a legacy + // payment that does not have its htlcs stored, or it is currently in + // flight and no htlcs have been dispatched. We return an error because + // we cannot get our destination with the information available. + if len(payment.Htlcs) == 0 { + return nil, errNoHtlcs + } + + // Since all htlcs go to the same node, we only need to get the + // destination of our first htlc to determine whether it's our own node. + // We expect the route this htlc took to have at least one hop, and fail + // if it does not. + hops := payment.Htlcs[0].Route.Hops + if len(hops) == 0 { + return nil, errNoHops + } + + lastHop := hops[len(hops)-1] + lastHopPubkey, err := route.NewVertexFromStr(lastHop.PubKey) + if err != nil { + return nil, err + } + + return &lastHopPubkey, nil +} + +// paymentRequestDestination attempts to decode a payment address, and returns +// the destination. +func paymentRequestDestination(paymentRequest string, + decode decodePaymentRequest) (*route.Vertex, error) { + + if paymentRequest == "" { + return nil, errNoPaymentRequest + } + + payReq, err := decode(paymentRequest) + if err != nil { + return nil, err + } + + return &payReq.Destination, nil +} + +// filterPayments filters out unsuccessful payments and those which did not +// occur within the range we specify. +func filterPayments(startTime, endTime time.Time, + payments []paymentInfo) []paymentInfo { + + // nolint: prealloc + var filtered []paymentInfo + + for _, payment := range payments { + if payment.Status.State != lnrpc.Payment_SUCCEEDED { + continue + } // Skip the payment if the oldest settle time is not within the // range we are looking at. - ts := time.Unix(0, latestTimeNs) - if !inRange(ts, startTime, endTime) { + if !inRange(payment.settleTime, startTime, endTime) { continue } // Add a settled payment to our set of settled payments with its // timestamp. - filtered = append(filtered, settledPayment{ - Payment: payment, - settleTime: ts, - }) + filtered = append(filtered, payment) } return filtered diff --git a/accounting/filter_test.go b/accounting/filter_test.go index 4f22040..6d06a85 100644 --- a/accounting/filter_test.go +++ b/accounting/filter_test.go @@ -7,6 +7,7 @@ import ( "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -142,6 +143,76 @@ func TestFilterInvoices(t *testing.T) { // TestFilterPayments tests filtering of payments based on their htlc // timestamps. func TestFilterPayments(t *testing.T) { + now := time.Now() + + startTime := now.Add(time.Hour * -2) + endTime := now.Add(time.Hour * 2) + + beforeStart := startTime.Add(time.Hour * -1) + inRange := startTime.Add(time.Hour) + afterEnd := endTime.Add(time.Hour) + + settledInRange := paymentInfo{ + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_SUCCEEDED, + }, + }, + settleTime: inRange, + } + + payments := []paymentInfo{ + settledInRange, + { + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_SUCCEEDED, + }, + }, + settleTime: beforeStart, + }, + { + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_SUCCEEDED, + }, + }, + settleTime: afterEnd, + }, + { + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_IN_FLIGHT, + }, + }, + settleTime: beforeStart, + }, + { + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_FAILED, + }, + }, + settleTime: inRange, + }, + { + Payment: lndclient.Payment{ + Status: &lndclient.PaymentStatus{ + State: lnrpc.Payment_FAILED, + }, + }, + settleTime: afterEnd, + }, + } + expected := []paymentInfo{settledInRange} + + filtered := filterPayments(startTime, endTime, payments) + require.Equal(t, expected, filtered) +} + +// TestPreProcessPayments tests getting of destinations and settle timestamps +// for payments. +func TestPreProcessPayments(t *testing.T) { // Fix current time for testing. now := time.Now() @@ -162,6 +233,7 @@ func TestFilterPayments(t *testing.T) { { Status: lnrpc.HTLCAttempt_FAILED, ResolveTimeNs: inRange.UnixNano(), + Route: routeToUs, }, { Status: lnrpc.HTLCAttempt_SUCCEEDED, @@ -180,6 +252,7 @@ func TestFilterPayments(t *testing.T) { { Status: lnrpc.HTLCAttempt_FAILED, ResolveTimeNs: beforeStart.UnixNano(), + Route: routeToUs, }, { Status: lnrpc.HTLCAttempt_SUCCEEDED, @@ -198,6 +271,7 @@ func TestFilterPayments(t *testing.T) { { Status: lnrpc.HTLCAttempt_SUCCEEDED, ResolveTimeNs: inRange.UnixNano(), + Route: routeToOther, }, { Status: lnrpc.HTLCAttempt_SUCCEEDED, @@ -215,6 +289,7 @@ func TestFilterPayments(t *testing.T) { { Status: lnrpc.HTLCAttempt_SUCCEEDED, ResolveTimeNs: inRange.UnixNano(), + Route: routeToUs, }, }, } @@ -226,18 +301,155 @@ func TestFilterPayments(t *testing.T) { inFlight, } - filtered := filterPayments(startTime, endTime, payments) + processed, err := preProcessPayments(payments, decode(true)) + require.NoError(t, err) // We only expect the payment that had its last successful htlc in the // relevant period to be included. Some rounding occurs when we go // from the rpc payment unix nanoseconds to a golang time struct, so // we round our settle time so that the two will be equal. - expected := []settledPayment{ + expected := []paymentInfo{ + { + Payment: succeededInPeriod, + destination: &ourPubKey, + settleTime: time.Unix(0, inRange.UnixNano()), + }, { - Payment: succeededInPeriod, - settleTime: time.Unix(0, inRange.UnixNano()), + Payment: succeededAfterPeriod, + destination: &ourPubKey, + settleTime: time.Unix(0, afterEnd.UnixNano()), + }, + { + Payment: succeededInAndAfterPeriod, + destination: &otherPubkey, + settleTime: time.Unix(0, afterEnd.UnixNano()), + }, + { + Payment: inFlight, + destination: &ourPubKey, + settleTime: time.Time{}, }, } - require.Equal(t, filtered, expected) + require.Equal(t, processed, expected) +} + +// Decode is a helper function which returns a decode function which will +// provide our own pubkey if toSelf is true, and another pubkey otherwise. +func decode(toSelf bool) func(_ string) (*lndclient.PaymentRequest, + error) { + + return func(_ string) (*lndclient.PaymentRequest, error) { + pubkey := ourPubKey + if !toSelf { + pubkey = otherPubkey + } + + return &lndclient.PaymentRequest{ + Destination: pubkey, + }, nil + } +} + +// TestPaymentHtlcDestination tests getting our payment destination from the +// payment's set of htlcs. +func TestPaymentHtlcDestination(t *testing.T) { + tests := []struct { + name string + dest *route.Vertex + htlcs []*lnrpc.HTLCAttempt + err error + }{ + { + name: "no htlcs", + htlcs: nil, + dest: nil, + err: errNoHtlcs, + }, + { + name: "route to us", + htlcs: []*lnrpc.HTLCAttempt{ + { + Route: routeToUs, + }, + }, + dest: &ourPubKey, + err: nil, + }, + { + name: "route not to us", + htlcs: []*lnrpc.HTLCAttempt{ + { + Route: routeToOther, + }, + }, + dest: &otherPubkey, + err: nil, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + // Create a payment with our test state and htlcs. + payment := lndclient.Payment{ + Htlcs: test.htlcs, + } + + dest, err := paymentHtlcDestination(payment) + require.Equal(t, test.err, err) + require.Equal(t, test.dest, dest) + }) + } +} + +// TestPaymentRequestDestination tests getting of payment destinations from our +// payment request. +func TestPaymentRequestDestination(t *testing.T) { + tests := []struct { + name string + paymentRequest string + decode decodePaymentRequest + dest *route.Vertex + err error + }{ + { + name: "no payment request", + decode: decode(true), + paymentRequest: "", + dest: nil, + err: errNoPaymentRequest, + }, + { + name: "to self", + decode: decode(true), + paymentRequest: paymentRequest, + dest: &ourPubKey, + err: nil, + }, + { + name: "not to self", + decode: decode(false), + paymentRequest: paymentRequest, + dest: &otherPubkey, + err: nil, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + dest, err := paymentRequestDestination( + test.paymentRequest, test.decode, + ) + require.Equal(t, test.err, err) + require.Equal(t, test.dest, dest) + }) + } } diff --git a/accounting/log.go b/accounting/log.go new file mode 100644 index 0000000..3457345 --- /dev/null +++ b/accounting/log.go @@ -0,0 +1,26 @@ +package accounting + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "ACNT" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the +// caller requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + UseLogger(build.NewSubLogger(Subsystem, nil)) +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/accounting/off_chain.go b/accounting/off_chain.go index 5e19774..0537faf 100644 --- a/accounting/off_chain.go +++ b/accounting/off_chain.go @@ -15,6 +15,14 @@ var ( // hops in its route. errNoHops = errors.New("payment htlc has a route with zero hops") + // errNoHtlcs is returned when we encounter a legacy payment which is + // settled but has no htlcs recorded with it. + errNoHtlcs = errors.New("settled payment has no htlcs") + + // errNoPaymentRequest is returned when a payment does not have a + // payment request. + errNoPaymentRequest = errors.New("no payment request present") + // errDifferentDuplicates is returned if we have payments with duplicate // payment hashes where one is made to our own node and one is made to // another node. This is unexpected because legacy duplicate payments in @@ -62,13 +70,20 @@ func offChainReportWithPrices(cfg *OffChainConfig, getPrice msatToFiat) (Report, return nil, err } + preProcessed, err := preProcessPayments(payments, cfg.DecodePayReq) + if err != nil { + return nil, err + } + // Get a list of all the payments we made to ourselves. - paymentsToSelf, err := getCircularPayments(cfg.OwnPubKey, payments) + paymentsToSelf, err := getCircularPayments(cfg.OwnPubKey, preProcessed) if err != nil { return nil, err } - filteredPayments := filterPayments(cfg.StartTime, cfg.EndTime, payments) + filteredPayments := filterPayments( + cfg.StartTime, cfg.EndTime, preProcessed, + ) if err := sanityCheckDuplicates(filteredPayments); err != nil { return nil, err } @@ -92,7 +107,7 @@ func offChainReportWithPrices(cfg *OffChainConfig, getPrice msatToFiat) (Report, // that were made to ourselves for the sake of appropriately reporting the // invoices they paid. -func offChainReport(invoices []lndclient.Invoice, payments []settledPayment, +func offChainReport(invoices []lndclient.Invoice, payments []paymentInfo, circularPayments map[string]bool, forwards []lndclient.ForwardingEvent, convert msatToFiat) (Report, error) { @@ -137,11 +152,13 @@ func offChainReport(invoices []lndclient.Invoice, payments []settledPayment, } // getCircularPayments returns a map of the payments that we made to our node. -// Note that this function does only account for settled payments because it +// Note that this function does not only account for settled payments because it // is possible that we made a payment to ourselves, settled the invoice and -// queried listPayments while the payment was still being settled back. We -// rather examine their htlcs, since we will check whether they are settled in -// our relevant period at a later stage. +// queried listPayments while the payment was still being settled back. If a +// payment does not have a record of its payment hash (which is the case for +// legacy payments, and payments that have just been dispatched), we log a +// warning but do not fail so that legacy nodes can still use this feature (they +// may just not detect old circular rebalances, which we document). // // To allow for legacy nodes that have payments with duplicate payment hashes, // we allow for payments with duplicate payment hashes. We only fail if we @@ -150,7 +167,7 @@ func offChainReport(invoices []lndclient.Invoice, payments []settledPayment, // the payments (resulting in bugs) and is not expected, because duplicate // payments are expected to reflect multiple attempts of the same payment. func getCircularPayments(ourPubkey route.Vertex, - payments []lndclient.Payment) (map[string]bool, error) { + payments []paymentInfo) (map[string]bool, error) { // Run through all payments and get those that were made to our own // node. We identify these payments by payment hash so that we can @@ -158,31 +175,21 @@ func getCircularPayments(ourPubkey route.Vertex, paymentsToSelf := make(map[string]bool) for _, payment := range payments { - // If our payment has no htlc attempts, it has not yet been sent - // our by our node. This payment therefore cannot be a payment - // to ourselves within this accounting period; if we are paying - // a regular invoice, it will not be settled yet, and if we are - // making a keysend, the invoice will not exist in our node yet. - if len(payment.Htlcs) == 0 { - continue - } - - // Since all htlcs go to the same node, we only need to get the - // destination of our first htlc to determine whether it's our - // own node. We expect the route this htlc took to have at least - // one hop, and fail if it does not. - hops := payment.Htlcs[0].Route.Hops - if len(hops) == 0 { - return nil, errNoHops - } + // Try to determine whether a payment is made to our own + // node by checking its htlcs. If we cannot get this information + // from our set of htlcs, we fallback to trying to decode our + // payment request. If the payment request is not present as + // well, we skip over this payment and log a warning (because + // legacy nodes will always have these payment present). + if payment.destination == nil { + log.Warnf("payment %v destination unknown", + payment.Hash) - lastHop := hops[len(hops)-1] - lastHopPubkey, err := route.NewVertexFromStr(lastHop.PubKey) - if err != nil { - return nil, err + continue } - toSelf := bytes.Equal(lastHopPubkey[:], ourPubkey[:]) + // Check whether the payment is made to our own node. + toSelf := bytes.Equal(ourPubkey[:], payment.destination[:]) // Before we add our entry to the map, we sanity check that if // it has any duplicates, the value in the map is the same as @@ -202,7 +209,7 @@ func getCircularPayments(ourPubkey route.Vertex, // sanityCheckDuplicates checks that we have no payments with duplicate payment // hashes. We do not support accounting for duplicate payments. -func sanityCheckDuplicates(payments []settledPayment) error { +func sanityCheckDuplicates(payments []paymentInfo) error { uniqueHashes := make(map[lntypes.Hash]bool, len(payments)) for _, payment := range payments { diff --git a/accounting/off_chain_test.go b/accounting/off_chain_test.go index 8c93f46..804838c 100644 --- a/accounting/off_chain_test.go +++ b/accounting/off_chain_test.go @@ -15,46 +15,47 @@ var ( ourPK = "03abfbad2e4387e73175949ba8b8d42e1101f4a21a73567da12b730a05db8a4f15" ourPubKey, _ = route.NewVertexFromStr(ourPK) - otherPK = "0349f7019b9c48bc456f011d17538a242f763bbc5759362f200854154113318727" + otherPK = "0349f7019b9c48bc456f011d17538a242f763bbc5759362f200854154113318727" + otherPubkey, _ = route.NewVertexFromStr(otherPK) paymentHash1 = "673507764b0ad03443d07e7446b884d6d908aa783ee5e2704fbabc09ada79a79" hash1, _ = lntypes.MakeHashFromStr(paymentHash1) paymentHash2 = "a5530c5930b9eb7ea4284bcff39da52c6bca3103fc790749eb632911edc7143b" hash2, _ = lntypes.MakeHashFromStr(paymentHash2) -) -// TestGetCircularPayments tests detection of payments that are made to -// ourselves based on the destination pubkey in the payment's htlc attempts. -func TestGetCircularPayments(t *testing.T) { - hopToUs := &lnrpc.Hop{ + hopToUs = &lnrpc.Hop{ PubKey: ourPK, } - hopToOther := &lnrpc.Hop{ + hopToOther = &lnrpc.Hop{ PubKey: otherPK, } - routeToUs := &lnrpc.Route{ + routeToUs = &lnrpc.Route{ Hops: []*lnrpc.Hop{ hopToOther, hopToUs, }, } - routeToOther := &lnrpc.Route{ + routeToOther = &lnrpc.Route{ Hops: []*lnrpc.Hop{ hopToUs, hopToOther, }, } +) +// TestGetCircularPayments tests detection of payments that are made to +// ourselves based on the destination pubkey in the payment's htlc attempts. +func TestGetCircularPayments(t *testing.T) { tests := []struct { name string // Payments is the set of payments that we examine for circular // payments. - payments []lndclient.Payment + payments []paymentInfo // circular is the set of circular payments we expect to be // returned. @@ -64,45 +65,28 @@ func TestGetCircularPayments(t *testing.T) { err error }{ { - // This test case is added to cover a race where we - // have just initiated a payment in lnd and do not - // have any htlcs in flight. This payment cannot have - // succeeded yet, so it is not relevant to our - // accounting period. - name: "Payment has no htlcs", - payments: []lndclient.Payment{ + // Test the case where we have a legacy payment that + // we do not know the destination for. + name: "legacy payment, no destination", + payments: []paymentInfo{ { - Hash: hash1, + destination: nil, }, }, circular: make(map[string]bool), err: nil, }, { - name: "Route has no hops", - payments: []lndclient.Payment{ - { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: &lnrpc.Route{}, - }, - }, - }, - }, - circular: nil, - err: errNoHops, - }, - { - name: "Last Hop to Us", - payments: []lndclient.Payment{ + // Test the case where we have a settled legacy payment + // with a payment request to ourselves, which allows us + // to identify it as circular. + name: "legacy, has payment request", + payments: []paymentInfo{ { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToUs, - }, + Payment: lndclient.Payment{ + Hash: hash1, }, + destination: &ourPubKey, }, }, circular: map[string]bool{ @@ -111,38 +95,19 @@ func TestGetCircularPayments(t *testing.T) { err: nil, }, { - name: "Last Hop not to Us", - payments: []lndclient.Payment{ + name: "duplicates both to us", + payments: []paymentInfo{ { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToOther, - }, + Payment: lndclient.Payment{ + Hash: hash1, }, + destination: &ourPubKey, }, - }, - circular: make(map[string]bool), - err: nil, - }, - { - name: "Duplicates both to us", - payments: []lndclient.Payment{ { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToUs, - }, - }, - }, - { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToUs, - }, + Payment: lndclient.Payment{ + Hash: hash1, }, + destination: &ourPubKey, }, }, circular: map[string]bool{ @@ -151,23 +116,19 @@ func TestGetCircularPayments(t *testing.T) { err: nil, }, { - name: "Duplicates not both to us", - payments: []lndclient.Payment{ + name: "duplicates not both to us", + payments: []paymentInfo{ { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToUs, - }, + Payment: lndclient.Payment{ + Hash: hash1, }, + destination: &ourPubKey, }, { - Hash: hash1, - Htlcs: []*lnrpc.HTLCAttempt{ - { - Route: routeToOther, - }, + Payment: lndclient.Payment{ + Hash: hash1, }, + destination: &otherPubkey, }, }, circular: nil, diff --git a/log.go b/log.go index beef4f4..ea04802 100644 --- a/log.go +++ b/log.go @@ -2,6 +2,7 @@ package faraday import ( "github.com/btcsuite/btclog" + "github.com/lightninglabs/faraday/accounting" "github.com/lightninglabs/faraday/dataset" "github.com/lightninglabs/faraday/fiat" "github.com/lightninglabs/faraday/frdrpc" @@ -30,6 +31,7 @@ func init() { addSubLogger(frdrpc.Subsystem, frdrpc.UseLogger) addSubLogger(revenue.Subsystem, revenue.UseLogger) addSubLogger(fiat.Subsystem, fiat.UseLogger) + addSubLogger(accounting.Subsystem, accounting.UseLogger) } // UseLogger uses a specified Logger to output package logging info.