Skip to content

Commit

Permalink
sweep: allow force sweeps
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Jan 9, 2020
1 parent 11ba4c5 commit 0a31880
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 69 deletions.
2 changes: 2 additions & 0 deletions cmd/lncli/walletrpc_types.go
Expand Up @@ -13,6 +13,7 @@ type PendingSweep struct {
NextBroadcastHeight uint32 `json:"next_broadcast_height"`
RequestedSatPerByte uint32 `json:"requested_sat_per_byte"`
RequestedConfTarget uint32 `json:"requested_conf_target"`
Force bool `json:"force"`
}

// NewPendingSweepFromProto converts the walletrpc.PendingSweep proto type into
Expand All @@ -27,5 +28,6 @@ func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSwee
NextBroadcastHeight: pendingSweep.NextBroadcastHeight,
RequestedSatPerByte: pendingSweep.RequestedSatPerByte,
RequestedConfTarget: pendingSweep.RequestedConfTarget,
Force: pendingSweep.Force,
}
}
142 changes: 77 additions & 65 deletions lnrpc/walletrpc/walletkit.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions lnrpc/walletrpc/walletkit.proto
Expand Up @@ -197,6 +197,12 @@ message PendingSweep {

// The requested fee rate, expressed in sat/byte, for this output.
uint32 requested_sat_per_byte = 9 [json_name = "requested_sat_per_byte"];

/**
Whether this input must be force-swept. This means that it is swept even
if it has a negative yield.
*/
bool force = 7 [json_name = "force"];
}

message PendingSweepsRequest {
Expand Down
1 change: 1 addition & 0 deletions lnrpc/walletrpc/walletkit_server.go
Expand Up @@ -416,6 +416,7 @@ func (w *WalletKit) PendingSweeps(ctx context.Context,
NextBroadcastHeight: nextBroadcastHeight,
RequestedSatPerByte: requestedFeeRate,
RequestedConfTarget: requestedFee.ConfTarget,
Force: pendingInput.Params.Force,
})
}

Expand Down
10 changes: 8 additions & 2 deletions sweep/sweeper.go
Expand Up @@ -67,6 +67,10 @@ 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
}

// pendingInput is created when an input reaches the main loop for the first
Expand Down Expand Up @@ -393,10 +397,12 @@ 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(),
"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,
Expand Down
27 changes: 25 additions & 2 deletions sweep/tx_input_set.go
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -129,6 +137,10 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {

switch constraints {

// For force adds, no further constraints apply.
case constraintsForce:
t.force = true

// If this input comes from the wallet, verify that we still gain
// something with this transaction.
case constraintsWallet:
Expand All @@ -148,7 +160,10 @@ 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 {
//
// TODO(joostjager): This may still be acceptable in the
// cpfp scenario with a force sweep. Add more logic.
if !t.force && newWalletTotal >= newOutputValue {
log.Debugf("Rejecting wallet input of %v, because it "+
"would make a negative yielding transaction "+
"(%v)",
Expand All @@ -170,6 +185,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)
Expand All @@ -189,11 +206,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
}
}
Expand Down
5 changes: 5 additions & 0 deletions sweep/txgenerator.go
Expand Up @@ -67,6 +67,11 @@ func generateInputPartitionings(sweepableInputs []txInput,
}

sort.Slice(sweepableInputs, func(i, j int) bool {
// Place force sweeps at the start of the list.
if sweepableInputs[i].parameters().Force {
return true
}

return yields[*sweepableInputs[i].OutPoint()] >
yields[*sweepableInputs[j].OutPoint()]
})
Expand Down

0 comments on commit 0a31880

Please sign in to comment.