sweep: allow force sweeps

This commit is contained in:
Joost Jager 2019-12-09 15:40:05 +01:00
parent 14237f5fd4
commit b0aae13d70
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
9 changed files with 172 additions and 90 deletions

View File

@ -4,7 +4,6 @@ package main
import ( import (
"context" "context"
"fmt"
"sort" "sort"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
@ -113,7 +112,13 @@ var bumpFeeCommand = cli.Command{
Note that this command currently doesn't perform any validation checks Note that this command currently doesn't perform any validation checks
on the fee preference being provided. For now, the responsibility of on the fee preference being provided. For now, the responsibility of
ensuring that the new fee preference is sufficient is delegated to the ensuring that the new fee preference is sufficient is delegated to the
user.`, user.
The force flag enables sweeping of inputs that are negatively yielding.
Normally it does not make sense to lose money on sweeping, unless a
parent transaction needs to get confirmed and there is only a small
output available to attach the child transaction to.
`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.Uint64Flag{ cli.Uint64Flag{
Name: "conf_target", Name: "conf_target",
@ -125,6 +130,10 @@ var bumpFeeCommand = cli.Command{
Usage: "a manual fee expressed in sat/byte that " + Usage: "a manual fee expressed in sat/byte that " +
"should be used when sweeping the output", "should be used when sweeping the output",
}, },
cli.BoolFlag{
Name: "force",
Usage: "sweep even if the yield is negative",
},
}, },
Action: actionDecorator(bumpFee), Action: actionDecorator(bumpFee),
} }
@ -132,7 +141,7 @@ var bumpFeeCommand = cli.Command{
func bumpFee(ctx *cli.Context) error { func bumpFee(ctx *cli.Context) error {
// Display the command's help message if we do not have the expected // Display the command's help message if we do not have the expected
// number of arguments/flags. // number of arguments/flags.
if ctx.NArg() != 1 || ctx.NumFlags() != 1 { if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "bumpfee") return cli.ShowCommandHelp(ctx, "bumpfee")
} }
@ -142,24 +151,14 @@ func bumpFee(ctx *cli.Context) error {
return err return err
} }
var confTarget, satPerByte uint32
switch {
case ctx.IsSet("conf_target") && ctx.IsSet("sat_per_byte"):
return fmt.Errorf("either conf_target or sat_per_byte should " +
"be set, but not both")
case ctx.IsSet("conf_target"):
confTarget = uint32(ctx.Uint64("conf_target"))
case ctx.IsSet("sat_per_byte"):
satPerByte = uint32(ctx.Uint64("sat_per_byte"))
}
client, cleanUp := getWalletClient(ctx) client, cleanUp := getWalletClient(ctx)
defer cleanUp() defer cleanUp()
resp, err := client.BumpFee(context.Background(), &walletrpc.BumpFeeRequest{ resp, err := client.BumpFee(context.Background(), &walletrpc.BumpFeeRequest{
Outpoint: protoOutPoint, Outpoint: protoOutPoint,
TargetConf: confTarget, TargetConf: uint32(ctx.Uint64("conf_target")),
SatPerByte: satPerByte, SatPerByte: uint32(ctx.Uint64("sat_per_byte")),
Force: ctx.Bool("force"),
}) })
if err != nil { if err != nil {
return err return err

View File

@ -13,6 +13,7 @@ type PendingSweep struct {
NextBroadcastHeight uint32 `json:"next_broadcast_height"` NextBroadcastHeight uint32 `json:"next_broadcast_height"`
RequestedSatPerByte uint32 `json:"requested_sat_per_byte"` RequestedSatPerByte uint32 `json:"requested_sat_per_byte"`
RequestedConfTarget uint32 `json:"requested_conf_target"` RequestedConfTarget uint32 `json:"requested_conf_target"`
Force bool `json:"force"`
} }
// NewPendingSweepFromProto converts the walletrpc.PendingSweep proto type into // NewPendingSweepFromProto converts the walletrpc.PendingSweep proto type into
@ -27,5 +28,6 @@ func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSwee
NextBroadcastHeight: pendingSweep.NextBroadcastHeight, NextBroadcastHeight: pendingSweep.NextBroadcastHeight,
RequestedSatPerByte: pendingSweep.RequestedSatPerByte, RequestedSatPerByte: pendingSweep.RequestedSatPerByte,
RequestedConfTarget: pendingSweep.RequestedConfTarget, RequestedConfTarget: pendingSweep.RequestedConfTarget,
Force: pendingSweep.Force,
} }
} }

View File

@ -535,7 +535,11 @@ type PendingSweep struct {
// The requested confirmation target for this output. // The requested confirmation target for this output.
RequestedConfTarget uint32 `protobuf:"varint,8,opt,name=requested_conf_target,proto3" json:"requested_conf_target,omitempty"` RequestedConfTarget uint32 `protobuf:"varint,8,opt,name=requested_conf_target,proto3" json:"requested_conf_target,omitempty"`
// The requested fee rate, expressed in sat/byte, for this output. // The requested fee rate, expressed in sat/byte, for this output.
RequestedSatPerByte uint32 `protobuf:"varint,9,opt,name=requested_sat_per_byte,proto3" json:"requested_sat_per_byte,omitempty"` RequestedSatPerByte uint32 `protobuf:"varint,9,opt,name=requested_sat_per_byte,proto3" json:"requested_sat_per_byte,omitempty"`
//*
//Whether this input must be force-swept. This means that it is swept even
//if it has a negative yield.
Force bool `protobuf:"varint,7,opt,name=force,proto3" json:"force,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -622,6 +626,13 @@ func (m *PendingSweep) GetRequestedSatPerByte() uint32 {
return 0 return 0
} }
func (m *PendingSweep) GetForce() bool {
if m != nil {
return m.Force
}
return false
}
type PendingSweepsRequest struct { type PendingSweepsRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
@ -702,7 +713,11 @@ type BumpFeeRequest struct {
// //
//The fee rate, expressed in sat/byte, that should be used to spend the input //The fee rate, expressed in sat/byte, that should be used to spend the input
//with. //with.
SatPerByte uint32 `protobuf:"varint,3,opt,name=sat_per_byte,proto3" json:"sat_per_byte,omitempty"` SatPerByte uint32 `protobuf:"varint,3,opt,name=sat_per_byte,proto3" json:"sat_per_byte,omitempty"`
//*
//Whether this input must be force-swept. This means that it is swept even
//if it has a negative yield.
Force bool `protobuf:"varint,4,opt,name=force,proto3" json:"force,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -754,6 +769,13 @@ func (m *BumpFeeRequest) GetSatPerByte() uint32 {
return 0 return 0
} }
func (m *BumpFeeRequest) GetForce() bool {
if m != nil {
return m.Force
}
return false
}
type BumpFeeResponse struct { type BumpFeeResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
@ -806,70 +828,72 @@ func init() {
func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) } func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) }
var fileDescriptor_6cc6942ac78249e5 = []byte{ var fileDescriptor_6cc6942ac78249e5 = []byte{
// 1003 bytes of a gzipped FileDescriptorProto // 1026 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xed, 0x6e, 0xe2, 0x46, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x6d, 0x6f, 0xe2, 0xc6,
0x14, 0x2d, 0x21, 0x21, 0xe1, 0x02, 0x89, 0x33, 0xf9, 0xf2, 0xb2, 0xd9, 0x0d, 0x75, 0x3f, 0x84, 0x13, 0xff, 0x13, 0x12, 0x02, 0x03, 0x24, 0xce, 0xe6, 0xc9, 0xc7, 0xe5, 0x2e, 0xfc, 0xdd, 0x07,
0xda, 0x0a, 0xd4, 0x6c, 0xbb, 0xaa, 0xda, 0x1f, 0x6d, 0x16, 0x1c, 0x11, 0xf1, 0x61, 0x6a, 0x3b, 0xa1, 0xb6, 0x02, 0x35, 0xd7, 0x9e, 0xaa, 0xf6, 0x45, 0x9b, 0x23, 0x8e, 0x12, 0x41, 0x30, 0xb5,
0x9b, 0x6e, 0x55, 0x69, 0x64, 0x60, 0x16, 0x2c, 0xc0, 0xf6, 0x8e, 0x87, 0x02, 0x7f, 0xdb, 0x07, 0x9d, 0x4b, 0xaf, 0xaa, 0xb4, 0x72, 0x60, 0x03, 0x16, 0x60, 0xfb, 0xd6, 0x4b, 0x81, 0xb7, 0xfd,
0xe8, 0xfb, 0xf4, 0xe9, 0x2a, 0x8f, 0x6d, 0x32, 0x86, 0x50, 0x69, 0x7f, 0xc5, 0x39, 0xe7, 0xdc, 0x16, 0xfd, 0x00, 0xfd, 0x12, 0xfd, 0x74, 0x95, 0xd7, 0x0f, 0x59, 0xf3, 0x50, 0xa9, 0xaf, 0x82,
0x33, 0x77, 0xe6, 0xde, 0xb9, 0x03, 0x3c, 0x9b, 0x5b, 0x93, 0x09, 0x61, 0xd4, 0xeb, 0x57, 0xc3, 0x7f, 0xbf, 0xdf, 0xcc, 0xce, 0xce, 0xcc, 0xce, 0x04, 0x5e, 0xcc, 0xac, 0xf1, 0x98, 0x30, 0xea,
0xaf, 0xb1, 0xcd, 0x2a, 0x1e, 0x75, 0x99, 0x8b, 0xb2, 0x2b, 0xaa, 0x98, 0xa5, 0x5e, 0x3f, 0x44, 0xf5, 0x1a, 0xe1, 0xaf, 0x91, 0xcd, 0xea, 0x1e, 0x75, 0x99, 0x8b, 0x0a, 0x09, 0x55, 0x29, 0x50,
0x8b, 0xa7, 0xbe, 0x3d, 0x74, 0x02, 0x79, 0xf0, 0x97, 0xd0, 0x10, 0x55, 0x7e, 0x85, 0x4c, 0x93, 0xaf, 0x17, 0xa2, 0x95, 0x23, 0xdf, 0x1e, 0x38, 0x81, 0x3c, 0xf8, 0x4b, 0x68, 0x88, 0x2a, 0x3f,
0x2c, 0x75, 0xf2, 0x01, 0x95, 0x41, 0x1a, 0x93, 0x25, 0x7e, 0x6f, 0x3b, 0x43, 0x42, 0xb1, 0x47, 0x43, 0xae, 0x45, 0x16, 0x3a, 0xf9, 0x88, 0x6a, 0x20, 0x8d, 0xc8, 0x02, 0x3f, 0xd9, 0xce, 0x80,
0x6d, 0x87, 0xc9, 0xa9, 0x52, 0xaa, 0xbc, 0xa7, 0x1f, 0x8e, 0xc9, 0xf2, 0x96, 0xc3, 0xdd, 0x00, 0x50, 0xec, 0x51, 0xdb, 0x61, 0x72, 0xa6, 0x9a, 0xa9, 0xed, 0xe8, 0x7b, 0x23, 0xb2, 0xb8, 0xe6,
0x45, 0x2f, 0x00, 0xb8, 0xd2, 0x9a, 0xda, 0x93, 0xa5, 0xbc, 0xc3, 0x35, 0xd9, 0x40, 0xc3, 0x01, 0x70, 0x37, 0x40, 0xd1, 0x2b, 0x00, 0xae, 0xb4, 0x26, 0xf6, 0x78, 0x21, 0x6f, 0x71, 0x4d, 0x21,
0xa5, 0x00, 0xb9, 0x9b, 0xc1, 0x80, 0xea, 0xe4, 0xc3, 0x8c, 0xf8, 0x4c, 0x51, 0x20, 0x1f, 0xfe, 0xd0, 0x70, 0x40, 0x29, 0x43, 0xf1, 0xb2, 0xdf, 0xa7, 0x3a, 0xf9, 0x38, 0x25, 0x3e, 0x53, 0x14,
0xeb, 0x7b, 0xae, 0xe3, 0x13, 0x84, 0x60, 0xd7, 0x1a, 0x0c, 0x28, 0xf7, 0xce, 0xea, 0xfc, 0x5b, 0x28, 0x85, 0x9f, 0xbe, 0xe7, 0x3a, 0x3e, 0x41, 0x08, 0xb6, 0xad, 0x7e, 0x9f, 0x72, 0xdf, 0x05,
0xf9, 0x1c, 0x72, 0x26, 0xb5, 0x1c, 0xdf, 0xea, 0x33, 0xdb, 0x75, 0xd0, 0x19, 0x64, 0xd8, 0x02, 0x9d, 0xff, 0x56, 0x3e, 0x85, 0xa2, 0x49, 0x2d, 0xc7, 0xb7, 0x7a, 0xcc, 0x76, 0x1d, 0x74, 0x0c,
0x8f, 0xc8, 0x82, 0x8b, 0xf2, 0xfa, 0x1e, 0x5b, 0x34, 0xc8, 0x42, 0x79, 0x0d, 0x47, 0xdd, 0x59, 0x39, 0x36, 0xc7, 0x43, 0x32, 0xe7, 0xa2, 0x92, 0xbe, 0xc3, 0xe6, 0x37, 0x64, 0xae, 0xbc, 0x85,
0x6f, 0x62, 0xfb, 0xa3, 0x95, 0xd9, 0x67, 0x50, 0xf0, 0x42, 0x08, 0x13, 0x4a, 0xdd, 0xd8, 0x35, 0xfd, 0xee, 0xf4, 0x71, 0x6c, 0xfb, 0xc3, 0xc4, 0xd9, 0x27, 0x50, 0xf6, 0x42, 0x08, 0x13, 0x4a,
0x1f, 0x81, 0x6a, 0x80, 0x29, 0x7f, 0x00, 0x32, 0x88, 0x33, 0xd0, 0x66, 0xcc, 0x9b, 0x31, 0x3f, 0xdd, 0xd8, 0x6b, 0x29, 0x02, 0xd5, 0x00, 0x53, 0x7e, 0x03, 0x64, 0x10, 0xa7, 0xaf, 0x4d, 0x99,
0xca, 0x0b, 0x5d, 0x02, 0xf8, 0x16, 0xc3, 0x1e, 0xa1, 0x78, 0x3c, 0xe7, 0x71, 0x69, 0xfd, 0xc0, 0x37, 0x65, 0x7e, 0x14, 0x17, 0x3a, 0x03, 0xf0, 0x2d, 0x86, 0x3d, 0x42, 0xf1, 0x68, 0xc6, 0xed,
0xb7, 0x58, 0x97, 0xd0, 0xe6, 0x1c, 0x95, 0x61, 0xdf, 0x0d, 0xf5, 0xf2, 0x4e, 0x29, 0x5d, 0xce, 0xb2, 0x7a, 0xde, 0xb7, 0x58, 0x97, 0xd0, 0xd6, 0x0c, 0xd5, 0x60, 0xd7, 0x0d, 0xf5, 0xf2, 0x56,
0x5d, 0x1f, 0x56, 0xa2, 0xf3, 0xab, 0x98, 0x0b, 0x6d, 0xc6, 0xf4, 0x98, 0x56, 0xbe, 0x81, 0x93, 0x35, 0x5b, 0x2b, 0x5e, 0xec, 0xd5, 0xa3, 0xfc, 0xd5, 0xcd, 0xb9, 0x36, 0x65, 0x7a, 0x4c, 0x2b,
0x84, 0x7b, 0x94, 0xd9, 0x19, 0x64, 0xa8, 0x35, 0xc7, 0x6c, 0xb5, 0x07, 0x6a, 0xcd, 0xcd, 0x85, 0x5f, 0xc1, 0x61, 0xca, 0x7b, 0x14, 0xd9, 0x31, 0xe4, 0xa8, 0x35, 0xc3, 0x2c, 0xb9, 0x03, 0xb5,
0xf2, 0x3d, 0x20, 0xd5, 0x67, 0xf6, 0xd4, 0x62, 0xe4, 0x96, 0x90, 0x38, 0x97, 0x2b, 0xc8, 0xf5, 0x66, 0xe6, 0x5c, 0xf9, 0x16, 0x90, 0xea, 0x33, 0x7b, 0x62, 0x31, 0x72, 0x4d, 0x48, 0x1c, 0xcb,
0x5d, 0xe7, 0x3d, 0x66, 0x16, 0x1d, 0x92, 0xf8, 0xd8, 0x21, 0x80, 0x4c, 0x8e, 0x28, 0xaf, 0xe0, 0x39, 0x14, 0x7b, 0xae, 0xf3, 0x84, 0x99, 0x45, 0x07, 0x24, 0x4e, 0x3b, 0x04, 0x90, 0xc9, 0x11,
0x24, 0x11, 0x16, 0x2d, 0xf2, 0xbf, 0x7b, 0x50, 0xfe, 0x49, 0x43, 0xbe, 0x4b, 0x9c, 0x81, 0xed, 0xe5, 0x0d, 0x1c, 0xa6, 0xcc, 0xa2, 0x43, 0xfe, 0xf5, 0x0e, 0xca, 0x5f, 0x59, 0x28, 0x75, 0x89,
0x0c, 0x8d, 0x39, 0x21, 0x1e, 0xfa, 0x1a, 0x0e, 0x82, 0xac, 0xdd, 0xb8, 0xb4, 0xb9, 0xeb, 0xa3, 0xd3, 0xb7, 0x9d, 0x81, 0x31, 0x23, 0xc4, 0x43, 0x5f, 0x42, 0x3e, 0x88, 0xda, 0x8d, 0x4b, 0x5b,
0xca, 0x84, 0xef, 0x49, 0x9b, 0xb1, 0x6e, 0x00, 0xeb, 0x2b, 0x01, 0xfa, 0x11, 0xf2, 0x73, 0x9b, 0xbc, 0xd8, 0xaf, 0x8f, 0xf9, 0x9d, 0xb4, 0x29, 0xeb, 0x06, 0xb0, 0x9e, 0x08, 0xd0, 0xf7, 0x50,
0x39, 0xc4, 0xf7, 0x31, 0x5b, 0x7a, 0x84, 0xd7, 0xf9, 0xf0, 0xfa, 0xbc, 0xb2, 0x6a, 0xae, 0xca, 0x9a, 0xd9, 0xcc, 0x21, 0xbe, 0x8f, 0xd9, 0xc2, 0x23, 0xbc, 0xce, 0x7b, 0x17, 0x27, 0xf5, 0xa4,
0x43, 0x48, 0x9b, 0x4b, 0x8f, 0xe8, 0x09, 0x2d, 0x7a, 0x09, 0x60, 0x4d, 0xdd, 0x99, 0xc3, 0xb0, 0xb9, 0xea, 0x0f, 0x21, 0x6d, 0x2e, 0x3c, 0xa2, 0xa7, 0xb4, 0xe8, 0x35, 0x80, 0x35, 0x71, 0xa7,
0x6f, 0x31, 0x39, 0x5d, 0x4a, 0x95, 0x0b, 0xba, 0x80, 0x20, 0x05, 0xf2, 0x71, 0xde, 0xbd, 0x25, 0x0e, 0xc3, 0xbe, 0xc5, 0xe4, 0x6c, 0x35, 0x53, 0x2b, 0xeb, 0x02, 0x82, 0x14, 0x28, 0xc5, 0x71,
0x23, 0xf2, 0x2e, 0x57, 0x24, 0x30, 0x54, 0x01, 0xd4, 0xa3, 0xae, 0x35, 0xe8, 0x5b, 0x3e, 0xc3, 0x3f, 0x2e, 0x18, 0x91, 0xb7, 0xb9, 0x22, 0x85, 0xa1, 0x3a, 0xa0, 0x47, 0xea, 0x5a, 0xfd, 0x9e,
0x16, 0x63, 0x64, 0xea, 0x31, 0x5f, 0xde, 0xe3, 0xca, 0x27, 0x18, 0xf4, 0x1d, 0x9c, 0x39, 0x64, 0xe5, 0x33, 0x6c, 0x31, 0x46, 0x26, 0x1e, 0xf3, 0xe5, 0x1d, 0xae, 0x5c, 0xc3, 0xa0, 0x6f, 0xe0,
0xc1, 0xf0, 0x23, 0x35, 0x22, 0xf6, 0x70, 0xc4, 0xe4, 0x0c, 0x0f, 0x79, 0x9a, 0x0c, 0xa2, 0x68, 0xd8, 0x21, 0x73, 0x86, 0x9f, 0xa9, 0x21, 0xb1, 0x07, 0x43, 0x26, 0xe7, 0xb8, 0xc9, 0x7a, 0x32,
0x58, 0x04, 0x32, 0xc0, 0x62, 0x0d, 0x0e, 0xc2, 0xa8, 0x27, 0x49, 0xf4, 0x1a, 0xce, 0x1f, 0x89, 0xb0, 0xa2, 0x61, 0x11, 0x48, 0x1f, 0x8b, 0x35, 0xc8, 0x87, 0x56, 0x6b, 0x49, 0xf4, 0x16, 0x4e,
0xc4, 0x4e, 0xb2, 0x3c, 0x6c, 0x0b, 0xab, 0x9c, 0xc3, 0xa9, 0x58, 0x90, 0xb8, 0x17, 0x95, 0xdf, 0x9e, 0x89, 0xd4, 0x4d, 0x0a, 0xdc, 0x6c, 0x03, 0x8b, 0x8e, 0x60, 0xe7, 0xc9, 0xa5, 0x3d, 0x22,
0xe0, 0x6c, 0x0d, 0x8f, 0x0a, 0xfc, 0x33, 0x1c, 0x7a, 0x21, 0x81, 0x7d, 0xce, 0xc8, 0x29, 0xde, 0xef, 0x56, 0x33, 0xb5, 0xbc, 0x1e, 0x7e, 0x28, 0x27, 0x70, 0x24, 0x96, 0x29, 0xee, 0x50, 0xe5,
0x8d, 0x17, 0x42, 0x19, 0xc4, 0x48, 0x7d, 0x4d, 0xae, 0xfc, 0x9d, 0x82, 0xc3, 0x37, 0xb3, 0xa9, 0x17, 0x38, 0x5e, 0xc2, 0xa3, 0xb2, 0xff, 0x08, 0x7b, 0x5e, 0x48, 0x60, 0x9f, 0x33, 0x72, 0x86,
0x27, 0x34, 0xdb, 0x47, 0x75, 0x41, 0x09, 0x72, 0xe1, 0x9e, 0xf9, 0xfe, 0x79, 0x13, 0x14, 0x74, 0xf7, 0xe8, 0xa9, 0x50, 0x1c, 0xd1, 0x52, 0x5f, 0x92, 0x2b, 0x7f, 0x66, 0x60, 0xef, 0xdd, 0x74,
0x11, 0xda, 0xa8, 0x65, 0x7a, 0xb3, 0x96, 0xca, 0x31, 0x1c, 0xad, 0x92, 0x08, 0x77, 0xf6, 0xd5, 0xe2, 0x09, 0x2d, 0xf8, 0x9f, 0x7a, 0xa3, 0x0a, 0xc5, 0x30, 0x13, 0x3c, 0x2b, 0xbc, 0x35, 0xca,
0x5f, 0x69, 0xc8, 0x09, 0x0d, 0x84, 0x4e, 0xe0, 0xe8, 0xbe, 0xd3, 0xec, 0x68, 0x0f, 0x1d, 0xfc, 0xba, 0x08, 0xad, 0x54, 0x38, 0xbb, 0xa6, 0xc2, 0x49, 0x36, 0xb6, 0xc5, 0x6c, 0x1c, 0xc0, 0x7e,
0x70, 0x67, 0x76, 0x54, 0xc3, 0x90, 0x3e, 0x41, 0x32, 0x9c, 0xd6, 0xb4, 0x76, 0xfb, 0xce, 0x6c, 0x12, 0x5a, 0x78, 0xdf, 0x2f, 0xfe, 0xc8, 0x42, 0x51, 0x68, 0x36, 0x74, 0x08, 0xfb, 0xf7, 0x9d,
0xab, 0x1d, 0x13, 0x9b, 0x77, 0x6d, 0x15, 0xb7, 0xb4, 0x5a, 0x53, 0x4a, 0xa1, 0x0b, 0x38, 0x11, 0x56, 0x47, 0x7b, 0xe8, 0xe0, 0x87, 0x5b, 0xb3, 0xa3, 0x1a, 0x86, 0xf4, 0x3f, 0x24, 0xc3, 0x51,
0x98, 0x8e, 0x86, 0xeb, 0x6a, 0xeb, 0xe6, 0x9d, 0xb4, 0x83, 0xce, 0xe0, 0x58, 0x20, 0x74, 0xf5, 0x53, 0xbb, 0xbb, 0xbb, 0x35, 0xef, 0xd4, 0x8e, 0x89, 0xcd, 0xdb, 0x3b, 0x15, 0xb7, 0xb5, 0x66,
0xad, 0xd6, 0x54, 0xa5, 0x74, 0xa0, 0x6f, 0x98, 0xad, 0x1a, 0xd6, 0x6e, 0x6f, 0x55, 0x5d, 0xad, 0x4b, 0xca, 0xa0, 0x53, 0x38, 0x14, 0x98, 0x8e, 0x86, 0xaf, 0xd4, 0xf6, 0xe5, 0x07, 0x69, 0x0b,
0xc7, 0xc4, 0x6e, 0xb0, 0x04, 0x27, 0x6e, 0x6a, 0x35, 0xb5, 0x6b, 0x3e, 0x32, 0x7b, 0xe8, 0x0b, 0x1d, 0xc3, 0x81, 0x40, 0xe8, 0xea, 0x7b, 0xad, 0xa5, 0x4a, 0xd9, 0x40, 0x7f, 0x63, 0xb6, 0x9b,
0xf8, 0x34, 0x11, 0x12, 0x2c, 0xaf, 0xdd, 0x9b, 0xd8, 0x50, 0x6b, 0x5a, 0xa7, 0x8e, 0x5b, 0xea, 0x58, 0xbb, 0xbe, 0x56, 0x75, 0xf5, 0x2a, 0x26, 0xb6, 0x83, 0x23, 0x38, 0x71, 0xd9, 0x6c, 0xaa,
0x5b, 0xb5, 0x25, 0x65, 0xd0, 0x97, 0xa0, 0x24, 0x0d, 0x8c, 0xfb, 0x5a, 0x4d, 0x35, 0x8c, 0xa4, 0x5d, 0xf3, 0x99, 0xd9, 0x41, 0x9f, 0xc1, 0xff, 0x53, 0x26, 0xc1, 0xf1, 0xda, 0xbd, 0x89, 0x0d,
0x6e, 0x1f, 0x5d, 0xc1, 0xf3, 0xb5, 0x0c, 0xda, 0x9a, 0xa9, 0xc6, 0xae, 0xd2, 0x01, 0x2a, 0xc1, 0xb5, 0xa9, 0x75, 0xae, 0x70, 0x5b, 0x7d, 0xaf, 0xb6, 0xa5, 0x1c, 0xfa, 0x1c, 0x94, 0xb4, 0x03,
0xe5, 0x7a, 0x26, 0x5c, 0x11, 0xf9, 0x49, 0x59, 0x74, 0x09, 0x32, 0x57, 0x88, 0xce, 0x71, 0xbe, 0xe3, 0xbe, 0xd9, 0x54, 0x0d, 0x23, 0xad, 0xdb, 0x45, 0xe7, 0xf0, 0x72, 0x29, 0x82, 0x3b, 0xcd,
0x80, 0x4e, 0x41, 0x8a, 0x4e, 0x0e, 0x37, 0xd5, 0x77, 0xb8, 0x71, 0x63, 0x34, 0xa4, 0x1c, 0x7a, 0x54, 0x63, 0xaf, 0x52, 0x1e, 0x55, 0xe1, 0x6c, 0x39, 0x12, 0xae, 0x88, 0xfc, 0x49, 0x05, 0x74,
0x0e, 0x17, 0x1d, 0xd5, 0x08, 0xec, 0x36, 0xc8, 0xfc, 0xf5, 0xbf, 0xbb, 0x90, 0x7d, 0xe0, 0x8d, 0x06, 0x32, 0x57, 0x88, 0x9e, 0xe3, 0x78, 0x01, 0x1d, 0x81, 0x14, 0x65, 0x0e, 0xb7, 0xd4, 0x0f,
0xd4, 0xb4, 0x83, 0x1b, 0x5f, 0xa8, 0x13, 0x6a, 0xff, 0x49, 0x3a, 0x64, 0xc1, 0x9a, 0x64, 0x89, 0xf8, 0xe6, 0xd2, 0xb8, 0x91, 0x8a, 0xe8, 0x25, 0x9c, 0x76, 0x54, 0x23, 0x70, 0xb7, 0x42, 0x96,
0x8e, 0x85, 0x2e, 0x0b, 0x5f, 0x89, 0xe2, 0xf9, 0x6a, 0x0c, 0x36, 0xc9, 0xb2, 0x4e, 0xfc, 0x3e, 0x2e, 0xfe, 0xde, 0x86, 0xc2, 0x03, 0x6f, 0xaf, 0x96, 0x1d, 0x4c, 0x87, 0xf2, 0x15, 0xa1, 0xf6,
0xb5, 0x3d, 0xe6, 0x52, 0xf4, 0x03, 0x64, 0xc3, 0xd8, 0x20, 0xee, 0x44, 0x14, 0xb5, 0xdc, 0xbe, 0xef, 0xa4, 0x43, 0xe6, 0xac, 0x45, 0x16, 0xe8, 0x40, 0xe8, 0xbd, 0x70, 0xa3, 0x54, 0x4e, 0x92,
0xc5, 0x5c, 0xba, 0x35, 0xf2, 0x27, 0x38, 0x08, 0xd6, 0x0b, 0xde, 0x08, 0x24, 0x4e, 0x17, 0xe1, 0x91, 0xd9, 0x22, 0x8b, 0x2b, 0xe2, 0xf7, 0xa8, 0xed, 0x31, 0x97, 0xa2, 0xef, 0xa0, 0x10, 0xda,
0x0d, 0x29, 0x5e, 0x6c, 0xe0, 0xd1, 0xfd, 0x68, 0x00, 0x8a, 0x9e, 0x04, 0xf1, 0xfd, 0x10, 0x6d, 0x06, 0x76, 0x87, 0xa2, 0xa8, 0xed, 0xf6, 0x2c, 0xe6, 0xd2, 0x8d, 0x96, 0x3f, 0x40, 0x3e, 0x38,
0x04, 0xbc, 0x58, 0x14, 0x6f, 0xcd, 0xda, 0x4b, 0xd2, 0x82, 0x9c, 0x30, 0xc6, 0xd1, 0x0b, 0x41, 0x2f, 0xd8, 0x27, 0x48, 0x9c, 0x44, 0xc2, 0xbe, 0xa9, 0x9c, 0xae, 0xe0, 0xd1, 0xab, 0xb9, 0x01,
0xba, 0xf9, 0x78, 0x14, 0x5f, 0x6e, 0xa3, 0x1f, 0xdd, 0x84, 0x79, 0x9d, 0x70, 0xdb, 0x1c, 0xff, 0x14, 0xad, 0x0f, 0x71, 0xd7, 0x88, 0x6e, 0x04, 0xbc, 0x52, 0x11, 0xdf, 0xd2, 0xd2, 0xd6, 0x69,
0x09, 0xb7, 0xa7, 0xc6, 0xbc, 0x0e, 0x85, 0xc4, 0x78, 0x40, 0x57, 0x5b, 0xae, 0xff, 0x2a, 0xbf, 0x43, 0x51, 0x18, 0xf9, 0xe8, 0x95, 0x20, 0x5d, 0x5d, 0x34, 0x95, 0xd7, 0x9b, 0xe8, 0x67, 0x6f,
0xd2, 0x76, 0x41, 0xe4, 0xf9, 0x0b, 0xec, 0x47, 0x57, 0x12, 0x3d, 0x13, 0xc4, 0xc9, 0x59, 0x91, 0xc2, 0x6c, 0x4f, 0x79, 0x5b, 0x5d, 0x15, 0x29, 0x6f, 0xeb, 0x56, 0x82, 0x0e, 0xe5, 0xd4, 0xd0,
0x38, 0xb1, 0xb5, 0x1b, 0xfc, 0xe6, 0xdb, 0xdf, 0xab, 0x43, 0x9b, 0x8d, 0x66, 0xbd, 0x4a, 0xdf, 0x40, 0xe7, 0x1b, 0x86, 0x42, 0x12, 0x5f, 0x75, 0xb3, 0x20, 0xf2, 0xf9, 0x13, 0xec, 0x46, 0x4f,
0x9d, 0x56, 0x27, 0xc1, 0x38, 0x75, 0x6c, 0x67, 0xe8, 0x10, 0x36, 0x77, 0xe9, 0xb8, 0x3a, 0x71, 0x12, 0xbd, 0x10, 0xc4, 0xe9, 0x09, 0x92, 0xca, 0xd8, 0xd2, 0x0b, 0x7e, 0xf7, 0xf5, 0xaf, 0x8d,
0x06, 0x55, 0x3e, 0x56, 0xaa, 0x2b, 0x8b, 0x5e, 0x86, 0xff, 0xe8, 0x78, 0xf5, 0x5f, 0x00, 0x00, 0x81, 0xcd, 0x86, 0xd3, 0xc7, 0x7a, 0xcf, 0x9d, 0x34, 0xc6, 0xc1, 0xe8, 0x75, 0x6c, 0x67, 0xe0,
0x00, 0xff, 0xff, 0x2c, 0x7c, 0x20, 0x44, 0xbd, 0x08, 0x00, 0x00, 0x10, 0x36, 0x73, 0xe9, 0xa8, 0x31, 0x76, 0xfa, 0x0d, 0x3e, 0x6c, 0x1a, 0x89, 0x8b, 0xc7, 0x1c,
0xff, 0x07, 0xe5, 0xcd, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x5a, 0x4e, 0xaf, 0xaf, 0xe9, 0x08,
0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.

View File

@ -197,6 +197,12 @@ message PendingSweep {
// The requested fee rate, expressed in sat/byte, for this output. // The requested fee rate, expressed in sat/byte, for this output.
uint32 requested_sat_per_byte = 9 [json_name = "requested_sat_per_byte"]; 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 { message PendingSweepsRequest {
@ -221,6 +227,12 @@ message BumpFeeRequest {
with. with.
*/ */
uint32 sat_per_byte = 3 [json_name = "sat_per_byte"]; uint32 sat_per_byte = 3 [json_name = "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 = 4 [json_name = "force"];
} }
message BumpFeeResponse { message BumpFeeResponse {

View File

@ -416,6 +416,7 @@ func (w *WalletKit) PendingSweeps(ctx context.Context,
NextBroadcastHeight: nextBroadcastHeight, NextBroadcastHeight: nextBroadcastHeight,
RequestedSatPerByte: requestedFeeRate, RequestedSatPerByte: requestedFeeRate,
RequestedConfTarget: requestedFee.ConfTarget, RequestedConfTarget: requestedFee.ConfTarget,
Force: pendingInput.Params.Force,
}) })
} }
@ -486,7 +487,8 @@ func (w *WalletKit) BumpFee(ctx context.Context,
// being broadcast. If it is not aware of the input however, // being broadcast. If it is not aware of the input however,
// lnwallet.ErrNotMine is returned. // lnwallet.ErrNotMine is returned.
params := sweep.Params{ params := sweep.Params{
Fee: feePreference, Fee: feePreference,
Force: in.Force,
} }
_, err = w.cfg.Sweeper.UpdateParams(*op, params) _, err = w.cfg.Sweeper.UpdateParams(*op, params)

View File

@ -67,11 +67,15 @@ type Params struct {
// swept. If a confirmation target is specified, then we'll map it into // 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. // a fee rate whenever we attempt to cluster inputs for a sweep.
Fee FeePreference 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. // String returns a human readable interpretation of the sweep parameters.
func (p Params) String() string { 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 // 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, "+ 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.WitnessType(), input.BlocksToMaturity(), input.OutPoint(), input.WitnessType(), input.BlocksToMaturity(),
btcutil.Amount(input.SignDesc().Output.Value), btcutil.Amount(input.SignDesc().Output.Value),
params.Fee) params.Fee, params.Force)
sweeperInput := &sweepInputMessage{ sweeperInput := &sweepInputMessage{
input: input, input: input,

View File

@ -24,6 +24,10 @@ const (
// constraintsWallet is for wallet inputs that are only added to bring up the tx // constraintsWallet is for wallet inputs that are only added to bring up the tx
// output value. // output value.
constraintsWallet 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 // 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 // wallet contains wallet functionality required by the input set to
// retrieve utxos. // retrieve utxos.
wallet Wallet 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. // newTxInputSet constructs a new, empty input set.
@ -138,6 +146,10 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
return false 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 // We are attaching a wallet input to raise the tx output value above
// the dust limit. // the dust limit.
case constraintsWallet: 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 // 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 // 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 // We should only add wallet inputs to get the tx output value
// above the dust limit, otherwise we'd only burn into fees. // 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 // value of the wallet input and what we get out of this
// transaction. To prevent attaching and locking a big utxo for // transaction. To prevent attaching and locking a big utxo for
// very little benefit. // very little benefit.
if newWalletTotal >= newOutputValue { if !t.force && newWalletTotal >= newOutputValue {
log.Debugf("Rejecting wallet input of %v, because it "+ log.Debugf("Rejecting wallet input of %v, because it "+
"would make a negative yielding transaction "+ "would make a negative yielding transaction "+
"(%v)", "(%v)",
@ -174,6 +187,8 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
} }
// Update running values. // Update running values.
//
// TODO: Return new instance?
t.inputTotal = newInputTotal t.inputTotal = newInputTotal
t.outputValue = newOutputValue t.outputValue = newOutputValue
t.inputs = append(t.inputs, input) t.inputs = append(t.inputs, input)
@ -193,11 +208,17 @@ func (t *txInputSet) add(input input.Input, constraints addConstraints) bool {
// whole. // whole.
func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) { func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) {
for _, input := range sweepableInputs { 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 // Try to add the input to the transaction. If that doesn't
// succeed because it wouldn't increase the output value, // succeed because it wouldn't increase the output value,
// return. Assuming inputs are sorted by yield, any further // return. Assuming inputs are sorted by yield, any further
// inputs wouldn't increase the output value either. // inputs wouldn't increase the output value either.
if !t.add(input, constraintsRegular) { if !t.add(input, constraints) {
return return
} }
} }

View File

@ -77,6 +77,16 @@ func TestTxInputSetFromWallet(t *testing.T) {
t.Fatal("expected dust limit not yet to be reached") 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() err := set.tryAddWalletInputsIfNeeded()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -67,6 +67,14 @@ func generateInputPartitionings(sweepableInputs []txInput,
} }
sort.Slice(sweepableInputs, func(i, j int) bool { 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()] > return yields[*sweepableInputs[i].OutPoint()] >
yields[*sweepableInputs[j].OutPoint()] yields[*sweepableInputs[j].OutPoint()]
}) })