2017-10-17 03:57:30 +02:00
|
|
|
package routing
|
|
|
|
|
|
|
|
import (
|
2021-01-19 09:57:13 +01:00
|
|
|
"errors"
|
2021-01-19 09:57:11 +01:00
|
|
|
"fmt"
|
2017-10-17 03:57:30 +02:00
|
|
|
"sync"
|
|
|
|
"time"
|
2017-10-18 04:41:46 +02:00
|
|
|
|
2022-05-26 10:18:12 +02:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2024-10-15 15:28:46 +02:00
|
|
|
"github.com/btcsuite/btclog/v2"
|
2024-08-13 18:00:55 +02:00
|
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
2024-08-13 19:01:42 +02:00
|
|
|
"github.com/lightningnetwork/lnd/build"
|
2019-06-26 09:49:16 +02:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2024-08-13 18:52:16 +02:00
|
|
|
"github.com/lightningnetwork/lnd/clock"
|
2024-07-16 14:53:31 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn"
|
2021-04-26 19:08:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
2018-03-27 05:53:46 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2019-04-05 17:36:11 +02:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2017-10-17 03:57:30 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-05-22 11:56:04 +02:00
|
|
|
// DefaultPenaltyHalfLife is the default half-life duration. The
|
2019-03-19 17:09:27 +01:00
|
|
|
// half-life duration defines after how much time a penalized node or
|
|
|
|
// channel is back at 50% probability.
|
2019-05-22 11:56:04 +02:00
|
|
|
DefaultPenaltyHalfLife = time.Hour
|
2019-06-26 08:39:34 +02:00
|
|
|
|
|
|
|
// minSecondChanceInterval is the minimum time required between
|
|
|
|
// second-chance failures.
|
|
|
|
//
|
|
|
|
// If nodes return a channel policy related failure, they may get a
|
|
|
|
// second chance to forward the payment. It could be that the channel
|
|
|
|
// policy that we are aware of is not up to date. This is especially
|
|
|
|
// important in case of mobile apps that are mostly offline.
|
|
|
|
//
|
|
|
|
// However, we don't want to give nodes the option to endlessly return
|
|
|
|
// new channel updates so that we are kept busy trying to route through
|
|
|
|
// that node until the payment loop times out.
|
|
|
|
//
|
|
|
|
// Therefore we only grant a second chance to a node if the previous
|
|
|
|
// second chance is sufficiently long ago. This is what
|
|
|
|
// minSecondChanceInterval defines. If a second policy failure comes in
|
|
|
|
// within that interval, we will apply a penalty.
|
|
|
|
//
|
|
|
|
// Second chances granted are tracked on the level of node pairs. This
|
|
|
|
// means that if a node has multiple channels to the same peer, they
|
|
|
|
// will only get a single second chance to route to that peer again.
|
|
|
|
// Nodes forward non-strict, so it isn't necessary to apply a less
|
|
|
|
// restrictive channel level tracking scheme here.
|
|
|
|
minSecondChanceInterval = time.Minute
|
2019-06-26 13:00:35 +02:00
|
|
|
|
|
|
|
// DefaultMaxMcHistory is the default maximum history size.
|
|
|
|
DefaultMaxMcHistory = 1000
|
2019-07-29 14:20:06 +02:00
|
|
|
|
2022-01-13 17:29:43 +01:00
|
|
|
// DefaultMcFlushInterval is the default interval we use to flush MC state
|
2021-06-25 22:22:12 +02:00
|
|
|
// to the database.
|
|
|
|
DefaultMcFlushInterval = time.Second
|
|
|
|
|
2019-07-29 14:20:06 +02:00
|
|
|
// prevSuccessProbability is the assumed probability for node pairs that
|
|
|
|
// successfully relayed the previous attempt.
|
|
|
|
prevSuccessProbability = 0.95
|
2019-09-04 17:40:14 +02:00
|
|
|
|
|
|
|
// DefaultAprioriWeight is the default a priori weight. See
|
|
|
|
// MissionControlConfig for further explanation.
|
|
|
|
DefaultAprioriWeight = 0.5
|
2020-03-23 19:59:16 +01:00
|
|
|
|
|
|
|
// DefaultMinFailureRelaxInterval is the default minimum time that must
|
|
|
|
// have passed since the previously recorded failure before the failure
|
|
|
|
// amount may be raised.
|
|
|
|
DefaultMinFailureRelaxInterval = time.Minute
|
2023-10-31 14:46:07 +01:00
|
|
|
|
|
|
|
// DefaultFeeEstimationTimeout is the default value for
|
|
|
|
// FeeEstimationTimeout. It defines the maximum duration that the
|
|
|
|
// probing fee estimation is allowed to take.
|
|
|
|
DefaultFeeEstimationTimeout = time.Minute
|
2024-08-13 18:24:46 +02:00
|
|
|
|
|
|
|
// 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"
|
2017-10-17 03:57:30 +02:00
|
|
|
)
|
|
|
|
|
2021-01-19 09:57:13 +01:00
|
|
|
var (
|
|
|
|
// ErrInvalidMcHistory is returned if we get a negative mission control
|
|
|
|
// history count.
|
|
|
|
ErrInvalidMcHistory = errors.New("mission control history must be " +
|
|
|
|
">= 0")
|
|
|
|
|
|
|
|
// ErrInvalidFailureInterval is returned if we get an invalid failure
|
|
|
|
// interval.
|
|
|
|
ErrInvalidFailureInterval = errors.New("failure interval must be >= 0")
|
|
|
|
)
|
|
|
|
|
2019-09-03 17:36:32 +02:00
|
|
|
// NodeResults contains previous results from a node to its peers.
|
2019-09-26 15:31:24 +02:00
|
|
|
type NodeResults map[route.Vertex]TimedPairResult
|
2019-09-03 17:36:32 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-05-23 20:05:30 +02:00
|
|
|
// MissionControl contains state which summarizes the past attempts of HTLC
|
2019-03-19 17:09:27 +01:00
|
|
|
// 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
|
|
|
|
// payment attempt success rate.
|
|
|
|
//
|
|
|
|
// Failed payment attempts are reported to mission control. These reports are
|
|
|
|
// used to track the time of the last node or channel level failure. The time
|
|
|
|
// since the last failure is used to estimate a success probability that is fed
|
|
|
|
// into the path finding process for subsequent payment attempts.
|
2019-05-23 20:05:30 +02:00
|
|
|
type MissionControl struct {
|
2024-08-13 18:52:16 +02:00
|
|
|
cfg *mcConfig
|
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
// state is the internal mission control state that is input for
|
|
|
|
// probability estimation.
|
|
|
|
state *missionControlState
|
2019-06-26 08:39:34 +02:00
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
store *missionControlStore
|
|
|
|
|
2019-09-04 17:40:14 +02:00
|
|
|
// estimator is the probability estimator that is used with the payment
|
|
|
|
// results that mission control collects.
|
2023-01-20 11:25:53 +01:00
|
|
|
estimator Estimator
|
2019-09-04 17:40:14 +02:00
|
|
|
|
2024-07-16 14:53:31 +02:00
|
|
|
// onConfigUpdate is a function that is called whenever the
|
|
|
|
// mission control state is updated.
|
|
|
|
onConfigUpdate fn.Option[func(cfg *MissionControlConfig)]
|
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
log btclog.Logger
|
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
mu sync.Mutex
|
2024-08-13 18:52:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MissionController manages MissionControl instances in various namespaces.
|
|
|
|
type MissionController struct {
|
2024-08-13 19:19:06 +02:00
|
|
|
db kvdb.Backend
|
|
|
|
cfg *mcConfig
|
|
|
|
defaultMCCfg *MissionControlConfig
|
2024-08-13 18:52:16 +02:00
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
mc map[string]*MissionControl
|
2024-08-13 18:52:16 +02:00
|
|
|
mu sync.Mutex
|
2017-10-17 03:57:30 +02:00
|
|
|
|
|
|
|
// TODO(roasbeef): further counters, if vertex continually unavailable,
|
|
|
|
// add to another generation
|
|
|
|
|
|
|
|
// TODO(roasbeef): also add favorable metrics for nodes
|
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
// 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) {
|
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
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
|
2024-08-13 18:52:16 +02:00
|
|
|
}
|
|
|
|
|
2019-05-22 11:56:04 +02:00
|
|
|
// MissionControlConfig defines parameters that control mission control
|
|
|
|
// behaviour.
|
|
|
|
type MissionControlConfig struct {
|
2023-01-20 11:25:53 +01:00
|
|
|
// Estimator gives probability estimates for node pairs.
|
|
|
|
Estimator Estimator
|
2019-06-26 13:00:35 +02:00
|
|
|
|
2024-07-16 14:53:31 +02:00
|
|
|
// OnConfigUpdate is function that is called whenever the
|
|
|
|
// mission control state is updated.
|
|
|
|
OnConfigUpdate fn.Option[func(cfg *MissionControlConfig)]
|
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
// MaxMcHistory defines the maximum number of payment results that are
|
|
|
|
// held on disk.
|
|
|
|
MaxMcHistory int
|
2019-09-04 17:40:14 +02:00
|
|
|
|
2021-06-25 22:22:12 +02:00
|
|
|
// McFlushInterval defines the ticker interval when we flush the
|
|
|
|
// accumulated state to the DB.
|
|
|
|
McFlushInterval time.Duration
|
|
|
|
|
2020-03-23 19:59:16 +01:00
|
|
|
// MinFailureRelaxInterval is the minimum time that must have passed
|
|
|
|
// since the previously recorded failure before the failure amount may
|
|
|
|
// be raised.
|
|
|
|
MinFailureRelaxInterval time.Duration
|
2019-05-22 11:56:04 +02:00
|
|
|
}
|
|
|
|
|
2021-01-19 09:57:13 +01:00
|
|
|
func (c *MissionControlConfig) validate() error {
|
|
|
|
if c.MaxMcHistory < 0 {
|
|
|
|
return ErrInvalidMcHistory
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.MinFailureRelaxInterval < 0 {
|
|
|
|
return ErrInvalidFailureInterval
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-19 09:57:11 +01:00
|
|
|
// String returns a string representation of a mission control config.
|
|
|
|
func (c *MissionControlConfig) String() string {
|
2023-01-20 11:25:53 +01:00
|
|
|
return fmt.Sprintf("maximum history: %v, minimum failure relax "+
|
|
|
|
"interval: %v", c.MaxMcHistory, c.MinFailureRelaxInterval)
|
2021-01-19 09:57:11 +01:00
|
|
|
}
|
|
|
|
|
2019-09-26 15:31:24 +02:00
|
|
|
// TimedPairResult describes a timestamped pair result.
|
|
|
|
type TimedPairResult struct {
|
2019-09-26 17:04:02 +02:00
|
|
|
// FailTime is the time of the last failure.
|
|
|
|
FailTime time.Time
|
|
|
|
|
|
|
|
// FailAmt is the amount of the last failure. This amount may be pushed
|
|
|
|
// up if a later success is higher than the last failed amount.
|
|
|
|
FailAmt lnwire.MilliSatoshi
|
|
|
|
|
|
|
|
// SuccessTime is the time of the last success.
|
|
|
|
SuccessTime time.Time
|
|
|
|
|
|
|
|
// SuccessAmt is the highest amount that successfully forwarded. This
|
|
|
|
// isn't necessarily the last success amount. The value of this field
|
|
|
|
// may also be pushed down if a later failure is lower than the highest
|
|
|
|
// success amount. Because of this, SuccessAmt may not match
|
|
|
|
// SuccessTime.
|
|
|
|
SuccessAmt lnwire.MilliSatoshi
|
2017-10-18 04:41:46 +02:00
|
|
|
}
|
|
|
|
|
2019-05-10 10:38:31 +02:00
|
|
|
// MissionControlSnapshot contains a snapshot of the current state of mission
|
|
|
|
// control.
|
|
|
|
type MissionControlSnapshot struct {
|
2019-07-29 15:10:58 +02:00
|
|
|
// Pairs is a list of channels for which specific information is
|
|
|
|
// logged.
|
|
|
|
Pairs []MissionControlPairSnapshot
|
2019-05-10 10:38:31 +02:00
|
|
|
}
|
|
|
|
|
2019-07-29 15:10:58 +02:00
|
|
|
// MissionControlPairSnapshot contains a snapshot of the current node pair
|
2019-05-10 10:38:31 +02:00
|
|
|
// state in mission control.
|
2019-07-29 15:10:58 +02:00
|
|
|
type MissionControlPairSnapshot struct {
|
|
|
|
// Pair is the node pair of which the state is described.
|
|
|
|
Pair DirectedNodePair
|
2019-05-10 10:38:31 +02:00
|
|
|
|
2019-09-30 09:20:55 +02:00
|
|
|
// TimedPairResult contains the data for this pair.
|
|
|
|
TimedPairResult
|
2019-05-10 10:38:31 +02:00
|
|
|
}
|
|
|
|
|
2019-06-26 12:25:23 +02:00
|
|
|
// paymentResult is the information that becomes available when a payment
|
|
|
|
// attempt completes.
|
|
|
|
type paymentResult struct {
|
|
|
|
id uint64
|
|
|
|
timeFwd, timeReply time.Time
|
2024-08-06 14:33:07 +02:00
|
|
|
route *mcRoute
|
2019-06-26 12:25:23 +02:00
|
|
|
success bool
|
|
|
|
failureSourceIdx *int
|
|
|
|
failure lnwire.FailureMessage
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
// NewMissionController returns a new instance of MissionController.
|
|
|
|
func NewMissionController(db kvdb.Backend, self route.Vertex,
|
|
|
|
cfg *MissionControlConfig) (*MissionController, error) {
|
2019-06-26 13:00:35 +02:00
|
|
|
|
2023-01-20 11:25:53 +01:00
|
|
|
log.Debugf("Instantiating mission control with config: %v, %v", cfg,
|
|
|
|
cfg.Estimator)
|
2017-10-17 03:57:30 +02:00
|
|
|
|
2021-01-19 09:57:13 +01:00
|
|
|
if err := cfg.validate(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
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
|
|
|
|
|
2021-06-25 22:22:12 +02:00
|
|
|
store, err := newMissionControlStore(
|
2024-08-13 19:19:06 +02:00
|
|
|
newNamespacedDB(m.db, namespace), cfg.MaxMcHistory,
|
2024-08-13 18:00:55 +02:00
|
|
|
cfg.McFlushInterval,
|
2021-06-25 22:22:12 +02:00
|
|
|
)
|
2019-06-26 13:00:35 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
mc := &MissionControl{
|
|
|
|
cfg: m.cfg,
|
2024-08-13 19:01:42 +02:00
|
|
|
state: newMissionControlState(cfg.MinFailureRelaxInterval),
|
|
|
|
store: store,
|
|
|
|
estimator: cfg.Estimator,
|
|
|
|
log: build.NewPrefixLog(
|
2024-08-13 19:19:06 +02:00
|
|
|
fmt.Sprintf("[%s]:", namespace), log,
|
2024-08-13 19:01:42 +02:00
|
|
|
),
|
2024-07-16 14:53:31 +02:00
|
|
|
onConfigUpdate: cfg.OnConfigUpdate,
|
2019-06-26 13:00:35 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
m.mc[namespace] = mc
|
2019-06-26 13:00:35 +02:00
|
|
|
|
|
|
|
return mc, nil
|
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
// RunStoreTickers runs the mission controller store's tickers.
|
|
|
|
func (m *MissionController) RunStoreTickers() {
|
2024-08-13 18:52:16 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
for _, mc := range m.mc {
|
|
|
|
mc.store.run()
|
|
|
|
}
|
2021-06-25 22:22:12 +02:00
|
|
|
}
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
// StopStoreTickers stops the mission control store's tickers.
|
|
|
|
func (m *MissionController) StopStoreTickers() {
|
2023-09-07 20:16:42 +02:00
|
|
|
log.Debug("Stopping mission control store ticker")
|
|
|
|
defer log.Debug("Mission control store ticker stopped")
|
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
2024-08-13 19:19:06 +02:00
|
|
|
for _, mc := range m.mc {
|
|
|
|
mc.store.stop()
|
|
|
|
}
|
2021-06-25 22:22:12 +02:00
|
|
|
}
|
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
// init initializes mission control with historical data.
|
|
|
|
func (m *MissionControl) init() error {
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Mission control state reconstruction started")
|
2019-06-26 13:00:35 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
results, err := m.store.fetchAll()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, result := range results {
|
2024-08-13 18:52:16 +02:00
|
|
|
_ = m.applyPaymentResult(result)
|
2019-05-23 20:05:29 +02:00
|
|
|
}
|
2019-06-26 13:00:35 +02:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Mission control state reconstruction finished: "+
|
2020-04-09 20:42:27 +02:00
|
|
|
"n=%v, time=%v", len(results), time.Since(start))
|
2019-06-26 13:00:35 +02:00
|
|
|
|
|
|
|
return nil
|
2019-05-23 20:05:29 +02:00
|
|
|
}
|
|
|
|
|
2021-01-19 09:57:14 +01:00
|
|
|
// GetConfig returns the config that mission control is currently configured
|
|
|
|
// with. All fields are copied by value, so we do not need to worry about
|
|
|
|
// mutation.
|
|
|
|
func (m *MissionControl) GetConfig() *MissionControlConfig {
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2021-01-19 09:57:14 +01:00
|
|
|
|
|
|
|
return &MissionControlConfig{
|
2023-01-20 11:25:53 +01:00
|
|
|
Estimator: m.estimator,
|
2021-01-19 09:57:14 +01:00
|
|
|
MaxMcHistory: m.store.maxRecords,
|
2021-06-25 22:22:12 +02:00
|
|
|
McFlushInterval: m.store.flushInterval,
|
2021-01-19 09:57:14 +01:00
|
|
|
MinFailureRelaxInterval: m.state.minFailureRelaxInterval,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetConfig validates the config provided and updates mission control's config
|
|
|
|
// if it is valid.
|
|
|
|
func (m *MissionControl) SetConfig(cfg *MissionControlConfig) error {
|
|
|
|
if cfg == nil {
|
|
|
|
return errors.New("nil mission control config")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cfg.validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2021-01-19 09:57:14 +01:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Infof("Active mission control cfg: %v, estimator: %v", cfg,
|
2023-01-20 11:25:53 +01:00
|
|
|
cfg.Estimator)
|
2021-01-19 09:57:14 +01:00
|
|
|
|
|
|
|
m.store.maxRecords = cfg.MaxMcHistory
|
|
|
|
m.state.minFailureRelaxInterval = cfg.MinFailureRelaxInterval
|
2023-01-20 11:25:53 +01:00
|
|
|
m.estimator = cfg.Estimator
|
2021-01-19 09:57:14 +01:00
|
|
|
|
2024-07-16 14:53:31 +02:00
|
|
|
// Execute the callback function if it is set.
|
|
|
|
m.onConfigUpdate.WhenSome(func(f func(cfg *MissionControlConfig)) {
|
|
|
|
f(cfg)
|
|
|
|
})
|
|
|
|
|
2021-01-19 09:57:14 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-23 20:05:30 +02:00
|
|
|
// ResetHistory resets the history of MissionControl returning it to a state as
|
2017-10-17 03:57:30 +02:00
|
|
|
// if no payment attempts have been made.
|
2019-06-26 13:00:35 +02:00
|
|
|
func (m *MissionControl) ResetHistory() error {
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2019-03-19 17:09:27 +01:00
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
if err := m.store.clear(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
m.state.resetHistory()
|
2019-03-19 17:09:27 +01:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Mission control history cleared")
|
2019-06-26 13:00:35 +02:00
|
|
|
|
|
|
|
return nil
|
2019-03-19 17:09:27 +01:00
|
|
|
}
|
|
|
|
|
2019-07-29 15:10:58 +02:00
|
|
|
// GetProbability is expected to return the success probability of a payment
|
2019-03-19 17:09:27 +01:00
|
|
|
// from fromNode along edge.
|
2019-07-29 15:10:58 +02:00
|
|
|
func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
|
2022-05-26 10:18:12 +02:00
|
|
|
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
|
2019-03-19 17:09:27 +01:00
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2019-03-19 17:09:27 +01:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
now := m.cfg.clock.Now()
|
2020-03-24 16:26:48 +01:00
|
|
|
results, _ := m.state.getLastPairResult(fromNode)
|
2019-03-19 17:09:27 +01:00
|
|
|
|
2019-11-07 11:24:38 +01:00
|
|
|
// Use a distinct probability estimation function for local channels.
|
2024-08-13 18:52:16 +02:00
|
|
|
if fromNode == m.cfg.selfNode {
|
2022-11-18 10:13:45 +01:00
|
|
|
return m.estimator.LocalPairProbability(now, results, toNode)
|
2019-11-07 11:24:38 +01:00
|
|
|
}
|
|
|
|
|
2022-11-18 10:13:45 +01:00
|
|
|
return m.estimator.PairProbability(
|
2022-05-26 10:18:12 +02:00
|
|
|
now, results, toNode, amt, capacity,
|
|
|
|
)
|
2019-09-03 17:36:32 +02:00
|
|
|
}
|
|
|
|
|
2019-05-10 10:38:31 +02:00
|
|
|
// GetHistorySnapshot takes a snapshot from the current mission control state
|
|
|
|
// and actual probability estimates.
|
|
|
|
func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2019-05-10 10:38:31 +02:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Requesting history snapshot from mission control")
|
2019-05-10 10:38:31 +02:00
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
return m.state.getSnapshot()
|
2019-05-10 10:38:31 +02:00
|
|
|
}
|
2019-06-26 09:49:16 +02:00
|
|
|
|
2021-03-18 09:46:45 +01:00
|
|
|
// ImportHistory imports the set of mission control results provided to our
|
|
|
|
// in-memory state. These results are not persisted, so will not survive
|
|
|
|
// restarts.
|
2022-01-19 17:01:09 +01:00
|
|
|
func (m *MissionControl) ImportHistory(history *MissionControlSnapshot,
|
|
|
|
force bool) error {
|
|
|
|
|
2021-03-18 09:46:45 +01:00
|
|
|
if history == nil {
|
|
|
|
return errors.New("cannot import nil history")
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2021-03-18 09:46:45 +01:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Infof("Importing history snapshot with %v pairs to mission "+
|
|
|
|
"control", len(history.Pairs))
|
2021-03-18 09:46:45 +01:00
|
|
|
|
2022-01-19 17:01:09 +01:00
|
|
|
imported := m.state.importSnapshot(history, force)
|
2021-03-18 09:46:45 +01:00
|
|
|
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Infof("Imported %v results to mission control", imported)
|
2021-03-18 09:46:45 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-27 11:43:12 +02:00
|
|
|
// GetPairHistorySnapshot returns the stored history for a given node pair.
|
|
|
|
func (m *MissionControl) GetPairHistorySnapshot(
|
|
|
|
fromNode, toNode route.Vertex) TimedPairResult {
|
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2019-09-27 11:43:12 +02:00
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
results, ok := m.state.getLastPairResult(fromNode)
|
2019-09-27 11:43:12 +02:00
|
|
|
if !ok {
|
|
|
|
return TimedPairResult{}
|
|
|
|
}
|
|
|
|
|
|
|
|
result, ok := results[toNode]
|
|
|
|
if !ok {
|
|
|
|
return TimedPairResult{}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-06-26 09:49:16 +02:00
|
|
|
// ReportPaymentFail reports a failed payment to mission control as input for
|
2019-06-26 11:34:25 +02:00
|
|
|
// future probability estimates. The failureSourceIdx argument indicates the
|
|
|
|
// failure source. If it is nil, the failure source is unknown. This function
|
2019-08-05 12:13:58 +02:00
|
|
|
// returns a reason if this failure is a final failure. In that case no further
|
|
|
|
// payment attempts need to be made.
|
2019-06-26 11:48:59 +02:00
|
|
|
func (m *MissionControl) ReportPaymentFail(paymentID uint64, rt *route.Route,
|
2019-08-05 12:13:58 +02:00
|
|
|
failureSourceIdx *int, failure lnwire.FailureMessage) (
|
|
|
|
*channeldb.FailureReason, error) {
|
2019-06-26 09:49:16 +02:00
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
timestamp := m.cfg.clock.Now()
|
2019-07-02 11:29:44 +02:00
|
|
|
|
2019-06-26 12:25:23 +02:00
|
|
|
result := &paymentResult{
|
|
|
|
success: false,
|
|
|
|
timeFwd: timestamp,
|
|
|
|
timeReply: timestamp,
|
|
|
|
id: paymentID,
|
|
|
|
failureSourceIdx: failureSourceIdx,
|
|
|
|
failure: failure,
|
2024-08-06 14:33:07 +02:00
|
|
|
route: extractMCRoute(rt),
|
2019-06-26 12:25:23 +02:00
|
|
|
}
|
|
|
|
|
2019-07-29 14:20:06 +02:00
|
|
|
return m.processPaymentResult(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReportPaymentSuccess reports a successful payment to mission control as input
|
|
|
|
// for future probability estimates.
|
|
|
|
func (m *MissionControl) ReportPaymentSuccess(paymentID uint64,
|
|
|
|
rt *route.Route) error {
|
|
|
|
|
2024-08-13 18:52:16 +02:00
|
|
|
timestamp := m.cfg.clock.Now()
|
2019-07-29 14:20:06 +02:00
|
|
|
|
|
|
|
result := &paymentResult{
|
|
|
|
timeFwd: timestamp,
|
|
|
|
timeReply: timestamp,
|
|
|
|
id: paymentID,
|
|
|
|
success: true,
|
2024-08-06 14:33:07 +02:00
|
|
|
route: extractMCRoute(rt),
|
2019-07-29 14:20:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_, err := m.processPaymentResult(result)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// processPaymentResult stores a payment result in the mission control store and
|
|
|
|
// updates mission control's in-memory state.
|
|
|
|
func (m *MissionControl) processPaymentResult(result *paymentResult) (
|
|
|
|
*channeldb.FailureReason, error) {
|
|
|
|
|
2019-06-26 13:00:35 +02:00
|
|
|
// Store complete result in database.
|
2021-06-25 22:22:12 +02:00
|
|
|
m.store.AddResult(result)
|
2019-06-26 13:00:35 +02:00
|
|
|
|
2024-08-13 18:26:54 +02:00
|
|
|
m.mu.Lock()
|
|
|
|
defer m.mu.Unlock()
|
2021-03-17 12:00:14 +01:00
|
|
|
|
2019-07-02 11:29:44 +02:00
|
|
|
// Apply result to update mission control state.
|
2019-08-05 12:29:16 +02:00
|
|
|
reason := m.applyPaymentResult(result)
|
2019-08-05 12:13:58 +02:00
|
|
|
|
2019-08-05 12:29:16 +02:00
|
|
|
return reason, nil
|
2019-07-02 11:29:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// applyPaymentResult applies a payment result as input for future probability
|
|
|
|
// estimates. It returns a bool indicating whether this error is a final error
|
|
|
|
// and no further payment attempts need to be made.
|
2019-08-05 12:29:16 +02:00
|
|
|
func (m *MissionControl) applyPaymentResult(
|
|
|
|
result *paymentResult) *channeldb.FailureReason {
|
2019-07-02 11:29:44 +02:00
|
|
|
|
2019-08-05 12:29:16 +02:00
|
|
|
// Interpret result.
|
|
|
|
i := interpretResult(
|
2019-07-29 14:20:06 +02:00
|
|
|
result.route, result.success, result.failureSourceIdx,
|
|
|
|
result.failure,
|
2019-07-02 11:29:44 +02:00
|
|
|
)
|
|
|
|
|
2019-08-05 12:29:16 +02:00
|
|
|
if i.policyFailure != nil {
|
2020-03-24 16:26:48 +01:00
|
|
|
if m.state.requestSecondChance(
|
2019-08-05 12:29:16 +02:00
|
|
|
result.timeReply,
|
|
|
|
i.policyFailure.From, i.policyFailure.To,
|
|
|
|
) {
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-26 09:49:16 +02:00
|
|
|
}
|
|
|
|
|
2019-09-04 17:40:14 +02:00
|
|
|
// If there is a node-level failure, record a failure for every tried
|
|
|
|
// connection of that node. A node-level failure can be considered as a
|
|
|
|
// failure that would have occurred with any of the node's channels.
|
|
|
|
//
|
|
|
|
// Ideally we'd also record the failure for the untried connections of
|
|
|
|
// the node. Unfortunately this would require access to the graph and
|
|
|
|
// adding this dependency and db calls does not outweigh the benefits.
|
|
|
|
//
|
|
|
|
// Untried connections will fall back to the node probability. After the
|
|
|
|
// call to setAllPairResult below, the node probability will be equal to
|
|
|
|
// the probability of the tried channels except that the a priori
|
|
|
|
// probability is mixed in too. This effect is controlled by the
|
|
|
|
// aprioriWeight parameter. If that parameter isn't set to an extreme
|
|
|
|
// and there are a few known connections, there shouldn't be much of a
|
|
|
|
// difference. The largest difference occurs when aprioriWeight is 1. In
|
|
|
|
// that case, a node-level failure would not be applied to untried
|
|
|
|
// channels.
|
2019-08-05 12:29:16 +02:00
|
|
|
if i.nodeFailure != nil {
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Reporting node failure to Mission Control: "+
|
2019-08-05 12:29:16 +02:00
|
|
|
"node=%v", *i.nodeFailure)
|
2019-06-26 09:49:16 +02:00
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
m.state.setAllFail(*i.nodeFailure, result.timeReply)
|
2019-06-26 09:49:16 +02:00
|
|
|
}
|
|
|
|
|
2019-07-29 14:20:06 +02:00
|
|
|
for pair, pairResult := range i.pairResults {
|
2019-09-26 17:04:02 +02:00
|
|
|
pairResult := pairResult
|
|
|
|
|
2019-07-29 14:20:06 +02:00
|
|
|
if pairResult.success {
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Reporting pair success to Mission "+
|
2020-03-18 13:06:05 +01:00
|
|
|
"Control: pair=%v, amt=%v",
|
|
|
|
pair, pairResult.amt)
|
2019-07-29 14:20:06 +02:00
|
|
|
} else {
|
2024-08-13 19:01:42 +02:00
|
|
|
m.log.Debugf("Reporting pair failure to Mission "+
|
2019-09-26 16:33:08 +02:00
|
|
|
"Control: pair=%v, amt=%v",
|
|
|
|
pair, pairResult.amt)
|
2019-07-29 14:20:06 +02:00
|
|
|
}
|
2019-06-26 09:49:16 +02:00
|
|
|
|
2020-03-24 16:26:48 +01:00
|
|
|
m.state.setLastPairResult(
|
2022-01-19 17:01:09 +01:00
|
|
|
pair.From, pair.To, result.timeReply, &pairResult, false,
|
2019-09-26 15:27:41 +02:00
|
|
|
)
|
2019-06-26 09:49:16 +02:00
|
|
|
}
|
|
|
|
|
2019-08-05 12:29:16 +02:00
|
|
|
return i.finalFailureReason
|
2019-06-26 09:49:16 +02:00
|
|
|
}
|
2024-08-13 18:00:55 +02:00
|
|
|
|
|
|
|
// namespacedDB is an implementation of the missionControlDB that gives a user
|
2024-08-13 18:24:46 +02:00
|
|
|
// of the interface access to a namespaced bucket within the top level mission
|
|
|
|
// control bucket.
|
2024-08-13 18:00:55 +02:00
|
|
|
type namespacedDB struct {
|
|
|
|
topLevelBucketKey []byte
|
2024-08-13 18:24:46 +02:00
|
|
|
namespace []byte
|
2024-08-13 18:00:55 +02:00
|
|
|
db kvdb.Backend
|
|
|
|
}
|
|
|
|
|
|
|
|
// A compile-time check to ensure that namespacedDB implements missionControlDB.
|
|
|
|
var _ missionControlDB = (*namespacedDB)(nil)
|
|
|
|
|
2024-08-13 18:24:46 +02:00
|
|
|
// newDefaultNamespacedStore creates an instance of namespaceDB that uses the
|
|
|
|
// default namespace.
|
|
|
|
func newDefaultNamespacedStore(db kvdb.Backend) missionControlDB {
|
|
|
|
return newNamespacedDB(db, DefaultMissionControlNamespace)
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:00:55 +02:00
|
|
|
// newNamespacedDB creates a new instance of missionControlDB where the DB will
|
2024-08-13 18:24:46 +02:00
|
|
|
// have access to a namespaced bucket within the top level mission control
|
|
|
|
// bucket.
|
|
|
|
func newNamespacedDB(db kvdb.Backend, namespace string) missionControlDB {
|
2024-08-13 18:00:55 +02:00
|
|
|
return &namespacedDB{
|
|
|
|
db: db,
|
2024-08-13 18:24:46 +02:00
|
|
|
namespace: []byte(namespace),
|
2024-08-13 18:00:55 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:24:46 +02:00
|
|
|
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)
|
2024-08-13 18:00:55 +02:00
|
|
|
}, 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")
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:24:46 +02:00
|
|
|
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)
|
2024-08-13 18:00:55 +02:00
|
|
|
}, 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 {
|
2024-08-13 18:24:46 +02:00
|
|
|
mcStoreBkt := tx.ReadWriteBucket(n.topLevelBucketKey)
|
|
|
|
if mcStoreBkt == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := mcStoreBkt.DeleteNestedBucket(n.namespace)
|
2024-08-13 18:00:55 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-13 18:24:46 +02:00
|
|
|
_, err = mcStoreBkt.CreateBucket(n.namespace)
|
2024-08-13 18:00:55 +02:00
|
|
|
|
|
|
|
return err
|
|
|
|
}, func() {})
|
|
|
|
}
|