invoices: use lntypes.Hash and lntypes.Preimage
Previously chainhash.Hash was used, which converts to/from string in reversed format. Payment hashes and preimages are supposed to be non-reversed.
This commit is contained in:
parent
18698663c5
commit
bacd92418a
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,7 +106,7 @@ type ContractTerm struct {
|
|||||||
// PaymentPreimage is the preimage which is to be revealed in the
|
// PaymentPreimage is the preimage which is to be revealed in the
|
||||||
// occasion that an HTLC paying to the hash of this preimage is
|
// occasion that an HTLC paying to the hash of this preimage is
|
||||||
// extended.
|
// extended.
|
||||||
PaymentPreimage [32]byte
|
PaymentPreimage lntypes.Preimage
|
||||||
|
|
||||||
// Value is the expected amount of milli-satoshis to be paid to an HTLC
|
// Value is the expected amount of milli-satoshis to be paid to an HTLC
|
||||||
// which can be satisfied by the above preimage.
|
// which can be satisfied by the above preimage.
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/sweep"
|
"github.com/lightningnetwork/lnd/sweep"
|
||||||
@ -139,7 +140,7 @@ type ChainArbitratorConfig struct {
|
|||||||
// SettleInvoice attempts to settle an existing invoice on-chain with
|
// SettleInvoice attempts to settle an existing invoice on-chain with
|
||||||
// the given payment hash. ErrInvoiceNotFound is returned if an invoice
|
// the given payment hash. ErrInvoiceNotFound is returned if an invoice
|
||||||
// is not found.
|
// is not found.
|
||||||
SettleInvoice func(chainhash.Hash, lnwire.MilliSatoshi) error
|
SettleInvoice func(lntypes.Hash, lnwire.MilliSatoshi) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
@ -176,7 +177,7 @@ func createTestChannelArbitrator(log ArbitratorLog) (*ChannelArbitrator,
|
|||||||
*lnwallet.IncomingHtlcResolution, uint32) error {
|
*lnwallet.IncomingHtlcResolution, uint32) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
SettleInvoice: func(chainhash.Hash, lnwire.MilliSatoshi) error {
|
SettleInvoice: func(lntypes.Hash, lnwire.MilliSatoshi) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package htlcswitch
|
package htlcswitch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,11 +14,11 @@ type InvoiceDatabase interface {
|
|||||||
// byte payment hash. This method should also reutrn the min final CLTV
|
// byte payment hash. This method should also reutrn the min final CLTV
|
||||||
// delta for this invoice. We'll use this to ensure that the HTLC
|
// delta for this invoice. We'll use this to ensure that the HTLC
|
||||||
// extended to us gives us enough time to settle as we prescribe.
|
// extended to us gives us enough time to settle as we prescribe.
|
||||||
LookupInvoice(chainhash.Hash) (channeldb.Invoice, uint32, error)
|
LookupInvoice(lntypes.Hash) (channeldb.Invoice, uint32, error)
|
||||||
|
|
||||||
// SettleInvoice attempts to mark an invoice corresponding to the
|
// SettleInvoice attempts to mark an invoice corresponding to the
|
||||||
// passed payment hash as fully settled.
|
// passed payment hash as fully settled.
|
||||||
SettleInvoice(payHash chainhash.Hash, paidAmount lnwire.MilliSatoshi) error
|
SettleInvoice(payHash lntypes.Hash, paidAmount lnwire.MilliSatoshi) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelLink is an interface which represents the subsystem for managing the
|
// ChannelLink is an interface which represents the subsystem for managing the
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -17,6 +16,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
@ -2312,7 +2312,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||||||
// We're the designated payment destination. Therefore
|
// We're the designated payment destination. Therefore
|
||||||
// we attempt to see if we have an invoice locally
|
// we attempt to see if we have an invoice locally
|
||||||
// which'll allow us to settle this htlc.
|
// which'll allow us to settle this htlc.
|
||||||
invoiceHash := chainhash.Hash(pd.RHash)
|
invoiceHash := lntypes.Hash(pd.RHash)
|
||||||
invoice, minCltvDelta, err := l.cfg.Registry.LookupInvoice(
|
invoice, minCltvDelta, err := l.cfg.Registry.LookupInvoice(
|
||||||
invoiceHash,
|
invoiceHash,
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/fastsha256"
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lightning-onion"
|
"github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
@ -25,6 +24,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
@ -686,18 +686,18 @@ var _ ChannelLink = (*mockChannelLink)(nil)
|
|||||||
type mockInvoiceRegistry struct {
|
type mockInvoiceRegistry struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
invoices map[chainhash.Hash]channeldb.Invoice
|
invoices map[lntypes.Hash]channeldb.Invoice
|
||||||
finalDelta uint32
|
finalDelta uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
|
func newMockRegistry(minDelta uint32) *mockInvoiceRegistry {
|
||||||
return &mockInvoiceRegistry{
|
return &mockInvoiceRegistry{
|
||||||
finalDelta: minDelta,
|
finalDelta: minDelta,
|
||||||
invoices: make(map[chainhash.Hash]channeldb.Invoice),
|
invoices: make(map[lntypes.Hash]channeldb.Invoice),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *mockInvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice, uint32, error) {
|
func (i *mockInvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) {
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
|
|
||||||
@ -710,7 +710,7 @@ func (i *mockInvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Inv
|
|||||||
return invoice, i.finalDelta, nil
|
return invoice, i.finalDelta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash,
|
func (i *mockInvoiceRegistry) SettleInvoice(rhash lntypes.Hash,
|
||||||
amt lnwire.MilliSatoshi) error {
|
amt lnwire.MilliSatoshi) error {
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
@ -736,8 +736,8 @@ func (i *mockInvoiceRegistry) AddInvoice(invoice channeldb.Invoice) error {
|
|||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
|
|
||||||
rhash := fastsha256.Sum256(invoice.Terms.PaymentPreimage[:])
|
rhash := invoice.Terms.PaymentPreimage.Hash()
|
||||||
i.invoices[chainhash.Hash(rhash)] = invoice
|
i.invoices[rhash] = invoice
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/shachain"
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
@ -651,11 +652,11 @@ func generateHops(payAmt lnwire.MilliSatoshi, startingHeight uint32,
|
|||||||
}
|
}
|
||||||
|
|
||||||
type paymentResponse struct {
|
type paymentResponse struct {
|
||||||
rhash chainhash.Hash
|
rhash lntypes.Hash
|
||||||
err chan error
|
err chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *paymentResponse) Wait(d time.Duration) (chainhash.Hash, error) {
|
func (r *paymentResponse) Wait(d time.Duration) (lntypes.Hash, error) {
|
||||||
select {
|
select {
|
||||||
case err := <-r.err:
|
case err := <-r.err:
|
||||||
close(r.err)
|
close(r.err)
|
||||||
@ -680,7 +681,7 @@ func (n *threeHopNetwork) makePayment(sendingPeer, receivingPeer lnpeer.Peer,
|
|||||||
|
|
||||||
paymentErr := make(chan error, 1)
|
paymentErr := make(chan error, 1)
|
||||||
|
|
||||||
var rhash chainhash.Hash
|
var rhash lntypes.Hash
|
||||||
|
|
||||||
sender := sendingPeer.(*mockServer)
|
sender := sendingPeer.(*mockServer)
|
||||||
receiver := receivingPeer.(*mockServer)
|
receiver := receivingPeer.(*mockServer)
|
||||||
|
@ -2,17 +2,16 @@ package invoices
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/queue"
|
"github.com/lightningnetwork/lnd/queue"
|
||||||
"github.com/lightningnetwork/lnd/zpay32"
|
"github.com/lightningnetwork/lnd/zpay32"
|
||||||
@ -24,10 +23,10 @@ var (
|
|||||||
// All nodes initialized with the flag active will immediately settle
|
// All nodes initialized with the flag active will immediately settle
|
||||||
// any incoming HTLC whose rHash corresponds with the debug
|
// any incoming HTLC whose rHash corresponds with the debug
|
||||||
// preimage.
|
// preimage.
|
||||||
DebugPre, _ = chainhash.NewHash(bytes.Repeat([]byte{1}, 32))
|
DebugPre, _ = lntypes.NewPreimage(bytes.Repeat([]byte{1}, 32))
|
||||||
|
|
||||||
// DebugHash is the hash of the default preimage.
|
// DebugHash is the hash of the default preimage.
|
||||||
DebugHash = chainhash.Hash(sha256.Sum256(DebugPre[:]))
|
DebugHash = DebugPre.Hash()
|
||||||
)
|
)
|
||||||
|
|
||||||
// InvoiceRegistry is a central registry of all the outstanding invoices
|
// InvoiceRegistry is a central registry of all the outstanding invoices
|
||||||
@ -49,7 +48,7 @@ type InvoiceRegistry struct {
|
|||||||
// debugInvoices is a map which stores special "debug" invoices which
|
// debugInvoices is a map which stores special "debug" invoices which
|
||||||
// should be only created/used when manual tests require an invoice
|
// should be only created/used when manual tests require an invoice
|
||||||
// that *all* nodes are able to fully settle.
|
// that *all* nodes are able to fully settle.
|
||||||
debugInvoices map[chainhash.Hash]*channeldb.Invoice
|
debugInvoices map[lntypes.Hash]*channeldb.Invoice
|
||||||
|
|
||||||
activeNetParams *chaincfg.Params
|
activeNetParams *chaincfg.Params
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ func NewRegistry(cdb *channeldb.DB,
|
|||||||
|
|
||||||
return &InvoiceRegistry{
|
return &InvoiceRegistry{
|
||||||
cdb: cdb,
|
cdb: cdb,
|
||||||
debugInvoices: make(map[chainhash.Hash]*channeldb.Invoice),
|
debugInvoices: make(map[lntypes.Hash]*channeldb.Invoice),
|
||||||
notificationClients: make(map[uint32]*InvoiceSubscription),
|
notificationClients: make(map[uint32]*InvoiceSubscription),
|
||||||
newSubscriptions: make(chan *InvoiceSubscription),
|
newSubscriptions: make(chan *InvoiceSubscription),
|
||||||
subscriptionCancels: make(chan uint32),
|
subscriptionCancels: make(chan uint32),
|
||||||
@ -264,8 +263,10 @@ func (i *InvoiceRegistry) deliverBacklogEvents(client *InvoiceSubscription) erro
|
|||||||
// by the passed preimage. Once this invoice is added, subsystems within the
|
// by the passed preimage. Once this invoice is added, subsystems within the
|
||||||
// daemon add/forward HTLCs that are able to obtain the proper preimage
|
// daemon add/forward HTLCs that are able to obtain the proper preimage
|
||||||
// required for redemption in the case that we're the final destination.
|
// required for redemption in the case that we're the final destination.
|
||||||
func (i *InvoiceRegistry) AddDebugInvoice(amt btcutil.Amount, preimage chainhash.Hash) {
|
func (i *InvoiceRegistry) AddDebugInvoice(amt btcutil.Amount,
|
||||||
paymentHash := chainhash.Hash(sha256.Sum256(preimage[:]))
|
preimage lntypes.Preimage) {
|
||||||
|
|
||||||
|
paymentHash := preimage.Hash()
|
||||||
|
|
||||||
invoice := &channeldb.Invoice{
|
invoice := &channeldb.Invoice{
|
||||||
CreationDate: time.Now(),
|
CreationDate: time.Now(),
|
||||||
@ -318,7 +319,7 @@ func (i *InvoiceRegistry) AddInvoice(invoice *channeldb.Invoice) (uint64, error)
|
|||||||
// according to the cltv delta.
|
// according to the cltv delta.
|
||||||
//
|
//
|
||||||
// TODO(roasbeef): ignore if settled?
|
// TODO(roasbeef): ignore if settled?
|
||||||
func (i *InvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice, uint32, error) {
|
func (i *InvoiceRegistry) LookupInvoice(rHash lntypes.Hash) (channeldb.Invoice, uint32, error) {
|
||||||
// First check the in-memory debug invoice index to see if this is an
|
// First check the in-memory debug invoice index to see if this is an
|
||||||
// existing invoice added for debugging.
|
// existing invoice added for debugging.
|
||||||
i.RLock()
|
i.RLock()
|
||||||
@ -350,7 +351,7 @@ func (i *InvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (channeldb.Invoice
|
|||||||
// SettleInvoice attempts to mark an invoice as settled. If the invoice is a
|
// SettleInvoice attempts to mark an invoice as settled. If the invoice is a
|
||||||
// debug invoice, then this method is a noop as debug invoices are never fully
|
// debug invoice, then this method is a noop as debug invoices are never fully
|
||||||
// settled.
|
// settled.
|
||||||
func (i *InvoiceRegistry) SettleInvoice(rHash chainhash.Hash,
|
func (i *InvoiceRegistry) SettleInvoice(rHash lntypes.Hash,
|
||||||
amtPaid lnwire.MilliSatoshi) error {
|
amtPaid lnwire.MilliSatoshi) error {
|
||||||
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
|
@ -3,10 +3,10 @@ package main
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func (p *preimageBeacon) LookupPreimage(payHash []byte) ([]byte, bool) {
|
|||||||
|
|
||||||
// First, we'll check the invoice registry to see if we already know of
|
// First, we'll check the invoice registry to see if we already know of
|
||||||
// the preimage as it's on that we created ourselves.
|
// the preimage as it's on that we created ourselves.
|
||||||
var invoiceKey chainhash.Hash
|
var invoiceKey lntypes.Hash
|
||||||
copy(invoiceKey[:], payHash)
|
copy(invoiceKey[:], payHash)
|
||||||
invoice, _, err := p.invoices.LookupInvoice(invoiceKey)
|
invoice, _, err := p.invoices.LookupInvoice(invoiceKey)
|
||||||
switch {
|
switch {
|
||||||
|
Loading…
Reference in New Issue
Block a user