From 90e36ca04b39856ab949df864d817f7e27bc34e3 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 8 Nov 2019 05:28:47 -0800 Subject: [PATCH 1/5] lnwire/features: add unified Features namespace --- lnwire/features.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lnwire/features.go b/lnwire/features.go index 9a34a637..2b7d9084 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -102,6 +102,21 @@ var GlobalFeatures = map[FeatureBit]string{ StaticRemoteKeyRequired: "static-remote-key", } +// 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. +var Features = map[FeatureBit]string{ + DataLossProtectRequired: "data-loss-protect", + DataLossProtectOptional: "data-loss-protect", + InitialRoutingSync: "initial-routing-sync", + GossipQueriesRequired: "gossip-queries", + GossipQueriesOptional: "gossip-queries", + TLVOnionPayloadRequired: "tlv-onion", + TLVOnionPayloadOptional: "tlv-onion", + StaticRemoteKeyOptional: "static-remote-key", + StaticRemoteKeyRequired: "static-remote-key", +} + // RawFeatureVector represents a set of feature bits as defined in BOLT-09. A // RawFeatureVector itself just stores a set of bit flags but can be used to // construct a FeatureVector which binds meaning to each bit. Feature vectors From fe566e1755d7cb65ceb21964ac78fc280633dcc8 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 8 Nov 2019 05:29:16 -0800 Subject: [PATCH 2/5] 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. --- feature/default_sets.go | 32 ++++++++ feature/manager.go | 97 +++++++++++++++++++++++ feature/manager_internal_test.go | 128 +++++++++++++++++++++++++++++++ feature/set.go | 41 ++++++++++ lnwire/features.go | 30 ++++++++ 5 files changed, 328 insertions(+) create mode 100644 feature/default_sets.go create mode 100644 feature/manager.go create mode 100644 feature/manager_internal_test.go create mode 100644 feature/set.go diff --git a/feature/default_sets.go b/feature/default_sets.go new file mode 100644 index 00000000..fe69226c --- /dev/null +++ b/feature/default_sets.go @@ -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: {}, + }, +} diff --git a/feature/manager.go b/feature/manager.go new file mode 100644 index 00000000..f201cad1 --- /dev/null +++ b/feature/manager.go @@ -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) +} diff --git a/feature/manager_internal_test.go b/feature/manager_internal_test.go new file mode 100644 index 00000000..9c97bd05 --- /dev/null +++ b/feature/manager_internal_test.go @@ -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) + } +} diff --git a/feature/set.go b/feature/set.go new file mode 100644 index 00000000..2ac2ce52 --- /dev/null +++ b/feature/set.go @@ -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" + } +} diff --git a/lnwire/features.go b/lnwire/features.go index 2b7d9084..6843086c 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -2,10 +2,17 @@ package lnwire import ( "encoding/binary" + "errors" "fmt" "io" ) +var ( + // ErrFeaturePairExists signals an error in feature vector construction + // where the opposing bit in a feature pair has already been set. + ErrFeaturePairExists = errors.New("feature pair exists") +) + // FeatureBit represents a feature that can be enabled in either a local or // global feature vector at a specific bit position. Feature bits follow the // "it's OK to be odd" rule, where features at even bit positions must be known @@ -136,6 +143,15 @@ func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector { return fv } +// Clone makes a copy of a feature vector. +func (fv *RawFeatureVector) Clone() *RawFeatureVector { + newFeatures := NewRawFeatureVector() + for bit := range fv.features { + newFeatures.Set(bit) + } + return newFeatures +} + // IsSet returns whether a particular feature bit is enabled in the vector. func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool { return fv.features[feature] @@ -146,6 +162,20 @@ func (fv *RawFeatureVector) Set(feature FeatureBit) { fv.features[feature] = true } +// SafeSet sets the chosen feature bit in the feature vector, but returns an +// error if the opposing feature bit is already set. This ensures both that we +// are creating properly structured feature vectors, and in some cases, that +// peers are sending properly encoded ones, i.e. it can't be both optional and +// required. +func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error { + if _, ok := fv.features[feature^1]; ok { + return ErrFeaturePairExists + } + + fv.Set(feature) + return nil +} + // Unset marks a feature as disabled in the vector. func (fv *RawFeatureVector) Unset(feature FeatureBit) { delete(fv.features, feature) From 6c86075354f6db6b784dfb93298a50933bd5108a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 8 Nov 2019 05:29:29 -0800 Subject: [PATCH 3/5] peer+server: use feature manager to generate feature vectors --- peer.go | 24 ++++++++++++++++-------- server.go | 39 +++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/peer.go b/peer.go index ade90b41..a46a1b7d 100644 --- a/peer.go +++ b/peer.go @@ -190,9 +190,16 @@ type peer struct { server *server - // localFeatures is the set of local features that we advertised to the - // remote node. - localFeatures *lnwire.RawFeatureVector + // features is the set of features that we advertised to the remote + // node. + features *lnwire.FeatureVector + + // legacyFeatures is the set of features that we advertised to the remote + // node for backwards compatibility. Nodes that have not implemented + // flat featurs will still be able to read our feature bits from the + // legacy global field, but we will also advertise everything in the + // default features field. + legacyFeatures *lnwire.FeatureVector // outgoingCltvRejectDelta defines the number of blocks before expiry of // an htlc where we don't offer an htlc anymore. @@ -234,7 +241,7 @@ var _ lnpeer.Peer = (*peer)(nil) // pointer to the main server. func newPeer(conn net.Conn, connReq *connmgr.ConnReq, server *server, addr *lnwire.NetAddress, inbound bool, - localFeatures *lnwire.RawFeatureVector, + features, legacyFeatures *lnwire.FeatureVector, chanActiveTimeout time.Duration, outgoingCltvRejectDelta uint32) ( *peer, error) { @@ -252,7 +259,8 @@ func newPeer(conn net.Conn, connReq *connmgr.ConnReq, server *server, server: server, - localFeatures: localFeatures, + features: features, + legacyFeatures: legacyFeatures, outgoingCltvRejectDelta: outgoingCltvRejectDelta, @@ -2425,7 +2433,7 @@ func (p *peer) handleInitMsg(msg *lnwire.Init) error { // // NOTE: Part of the lnpeer.Peer interface. func (p *peer) LocalGlobalFeatures() *lnwire.FeatureVector { - return p.server.globalFeatures + return p.features } // RemoteGlobalFeatures returns the set of global features that has been @@ -2441,8 +2449,8 @@ func (p *peer) RemoteGlobalFeatures() *lnwire.FeatureVector { // supported local and global features. func (p *peer) sendInitMsg() error { msg := lnwire.NewInitMessage( - p.server.globalFeatures.RawFeatureVector, - p.localFeatures, + p.legacyFeatures.RawFeatureVector, + p.features.RawFeatureVector, ) return p.writeMessage(msg) diff --git a/server.go b/server.go index b8872e9e..fbe88c26 100644 --- a/server.go +++ b/server.go @@ -35,6 +35,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/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" @@ -232,9 +233,9 @@ type server struct { readPool *pool.Read - // globalFeatures feature vector which affects HTLCs and thus are also - // advertised to other nodes. - globalFeatures *lnwire.FeatureVector + // featureMgr dispatches feature vectors for various contexts within the + // daemon. + featureMgr *feature.Manager // currentNodeAnn is the node announcement that has been broadcast to // the network upon startup, if the attributes of the node (us) has @@ -369,6 +370,14 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout, ) + featureMgr, err := feature.NewManager(feature.Config{ + NoTLVOnion: cfg.LegacyProtocol.LegacyOnion(), + NoStaticRemoteKey: cfg.LegacyProtocol.LegacyCommitment(), + }) + if err != nil { + return nil, err + } + s := &server{ chanDB: chanDB, cc: cc, @@ -405,10 +414,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, peerConnectedListeners: make(map[string][]chan<- lnpeer.Peer), peerDisconnectedListeners: make(map[string][]chan<- struct{}), - globalFeatures: lnwire.NewFeatureVector( - globalFeatures, lnwire.GlobalFeatures, - ), - quit: make(chan struct{}), + featureMgr: featureMgr, + quit: make(chan struct{}), } s.witnessBeacon = &preimageBeacon{ @@ -594,7 +601,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, LastUpdate: time.Now(), Addresses: selfAddrs, Alias: nodeAlias.String(), - Features: s.globalFeatures, + Features: s.featureMgr.Get(feature.SetNodeAnn), Color: color, } copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed()) @@ -2727,14 +2734,10 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, ChainNet: activeNetParams.Net, } - // With the brontide connection established, we'll now craft the local - // feature vector to advertise to the remote node. - localFeatures := lnwire.NewRawFeatureVector() - - // We'll signal that we understand the data loss protection feature, - // and also that we support the new gossip query features. - localFeatures.Set(lnwire.DataLossProtectRequired) - localFeatures.Set(lnwire.GossipQueriesOptional) + // With the brontide connection established, we'll now craft the feature + // vectors to advertise to the remote node. + initFeatures := s.featureMgr.Get(feature.SetInit) + legacyFeatures := s.featureMgr.Get(feature.SetLegacyGlobal) // Now that we've established a connection, create a peer, and it to the // set of currently active peers. Configure the peer with the incoming @@ -2743,8 +2746,8 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, // htlcs, an extra block is added to prevent the channel from being // closed when the htlc is outstanding and a new block comes in. p, err := newPeer( - conn, connReq, s, peerAddr, inbound, localFeatures, - cfg.ChanEnableTimeout, + conn, connReq, s, peerAddr, inbound, initFeatures, + legacyFeatures, cfg.ChanEnableTimeout, defaultOutgoingCltvRejectDelta, ) if err != nil { From 16318c5a41fa52d900dc143d26bb082f1effef0e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 8 Nov 2019 05:31:47 -0800 Subject: [PATCH 4/5] multi: merge local+global features from remote peer --- discovery/mock_test.go | 4 +-- fundingmanager.go | 8 +++--- fundingmanager_test.go | 4 +-- htlcswitch/link_test.go | 4 +-- htlcswitch/mock.go | 4 +-- lnpeer/peer.go | 15 +++++----- lnwire/features.go | 11 +++++++ lnwire/init_message.go | 24 ++++++++++------ peer.go | 63 ++++++++++++++++++++--------------------- 9 files changed, 75 insertions(+), 62 deletions(-) diff --git a/discovery/mock_test.go b/discovery/mock_test.go index 57fc319a..f3f707d5 100644 --- a/discovery/mock_test.go +++ b/discovery/mock_test.go @@ -56,10 +56,10 @@ func (p *mockPeer) Address() net.Addr { return nil } func (p *mockPeer) QuitSignal() <-chan struct{} { return p.quit } -func (p *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector { +func (p *mockPeer) LocalFeatures() *lnwire.FeatureVector { return nil } -func (p *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector { +func (p *mockPeer) RemoteFeatures() *lnwire.FeatureVector { return nil } diff --git a/fundingmanager.go b/fundingmanager.go index f33ce498..ca7959d0 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1205,10 +1205,10 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // negotiated the new tweakless commitment format. This is only the // case if *both* us and the remote peer are signaling the proper // feature bit. - localTweakless := fmsg.peer.LocalGlobalFeatures().HasFeature( + localTweakless := fmsg.peer.LocalFeatures().HasFeature( lnwire.StaticRemoteKeyOptional, ) - remoteTweakless := fmsg.peer.RemoteGlobalFeatures().HasFeature( + remoteTweakless := fmsg.peer.RemoteFeatures().HasFeature( lnwire.StaticRemoteKeyOptional, ) tweaklessCommitment := localTweakless && remoteTweakless @@ -2780,10 +2780,10 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // negotiated the new tweakless commitment format. This is only the // case if *both* us and the remote peer are signaling the proper // feature bit. - localTweakless := msg.peer.LocalGlobalFeatures().HasFeature( + localTweakless := msg.peer.LocalFeatures().HasFeature( lnwire.StaticRemoteKeyOptional, ) - remoteTweakless := msg.peer.RemoteGlobalFeatures().HasFeature( + remoteTweakless := msg.peer.RemoteFeatures().HasFeature( lnwire.StaticRemoteKeyOptional, ) tweaklessCommitment := localTweakless && remoteTweakless diff --git a/fundingmanager_test.go b/fundingmanager_test.go index 024339d9..09bbe660 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -184,11 +184,11 @@ func (n *testNode) QuitSignal() <-chan struct{} { return n.shutdownChannel } -func (n *testNode) LocalGlobalFeatures() *lnwire.FeatureVector { +func (n *testNode) LocalFeatures() *lnwire.FeatureVector { return lnwire.NewFeatureVector(nil, nil) } -func (n *testNode) RemoteGlobalFeatures() *lnwire.FeatureVector { +func (n *testNode) RemoteFeatures() *lnwire.FeatureVector { return lnwire.NewFeatureVector(nil, nil) } diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index 93eb3763..96e2430b 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1653,10 +1653,10 @@ func (m *mockPeer) IdentityKey() *btcec.PublicKey { func (m *mockPeer) Address() net.Addr { return nil } -func (m *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector { +func (m *mockPeer) LocalFeatures() *lnwire.FeatureVector { return nil } -func (m *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector { +func (m *mockPeer) RemoteFeatures() *lnwire.FeatureVector { return nil } diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index 3dc1160d..c63aa171 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -607,11 +607,11 @@ func (s *mockServer) WipeChannel(*wire.OutPoint) error { return nil } -func (s *mockServer) LocalGlobalFeatures() *lnwire.FeatureVector { +func (s *mockServer) LocalFeatures() *lnwire.FeatureVector { return nil } -func (s *mockServer) RemoteGlobalFeatures() *lnwire.FeatureVector { +func (s *mockServer) RemoteFeatures() *lnwire.FeatureVector { return nil } diff --git a/lnpeer/peer.go b/lnpeer/peer.go index e21e2796..53f57131 100644 --- a/lnpeer/peer.go +++ b/lnpeer/peer.go @@ -47,15 +47,14 @@ type Peer interface { // implementation exits. QuitSignal() <-chan struct{} - // LocalGlobalFeatures returns the set of global features that has been - // advertised by the local peer. This allows sub-systems that use this + // LocalFeatures returns the set of features that has been advertised by + // the us to the remote peer. This allows sub-systems that use this // interface to gate their behavior off the set of negotiated feature // bits. - LocalGlobalFeatures() *lnwire.FeatureVector + LocalFeatures() *lnwire.FeatureVector - // RemoteGlobalFeatures returns the set of global features that has - // been advertised by the remote peer. This allows sub-systems that use - // this interface to gate their behavior off the set of negotiated - // feature bits. - RemoteGlobalFeatures() *lnwire.FeatureVector + // RemoteFeatures returns the set of features that has been advertised + // by the remote peer. This allows sub-systems that use this interface + // to gate their behavior off the set of negotiated feature bits. + RemoteFeatures() *lnwire.FeatureVector } diff --git a/lnwire/features.go b/lnwire/features.go index 6843086c..7ae0cac9 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -143,6 +143,17 @@ func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector { return fv } +// Merges sets all feature bits in other on the receiver's feature vector. +func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error { + for bit := range other.features { + err := fv.SafeSet(bit) + if err != nil { + return err + } + } + return nil +} + // Clone makes a copy of a feature vector. func (fv *RawFeatureVector) Clone() *RawFeatureVector { newFeatures := NewRawFeatureVector() diff --git a/lnwire/init_message.go b/lnwire/init_message.go index f64f9f8c..0236a71f 100644 --- a/lnwire/init_message.go +++ b/lnwire/init_message.go @@ -7,20 +7,26 @@ import "io" // diagnosis where features are incompatible. Each node MUST wait to receive // init before sending any other messages. type Init struct { - // GlobalFeatures is feature vector which affects HTLCs and thus are - // also advertised to other nodes. + // GlobalFeatures is a legacy feature vector used for backwards + // compatibility with older nodes. Any features defined here should be + // merged with those presented in Features. GlobalFeatures *RawFeatureVector - // LocalFeatures is feature vector which only affect the protocol - // between two nodes. - LocalFeatures *RawFeatureVector + // Features is a feature vector containing a the features supported by + // the remote node. + // + // NOTE: Older nodes may place some features in GlobalFeatures, but all + // new features are to be added in Features. When handling an Init + // message, any GlobalFeatures should be merged into the unified + // Features field. + Features *RawFeatureVector } // NewInitMessage creates new instance of init message object. -func NewInitMessage(gf *RawFeatureVector, lf *RawFeatureVector) *Init { +func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init { return &Init{ GlobalFeatures: gf, - LocalFeatures: lf, + Features: f, } } @@ -35,7 +41,7 @@ var _ Message = (*Init)(nil) func (msg *Init) Decode(r io.Reader, pver uint32) error { return ReadElements(r, &msg.GlobalFeatures, - &msg.LocalFeatures, + &msg.Features, ) } @@ -46,7 +52,7 @@ func (msg *Init) Decode(r io.Reader, pver uint32) error { func (msg *Init) Encode(w io.Writer, pver uint32) error { return WriteElements(w, msg.GlobalFeatures, - msg.LocalFeatures, + msg.Features, ) } diff --git a/peer.go b/peer.go index a46a1b7d..a007701b 100644 --- a/peer.go +++ b/peer.go @@ -205,13 +205,9 @@ type peer struct { // an htlc where we don't offer an htlc anymore. outgoingCltvRejectDelta uint32 - // remoteLocalFeatures is the local feature vector received from the - // peer during the connection handshake. - remoteLocalFeatures *lnwire.FeatureVector - - // remoteGlobalFeatures is the global feature vector received from the - // peer during the connection handshake. - remoteGlobalFeatures *lnwire.FeatureVector + // remoteFeatures is the feature vector received from the peer during + // the connection handshake. + remoteFeatures *lnwire.FeatureVector // failedChannels is a set that tracks channels we consider `failed`. // This is a temporary measure until we have implemented real failure @@ -407,7 +403,7 @@ func (p *peer) initGossipSync() { // If the remote peer knows of the new gossip queries feature, then // we'll create a new gossipSyncer in the AuthenticatedGossiper for it. - case p.remoteLocalFeatures.HasFeature(lnwire.GossipQueriesOptional): + case p.remoteFeatures.HasFeature(lnwire.GossipQueriesOptional): srvrLog.Infof("Negotiated chan series queries with %x", p.pubKeyBytes[:]) @@ -2395,54 +2391,55 @@ func (p *peer) WipeChannel(chanPoint *wire.OutPoint) error { // handleInitMsg handles the incoming init message which contains global and // local features vectors. If feature vectors are incompatible then disconnect. func (p *peer) handleInitMsg(msg *lnwire.Init) error { - p.remoteLocalFeatures = lnwire.NewFeatureVector( - msg.LocalFeatures, lnwire.LocalFeatures, - ) - p.remoteGlobalFeatures = lnwire.NewFeatureVector( - msg.GlobalFeatures, lnwire.GlobalFeatures, + // First, merge any features from the legacy global features field into + // those presented in the local features fields. + err := msg.Features.Merge(msg.GlobalFeatures) + if err != nil { + return fmt.Errorf("unable to merge legacy global featues: %v", + err) + } + + // Then, finalize the remote feature vector providing the flatteneed + // feature bit namespace. + p.remoteFeatures = lnwire.NewFeatureVector( + msg.Features, lnwire.Features, ) // Now that we have their features loaded, we'll ensure that they // didn't set any required bits that we don't know of. - unknownLocalFeatures := p.remoteLocalFeatures.UnknownRequiredFeatures() - if len(unknownLocalFeatures) > 0 { - err := fmt.Errorf("Peer set unknown local feature bits: %v", - unknownLocalFeatures) - return err - } - unknownGlobalFeatures := p.remoteGlobalFeatures.UnknownRequiredFeatures() - if len(unknownGlobalFeatures) > 0 { - err := fmt.Errorf("Peer set unknown global feature bits: %v", - unknownGlobalFeatures) + unknownFeatures := p.remoteFeatures.UnknownRequiredFeatures() + if len(unknownFeatures) > 0 { + err := fmt.Errorf("peer set unknown feature bits: %v", + unknownFeatures) return err } // Now that we know we understand their requirements, we'll check to // see if they don't support anything that we deem to be mandatory. switch { - case !p.remoteLocalFeatures.HasFeature(lnwire.DataLossProtectRequired): + case !p.remoteFeatures.HasFeature(lnwire.DataLossProtectRequired): return fmt.Errorf("data loss protection required") } return nil } -// LocalGlobalFeatures returns the set of global features that has been -// advertised by the local node. This allows sub-systems that use this -// interface to gate their behavior off the set of negotiated feature bits. +// LocalFeatures returns the set of global features that has been advertised by +// the local node. This allows sub-systems that use this interface to gate their +// behavior off the set of negotiated feature bits. // // NOTE: Part of the lnpeer.Peer interface. -func (p *peer) LocalGlobalFeatures() *lnwire.FeatureVector { +func (p *peer) LocalFeatures() *lnwire.FeatureVector { return p.features } -// RemoteGlobalFeatures returns the set of global features that has been -// advertised by the remote node. This allows sub-systems that use this -// interface to gate their behavior off the set of negotiated feature bits. +// RemoteFeatures returns the set of global features that has been advertised by +// the remote node. This allows sub-systems that use this interface to gate +// their behavior off the set of negotiated feature bits. // // NOTE: Part of the lnpeer.Peer interface. -func (p *peer) RemoteGlobalFeatures() *lnwire.FeatureVector { - return p.remoteGlobalFeatures +func (p *peer) RemoteFeatures() *lnwire.FeatureVector { + return p.remoteFeatures } // sendInitMsg sends init message to remote peer which contains our currently From 5e27b5022c000324de6a68ed450e7dabd0bd8bf5 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Fri, 8 Nov 2019 05:32:00 -0800 Subject: [PATCH 5/5] multi: remove LocalFeatures and GlobalFeatures --- autopilot/graph.go | 13 ++++++++---- channeldb/graph.go | 2 +- channeldb/graph_test.go | 2 +- channeldb/migration_01_to_11/graph.go | 2 +- channeldb/migration_01_to_11/graph_test.go | 2 +- discovery/gossiper.go | 4 +--- lnwire/features.go | 24 ---------------------- routing/notifications_test.go | 2 +- server.go | 2 +- 9 files changed, 16 insertions(+), 37 deletions(-) diff --git a/autopilot/graph.go b/autopilot/graph.go index 413aaf13..f02d0a8e 100644 --- a/autopilot/graph.go +++ b/autopilot/graph.go @@ -157,8 +157,9 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey, IP: bytes.Repeat([]byte("a"), 16), }, }, - Features: lnwire.NewFeatureVector(nil, - lnwire.GlobalFeatures), + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), AuthSigBytes: testSig.Serialize(), } graphNode.AddPubKey(pub) @@ -183,7 +184,9 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey, IP: bytes.Repeat([]byte("a"), 16), }, }, - Features: lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures), + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), AuthSigBytes: testSig.Serialize(), } dbNode.AddPubKey(nodeKey) @@ -287,7 +290,9 @@ func (d *databaseChannelGraph) addRandNode() (*btcec.PublicKey, error) { IP: bytes.Repeat([]byte("a"), 16), }, }, - Features: lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures), + Features: lnwire.NewFeatureVector( + nil, lnwire.Features, + ), AuthSigBytes: testSig.Serialize(), } dbNode.AddPubKey(nodeKey) diff --git a/channeldb/graph.go b/channeldb/graph.go index 46ba1311..275d6858 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -3506,7 +3506,7 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) { return LightningNode{}, err } - fv := lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures) + fv := lnwire.NewFeatureVector(nil, lnwire.Features) err = fv.Decode(r) if err != nil { return LightningNode{}, err diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index de8774a9..0768e829 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -36,7 +36,7 @@ var ( _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) - testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures) + testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features) ) func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) { diff --git a/channeldb/migration_01_to_11/graph.go b/channeldb/migration_01_to_11/graph.go index 8e8f4a4a..a782b9c4 100644 --- a/channeldb/migration_01_to_11/graph.go +++ b/channeldb/migration_01_to_11/graph.go @@ -744,7 +744,7 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) { return LightningNode{}, err } - fv := lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures) + fv := lnwire.NewFeatureVector(nil, nil) err = fv.Decode(r) if err != nil { return LightningNode{}, err diff --git a/channeldb/migration_01_to_11/graph_test.go b/channeldb/migration_01_to_11/graph_test.go index a65f0046..dc21fccf 100644 --- a/channeldb/migration_01_to_11/graph_test.go +++ b/channeldb/migration_01_to_11/graph_test.go @@ -25,7 +25,7 @@ var ( _, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) - testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures) + testFeatures = lnwire.NewFeatureVector(nil, nil) ) func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) { diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 5e8e0215..8c9ed6e5 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -1459,9 +1459,7 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement) error { } timestamp := time.Unix(int64(msg.Timestamp), 0) - features := lnwire.NewFeatureVector( - msg.Features, lnwire.GlobalFeatures, - ) + features := lnwire.NewFeatureVector(msg.Features, lnwire.Features) node := &channeldb.LightningNode{ HaveNodeAnnouncement: true, LastUpdate: timestamp, diff --git a/lnwire/features.go b/lnwire/features.go index 7ae0cac9..7700e2d5 100644 --- a/lnwire/features.go +++ b/lnwire/features.go @@ -85,30 +85,6 @@ const ( maxAllowedSize = 32764 ) -// LocalFeatures is a mapping of known connection-local feature bits to a -// descriptive name. All known local feature bits must be assigned a name in -// this mapping. Local features are those which are only sent to the peer and -// not advertised to the entire network. A full description of these feature -// bits is provided in the BOLT-09 specification. -var LocalFeatures = map[FeatureBit]string{ - DataLossProtectRequired: "data-loss-protect", - DataLossProtectOptional: "data-loss-protect", - InitialRoutingSync: "initial-routing-sync", - GossipQueriesRequired: "gossip-queries", - GossipQueriesOptional: "gossip-queries", -} - -// GlobalFeatures is a mapping of known global feature bits to a descriptive -// name. All known global feature bits must be assigned a name in this mapping. -// Global features are those which are advertised to the entire network. A full -// description of these feature bits is provided in the BOLT-09 specification. -var GlobalFeatures = map[FeatureBit]string{ - TLVOnionPayloadRequired: "tlv-onion", - TLVOnionPayloadOptional: "tlv-onion", - StaticRemoteKeyOptional: "static-remote-key", - StaticRemoteKeyRequired: "static-remote-key", -} - // 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. diff --git a/routing/notifications_test.go b/routing/notifications_test.go index a18f5c90..2371a4e1 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -28,7 +28,7 @@ var ( Port: 9000} testAddrs = []net.Addr{testAddr} - testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures) + testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features) testHash = [32]byte{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, diff --git a/server.go b/server.go index fbe88c26..87fa17dc 100644 --- a/server.go +++ b/server.go @@ -1971,7 +1971,7 @@ func (s *server) initTorController() error { Addresses: newNodeAnn.Addresses, Alias: newNodeAnn.Alias.String(), Features: lnwire.NewFeatureVector( - newNodeAnn.Features, lnwire.GlobalFeatures, + newNodeAnn.Features, lnwire.Features, ), Color: newNodeAnn.RGBColor, AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(),