Skip to content

Commit a6e9a2b

Browse files
authored
Merge pull request #791 from starius/sweepbatcher-mixed-batches
sweepbatcher: add mixed batches as an option
2 parents ff144c7 + b171f76 commit a6e9a2b

6 files changed

+1309
-65
lines changed

sweepbatcher/greedy_batch_selection.go

+56-26
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (b *Batcher) greedyAddSweep(ctx context.Context, sweep *sweep) error {
5151
// Run the algorithm. Get batchId of possible batches, sorted from best
5252
// to worst.
5353
batchesIds, err := selectBatches(
54-
batches, sweepFeeDetails, newBatchFeeDetails,
54+
batches, sweepFeeDetails, newBatchFeeDetails, b.mixedBatch,
5555
)
5656
if err != nil {
5757
return fmt.Errorf("batch selection algorithm failed for sweep "+
@@ -125,10 +125,11 @@ func estimateSweepFeeIncrement(s *sweep) (feeDetails, feeDetails, error) {
125125
// Create feeDetails for sweep.
126126
sweepFeeDetails := feeDetails{
127127
FeeRate: s.minFeeRate,
128-
NonCoopHint: s.nonCoopHint,
128+
NonCoopHint: s.nonCoopHint || s.coopFailed,
129129
IsExternalAddr: s.isExternalAddr,
130130

131131
// Calculate sweep weight as a difference.
132+
MixedWeight: fd2.MixedWeight - fd1.MixedWeight,
132133
CoopWeight: fd2.CoopWeight - fd1.CoopWeight,
133134
NonCoopWeight: fd2.NonCoopWeight - fd1.NonCoopWeight,
134135
}
@@ -152,7 +153,7 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
152153
// Find if the batch has at least one non-cooperative sweep.
153154
hasNonCoop := false
154155
for _, sweep := range batch.sweeps {
155-
if sweep.nonCoopHint {
156+
if sweep.nonCoopHint || sweep.coopFailed {
156157
hasNonCoop = true
157158
}
158159
}
@@ -177,11 +178,16 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
177178
destAddr = (*btcutil.AddressTaproot)(nil)
178179
}
179180

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

183184
// Add output weight to the estimator.
184-
err := sweeppkg.AddOutputEstimate(&coopWeight, destAddr)
185+
err := sweeppkg.AddOutputEstimate(&mixedWeight, destAddr)
186+
if err != nil {
187+
return feeDetails{}, fmt.Errorf("sweep.AddOutputEstimate: %w",
188+
err)
189+
}
190+
err = sweeppkg.AddOutputEstimate(&coopWeight, destAddr)
185191
if err != nil {
186192
return feeDetails{}, fmt.Errorf("sweep.AddOutputEstimate: %w",
187193
err)
@@ -194,6 +200,19 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
194200

195201
// Add inputs.
196202
for _, sweep := range batch.sweeps {
203+
if sweep.nonCoopHint || sweep.coopFailed {
204+
err = sweep.htlcSuccessEstimator(&mixedWeight)
205+
if err != nil {
206+
return feeDetails{}, fmt.Errorf(
207+
"htlcSuccessEstimator failed: %w", err,
208+
)
209+
}
210+
} else {
211+
mixedWeight.AddTaprootKeySpendInput(
212+
txscript.SigHashDefault,
213+
)
214+
}
215+
197216
coopWeight.AddTaprootKeySpendInput(txscript.SigHashDefault)
198217

199218
err = sweep.htlcSuccessEstimator(&nonCoopWeight)
@@ -206,6 +225,7 @@ func estimateBatchWeight(batch *batch) (feeDetails, error) {
206225
return feeDetails{
207226
BatchId: batch.id,
208227
FeeRate: batch.rbfCache.FeeRate,
228+
MixedWeight: mixedWeight.Weight(),
209229
CoopWeight: coopWeight.Weight(),
210230
NonCoopWeight: nonCoopWeight.Weight(),
211231
NonCoopHint: hasNonCoop,
@@ -222,18 +242,22 @@ const newBatchSignal = -1
222242
type feeDetails struct {
223243
BatchId int32
224244
FeeRate chainfee.SatPerKWeight
245+
MixedWeight lntypes.WeightUnit
225246
CoopWeight lntypes.WeightUnit
226247
NonCoopWeight lntypes.WeightUnit
227248
NonCoopHint bool
228249
IsExternalAddr bool
229250
}
230251

231252
// fee returns fee of onchain transaction representing this instance.
232-
func (e feeDetails) fee() btcutil.Amount {
253+
func (e feeDetails) fee(mixedBatch bool) btcutil.Amount {
233254
var weight lntypes.WeightUnit
234-
if e.NonCoopHint {
255+
switch {
256+
case mixedBatch:
257+
weight = e.MixedWeight
258+
case e.NonCoopHint:
235259
weight = e.NonCoopWeight
236-
} else {
260+
default:
237261
weight = e.CoopWeight
238262
}
239263

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

251275
return feeDetails{
252276
FeeRate: feeRate,
277+
MixedWeight: e1.MixedWeight + e2.MixedWeight,
253278
CoopWeight: e1.CoopWeight + e2.CoopWeight,
254279
NonCoopWeight: e1.NonCoopWeight + e2.NonCoopWeight,
255280
NonCoopHint: e1.NonCoopHint || e2.NonCoopHint,
@@ -259,21 +284,26 @@ func (e1 feeDetails) combine(e2 feeDetails) feeDetails {
259284

260285
// selectBatches returns the list of id of batches sorted from best to worst.
261286
// Creation a new batch is encoded as newBatchSignal. For each batch its fee
262-
// rate and two weights are provided: weight in case of cooperative spending and
263-
// weight in case non-cooperative spending (using preimages instead of taproot
264-
// key spend). Also, a hint is provided to signal if the batch has to use
265-
// non-cooperative spending path. The same data is also provided to the sweep
266-
// for which we are selecting a batch to add. In case of the sweep weights are
267-
// weight deltas resulted from adding the sweep. Finally, the same data is
268-
// provided for new batch having this sweep only. The algorithm compares costs
269-
// of adding the sweep to each existing batch, and costs of new batch creation
270-
// for this sweep and returns BatchId of the winning batch. If the best option
271-
// is to create a new batch, return newBatchSignal. Each fee details has also
272-
// IsExternalAddr flag. There is a rule that sweeps having flag IsExternalAddr
273-
// must go in individual batches. Cooperative spending is only available if all
274-
// the sweeps support cooperative spending path.
275-
func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails) (
276-
[]int32, error) {
287+
// rate and a set of weights are provided: weight in case of a mixed batch,
288+
// weight in case of cooperative spending and weight in case non-cooperative
289+
// spending. Also, a hint is provided to signal what spending path will be used
290+
// by the batch.
291+
//
292+
// The same data is also provided for the sweep for which we are selecting a
293+
// batch to add. In case of the sweep weights are weight deltas resulted from
294+
// adding the sweep. Finally, the same data is provided for new batch having
295+
// this sweep only.
296+
//
297+
// The algorithm compares costs of adding the sweep to each existing batch, and
298+
// costs of new batch creation for this sweep and returns BatchId of the winning
299+
// batch. If the best option is to create a new batch, return newBatchSignal.
300+
//
301+
// Each fee details has also IsExternalAddr flag. There is a rule that sweeps
302+
// having flag IsExternalAddr must go in individual batches. Cooperative
303+
// spending is only available if all the sweeps support cooperative spending
304+
// path of in a mixed batch.
305+
func selectBatches(batches []feeDetails, sweep, oneSweepBatch feeDetails,
306+
mixedBatch bool) ([]int32, error) {
277307

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

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

312342
// The cost is the fee increase.
313-
cost := combinedBatch.fee() - batch.fee()
343+
cost := combinedBatch.fee(mixedBatch) - batch.fee(mixedBatch)
314344

315345
// The cost must be positive, because we added a sweep.
316346
if cost <= 0 {

0 commit comments

Comments
 (0)