sweep: allow force sweeps

This commit is contained in:
Joost Jager committed 2020-01-15 16:56:44 +01:00
1 parent 14237f5fd4
commit b0aae13d70
9 files changed
+172 -90

No files matched your search

+8 -4
View File
@@ -67,11 +67,15 @@ type Params struct {
// swept. If a confirmation target is specified, then we'll map it into
// a fee rate whenever we attempt to cluster inputs for a sweep.
Fee FeePreference
// Force indicates whether the input should be swept regardless of
// whether it is economical to do so.
Force bool
}
// String returns a human readable interpretation of the sweep parameters.
func (p Params) String() string {
return fmt.Sprintf("fee=%v", p.Fee)
return fmt.Sprintf("fee=%v, force=%v", p.Fee, p.Force)
}
// pendingInput is created when an input reaches the main loop for the first
@@ -398,10 +402,10 @@ func (s *UtxoSweeper) SweepInput(input input.Input,
}
log.Infof("Sweep request received: out_point=%v, witness_type=%v, "+
"time_lock=%v, amount=%v, fee_preference=%v", input.OutPoint(),
input.WitnessType(), input.BlocksToMaturity(),
"time_lock=%v, amount=%v, fee_preference=%v, force=%v",
input.OutPoint(), input.WitnessType(), input.BlocksToMaturity(),
btcutil.Amount(input.SignDesc().Output.Value),
params.Fee)
params.Fee, params.Force)
sweeperInput := &sweepInputMessage{
input: input,
+24 -3
View File
@@ -24,6 +24,10 @@ const (
// constraintsWallet is for wallet inputs that are only added to bring up the tx
// output value.
constraintsWallet
// constraintsForce is for inputs that should be swept even with a negative
// yield at the set fee rate.
constraintsForce
)
// txInputSet is an object that accumulates tx inputs and keeps running counters
@@ -58,6 +62,10 @@ type txInputSet struct {
// wallet contains wallet functionality required by the input set to
// retrieve utxos.
wallet Wallet
// force indicates that this set must be swept even if the total yield
// is negative.
force bool
}
// newTxInputSet constructs a new, empty input set.
@@ -138,6 +146,10 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
return false
}
// For force adds, no further constraints apply.
case constraintsForce:
t.force = true
// We are attaching a wallet input to raise the tx output value above
// the dust limit.
case constraintsWallet:
@@ -153,7 +165,8 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
// In any case, we don't want to lose money by sweeping. If we
// don't get more out of the tx then we put in ourselves, do not
// add this wallet input.
// add this wallet input. If there is at least one force sweep
// in the set, this does no longer apply.
//
// We should only add wallet inputs to get the tx output value
// above the dust limit, otherwise we'd only burn into fees.
@@ -163,7 +176,7 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
// value of the wallet input and what we get out of this
// transaction. To prevent attaching and locking a big utxo for
// very little benefit.
if newWalletTotal >= newOutputValue {
if !t.force && newWalletTotal >= newOutputValue {
log.Debugf("Rejecting wallet input of %v, because it "+
"would make a negative yielding transaction "+
"(%v)",
@@ -174,6 +187,8 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
}
// Update running values.
//
// TODO: Return new instance?
t.inputTotal = newInputTotal
t.outputValue = newOutputValue
t.inputs = append(t.inputs, input)
@@ -193,11 +208,17 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
// whole.
func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) {
for _, input := range sweepableInputs {
// Apply relaxed constraints for force sweeps.
constraints := constraintsRegular
if input.parameters().Force {
constraints = constraintsForce
}
// Try to add the input to the transaction. If that doesn't
// succeed because it wouldn't increase the output value,
// return. Assuming inputs are sorted by yield, any further
// inputs wouldn't increase the output value either.
if !t.add(input, constraintsRegular) {
if !t.add(input, constraints) {
return
}
}
+10
View File
@@ -77,6 +77,16 @@ func TestTxInputSetFromWallet(t *testing.T) {
t.Fatal("expected dust limit not yet to be reached")
}
// Expect that adding a negative yield input fails.
if set.add(createP2WKHInput(50), constraintsRegular) {
t.Fatal("expected negative yield input add to fail")
}
// Force add the negative yield input. It should succeed.
if !set.add(createP2WKHInput(50), constraintsForce) {
t.Fatal("expected forced add to succeed")
}
err := set.tryAddWalletInputsIfNeeded()
if err != nil {
t.Fatal(err)
+8
View File
@@ -67,6 +67,14 @@ func generateInputPartitionings(sweepableInputs []txInput,
}
sort.Slice(sweepableInputs, func(i, j int) bool {
// Because of the specific ordering and termination condition
// that is described above, we place force sweeps at the start
// of the list. Otherwise we can't be sure that they will be
// included in an input set.
if sweepableInputs[i].parameters().Force {
return true
}
return yields[*sweepableInputs[i].OutPoint()] >
yields[*sweepableInputs[j].OutPoint()]
})