Merge pull request #9001 from ellemouton/namespacedMC

routing: Namespaced Mission Control
This commit is contained in:
Elle authored and GitHub committed 2024-10-01 20:21:53 +02:00
commit f1207ef740
19 files changed
+667 -138

No files matched your search

+5
View File
@@ -27,6 +27,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/migration30" "github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31" "github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration33"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/invoices"
@@ -291,6 +292,10 @@ var (
number: 32, number: 32,
migration: migration32.MigrateMCRouteSerialisation, migration: migration32.MigrateMCRouteSerialisation,
}, },
{
number: 33,
migration: migration33.MigrateMCStoreNameSpacedResults,
},
} }
// optionalVersions stores all optional migrations that are applied // optionalVersions stores all optional migrations that are applied
+2
View File
@@ -11,6 +11,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb/migration30" "github.com/lightningnetwork/lnd/channeldb/migration30"
"github.com/lightningnetwork/lnd/channeldb/migration31" "github.com/lightningnetwork/lnd/channeldb/migration31"
"github.com/lightningnetwork/lnd/channeldb/migration32" "github.com/lightningnetwork/lnd/channeldb/migration32"
"github.com/lightningnetwork/lnd/channeldb/migration33"
"github.com/lightningnetwork/lnd/channeldb/migration_01_to_11" "github.com/lightningnetwork/lnd/channeldb/migration_01_to_11"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
) )
@@ -44,5 +45,6 @@ func UseLogger(logger btclog.Logger) {
migration30.UseLogger(logger) migration30.UseLogger(logger)
migration31.UseLogger(logger) migration31.UseLogger(logger)
migration32.UseLogger(logger) migration32.UseLogger(logger)
migration33.UseLogger(logger)
kvdb.UseLogger(logger) kvdb.UseLogger(logger)
} }
+14
View File
@@ -0,0 +1,14 @@
package migration33
import (
"github.com/btcsuite/btclog"
)
// log is a logger that is initialized as disabled. This means the package will
// not perform any logging by default until a logger is set.
var log = btclog.Disabled
// UseLogger uses a specified Logger to output package logging info.
func UseLogger(logger btclog.Logger) {
log = logger
}
+69
View File
@@ -0,0 +1,69 @@
package migration33
import (
"bytes"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// resultsKey is the fixed key under which the attempt results are
// stored.
resultsKey = []byte("missioncontrol-results")
// defaultMCNamespaceKey is the key of the default mission control store
// namespace.
defaultMCNamespaceKey = []byte("default")
)
// MigrateMCStoreNameSpacedResults reads in all the current mission control
// entries and re-writes them under a new default namespace.
func MigrateMCStoreNameSpacedResults(tx kvdb.RwTx) error {
log.Infof("Migrating Mission Control store to use namespaced results")
// Get the top level bucket. All the MC results are currently stored
// as KV pairs in this bucket
topLevelBucket := tx.ReadWriteBucket(resultsKey)
// If the results bucket does not exist then there are no entries in
// the mission control store yet and so there is nothing to migrate.
if topLevelBucket == nil {
return nil
}
// Create a new default namespace bucket under the top-level bucket.
defaultNSBkt, err := topLevelBucket.CreateBucket(defaultMCNamespaceKey)
if err != nil {
return err
}
// Iterate through each of the existing result pairs, write them to the
// new namespaced bucket. Also collect the set of keys so that we can
// later delete them from the top level bucket.
var keys [][]byte
err = topLevelBucket.ForEach(func(k, v []byte) error {
// Skip the new default namespace key.
if bytes.Equal(k, defaultMCNamespaceKey) {
return nil
}
// Collect the key.
keys = append(keys, k)
// Write the pair under the default namespace.
return defaultNSBkt.Put(k, v)
})
if err != nil {
return err
}
// Finally, iterate through the set of keys and delete them from the
// top level bucket.
for _, k := range keys {
if err := topLevelBucket.Delete(k); err != nil {
return err
}
}
return err
}
+41
View File
@@ -0,0 +1,41 @@
package migration33
import (
"testing"
"github.com/lightningnetwork/lnd/channeldb/migtest"
"github.com/lightningnetwork/lnd/kvdb"
)
var (
// before represents the structure of the MC store before the migration.
before = map[string]interface{}{
"key1": "result1",
"key2": "result2",
"key3": "result3",
"key4": "result4",
}
// after represents the expected structure of the store after the
// migration. It should be identical to before except all the kv pairs
// are now under a new default namespace key.
after = map[string]interface{}{
string(defaultMCNamespaceKey): before,
}
)
// TestMigrateMCStoreNameSpacedResults tests that the MC store results are
// correctly moved to be under a new default namespace bucket.
func TestMigrateMCStoreNameSpacedResults(t *testing.T) {
before := func(tx kvdb.RwTx) error {
return migtest.RestoreDB(tx, resultsKey, before)
}
after := func(tx kvdb.RwTx) error {
return migtest.VerifyDB(tx, resultsKey, after)
}
migtest.ApplyMigration(
t, before, after, MigrateMCStoreNameSpacedResults, false,
)
}
@@ -66,6 +66,10 @@
store](https://github.com/lightningnetwork/lnd/pull/8911) to use a more store](https://github.com/lightningnetwork/lnd/pull/8911) to use a more
minimal encoding for payment attempt routes. minimal encoding for payment attempt routes.
* [Migrate the mission control
store](https://github.com/lightningnetwork/lnd/pull/9001) so that results are
namespaced. All existing results are written to the "default" namespace.
## Code Health ## Code Health
## Tooling and Documentation ## Tooling and Documentation
+8 -5
View File
@@ -163,12 +163,15 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
} }
}) })
// Instantiate a new mission control with the current configuration // Instantiate a new mission controller with the current configuration
// values. // values.
mc, err := NewMissionControl(db, c.source.pubkey, &c.mcCfg) mcController, err := NewMissionController(db, c.source.pubkey, &c.mcCfg)
if err != nil { require.NoError(c.t, err)
c.t.Fatal(err)
} mc, err := mcController.GetNamespacedStore(
DefaultMissionControlNamespace,
)
require.NoError(c.t, err)
getBandwidthHints := func(_ Graph) (bandwidthHints, error) { getBandwidthHints := func(_ Graph) (bandwidthHints, error) {
// Create bandwidth hints based on local channel balances. // Create bandwidth hints based on local channel balances.
+317 -59
View File
@@ -7,7 +7,11 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btclog"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@@ -68,6 +72,11 @@ const (
// FeeEstimationTimeout. It defines the maximum duration that the // FeeEstimationTimeout. It defines the maximum duration that the
// probing fee estimation is allowed to take. // probing fee estimation is allowed to take.
DefaultFeeEstimationTimeout = time.Minute DefaultFeeEstimationTimeout = time.Minute
// DefaultMissionControlNamespace is the name of the default mission
// control name space. This is used as the sub-bucket key within the
// top level DB bucket to store mission control results.
DefaultMissionControlNamespace = "default"
) )
var ( var (
@@ -84,6 +93,16 @@ var (
// NodeResults contains previous results from a node to its peers. // NodeResults contains previous results from a node to its peers.
type NodeResults map[route.Vertex]TimedPairResult type NodeResults map[route.Vertex]TimedPairResult
// mcConfig holds various config members that will be required by all
// MissionControl instances and will be the same regardless of namespace.
type mcConfig struct {
// clock is a time source used by mission control.
clock clock.Clock
// selfNode is our pubkey.
selfNode route.Vertex
}
// MissionControl contains state which summarizes the past attempts of HTLC // MissionControl contains state which summarizes the past attempts of HTLC
// routing by external callers when sending payments throughout the network. It // routing by external callers when sending payments throughout the network. It
// acts as a shared memory during routing attempts with the goal to optimize the // acts as a shared memory during routing attempts with the goal to optimize the
@@ -94,17 +113,12 @@ type NodeResults map[route.Vertex]TimedPairResult
// since the last failure is used to estimate a success probability that is fed // since the last failure is used to estimate a success probability that is fed
// into the path finding process for subsequent payment attempts. // into the path finding process for subsequent payment attempts.
type MissionControl struct { type MissionControl struct {
cfg *mcConfig
// state is the internal mission control state that is input for // state is the internal mission control state that is input for
// probability estimation. // probability estimation.
state *missionControlState state *missionControlState
// now is expected to return the current time. It is supplied as an
// external function to enable deterministic unit tests.
now func() time.Time
// selfNode is our pubkey.
selfNode route.Vertex
store *missionControlStore store *missionControlStore
// estimator is the probability estimator that is used with the payment // estimator is the probability estimator that is used with the payment
@@ -115,7 +129,19 @@ type MissionControl struct {
// mission control state is updated. // mission control state is updated.
onConfigUpdate fn.Option[func(cfg *MissionControlConfig)] onConfigUpdate fn.Option[func(cfg *MissionControlConfig)]
sync.Mutex log btclog.Logger
mu sync.Mutex
}
// MissionController manages MissionControl instances in various namespaces.
type MissionController struct {
db kvdb.Backend
cfg *mcConfig
defaultMCCfg *MissionControlConfig
mc map[string]*MissionControl
mu sync.Mutex
// TODO(roasbeef): further counters, if vertex continually unavailable, // TODO(roasbeef): further counters, if vertex continually unavailable,
// add to another generation // add to another generation
@@ -123,6 +149,35 @@ type MissionControl struct {
// TODO(roasbeef): also add favorable metrics for nodes // TODO(roasbeef): also add favorable metrics for nodes
} }
// GetNamespacedStore returns the MissionControl in the given namespace. If one
// does not yet exist, then it is initialised.
func (m *MissionController) GetNamespacedStore(ns string) (*MissionControl,
error) {
m.mu.Lock()
defer m.mu.Unlock()
if mc, ok := m.mc[ns]; ok {
return mc, nil
}
return m.initMissionControl(ns)
}
// ListNamespaces returns a list of the namespaces that the MissionController
// is aware of.
func (m *MissionController) ListNamespaces() []string {
m.mu.Lock()
defer m.mu.Unlock()
namespaces := make([]string, 0, len(m.mc))
for ns := range m.mc {
namespaces = append(namespaces, ns)
}
return namespaces
}
// MissionControlConfig defines parameters that control mission control // MissionControlConfig defines parameters that control mission control
// behaviour. // behaviour.
type MissionControlConfig struct { type MissionControlConfig struct {
@@ -214,9 +269,9 @@ type paymentResult struct {
failure lnwire.FailureMessage failure lnwire.FailureMessage
} }
// NewMissionControl returns a new instance of missionControl. // NewMissionController returns a new instance of MissionController.
func NewMissionControl(db kvdb.Backend, self route.Vertex, func NewMissionController(db kvdb.Backend, self route.Vertex,
cfg *MissionControlConfig) (*MissionControl, error) { cfg *MissionControlConfig) (*MissionController, error) {
log.Debugf("Instantiating mission control with config: %v, %v", cfg, log.Debugf("Instantiating mission control with config: %v, %v", cfg,
cfg.Estimator) cfg.Estimator)
@@ -225,47 +280,151 @@ func NewMissionControl(db kvdb.Backend, self route.Vertex,
return nil, err return nil, err
} }
mcCfg := &mcConfig{
clock: clock.NewDefaultClock(),
selfNode: self,
}
mgr := &MissionController{
db: db,
defaultMCCfg: cfg,
cfg: mcCfg,
mc: make(map[string]*MissionControl),
}
if err := mgr.loadMissionControls(); err != nil {
return nil, err
}
for _, mc := range mgr.mc {
if err := mc.init(); err != nil {
return nil, err
}
}
return mgr, nil
}
// loadMissionControls initialises a MissionControl in the default namespace if
// one does not yet exist. It then initialises a MissionControl for all other
// namespaces found in the DB.
//
// NOTE: this should only be called once during MissionController construction.
func (m *MissionController) loadMissionControls() error {
m.mu.Lock()
defer m.mu.Unlock()
// Always initialise the default namespace.
_, err := m.initMissionControl(DefaultMissionControlNamespace)
if err != nil {
return err
}
namespaces := make(map[string]struct{})
err = m.db.View(func(tx walletdb.ReadTx) error {
mcStoreBkt := tx.ReadBucket(resultsKey)
if mcStoreBkt == nil {
return fmt.Errorf("top level mission control bucket " +
"not found")
}
// Iterate through all the keys in the bucket and collect the
// namespaces.
return mcStoreBkt.ForEach(func(k, _ []byte) error {
// We've already initialised the default namespace so
// we can skip it.
if string(k) == DefaultMissionControlNamespace {
return nil
}
namespaces[string(k)] = struct{}{}
return nil
})
}, func() {})
if err != nil {
return err
}
// Now, iterate through all the namespaces and initialise them.
for ns := range namespaces {
_, err = m.initMissionControl(ns)
if err != nil {
return err
}
}
return nil
}
// initMissionControl creates a new MissionControl instance with the given
// namespace if one does not yet exist.
//
// NOTE: the MissionController's mutex must be held before calling this method.
func (m *MissionController) initMissionControl(namespace string) (
*MissionControl, error) {
// If a mission control with this namespace has already been initialised
// then there is nothing left to do.
if mc, ok := m.mc[namespace]; ok {
return mc, nil
}
cfg := m.defaultMCCfg
store, err := newMissionControlStore( store, err := newMissionControlStore(
db, cfg.MaxMcHistory, cfg.McFlushInterval, newNamespacedDB(m.db, namespace), cfg.MaxMcHistory,
cfg.McFlushInterval,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
mc := &MissionControl{ mc := &MissionControl{
state: newMissionControlState( cfg: m.cfg,
cfg.MinFailureRelaxInterval, state: newMissionControlState(cfg.MinFailureRelaxInterval),
store: store,
estimator: cfg.Estimator,
log: build.NewPrefixLog(
fmt.Sprintf("[%s]:", namespace), log,
), ),
now: time.Now,
selfNode: self,
store: store,
estimator: cfg.Estimator,
onConfigUpdate: cfg.OnConfigUpdate, onConfigUpdate: cfg.OnConfigUpdate,
} }
if err := mc.init(); err != nil { m.mc[namespace] = mc
return nil, err
}
return mc, nil return mc, nil
} }
// RunStoreTicker runs the mission control store's ticker. // RunStoreTickers runs the mission controller store's tickers.
func (m *MissionControl) RunStoreTicker() { func (m *MissionController) RunStoreTickers() {
m.store.run() m.mu.Lock()
defer m.mu.Unlock()
for _, mc := range m.mc {
mc.store.run()
}
} }
// StopStoreTicker stops the mission control store's ticker. // StopStoreTickers stops the mission control store's tickers.
func (m *MissionControl) StopStoreTicker() { func (m *MissionController) StopStoreTickers() {
log.Debug("Stopping mission control store ticker") log.Debug("Stopping mission control store ticker")
defer log.Debug("Mission control store ticker stopped") defer log.Debug("Mission control store ticker stopped")
m.store.stop() m.mu.Lock()
defer m.mu.Unlock()
for _, mc := range m.mc {
mc.store.stop()
}
} }
// init initializes mission control with historical data. // init initializes mission control with historical data.
func (m *MissionControl) init() error { func (m *MissionControl) init() error {
log.Debugf("Mission control state reconstruction started") m.log.Debugf("Mission control state reconstruction started")
m.mu.Lock()
defer m.mu.Unlock()
start := time.Now() start := time.Now()
@@ -275,10 +434,10 @@ func (m *MissionControl) init() error {
} }
for _, result := range results { for _, result := range results {
m.applyPaymentResult(result) _ = m.applyPaymentResult(result)
} }
log.Debugf("Mission control state reconstruction finished: "+ m.log.Debugf("Mission control state reconstruction finished: "+
"n=%v, time=%v", len(results), time.Since(start)) "n=%v, time=%v", len(results), time.Since(start))
return nil return nil
@@ -288,8 +447,8 @@ func (m *MissionControl) init() error {
// with. All fields are copied by value, so we do not need to worry about // with. All fields are copied by value, so we do not need to worry about
// mutation. // mutation.
func (m *MissionControl) GetConfig() *MissionControlConfig { func (m *MissionControl) GetConfig() *MissionControlConfig {
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
return &MissionControlConfig{ return &MissionControlConfig{
Estimator: m.estimator, Estimator: m.estimator,
@@ -310,10 +469,10 @@ func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error {
return err return err
} }
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
log.Infof("Active mission control cfg: %v, estimator: %v", cfg, m.log.Infof("Active mission control cfg: %v, estimator: %v", cfg,
cfg.Estimator) cfg.Estimator)
m.store.maxRecords = cfg.MaxMcHistory m.store.maxRecords = cfg.MaxMcHistory
@@ -331,8 +490,8 @@ func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error {
// ResetHistory resets the history of MissionControl returning it to a state as // ResetHistory resets the history of MissionControl returning it to a state as
// if no payment attempts have been made. // if no payment attempts have been made.
func (m *MissionControl) ResetHistory() error { func (m *MissionControl) ResetHistory() error {
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
if err := m.store.clear(); err != nil { if err := m.store.clear(); err != nil {
return err return err
@@ -340,7 +499,7 @@ func (m *MissionControl) ResetHistory() error {
m.state.resetHistory() m.state.resetHistory()
log.Debugf("Mission control history cleared") m.log.Debugf("Mission control history cleared")
return nil return nil
} }
@@ -350,14 +509,14 @@ func (m *MissionControl) ResetHistory() error {
func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex, func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 { amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
now := m.now() now := m.cfg.clock.Now()
results, _ := m.state.getLastPairResult(fromNode) results, _ := m.state.getLastPairResult(fromNode)
// Use a distinct probability estimation function for local channels. // Use a distinct probability estimation function for local channels.
if fromNode == m.selfNode { if fromNode == m.cfg.selfNode {
return m.estimator.LocalPairProbability(now, results, toNode) return m.estimator.LocalPairProbability(now, results, toNode)
} }
@@ -369,10 +528,10 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
// GetHistorySnapshot takes a snapshot from the current mission control state // GetHistorySnapshot takes a snapshot from the current mission control state
// and actual probability estimates. // and actual probability estimates.
func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot { func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
log.Debugf("Requesting history snapshot from mission control") m.log.Debugf("Requesting history snapshot from mission control")
return m.state.getSnapshot() return m.state.getSnapshot()
} }
@@ -387,15 +546,15 @@ func (m *MissionControl) ImportHistory(history *MissionControlSnapshot,
return errors.New("cannot import nil history") return errors.New("cannot import nil history")
} }
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
log.Infof("Importing history snapshot with %v pairs to mission control", m.log.Infof("Importing history snapshot with %v pairs to mission "+
len(history.Pairs)) "control", len(history.Pairs))
imported := m.state.importSnapshot(history, force) imported := m.state.importSnapshot(history, force)
log.Infof("Imported %v results to mission control", imported) m.log.Infof("Imported %v results to mission control", imported)
return nil return nil
} }
@@ -404,8 +563,8 @@ func (m *MissionControl) ImportHistory(history *MissionControlSnapshot,
func (m *MissionControl) GetPairHistorySnapshot( func (m *MissionControl) GetPairHistorySnapshot(
fromNode, toNode route.Vertex) TimedPairResult { fromNode, toNode route.Vertex) TimedPairResult {
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
results, ok := m.state.getLastPairResult(fromNode) results, ok := m.state.getLastPairResult(fromNode)
if !ok { if !ok {
@@ -429,7 +588,7 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
failureSourceIdx *int, failure lnwire.FailureMessage) ( failureSourceIdx *int, failure lnwire.FailureMessage) (
*channeldb.FailureReason, error) { *channeldb.FailureReason, error) {
timestamp := m.now() timestamp := m.cfg.clock.Now()
result := &paymentResult{ result := &paymentResult{
success: false, success: false,
@@ -449,7 +608,7 @@ func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
func (m *MissionControl) ReportPaymentSuccess(paymentID uint64, func (m *MissionControl) ReportPaymentSuccess(paymentID uint64,
rt *route.Route) error { rt *route.Route) error {
timestamp := m.now() timestamp := m.cfg.clock.Now()
result := &paymentResult{ result := &paymentResult{
timeFwd: timestamp, timeFwd: timestamp,
@@ -471,8 +630,8 @@ func (m *MissionControl) processPaymentResult(result *paymentResult) (
// Store complete result in database. // Store complete result in database.
m.store.AddResult(result) m.store.AddResult(result)
m.Lock() m.mu.Lock()
defer m.Unlock() defer m.mu.Unlock()
// Apply result to update mission control state. // Apply result to update mission control state.
reason := m.applyPaymentResult(result) reason := m.applyPaymentResult(result)
@@ -519,7 +678,7 @@ func (m *MissionControl) applyPaymentResult(
// that case, a node-level failure would not be applied to untried // that case, a node-level failure would not be applied to untried
// channels. // channels.
if i.nodeFailure != nil { if i.nodeFailure != nil {
log.Debugf("Reporting node failure to Mission Control: "+ m.log.Debugf("Reporting node failure to Mission Control: "+
"node=%v", *i.nodeFailure) "node=%v", *i.nodeFailure)
m.state.setAllFail(*i.nodeFailure, result.timeReply) m.state.setAllFail(*i.nodeFailure, result.timeReply)
@@ -529,11 +688,11 @@ func (m *MissionControl) applyPaymentResult(
pairResult := pairResult pairResult := pairResult
if pairResult.success { if pairResult.success {
log.Debugf("Reporting pair success to Mission "+ m.log.Debugf("Reporting pair success to Mission "+
"Control: pair=%v, amt=%v", "Control: pair=%v, amt=%v",
pair, pairResult.amt) pair, pairResult.amt)
} else { } else {
log.Debugf("Reporting pair failure to Mission "+ m.log.Debugf("Reporting pair failure to Mission "+
"Control: pair=%v, amt=%v", "Control: pair=%v, amt=%v",
pair, pairResult.amt) pair, pairResult.amt)
} }
@@ -545,3 +704,102 @@ func (m *MissionControl) applyPaymentResult(
return i.finalFailureReason return i.finalFailureReason
} }
// namespacedDB is an implementation of the missionControlDB that gives a user
// of the interface access to a namespaced bucket within the top level mission
// control bucket.
type namespacedDB struct {
topLevelBucketKey []byte
namespace []byte
db kvdb.Backend
}
// A compile-time check to ensure that namespacedDB implements missionControlDB.
var _ missionControlDB = (*namespacedDB)(nil)
// newDefaultNamespacedStore creates an instance of namespaceDB that uses the
// default namespace.
func newDefaultNamespacedStore(db kvdb.Backend) missionControlDB {
return newNamespacedDB(db, DefaultMissionControlNamespace)
}
// newNamespacedDB creates a new instance of missionControlDB where the DB will
// have access to a namespaced bucket within the top level mission control
// bucket.
func newNamespacedDB(db kvdb.Backend, namespace string) missionControlDB {
return &namespacedDB{
db: db,
namespace: []byte(namespace),
topLevelBucketKey: resultsKey,
}
}
// update can be used to perform reads and writes on the given bucket.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) update(f func(bkt walletdb.ReadWriteBucket) error,
reset func()) error {
return n.db.Update(func(tx kvdb.RwTx) error {
mcStoreBkt, err := tx.CreateTopLevelBucket(n.topLevelBucketKey)
if err != nil {
return fmt.Errorf("cannot create top level mission "+
"control bucket: %w", err)
}
namespacedBkt, err := mcStoreBkt.CreateBucketIfNotExists(
n.namespace,
)
if err != nil {
return fmt.Errorf("cannot create namespaced bucket "+
"(%s) in mission control store: %w",
n.namespace, err)
}
return f(namespacedBkt)
}, reset)
}
// view can be used to perform reads on the given bucket.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) view(f func(bkt walletdb.ReadBucket) error,
reset func()) error {
return n.db.View(func(tx kvdb.RTx) error {
mcStoreBkt := tx.ReadBucket(n.topLevelBucketKey)
if mcStoreBkt == nil {
return fmt.Errorf("top level mission control bucket " +
"not found")
}
namespacedBkt := mcStoreBkt.NestedReadBucket(n.namespace)
if namespacedBkt == nil {
return fmt.Errorf("namespaced bucket (%s) not found "+
"in mission control store", n.namespace)
}
return f(namespacedBkt)
}, reset)
}
// purge will delete all the contents in the namespace.
//
// NOTE: this is part of the missionControlDB interface.
func (n *namespacedDB) purge() error {
return n.db.Update(func(tx kvdb.RwTx) error {
mcStoreBkt := tx.ReadWriteBucket(n.topLevelBucketKey)
if mcStoreBkt == nil {
return nil
}
err := mcStoreBkt.DeleteNestedBucket(n.namespace)
if err != nil {
return err
}
_, err = mcStoreBkt.CreateBucket(n.namespace)
return err
}, func() {})
}
+4 -3
View File
@@ -37,7 +37,8 @@ func newMissionControlState(
} }
} }
// getLastPairResult returns the current state for connections to the given node. // getLastPairResult returns the current state for connections to the given
// node.
func (m *missionControlState) getLastPairResult(node route.Vertex) (NodeResults, func (m *missionControlState) getLastPairResult(node route.Vertex) (NodeResults,
bool) { bool) {
@@ -45,8 +46,8 @@ func (m *missionControlState) getLastPairResult(node route.Vertex) (NodeResults,
return result, ok return result, ok
} }
// ResetHistory resets the history of MissionControl returning it to a state as // ResetHistory resets the history of missionControlState returning it to a
// if no payment attempts have been made. // state as if no payment attempts have been made.
func (m *missionControlState) resetHistory() { func (m *missionControlState) resetHistory() {
m.lastPairResult = make(map[route.Vertex]NodeResults) m.lastPairResult = make(map[route.Vertex]NodeResults)
m.lastSecondChance = make(map[DirectedNodePair]time.Time) m.lastSecondChance = make(map[DirectedNodePair]time.Time)
+22 -24
View File
@@ -32,6 +32,21 @@ const (
unknownFailureSourceIdx = -1 unknownFailureSourceIdx = -1
) )
// missionControlDB is an interface that defines the database methods that a
// single missionControlStore has access to. It allows the missionControlStore
// to be unaware of the overall DB structure and restricts its access to the DB
// by only providing it the bucket that it needs to care about.
type missionControlDB interface {
// update can be used to perform reads and writes on the given bucket.
update(f func(bkt kvdb.RwBucket) error, reset func()) error
// view can be used to perform reads on the given bucket.
view(f func(bkt kvdb.RBucket) error, reset func()) error
// purge will delete all the contents in this store.
purge() error
}
// missionControlStore is a bolt db based implementation of a mission control // missionControlStore is a bolt db based implementation of a mission control
// store. It stores the raw payment attempt data from which the internal mission // store. It stores the raw payment attempt data from which the internal mission
// controls state can be rederived on startup. This allows the mission control // controls state can be rederived on startup. This allows the mission control
@@ -41,7 +56,7 @@ const (
type missionControlStore struct { type missionControlStore struct {
done chan struct{} done chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
db kvdb.Backend db missionControlDB
// queueCond is signalled when items are put into the queue. // queueCond is signalled when items are put into the queue.
queueCond *sync.Cond queueCond *sync.Cond
@@ -67,7 +82,7 @@ type missionControlStore struct {
flushInterval time.Duration flushInterval time.Duration
} }
func newMissionControlStore(db kvdb.Backend, maxRecords int, func newMissionControlStore(db missionControlDB, maxRecords int,
flushInterval time.Duration) (*missionControlStore, error) { flushInterval time.Duration) (*missionControlStore, error) {
var ( var (
@@ -76,13 +91,7 @@ func newMissionControlStore(db kvdb.Backend, maxRecords int,
) )
// Create buckets if not yet existing. // Create buckets if not yet existing.
err := kvdb.Update(db, func(tx kvdb.RwTx) error { err := db.update(func(resultsBucket kvdb.RwBucket) error {
resultsBucket, err := tx.CreateTopLevelBucket(resultsKey)
if err != nil {
return fmt.Errorf("cannot create results bucket: %w",
err)
}
// Collect all keys to be able to quickly calculate the // Collect all keys to be able to quickly calculate the
// difference when updating the DB state. // difference when updating the DB state.
c := resultsBucket.ReadCursor() c := resultsBucket.ReadCursor()
@@ -119,20 +128,12 @@ func (b *missionControlStore) clear() error {
b.queueCond.L.Lock() b.queueCond.L.Lock()
defer b.queueCond.L.Unlock() defer b.queueCond.L.Unlock()
err := kvdb.Update(b.db, func(tx kvdb.RwTx) error { if err := b.db.purge(); err != nil {
if err := tx.DeleteTopLevelBucket(resultsKey); err != nil {
return err
}
_, err := tx.CreateTopLevelBucket(resultsKey)
return err
}, func() {})
if err != nil {
return err return err
} }
b.queue = list.New() b.queue = list.New()
return nil return nil
} }
@@ -140,8 +141,7 @@ func (b *missionControlStore) clear() error {
func (b *missionControlStore) fetchAll() ([]*paymentResult, error) { func (b *missionControlStore) fetchAll() ([]*paymentResult, error) {
var results []*paymentResult var results []*paymentResult
err := kvdb.View(b.db, func(tx kvdb.RTx) error { err := b.db.view(func(resultBucket kvdb.RBucket) error {
resultBucket := tx.ReadBucket(resultsKey)
results = make([]*paymentResult, 0) results = make([]*paymentResult, 0)
return resultBucket.ForEach(func(k, v []byte) error { return resultBucket.ForEach(func(k, v []byte) error {
@@ -511,9 +511,7 @@ func (b *missionControlStore) storeResults() error {
} }
} }
err := kvdb.Update(b.db, func(tx kvdb.RwTx) error { err := b.db.update(func(bucket kvdb.RwBucket) error {
bucket := tx.ReadWriteBucket(resultsKey)
for e := l.Front(); e != nil; e = e.Next() { for e := l.Front(); e != nil; e = e.Next() {
pr, ok := e.Value.(*paymentResult) pr, ok := e.Value.(*paymentResult)
if !ok { if !ok {
+9 -3
View File
@@ -62,7 +62,9 @@ func newMCStoreTestHarness(t testing.TB, maxRecords int,
require.NoError(t, db.Close()) require.NoError(t, db.Close())
}) })
store, err := newMissionControlStore(db, maxRecords, flushInterval) store, err := newMissionControlStore(
newDefaultNamespacedStore(db), maxRecords, flushInterval,
)
require.NoError(t, err) require.NoError(t, err)
return mcStoreTestHarness{db: db, store: store} return mcStoreTestHarness{db: db, store: store}
@@ -115,7 +117,9 @@ func TestMissionControlStore(t *testing.T) {
require.Equal(t, &result2, results[1]) require.Equal(t, &result2, results[1])
// Recreate store to test pruning. // Recreate store to test pruning.
store, err = newMissionControlStore(db, testMaxRecords, time.Second) store, err = newMissionControlStore(
newDefaultNamespacedStore(db), testMaxRecords, time.Second,
)
require.NoError(t, err) require.NoError(t, err)
// Add a newer result which failed due to mpp timeout. // Add a newer result which failed due to mpp timeout.
@@ -213,7 +217,9 @@ func TestMissionControlStoreFlushing(t *testing.T) {
store.stop() store.stop()
// Recreate store. // Recreate store.
store, err := newMissionControlStore(db, testMaxRecords, flushInterval) store, err := newMissionControlStore(
newDefaultNamespacedStore(db), testMaxRecords, flushInterval,
)
require.NoError(t, err) require.NoError(t, err)
store.run() store.run()
defer store.stop() defer store.stop()
+120 -14
View File
@@ -5,6 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
@@ -40,9 +41,11 @@ var (
) )
type mcTestContext struct { type mcTestContext struct {
t *testing.T t *testing.T
mc *MissionControl
now time.Time mcController *MissionController
mc *MissionControl
clock *testClock
db kvdb.Backend db kvdb.Backend
dbPath string dbPath string
@@ -52,8 +55,8 @@ type mcTestContext struct {
func createMcTestContext(t *testing.T) *mcTestContext { func createMcTestContext(t *testing.T) *mcTestContext {
ctx := &mcTestContext{ ctx := &mcTestContext{
t: t, t: t,
now: mcTestTime, clock: newTestClock(mcTestTime),
} }
file, err := os.CreateTemp("", "*.db") file, err := os.CreateTemp("", "*.db")
@@ -85,9 +88,11 @@ func createMcTestContext(t *testing.T) *mcTestContext {
// restartMc creates a new instances of mission control on the same database. // restartMc creates a new instances of mission control on the same database.
func (ctx *mcTestContext) restartMc() { func (ctx *mcTestContext) restartMc() {
// Since we don't run a timer to store results in unit tests, we store // Since we don't run a timer to store results in unit tests, we store
// them here before fetching back everything in NewMissionControl. // them here before fetching back everything in NewMissionController.
if ctx.mc != nil { if ctx.mcController != nil {
require.NoError(ctx.t, ctx.mc.store.storeResults()) for _, mc := range ctx.mcController.mc {
require.NoError(ctx.t, mc.store.storeResults())
}
} }
aCfg := AprioriConfig{ aCfg := AprioriConfig{
@@ -99,7 +104,7 @@ func (ctx *mcTestContext) restartMc() {
estimator, err := NewAprioriEstimator(aCfg) estimator, err := NewAprioriEstimator(aCfg)
require.NoError(ctx.t, err) require.NoError(ctx.t, err)
mc, err := NewMissionControl( ctx.mcController, err = NewMissionController(
ctx.db, mcTestSelf, ctx.db, mcTestSelf,
&MissionControlConfig{Estimator: estimator}, &MissionControlConfig{Estimator: estimator},
) )
@@ -107,8 +112,18 @@ func (ctx *mcTestContext) restartMc() {
ctx.t.Fatal(err) ctx.t.Fatal(err)
} }
mc.now = func() time.Time { return ctx.now } ctx.mcController.cfg.clock = ctx.clock
ctx.mc = mc
// By default, select the default namespace.
ctx.setNamespacedMC(DefaultMissionControlNamespace)
}
// setNamespacedMC sets the currently selected MissionControl instance of the
// mcTextContext to the one with the given namespace.
func (ctx *mcTestContext) setNamespacedMC(namespace string) {
var err error
ctx.mc, err = ctx.mcController.GetNamespacedStore(namespace)
require.NoError(ctx.t, err)
} }
// Assert that mission control returns a probability for an edge. // Assert that mission control returns a probability for an edge.
@@ -150,7 +165,7 @@ func (ctx *mcTestContext) reportSuccess() {
func TestMissionControl(t *testing.T) { func TestMissionControl(t *testing.T) {
ctx := createMcTestContext(t) ctx := createMcTestContext(t)
ctx.now = testTime ctx.clock.setTime(testTime)
testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
@@ -178,7 +193,7 @@ func TestMissionControl(t *testing.T) {
// Edge decay started. The node probability weighted average should now // Edge decay started. The node probability weighted average should now
// have shifted from 1:1 to 1:0.5 -> 60%. The connection probability is // have shifted from 1:1 to 1:0.5 -> 60%. The connection probability is
// half way through the recovery, so we expect 30% here. // half way through the recovery, so we expect 30% here.
ctx.now = testTime.Add(30 * time.Minute) ctx.clock.setTime(testTime.Add(30 * time.Minute))
ctx.expectP(1000, 0.3) ctx.expectP(1000, 0.3)
// Edge fails again, this time without a min penalization amt. The edge // Edge fails again, this time without a min penalization amt. The edge
@@ -188,7 +203,7 @@ func TestMissionControl(t *testing.T) {
ctx.expectP(500, 0) ctx.expectP(500, 0)
// Edge decay started. // Edge decay started.
ctx.now = testTime.Add(60 * time.Minute) ctx.clock.setTime(testTime.Add(60 * time.Minute))
ctx.expectP(1000, 0.3) ctx.expectP(1000, 0.3)
// Restart mission control to test persistence. // Restart mission control to test persistence.
@@ -230,3 +245,94 @@ func TestMissionControlChannelUpdate(t *testing.T) {
) )
ctx.expectP(100, 0) ctx.expectP(100, 0)
} }
// TestMissionControlNamespaces tests that the results reported to a
// MissionControl instance in one namespace does not affect the query results in
// another namespace.
func TestMissionControlNamespaces(t *testing.T) {
// Create a new MC context. This will select the default namespace
// MissionControl instance.
ctx := createMcTestContext(t)
// Initially, the controller should only be aware of the default
// namespace.
require.ElementsMatch(t, ctx.mcController.ListNamespaces(), []string{
DefaultMissionControlNamespace,
})
// Initial probability is expected to be the apriori.
ctx.expectP(1000, testAprioriHopProbability)
// Expect probability to be zero after reporting the edge as failed.
ctx.reportFailure(1000, lnwire.NewTemporaryChannelFailure(nil))
ctx.expectP(1000, 0)
// Now, switch namespaces.
const newNs = "new-namespace"
ctx.setNamespacedMC(newNs)
// Now, the controller should only be aware of the default namespace and
// the new one.
require.ElementsMatch(t, ctx.mcController.ListNamespaces(), []string{
DefaultMissionControlNamespace,
newNs,
})
// Since this new namespace has no idea about the reported failure, the
// expected probability should once again be the apriori probability.
ctx.expectP(1000, testAprioriHopProbability)
// Report a success in the new namespace.
ctx.reportSuccess()
// The probability of the pair should now have increased.
ctx.expectP(1000, testAprioriHopProbability+0.05)
// Switch back to the default namespace.
ctx.setNamespacedMC(DefaultMissionControlNamespace)
// The probability in the default namespace should still be zero.
ctx.expectP(1000, 0)
// We also want to test that the initial loading of the namespaces is
// done correctly. So let's reload the controller and assert that the
// probabilities in both namespaces remain the same after restart.
ctx.restartMc()
// Assert that both namespaces were loaded.
require.ElementsMatch(t, ctx.mcController.ListNamespaces(), []string{
DefaultMissionControlNamespace,
newNs,
})
// Assert that the probabilities in both namespaces remain unchanged.
ctx.expectP(1000, 0)
ctx.setNamespacedMC(newNs)
ctx.expectP(1000, testAprioriHopProbability+0.05)
}
// testClock is an implementation of clock.Clock that lets the caller overwrite
// the current time at any point.
type testClock struct {
now time.Time
clock.Clock
}
// newTestClock constructs a new testClock.
func newTestClock(startTime time.Time) *testClock {
return &testClock{
now: startTime,
}
}
// Now returns the underlying current time.
//
// NOTE: this is part of the clock.Clock interface.
func (c *testClock) Now() time.Time {
return c.now
}
// setTime overwrites the current time.
func (c *testClock) setTime(n time.Time) {
c.now = n
}
+3 -3
View File
@@ -125,10 +125,10 @@ func (m *mockPaymentSessionSourceOld) NewPaymentSessionEmpty() PaymentSession {
} }
type mockMissionControlOld struct { type mockMissionControlOld struct {
MissionControl MissionController
} }
var _ MissionController = (*mockMissionControlOld)(nil) var _ MissionControlQuerier = (*mockMissionControlOld)(nil)
func (m *mockMissionControlOld) ReportPaymentFail( func (m *mockMissionControlOld) ReportPaymentFail(
paymentID uint64, rt *route.Route, paymentID uint64, rt *route.Route,
@@ -657,7 +657,7 @@ type mockMissionControl struct {
mock.Mock mock.Mock
} }
var _ MissionController = (*mockMissionControl)(nil) var _ MissionControlQuerier = (*mockMissionControl)(nil)
func (m *mockMissionControl) ReportPaymentFail( func (m *mockMissionControl) ReportPaymentFail(
paymentID uint64, rt *route.Route, paymentID uint64, rt *route.Route,
+5 -4
View File
@@ -159,7 +159,7 @@ type PaymentSession interface {
// paymentSession is used during an HTLC routings session to prune the local // paymentSession is used during an HTLC routings session to prune the local
// chain view in response to failures, and also report those failures back to // chain view in response to failures, and also report those failures back to
// MissionControl. The snapshot copied for this session will only ever grow, // MissionController. The snapshot copied for this session will only ever grow,
// and will now be pruned after a decay like the main view within mission // and will now be pruned after a decay like the main view within mission
// control. We do this as we want to avoid the case where we continually try a // control. We do this as we want to avoid the case where we continually try a
// bad edge or route multiple times in a session. This can lead to an infinite // bad edge or route multiple times in a session. This can lead to an infinite
@@ -184,7 +184,7 @@ type paymentSession struct {
// trade-off in path finding between fees and probability. // trade-off in path finding between fees and probability.
pathFindingConfig PathFindingConfig pathFindingConfig PathFindingConfig
missionControl MissionController missionControl MissionControlQuerier
// minShardAmt is the amount beyond which we won't try to further split // minShardAmt is the amount beyond which we won't try to further split
// the payment if no route is found. If the maximum number of htlcs // the payment if no route is found. If the maximum number of htlcs
@@ -199,7 +199,8 @@ type paymentSession struct {
// newPaymentSession instantiates a new payment session. // newPaymentSession instantiates a new payment session.
func newPaymentSession(p *LightningPayment, selfNode route.Vertex, func newPaymentSession(p *LightningPayment, selfNode route.Vertex,
getBandwidthHints func(Graph) (bandwidthHints, error), getBandwidthHints func(Graph) (bandwidthHints, error),
graphSessFactory GraphSessionFactory, missionControl MissionController, graphSessFactory GraphSessionFactory,
missionControl MissionControlQuerier,
pathFindingConfig PathFindingConfig) (*paymentSession, error) { pathFindingConfig PathFindingConfig) (*paymentSession, error) {
edges, err := RouteHintsToEdges(p.RouteHints, p.Target) edges, err := RouteHintsToEdges(p.RouteHints, p.Target)
@@ -266,7 +267,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
// Taking into account this prune view, we'll attempt to locate a path // Taking into account this prune view, we'll attempt to locate a path
// to our destination, respecting the recommendations from // to our destination, respecting the recommendations from
// MissionControl. // MissionController.
restrictions := &RestrictParams{ restrictions := &RestrictParams{
ProbabilitySource: p.missionControl.GetProbability, ProbabilitySource: p.missionControl.GetProbability,
FeeLimit: feeLimit, FeeLimit: feeLimit,
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"github.com/lightningnetwork/lnd/zpay32" "github.com/lightningnetwork/lnd/zpay32"
) )
// A compile time assertion to ensure MissionControl meets the // A compile time assertion to ensure SessionSource meets the
// PaymentSessionSource interface. // PaymentSessionSource interface.
var _ PaymentSessionSource = (*SessionSource)(nil) var _ PaymentSessionSource = (*SessionSource)(nil)
@@ -40,7 +40,7 @@ type SessionSource struct {
// then take into account this set of pruned vertexes/edges to reduce // then take into account this set of pruned vertexes/edges to reduce
// route failure and pass on graph information gained to the next // route failure and pass on graph information gained to the next
// execution. // execution.
MissionControl MissionController MissionControl MissionControlQuerier
// PathFindingConfig defines global parameters that control the // PathFindingConfig defines global parameters that control the
// trade-off in path finding between fees and probability. // trade-off in path finding between fees and probability.
+3 -3
View File
@@ -167,9 +167,9 @@ type PaymentSessionSource interface {
NewPaymentSessionEmpty() PaymentSession NewPaymentSessionEmpty() PaymentSession
} }
// MissionController is an interface that exposes failure reporting and // MissionControlQuerier is an interface that exposes failure reporting and
// probability estimation. // probability estimation.
type MissionController interface { type MissionControlQuerier interface {
// ReportPaymentFail reports a failed payment to mission control as // ReportPaymentFail reports a failed payment to mission control as
// input for future probability estimates. It returns a bool indicating // input for future probability estimates. It returns a bool indicating
// whether this error is a final error and no further payment attempts // whether this error is a final error and no further payment attempts
@@ -260,7 +260,7 @@ type Config struct {
// Each run will then take into account this set of pruned // Each run will then take into account this set of pruned
// vertexes/edges to reduce route failure and pass on graph information // vertexes/edges to reduce route failure and pass on graph information
// gained to the next execution. // gained to the next execution.
MissionControl MissionController MissionControl MissionControlQuerier
// SessionSource defines a source for the router to retrieve new payment // SessionSource defines a source for the router to retrieve new payment
// sessions. // sessions.
+19 -8
View File
@@ -128,11 +128,16 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
mcConfig := &MissionControlConfig{Estimator: estimator} mcConfig := &MissionControlConfig{Estimator: estimator}
mc, err := NewMissionControl( mcController, err := NewMissionController(
graphInstance.graphBackend, route.Vertex{}, mcConfig, graphInstance.graphBackend, route.Vertex{}, mcConfig,
) )
require.NoError(t, err, "failed to create missioncontrol") require.NoError(t, err, "failed to create missioncontrol")
mc, err := mcController.GetNamespacedStore(
DefaultMissionControlNamespace,
)
require.NoError(t, err)
sourceNode, err := graphInstance.graph.SourceNode() sourceNode, err := graphInstance.graph.SourceNode()
require.NoError(t, err) require.NoError(t, err)
sessionSource := &SessionSource{ sessionSource := &SessionSource{
@@ -1081,11 +1086,15 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
return preImage, nil return preImage, nil
}) })
ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() require.IsType(t, ctx.router.cfg.MissionControl, &MissionControl{})
mc, _ := ctx.router.cfg.MissionControl.(*MissionControl)
err := mc.ResetHistory()
require.NoError(t, err)
// When we try to dispatch that payment, we should receive an error as // When we try to dispatch that payment, we should receive an error as
// both attempts should fail and cause both routes to be pruned. // both attempts should fail and cause both routes to be pruned.
_, _, err := ctx.router.SendPayment(payment) _, _, err = ctx.router.SendPayment(payment)
require.Error(t, err, "payment didn't return error") require.Error(t, err, "payment didn't return error")
// The final error returned should also indicate that the peer wasn't // The final error returned should also indicate that the peer wasn't
@@ -1102,12 +1111,10 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// We expect the first attempt to have failed with a // We expect the first attempt to have failed with a
// TemporaryChannelFailure, the second with UnknownNextPeer. // TemporaryChannelFailure, the second with UnknownNextPeer.
msg := htlcs[0].Failure.Message msg := htlcs[0].Failure.Message
_, ok := msg.(*lnwire.FailTemporaryChannelFailure) require.IsType(t, msg, &lnwire.FailTemporaryChannelFailure{})
require.True(t, ok, "unexpected fail message")
msg = htlcs[1].Failure.Message msg = htlcs[1].Failure.Message
_, ok = msg.(*lnwire.FailUnknownNextPeer) require.IsType(t, msg, &lnwire.FailUnknownNextPeer{})
require.True(t, ok, "unexpected fail message")
err = ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() err = ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory()
require.NoError(t, err, "reset history failed") require.NoError(t, err, "reset history failed")
@@ -1144,7 +1151,11 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases), getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases),
) )
ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() require.IsType(t, ctx.router.cfg.MissionControl, &MissionControl{})
mc, _ = ctx.router.cfg.MissionControl.(*MissionControl)
err = mc.ResetHistory()
require.NoError(t, err)
// Finally, we'll modify the SendToSwitch function to indicate that the // Finally, we'll modify the SendToSwitch function to indicate that the
// roasbeef -> luoji channel has insufficient capacity. This should // roasbeef -> luoji channel has insufficient capacity. This should
+2 -2
View File
@@ -729,7 +729,7 @@ func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
return info.NodeKey1Bytes, info.NodeKey2Bytes, nil return info.NodeKey1Bytes, info.NodeKey2Bytes, nil
}, },
FindRoute: s.chanRouter.FindRoute, FindRoute: s.chanRouter.FindRoute,
MissionControl: s.missionControl, MissionControl: s.defaultMC,
ActiveNetParams: r.cfg.ActiveNetParams.Params, ActiveNetParams: r.cfg.ActiveNetParams.Params,
Tower: s.controlTower, Tower: s.controlTower,
MaxTotalTimelock: r.cfg.MaxOutgoingCltvExpiry, MaxTotalTimelock: r.cfg.MaxOutgoingCltvExpiry,
@@ -6071,7 +6071,7 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
return r.server.chanRouter.FindBlindedPaths( return r.server.chanRouter.FindBlindedPaths(
r.selfNode, amt, r.selfNode, amt,
r.server.missionControl.GetProbability, r.server.defaultMC.GetProbability,
blindingRestrictions, blindingRestrictions,
) )
}, },
+18 -8
View File
@@ -275,7 +275,8 @@ type server struct {
breachArbitrator *contractcourt.BreachArbitrator breachArbitrator *contractcourt.BreachArbitrator
missionControl *routing.MissionControl missionController *routing.MissionController
defaultMC *routing.MissionControl
graphBuilder *graph.Builder graphBuilder *graph.Builder
@@ -955,11 +956,20 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
McFlushInterval: routingConfig.McFlushInterval, McFlushInterval: routingConfig.McFlushInterval,
MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval, MinFailureRelaxInterval: routing.DefaultMinFailureRelaxInterval,
} }
s.missionControl, err = routing.NewMissionControl(
s.missionController, err = routing.NewMissionController(
dbs.ChanStateDB, selfNode.PubKeyBytes, mcCfg, dbs.ChanStateDB, selfNode.PubKeyBytes, mcCfg,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("can't create mission control: %w", err) return nil, fmt.Errorf("can't create mission control "+
"manager: %w", err)
}
s.defaultMC, err = s.missionController.GetNamespacedStore(
routing.DefaultMissionControlNamespace,
)
if err != nil {
return nil, fmt.Errorf("can't create mission control in the "+
"default namespace: %w", err)
} }
srvrLog.Debugf("Instantiating payment session source with config: "+ srvrLog.Debugf("Instantiating payment session source with config: "+
@@ -985,7 +995,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
chanGraph, chanGraph,
), ),
SourceNode: sourceNode, SourceNode: sourceNode,
MissionControl: s.missionControl, MissionControl: s.defaultMC,
GetLink: s.htlcSwitch.GetLinkByShortID, GetLink: s.htlcSwitch.GetLinkByShortID,
PathFindingConfig: pathFindingConfig, PathFindingConfig: pathFindingConfig,
} }
@@ -1020,7 +1030,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
Chain: cc.ChainIO, Chain: cc.ChainIO,
Payer: s.htlcSwitch, Payer: s.htlcSwitch,
Control: s.controlTower, Control: s.controlTower,
MissionControl: s.missionControl, MissionControl: s.defaultMC,
SessionSource: paymentSessionSource, SessionSource: paymentSessionSource,
GetLink: s.htlcSwitch.GetLinkByShortID, GetLink: s.htlcSwitch.GetLinkByShortID,
NextPaymentID: sequencer.NextID, NextPaymentID: sequencer.NextID,
@@ -2182,10 +2192,10 @@ func (s *server) Start() error {
} }
cleanup.add(func() error { cleanup.add(func() error {
s.missionControl.StopStoreTicker() s.missionController.StopStoreTickers()
return nil return nil
}) })
s.missionControl.RunStoreTicker() s.missionController.RunStoreTickers()
// Before we start the connMgr, we'll check to see if we have // Before we start the connMgr, we'll check to see if we have
// any backups to recover. We do this now as we want to ensure // any backups to recover. We do this now as we want to ensure
@@ -2467,7 +2477,7 @@ func (s *server) Stop() error {
srvrLog.Warnf("Unable to stop ChannelEventStore: %v", srvrLog.Warnf("Unable to stop ChannelEventStore: %v",
err) err)
} }
s.missionControl.StopStoreTicker() s.missionController.StopStoreTickers()
// Disconnect from each active peers to ensure that // Disconnect from each active peers to ensure that
// peerTerminationWatchers signal completion to each peer. // peerTerminationWatchers signal completion to each peer.