Skip to content

Commit

Permalink
Merge pull request #791 from starius/sweepbatcher-mixed-batches
Browse files Browse the repository at this point in the history
sweepbatcher: add mixed batches as an option
  • Loading branch information
starius authored Aug 21, 2024
2 parents ff144c7 + b171f76 commit a6e9a2b
Show file tree
Hide file tree
Showing 6 changed files with 1,309 additions and 65 deletions.
82 changes: 56 additions & 26 deletions sweepbatcher/greedy_batch_selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (b *Batcher) greedyAddSweep(ctx context.Context, sweep *sweep) error {
// Run the algorithm. Get batchId of possible batches, sorted from best
// to worst.
batchesIds, err := selectBatches(
batches, sweepFeeDetails, newBatchFeeDetails,
batches, sweepFeeDetails, newBatchFeeDetails, b.mixedBatch,
)
if err != nil {
return fmt.Errorf("batch selection algorithm failed for sweep "+
Expand Down Expand Up @@ -125,10 +125,11 @@ func estimateSweepFeeIncrement(s *sweep) (feeDetails, feeDetails, error) {
// Create feeDetails for sweep.
sweepFeeDetails := feeDetails{
FeeRate: s.minFeeRate,
NonCoopHint: s.nonCoopHint,
NonCoopHint: s.nonCoopHint || s.coopFailed,
IsExternalAddr: s.isExternalAddr,

// Calculate sweep weight as a difference.
MixedWeight: fd2.MixedWeight - fd1.MixedWeight,
CoopWeight: fd2.CoopWeight - fd1.CoopWeight,
NonCoopWeight: fd2.NonCoopWeight - fd1.NonCoopWeight,
}
Expand All @@ -152,7 +153,7 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
// Find if the batch has at least one non-cooperative sweep.
hasNonCoop := false
for _, sweep := range batch.sweeps {
if sweep.nonCoopHint {
if sweep.nonCoopHint || sweep.coopFailed {
hasNonCoop = true
}
}
Expand All @@ -177,11 +178,16 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
destAddr = (*btcutil.AddressTaproot)(nil)
}

// Make two estimators: for coop and non-coop cases.
var coopWeight, nonCoopWeight input.TxWeightEstimator
// Make three estimators: for mixed, coop and non-coop cases.
var mixedWeight, coopWeight, nonCoopWeight input.TxWeightEstimator

// Add output weight to the estimator.
err := sweeppkg.AddOutputEstimate(&coopWeight, destAddr)
err := sweeppkg.AddOutputEstimate(&mixedWeight, destAddr)
if err != nil {
return feeDetails{}, fmt.Errorf("sweep.AddOutputEstimate: %w",
err)
}
err = sweeppkg.AddOutputEstimate(&coopWeight, destAddr)
if err != nil {
return feeDetails{}, fmt.Errorf("sweep.AddOutputEstimate: %w",
err)
Expand All @@ -194,6 +200,19 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {

// Add inputs.
for _, sweep := range batch.sweeps {
if sweep.nonCoopHint || sweep.coopFailed {
err = sweep.htlcSuccessEstimator(&mixedWeight)
if err != nil {
return feeDetails{}, fmt.Errorf(
"htlcSuccessEstimator failed: %w", err,
)
}
} else {
mixedWeight.AddTaprootKeySpendInput(
txscript.SigHashDefault,
)
}

coopWeight.AddTaprootKeySpendInput(txscript.SigHashDefault)

err = sweep.htlcSuccessEstimator(&nonCoopWeight)
Expand All @@ -206,6 +225,7 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
return feeDetails{
BatchId: batch.id,
FeeRate: batch.rbfCache.FeeRate,
MixedWeight: mixedWeight.Weight(),
CoopWeight: coopWeight.Weight(),
NonCoopWeight: nonCoopWeight.Weight(),
NonCoopHint: hasNonCoop,
Expand All @@ -222,18 +242,22 @@ const newBatchSignal = -1
type feeDetails struct {
BatchId int32
FeeRate chainfee.SatPerKWeight
MixedWeight lntypes.WeightUnit
CoopWeight lntypes.WeightUnit
NonCoopWeight lntypes.WeightUnit
NonCoopHint bool
IsExternalAddr bool
}

// fee returns fee of onchain transaction representing this instance.
func (e feeDetails) fee() btcutil.Amount {
func (e feeDetails) fee(mixedBatch bool) btcutil.Amount {
var weight lntypes.WeightUnit
if e.NonCoopHint {
switch {
case mixedBatch:
weight = e.MixedWeight
case e.NonCoopHint:
weight = e.NonCoopWeight
} else {
default:
weight = e.CoopWeight
}

Expand All @@ -250,6 +274,7 @@ func (e1 feeDetails) combine(e2 feeDetails) feeDetails {

return feeDetails{
FeeRate: feeRate,
MixedWeight: e1.MixedWeight + e2.MixedWeight,
CoopWeight: e1.CoopWeight + e2.CoopWeight,
NonCoopWeight: e1.NonCoopWeight + e2.NonCoopWeight,
NonCoopHint: e1.NonCoopHint || e2.NonCoopHint,
Expand All @@ -259,21 +284,26 @@ func (e1 feeDetails) combine(e2 feeDetails) feeDetails {

// selectBatches returns the list of id of batches sorted from best to worst.
// Creation a new batch is encoded as newBatchSignal. For each batch its fee
// rate and two weights are provided: weight in case of cooperative spending and
// weight in case non-cooperative spending (using preimages instead of taproot
// key spend). Also, a hint is provided to signal if the batch has to use
// non-cooperative spending path. The same data is also provided to the sweep
// for which we are selecting a batch to add. In case of the sweep weights are
// weight deltas resulted from adding the sweep. Finally, the same data is
// provided for new batch having this sweep only. The algorithm compares costs
// of adding the sweep to each existing batch, and costs of new batch creation
// for this sweep and returns BatchId of the winning batch. If the best option
// is to create a new batch, return newBatchSignal. Each fee details has also
// IsExternalAddr flag. There is a rule that sweeps having flag IsExternalAddr
// must go in individual batches. Cooperative spending is only available if all
// the sweeps support cooperative spending path.
func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails) (
[]int32, error) {
// rate and a set of weights are provided: weight in case of a mixed batch,
// weight in case of cooperative spending and weight in case non-cooperative
// spending. Also, a hint is provided to signal what spending path will be used
// by the batch.
//
// The same data is also provided for the sweep for which we are selecting a
// batch to add. In case of the sweep weights are weight deltas resulted from
// adding the sweep. Finally, the same data is provided for new batch having
// this sweep only.
//
// The algorithm compares costs of adding the sweep to each existing batch, and
// costs of new batch creation for this sweep and returns BatchId of the winning
// batch. If the best option is to create a new batch, return newBatchSignal.
//
// Each fee details has also IsExternalAddr flag. There is a rule that sweeps
// having flag IsExternalAddr must go in individual batches. Cooperative
// spending is only available if all the sweeps support cooperative spending
// path of in a mixed batch.
func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails,
mixedBatch bool) ([]int32, error) {

// If the sweep has IsExternalAddr flag, the sweep can't be added to
// a batch, so create new batch for it.
Expand All @@ -294,7 +324,7 @@ func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails) (
// creation with this sweep only in it. The cost is its full fee.
alternatives = append(alternatives, alternative{
batchId: newBatchSignal,
cost: oneSweepBatch.fee(),
cost: oneSweepBatch.fee(mixedBatch),
})

// Try to add the sweep to every batch, calculate the costs and
Expand All @@ -310,7 +340,7 @@ func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails) (
combinedBatch := batch.combine(sweep)

// The cost is the fee increase.
cost := combinedBatch.fee() - batch.fee()
cost := combinedBatch.fee(mixedBatch) - batch.fee(mixedBatch)

// The cost must be positive, because we added a sweep.
if cost <= 0 {
Expand Down
Loading

0 comments on commit a6e9a2b

Please sign in to comment.