mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 06:35:07 +01:00
Merge pull request #7568 from carlaKC/7094-updatefeaturemanager
Add config options for custom feature bits
This commit is contained in:
commit
728452636a
23 changed files with 844 additions and 145 deletions
|
@ -76,7 +76,7 @@ var updateNodeAnnouncementCommand = cli.Command{
|
|||
},
|
||||
cli.Int64SliceFlag{
|
||||
Name: "feature_bit_remove",
|
||||
Usage: "a feature bit that needs to be disabled" +
|
||||
Usage: "a feature bit that needs to be disabled. " +
|
||||
"Can be set multiple times in the same command",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -203,11 +203,14 @@ type Config struct {
|
|||
// notification for when it reconnects.
|
||||
NotifyWhenOffline func(peerPubKey [33]byte) <-chan struct{}
|
||||
|
||||
// SelfNodeAnnouncement is a function that fetches our own current node
|
||||
// announcement, for use when determining whether we should update our
|
||||
// peers about our presence on the network. If the refresh is true, a
|
||||
// new and updated announcement will be returned.
|
||||
SelfNodeAnnouncement func(refresh bool) (lnwire.NodeAnnouncement, error)
|
||||
// FetchSelfAnnouncement retrieves our current node announcement, for
|
||||
// use when determining whether we should update our peers about our
|
||||
// presence in the network.
|
||||
FetchSelfAnnouncement func() lnwire.NodeAnnouncement
|
||||
|
||||
// UpdateSelfAnnouncement produces a new announcement for our node with
|
||||
// an updated timestamp which can be broadcast to our peers.
|
||||
UpdateSelfAnnouncement func() (lnwire.NodeAnnouncement, error)
|
||||
|
||||
// ProofMatureDelta the number of confirmations which is needed before
|
||||
// exchange the channel announcement proofs.
|
||||
|
@ -1660,12 +1663,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
|
|||
}
|
||||
|
||||
// We'll also check that our NodeAnnouncement is not too old.
|
||||
currentNodeAnn, err := d.cfg.SelfNodeAnnouncement(false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get current node announment: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
currentNodeAnn := d.cfg.FetchSelfAnnouncement()
|
||||
timestamp := time.Unix(int64(currentNodeAnn.Timestamp), 0)
|
||||
timeElapsed := now.Sub(timestamp)
|
||||
|
||||
|
@ -1673,7 +1671,7 @@ func (d *AuthenticatedGossiper) retransmitStaleAnns(now time.Time) error {
|
|||
// node announcement, refresh it and resend it.
|
||||
nodeAnnStr := ""
|
||||
if timeElapsed >= d.cfg.RebroadcastInterval {
|
||||
newNodeAnn, err := d.cfg.SelfNodeAnnouncement(true)
|
||||
newNodeAnn, err := d.cfg.UpdateSelfAnnouncement()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get refreshed node "+
|
||||
"announcement: %v", err)
|
||||
|
|
|
@ -775,7 +775,14 @@ func createTestCtx(t *testing.T, startHeight uint32) (*testCtx, error) {
|
|||
c := make(chan struct{})
|
||||
return c
|
||||
},
|
||||
SelfNodeAnnouncement: func(bool) (lnwire.NodeAnnouncement, error) {
|
||||
FetchSelfAnnouncement: func() lnwire.NodeAnnouncement {
|
||||
return lnwire.NodeAnnouncement{
|
||||
Timestamp: testTimestamp,
|
||||
}
|
||||
},
|
||||
UpdateSelfAnnouncement: func() (lnwire.NodeAnnouncement,
|
||||
error) {
|
||||
|
||||
return lnwire.NodeAnnouncement{
|
||||
Timestamp: testTimestamp,
|
||||
}, nil
|
||||
|
@ -1446,28 +1453,30 @@ func TestSignatureAnnouncementRetryAtStartup(t *testing.T) {
|
|||
return lnwire.ShortChannelID{}, fmt.Errorf("no peer alias")
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
gossiper := New(Config{
|
||||
Notifier: ctx.gossiper.cfg.Notifier,
|
||||
Broadcast: ctx.gossiper.cfg.Broadcast,
|
||||
NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline,
|
||||
NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline,
|
||||
SelfNodeAnnouncement: ctx.gossiper.cfg.SelfNodeAnnouncement,
|
||||
Router: ctx.gossiper.cfg.Router,
|
||||
TrickleDelay: trickleDelay,
|
||||
RetransmitTicker: ticker.NewForce(retransmitDelay),
|
||||
RebroadcastInterval: rebroadcastInterval,
|
||||
ProofMatureDelta: proofMatureDelta,
|
||||
WaitingProofStore: ctx.gossiper.cfg.WaitingProofStore,
|
||||
MessageStore: ctx.gossiper.cfg.MessageStore,
|
||||
RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval),
|
||||
HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval),
|
||||
NumActiveSyncers: 3,
|
||||
MinimumBatchSize: 10,
|
||||
SubBatchDelay: time.Second * 5,
|
||||
IsAlias: isAlias,
|
||||
SignAliasUpdate: signAliasUpdate,
|
||||
FindBaseByAlias: findBaseByAlias,
|
||||
GetAlias: getAlias,
|
||||
Notifier: ctx.gossiper.cfg.Notifier,
|
||||
Broadcast: ctx.gossiper.cfg.Broadcast,
|
||||
NotifyWhenOnline: ctx.gossiper.reliableSender.cfg.NotifyWhenOnline,
|
||||
NotifyWhenOffline: ctx.gossiper.reliableSender.cfg.NotifyWhenOffline,
|
||||
FetchSelfAnnouncement: ctx.gossiper.cfg.FetchSelfAnnouncement,
|
||||
UpdateSelfAnnouncement: ctx.gossiper.cfg.UpdateSelfAnnouncement,
|
||||
Router: ctx.gossiper.cfg.Router,
|
||||
TrickleDelay: trickleDelay,
|
||||
RetransmitTicker: ticker.NewForce(retransmitDelay),
|
||||
RebroadcastInterval: rebroadcastInterval,
|
||||
ProofMatureDelta: proofMatureDelta,
|
||||
WaitingProofStore: ctx.gossiper.cfg.WaitingProofStore,
|
||||
MessageStore: ctx.gossiper.cfg.MessageStore,
|
||||
RotateTicker: ticker.NewForce(DefaultSyncerRotationInterval),
|
||||
HistoricalSyncTicker: ticker.NewForce(DefaultHistoricalSyncInterval),
|
||||
NumActiveSyncers: 3,
|
||||
MinimumBatchSize: 10,
|
||||
SubBatchDelay: time.Second * 5,
|
||||
IsAlias: isAlias,
|
||||
SignAliasUpdate: signAliasUpdate,
|
||||
FindBaseByAlias: findBaseByAlias,
|
||||
GetAlias: getAlias,
|
||||
}, &keychain.KeyDescriptor{
|
||||
PubKey: ctx.gossiper.selfKey,
|
||||
KeyLocator: ctx.gossiper.selfKeyLoc,
|
||||
|
|
|
@ -18,14 +18,25 @@
|
|||
* [SendOutputs](https://github.com/lightningnetwork/lnd/pull/7631) now adheres
|
||||
to the anchor channel reserve requirement.
|
||||
|
||||
* The [UpdateNodeAnnouncement](https://github.com/lightningnetwork/lnd/pull/7568)
|
||||
API can no longer be used to set/unset protocol features that are defined by
|
||||
LND.
|
||||
|
||||
Custom node announcement feature bits can also be specified in config using
|
||||
the `dev` build tag and `--protocol.custom-nodeann`, `--protocol.custom-init`
|
||||
and `--protocol.custom-invoice` flags to set feature bits for various feature
|
||||
"sets", as defined in [BOLT 9](https://github.com/lightning/bolts/blob/master/09-features.md).
|
||||
|
||||
## Misc
|
||||
|
||||
* [Generate default macaroons
|
||||
independently](https://github.com/lightningnetwork/lnd/pull/7592) on wallet
|
||||
unlock or create.
|
||||
|
||||
|
||||
# Contributors (Alphabetical Order)
|
||||
|
||||
* Carla Kirk-Cohen
|
||||
* Daniel McNally
|
||||
* Elle Mouton
|
||||
* hieblmi
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
package feature
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownSet is returned if a proposed feature vector contains a
|
||||
// set that is unknown to LND.
|
||||
ErrUnknownSet = errors.New("unknown feature bit set")
|
||||
|
||||
// ErrFeatureConfigured is returned if an attempt is made to unset a
|
||||
// feature that was configured at startup.
|
||||
ErrFeatureConfigured = errors.New("can't unset configured feature")
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
@ -44,6 +55,10 @@ type Config struct {
|
|||
// NoAnySegwit unsets any bits that signal support for using other
|
||||
// segwit witness versions for co-op closes.
|
||||
NoAnySegwit bool
|
||||
|
||||
// CustomFeatures is a set of custom features to advertise in each
|
||||
// set.
|
||||
CustomFeatures map[Set][]lnwire.FeatureBit
|
||||
}
|
||||
|
||||
// Manager is responsible for generating feature vectors for different requested
|
||||
|
@ -52,6 +67,11 @@ type Manager struct {
|
|||
// fsets is a static map of feature set to raw feature vectors. Requests
|
||||
// are fulfilled by cloning these internal feature vectors.
|
||||
fsets map[Set]*lnwire.RawFeatureVector
|
||||
|
||||
// configFeatures is a set of custom features that were "hard set" in
|
||||
// lnd's config that cannot be updated at runtime (as is the case with
|
||||
// our "standard" features that are defined in LND).
|
||||
configFeatures map[Set]*lnwire.FeatureVector
|
||||
}
|
||||
|
||||
// NewManager creates a new feature Manager, applying any custom modifications
|
||||
|
@ -90,6 +110,7 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
|||
}
|
||||
|
||||
// Now, remove any features as directed by the config.
|
||||
configFeatures := make(map[Set]*lnwire.FeatureVector)
|
||||
for set, raw := range fsets {
|
||||
if cfg.NoTLVOnion {
|
||||
raw.Unset(lnwire.TLVOnionPayloadOptional)
|
||||
|
@ -151,6 +172,32 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
|||
raw.Unset(lnwire.ShutdownAnySegwitRequired)
|
||||
}
|
||||
|
||||
for _, custom := range cfg.CustomFeatures[set] {
|
||||
if custom > set.Maximum() {
|
||||
return nil, fmt.Errorf("feature bit: %v "+
|
||||
"exceeds set: %v maximum: %v", custom,
|
||||
set, set.Maximum())
|
||||
}
|
||||
|
||||
if raw.IsSet(custom) {
|
||||
return nil, fmt.Errorf("feature bit: %v "+
|
||||
"already set", custom)
|
||||
}
|
||||
|
||||
if err := raw.SafeSet(custom); err != nil {
|
||||
return nil, fmt.Errorf("%w: could not set "+
|
||||
"feature: %d", err, custom)
|
||||
}
|
||||
}
|
||||
|
||||
// Track custom features separately so that we can check that
|
||||
// they aren't unset in subsequent updates. If there is no
|
||||
// entry for the set, the vector will just be empty.
|
||||
configFeatures[set] = lnwire.NewFeatureVector(
|
||||
lnwire.NewRawFeatureVector(cfg.CustomFeatures[set]...),
|
||||
lnwire.Features,
|
||||
)
|
||||
|
||||
// Ensure that all of our feature sets properly set any
|
||||
// dependent features.
|
||||
fv := lnwire.NewFeatureVector(raw, lnwire.Features)
|
||||
|
@ -162,7 +209,8 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
|
|||
}
|
||||
|
||||
return &Manager{
|
||||
fsets: fsets,
|
||||
fsets: fsets,
|
||||
configFeatures: configFeatures,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -176,8 +224,8 @@ func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector {
|
|||
return lnwire.NewRawFeatureVector()
|
||||
}
|
||||
|
||||
// SetRaw sets a new raw feature vector for the given set.
|
||||
func (m *Manager) SetRaw(set Set, raw *lnwire.RawFeatureVector) {
|
||||
// setRaw sets a new raw feature vector for the given set.
|
||||
func (m *Manager) setRaw(set Set, raw *lnwire.RawFeatureVector) {
|
||||
m.fsets[set] = raw
|
||||
}
|
||||
|
||||
|
@ -198,3 +246,54 @@ func (m *Manager) ListSets() []Set {
|
|||
|
||||
return sets
|
||||
}
|
||||
|
||||
// UpdateFeatureSets accepts a map of new feature vectors for each of the
|
||||
// manager's known sets, validates that the update can be applied and modifies
|
||||
// the feature manager's internal state. If a set is not included in the update
|
||||
// map, it is left unchanged. The feature vectors provided are expected to
|
||||
// include the current set of features, updated with desired bits added/removed.
|
||||
func (m *Manager) UpdateFeatureSets(
|
||||
updates map[Set]*lnwire.RawFeatureVector) error {
|
||||
|
||||
for set, newFeatures := range updates {
|
||||
if !set.valid() {
|
||||
return fmt.Errorf("%w: set: %d", ErrUnknownSet, set)
|
||||
}
|
||||
|
||||
if err := newFeatures.ValidatePairs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.Get(set).ValidateUpdate(
|
||||
newFeatures, set.Maximum(),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If any features were configured for this set, ensure that
|
||||
// they are still set in the new feature vector.
|
||||
if cfgFeat, haveCfgFeat := m.configFeatures[set]; haveCfgFeat {
|
||||
for feature := range cfgFeat.Features() {
|
||||
if !newFeatures.IsSet(feature) {
|
||||
return fmt.Errorf("%w: can't unset: "+
|
||||
"%d", ErrFeatureConfigured,
|
||||
feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fv := lnwire.NewFeatureVector(newFeatures, lnwire.Features)
|
||||
if err := ValidateDeps(fv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Only update the current feature sets once every proposed set has
|
||||
// passed validation so that we don't partially update any sets then
|
||||
// fail out on a later set's validation.
|
||||
for set, features := range updates {
|
||||
m.setRaw(set, features.Clone())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -135,3 +135,157 @@ func testManager(t *testing.T, test managerTest) {
|
|||
assertSet(lnwire.StaticRemoteKeyOptional)
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateFeatureSets tests validation of the update of various features in
|
||||
// each of our sets, asserting that the feature set is not partially modified
|
||||
// if one set in incorrectly specified.
|
||||
func TestUpdateFeatureSets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Use a reduced set description to make reasoning about our sets
|
||||
// easier.
|
||||
setDesc := setDesc{
|
||||
lnwire.DataLossProtectRequired: {
|
||||
SetInit: {}, // I
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
lnwire.GossipQueriesOptional: {
|
||||
SetNodeAnn: {}, // N
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
features map[Set]*lnwire.RawFeatureVector
|
||||
config Config
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "unknown set",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
setSentinel + 1: lnwire.NewRawFeatureVector(),
|
||||
},
|
||||
err: ErrUnknownSet,
|
||||
},
|
||||
{
|
||||
name: "invalid pairwise feature",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetNodeAnn: lnwire.NewRawFeatureVector(
|
||||
lnwire.FeatureBit(1000),
|
||||
lnwire.FeatureBit(1001),
|
||||
),
|
||||
},
|
||||
err: lnwire.ErrFeaturePairExists,
|
||||
},
|
||||
{
|
||||
name: "error in one set",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetNodeAnn: lnwire.NewRawFeatureVector(
|
||||
lnwire.FeatureBit(1000),
|
||||
lnwire.FeatureBit(1001),
|
||||
),
|
||||
SetInit: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
),
|
||||
},
|
||||
err: lnwire.ErrFeaturePairExists,
|
||||
},
|
||||
{
|
||||
name: "update existing sets ok",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetInit: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
lnwire.FeatureBit(1001),
|
||||
),
|
||||
SetNodeAnn: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
lnwire.GossipQueriesOptional,
|
||||
lnwire.FeatureBit(1000),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update new, valid set ok",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetInvoice: lnwire.NewRawFeatureVector(
|
||||
lnwire.FeatureBit(1001),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing configured feature",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetInit: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
),
|
||||
SetNodeAnn: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
lnwire.GossipQueriesOptional,
|
||||
),
|
||||
},
|
||||
config: Config{
|
||||
CustomFeatures: map[Set][]lnwire.FeatureBit{
|
||||
SetInit: {
|
||||
lnwire.FeatureBit(333),
|
||||
},
|
||||
},
|
||||
},
|
||||
err: ErrFeatureConfigured,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
features: map[Set]*lnwire.RawFeatureVector{
|
||||
SetInit: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
),
|
||||
SetNodeAnn: lnwire.NewRawFeatureVector(
|
||||
lnwire.DataLossProtectRequired,
|
||||
lnwire.GossipQueriesOptional,
|
||||
lnwire.FeatureBit(500),
|
||||
),
|
||||
SetInvoice: lnwire.NewRawFeatureVector(
|
||||
lnwire.FeatureBit(333),
|
||||
),
|
||||
},
|
||||
config: Config{
|
||||
CustomFeatures: map[Set][]lnwire.FeatureBit{
|
||||
SetInvoice: {
|
||||
lnwire.FeatureBit(333),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
featureMgr, err := newManager(testCase.config, setDesc)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = featureMgr.UpdateFeatureSets(testCase.features)
|
||||
require.ErrorIs(t, err, testCase.err)
|
||||
|
||||
// Compare the feature manager's sets to the updated
|
||||
// set if no error was hit, otherwise assert that it
|
||||
// is unchanged.
|
||||
expected := testCase.features
|
||||
actual := featureMgr
|
||||
if err != nil {
|
||||
originalMgr, err := newManager(
|
||||
testCase.config, setDesc,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
expected = originalMgr.fsets
|
||||
}
|
||||
|
||||
for set, expectedFeatures := range expected {
|
||||
actualSet := actual.GetRaw(set)
|
||||
require.True(t,
|
||||
actualSet.Equals(expectedFeatures))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
package feature
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
@ -26,8 +32,19 @@ const (
|
|||
// SetInvoiceAmp identifies the features that should be advertised on
|
||||
// AMP invoices generated by the daemon.
|
||||
SetInvoiceAmp
|
||||
|
||||
// setSentinel is used to mark the end of our known sets. This enum
|
||||
// member must *always* be the last item in the iota list to ensure
|
||||
// that validation works as expected.
|
||||
setSentinel
|
||||
)
|
||||
|
||||
// valid returns a boolean indicating whether a set value is one of our
|
||||
// predefined feature sets.
|
||||
func (s Set) valid() bool {
|
||||
return s < setSentinel
|
||||
}
|
||||
|
||||
// String returns a human-readable description of a Set.
|
||||
func (s Set) String() string {
|
||||
switch s {
|
||||
|
@ -45,3 +62,22 @@ func (s Set) String() string {
|
|||
return "SetUnknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum returns the maximum allowable value for a feature bit in the context
|
||||
// of a set. The maximum feature value we can express differs by set context
|
||||
// because the amount of space available varies between protocol messages. In
|
||||
// practice this should never be a problem (reasonably one would never hit
|
||||
// these high ranges), but we enforce these maximums for the sake of sane
|
||||
// validation.
|
||||
func (s Set) Maximum() lnwire.FeatureBit {
|
||||
switch s {
|
||||
case SetInvoice, SetInvoiceAmp:
|
||||
return lnwire.MaxBolt11Feature
|
||||
|
||||
// The space available in other sets is > math.MaxUint16, so we just
|
||||
// return the maximum value our expression of a feature bit allows so
|
||||
// that any value will pass.
|
||||
default:
|
||||
return math.MaxUint16
|
||||
}
|
||||
}
|
||||
|
|
|
@ -373,7 +373,8 @@ type Config struct {
|
|||
msg []byte, doubleHash bool) (*ecdsa.Signature, error)
|
||||
|
||||
// CurrentNodeAnnouncement should return the latest, fully signed node
|
||||
// announcement from the backing Lightning Network node.
|
||||
// announcement from the backing Lightning Network node with a fresh
|
||||
// timestamp.
|
||||
CurrentNodeAnnouncement func() (lnwire.NodeAnnouncement, error)
|
||||
|
||||
// SendAnnouncement is used by the FundingManager to send announcement
|
||||
|
|
|
@ -531,4 +531,8 @@ var allTestCases = []*lntest.TestCase{
|
|||
Name: "htlc timeout resolver extract preimage local",
|
||||
TestFunc: testHtlcTimeoutResolverExtractPreimageLocal,
|
||||
},
|
||||
{
|
||||
Name: "custom features",
|
||||
TestFunc: testCustomFeatures,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/lightningnetwork/lnd/lntest/node"
|
||||
"github.com/lightningnetwork/lnd/lntest/wait"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -491,10 +492,29 @@ func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
|
|||
)
|
||||
}
|
||||
|
||||
// This feature bit is used to test that our endpoint sets/unsets
|
||||
// feature bits properly. If the current FeatureBit is set by default
|
||||
// update this one for another one unset by default at random.
|
||||
featureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ
|
||||
// Test that we can't modify a feature bit that is known to LND.
|
||||
reservedFeatureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ
|
||||
_, known := lnwire.Features[lnwire.FeatureBit(reservedFeatureBit)]
|
||||
require.True(ht, known, "feature bit should be known to lnd")
|
||||
|
||||
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
|
||||
FeatureUpdates: []*peersrpc.UpdateFeatureAction{
|
||||
{
|
||||
Action: peersrpc.UpdateAction_ADD,
|
||||
FeatureBit: reservedFeatureBit,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Node announcement should fail because we're not allowed to alter
|
||||
// "known" feature bits.
|
||||
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
|
||||
|
||||
// We also set an unknown feature bit (which we should be able to
|
||||
// set/unset) and test that we can update it accordingly.
|
||||
featureBit := lnrpc.FeatureBit(999)
|
||||
_, known = lnwire.Features[lnwire.FeatureBit(featureBit)]
|
||||
require.False(ht, known, "feature bit should be unknown to lnd")
|
||||
featureIdx := uint32(featureBit)
|
||||
_, ok := resp.Features[featureIdx]
|
||||
require.False(ht, ok, "unexpected feature bit enabled by default")
|
||||
|
@ -566,7 +586,7 @@ func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
|
|||
},
|
||||
}
|
||||
|
||||
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
|
||||
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
|
||||
Alias: newAlias,
|
||||
Color: newColor,
|
||||
AddressUpdates: updateAddressActions,
|
||||
|
|
96
itest/lnd_custom_features.go
Normal file
96
itest/lnd_custom_features.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package itest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
|
||||
"github.com/lightningnetwork/lnd/lntest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// testCustomFeatures tests advertisement of custom features in various bolt 9
|
||||
// sets. For completeness, it also asserts that features aren't set in places
|
||||
// where they aren't intended to be.
|
||||
func testCustomFeatures(ht *lntest.HarnessTest) {
|
||||
var (
|
||||
// Odd custom features so that we don't need to worry about
|
||||
// issues connecting to peers.
|
||||
customInit uint32 = 101
|
||||
customNodeAnn uint32 = 103
|
||||
customInvoice uint32 = 105
|
||||
)
|
||||
|
||||
// Restart Alice with custom feature bits configured.
|
||||
extraArgs := []string{
|
||||
fmt.Sprintf("--protocol.custom-init=%v", customInit),
|
||||
fmt.Sprintf("--protocol.custom-nodeann=%v", customNodeAnn),
|
||||
fmt.Sprintf("--protocol.custom-invoice=%v", customInvoice),
|
||||
}
|
||||
ht.RestartNodeWithExtraArgs(ht.Alice, extraArgs)
|
||||
|
||||
// Connect nodes and open a channel so that Alice will be included
|
||||
// in Bob's graph.
|
||||
ht.ConnectNodes(ht.Alice, ht.Bob)
|
||||
chanPoint := ht.OpenChannel(
|
||||
ht.Alice, ht.Bob, lntest.OpenChannelParams{Amt: 1000000},
|
||||
)
|
||||
|
||||
// Check that Alice's custom feature bit was sent to Bob in her init
|
||||
// message.
|
||||
peers := ht.Bob.RPC.ListPeers()
|
||||
require.Len(ht, peers.Peers, 1)
|
||||
require.Equal(ht, peers.Peers[0].PubKey, ht.Alice.PubKeyStr)
|
||||
|
||||
_, customInitSet := peers.Peers[0].Features[customInit]
|
||||
require.True(ht, customInitSet)
|
||||
assertFeatureNotInSet(
|
||||
ht, []uint32{customNodeAnn, customInvoice},
|
||||
peers.Peers[0].Features,
|
||||
)
|
||||
|
||||
// Assert that Alice's custom feature bit is contained in the node
|
||||
// announcement sent to Bob.
|
||||
updates := ht.AssertNumNodeAnns(ht.Bob, ht.Alice.PubKeyStr, 1)
|
||||
features := updates[len(updates)-1].Features
|
||||
_, customFeature := features[customNodeAnn]
|
||||
require.True(ht, customFeature)
|
||||
assertFeatureNotInSet(
|
||||
ht, []uint32{customInit, customInvoice}, features,
|
||||
)
|
||||
|
||||
// Assert that Alice's custom feature bit is included in invoices.
|
||||
invoice := ht.Alice.RPC.AddInvoice(&lnrpc.Invoice{})
|
||||
payReq := ht.Alice.RPC.DecodePayReq(invoice.PaymentRequest)
|
||||
_, customInvoiceSet := payReq.Features[customInvoice]
|
||||
require.True(ht, customInvoiceSet)
|
||||
assertFeatureNotInSet(
|
||||
ht, []uint32{customInit, customNodeAnn}, payReq.Features,
|
||||
)
|
||||
|
||||
// Check that Alice can't unset configured features via the node
|
||||
// announcement update API. This is only checked for node announcement
|
||||
// because it is the only set that can be updated via the API.
|
||||
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
|
||||
FeatureUpdates: []*peersrpc.UpdateFeatureAction{
|
||||
{
|
||||
Action: peersrpc.UpdateAction_ADD,
|
||||
FeatureBit: lnrpc.FeatureBit(customNodeAnn),
|
||||
},
|
||||
},
|
||||
}
|
||||
ht.Alice.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
|
||||
|
||||
ht.CloseChannel(ht.Alice, chanPoint)
|
||||
}
|
||||
|
||||
// assertFeatureNotInSet checks that the features provided aren't contained in
|
||||
// a feature map.
|
||||
func assertFeatureNotInSet(ht *lntest.HarnessTest, features []uint32,
|
||||
advertised map[uint32]*lnrpc.Feature) {
|
||||
|
||||
for _, feature := range features {
|
||||
_, featureSet := advertised[feature]
|
||||
require.False(ht, featureSet)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,11 @@
|
|||
|
||||
package lncfg
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// ExperimentalProtocol is a sub-config that houses any experimental protocol
|
||||
// features that also require a build-tag to activate.
|
||||
type ExperimentalProtocol struct {
|
||||
|
@ -13,3 +18,8 @@ type ExperimentalProtocol struct {
|
|||
func (p ExperimentalProtocol) CustomMessageOverrides() []uint16 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CustomFeatures returns a custom set of feature bits to advertise.
|
||||
func (p ExperimentalProtocol) CustomFeatures() map[feature.Set][]lnwire.FeatureBit {
|
||||
return map[feature.Set][]lnwire.FeatureBit{}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,23 @@
|
|||
|
||||
package lncfg
|
||||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// ExperimentalProtocol is a sub-config that houses any experimental protocol
|
||||
// features that also require a build-tag to activate.
|
||||
//
|
||||
//nolint:lll
|
||||
type ExperimentalProtocol struct {
|
||||
CustomMessage []uint16 `long:"custom-message" description:"allows the custom message apis to send and report messages with the protocol number provided that fall outside of the custom message number range."`
|
||||
|
||||
CustomInit []uint16 `long:"custom-init" description:"custom feature bits to advertise in the node's init message"`
|
||||
|
||||
CustomNodeAnn []uint16 `long:"custom-nodeann" description:"custom feature bits to advertise in the node's announcement message"`
|
||||
|
||||
CustomInvoice []uint16 `long:"custom-invoice" description:"custom feature bits to advertise in the node's invoices"`
|
||||
}
|
||||
|
||||
// CustomMessageOverrides returns the set of protocol messages that we override
|
||||
|
@ -16,3 +27,25 @@ type ExperimentalProtocol struct {
|
|||
func (p ExperimentalProtocol) CustomMessageOverrides() []uint16 {
|
||||
return p.CustomMessage
|
||||
}
|
||||
|
||||
// CustomFeatures returns a custom set of feature bits to advertise.
|
||||
//
|
||||
//nolint:lll
|
||||
func (p ExperimentalProtocol) CustomFeatures() map[feature.Set][]lnwire.FeatureBit {
|
||||
customFeatures := make(map[feature.Set][]lnwire.FeatureBit)
|
||||
|
||||
setFeatures := func(set feature.Set, bits []uint16) {
|
||||
for _, customFeature := range bits {
|
||||
customFeatures[set] = append(
|
||||
customFeatures[set],
|
||||
lnwire.FeatureBit(customFeature),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setFeatures(feature.SetInit, p.CustomInit)
|
||||
setFeatures(feature.SetNodeAnn, p.CustomNodeAnn)
|
||||
setFeatures(feature.SetInvoice, p.CustomInvoice)
|
||||
|
||||
return customFeatures
|
||||
}
|
||||
|
|
|
@ -18,12 +18,15 @@ import (
|
|||
type Config struct {
|
||||
// GetNodeAnnouncement is used to send our retrieve the current
|
||||
// node announcement information.
|
||||
GetNodeAnnouncement func() (lnwire.NodeAnnouncement, error)
|
||||
GetNodeAnnouncement func() lnwire.NodeAnnouncement
|
||||
|
||||
// ParseAddr parses an address from its string format to a net.Addr.
|
||||
ParseAddr func(addr string) (net.Addr, error)
|
||||
|
||||
// UpdateNodeAnnouncement updates our node announcement applying the
|
||||
// given NodeAnnModifiers and broadcasts the new version to the network.
|
||||
UpdateNodeAnnouncement func(...netann.NodeAnnModifier) error
|
||||
// UpdateNodeAnnouncement updates and broadcasts our node announcement,
|
||||
// setting the feature vector provided and applying the
|
||||
// NodeAnnModifiers. If no feature updates are required, a nil feature
|
||||
// vector should be provided.
|
||||
UpdateNodeAnnouncement func(features *lnwire.RawFeatureVector,
|
||||
mods ...netann.NodeAnnModifier) error
|
||||
}
|
||||
|
|
|
@ -313,26 +313,23 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context,
|
|||
resp := &NodeAnnouncementUpdateResponse{}
|
||||
nodeModifiers := make([]netann.NodeAnnModifier, 0)
|
||||
|
||||
currentNodeAnn, err := s.cfg.GetNodeAnnouncement()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get current node "+
|
||||
"announcement: %v", err)
|
||||
}
|
||||
currentNodeAnn := s.cfg.GetNodeAnnouncement()
|
||||
|
||||
if len(req.FeatureUpdates) > 0 {
|
||||
features, ops, err := s.updateFeatures(
|
||||
currentNodeAnn.Features,
|
||||
req.FeatureUpdates,
|
||||
nodeAnnFeatures := currentNodeAnn.Features
|
||||
featureUpdates := len(req.FeatureUpdates) > 0
|
||||
if featureUpdates {
|
||||
var (
|
||||
ops *lnrpc.Op
|
||||
err error
|
||||
)
|
||||
nodeAnnFeatures, ops, err = s.updateFeatures(
|
||||
nodeAnnFeatures, req.FeatureUpdates,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error trying to update node "+
|
||||
"features: %w", err)
|
||||
}
|
||||
resp.Ops = append(resp.Ops, ops)
|
||||
nodeModifiers = append(
|
||||
nodeModifiers,
|
||||
netann.NodeAnnSetFeatures(features),
|
||||
)
|
||||
}
|
||||
|
||||
if req.Color != "" {
|
||||
|
@ -390,12 +387,14 @@ func (s *Server) UpdateNodeAnnouncement(_ context.Context,
|
|||
)
|
||||
}
|
||||
|
||||
if len(nodeModifiers) == 0 {
|
||||
return nil, fmt.Errorf("unable detect any new values to " +
|
||||
if len(nodeModifiers) == 0 && !featureUpdates {
|
||||
return nil, fmt.Errorf("unable to detect any new values to " +
|
||||
"update the node announcement")
|
||||
}
|
||||
|
||||
if err := s.cfg.UpdateNodeAnnouncement(nodeModifiers...); err != nil {
|
||||
if err := s.cfg.UpdateNodeAnnouncement(
|
||||
nodeAnnFeatures, nodeModifiers...,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package lnwire
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -10,6 +11,15 @@ 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")
|
||||
|
||||
// ErrFeatureStandard is returned when attempts to modify LND's known
|
||||
// set of features are made.
|
||||
ErrFeatureStandard = errors.New("feature is used in standard " +
|
||||
"protocol set")
|
||||
|
||||
// ErrFeatureBitMaximum is returned when a feature bit exceeds the
|
||||
// maximum allowable value.
|
||||
ErrFeatureBitMaximum = errors.New("feature bit exceeds allowed maximum")
|
||||
)
|
||||
|
||||
// FeatureBit represents a feature that can be enabled in either a local or
|
||||
|
@ -221,17 +231,18 @@ const (
|
|||
// TODO: Decide on actual feature bit value.
|
||||
ScriptEnforcedLeaseOptional FeatureBit = 2023
|
||||
|
||||
// maxAllowedSize is a maximum allowed size of feature vector.
|
||||
// MaxBolt11Feature is the maximum feature bit value allowed in bolt 11
|
||||
// invoices.
|
||||
//
|
||||
// NOTE: Within the protocol, the maximum allowed message size is 65535
|
||||
// bytes for all messages. Accounting for the overhead within the feature
|
||||
// message to signal the type of message, that leaves us with 65533 bytes
|
||||
// for the init message itself. Next, we reserve 4 bytes to encode the
|
||||
// lengths of both the local and global feature vectors, so 65529 bytes
|
||||
// for the local and global features. Knocking off one byte for the sake
|
||||
// of the calculation, that leads us to 32764 bytes for each feature
|
||||
// vector, or 131056 different features.
|
||||
maxAllowedSize = 32764
|
||||
// The base 32 encoded tagged fields in invoices are limited to 10 bits
|
||||
// to express the length of the field's data.
|
||||
//nolint:lll
|
||||
// See: https://github.com/lightning/bolts/blob/master/11-payment-encoding.md#tagged-fields
|
||||
//
|
||||
// With a maximum length field of 1023 (2^10 -1) and 5 bit encoding,
|
||||
// the highest feature bit that can be expressed is:
|
||||
// 1023 * 5 - 1 = 5114.
|
||||
MaxBolt11Feature = 5114
|
||||
)
|
||||
|
||||
// IsRequired returns true if the feature bit is even, and false otherwise.
|
||||
|
@ -344,6 +355,67 @@ func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValidateUpdate checks whether a feature vector can safely be updated to the
|
||||
// new feature vector provided, checking that it does not alter any of the
|
||||
// "standard" features that are defined by LND. The new feature vector should
|
||||
// be inclusive of all features in the original vector that it still wants to
|
||||
// advertise, setting and unsetting updates as desired. Features in the vector
|
||||
// are also checked against a maximum inclusive value, as feature vectors in
|
||||
// different contexts have different maximum values.
|
||||
func (fv *RawFeatureVector) ValidateUpdate(other *RawFeatureVector,
|
||||
maximumValue FeatureBit) error {
|
||||
|
||||
// Run through the new set of features and check that we're not adding
|
||||
// any feature bits that are defined but not set in LND.
|
||||
for feature := range other.features {
|
||||
if fv.IsSet(feature) {
|
||||
continue
|
||||
}
|
||||
|
||||
if feature > maximumValue {
|
||||
return fmt.Errorf("can't set feature bit %d: %w %v",
|
||||
feature, ErrFeatureBitMaximum,
|
||||
maximumValue)
|
||||
}
|
||||
|
||||
if name, known := Features[feature]; known {
|
||||
return fmt.Errorf("can't set feature "+
|
||||
"bit %d (%v): %w", feature, name,
|
||||
ErrFeatureStandard)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the new feature vector for this set does not unset any
|
||||
// features that are standard in LND by comparing the features in our
|
||||
// current set to the omitted values in the new set.
|
||||
for feature := range fv.features {
|
||||
if other.IsSet(feature) {
|
||||
continue
|
||||
}
|
||||
|
||||
if name, known := Features[feature]; known {
|
||||
return fmt.Errorf("can't unset feature "+
|
||||
"bit %d (%v): %w", feature, name,
|
||||
ErrFeatureStandard)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePairs checks each feature bit in a raw vector to ensure that the
|
||||
// opposing bit is not set, validating that the vector has either the optional
|
||||
// or required bit set, not both.
|
||||
func (fv *RawFeatureVector) ValidatePairs() error {
|
||||
for feature := range fv.features {
|
||||
if _, ok := fv.features[feature^1]; ok {
|
||||
return ErrFeaturePairExists
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone makes a copy of a feature vector.
|
||||
func (fv *RawFeatureVector) Clone() *RawFeatureVector {
|
||||
newFeatures := NewRawFeatureVector()
|
||||
|
|
|
@ -2,6 +2,7 @@ package lnwire
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
@ -396,3 +397,134 @@ func TestIsEmptyFeatureVector(t *testing.T) {
|
|||
fv.Unset(StaticRemoteKeyOptional)
|
||||
require.True(t, fv.IsEmpty())
|
||||
}
|
||||
|
||||
// TestValidatePairs tests that feature vectors can only set the required or
|
||||
// optional feature bit in a pair, not both.
|
||||
func TestValidatePairs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rfv := NewRawFeatureVector(
|
||||
StaticRemoteKeyOptional,
|
||||
StaticRemoteKeyRequired,
|
||||
)
|
||||
require.Equal(t, ErrFeaturePairExists, rfv.ValidatePairs())
|
||||
|
||||
rfv = NewRawFeatureVector(
|
||||
StaticRemoteKeyOptional,
|
||||
PaymentAddrRequired,
|
||||
)
|
||||
require.Nil(t, rfv.ValidatePairs())
|
||||
}
|
||||
|
||||
// TestValidateUpdate tests validation of an update to a feature vector.
|
||||
func TestValidateUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
currentFeatures []FeatureBit
|
||||
newFeatures []FeatureBit
|
||||
maximumValue FeatureBit
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "defined feature bit set, can include",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "defined feature bit not already set",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
PaymentAddrRequired,
|
||||
},
|
||||
err: ErrFeatureStandard,
|
||||
},
|
||||
{
|
||||
name: "known feature missing",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
PaymentAddrRequired,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
err: ErrFeatureStandard,
|
||||
},
|
||||
{
|
||||
name: "can set unknown feature",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
FeatureBit(1001),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "can unset unknown feature",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
FeatureBit(1001),
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "at allowed maximum",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
100,
|
||||
},
|
||||
maximumValue: 100,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "above allowed maximum",
|
||||
currentFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
},
|
||||
newFeatures: []FeatureBit{
|
||||
StaticRemoteKeyOptional,
|
||||
101,
|
||||
},
|
||||
maximumValue: 100,
|
||||
err: ErrFeatureBitMaximum,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
currentFV := NewRawFeatureVector(
|
||||
testCase.currentFeatures...,
|
||||
)
|
||||
newFV := NewRawFeatureVector(testCase.newFeatures...)
|
||||
|
||||
// Set maximum value if not populated in the test case.
|
||||
maximumValue := testCase.maximumValue
|
||||
if testCase.maximumValue == 0 {
|
||||
maximumValue = math.MaxUint16
|
||||
}
|
||||
|
||||
err := currentFV.ValidateUpdate(newFV, maximumValue)
|
||||
require.ErrorIs(t, err, testCase.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,15 +168,18 @@ func (h *HostAnnouncer) hostWatcher() {
|
|||
// NodeAnnUpdater describes a function that's able to update our current node
|
||||
// announcement on disk. It returns the updated node announcement given a set
|
||||
// of updates to be applied to the current node announcement.
|
||||
type NodeAnnUpdater func(refresh bool, modifier ...NodeAnnModifier,
|
||||
type NodeAnnUpdater func(modifier ...NodeAnnModifier,
|
||||
) (lnwire.NodeAnnouncement, error)
|
||||
|
||||
// IPAnnouncer is a factory function that generates a new function that uses
|
||||
// the passed annUpdater function to to announce new IP changes for a given
|
||||
// host.
|
||||
func IPAnnouncer(annUpdater NodeAnnUpdater) func([]net.Addr, map[string]struct{}) error {
|
||||
func IPAnnouncer(annUpdater NodeAnnUpdater) func([]net.Addr,
|
||||
map[string]struct{}) error {
|
||||
|
||||
return func(newAddrs []net.Addr, oldAddrs map[string]struct{}) error {
|
||||
_, err := annUpdater(true, func(currentNodeAnn *lnwire.NodeAnnouncement) {
|
||||
_, err := annUpdater(nil, func(
|
||||
currentNodeAnn *lnwire.NodeAnnouncement) {
|
||||
// To ensure we don't duplicate any addresses, we'll
|
||||
// filter out the same of addresses we should no longer
|
||||
// advertise.
|
||||
|
|
|
@ -60,18 +60,11 @@ func NodeAnnSetTimestamp(nodeAnn *lnwire.NodeAnnouncement) {
|
|||
nodeAnn.Timestamp = newTimestamp
|
||||
}
|
||||
|
||||
// SignNodeAnnouncement applies the given modifies to the passed
|
||||
// lnwire.NodeAnnouncement, then signs the resulting announcement. The provided
|
||||
// update should be the most recent, valid update, otherwise the timestamp may
|
||||
// not monotonically increase from the prior.
|
||||
// SignNodeAnnouncement signs the lnwire.NodeAnnouncement provided, which
|
||||
// should be the most recent, valid update, otherwise the timestamp may not
|
||||
// monotonically increase from the prior.
|
||||
func SignNodeAnnouncement(signer lnwallet.MessageSigner,
|
||||
keyLoc keychain.KeyLocator, nodeAnn *lnwire.NodeAnnouncement,
|
||||
mods ...NodeAnnModifier) error {
|
||||
|
||||
// Apply the requested changes to the node announcement.
|
||||
for _, modifier := range mods {
|
||||
modifier(nodeAnn)
|
||||
}
|
||||
keyLoc keychain.KeyLocator, nodeAnn *lnwire.NodeAnnouncement) error {
|
||||
|
||||
// Create the DER-encoded ECDSA signature over the message digest.
|
||||
sig, err := SignAnnouncement(signer, keyLoc, nodeAnn)
|
||||
|
|
|
@ -263,8 +263,8 @@ type Config struct {
|
|||
|
||||
// GenNodeAnnouncement is used to send our node announcement to the remote
|
||||
// on startup.
|
||||
GenNodeAnnouncement func(bool,
|
||||
...netann.NodeAnnModifier) (lnwire.NodeAnnouncement, error)
|
||||
GenNodeAnnouncement func(...netann.NodeAnnModifier) (
|
||||
lnwire.NodeAnnouncement, error)
|
||||
|
||||
// PrunePersistentPeerConnection is used to remove all internal state
|
||||
// related to this peer in the server.
|
||||
|
@ -1036,7 +1036,7 @@ func (p *Brontide) maybeSendNodeAnn(channels []*channeldb.OpenChannel) {
|
|||
return
|
||||
}
|
||||
|
||||
ourNodeAnn, err := p.cfg.GenNodeAnnouncement(false)
|
||||
ourNodeAnn, err := p.cfg.GenNodeAnnouncement()
|
||||
if err != nil {
|
||||
p.log.Debugf("Unable to retrieve node announcement: %v", err)
|
||||
return
|
||||
|
|
13
rpcserver.go
13
rpcserver.go
|
@ -762,10 +762,6 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
|
|||
return s.featureMgr.Get(feature.SetInvoiceAmp)
|
||||
}
|
||||
|
||||
getNodeAnnouncement := func() (lnwire.NodeAnnouncement, error) {
|
||||
return s.genNodeAnnouncement(false)
|
||||
}
|
||||
|
||||
parseAddr := func(addr string) (net.Addr, error) {
|
||||
return parseAddr(addr, r.cfg.net)
|
||||
}
|
||||
|
@ -786,7 +782,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
|
|||
routerBackend, s.nodeSigner, s.graphDB, s.chanStateDB,
|
||||
s.sweeper, tower, s.towerClient, s.anchorTowerClient,
|
||||
r.cfg.net.ResolveTCPAddr, genInvoiceFeatures,
|
||||
genAmpInvoiceFeatures, getNodeAnnouncement,
|
||||
genAmpInvoiceFeatures, s.getNodeAnnouncement,
|
||||
s.updateAndBrodcastSelfNode, parseAddr, rpcsLog,
|
||||
s.aliasMgr.GetPeerAlias,
|
||||
)
|
||||
|
@ -2910,11 +2906,8 @@ func (r *rpcServer) GetInfo(_ context.Context,
|
|||
|
||||
// Check if external IP addresses were provided to lnd and use them
|
||||
// to set the URIs.
|
||||
nodeAnn, err := r.server.genNodeAnnouncement(false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve current fully signed "+
|
||||
"node announcement: %v", err)
|
||||
}
|
||||
nodeAnn := r.server.getNodeAnnouncement()
|
||||
|
||||
addrs := nodeAnn.Addresses
|
||||
uris := make([]string, len(addrs))
|
||||
for i, addr := range addrs {
|
||||
|
|
116
server.go
116
server.go
|
@ -530,6 +530,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout,
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
featureMgr, err := feature.NewManager(feature.Config{
|
||||
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
|
||||
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
|
||||
|
@ -540,6 +541,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
NoOptionScidAlias: !cfg.ProtocolOptions.ScidAlias(),
|
||||
NoZeroConf: !cfg.ProtocolOptions.ZeroConf(),
|
||||
NoAnySegwit: cfg.ProtocolOptions.NoAnySegwit(),
|
||||
CustomFeatures: cfg.ProtocolOptions.ExperimentalProtocol.CustomFeatures(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -993,15 +995,18 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
}
|
||||
|
||||
s.authGossiper = discovery.New(discovery.Config{
|
||||
Router: s.chanRouter,
|
||||
Notifier: s.cc.ChainNotifier,
|
||||
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
|
||||
Broadcast: s.BroadcastMessage,
|
||||
ChanSeries: chanSeries,
|
||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||
NotifyWhenOffline: s.NotifyWhenOffline,
|
||||
SelfNodeAnnouncement: func(refresh bool) (lnwire.NodeAnnouncement, error) {
|
||||
return s.genNodeAnnouncement(refresh)
|
||||
Router: s.chanRouter,
|
||||
Notifier: s.cc.ChainNotifier,
|
||||
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
|
||||
Broadcast: s.BroadcastMessage,
|
||||
ChanSeries: chanSeries,
|
||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||
NotifyWhenOffline: s.NotifyWhenOffline,
|
||||
FetchSelfAnnouncement: s.getNodeAnnouncement,
|
||||
UpdateSelfAnnouncement: func() (lnwire.NodeAnnouncement,
|
||||
error) {
|
||||
|
||||
return s.genNodeAnnouncement(nil)
|
||||
},
|
||||
ProofMatureDelta: 0,
|
||||
TrickleDelay: time.Millisecond * time.Duration(cfg.TrickleDelay),
|
||||
|
@ -1271,6 +1276,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
return ourPolicy, err
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
|
||||
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
|
||||
IDKey: nodeKeyDesc.PubKey,
|
||||
|
@ -1283,8 +1289,10 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
Notifier: cc.ChainNotifier,
|
||||
FeeEstimator: cc.FeeEstimator,
|
||||
SignMessage: cc.MsgSigner.SignMessage,
|
||||
CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement, error) {
|
||||
return s.genNodeAnnouncement(true)
|
||||
CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement,
|
||||
error) {
|
||||
|
||||
return s.genNodeAnnouncement(nil)
|
||||
},
|
||||
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
|
||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||
|
@ -1625,8 +1633,15 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||
cfg.net.ResolveTCPAddr,
|
||||
)
|
||||
},
|
||||
AdvertisedIPs: advertisedIPs,
|
||||
AnnounceNewIPs: netann.IPAnnouncer(s.genNodeAnnouncement),
|
||||
AdvertisedIPs: advertisedIPs,
|
||||
AnnounceNewIPs: netann.IPAnnouncer(
|
||||
func(modifier ...netann.NodeAnnModifier) (
|
||||
lnwire.NodeAnnouncement, error) {
|
||||
|
||||
return s.genNodeAnnouncement(
|
||||
nil, modifier...,
|
||||
)
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2476,12 +2491,8 @@ out:
|
|||
// throughout the network. We'll only include addresses
|
||||
// that have a different IP from the previous one, as
|
||||
// the previous IP is no longer valid.
|
||||
currentNodeAnn, err := s.genNodeAnnouncement(false)
|
||||
if err != nil {
|
||||
srvrLog.Debugf("Unable to retrieve current "+
|
||||
"node announcement: %v", err)
|
||||
continue
|
||||
}
|
||||
currentNodeAnn := s.getNodeAnnouncement()
|
||||
|
||||
for _, addr := range currentNodeAnn.Addresses {
|
||||
host, _, err := net.SplitHostPort(addr.String())
|
||||
if err != nil {
|
||||
|
@ -2503,7 +2514,7 @@ out:
|
|||
// announcement with the updated addresses and broadcast
|
||||
// it to our peers.
|
||||
newNodeAnn, err := s.genNodeAnnouncement(
|
||||
true, netann.NodeAnnSetAddrs(newAddrs),
|
||||
nil, netann.NodeAnnSetAddrs(newAddrs),
|
||||
)
|
||||
if err != nil {
|
||||
srvrLog.Debugf("Unable to generate new node "+
|
||||
|
@ -2892,7 +2903,7 @@ func (s *server) createNewHiddenService() error {
|
|||
// Now that the onion service has been created, we'll add the onion
|
||||
// address it can be reached at to our list of advertised addresses.
|
||||
newNodeAnn, err := s.genNodeAnnouncement(
|
||||
true, func(currentAnn *lnwire.NodeAnnouncement) {
|
||||
nil, func(currentAnn *lnwire.NodeAnnouncement) {
|
||||
currentAnn.Addresses = append(currentAnn.Addresses, addr)
|
||||
},
|
||||
)
|
||||
|
@ -2942,30 +2953,54 @@ func (s *server) findChannel(node *btcec.PublicKey, chanID lnwire.ChannelID) (
|
|||
return nil, fmt.Errorf("unable to find channel")
|
||||
}
|
||||
|
||||
// getNodeAnnouncement fetches the current, fully signed node announcement.
|
||||
func (s *server) getNodeAnnouncement() lnwire.NodeAnnouncement {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return *s.currentNodeAnn
|
||||
}
|
||||
|
||||
// genNodeAnnouncement generates and returns the current fully signed node
|
||||
// announcement. If refresh is true, then the time stamp of the announcement
|
||||
// will be updated in order to ensure it propagates through the network.
|
||||
func (s *server) genNodeAnnouncement(refresh bool,
|
||||
// announcement. The time stamp of the announcement will be updated in order
|
||||
// to ensure it propagates through the network.
|
||||
func (s *server) genNodeAnnouncement(features *lnwire.RawFeatureVector,
|
||||
modifiers ...netann.NodeAnnModifier) (lnwire.NodeAnnouncement, error) {
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// If we don't need to refresh the announcement, then we can return a
|
||||
// copy of our cached version.
|
||||
if !refresh {
|
||||
return *s.currentNodeAnn, nil
|
||||
// First, try to update our feature manager with the updated set of
|
||||
// features.
|
||||
if features != nil {
|
||||
proposedFeatures := map[feature.Set]*lnwire.RawFeatureVector{
|
||||
feature.SetNodeAnn: features,
|
||||
}
|
||||
err := s.featureMgr.UpdateFeatureSets(proposedFeatures)
|
||||
if err != nil {
|
||||
return lnwire.NodeAnnouncement{}, err
|
||||
}
|
||||
|
||||
// If we could successfully update our feature manager, add
|
||||
// an update modifier to include these new features to our
|
||||
// set.
|
||||
modifiers = append(
|
||||
modifiers, netann.NodeAnnSetFeatures(features),
|
||||
)
|
||||
}
|
||||
|
||||
// Always update the timestamp when refreshing to ensure the update
|
||||
// propagates.
|
||||
modifiers = append(modifiers, netann.NodeAnnSetTimestamp)
|
||||
|
||||
// Otherwise, we'll sign a new update after applying all of the passed
|
||||
// modifiers.
|
||||
// Apply the requested changes to the node announcement.
|
||||
for _, modifier := range modifiers {
|
||||
modifier(s.currentNodeAnn)
|
||||
}
|
||||
|
||||
// Sign a new update after applying all of the passed modifiers.
|
||||
err := netann.SignNodeAnnouncement(
|
||||
s.nodeSigner, s.identityKeyLoc, s.currentNodeAnn,
|
||||
modifiers...,
|
||||
)
|
||||
if err != nil {
|
||||
return lnwire.NodeAnnouncement{}, err
|
||||
|
@ -2978,10 +3013,10 @@ func (s *server) genNodeAnnouncement(refresh bool,
|
|||
// applying the giving modifiers and updating the time stamp
|
||||
// to ensure it propagates through the network. Then it brodcasts
|
||||
// it to the network.
|
||||
func (s *server) updateAndBrodcastSelfNode(
|
||||
func (s *server) updateAndBrodcastSelfNode(features *lnwire.RawFeatureVector,
|
||||
modifiers ...netann.NodeAnnModifier) error {
|
||||
|
||||
newNodeAnn, err := s.genNodeAnnouncement(true, modifiers...)
|
||||
newNodeAnn, err := s.genNodeAnnouncement(features, modifiers...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to generate new node "+
|
||||
"announcement: %v", err)
|
||||
|
@ -2999,9 +3034,7 @@ func (s *server) updateAndBrodcastSelfNode(
|
|||
selfNode.LastUpdate = time.Unix(int64(newNodeAnn.Timestamp), 0)
|
||||
selfNode.Addresses = newNodeAnn.Addresses
|
||||
selfNode.Alias = newNodeAnn.Alias.String()
|
||||
selfNode.Features = lnwire.NewFeatureVector(
|
||||
newNodeAnn.Features, lnwire.Features,
|
||||
)
|
||||
selfNode.Features = s.featureMgr.Get(feature.SetNodeAnn)
|
||||
selfNode.Color = newNodeAnn.RGBColor
|
||||
selfNode.AuthSigBytes = newNodeAnn.Signature.ToSignatureBytes()
|
||||
|
||||
|
@ -3011,11 +3044,6 @@ func (s *server) updateAndBrodcastSelfNode(
|
|||
return fmt.Errorf("can't set self node: %v", err)
|
||||
}
|
||||
|
||||
// Update the feature bits for the SetNodeAnn in case they changed.
|
||||
s.featureMgr.SetRaw(
|
||||
feature.SetNodeAnn, selfNode.Features.RawFeatureVector,
|
||||
)
|
||||
|
||||
// Finally, propagate it to the nodes in the network.
|
||||
err = s.BroadcastMessage(nil, &newNodeAnn)
|
||||
if err != nil {
|
||||
|
@ -3821,7 +3849,11 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||
TowerClient: s.towerClient,
|
||||
AnchorTowerClient: s.anchorTowerClient,
|
||||
DisconnectPeer: s.DisconnectPeer,
|
||||
GenNodeAnnouncement: s.genNodeAnnouncement,
|
||||
GenNodeAnnouncement: func(...netann.NodeAnnModifier) (
|
||||
lnwire.NodeAnnouncement, error) {
|
||||
|
||||
return s.genNodeAnnouncement(nil)
|
||||
},
|
||||
|
||||
PongBuf: s.pongBuf,
|
||||
|
||||
|
|
|
@ -118,8 +118,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
|
|||
tcpResolver lncfg.TCPResolver,
|
||||
genInvoiceFeatures func() *lnwire.FeatureVector,
|
||||
genAmpInvoiceFeatures func() *lnwire.FeatureVector,
|
||||
getNodeAnnouncement func() (lnwire.NodeAnnouncement, error),
|
||||
updateNodeAnnouncement func(modifiers ...netann.NodeAnnModifier) error,
|
||||
getNodeAnnouncement func() lnwire.NodeAnnouncement,
|
||||
updateNodeAnnouncement func(features *lnwire.RawFeatureVector,
|
||||
modifiers ...netann.NodeAnnModifier) error,
|
||||
parseAddr func(addr string) (net.Addr, error),
|
||||
rpcLogger btclog.Logger,
|
||||
getAlias func(lnwire.ChannelID) (lnwire.ShortChannelID, error)) error {
|
||||
|
|
Loading…
Add table
Reference in a new issue