-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: add new feature pkg to manage feature sets
This commit introduces a feature.Manager, which derives feature vectors for various contexts within the daemon. The sets can be described via a staticly compiled format, which makes any runtime adjustments to the feature sets when the manager is initialized.
- Loading branch information
1 parent
90e36ca
commit fe566e1
Showing
5 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package feature | ||
|
||
import "github.com/lightningnetwork/lnd/lnwire" | ||
|
||
// setDesc describes which feature bits should be advertised in which feature | ||
// sets. | ||
type setDesc map[lnwire.FeatureBit]map[Set]struct{} | ||
|
||
// defaultSetDesc are the default set descriptors for generating feature | ||
// vectors. Each set is annotated with the corresponding identifier from BOLT 9 | ||
// indicating where it should be advertised. | ||
var defaultSetDesc = setDesc{ | ||
lnwire.DataLossProtectRequired: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
}, | ||
lnwire.GossipQueriesOptional: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
}, | ||
lnwire.TLVOnionPayloadOptional: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
SetInvoice: {}, // 9 | ||
SetLegacyGlobal: {}, | ||
}, | ||
lnwire.StaticRemoteKeyOptional: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
SetLegacyGlobal: {}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package feature | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/lightningnetwork/lnd/lnwire" | ||
) | ||
|
||
// Config houses any runtime modifications to the default set descriptors. For | ||
// our purposes, this typically means disabling certain features to test legacy | ||
// protocol interoperability or functionality. | ||
type Config struct { | ||
// NoTLVOnion unsets any optional or required TLVOnionPaylod bits from | ||
// all feature sets. | ||
NoTLVOnion bool | ||
|
||
// NoStaticRemoteKey unsets any optional or required StaticRemoteKey | ||
// bits from all feature sets. | ||
NoStaticRemoteKey bool | ||
} | ||
|
||
// Manager is responsible for generating feature vectors for different requested | ||
// feature sets. | ||
type Manager struct { | ||
// fsets is a static map of feature set to raw feature vectors. Requests | ||
// are fulfilled by cloning these interal feature vectors. | ||
fsets map[Set]*lnwire.RawFeatureVector | ||
} | ||
|
||
// NewManager creates a new feature Manager, applying any custom modifications | ||
// to its feature sets before returning. | ||
func NewManager(cfg Config) (*Manager, error) { | ||
return newManager(cfg, defaultSetDesc) | ||
} | ||
|
||
// newManager creates a new feeature Manager, applying any custom modifications | ||
// to its feature sets before returning. This method accepts the setDesc as its | ||
// own parameter so that it can be unit tested. | ||
func newManager(cfg Config, desc setDesc) (*Manager, error) { | ||
// First build the default feature vector for all known sets. | ||
fsets := make(map[Set]*lnwire.RawFeatureVector) | ||
for bit, sets := range desc { | ||
for set := range sets { | ||
// Fetch the feature vector for this set, allocating a | ||
// new one if it doesn't exist. | ||
fv, ok := fsets[set] | ||
if !ok { | ||
fv = lnwire.NewRawFeatureVector() | ||
} | ||
|
||
// Set the configured bit on the feature vector, | ||
// ensuring that we don't set two feature bits for the | ||
// same pair. | ||
err := fv.SafeSet(bit) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to set "+ | ||
"%v in %v: %v", bit, set, err) | ||
} | ||
|
||
// Write the updated feature vector under its set. | ||
fsets[set] = fv | ||
} | ||
} | ||
|
||
// Now, remove any features as directed by the config. | ||
for _, fv := range fsets { | ||
if cfg.NoTLVOnion { | ||
fv.Unset(lnwire.TLVOnionPayloadOptional) | ||
fv.Unset(lnwire.TLVOnionPayloadRequired) | ||
} | ||
if cfg.NoStaticRemoteKey { | ||
fv.Unset(lnwire.StaticRemoteKeyOptional) | ||
fv.Unset(lnwire.StaticRemoteKeyRequired) | ||
} | ||
} | ||
|
||
return &Manager{ | ||
fsets: fsets, | ||
}, nil | ||
} | ||
|
||
// GetRaw returns a raw feature vector for the passed set. If no set is known, | ||
// an empty raw feature vector is returned. | ||
func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector { | ||
if fv, ok := m.fsets[set]; ok { | ||
return fv.Clone() | ||
} | ||
|
||
return lnwire.NewRawFeatureVector() | ||
} | ||
|
||
// Get returns a feature vector for the passed set. If no set is known, an empty | ||
// feature vector is returned. | ||
func (m *Manager) Get(set Set) *lnwire.FeatureVector { | ||
raw := m.GetRaw(set) | ||
return lnwire.NewFeatureVector(raw, lnwire.Features) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package feature | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/lightningnetwork/lnd/lnwire" | ||
) | ||
|
||
type managerTest struct { | ||
name string | ||
cfg Config | ||
} | ||
|
||
const unknownFeature lnwire.FeatureBit = 30 | ||
|
||
var testSetDesc = setDesc{ | ||
lnwire.DataLossProtectRequired: { | ||
SetNodeAnn: {}, // I | ||
}, | ||
lnwire.TLVOnionPayloadOptional: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
}, | ||
lnwire.StaticRemoteKeyOptional: { | ||
SetInit: {}, // I | ||
SetNodeAnn: {}, // N | ||
}, | ||
} | ||
|
||
var managerTests = []managerTest{ | ||
{ | ||
name: "default", | ||
cfg: Config{}, | ||
}, | ||
{ | ||
name: "no tlv", | ||
cfg: Config{ | ||
NoTLVOnion: true, | ||
}, | ||
}, | ||
{ | ||
name: "no static remote key", | ||
cfg: Config{ | ||
NoStaticRemoteKey: true, | ||
}, | ||
}, | ||
{ | ||
name: "no tlv or static remote key", | ||
cfg: Config{ | ||
NoTLVOnion: true, | ||
NoStaticRemoteKey: true, | ||
}, | ||
}, | ||
} | ||
|
||
// TestManager asserts basic initialazation and operation of a feature manager, | ||
// including that the proper features are removed in response to config changes. | ||
func TestManager(t *testing.T) { | ||
for _, test := range managerTests { | ||
test := test | ||
t.Run(test.name, func(t *testing.T) { | ||
testManager(t, test) | ||
}) | ||
} | ||
} | ||
|
||
func testManager(t *testing.T, test managerTest) { | ||
m, err := newManager(test.cfg, testSetDesc) | ||
if err != nil { | ||
t.Fatalf("unable to create feature manager: %v", err) | ||
} | ||
|
||
sets := []Set{ | ||
SetInit, | ||
SetLegacyGlobal, | ||
SetNodeAnn, | ||
SetInvoice, | ||
} | ||
|
||
for _, set := range sets { | ||
raw := m.GetRaw(set) | ||
fv := m.Get(set) | ||
|
||
fv2 := lnwire.NewFeatureVector(raw, lnwire.Features) | ||
|
||
if !reflect.DeepEqual(fv, fv2) { | ||
t.Fatalf("mismatch Get vs GetRaw, raw: %v vs fv: %v", | ||
fv2, fv) | ||
} | ||
|
||
assertUnset := func(bit lnwire.FeatureBit) { | ||
hasBit := fv.HasFeature(bit) || fv.HasFeature(bit^1) | ||
if hasBit { | ||
t.Fatalf("bit %v or %v is set", bit, bit^1) | ||
} | ||
} | ||
|
||
// Assert that the manager properly unset the configured feature | ||
// bits from all sets. | ||
if test.cfg.NoTLVOnion { | ||
assertUnset(lnwire.TLVOnionPayloadOptional) | ||
} | ||
if test.cfg.NoStaticRemoteKey { | ||
assertUnset(lnwire.StaticRemoteKeyOptional) | ||
} | ||
|
||
assertUnset(unknownFeature) | ||
} | ||
|
||
// Do same basic sanity checks on features that are always present. | ||
nodeFeatures := m.Get(SetNodeAnn) | ||
|
||
assertSet := func(bit lnwire.FeatureBit) { | ||
has := nodeFeatures.HasFeature(bit) | ||
if !has { | ||
t.Fatalf("node features don't advertised %v", bit) | ||
} | ||
} | ||
|
||
assertSet(lnwire.DataLossProtectOptional) | ||
if !test.cfg.NoTLVOnion { | ||
assertSet(lnwire.TLVOnionPayloadRequired) | ||
} | ||
if !test.cfg.NoStaticRemoteKey { | ||
assertSet(lnwire.StaticRemoteKeyOptional) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package feature | ||
|
||
// Set is an enum identifying various feature sets, which separates the single | ||
// feature namespace into distinct categories depending what context a feature | ||
// vector is being used. | ||
type Set uint8 | ||
|
||
const ( | ||
// SetInit identifies features that should be sent in an Init message to | ||
// a remote peer. | ||
SetInit Set = iota | ||
|
||
// SetLegacyGlobal identifies features that should be set in the legacy | ||
// GlobalFeatures field of an Init message, which maintains backwards | ||
// compatibility with nodes that haven't implemented flat features. | ||
SetLegacyGlobal | ||
|
||
// SetNodeAnn identifies features that should be advertised on node | ||
// announcements. | ||
SetNodeAnn | ||
|
||
// SetInvoice identifies features that should be advertised on invoices | ||
// generated by the daemon. | ||
SetInvoice | ||
) | ||
|
||
// String returns a human-readable description of a Set. | ||
func (s Set) String() string { | ||
switch s { | ||
case SetInit: | ||
return "SetInit" | ||
case SetLegacyGlobal: | ||
return "SetLegacyGlobal" | ||
case SetNodeAnn: | ||
return "SetNodeAnn" | ||
case SetInvoice: | ||
return "SetInvoice" | ||
default: | ||
return "SetUnknown" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters