Merge pull request #3802 from cfromknecht/rpc-invoice-features

rpc: expose invoice features in decodepayreq
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2019-12-10 15:12:19 -08:00
commit a68144b709
12 files changed
+827 -608

No files matched your search

+6 -5
View File
@@ -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
+4
View File
@@ -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
} }
+1
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -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 {}
+26
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
} }
+6 -1
View File
@@ -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
View File
@@ -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 "+
+2 -2
View File
@@ -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,
), ),
} }
}, },