Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions lnrpc/invoicesrpc/addinvoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type AddInvoiceConfig struct {
// ChanDB is a global boltdb instance which is needed to access the
// channel graph.
ChanDB *channeldb.DB

// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
Comment thread
Roasbeef marked this conversation as resolved.
Outdated
}

// AddInvoiceData contains the required data to create a new invoice.
Expand Down Expand Up @@ -363,11 +367,8 @@ func AddInvoice(ctx context.Context, cfg *AddInvoiceConfig,

}

// Set a blank feature vector, as our invoice generation forbids nil
// features.
invoiceFeatures := lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(), lnwire.Features,
)
// Set our desired invoice features and add them to our list of options.
invoiceFeatures := cfg.GenInvoiceFeatures()
options = append(options, zpay32.Features(invoiceFeatures))

// Generate and set a random payment address for this invoice. If the
Expand Down
4 changes: 4 additions & 0 deletions lnrpc/invoicesrpc/config_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ type Config struct {
// ChanDB is a global boltdb instance which is needed to access the
// channel graph.
ChanDB *channeldb.DB

// GenInvoiceFeatures returns a feature containing feature bits that
// should be advertised on freshly generated invoices.
GenInvoiceFeatures func() *lnwire.FeatureVector
}
15 changes: 8 additions & 7 deletions lnrpc/invoicesrpc/invoices_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,14 @@ func (s *Server) AddHoldInvoice(ctx context.Context,
invoice *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) {

addInvoiceCfg := &AddInvoiceConfig{
AddInvoice: s.cfg.InvoiceRegistry.AddInvoice,
IsChannelActive: s.cfg.IsChannelActive,
ChainParams: s.cfg.ChainParams,
NodeSigner: s.cfg.NodeSigner,
MaxPaymentMSat: s.cfg.MaxPaymentMSat,
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
ChanDB: s.cfg.ChanDB,
AddInvoice: s.cfg.InvoiceRegistry.AddInvoice,
IsChannelActive: s.cfg.IsChannelActive,
ChainParams: s.cfg.ChainParams,
NodeSigner: s.cfg.NodeSigner,
MaxPaymentMSat: s.cfg.MaxPaymentMSat,
DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry,
ChanDB: s.cfg.ChanDB,
GenInvoiceFeatures: s.cfg.GenInvoiceFeatures,
}

hash, err := lntypes.MakeHash(invoice.Hash)
Expand Down
1,240 changes: 658 additions & 582 deletions lnrpc/rpc.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions lnrpc/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2628,6 +2628,14 @@ message PayReq {
repeated RouteHint route_hints = 10 [json_name = "route_hints"];
bytes payment_addr = 11 [json_name = "payment_addr"];
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"];
Comment thread
cfromknecht marked this conversation as resolved.
Outdated
bool is_known = 4 [json_name = "is_known"];
}

message FeeReportRequest {}
Expand Down
26 changes: 26 additions & 0 deletions lnrpc/rpc.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -3271,6 +3291,12 @@
"num_msat": {
"type": "string",
"format": "int64"
},
"features": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcFeature"
}
}
}
},
Expand Down
19 changes: 16 additions & 3 deletions lnwire/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package lnwire
import (
"encoding/binary"
"errors"
"fmt"
"io"
)

Expand Down Expand Up @@ -95,6 +94,11 @@ const (
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
// feature bits must be assigned a name in this mapping, and feature bit pairs
// must be assigned together for correct behavior.
Expand Down Expand Up @@ -362,9 +366,9 @@ func (fv *FeatureVector) UnknownRequiredFeatures() []FeatureBit {
func (fv *FeatureVector) Name(bit FeatureBit) string {
name, known := fv.featureNames[bit]
if !known {
name = "unknown"
return "unknown"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit message: featire -> feature.

Perhaps we should leave displaying the bit for unknown features?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bit is exposed separately in the rpc, wouldn't it be redundant?

}
return fmt.Sprintf("%s(%d)", name, bit)
return name
}

// IsKnown returns whether this feature bit represents a known feature.
Expand All @@ -384,6 +388,15 @@ func (fv *FeatureVector) isFeatureBitPair(bit FeatureBit) bool {
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
// names are not copied.
func (fv *FeatureVector) Clone() *FeatureVector {
Expand Down
81 changes: 73 additions & 8 deletions lnwire/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,42 +205,42 @@ func TestFeatureNames(t *testing.T) {
}{
{
bit: 0,
expectedName: "feature1(0)",
expectedName: "feature1",
expectedKnown: true,
},
{
bit: 1,
expectedName: "unknown(1)",
expectedName: "unknown",
expectedKnown: false,
},
{
bit: 2,
expectedName: "unknown(2)",
expectedName: "unknown",
expectedKnown: false,
},
{
bit: 3,
expectedName: "feature2(3)",
expectedName: "feature2",
expectedKnown: true,
},
{
bit: 4,
expectedName: "feature3(4)",
expectedName: "feature3",
expectedKnown: true,
},
{
bit: 5,
expectedName: "feature3(5)",
expectedName: "feature3",
expectedKnown: true,
},
{
bit: 6,
expectedName: "unknown(6)",
expectedName: "unknown",
expectedKnown: false,
},
{
bit: 7,
expectedName: "unknown(7)",
expectedName: "unknown",
expectedKnown: false,
},
}
Expand All @@ -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())
}
})
}
}
25 changes: 24 additions & 1 deletion rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/invoices"
Expand Down Expand Up @@ -549,6 +550,10 @@ func newRPCServer(s *server, macService *macaroons.Service,
MaxTotalTimelock: cfg.MaxOutgoingCltvExpiry,
}

genInvoiceFeatures := func() *lnwire.FeatureVector {
return s.featureMgr.Get(feature.SetInvoice)
}

var (
subServers []lnrpc.SubServer
subServerPerms []lnrpc.MacaroonPerms
Expand All @@ -561,7 +566,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
s.cc, networkDir, macService, atpl, invoiceRegistry,
s.htlcSwitch, activeNetParams.Params, s.chanRouter,
routerBackend, s.nodeSigner, s.chanDB, s.sweeper, tower,
s.towerClient, cfg.net.ResolveTCPAddr,
s.towerClient, cfg.net.ResolveTCPAddr, genInvoiceFeatures,
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -3633,6 +3638,9 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
MaxPaymentMSat: MaxPaymentMSat,
DefaultCLTVExpiry: defaultDelta,
ChanDB: r.server.chanDB,
GenInvoiceFeatures: func() *lnwire.FeatureVector {
return r.server.featureMgr.Get(feature.SetInvoice)
},
}

value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
Expand Down Expand Up @@ -4620,6 +4628,20 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
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()
return &lnrpc.PayReq{
Destination: hex.EncodeToString(dest),
Expand All @@ -4634,6 +4656,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
CltvExpiry: int64(payReq.MinFinalCLTVExpiry()),
RouteHints: routeHints,
PaymentAddr: paymentAddr,
Features: features,
}, nil
}

Expand Down
7 changes: 6 additions & 1 deletion subrpcserver_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/netann"
"github.com/lightningnetwork/lnd/routing"
Expand Down Expand Up @@ -92,7 +93,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
sweeper *sweep.UtxoSweeper,
tower *watchtower.Standalone,
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
// that allows us to programmatically inspect its fields.
Expand Down Expand Up @@ -210,6 +212,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
subCfgValue.FieldByName("ChanDB").Set(
reflect.ValueOf(chanDB),
)
subCfgValue.FieldByName("GenInvoiceFeatures").Set(
reflect.ValueOf(genInvoiceFeatures),
)

case *routerrpc.Config:
subCfgValue := extractReflectValue(subCfg)
Expand Down
9 changes: 3 additions & 6 deletions zpay32/invoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,8 @@ const (
)

var (
// InvoiceFeatures holds the set of all known feature bits that are
// exposed as BOLT 11 features.
InvoiceFeatures = map[lnwire.FeatureBit]string{}

// ErrInvoiceTooLarge is returned when an invoice exceeds maxInvoiceLength.
// ErrInvoiceTooLarge is returned when an invoice exceeds
// maxInvoiceLength.
ErrInvoiceTooLarge = errors.New("invoice is too large")

// ErrInvalidFieldLength is returned when a tagged field was specified
Expand Down Expand Up @@ -934,7 +931,7 @@ func parseFeatures(data []byte) (*lnwire.FeatureVector, error) {
return nil, err
}

fv := lnwire.NewFeatureVector(rawFeatures, InvoiceFeatures)
fv := lnwire.NewFeatureVector(rawFeatures, lnwire.Features)
unknownFeatures := fv.UnknownRequiredFeatures()
if len(unknownFeatures) > 0 {
return nil, fmt.Errorf("invoice contains unknown required "+
Expand Down
Loading