invoicerpc: Allow user specify routing hints.
This commit is contained in:
parent
1c96cc7e10
commit
4463a4ce17
@ -94,6 +94,10 @@ type AddInvoiceData struct {
|
|||||||
// HodlInvoice signals that this invoice shouldn't be settled
|
// HodlInvoice signals that this invoice shouldn't be settled
|
||||||
// immediately upon receiving the payment.
|
// immediately upon receiving the payment.
|
||||||
HodlInvoice bool
|
HodlInvoice bool
|
||||||
|
|
||||||
|
// RouteHints are optional route hints that can each be individually used
|
||||||
|
// to assist in reaching the invoice's destination.
|
||||||
|
RouteHints [][]zpay32.HopHint
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddInvoice attempts to add a new invoice to the invoice database. Any
|
// AddInvoice attempts to add a new invoice to the invoice database. Any
|
||||||
@ -246,6 +250,27 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
|
|||||||
options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
|
options = append(options, zpay32.CLTVExpiry(uint64(defaultDelta)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We make sure that the given invoice routing hints number is within the
|
||||||
|
// valid range
|
||||||
|
if len(invoice.RouteHints) > 20 {
|
||||||
|
return nil, nil, fmt.Errorf("number of routing hints must not exceed " +
|
||||||
|
"maximum of 20")
|
||||||
|
}
|
||||||
|
|
||||||
|
// We continue by populating the requested routing hints indexing their
|
||||||
|
// corresponding channels so we won't duplicate them.
|
||||||
|
forcedHints := make(map[uint64]struct{})
|
||||||
|
for _, h := range invoice.RouteHints {
|
||||||
|
if len(h) == 0 {
|
||||||
|
return nil, nil, fmt.Errorf("number of hop hint within a route must " +
|
||||||
|
"be positive")
|
||||||
|
}
|
||||||
|
options = append(options, zpay32.RouteHint(h))
|
||||||
|
|
||||||
|
// Only this first hop is our direct channel.
|
||||||
|
forcedHints[h[0].ChannelID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// If we were requested to include routing hints in the invoice, then
|
// If we were requested to include routing hints in the invoice, then
|
||||||
// we'll fetch all of our available private channels and create routing
|
// we'll fetch all of our available private channels and create routing
|
||||||
// hints for them.
|
// hints for them.
|
||||||
@ -256,11 +281,21 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(openChannels) > 0 {
|
if len(openChannels) > 0 {
|
||||||
|
// We filter the channels by excluding the ones that were specified by
|
||||||
|
// the caller and were already added.
|
||||||
|
var filteredChannels []*channeldb.OpenChannel
|
||||||
|
for _, c := range openChannels {
|
||||||
|
if _, ok := forcedHints[c.ShortChanID().ToUint64()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredChannels = append(filteredChannels, c)
|
||||||
|
}
|
||||||
|
|
||||||
// We'll restrict the number of individual route hints
|
// We'll restrict the number of individual route hints
|
||||||
// to 20 to avoid creating overly large invoices.
|
// to 20 to avoid creating overly large invoices.
|
||||||
const numMaxHophints = 20
|
numMaxHophints := 20 - len(forcedHints)
|
||||||
hopHints := selectHopHints(
|
hopHints := selectHopHints(
|
||||||
amtMSat, cfg, openChannels, numMaxHophints,
|
amtMSat, cfg, filteredChannels, numMaxHophints,
|
||||||
)
|
)
|
||||||
|
|
||||||
options = append(options, hopHints...)
|
options = append(options, hopHints...)
|
||||||
|
@ -289,6 +289,11 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the passed routing hints to the required format.
|
||||||
|
routeHints, err := CreateZpay32HopHints(invoice.RouteHints)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
addInvoiceData := &AddInvoiceData{
|
addInvoiceData := &AddInvoiceData{
|
||||||
Memo: invoice.Memo,
|
Memo: invoice.Memo,
|
||||||
Hash: &hash,
|
Hash: &hash,
|
||||||
@ -300,6 +305,7 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
|
|||||||
Private: invoice.Private,
|
Private: invoice.Private,
|
||||||
HodlInvoice: true,
|
HodlInvoice: true,
|
||||||
Preimage: nil,
|
Preimage: nil,
|
||||||
|
RouteHints: routeHints,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, dbInvoice, err := AddInvoice(ctx, addInvoiceCfg, addInvoiceData)
|
_, dbInvoice, err := AddInvoice(ctx, addInvoiceCfg, addInvoiceData)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
@ -204,3 +205,31 @@ func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateZpay32HopHints takes in the lnrpc form of route hints and converts them
|
||||||
|
// into an invoice decoded form.
|
||||||
|
func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) {
|
||||||
|
var res [][]zpay32.HopHint
|
||||||
|
for _, route := range routeHints {
|
||||||
|
hopHints := make([]zpay32.HopHint, 0, len(route.HopHints))
|
||||||
|
for _, hop := range route.HopHints {
|
||||||
|
pubKeyBytes, err := hex.DecodeString(hop.NodeId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hopHints = append(hopHints, zpay32.HopHint{
|
||||||
|
NodeID: p,
|
||||||
|
ChannelID: hop.ChanId,
|
||||||
|
FeeBaseMSat: hop.FeeBaseMsat,
|
||||||
|
FeeProportionalMillionths: hop.FeeProportionalMillionths,
|
||||||
|
CLTVExpiryDelta: uint16(hop.CltvExpiryDelta),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res = append(res, hopHints)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/record"
|
"github.com/lightningnetwork/lnd/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -161,6 +162,68 @@ func testSingleHopInvoice(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now create an invoice and specify routing hints.
|
||||||
|
// We will test that the routing hints are encoded properly.
|
||||||
|
hintChannel := lnwire.ShortChannelID{BlockHeight: 10}
|
||||||
|
bobPubKey := hex.EncodeToString(net.Bob.PubKey[:])
|
||||||
|
hints := []*lnrpc.RouteHint{
|
||||||
|
{
|
||||||
|
HopHints: []*lnrpc.HopHint{
|
||||||
|
{
|
||||||
|
NodeId: bobPubKey,
|
||||||
|
ChanId: hintChannel.ToUint64(),
|
||||||
|
FeeBaseMsat: 1,
|
||||||
|
FeeProportionalMillionths: 1000000,
|
||||||
|
CltvExpiryDelta: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice = &lnrpc.Invoice{
|
||||||
|
Memo: "hints",
|
||||||
|
Value: paymentAmt,
|
||||||
|
RouteHints: hints,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxt, defaultTimeout)
|
||||||
|
invoiceResp, err = net.Bob.AddInvoice(ctxt, invoice)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to add invoice: %v", err)
|
||||||
|
}
|
||||||
|
payreq, err := net.Bob.DecodePayReq(ctxt, &lnrpc.PayReqString{PayReq: invoiceResp.PaymentRequest})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to decode payment request %v", err)
|
||||||
|
}
|
||||||
|
if len(payreq.RouteHints) != 1 {
|
||||||
|
t.Fatalf("expected one routing hint")
|
||||||
|
}
|
||||||
|
routingHint := payreq.RouteHints[0]
|
||||||
|
if len(routingHint.HopHints) != 1 {
|
||||||
|
t.Fatalf("expected one hop hint")
|
||||||
|
}
|
||||||
|
hopHint := routingHint.HopHints[0]
|
||||||
|
if hopHint.FeeProportionalMillionths != 1000000 {
|
||||||
|
t.Fatalf("wrong FeeProportionalMillionths %v",
|
||||||
|
hopHint.FeeProportionalMillionths)
|
||||||
|
}
|
||||||
|
if hopHint.NodeId != bobPubKey {
|
||||||
|
t.Fatalf("wrong NodeId %v",
|
||||||
|
hopHint.NodeId)
|
||||||
|
}
|
||||||
|
if hopHint.ChanId != hintChannel.ToUint64() {
|
||||||
|
t.Fatalf("wrong ChanId %v",
|
||||||
|
hopHint.ChanId)
|
||||||
|
}
|
||||||
|
if hopHint.FeeBaseMsat != 1 {
|
||||||
|
t.Fatalf("wrong FeeBaseMsat %v",
|
||||||
|
hopHint.FeeBaseMsat)
|
||||||
|
}
|
||||||
|
if hopHint.CltvExpiryDelta != 20 {
|
||||||
|
t.Fatalf("wrong CltvExpiryDelta %v",
|
||||||
|
hopHint.CltvExpiryDelta)
|
||||||
|
}
|
||||||
|
|
||||||
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
|
||||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
|
||||||
}
|
}
|
||||||
|
@ -4745,6 +4745,11 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the passed routing hints to the required format.
|
||||||
|
routeHints, err := invoicesrpc.CreateZpay32HopHints(invoice.RouteHints)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
addInvoiceData := &invoicesrpc.AddInvoiceData{
|
addInvoiceData := &invoicesrpc.AddInvoiceData{
|
||||||
Memo: invoice.Memo,
|
Memo: invoice.Memo,
|
||||||
Value: value,
|
Value: value,
|
||||||
@ -4753,6 +4758,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
|||||||
FallbackAddr: invoice.FallbackAddr,
|
FallbackAddr: invoice.FallbackAddr,
|
||||||
CltvExpiry: invoice.CltvExpiry,
|
CltvExpiry: invoice.CltvExpiry,
|
||||||
Private: invoice.Private,
|
Private: invoice.Private,
|
||||||
|
RouteHints: routeHints,
|
||||||
}
|
}
|
||||||
|
|
||||||
if invoice.RPreimage != nil {
|
if invoice.RPreimage != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user