sweep

package
v0.18.0-beta.rc3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2024 License: MIT Imports: 34 Imported by: 0

README

Sweep

sweep is a subservice that handles sweeping UTXOs back to lnd's wallet. Its main purpose is to sweep back the outputs resulting from a force close transaction, although users can also call BumpFee to feed new unconfirmed inputs to be handled by the sweeper.

In order to sweep economically, the sweeper needs to understand the time sensitivity and max fees that can be used when sweeping the inputs. This means each input must come with a deadline and a fee budget, which can be set via the RPC request or the config, otherwise the default values will be used. Once offered to the sweeper, when a new block arrives, inputs with the same deadline will be batched into a single sweeping transaction to minimize the cost.

The sweeper will publish this transaction and monitor it for potential fee bumping, a process that won’t exit until the sweeping transaction is confirmed, or the specified budget has been used up.

Understanding Budget and Deadline

There are two questions when spending a UTXO - how much fees to pay and what the confirmation target is, which gives us the concepts of budget and deadline. This is especially important when sweeping the outputs of a force close transaction - some of the outputs are time-sensitive, and may result in fund loss if not confirmed in time. On the other hand, we don’t want to pay more than what we can get back - if a sweeping transaction spends more than what is meant to be swept, we are losing money due to fees.

To properly handle the case, the concept budget and deadline have been introduced to lnd since v0.18.0 - for each new sweeping request, the sweeper requires the caller to specify a deadline and a budget so it can make economic decisions. A fee function is then created based on the budget and deadline, which proposes a fee rate to use for the sweeping transaction. When a new block arrives, unless the transaction is confirmed or the budget is used up, the sweeper will perform a fee bump on it via RBF.

Package Structure

On a high level, a UTXO is offered to the sweeper via SweepInput. The sweeper keeps track of the pending inputs. When a new block arrives, it asks the UtxoAggregator to group all the pending inputs into batches via ClusterInputs. Each batch is an InputSet, and is sent to the Bumper. The Bumper creates a FeeFunction and a sweeping transaction using the InputSet, and monitors its confirmation status. Every time it's not confirmed when a new block arrives, the Bumper will perform an RBF by calling IncreaseFeeRate on the FeeFunction.

flowchart LR
        subgraph SweepInput
        UTXO1-->sweeper
      UTXO2-->sweeper
        UTXO3-->sweeper
        UTXO["..."]-->sweeper
        sweeper
    end

    subgraph ClusterInputs
        sweeper-->UtxoAggregator
      UtxoAggregator-->InputSet1
        UtxoAggregator-->InputSet2
        UtxoAggregator-->InputSet["..."]
    end

    subgraph Broadcast
            InputSet1-->Bumper
            InputSet2-->Bumper
            InputSet-->Bumper
    end

    subgraph IncreaseFeeRate
        FeeFunction-->Bumper
    end

        block["new block"] ==> ClusterInputs
UtxoAggregator and InputSet

UtxoAggregator is an interface that handles the batching of inputs. BudgetAggregator implements this interface by grouping inputs with the same deadline together. Inputs with the same deadline express the same time sensitivity so it makes sense to sweep them in the same transaction. Once grouped, inputs in each batch are sorted based on their budgets. The only exception is inputs with the ExclusiveGroup flag set, which will be swept alone.

Once the batching is finished, an InputSet is returned, which is an interface used to decide whether a wallet UTXO is needed or not when creating the sweeping transaction. BudgetInputSet implements this interface by checking the sum of the output values from these inputs against the sum of their budgets - if the total budget cannot be covered, one or more wallet UTXOs are needed.

For instance, commitment and HTLC transactions usually have some proportion of their outputs timelocked, preventing them from being used to pay fees immediately. For these transactions, wallet UTXOs are often needed to get them confirmed in a timely manner.

Bumper

Bumper is a transaction creator, publisher, and monitor that works on an InputSet. Once a sweeping transaction is created using the InputSet, the Bumper will monitor its confirmation status and attempt an RBF if the transaction is not confirmed in the next block. It relies on the FeeFunction to determine the new fee rate every block, and this new fee rate may or may not meet the BIP 125 fee requirements - in that case, the Bumper will try to perform an RBF again in the coming blocks.

TxPublisher implements the Bumper interface. When a transaction is created for the first time, unless its budget has been used up, TxPublisher will guarantee that the initial publish meets the RBF requirements.

FeeFunction

FeeFunction is an interface that specifies a function over a starting fee rate, an ending fee rate, and a width (the deadline delta). It's used by the Bumper to suggest a new fee rate for bumping the sweeping transaction.

LinearFeeFunction implements this interface using a linear function - it calculates a fee rate delta using (ending_fee_rate - starting_fee_rate) / deadline, and increases the fee rate by this delta value everytime a new block arrives. Once the deadline is passed, LinearFeeFunction will cap its returning fee rate at the ending fee rate.

The starting fee rate is the estimated fee rate from the fee estimator, which is the result from calling estimatesmartfee(bitcoind), estimatefee(btcd), or feeurl depending on the config. This fee estimator is called using the deadline as the conf target, and the returned fee rate is used as the starting fee rate. This behavior can be overridden by setting the --sat_per_vbyte via bumpfee cli when fee bumping a specific input, which allows users to bypass the fee estimation and set the starting fee rate directly.

The ending fee rate is the value from dividing the budget by the size of the sweeping transaction, and capped at the --sweeper.maxfeerate. The ending fee rate can be overridden by setting the --budget via bumpfee cli.

For instance, suppose lnd is using bitcoind as its fee estimator, and an input with a deadline of 1000 blocks and a budget of 200,000 sats is being swept in a transaction that has a size of 500 vbytes, the fee function will be initialized with:

  • a starting fee rate of 10 sat/vB, which is the result from calling estimatesmartfee 1000.
  • an ending fee rate of 400 sat/vB, which is the result of 200,000/500.
  • a fee rate delta of 390 sat/kvB, which is the result of (400 - 10) / 1000 * 1000.

Sweeping Outputs from a Force Close Transaction

A force close transaction may have the following outputs:

  • Commit outputs, which are the to_local and to_remote outputs.
  • HTLC outputs, which are the incoming_htlc and outgoing_htlc outputs.
  • Anchor outputs, which are the local and remote anchor outputs.
Sweeping Commit Outputs

The only output we can spend is the to_local output. Because it can only be spent using our signature, there’s no time pressure here. By default, the sweeper will use a deadline of 1008 blocks as the confirmation target for non-time-sensitive outputs. To overwrite the default, users can specify a value using the config --sweeper.nodeadlineconftarget.

To specify the budget, users can use --sweeper.budget.tolocal to set the max allowed fees in sats, or use --sweeper.budget.tolocalratio to set a proportion of the to_local value to be used as the budget.

Sweeping HTLC Outputs

When facing a local force close transaction, HTLCs are spent in a two-stage setup - the first stage is to spend the outputs using pre-signed HTLC success/timeout transactions, the second stage is to spend the outputs from these success/timeout transactions. All these outputs are automatically handled by lnd. In specific,

  • For an incoming HTLC in stage one, the deadline is specified using its CLTV from the timeout path. This output is time-sensitive.
  • For an outgoing HTLC in stage one, the deadline is derived from its corresponding incoming HTLC’s CLTV. This output is time-sensitive.
  • For both incoming and outgoing HTLCs in stage two, because they can only be spent by us, there is no time pressure to confirm them under a deadline.

When facing a remote force close transaction, HTLCs can be directly spent from the commitment transaction, and both incoming and outgoing HTLCs are time-sensitive.

By default, lnd will use 50% of the HTLC value as its budget. To customize it, users can specify --sweeper.budget.deadlinehtlc and --sweeper.budget.deadlinehtlcratio for time-sensitive HTLCs, and --sweeper.budget.nodeadlinehtlc and --sweeper.budget.nodeadlinehtlcratio for non-time-sensitive sweeps.

Sweeping Anchor Outputs

An anchor output is a special output that functions as “anchor” to speed up the unconfirmed force closing transaction via CPFP. If the force close transaction doesn't contain any HTLCs, the anchor output is generally uneconomical to sweep and will be ignored. However, if the force close transaction does contain time-sensitive outputs (HTLCs), the anchor output will be swept to CPFP the transaction and accelerate the force close process.

For CPFP-purpose anchor sweeping, the deadline is the closest deadline value of all the HTLCs on the force close transaction. The budget, however, cannot be a ratio of the anchor output because the value is too small to contribute meaningful fees (330 sats). Since its purpose is to accelerate the force close transaction so the time-sensitive outputs can be swept, the budget is actually drawn from what we call “value under protection”, which is the sum of all HTLC outputs minus the sum of their budgets. By default, 50% of this value is used as the budget, to customize it, either use --sweeper.budget.anchorcpfp to specify sats, or use --sweeper.budget.anchorcpfpratio to specify a ratio.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidBumpResult is returned when the bump result is invalid.
	ErrInvalidBumpResult = errors.New("invalid bump result")

	// ErrNotEnoughBudget is returned when the fee bumper decides the
	// current budget cannot cover the fee.
	ErrNotEnoughBudget = errors.New("not enough budget")

	// ErrLocktimeImmature is returned when sweeping an input whose
	// locktime is not reached.
	ErrLocktimeImmature = errors.New("immature input")

	// ErrTxNoOutput is returned when an output cannot be created during tx
	// preparation, usually due to the output being dust.
	ErrTxNoOutput = errors.New("tx has no output")

	// ErrThirdPartySpent is returned when a third party has spent the
	// input in the sweeping tx.
	ErrThirdPartySpent = errors.New("third party spent the output")
)
View Source
var (
	// ErrRemoteSpend is returned in case an output that we try to sweep is
	// confirmed in a tx of the remote party.
	ErrRemoteSpend = errors.New("remote party swept utxo")

	// ErrFeePreferenceTooLow is returned when the fee preference gives a
	// fee rate that's below the relay fee rate.
	ErrFeePreferenceTooLow = errors.New("fee preference too low")

	// ErrExclusiveGroupSpend is returned in case a different input of the
	// same exclusive group was spent.
	ErrExclusiveGroupSpend = errors.New("other member of exclusive group " +
		"was spent")

	// ErrSweeperShuttingDown is an error returned when a client attempts to
	// make a request to the UtxoSweeper, but it is unable to handle it as
	// it is/has already been stopped.
	ErrSweeperShuttingDown = errors.New("utxo sweeper shutting down")

	// DefaultDeadlineDelta defines a default deadline delta (1 week) to be
	// used when sweeping inputs with no deadline pressure.
	DefaultDeadlineDelta = int32(1008)
)
View Source
var (
	// ErrNotEnoughInputs is returned when there are not enough wallet
	// inputs to construct a non-dust change output for an input set.
	ErrNotEnoughInputs = fmt.Errorf("not enough inputs")

	// ErrDeadlinesMismatch is returned when the deadlines of the input
	// sets do not match.
	ErrDeadlinesMismatch = fmt.Errorf("deadlines mismatch")

	// ErrDustOutput is returned when the output value is below the dust
	// limit.
	ErrDustOutput = fmt.Errorf("dust output")
)
View Source
var (
	// DefaultMaxInputsPerTx specifies the default maximum number of inputs
	// allowed in a single sweep tx. If more need to be swept, multiple txes
	// are created and published.
	DefaultMaxInputsPerTx = uint32(100)

	// ErrLocktimeConflict is returned when inputs with different
	// transaction nLockTime values are included in the same transaction.
	//
	// NOTE: due the SINGLE|ANYONECANPAY sighash flag, which is used in the
	// second level success/timeout txns, only the txns sharing the same
	// nLockTime can exist in the same tx.
	ErrLocktimeConflict = errors.New("incompatible locktime")
)
View Source
var (
	// ErrNoFeePreference is returned when we attempt to satisfy a sweep
	// request from a client whom did not specify a fee preference.
	ErrNoFeePreference = errors.New("no fee preference specified")

	// ErrFeePreferenceConflict is returned when both a fee rate and a conf
	// target is set for a fee preference.
	ErrFeePreferenceConflict = errors.New("fee preference conflict")
)
View Source
var (
	// DefaultMaxFeeRate is the default maximum fee rate allowed within the
	// UtxoSweeper. The current value is equivalent to a fee rate of 1,000
	// sat/vbyte.
	DefaultMaxFeeRate chainfee.SatPerVByte = 1e3
)
View Source
var (
	// ErrMaxPosition is returned when trying to increase the position of
	// the fee function while it's already at its max.
	ErrMaxPosition = errors.New("position already at max")
)
View Source
var (

	// ErrTxNotFound is returned when querying using a txid that's not
	// found in our db.
	ErrTxNotFound = errors.New("tx not found")
)

Functions

func DisableLog

func DisableLog()

DisableLog disables all library log output. Logging output is disabled by default until UseLogger is called.

func UseLogger

func UseLogger(logger btclog.Logger)

UseLogger uses a specified Logger to output package logging info. This should be used in preference to SetLogWriter if the caller is also using btclog.

Types

type BudgetAggregator

type BudgetAggregator struct {
	// contains filtered or unexported fields
}

BudgetAggregator is a budget-based aggregator that creates clusters based on deadlines and budgets of inputs.

func NewBudgetAggregator

func NewBudgetAggregator(estimator chainfee.Estimator,
	maxInputs uint32) *BudgetAggregator

NewBudgetAggregator creates a new instance of a BudgetAggregator.

func (*BudgetAggregator) ClusterInputs

func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet

ClusterInputs creates a list of input sets from pending inputs. 1. filter out inputs whose budget cannot cover min relay fee. 2. filter a list of exclusive inputs. 3. group the inputs into clusters based on their deadline height. 4. sort the inputs in each cluster by their budget. 5. optionally split a cluster if it exceeds the max input limit. 6. create input sets from each of the clusters. 7. create input sets for each of the exclusive inputs.

type BudgetInputSet

type BudgetInputSet struct {
	// contains filtered or unexported fields
}

BudgetInputSet implements the interface `InputSet`. It takes a list of pending inputs which share the same deadline height and groups them into a set conditionally based on their economical values.

func NewBudgetInputSet

func NewBudgetInputSet(inputs []SweeperInput,
	deadlineHeight int32) (*BudgetInputSet, error)

NewBudgetInputSet creates a new BudgetInputSet.

func (*BudgetInputSet) AddWalletInputs

func (b *BudgetInputSet) AddWalletInputs(wallet Wallet) error

AddWalletInputs adds wallet inputs to the set until the specified budget is met. When sweeping inputs with required outputs, although there's budget specified, it cannot be directly spent from these required outputs. Instead, we need to borrow budget from other inputs to make the sweep happen. There are two sources to borrow from: 1) other inputs, 2) wallet utxos. If we are calling this method, it means other inputs cannot cover the specified budget, so we need to borrow from wallet utxos.

Return an error if there are not enough wallet inputs, and the budget set is set to its initial state by removing any wallet inputs added.

NOTE: must be called with the wallet lock held via `WithCoinSelectLock`.

func (*BudgetInputSet) Budget

func (b *BudgetInputSet) Budget() btcutil.Amount

Budget returns the total budget of the set.

NOTE: part of the InputSet interface.

func (*BudgetInputSet) DeadlineHeight

func (b *BudgetInputSet) DeadlineHeight() int32

DeadlineHeight returns the deadline height of the set.

NOTE: part of the InputSet interface.

func (*BudgetInputSet) Inputs

func (b *BudgetInputSet) Inputs() []input.Input

Inputs returns the inputs that should be used to create a tx.

NOTE: part of the InputSet interface.

func (*BudgetInputSet) NeedWalletInput

func (b *BudgetInputSet) NeedWalletInput() bool

NeedWalletInput returns true if the input set needs more wallet inputs.

A set may need wallet inputs when it has a required output or its total value cannot cover its total budget.

func (*BudgetInputSet) StartingFeeRate

func (b *BudgetInputSet) StartingFeeRate() fn.Option[chainfee.SatPerKWeight]

StartingFeeRate returns the max starting fee rate found in the inputs.

NOTE: part of the InputSet interface.

func (*BudgetInputSet) String

func (b *BudgetInputSet) String() string

String returns a human-readable description of the input set.

type BumpEvent

type BumpEvent uint8

BumpEvent represents the event of a fee bumping attempt.

const (
	// TxPublished is sent when the broadcast attempt is finished.
	TxPublished BumpEvent = iota

	// TxFailed is sent when the broadcast attempt fails.
	TxFailed

	// TxReplaced is sent when the original tx is replaced by a new one.
	TxReplaced

	// TxConfirmed is sent when the tx is confirmed.
	TxConfirmed
)

func (BumpEvent) String

func (e BumpEvent) String() string

String returns a human-readable string for the event.

func (BumpEvent) Unknown

func (e BumpEvent) Unknown() bool

Unknown returns true if the event is unknown.

type BumpRequest

type BumpRequest struct {
	// Budget givens the total amount that can be used as fees by these
	// inputs.
	Budget btcutil.Amount

	// Inputs is the set of inputs to sweep.
	Inputs []input.Input

	// DeadlineHeight is the block height at which the tx should be
	// confirmed.
	DeadlineHeight int32

	// DeliveryAddress is the script to send the change output to.
	DeliveryAddress []byte

	// MaxFeeRate is the maximum fee rate that can be used for fee bumping.
	MaxFeeRate chainfee.SatPerKWeight

	// StartingFeeRate is an optional parameter that can be used to specify
	// the initial fee rate to use for the fee function.
	StartingFeeRate fn.Option[chainfee.SatPerKWeight]
}

BumpRequest is used by the caller to give the Bumper the necessary info to create and manage potential fee bumps for a set of inputs.

func (*BumpRequest) MaxFeeRateAllowed

func (r *BumpRequest) MaxFeeRateAllowed() (chainfee.SatPerKWeight, error)

MaxFeeRateAllowed returns the maximum fee rate allowed for the given request. It calculates the feerate using the supplied budget and the weight, compares it with the specified MaxFeeRate, and returns the smaller of the two.

type BumpResult

type BumpResult struct {
	// Event is the type of event that the result is for.
	Event BumpEvent

	// Tx is the tx being broadcast.
	Tx *wire.MsgTx

	// ReplacedTx is the old, replaced tx if a fee bump is attempted.
	ReplacedTx *wire.MsgTx

	// FeeRate is the fee rate used for the new tx.
	FeeRate chainfee.SatPerKWeight

	// Fee is the fee paid by the new tx.
	Fee btcutil.Amount

	// Err is the error that occurred during the broadcast.
	Err error
	// contains filtered or unexported fields
}

BumpResult is used by the Bumper to send updates about the tx being broadcast.

func (*BumpResult) Validate

func (b *BumpResult) Validate() error

Validate validates the BumpResult so it's safe to use.

type Bumper

type Bumper interface {
	// Broadcast is used to publish the tx created from the given inputs
	// specified in the request. It handles the tx creation, broadcasts it,
	// and monitors its confirmation status for potential fee bumping. It
	// returns a chan that the caller can use to receive updates about the
	// broadcast result and potential RBF attempts.
	Broadcast(req *BumpRequest) (<-chan *BumpResult, error)
}

Bumper defines an interface that can be used by other subsystems for fee bumping.

type CoinSelectionLocker

type CoinSelectionLocker interface {
	// WithCoinSelectLock will execute the passed function closure in a
	// synchronized manner preventing any coin selection operations from
	// proceeding while the closure is executing. This can be seen as the
	// ability to execute a function closure under an exclusive coin
	// selection lock.
	WithCoinSelectLock(func() error) error
}

CoinSelectionLocker is an interface that allows the caller to perform an operation, which is synchronized with all coin selection attempts. This can be used when an operation requires that all coin selection operations cease forward progress. Think of this as an exclusive lock on coin selection operations.

type DeliveryAddr

type DeliveryAddr struct {
	// Addr is the address to pay to.
	Addr btcutil.Address

	// Amt is the amount to pay to the given address.
	Amt btcutil.Amount
}

DeliveryAddr is a pair of (address, amount) used to craft a transaction paying to more than one specified address.

type FeeEstimateInfo

type FeeEstimateInfo struct {
	// ConfTarget if non-zero, signals a fee preference expressed in the
	// number of desired blocks between first broadcast, and confirmation.
	ConfTarget uint32

	// FeeRate if non-zero, signals a fee pre fence expressed in the fee
	// rate expressed in sat/kw for a particular transaction.
	FeeRate chainfee.SatPerKWeight
}

FeeEstimateInfo allows callers to express their time value for inclusion of a transaction into a block via either a confirmation target, or a fee rate.

func (FeeEstimateInfo) Estimate

func (f FeeEstimateInfo) Estimate(estimator chainfee.Estimator,
	maxFeeRate chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error)

Estimate returns a fee rate for the given fee preference. It ensures that the fee rate respects the bounds of the relay fee and the max fee rates, if specified.

func (FeeEstimateInfo) String

func (f FeeEstimateInfo) String() string

String returns a human-readable string of the fee preference.

type FeeFunction

type FeeFunction interface {
	// FeeRate returns the current fee rate calculated by the fee function.
	FeeRate() chainfee.SatPerKWeight

	// Increment increases the fee rate by one step. The definition of one
	// step is up to the implementation. After calling this method, it's
	// expected to change the state of the fee function such that calling
	// `FeeRate` again will return the increased value.
	//
	// It returns a boolean to indicate whether the fee rate is increased,
	// as fee bump should not be attempted if the increased fee rate is not
	// greater than the current fee rate, which may happen if the algorithm
	// gives the same fee rates at two positions.
	//
	// An error is returned when the max fee rate is reached.
	//
	// NOTE: we intentionally don't return the new fee rate here, so both
	// the implementation and the caller are aware of the state change.
	Increment() (bool, error)

	// IncreaseFeeRate increases the fee rate to the new position
	// calculated using (width - confTarget). It returns a boolean to
	// indicate whether the fee rate is increased, and an error if the
	// position is greater than the width.
	//
	// NOTE: this method is provided to allow the caller to increase the
	// fee rate based on a conf target without taking care of the fee
	// function's current state (position).
	IncreaseFeeRate(confTarget uint32) (bool, error)
}

FeeFunction defines an interface that is used to calculate fee rates for transactions. It's expected the implementations use three params, the starting fee rate, the ending fee rate, and number of blocks till deadline block height, to build an algorithm to calculate the fee rate based on the current block height.

type FeePreference

type FeePreference interface {
	// String returns a human-readable string of the fee preference.
	String() string

	// Estimate takes a fee estimator and a max allowed fee rate and
	// returns a fee rate for the given fee preference. It ensures that the
	// fee rate respects the bounds of the relay fee and the specified max
	// fee rates.
	Estimate(chainfee.Estimator,
		chainfee.SatPerKWeight) (chainfee.SatPerKWeight, error)
}

FeePreference defines an interface that allows the caller to specify how the fee rate should be handled. Depending on the implementation, the fee rate can either be specified directly, or via a conf target which relies on the chain backend(`bitcoind`) to give a fee estimation, or a customized fee function which handles fee calculation based on the specified urgency(deadline).

type InputSet

type InputSet interface {
	// Inputs returns the set of inputs that should be used to create a tx.
	Inputs() []input.Input

	// AddWalletInputs adds wallet inputs to the set until a non-dust
	// change output can be made. Return an error if there are not enough
	// wallet inputs.
	AddWalletInputs(wallet Wallet) error

	// NeedWalletInput returns true if the input set needs more wallet
	// inputs.
	NeedWalletInput() bool

	// DeadlineHeight returns an absolute block height to express the
	// time-sensitivity of the input set. The outputs from a force close tx
	// have different time preferences:
	// - to_local: no time pressure as it can only be swept by us.
	// - first level outgoing HTLC: must be swept before its corresponding
	//   incoming HTLC's CLTV is reached.
	// - first level incoming HTLC: must be swept before its CLTV is
	//   reached.
	// - second level HTLCs: no time pressure.
	// - anchor: for CPFP-purpose anchor, it must be swept before any of
	//   the above CLTVs is reached. For non-CPFP purpose anchor, there's
	//   no time pressure.
	DeadlineHeight() int32

	// Budget givens the total amount that can be used as fees by this
	// input set.
	Budget() btcutil.Amount

	// StartingFeeRate returns the max starting fee rate found in the
	// inputs.
	StartingFeeRate() fn.Option[chainfee.SatPerKWeight]
}

InputSet defines an interface that's responsible for filtering a set of inputs that can be swept economically.

type InputsMap

type InputsMap = map[wire.OutPoint]*SweeperInput

InputsMap is a type alias for a set of pending inputs.

type LinearFeeFunction

type LinearFeeFunction struct {
	// contains filtered or unexported fields
}

LinearFeeFunction implements the FeeFunction interface with a linear function:

feeRate = startingFeeRate + position * delta.
     - width: deadlineBlockHeight - startingBlockHeight
     - delta: (endingFeeRate - startingFeeRate) / width
     - position: currentBlockHeight - startingBlockHeight

The fee rate will be capped at endingFeeRate.

TODO(yy): implement more functions specified here: - https://github.com/lightningnetwork/lnd/issues/4215

func NewLinearFeeFunction

func NewLinearFeeFunction(maxFeeRate chainfee.SatPerKWeight,
	confTarget uint32, estimator chainfee.Estimator,
	startingFeeRate fn.Option[chainfee.SatPerKWeight]) (
	*LinearFeeFunction, error)

NewLinearFeeFunction creates a new linear fee function and initializes it with a starting fee rate which is an estimated value returned from the fee estimator using the initial conf target.

func (*LinearFeeFunction) FeeRate

FeeRate returns the current fee rate.

NOTE: part of the FeeFunction interface.

func (*LinearFeeFunction) IncreaseFeeRate

func (l *LinearFeeFunction) IncreaseFeeRate(confTarget uint32) (bool, error)

IncreaseFeeRate calculate a new position using the given conf target, and increases the fee rate to the new position by calling the Increment method.

NOTE: this method will change the state of the fee function as it increases its current fee rate.

NOTE: part of the FeeFunction interface.

func (*LinearFeeFunction) Increment

func (l *LinearFeeFunction) Increment() (bool, error)

Increment increases the fee rate by one position, returns a boolean to indicate whether the fee rate was increased, and an error if the position is greater than the width. The increased fee rate will be set as the current fee rate, and the internal position will be incremented.

NOTE: this method will change the state of the fee function as it increases its current fee rate.

NOTE: part of the FeeFunction interface.

type MockNotifier

type MockNotifier struct {
	// contains filtered or unexported fields
}

MockNotifier simulates the chain notifier for test purposes. This type is exported because it is used in nursery tests.

func NewMockNotifier

func NewMockNotifier(t *testing.T) *MockNotifier

NewMockNotifier instantiates a new mock notifier.

func (*MockNotifier) ConfirmTx

func (m *MockNotifier) ConfirmTx(txid *chainhash.Hash, height uint32) error

ConfirmTx simulates a tx confirming.

func (*MockNotifier) NotifyEpoch

func (m *MockNotifier) NotifyEpoch(height int32)

NotifyEpoch simulates a new epoch arriving.

func (*MockNotifier) NotifyEpochNonBlocking

func (m *MockNotifier) NotifyEpochNonBlocking(height int32)

NotifyEpochNonBlocking simulates a new epoch arriving without blocking when the epochChan is not read.

func (*MockNotifier) RegisterBlockEpochNtfn

func (m *MockNotifier) RegisterBlockEpochNtfn(
	bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error)

RegisterBlockEpochNtfn registers a block notification.

func (*MockNotifier) RegisterConfirmationsNtfn

func (m *MockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
	_ []byte, numConfs, heightHint uint32,
	opt ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent, error)

RegisterConfirmationsNtfn registers for tx confirm notifications.

func (*MockNotifier) RegisterSpendNtfn

func (m *MockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
	_ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error)

RegisterSpendNtfn registers for spend notifications.

func (*MockNotifier) SpendOutpoint

func (m *MockNotifier) SpendOutpoint(outpoint wire.OutPoint,
	spendingTx wire.MsgTx)

SpendOutpoint simulates a utxo being spent.

func (*MockNotifier) Start

func (m *MockNotifier) Start() error

Start the notifier.

func (*MockNotifier) Started

func (m *MockNotifier) Started() bool

Started checks if started.

func (*MockNotifier) Stop

func (m *MockNotifier) Stop() error

Stop the notifier.

type OutputLeaser

type OutputLeaser interface {
	// LeaseOutput leases a target output, rendering it unusable for coin
	// selection.
	LeaseOutput(i wtxmgr.LockID, o wire.OutPoint, d time.Duration) (
		time.Time, []byte, btcutil.Amount, error)

	// ReleaseOutput releases a target output, allowing it to be used for
	// coin selection once again.
	ReleaseOutput(i wtxmgr.LockID, o wire.OutPoint) error
}

OutputLeaser allows a caller to lease/release an output. When leased, the outputs shouldn't be used for any sort of channel funding or coin selection. Leased outputs are expected to be persisted between restarts.

type Params

type Params struct {
	// ExclusiveGroup is an identifier that, if set, prevents other inputs
	// with the same identifier from being batched together.
	ExclusiveGroup *uint64

	// DeadlineHeight specifies an absolute block height that this input
	// should be confirmed by. This value is used by the fee bumper to
	// decide its urgency and adjust its feerate used.
	DeadlineHeight fn.Option[int32]

	// Budget specifies the maximum amount of satoshis that can be spent on
	// fees for this sweep.
	Budget btcutil.Amount

	// Immediate indicates that the input should be swept immediately
	// without waiting for blocks to come to trigger the sweeping of
	// inputs.
	Immediate bool

	// StartingFeeRate is an optional parameter that can be used to specify
	// the initial fee rate to use for the fee function.
	StartingFeeRate fn.Option[chainfee.SatPerKWeight]
}

Params contains the parameters that control the sweeping process.

func (Params) String

func (p Params) String() string

String returns a human readable interpretation of the sweep parameters.

type PendingInputResponse

type PendingInputResponse struct {
	// OutPoint is the identify outpoint of the input being swept.
	OutPoint wire.OutPoint

	// WitnessType is the witness type of the input being swept.
	WitnessType input.WitnessType

	// Amount is the amount of the input being swept.
	Amount btcutil.Amount

	// LastFeeRate is the most recent fee rate used for the input being
	// swept within a transaction broadcast to the network.
	LastFeeRate chainfee.SatPerKWeight

	// BroadcastAttempts is the number of attempts we've made to sweept the
	// input.
	BroadcastAttempts int

	// Params contains the sweep parameters for this pending request.
	Params Params

	// DeadlineHeight records the deadline height of this input.
	DeadlineHeight uint32
}

PendingInputResponse contains information about an input that is currently being swept by the UtxoSweeper.

type RBFInfo

type RBFInfo struct {
	// Txid is the txid of the sweeping tx.
	Txid chainhash.Hash

	// FeeRate is the fee rate of the sweeping tx.
	FeeRate chainfee.SatPerKWeight

	// Fee is the total fee of the sweeping tx.
	Fee btcutil.Amount
}

RBFInfo stores the information required to perform a RBF bump on a pending sweeping tx.

type Result

type Result struct {
	// Err is the final result of the sweep. It is nil when the input is
	// swept successfully by us. ErrRemoteSpend is returned when another
	// party took the input.
	Err error

	// Tx is the transaction that spent the input.
	Tx *wire.MsgTx
}

Result is the struct that is pushed through the result channel. Callers can use this to be informed of the final sweep result. In case of a remote spend, Err will be ErrRemoteSpend.

type SweepState

type SweepState uint8

SweepState represents the current state of a pending input.

const (
	// Init is the initial state of a pending input. This is set when a new
	// sweeping request for a given input is made.
	Init SweepState = iota

	// PendingPublish specifies an input's state where it's already been
	// included in a sweeping tx but the tx is not published yet.  Inputs
	// in this state should not be used for grouping again.
	PendingPublish

	// Published is the state where the input's sweeping tx has
	// successfully been published. Inputs in this state can only be
	// updated via RBF.
	Published

	// PublishFailed is the state when an error is returned from publishing
	// the sweeping tx. Inputs in this state can be re-grouped in to a new
	// sweeping tx.
	PublishFailed

	// Swept is the final state of a pending input. This is set when the
	// input has been successfully swept.
	Swept

	// Excluded is the state of a pending input that has been excluded and
	// can no longer be swept. For instance, when one of the three anchor
	// sweeping transactions confirmed, the remaining two will be excluded.
	Excluded

	// Failed is the state when a pending input has too many failed publish
	// atttempts or unknown broadcast error is returned.
	Failed
)

func (SweepState) String

func (s SweepState) String() string

String gives a human readable text for the sweep states.

type SweeperInput

type SweeperInput struct {
	input.Input

	// DeadlineHeight is the deadline height for this input. This is
	// different from the DeadlineHeight in its params as it's an actual
	// value than an option.
	DeadlineHeight int32
	// contains filtered or unexported fields
}

SweeperInput is created when an input reaches the main loop for the first time. It wraps the input and tracks all relevant state that is needed for sweeping.

func (*SweeperInput) String

func (p *SweeperInput) String() string

String returns a human readable interpretation of the pending input.

type SweeperStore

type SweeperStore interface {
	// IsOurTx determines whether a tx is published by us, based on its
	// hash.
	IsOurTx(hash chainhash.Hash) (bool, error)

	// StoreTx stores a tx hash we are about to publish.
	StoreTx(*TxRecord) error

	// ListSweeps lists all the sweeps we have successfully published.
	ListSweeps() ([]chainhash.Hash, error)

	// GetTx queries the database to find the tx that matches the given
	// txid. Returns ErrTxNotFound if it cannot be found.
	GetTx(hash chainhash.Hash) (*TxRecord, error)

	// DeleteTx removes a tx specified by the hash from the store.
	DeleteTx(hash chainhash.Hash) error
}

SweeperStore stores published txes.

func NewSweeperStore

func NewSweeperStore(db kvdb.Backend, chainHash *chainhash.Hash) (
	SweeperStore, error)

NewSweeperStore returns a new store instance.

type TxPublisher

type TxPublisher struct {
	// contains filtered or unexported fields
}

TxPublisher is an implementation of the Bumper interface. It utilizes the `testmempoolaccept` RPC to bump the fee of txns it created based on different fee function selected or configed by the caller. Its purpose is to take a list of inputs specified, and create a tx that spends them to a specified output. It will then monitor the confirmation status of the tx, and if it's not confirmed within a certain time frame, it will attempt to bump the fee of the tx by creating a new tx that spends the same inputs to the same output, but with a higher fee rate. It will continue to do this until the tx is confirmed or the fee rate reaches the maximum fee rate specified by the caller.

func NewTxPublisher

func NewTxPublisher(cfg TxPublisherConfig) *TxPublisher

NewTxPublisher creates a new TxPublisher.

func (*TxPublisher) Broadcast

func (t *TxPublisher) Broadcast(req *BumpRequest) (<-chan *BumpResult, error)

Broadcast is used to publish the tx created from the given inputs. It will, 1. init a fee function based on the given strategy. 2. create an RBF-compliant tx and monitor it for confirmation. 3. notify the initial broadcast result back to the caller. The initial broadcast is guaranteed to be RBF-compliant unless the budget specified cannot cover the fee.

NOTE: part of the Bumper interface.

func (*TxPublisher) Start

func (t *TxPublisher) Start() error

Start starts the publisher by subscribing to block epoch updates and kicking off the monitor loop.

func (*TxPublisher) Stop

func (t *TxPublisher) Stop()

Stop stops the publisher and waits for the monitor loop to exit.

type TxPublisherConfig

type TxPublisherConfig struct {
	// Signer is used to create the tx signature.
	Signer input.Signer

	// Wallet is used primarily to publish the tx.
	Wallet Wallet

	// Estimator is used to estimate the fee rate for the new tx based on
	// its deadline conf target.
	Estimator chainfee.Estimator

	// Notifier is used to monitor the confirmation status of the tx.
	Notifier chainntnfs.ChainNotifier
}

TxPublisherConfig is the config used to create a new TxPublisher.

type TxRecord

type TxRecord struct {
	// Txid is the sweeping tx's txid, which is used as the key to store
	// the following values.
	Txid chainhash.Hash

	// FeeRate is the fee rate of the sweeping tx, unit is sats/kw.
	FeeRate uint64

	// Fee is the fee of the sweeping tx, unit is sat.
	Fee uint64

	// Published indicates whether the tx has been published.
	Published bool
}

TxRecord specifies a record of a tx that's stored in the database.

type UtxoAggregator

type UtxoAggregator interface {
	// ClusterInputs takes a list of inputs and groups them into input
	// sets. Each input set will be used to create a sweeping transaction.
	ClusterInputs(inputs InputsMap) []InputSet
}

UtxoAggregator defines an interface that takes a list of inputs and aggregate them into groups. Each group is used as the inputs to create a sweeping transaction.

type UtxoSource

type UtxoSource interface {
	// ListUnspentWitness returns all UTXOs from the default wallet account
	// that have between minConfs and maxConfs number of confirmations.
	ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
		[]*lnwallet.Utxo, error)
}

UtxoSource is an interface that allows a caller to access a source of UTXOs to use when crafting sweep transactions.

type UtxoSweeper

type UtxoSweeper struct {
	// contains filtered or unexported fields
}

UtxoSweeper is responsible for sweeping outputs back into the wallet

func New

func New(cfg *UtxoSweeperConfig) *UtxoSweeper

New returns a new Sweeper instance.

func (*UtxoSweeper) IsSweeperOutpoint

func (s *UtxoSweeper) IsSweeperOutpoint(op wire.OutPoint) bool

IsSweeperOutpoint determines whether the outpoint was created by the sweeper.

NOTE: It is enough to check the txid because the sweeper will create outpoints which solely belong to the internal LND wallet.

func (*UtxoSweeper) ListSweeps

func (s *UtxoSweeper) ListSweeps() ([]chainhash.Hash, error)

ListSweeps returns a list of the sweeps recorded by the sweep store.

func (*UtxoSweeper) PendingInputs

func (s *UtxoSweeper) PendingInputs() (
	map[wire.OutPoint]*PendingInputResponse, error)

PendingInputs returns the set of inputs that the UtxoSweeper is currently attempting to sweep.

func (*UtxoSweeper) RelayFeePerKW

func (s *UtxoSweeper) RelayFeePerKW() chainfee.SatPerKWeight

RelayFeePerKW returns the minimum fee rate required for transactions to be relayed.

func (*UtxoSweeper) Start

func (s *UtxoSweeper) Start() error

Start starts the process of constructing and publish sweep txes.

func (*UtxoSweeper) Stop

func (s *UtxoSweeper) Stop() error

Stop stops sweeper from listening to block epochs and constructing sweep txes.

func (*UtxoSweeper) SweepInput

func (s *UtxoSweeper) SweepInput(inp input.Input,
	params Params) (chan Result, error)

SweepInput sweeps inputs back into the wallet. The inputs will be batched and swept after the batch time window ends. A custom fee preference can be provided to determine what fee rate should be used for the input. Note that the input may not always be swept with this exact value, as its possible for it to be batched under the same transaction with other similar fee rate inputs.

NOTE: Extreme care needs to be taken that input isn't changed externally. Because it is an interface and we don't know what is exactly behind it, we cannot make a local copy in sweeper.

TODO(yy): make sure the caller is using the Result chan.

func (*UtxoSweeper) UpdateParams

func (s *UtxoSweeper) UpdateParams(input wire.OutPoint,
	params Params) (chan Result, error)

UpdateParams allows updating the sweep parameters of a pending input in the UtxoSweeper. This function can be used to provide an updated fee preference and force flag that will be used for a new sweep transaction of the input that will act as a replacement transaction (RBF) of the original sweeping transaction, if any. The exclusive group is left unchanged.

NOTE: This currently doesn't do any fee rate validation to ensure that a bump is actually successful. The responsibility of doing so should be handled by the caller.

type UtxoSweeperConfig

type UtxoSweeperConfig struct {
	// GenSweepScript generates a P2WKH script belonging to the wallet where
	// funds can be swept.
	GenSweepScript func() ([]byte, error)

	// FeeEstimator is used when crafting sweep transactions to estimate
	// the necessary fee relative to the expected size of the sweep
	// transaction.
	FeeEstimator chainfee.Estimator

	// Wallet contains the wallet functions that sweeper requires.
	Wallet Wallet

	// Notifier is an instance of a chain notifier we'll use to watch for
	// certain on-chain events.
	Notifier chainntnfs.ChainNotifier

	// Mempool is the mempool watcher that will be used to query whether a
	// given input is already being spent by a transaction in the mempool.
	Mempool chainntnfs.MempoolWatcher

	// Store stores the published sweeper txes.
	Store SweeperStore

	// Signer is used by the sweeper to generate valid witnesses at the
	// time the incubated outputs need to be spent.
	Signer input.Signer

	// MaxInputsPerTx specifies the default maximum number of inputs allowed
	// in a single sweep tx. If more need to be swept, multiple txes are
	// created and published.
	MaxInputsPerTx uint32

	// MaxFeeRate is the maximum fee rate allowed within the UtxoSweeper.
	MaxFeeRate chainfee.SatPerVByte

	// Aggregator is used to group inputs into clusters based on its
	// implemention-specific strategy.
	Aggregator UtxoAggregator

	// Publisher is used to publish the sweep tx crafted here and monitors
	// it for potential fee bumps.
	Publisher Bumper

	// NoDeadlineConfTarget is the conf target to use when sweeping
	// non-time-sensitive outputs.
	NoDeadlineConfTarget uint32
}

UtxoSweeperConfig contains dependencies of UtxoSweeper.

type Wallet

type Wallet interface {
	// PublishTransaction performs cursory validation (dust checks, etc) and
	// broadcasts the passed transaction to the Bitcoin network.
	PublishTransaction(tx *wire.MsgTx, label string) error

	// ListUnspentWitnessFromDefaultAccount returns all unspent outputs
	// which are version 0 witness programs from the default wallet account.
	// The 'minConfs' and 'maxConfs' parameters indicate the minimum
	// and maximum number of confirmations an output needs in order to be
	// returned by this method.
	ListUnspentWitnessFromDefaultAccount(minConfs, maxConfs int32) (
		[]*lnwallet.Utxo, error)

	// WithCoinSelectLock will execute the passed function closure in a
	// synchronized manner preventing any coin selection operations from
	// proceeding while the closure is executing. This can be seen as the
	// ability to execute a function closure under an exclusive coin
	// selection lock.
	WithCoinSelectLock(f func() error) error

	// RemoveDescendants removes any wallet transactions that spends
	// outputs created by the specified transaction.
	RemoveDescendants(*wire.MsgTx) error

	// FetchTx returns the transaction that corresponds to the transaction
	// hash passed in. If the transaction can't be found then a nil
	// transaction pointer is returned.
	FetchTx(chainhash.Hash) (*wire.MsgTx, error)

	// CancelRebroadcast is used to inform the rebroadcaster sub-system
	// that it no longer needs to try to rebroadcast a transaction. This is
	// used to ensure that invalid transactions (inputs spent) aren't
	// retried in the background.
	CancelRebroadcast(tx chainhash.Hash)

	// CheckMempoolAcceptance checks whether a transaction follows mempool
	// policies and returns an error if it cannot be accepted into the
	// mempool.
	CheckMempoolAcceptance(tx *wire.MsgTx) error

	// GetTransactionDetails returns a detailed description of a tx given
	// its transaction hash.
	GetTransactionDetails(txHash *chainhash.Hash) (
		*lnwallet.TransactionDetail, error)

	// BackEnd returns a name for the wallet's backing chain service,
	// which could be e.g. btcd, bitcoind, neutrino, or another consensus
	// service.
	BackEnd() string
}

Wallet contains all wallet related functionality required by sweeper.

type WalletSweepPackage

type WalletSweepPackage struct {
	// SweepTx is a fully signed, and valid transaction that is broadcast,
	// will sweep ALL confirmed coins in the wallet with a single
	// transaction.
	SweepTx *wire.MsgTx

	// CancelSweepAttempt allows the caller to cancel the sweep attempt.
	//
	// NOTE: If the sweeping transaction isn't or cannot be broadcast, then
	// this closure MUST be called, otherwise all selected utxos will be
	// unable to be used.
	CancelSweepAttempt func()
}

WalletSweepPackage is a package that gives the caller the ability to sweep ALL funds from a wallet in a single transaction. We also package a function closure that allows one to abort the operation.

func CraftSweepAllTx

func CraftSweepAllTx(feeRate, maxFeeRate chainfee.SatPerKWeight,
	blockHeight uint32, deliveryAddrs []DeliveryAddr,
	changeAddr btcutil.Address, coinSelectLocker CoinSelectionLocker,
	utxoSource UtxoSource, outputLeaser OutputLeaser,
	signer input.Signer, minConfs int32) (*WalletSweepPackage, error)

CraftSweepAllTx attempts to craft a WalletSweepPackage which will allow the caller to sweep ALL outputs within the wallet to a list of outputs. Any leftover amount after these outputs and transaction fee, is sent to a single output, as specified by the change address. The sweep transaction will be crafted with the target fee rate, and will use the utxoSource and outputLeaser as sources for wallet funds.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL