Merge pull request #3802 from cfromknecht/rpc-invoice-features
rpc: expose invoice features in decodepayreq
This commit is contained in:
12 files changed
+827
-608
No files matched your search
@@ -47,6 +47,10 @@ type AddInvoiceConfig struct {
|
|||||||
// ChanDB is a global boltdb instance which is needed to access the
|
// ChanDB is a global boltdb instance which is needed to access the
|
||||||
// channel graph.
|
// channel graph.
|
||||||
ChanDB *channeldb.DB
|
ChanDB *channeldb.DB
|
||||||
|
|
||||||
|
// GenInvoiceFeatures returns a feature containing feature bits that
|
||||||
|
// should be advertised on freshly generated invoices.
|
||||||
|
GenInvoiceFeatures func() *lnwire.FeatureVector
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddInvoiceData contains the required data to create a new invoice.
|
// AddInvoiceData contains the required data to create a new invoice.
|
||||||
@@ -363,11 +367,8 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a blank feature vector, as our invoice generation forbids nil
|
// Set our desired invoice features and add them to our list of options.
|
||||||
// features.
|
invoiceFeatures := cfg.GenInvoiceFeatures()
|
||||||
invoiceFeatures := lnwire.NewFeatureVector(
|
|
||||||
lnwire.NewRawFeatureVector(), lnwire.Features,
|
|
||||||
)
|
|
||||||
options = append(options, zpay32.Features(invoiceFeatures))
|
options = append(options, zpay32.Features(invoiceFeatures))
|
||||||
|
|
||||||
// Generate and set a random payment address for this invoice. If the
|
// Generate and set a random payment address for this invoice. If the
|
||||||
|
|||||||
@@ -50,4 +50,8 @@ type Config struct {
|
|||||||
// ChanDB is a global boltdb instance which is needed to access the
|
// ChanDB is a global boltdb instance which is needed to access the
|
||||||
// channel graph.
|
// channel graph.
|
||||||
ChanDB *channeldb.DB
|
ChanDB *channeldb.DB
|
||||||
|
|
||||||
|
// GenInvoiceFeatures returns a feature containing feature bits that
|
||||||
|
// should be advertised on freshly generated invoices.
|
||||||
|
GenInvoiceFeatures func() *lnwire.FeatureVector
|
||||||
}
|
}
|
||||||
@@ -253,6 +253,7 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
|
|||||||
MaxPaymentMSat: s.cfg.MaxPaymentMSat,
|
MaxPaymentMSat: s.cfg.MaxPaymentMSat,
|
||||||
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
|
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
|
||||||
ChanDB: s.cfg.ChanDB,
|
ChanDB: s.cfg.ChanDB,
|
||||||
|
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := lntypes.MakeHash(invoice.Hash)
|
hash, err := lntypes.MakeHash(invoice.Hash)
|
||||||
|
|||||||
+658
-582
File diff suppressed because it is too large.
Load diff
@@ -2628,6 +2628,14 @@ message PayReq {
|
|||||||
repeated RouteHint route_hints = 10 [json_name = "route_hints"];
|
repeated RouteHint route_hints = 10 [json_name = "route_hints"];
|
||||||
bytes payment_addr = 11 [json_name = "payment_addr"];
|
bytes payment_addr = 11 [json_name = "payment_addr"];
|
||||||
int64 num_msat = 12 [json_name = "num_msat"];
|
int64 num_msat = 12 [json_name = "num_msat"];
|
||||||
|
repeated Feature features = 13 [json_name = "features"];
|
||||||
|
}
|
||||||
|
|
||||||
|
message Feature {
|
||||||
|
uint32 bit = 1 [json_name = "bit"];
|
||||||
|
string name = 2 [json_name = "name"];
|
||||||
|
bool is_required = 3 [json_name = "is_required"];
|
||||||
|
bool is_known = 4 [json_name = "is_known"];
|
||||||
}
|
}
|
||||||
|
|
||||||
message FeeReportRequest {}
|
message FeeReportRequest {}
|
||||||
|
|||||||
@@ -2243,6 +2243,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lnrpcFeature": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bit": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"is_required": {
|
||||||
|
"type": "boolean",
|
||||||
|
"format": "boolean"
|
||||||
|
},
|
||||||
|
"is_known": {
|
||||||
|
"type": "boolean",
|
||||||
|
"format": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"lnrpcFeeLimit": {
|
"lnrpcFeeLimit": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -3271,6 +3291,12 @@
|
|||||||
"num_msat": {
|
"num_msat": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/lnrpcFeature"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+16
-3
@@ -3,7 +3,6 @@ package lnwire
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,6 +94,11 @@ const (
|
|||||||
maxAllowedSize = 32764
|
maxAllowedSize = 32764
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsRequired returns true if the feature bit is even, and false otherwise.
|
||||||
|
func (b FeatureBit) IsRequired() bool {
|
||||||
|
return b&0x01 == 0x00
|
||||||
|
}
|
||||||
|
|
||||||
// Features is a mapping of known feature bits to a descriptive name. All known
|
// Features is a mapping of known feature bits to a descriptive name. All known
|
||||||
// feature bits must be assigned a name in this mapping, and feature bit pairs
|
// feature bits must be assigned a name in this mapping, and feature bit pairs
|
||||||
// must be assigned together for correct behavior.
|
// must be assigned together for correct behavior.
|
||||||
@@ -362,9 +366,9 @@ func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
|
|||||||
func (fv *FeatureVector) Name(bit FeatureBit) string {
|
func (fv *FeatureVector) Name(bit FeatureBit) string {
|
||||||
name, known := fv.featureNames[bit]
|
name, known := fv.featureNames[bit]
|
||||||
if !known {
|
if !known {
|
||||||
name = "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s(%d)", name, bit)
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsKnown returns whether this feature bit represents a known feature.
|
// IsKnown returns whether this feature bit represents a known feature.
|
||||||
@@ -384,6 +388,15 @@ func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
|
|||||||
return known1 && known2 && name1 == name2
|
return known1 && known2 && name1 == name2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Features returns the set of raw features contained in the feature vector.
|
||||||
|
func (fv *FeatureVector) Features() map[FeatureBit]struct{} {
|
||||||
|
fs := make(map[FeatureBit]struct{}, len(fv.RawFeatureVector.features))
|
||||||
|
for b := range fv.RawFeatureVector.features {
|
||||||
|
fs[b] = struct{}{}
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
// Clone copies a feature vector, carrying over its feature bits. The feature
|
// Clone copies a feature vector, carrying over its feature bits. The feature
|
||||||
// names are not copied.
|
// names are not copied.
|
||||||
func (fv *FeatureVector) Clone() *FeatureVector {
|
func (fv *FeatureVector) Clone() *FeatureVector {
|
||||||
|
|||||||
+73
-8
@@ -205,42 +205,42 @@ func TestFeatureNames(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
bit: 0,
|
bit: 0,
|
||||||
expectedName: "feature1(0)",
|
expectedName: "feature1",
|
||||||
expectedKnown: true,
|
expectedKnown: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 1,
|
bit: 1,
|
||||||
expectedName: "unknown(1)",
|
expectedName: "unknown",
|
||||||
expectedKnown: false,
|
expectedKnown: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 2,
|
bit: 2,
|
||||||
expectedName: "unknown(2)",
|
expectedName: "unknown",
|
||||||
expectedKnown: false,
|
expectedKnown: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 3,
|
bit: 3,
|
||||||
expectedName: "feature2(3)",
|
expectedName: "feature2",
|
||||||
expectedKnown: true,
|
expectedKnown: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 4,
|
bit: 4,
|
||||||
expectedName: "feature3(4)",
|
expectedName: "feature3",
|
||||||
expectedKnown: true,
|
expectedKnown: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 5,
|
bit: 5,
|
||||||
expectedName: "feature3(5)",
|
expectedName: "feature3",
|
||||||
expectedKnown: true,
|
expectedKnown: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 6,
|
bit: 6,
|
||||||
expectedName: "unknown(6)",
|
expectedName: "unknown",
|
||||||
expectedKnown: false,
|
expectedKnown: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
bit: 7,
|
bit: 7,
|
||||||
expectedName: "unknown(7)",
|
expectedName: "unknown",
|
||||||
expectedKnown: false,
|
expectedKnown: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -260,3 +260,68 @@ func TestFeatureNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIsRequired asserts that feature bits properly return their IsRequired
|
||||||
|
// status. We require that even features be required and odd features be
|
||||||
|
// optional.
|
||||||
|
func TestIsRequired(t *testing.T) {
|
||||||
|
optional := FeatureBit(1)
|
||||||
|
if optional.IsRequired() {
|
||||||
|
t.Fatalf("optional feature should not be required")
|
||||||
|
}
|
||||||
|
|
||||||
|
required := FeatureBit(0)
|
||||||
|
if !required.IsRequired() {
|
||||||
|
t.Fatalf("required feature should be required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFeatures asserts that the Features() method on a FeatureVector properly
|
||||||
|
// returns the set of feature bits it stores internallly.
|
||||||
|
func TestFeatures(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
exp map[FeatureBit]struct{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
exp: map[FeatureBit]struct{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
exp: map[FeatureBit]struct{}{
|
||||||
|
5: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "several",
|
||||||
|
exp: map[FeatureBit]struct{}{
|
||||||
|
0: {},
|
||||||
|
5: {},
|
||||||
|
23948: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
toRawFV := func(set map[FeatureBit]struct{}) *RawFeatureVector {
|
||||||
|
var bits []FeatureBit
|
||||||
|
for bit := range set {
|
||||||
|
bits = append(bits, bit)
|
||||||
|
}
|
||||||
|
return NewRawFeatureVector(bits...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
fv := NewFeatureVector(
|
||||||
|
toRawFV(test.exp), Features,
|
||||||
|
)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(fv.Features(), test.exp) {
|
||||||
|
t.Fatalf("feature mismatch, want: %v, got: %v",
|
||||||
|
test.exp, fv.Features())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-1
@@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
"github.com/lightningnetwork/lnd/discovery"
|
"github.com/lightningnetwork/lnd/discovery"
|
||||||
|
"github.com/lightningnetwork/lnd/feature"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
@@ -549,6 +550,10 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
|||||||
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
|
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
genInvoiceFeatures := func() *lnwire.FeatureVector {
|
||||||
|
return s.featureMgr.Get(feature.SetInvoice)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
subServers []lnrpc.SubServer
|
subServers []lnrpc.SubServer
|
||||||
subServerPerms []lnrpc.MacaroonPerms
|
subServerPerms []lnrpc.MacaroonPerms
|
||||||
@@ -561,7 +566,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
|
|||||||
s.cc, networkDir, macService, atpl, invoiceRegistry,
|
s.cc, networkDir, macService, atpl, invoiceRegistry,
|
||||||
s.htlcSwitch, activeNetParams.Params, s.chanRouter,
|
s.htlcSwitch, activeNetParams.Params, s.chanRouter,
|
||||||
routerBackend, s.nodeSigner, s.chanDB, s.sweeper, tower,
|
routerBackend, s.nodeSigner, s.chanDB, s.sweeper, tower,
|
||||||
s.towerClient, cfg.net.ResolveTCPAddr,
|
s.towerClient, cfg.net.ResolveTCPAddr, genInvoiceFeatures,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3633,6 +3638,9 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
|
|||||||
MaxPaymentMSat: MaxPaymentMSat,
|
MaxPaymentMSat: MaxPaymentMSat,
|
||||||
DefaultCLTVExpiry: defaultDelta,
|
DefaultCLTVExpiry: defaultDelta,
|
||||||
ChanDB: r.server.chanDB,
|
ChanDB: r.server.chanDB,
|
||||||
|
GenInvoiceFeatures: func() *lnwire.FeatureVector {
|
||||||
|
return r.server.featureMgr.Get(feature.SetInvoice)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
|
value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
|
||||||
@@ -4620,6 +4628,20 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
|
|||||||
paymentAddr = payReq.PaymentAddr[:]
|
paymentAddr = payReq.PaymentAddr[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert any features on the payment request into a descriptive format
|
||||||
|
// for the rpc.
|
||||||
|
invFeatures := payReq.Features.Features()
|
||||||
|
features := make([]*lnrpc.Feature, 0, len(invFeatures))
|
||||||
|
for bit := range invFeatures {
|
||||||
|
name := payReq.Features.Name(bit)
|
||||||
|
features = append(features, &lnrpc.Feature{
|
||||||
|
Bit: uint32(bit),
|
||||||
|
Name: name,
|
||||||
|
IsRequired: bit.IsRequired(),
|
||||||
|
IsKnown: name != "unknown",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
dest := payReq.Destination.SerializeCompressed()
|
dest := payReq.Destination.SerializeCompressed()
|
||||||
return &lnrpc.PayReq{
|
return &lnrpc.PayReq{
|
||||||
Destination: hex.EncodeToString(dest),
|
Destination: hex.EncodeToString(dest),
|
||||||
@@ -4634,6 +4656,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
|
|||||||
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
|
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
|
||||||
RouteHints: routeHints,
|
RouteHints: routeHints,
|
||||||
PaymentAddr: paymentAddr,
|
PaymentAddr: paymentAddr,
|
||||||
|
Features: features,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/macaroons"
|
"github.com/lightningnetwork/lnd/macaroons"
|
||||||
"github.com/lightningnetwork/lnd/netann"
|
"github.com/lightningnetwork/lnd/netann"
|
||||||
"github.com/lightningnetwork/lnd/routing"
|
"github.com/lightningnetwork/lnd/routing"
|
||||||
@@ -92,7 +93,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
|
|||||||
sweeper *sweep.UtxoSweeper,
|
sweeper *sweep.UtxoSweeper,
|
||||||
tower *watchtower.Standalone,
|
tower *watchtower.Standalone,
|
||||||
towerClient wtclient.Client,
|
towerClient wtclient.Client,
|
||||||
tcpResolver lncfg.TCPResolver) error {
|
tcpResolver lncfg.TCPResolver,
|
||||||
|
genInvoiceFeatures func() *lnwire.FeatureVector) error {
|
||||||
|
|
||||||
// First, we'll use reflect to obtain a version of the config struct
|
// First, we'll use reflect to obtain a version of the config struct
|
||||||
// that allows us to programmatically inspect its fields.
|
// that allows us to programmatically inspect its fields.
|
||||||
@@ -210,6 +212,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
|
|||||||
subCfgValue.FieldByName("ChanDB").Set(
|
subCfgValue.FieldByName("ChanDB").Set(
|
||||||
reflect.ValueOf(chanDB),
|
reflect.ValueOf(chanDB),
|
||||||
)
|
)
|
||||||
|
subCfgValue.FieldByName("GenInvoiceFeatures").Set(
|
||||||
|
reflect.ValueOf(genInvoiceFeatures),
|
||||||
|
)
|
||||||
|
|
||||||
case *routerrpc.Config:
|
case *routerrpc.Config:
|
||||||
subCfgValue := extractReflectValue(subCfg)
|
subCfgValue := extractReflectValue(subCfg)
|
||||||
|
|||||||
+3
-6
@@ -85,11 +85,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// InvoiceFeatures holds the set of all known feature bits that are
|
// ErrInvoiceTooLarge is returned when an invoice exceeds
|
||||||
// exposed as BOLT 11 features.
|
// maxInvoiceLength.
|
||||||
InvoiceFeatures = map[lnwire.FeatureBit]string{}
|
|
||||||
|
|
||||||
// ErrInvoiceTooLarge is returned when an invoice exceeds maxInvoiceLength.
|
|
||||||
ErrInvoiceTooLarge = errors.New("invoice is too large")
|
ErrInvoiceTooLarge = errors.New("invoice is too large")
|
||||||
|
|
||||||
// ErrInvalidFieldLength is returned when a tagged field was specified
|
// ErrInvalidFieldLength is returned when a tagged field was specified
|
||||||
@@ -934,7 +931,7 @@ func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fv := lnwire.NewFeatureVector(rawFeatures, InvoiceFeatures)
|
fv := lnwire.NewFeatureVector(rawFeatures, lnwire.Features)
|
||||||
unknownFeatures := fv.UnknownRequiredFeatures()
|
unknownFeatures := fv.UnknownRequiredFeatures()
|
||||||
if len(unknownFeatures) > 0 {
|
if len(unknownFeatures) > 0 {
|
||||||
return nil, fmt.Errorf("invoice contains unknown required "+
|
return nil, fmt.Errorf("invoice contains unknown required "+
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
Destination: testPubKey,
|
Destination: testPubKey,
|
||||||
Features: lnwire.NewFeatureVector(
|
Features: lnwire.NewFeatureVector(
|
||||||
lnwire.NewRawFeatureVector(1, 9),
|
lnwire.NewRawFeatureVector(1, 9),
|
||||||
InvoiceFeatures,
|
lnwire.Features,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -523,7 +523,7 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
Destination: testPubKey,
|
Destination: testPubKey,
|
||||||
Features: lnwire.NewFeatureVector(
|
Features: lnwire.NewFeatureVector(
|
||||||
lnwire.NewRawFeatureVector(1, 9, 100),
|
lnwire.NewRawFeatureVector(1, 9, 100),
|
||||||
InvoiceFeatures,
|
lnwire.Features,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user