diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go index 35dc562d67..31b14b764f 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/lncli/walletrpc_active.go @@ -4,7 +4,6 @@ package main import ( "context" - "fmt" "sort" "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 on the fee preference being provided. For now, the responsibility of 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{ cli.Uint64Flag{ Name: "conf_target", @@ -125,6 +130,10 @@ var bumpFeeCommand = cli.Command{ Usage: "a manual fee expressed in sat/byte that " + "should be used when sweeping the output", }, + cli.BoolFlag{ + Name: "force", + Usage: "sweep even if the yield is negative", + }, }, Action: actionDecorator(bumpFee), } @@ -132,7 +141,7 @@ var bumpFeeCommand = cli.Command{ func bumpFee(ctx *cli.Context) error { // Display the command's help message if we do not have the expected // number of arguments/flags. - if ctx.NArg() != 1 || ctx.NumFlags() != 1 { + if ctx.NArg() != 1 { return cli.ShowCommandHelp(ctx, "bumpfee") } @@ -142,24 +151,14 @@ func bumpFee(ctx *cli.Context) error { 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) defer cleanUp() resp, err := client.BumpFee(context.Background(), &walletrpc.BumpFeeRequest{ Outpoint: protoOutPoint, - TargetConf: confTarget, - SatPerByte: satPerByte, + TargetConf: uint32(ctx.Uint64("conf_target")), + SatPerByte: uint32(ctx.Uint64("sat_per_byte")), + Force: ctx.Bool("force"), }) if err != nil { return err diff --git a/cmd/lncli/walletrpc_types.go b/cmd/lncli/walletrpc_types.go index 11842c6f23..c2b5698191 100644 --- a/cmd/lncli/walletrpc_types.go +++ b/cmd/lncli/walletrpc_types.go @@ -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 @@ -27,5 +28,6 @@ func NewPendingSweepFromProto(pendingSweep *walletrpc.PendingSweep) *PendingSwee NextBroadcastHeight: pendingSweep.NextBroadcastHeight, RequestedSatPerByte: pendingSweep.RequestedSatPerByte, RequestedConfTarget: pendingSweep.RequestedConfTarget, + Force: pendingSweep.Force, } } diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 6feab45ba9..e2ded0cc0c 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -535,7 +535,11 @@ type PendingSweep struct { // The requested confirmation target for this output. 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. - 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_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -622,6 +626,13 @@ func (m *PendingSweep) GetRequestedSatPerByte() uint32 { return 0 } +func (m *PendingSweep) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + type PendingSweepsRequest struct { XXX_NoUnkeyedLiteral struct{} `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 //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_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -754,6 +769,13 @@ func (m *BumpFeeRequest) GetSatPerByte() uint32 { return 0 } +func (m *BumpFeeRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + type BumpFeeResponse struct { XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -806,70 +828,72 @@ func init() { func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) } var fileDescriptor_6cc6942ac78249e5 = []byte{ - // 1003 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xed, 0x6e, 0xe2, 0x46, - 0x14, 0x2d, 0x21, 0x21, 0xe1, 0x02, 0x89, 0x33, 0xf9, 0xf2, 0xb2, 0xd9, 0x0d, 0x75, 0x3f, 0x84, - 0xda, 0x0a, 0xd4, 0x6c, 0xbb, 0xaa, 0xda, 0x1f, 0x6d, 0x16, 0x1c, 0x11, 0xf1, 0x61, 0x6a, 0x3b, - 0x9b, 0x6e, 0x55, 0x69, 0x64, 0x60, 0x16, 0x2c, 0xc0, 0xf6, 0x8e, 0x87, 0x02, 0x7f, 0xdb, 0x07, - 0xe8, 0xfb, 0xf4, 0xe9, 0x2a, 0x8f, 0x6d, 0x32, 0x86, 0x50, 0x69, 0x7f, 0xc5, 0x39, 0xe7, 0xdc, - 0x33, 0x77, 0xe6, 0xde, 0xb9, 0x03, 0x3c, 0x9b, 0x5b, 0x93, 0x09, 0x61, 0xd4, 0xeb, 0x57, 0xc3, - 0xaf, 0xb1, 0xcd, 0x2a, 0x1e, 0x75, 0x99, 0x8b, 0xb2, 0x2b, 0xaa, 0x98, 0xa5, 0x5e, 0x3f, 0x44, - 0x8b, 0xa7, 0xbe, 0x3d, 0x74, 0x02, 0x79, 0xf0, 0x97, 0xd0, 0x10, 0x55, 0x7e, 0x85, 0x4c, 0x93, - 0x2c, 0x75, 0xf2, 0x01, 0x95, 0x41, 0x1a, 0x93, 0x25, 0x7e, 0x6f, 0x3b, 0x43, 0x42, 0xb1, 0x47, - 0x6d, 0x87, 0xc9, 0xa9, 0x52, 0xaa, 0xbc, 0xa7, 0x1f, 0x8e, 0xc9, 0xf2, 0x96, 0xc3, 0xdd, 0x00, - 0x45, 0x2f, 0x00, 0xb8, 0xd2, 0x9a, 0xda, 0x93, 0xa5, 0xbc, 0xc3, 0x35, 0xd9, 0x40, 0xc3, 0x01, - 0xa5, 0x00, 0xb9, 0x9b, 0xc1, 0x80, 0xea, 0xe4, 0xc3, 0x8c, 0xf8, 0x4c, 0x51, 0x20, 0x1f, 0xfe, - 0xeb, 0x7b, 0xae, 0xe3, 0x13, 0x84, 0x60, 0xd7, 0x1a, 0x0c, 0x28, 0xf7, 0xce, 0xea, 0xfc, 0x5b, - 0xf9, 0x1c, 0x72, 0x26, 0xb5, 0x1c, 0xdf, 0xea, 0x33, 0xdb, 0x75, 0xd0, 0x19, 0x64, 0xd8, 0x02, - 0x8f, 0xc8, 0x82, 0x8b, 0xf2, 0xfa, 0x1e, 0x5b, 0x34, 0xc8, 0x42, 0x79, 0x0d, 0x47, 0xdd, 0x59, - 0x6f, 0x62, 0xfb, 0xa3, 0x95, 0xd9, 0x67, 0x50, 0xf0, 0x42, 0x08, 0x13, 0x4a, 0xdd, 0xd8, 0x35, - 0x1f, 0x81, 0x6a, 0x80, 0x29, 0x7f, 0x00, 0x32, 0x88, 0x33, 0xd0, 0x66, 0xcc, 0x9b, 0x31, 0x3f, - 0xca, 0x0b, 0x5d, 0x02, 0xf8, 0x16, 0xc3, 0x1e, 0xa1, 0x78, 0x3c, 0xe7, 0x71, 0x69, 0xfd, 0xc0, - 0xb7, 0x58, 0x97, 0xd0, 0xe6, 0x1c, 0x95, 0x61, 0xdf, 0x0d, 0xf5, 0xf2, 0x4e, 0x29, 0x5d, 0xce, - 0x5d, 0x1f, 0x56, 0xa2, 0xf3, 0xab, 0x98, 0x0b, 0x6d, 0xc6, 0xf4, 0x98, 0x56, 0xbe, 0x81, 0x93, - 0x84, 0x7b, 0x94, 0xd9, 0x19, 0x64, 0xa8, 0x35, 0xc7, 0x6c, 0xb5, 0x07, 0x6a, 0xcd, 0xcd, 0x85, - 0xf2, 0x3d, 0x20, 0xd5, 0x67, 0xf6, 0xd4, 0x62, 0xe4, 0x96, 0x90, 0x38, 0x97, 0x2b, 0xc8, 0xf5, - 0x5d, 0xe7, 0x3d, 0x66, 0x16, 0x1d, 0x92, 0xf8, 0xd8, 0x21, 0x80, 0x4c, 0x8e, 0x28, 0xaf, 0xe0, - 0x24, 0x11, 0x16, 0x2d, 0xf2, 0xbf, 0x7b, 0x50, 0xfe, 0x49, 0x43, 0xbe, 0x4b, 0x9c, 0x81, 0xed, - 0x0c, 0x8d, 0x39, 0x21, 0x1e, 0xfa, 0x1a, 0x0e, 0x82, 0xac, 0xdd, 0xb8, 0xb4, 0xb9, 0xeb, 0xa3, - 0xca, 0x84, 0xef, 0x49, 0x9b, 0xb1, 0x6e, 0x00, 0xeb, 0x2b, 0x01, 0xfa, 0x11, 0xf2, 0x73, 0x9b, - 0x39, 0xc4, 0xf7, 0x31, 0x5b, 0x7a, 0x84, 0xd7, 0xf9, 0xf0, 0xfa, 0xbc, 0xb2, 0x6a, 0xae, 0xca, - 0x43, 0x48, 0x9b, 0x4b, 0x8f, 0xe8, 0x09, 0x2d, 0x7a, 0x09, 0x60, 0x4d, 0xdd, 0x99, 0xc3, 0xb0, - 0x6f, 0x31, 0x39, 0x5d, 0x4a, 0x95, 0x0b, 0xba, 0x80, 0x20, 0x05, 0xf2, 0x71, 0xde, 0xbd, 0x25, - 0x23, 0xf2, 0x2e, 0x57, 0x24, 0x30, 0x54, 0x01, 0xd4, 0xa3, 0xae, 0x35, 0xe8, 0x5b, 0x3e, 0xc3, - 0x16, 0x63, 0x64, 0xea, 0x31, 0x5f, 0xde, 0xe3, 0xca, 0x27, 0x18, 0xf4, 0x1d, 0x9c, 0x39, 0x64, - 0xc1, 0xf0, 0x23, 0x35, 0x22, 0xf6, 0x70, 0xc4, 0xe4, 0x0c, 0x0f, 0x79, 0x9a, 0x0c, 0xa2, 0x68, - 0x58, 0x04, 0x32, 0xc0, 0x62, 0x0d, 0x0e, 0xc2, 0xa8, 0x27, 0x49, 0xf4, 0x1a, 0xce, 0x1f, 0x89, - 0xc4, 0x4e, 0xb2, 0x3c, 0x6c, 0x0b, 0xab, 0x9c, 0xc3, 0xa9, 0x58, 0x90, 0xb8, 0x17, 0x95, 0xdf, - 0xe0, 0x6c, 0x0d, 0x8f, 0x0a, 0xfc, 0x33, 0x1c, 0x7a, 0x21, 0x81, 0x7d, 0xce, 0xc8, 0x29, 0xde, - 0x8d, 0x17, 0x42, 0x19, 0xc4, 0x48, 0x7d, 0x4d, 0xae, 0xfc, 0x9d, 0x82, 0xc3, 0x37, 0xb3, 0xa9, - 0x27, 0x34, 0xdb, 0x47, 0x75, 0x41, 0x09, 0x72, 0xe1, 0x9e, 0xf9, 0xfe, 0x79, 0x13, 0x14, 0x74, - 0x11, 0xda, 0xa8, 0x65, 0x7a, 0xb3, 0x96, 0xca, 0x31, 0x1c, 0xad, 0x92, 0x08, 0x77, 0xf6, 0xd5, - 0x5f, 0x69, 0xc8, 0x09, 0x0d, 0x84, 0x4e, 0xe0, 0xe8, 0xbe, 0xd3, 0xec, 0x68, 0x0f, 0x1d, 0xfc, - 0x70, 0x67, 0x76, 0x54, 0xc3, 0x90, 0x3e, 0x41, 0x32, 0x9c, 0xd6, 0xb4, 0x76, 0xfb, 0xce, 0x6c, - 0xab, 0x1d, 0x13, 0x9b, 0x77, 0x6d, 0x15, 0xb7, 0xb4, 0x5a, 0x53, 0x4a, 0xa1, 0x0b, 0x38, 0x11, - 0x98, 0x8e, 0x86, 0xeb, 0x6a, 0xeb, 0xe6, 0x9d, 0xb4, 0x83, 0xce, 0xe0, 0x58, 0x20, 0x74, 0xf5, - 0xad, 0xd6, 0x54, 0xa5, 0x74, 0xa0, 0x6f, 0x98, 0xad, 0x1a, 0xd6, 0x6e, 0x6f, 0x55, 0x5d, 0xad, - 0xc7, 0xc4, 0x6e, 0xb0, 0x04, 0x27, 0x6e, 0x6a, 0x35, 0xb5, 0x6b, 0x3e, 0x32, 0x7b, 0xe8, 0x0b, - 0xf8, 0x34, 0x11, 0x12, 0x2c, 0xaf, 0xdd, 0x9b, 0xd8, 0x50, 0x6b, 0x5a, 0xa7, 0x8e, 0x5b, 0xea, - 0x5b, 0xb5, 0x25, 0x65, 0xd0, 0x97, 0xa0, 0x24, 0x0d, 0x8c, 0xfb, 0x5a, 0x4d, 0x35, 0x8c, 0xa4, - 0x6e, 0x1f, 0x5d, 0xc1, 0xf3, 0xb5, 0x0c, 0xda, 0x9a, 0xa9, 0xc6, 0xae, 0xd2, 0x01, 0x2a, 0xc1, - 0xe5, 0x7a, 0x26, 0x5c, 0x11, 0xf9, 0x49, 0x59, 0x74, 0x09, 0x32, 0x57, 0x88, 0xce, 0x71, 0xbe, - 0x80, 0x4e, 0x41, 0x8a, 0x4e, 0x0e, 0x37, 0xd5, 0x77, 0xb8, 0x71, 0x63, 0x34, 0xa4, 0x1c, 0x7a, - 0x0e, 0x17, 0x1d, 0xd5, 0x08, 0xec, 0x36, 0xc8, 0xfc, 0xf5, 0xbf, 0xbb, 0x90, 0x7d, 0xe0, 0x8d, - 0xd4, 0xb4, 0x83, 0x1b, 0x5f, 0xa8, 0x13, 0x6a, 0xff, 0x49, 0x3a, 0x64, 0xc1, 0x9a, 0x64, 0x89, - 0x8e, 0x85, 0x2e, 0x0b, 0x5f, 0x89, 0xe2, 0xf9, 0x6a, 0x0c, 0x36, 0xc9, 0xb2, 0x4e, 0xfc, 0x3e, - 0xb5, 0x3d, 0xe6, 0x52, 0xf4, 0x03, 0x64, 0xc3, 0xd8, 0x20, 0xee, 0x44, 0x14, 0xb5, 0xdc, 0xbe, - 0xc5, 0x5c, 0xba, 0x35, 0xf2, 0x27, 0x38, 0x08, 0xd6, 0x0b, 0xde, 0x08, 0x24, 0x4e, 0x17, 0xe1, - 0x0d, 0x29, 0x5e, 0x6c, 0xe0, 0xd1, 0xfd, 0x68, 0x00, 0x8a, 0x9e, 0x04, 0xf1, 0xfd, 0x10, 0x6d, - 0x04, 0xbc, 0x58, 0x14, 0x6f, 0xcd, 0xda, 0x4b, 0xd2, 0x82, 0x9c, 0x30, 0xc6, 0xd1, 0x0b, 0x41, - 0xba, 0xf9, 0x78, 0x14, 0x5f, 0x6e, 0xa3, 0x1f, 0xdd, 0x84, 0x79, 0x9d, 0x70, 0xdb, 0x1c, 0xff, - 0x09, 0xb7, 0xa7, 0xc6, 0xbc, 0x0e, 0x85, 0xc4, 0x78, 0x40, 0x57, 0x5b, 0xae, 0xff, 0x2a, 0xbf, - 0xd2, 0x76, 0x41, 0xe4, 0xf9, 0x0b, 0xec, 0x47, 0x57, 0x12, 0x3d, 0x13, 0xc4, 0xc9, 0x59, 0x91, - 0x38, 0xb1, 0xb5, 0x1b, 0xfc, 0xe6, 0xdb, 0xdf, 0xab, 0x43, 0x9b, 0x8d, 0x66, 0xbd, 0x4a, 0xdf, - 0x9d, 0x56, 0x27, 0xc1, 0x38, 0x75, 0x6c, 0x67, 0xe8, 0x10, 0x36, 0x77, 0xe9, 0xb8, 0x3a, 0x71, - 0x06, 0x55, 0x3e, 0x56, 0xaa, 0x2b, 0x8b, 0x5e, 0x86, 0xff, 0xe8, 0x78, 0xf5, 0x5f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x2c, 0x7c, 0x20, 0x44, 0xbd, 0x08, 0x00, 0x00, + // 1026 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0x6d, 0x6f, 0xe2, 0xc6, + 0x13, 0xff, 0x13, 0x12, 0x02, 0x03, 0x24, 0xce, 0xe6, 0xc9, 0xc7, 0xe5, 0x2e, 0xfc, 0xdd, 0x07, + 0xa1, 0xb6, 0x02, 0x35, 0xd7, 0x9e, 0xaa, 0xf6, 0x45, 0x9b, 0x23, 0x8e, 0x12, 0x41, 0x30, 0xb5, + 0x9d, 0x4b, 0xaf, 0xaa, 0xb4, 0x72, 0x60, 0x03, 0x16, 0x60, 0xfb, 0xd6, 0x4b, 0x81, 0xb7, 0xfd, + 0x16, 0xfd, 0x00, 0xfd, 0x12, 0xfd, 0x74, 0x95, 0xd7, 0x0f, 0x59, 0xf3, 0x50, 0xa9, 0xaf, 0x82, + 0x7f, 0xbf, 0xdf, 0xcc, 0xce, 0xce, 0xcc, 0xce, 0x04, 0x5e, 0xcc, 0xac, 0xf1, 0x98, 0x30, 0xea, + 0xf5, 0x1a, 0xe1, 0xaf, 0x91, 0xcd, 0xea, 0x1e, 0x75, 0x99, 0x8b, 0x0a, 0x09, 0x55, 0x29, 0x50, + 0xaf, 0x17, 0xa2, 0x95, 0x23, 0xdf, 0x1e, 0x38, 0x81, 0x3c, 0xf8, 0x4b, 0x68, 0x88, 0x2a, 0x3f, + 0x43, 0xae, 0x45, 0x16, 0x3a, 0xf9, 0x88, 0x6a, 0x20, 0x8d, 0xc8, 0x02, 0x3f, 0xd9, 0xce, 0x80, + 0x50, 0xec, 0x51, 0xdb, 0x61, 0x72, 0xa6, 0x9a, 0xa9, 0xed, 0xe8, 0x7b, 0x23, 0xb2, 0xb8, 0xe6, + 0x70, 0x37, 0x40, 0xd1, 0x2b, 0x00, 0xae, 0xb4, 0x26, 0xf6, 0x78, 0x21, 0x6f, 0x71, 0x4d, 0x21, + 0xd0, 0x70, 0x40, 0x29, 0x43, 0xf1, 0xb2, 0xdf, 0xa7, 0x3a, 0xf9, 0x38, 0x25, 0x3e, 0x53, 0x14, + 0x28, 0x85, 0x9f, 0xbe, 0xe7, 0x3a, 0x3e, 0x41, 0x08, 0xb6, 0xad, 0x7e, 0x9f, 0x72, 0xdf, 0x05, + 0x9d, 0xff, 0x56, 0x3e, 0x85, 0xa2, 0x49, 0x2d, 0xc7, 0xb7, 0x7a, 0xcc, 0x76, 0x1d, 0x74, 0x0c, + 0x39, 0x36, 0xc7, 0x43, 0x32, 0xe7, 0xa2, 0x92, 0xbe, 0xc3, 0xe6, 0x37, 0x64, 0xae, 0xbc, 0x85, + 0xfd, 0xee, 0xf4, 0x71, 0x6c, 0xfb, 0xc3, 0xc4, 0xd9, 0x27, 0x50, 0xf6, 0x42, 0x08, 0x13, 0x4a, + 0xdd, 0xd8, 0x6b, 0x29, 0x02, 0xd5, 0x00, 0x53, 0x7e, 0x03, 0x64, 0x10, 0xa7, 0xaf, 0x4d, 0x99, + 0x37, 0x65, 0x7e, 0x14, 0x17, 0x3a, 0x03, 0xf0, 0x2d, 0x86, 0x3d, 0x42, 0xf1, 0x68, 0xc6, 0xed, + 0xb2, 0x7a, 0xde, 0xb7, 0x58, 0x97, 0xd0, 0xd6, 0x0c, 0xd5, 0x60, 0xd7, 0x0d, 0xf5, 0xf2, 0x56, + 0x35, 0x5b, 0x2b, 0x5e, 0xec, 0xd5, 0xa3, 0xfc, 0xd5, 0xcd, 0xb9, 0x36, 0x65, 0x7a, 0x4c, 0x2b, + 0x5f, 0xc1, 0x61, 0xca, 0x7b, 0x14, 0xd9, 0x31, 0xe4, 0xa8, 0x35, 0xc3, 0x2c, 0xb9, 0x03, 0xb5, + 0x66, 0xe6, 0x5c, 0xf9, 0x16, 0x90, 0xea, 0x33, 0x7b, 0x62, 0x31, 0x72, 0x4d, 0x48, 0x1c, 0xcb, + 0x39, 0x14, 0x7b, 0xae, 0xf3, 0x84, 0x99, 0x45, 0x07, 0x24, 0x4e, 0x3b, 0x04, 0x90, 0xc9, 0x11, + 0xe5, 0x0d, 0x1c, 0xa6, 0xcc, 0xa2, 0x43, 0xfe, 0xf5, 0x0e, 0xca, 0x5f, 0x59, 0x28, 0x75, 0x89, + 0xd3, 0xb7, 0x9d, 0x81, 0x31, 0x23, 0xc4, 0x43, 0x5f, 0x42, 0x3e, 0x88, 0xda, 0x8d, 0x4b, 0x5b, + 0xbc, 0xd8, 0xaf, 0x8f, 0xf9, 0x9d, 0xb4, 0x29, 0xeb, 0x06, 0xb0, 0x9e, 0x08, 0xd0, 0xf7, 0x50, + 0x9a, 0xd9, 0xcc, 0x21, 0xbe, 0x8f, 0xd9, 0xc2, 0x23, 0xbc, 0xce, 0x7b, 0x17, 0x27, 0xf5, 0xa4, + 0xb9, 0xea, 0x0f, 0x21, 0x6d, 0x2e, 0x3c, 0xa2, 0xa7, 0xb4, 0xe8, 0x35, 0x80, 0x35, 0x71, 0xa7, + 0x0e, 0xc3, 0xbe, 0xc5, 0xe4, 0x6c, 0x35, 0x53, 0x2b, 0xeb, 0x02, 0x82, 0x14, 0x28, 0xc5, 0x71, + 0x3f, 0x2e, 0x18, 0x91, 0xb7, 0xb9, 0x22, 0x85, 0xa1, 0x3a, 0xa0, 0x47, 0xea, 0x5a, 0xfd, 0x9e, + 0xe5, 0x33, 0x6c, 0x31, 0x46, 0x26, 0x1e, 0xf3, 0xe5, 0x1d, 0xae, 0x5c, 0xc3, 0xa0, 0x6f, 0xe0, + 0xd8, 0x21, 0x73, 0x86, 0x9f, 0xa9, 0x21, 0xb1, 0x07, 0x43, 0x26, 0xe7, 0xb8, 0xc9, 0x7a, 0x32, + 0xb0, 0xa2, 0x61, 0x11, 0x48, 0x1f, 0x8b, 0x35, 0xc8, 0x87, 0x56, 0x6b, 0x49, 0xf4, 0x16, 0x4e, + 0x9e, 0x89, 0xd4, 0x4d, 0x0a, 0xdc, 0x6c, 0x03, 0x8b, 0x8e, 0x60, 0xe7, 0xc9, 0xa5, 0x3d, 0x22, + 0xef, 0x56, 0x33, 0xb5, 0xbc, 0x1e, 0x7e, 0x28, 0x27, 0x70, 0x24, 0x96, 0x29, 0xee, 0x50, 0xe5, + 0x17, 0x38, 0x5e, 0xc2, 0xa3, 0xb2, 0xff, 0x08, 0x7b, 0x5e, 0x48, 0x60, 0x9f, 0x33, 0x72, 0x86, + 0xf7, 0xe8, 0xa9, 0x50, 0x1c, 0xd1, 0x52, 0x5f, 0x92, 0x2b, 0x7f, 0x66, 0x60, 0xef, 0xdd, 0x74, + 0xe2, 0x09, 0x2d, 0xf8, 0x9f, 0x7a, 0xa3, 0x0a, 0xc5, 0x30, 0x13, 0x3c, 0x2b, 0xbc, 0x35, 0xca, + 0xba, 0x08, 0xad, 0x54, 0x38, 0xbb, 0xa6, 0xc2, 0x49, 0x36, 0xb6, 0xc5, 0x6c, 0x1c, 0xc0, 0x7e, + 0x12, 0x5a, 0x78, 0xdf, 0x2f, 0xfe, 0xc8, 0x42, 0x51, 0x68, 0x36, 0x74, 0x08, 0xfb, 0xf7, 0x9d, + 0x56, 0x47, 0x7b, 0xe8, 0xe0, 0x87, 0x5b, 0xb3, 0xa3, 0x1a, 0x86, 0xf4, 0x3f, 0x24, 0xc3, 0x51, + 0x53, 0xbb, 0xbb, 0xbb, 0x35, 0xef, 0xd4, 0x8e, 0x89, 0xcd, 0xdb, 0x3b, 0x15, 0xb7, 0xb5, 0x66, + 0x4b, 0xca, 0xa0, 0x53, 0x38, 0x14, 0x98, 0x8e, 0x86, 0xaf, 0xd4, 0xf6, 0xe5, 0x07, 0x69, 0x0b, + 0x1d, 0xc3, 0x81, 0x40, 0xe8, 0xea, 0x7b, 0xad, 0xa5, 0x4a, 0xd9, 0x40, 0x7f, 0x63, 0xb6, 0x9b, + 0x58, 0xbb, 0xbe, 0x56, 0x75, 0xf5, 0x2a, 0x26, 0xb6, 0x83, 0x23, 0x38, 0x71, 0xd9, 0x6c, 0xaa, + 0x5d, 0xf3, 0x99, 0xd9, 0x41, 0x9f, 0xc1, 0xff, 0x53, 0x26, 0xc1, 0xf1, 0xda, 0xbd, 0x89, 0x0d, + 0xb5, 0xa9, 0x75, 0xae, 0x70, 0x5b, 0x7d, 0xaf, 0xb6, 0xa5, 0x1c, 0xfa, 0x1c, 0x94, 0xb4, 0x03, + 0xe3, 0xbe, 0xd9, 0x54, 0x0d, 0x23, 0xad, 0xdb, 0x45, 0xe7, 0xf0, 0x72, 0x29, 0x82, 0x3b, 0xcd, + 0x54, 0x63, 0xaf, 0x52, 0x1e, 0x55, 0xe1, 0x6c, 0x39, 0x12, 0xae, 0x88, 0xfc, 0x49, 0x05, 0x74, + 0x06, 0x32, 0x57, 0x88, 0x9e, 0xe3, 0x78, 0x01, 0x1d, 0x81, 0x14, 0x65, 0x0e, 0xb7, 0xd4, 0x0f, + 0xf8, 0xe6, 0xd2, 0xb8, 0x91, 0x8a, 0xe8, 0x25, 0x9c, 0x76, 0x54, 0x23, 0x70, 0xb7, 0x42, 0x96, + 0x2e, 0xfe, 0xde, 0x86, 0xc2, 0x03, 0x6f, 0xaf, 0x96, 0x1d, 0x4c, 0x87, 0xf2, 0x15, 0xa1, 0xf6, + 0xef, 0xa4, 0x43, 0xe6, 0xac, 0x45, 0x16, 0xe8, 0x40, 0xe8, 0xbd, 0x70, 0xa3, 0x54, 0x4e, 0x92, + 0x91, 0xd9, 0x22, 0x8b, 0x2b, 0xe2, 0xf7, 0xa8, 0xed, 0x31, 0x97, 0xa2, 0xef, 0xa0, 0x10, 0xda, + 0x06, 0x76, 0x87, 0xa2, 0xa8, 0xed, 0xf6, 0x2c, 0xe6, 0xd2, 0x8d, 0x96, 0x3f, 0x40, 0x3e, 0x38, + 0x2f, 0xd8, 0x27, 0x48, 0x9c, 0x44, 0xc2, 0xbe, 0xa9, 0x9c, 0xae, 0xe0, 0xd1, 0xab, 0xb9, 0x01, + 0x14, 0xad, 0x0f, 0x71, 0xd7, 0x88, 0x6e, 0x04, 0xbc, 0x52, 0x11, 0xdf, 0xd2, 0xd2, 0xd6, 0x69, + 0x43, 0x51, 0x18, 0xf9, 0xe8, 0x95, 0x20, 0x5d, 0x5d, 0x34, 0x95, 0xd7, 0x9b, 0xe8, 0x67, 0x6f, + 0xc2, 0x6c, 0x4f, 0x79, 0x5b, 0x5d, 0x15, 0x29, 0x6f, 0xeb, 0x56, 0x82, 0x0e, 0xe5, 0xd4, 0xd0, + 0x40, 0xe7, 0x1b, 0x86, 0x42, 0x12, 0x5f, 0x75, 0xb3, 0x20, 0xf2, 0xf9, 0x13, 0xec, 0x46, 0x4f, + 0x12, 0xbd, 0x10, 0xc4, 0xe9, 0x09, 0x92, 0xca, 0xd8, 0xd2, 0x0b, 0x7e, 0xf7, 0xf5, 0xaf, 0x8d, + 0x81, 0xcd, 0x86, 0xd3, 0xc7, 0x7a, 0xcf, 0x9d, 0x34, 0xc6, 0xc1, 0xe8, 0x75, 0x6c, 0x67, 0xe0, + 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. diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index 5b5ae694f6..446e79fe32 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -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 { @@ -221,6 +227,12 @@ message BumpFeeRequest { with. */ 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 { diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 7be25ade39..42534c653e 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -416,6 +416,7 @@ func (w *WalletKit) PendingSweeps(ctx context.Context, NextBroadcastHeight: nextBroadcastHeight, RequestedSatPerByte: requestedFeeRate, 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, // lnwallet.ErrNotMine is returned. params := sweep.Params{ - Fee: feePreference, + Fee: feePreference, + Force: in.Force, } _, err = w.cfg.Sweeper.UpdateParams(*op, params) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index b044ec097c..fc65879f66 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -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, diff --git a/sweep/tx_input_set.go b/sweep/tx_input_set.go index 88d749a90a..1f21f9602c 100644 --- a/sweep/tx_input_set.go +++ b/sweep/tx_input_set.go @@ -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 } } diff --git a/sweep/tx_input_set_test.go b/sweep/tx_input_set_test.go index c3b8b0af7a..d9e98f7330 100644 --- a/sweep/tx_input_set_test.go +++ b/sweep/tx_input_set_test.go @@ -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) diff --git a/sweep/txgenerator.go b/sweep/txgenerator.go index 47c8d3c652..229ecf3eb4 100644 --- a/sweep/txgenerator.go +++ b/sweep/txgenerator.go @@ -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()] })