Browse Source
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.master
Conner Fromknecht
5 years ago
5 changed files with 328 additions and 0 deletions
@ -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: {}, |
||||
}, |
||||
} |
@ -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) |
||||
} |
@ -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) |
||||
} |
||||
} |
@ -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" |
||||
} |
||||
} |
Loading…
Reference in new issue