diff --git a/lnrpc/walletrpc/config_active.go b/lnrpc/walletrpc/config_active.go index 6f7a207e..77dc0e57 100644 --- a/lnrpc/walletrpc/config_active.go +++ b/lnrpc/walletrpc/config_active.go @@ -6,6 +6,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/macaroons" + "github.com/lightningnetwork/lnd/sweep" ) // Config is the primary configuration struct for the WalletKit RPC server. It @@ -38,4 +39,8 @@ type Config struct { // KeyRing is an interface that the WalletKit will use to derive any // keys due to incoming client requests. KeyRing keychain.KeyRing + + // Sweeper is the central batching engine of lnd. It is responsible for + // sweeping inputs in batches back into the wallet. + Sweeper *sweep.UtxoSweeper } diff --git a/lnrpc/walletrpc/walletkit.pb.go b/lnrpc/walletrpc/walletkit.pb.go index 60a4a9a9..ffdb00bc 100644 --- a/lnrpc/walletrpc/walletkit.pb.go +++ b/lnrpc/walletrpc/walletkit.pb.go @@ -7,6 +7,7 @@ import ( context "context" fmt "fmt" proto "github.com/golang/protobuf/proto" + lnrpc "github.com/lightningnetwork/lnd/lnrpc" signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc" grpc "google.golang.org/grpc" math "math" @@ -23,6 +24,108 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +type WitnessType int32 + +const ( + WitnessType_UNKNOWN_WITNESS WitnessType = 0 + // + //A witness that allows us to spend the output of a commitment transaction + //after a relative lock-time lockout. + WitnessType_COMMITMENT_TIME_LOCK WitnessType = 1 + // + //A witness that allows us to spend a settled no-delay output immediately on a + //counterparty's commitment transaction. + WitnessType_COMMITMENT_NO_DELAY WitnessType = 2 + // + //A witness that allows us to sweep the settled output of a malicious + //counterparty's who broadcasts a revoked commitment transaction. + WitnessType_COMMITMENT_REVOKE WitnessType = 3 + // + //A witness that allows us to sweep an HTLC which we offered to the remote + //party in the case that they broadcast a revoked commitment state. + WitnessType_HTLC_OFFERED_REVOKE WitnessType = 4 + // + //A witness that allows us to sweep an HTLC output sent to us in the case that + //the remote party broadcasts a revoked commitment state. + WitnessType_HTLC_ACCEPTED_REVOKE WitnessType = 5 + // + //A witness that allows us to sweep an HTLC output that we extended to a + //party, but was never fulfilled. This HTLC output isn't directly on the + //commitment transaction, but is the result of a confirmed second-level HTLC + //transaction. As a result, we can only spend this after a CSV delay. + WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL WitnessType = 6 + // + //A witness that allows us to sweep an HTLC output that was offered to us, and + //for which we have a payment preimage. This HTLC output isn't directly on our + //commitment transaction, but is the result of confirmed second-level HTLC + //transaction. As a result, we can only spend this after a CSV delay. + WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL WitnessType = 7 + // + //A witness that allows us to sweep an HTLC that we offered to the remote + //party which lies in the commitment transaction of the remote party. We can + //spend this output after the absolute CLTV timeout of the HTLC as passed. + WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT WitnessType = 8 + // + //A witness that allows us to sweep an HTLC that was offered to us by the + //remote party. We use this witness in the case that the remote party goes to + //chain, and we know the pre-image to the HTLC. We can sweep this without any + //additional timeout. + WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS WitnessType = 9 + // + //A witness that allows us to sweep an HTLC from the remote party's commitment + //transaction in the case that the broadcast a revoked commitment, but then + //also immediately attempt to go to the second level to claim the HTLC. + WitnessType_HTLC_SECOND_LEVEL_REVOKE WitnessType = 10 + // + //A witness type that allows us to spend a regular p2wkh output that's sent to + //an output which is under complete control of the backing wallet. + WitnessType_WITNESS_KEY_HASH WitnessType = 11 + // + //A witness type that allows us to sweep an output that sends to a nested P2SH + //script that pays to a key solely under our control. + WitnessType_NESTED_WITNESS_KEY_HASH WitnessType = 12 +) + +var WitnessType_name = map[int32]string{ + 0: "UNKNOWN_WITNESS", + 1: "COMMITMENT_TIME_LOCK", + 2: "COMMITMENT_NO_DELAY", + 3: "COMMITMENT_REVOKE", + 4: "HTLC_OFFERED_REVOKE", + 5: "HTLC_ACCEPTED_REVOKE", + 6: "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL", + 7: "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL", + 8: "HTLC_OFFERED_REMOTE_TIMEOUT", + 9: "HTLC_ACCEPTED_REMOTE_SUCCESS", + 10: "HTLC_SECOND_LEVEL_REVOKE", + 11: "WITNESS_KEY_HASH", + 12: "NESTED_WITNESS_KEY_HASH", +} + +var WitnessType_value = map[string]int32{ + "UNKNOWN_WITNESS": 0, + "COMMITMENT_TIME_LOCK": 1, + "COMMITMENT_NO_DELAY": 2, + "COMMITMENT_REVOKE": 3, + "HTLC_OFFERED_REVOKE": 4, + "HTLC_ACCEPTED_REVOKE": 5, + "HTLC_OFFERED_TIMEOUT_SECOND_LEVEL": 6, + "HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL": 7, + "HTLC_OFFERED_REMOTE_TIMEOUT": 8, + "HTLC_ACCEPTED_REMOTE_SUCCESS": 9, + "HTLC_SECOND_LEVEL_REVOKE": 10, + "WITNESS_KEY_HASH": 11, + "NESTED_WITNESS_KEY_HASH": 12, +} + +func (x WitnessType) String() string { + return proto.EnumName(WitnessType_name, int32(x)) +} + +func (WitnessType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{0} +} + type KeyReq struct { //* //Is the key finger print of the root pubkey that this request is targeting. @@ -411,7 +514,170 @@ func (m *EstimateFeeResponse) GetSatPerKw() int64 { return 0 } +type PendingSweep struct { + // The outpoint of the output we're attempting to sweep. + Outpoint *lnrpc.OutPoint `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + // The witness type of the output we're attempting to sweep. + WitnessType WitnessType `protobuf:"varint,2,opt,name=witness_type,proto3,enum=walletrpc.WitnessType" json:"witness_type,omitempty"` + // The value of the output we're attempting to sweep. + AmountSat uint32 `protobuf:"varint,3,opt,name=amount_sat,proto3" json:"amount_sat,omitempty"` + // + //The fee rate we'll use to sweep the output. The fee rate is only determined + //once a sweeping transaction for the output is created, so it's possible for + //this to be 0 before this. + SatPerByte uint32 `protobuf:"varint,4,opt,name=sat_per_byte,proto3" json:"sat_per_byte,omitempty"` + // The number of broadcast attempts we've made to sweep the output. + BroadcastAttempts uint32 `protobuf:"varint,5,opt,name=broadcast_attempts,proto3" json:"broadcast_attempts,omitempty"` + // + //The next height of the chain at which we'll attempt to broadcast the + //sweep transaction of the output. + NextBroadcastHeight uint32 `protobuf:"varint,6,opt,name=next_broadcast_height,proto3" json:"next_broadcast_height,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweep) Reset() { *m = PendingSweep{} } +func (m *PendingSweep) String() string { return proto.CompactTextString(m) } +func (*PendingSweep) ProtoMessage() {} +func (*PendingSweep) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{9} +} + +func (m *PendingSweep) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweep.Unmarshal(m, b) +} +func (m *PendingSweep) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweep.Marshal(b, m, deterministic) +} +func (m *PendingSweep) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweep.Merge(m, src) +} +func (m *PendingSweep) XXX_Size() int { + return xxx_messageInfo_PendingSweep.Size(m) +} +func (m *PendingSweep) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweep.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweep proto.InternalMessageInfo + +func (m *PendingSweep) GetOutpoint() *lnrpc.OutPoint { + if m != nil { + return m.Outpoint + } + return nil +} + +func (m *PendingSweep) GetWitnessType() WitnessType { + if m != nil { + return m.WitnessType + } + return WitnessType_UNKNOWN_WITNESS +} + +func (m *PendingSweep) GetAmountSat() uint32 { + if m != nil { + return m.AmountSat + } + return 0 +} + +func (m *PendingSweep) GetSatPerByte() uint32 { + if m != nil { + return m.SatPerByte + } + return 0 +} + +func (m *PendingSweep) GetBroadcastAttempts() uint32 { + if m != nil { + return m.BroadcastAttempts + } + return 0 +} + +func (m *PendingSweep) GetNextBroadcastHeight() uint32 { + if m != nil { + return m.NextBroadcastHeight + } + return 0 +} + +type PendingSweepsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweepsRequest) Reset() { *m = PendingSweepsRequest{} } +func (m *PendingSweepsRequest) String() string { return proto.CompactTextString(m) } +func (*PendingSweepsRequest) ProtoMessage() {} +func (*PendingSweepsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{10} +} + +func (m *PendingSweepsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweepsRequest.Unmarshal(m, b) +} +func (m *PendingSweepsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweepsRequest.Marshal(b, m, deterministic) +} +func (m *PendingSweepsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweepsRequest.Merge(m, src) +} +func (m *PendingSweepsRequest) XXX_Size() int { + return xxx_messageInfo_PendingSweepsRequest.Size(m) +} +func (m *PendingSweepsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweepsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweepsRequest proto.InternalMessageInfo + +type PendingSweepsResponse struct { + // + //The set of outputs currently being swept by lnd's central batching engine. + PendingSweeps []*PendingSweep `protobuf:"bytes,1,rep,name=pending_sweeps,proto3" json:"pending_sweeps,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PendingSweepsResponse) Reset() { *m = PendingSweepsResponse{} } +func (m *PendingSweepsResponse) String() string { return proto.CompactTextString(m) } +func (*PendingSweepsResponse) ProtoMessage() {} +func (*PendingSweepsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_6cc6942ac78249e5, []int{11} +} + +func (m *PendingSweepsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PendingSweepsResponse.Unmarshal(m, b) +} +func (m *PendingSweepsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PendingSweepsResponse.Marshal(b, m, deterministic) +} +func (m *PendingSweepsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingSweepsResponse.Merge(m, src) +} +func (m *PendingSweepsResponse) XXX_Size() int { + return xxx_messageInfo_PendingSweepsResponse.Size(m) +} +func (m *PendingSweepsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_PendingSweepsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingSweepsResponse proto.InternalMessageInfo + +func (m *PendingSweepsResponse) GetPendingSweeps() []*PendingSweep { + if m != nil { + return m.PendingSweeps + } + return nil +} + func init() { + proto.RegisterEnum("walletrpc.WitnessType", WitnessType_name, WitnessType_value) proto.RegisterType((*KeyReq)(nil), "walletrpc.KeyReq") proto.RegisterType((*AddrRequest)(nil), "walletrpc.AddrRequest") proto.RegisterType((*AddrResponse)(nil), "walletrpc.AddrResponse") @@ -421,45 +687,73 @@ func init() { proto.RegisterType((*SendOutputsResponse)(nil), "walletrpc.SendOutputsResponse") proto.RegisterType((*EstimateFeeRequest)(nil), "walletrpc.EstimateFeeRequest") proto.RegisterType((*EstimateFeeResponse)(nil), "walletrpc.EstimateFeeResponse") + proto.RegisterType((*PendingSweep)(nil), "walletrpc.PendingSweep") + proto.RegisterType((*PendingSweepsRequest)(nil), "walletrpc.PendingSweepsRequest") + proto.RegisterType((*PendingSweepsResponse)(nil), "walletrpc.PendingSweepsResponse") } func init() { proto.RegisterFile("walletrpc/walletkit.proto", fileDescriptor_6cc6942ac78249e5) } var fileDescriptor_6cc6942ac78249e5 = []byte{ - // 524 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0x5d, 0x6f, 0xd3, 0x30, - 0x14, 0xd5, 0x56, 0x56, 0xd6, 0xdb, 0x76, 0x80, 0xcb, 0x46, 0x89, 0x18, 0x4c, 0x81, 0x87, 0x3e, - 0xa0, 0x54, 0x6c, 0x02, 0x21, 0x78, 0x02, 0x6d, 0xd3, 0xa4, 0x4e, 0xac, 0x84, 0x4a, 0x48, 0x08, - 0x29, 0x72, 0x93, 0xbb, 0xd4, 0x6a, 0x6a, 0x67, 0xce, 0x0d, 0x49, 0xfe, 0x0f, 0x3f, 0x14, 0xe5, - 0xa3, 0x5d, 0x4a, 0x19, 0x4f, 0x71, 0x8e, 0xcf, 0x3d, 0xbe, 0xd7, 0xe7, 0x18, 0x9e, 0x26, 0x3c, - 0x08, 0x90, 0x74, 0xe8, 0x0e, 0xcb, 0xd5, 0x5c, 0x90, 0x15, 0x6a, 0x45, 0x8a, 0xb5, 0x56, 0x5b, - 0xc6, 0xe3, 0x48, 0xf8, 0x32, 0xe7, 0xe4, 0x5f, 0xd4, 0x25, 0xc1, 0xfc, 0x0a, 0xcd, 0x11, 0x66, - 0x36, 0xde, 0xb0, 0x01, 0x3c, 0x9c, 0x63, 0xe6, 0x5c, 0x0b, 0xe9, 0xa3, 0x76, 0x42, 0x2d, 0x24, - 0xf5, 0xb7, 0x8e, 0xb6, 0x06, 0x3b, 0xf6, 0xde, 0x1c, 0xb3, 0xf3, 0x02, 0x1e, 0xe7, 0x28, 0x3b, - 0x04, 0x28, 0x98, 0x7c, 0x21, 0x82, 0xac, 0xbf, 0x5d, 0x70, 0x5a, 0x39, 0xa7, 0x00, 0xcc, 0x2e, - 0xb4, 0x3f, 0x79, 0x9e, 0xb6, 0xf1, 0x26, 0xc6, 0x88, 0x4c, 0x13, 0x3a, 0xe5, 0x6f, 0x14, 0x2a, - 0x19, 0x21, 0x63, 0x70, 0x8f, 0x7b, 0x9e, 0x2e, 0xb4, 0x5b, 0x76, 0xb1, 0x36, 0x5f, 0x41, 0x7b, - 0xa2, 0xb9, 0x8c, 0xb8, 0x4b, 0x42, 0x49, 0xb6, 0x0f, 0x4d, 0x4a, 0x9d, 0x19, 0xa6, 0x05, 0xa9, - 0x63, 0xef, 0x50, 0x7a, 0x81, 0xa9, 0xf9, 0x0e, 0x1e, 0x8c, 0xe3, 0x69, 0x20, 0xa2, 0xd9, 0x4a, - 0xec, 0x25, 0x74, 0xc3, 0x12, 0x72, 0x50, 0x6b, 0xb5, 0x54, 0xed, 0x54, 0xe0, 0x59, 0x8e, 0x99, - 0x3f, 0x81, 0x7d, 0x43, 0xe9, 0x5d, 0xc5, 0x14, 0xc6, 0x14, 0x55, 0x7d, 0xb1, 0x67, 0x00, 0x11, - 0x27, 0x27, 0x44, 0xed, 0xcc, 0x93, 0xa2, 0xae, 0x61, 0xef, 0x46, 0x9c, 0xc6, 0xa8, 0x47, 0x09, - 0x1b, 0xc0, 0x7d, 0x55, 0xf2, 0xfb, 0xdb, 0x47, 0x8d, 0x41, 0xfb, 0x78, 0xcf, 0xaa, 0xee, 0xcf, - 0x9a, 0xa4, 0x57, 0x31, 0xd9, 0xcb, 0x6d, 0xf3, 0x35, 0xf4, 0xd6, 0xd4, 0xab, 0xce, 0xf6, 0xa1, - 0xa9, 0x79, 0xe2, 0xd0, 0x6a, 0x06, 0xcd, 0x93, 0x49, 0x6a, 0xbe, 0x05, 0x76, 0x16, 0x91, 0x58, - 0x70, 0xc2, 0x73, 0xc4, 0x65, 0x2f, 0x2f, 0xa0, 0xed, 0x2a, 0x79, 0xed, 0x10, 0xd7, 0x3e, 0x2e, - 0xaf, 0x1d, 0x72, 0x68, 0x52, 0x20, 0xe6, 0x09, 0xf4, 0xd6, 0xca, 0xaa, 0x43, 0xfe, 0x3b, 0xc3, - 0xf1, 0xef, 0x06, 0xb4, 0xbe, 0x17, 0xfe, 0x8f, 0x04, 0xb1, 0x0f, 0xd0, 0x3d, 0x45, 0x2d, 0x7e, - 0xe1, 0x17, 0x4c, 0x69, 0x84, 0x19, 0x7b, 0x64, 0xad, 0xc2, 0x61, 0x95, 0x19, 0x30, 0x0e, 0x56, - 0x43, 0x8e, 0x30, 0x3b, 0xc5, 0xc8, 0xd5, 0x22, 0x24, 0xa5, 0xd9, 0x7b, 0x68, 0x95, 0xb5, 0x79, - 0x5d, 0xaf, 0x4e, 0xba, 0x54, 0x2e, 0x27, 0xa5, 0xef, 0xac, 0xfc, 0x08, 0xbb, 0xf9, 0x79, 0x79, - 0x02, 0xd8, 0x41, 0xed, 0xc0, 0x5a, 0x42, 0x8c, 0x27, 0x1b, 0x78, 0x35, 0xde, 0x05, 0xb0, 0xca, - 0xf0, 0x7a, 0x3a, 0xea, 0x32, 0x35, 0xdc, 0x30, 0x6a, 0xf8, 0xdf, 0x39, 0xb9, 0x84, 0x76, 0xcd, - 0x24, 0x76, 0x58, 0xa3, 0x6e, 0x46, 0xc3, 0x78, 0x7e, 0xd7, 0xf6, 0xad, 0x5a, 0xcd, 0x8d, 0x35, - 0xb5, 0x4d, 0x73, 0xd7, 0xd4, 0xfe, 0x61, 0xe2, 0xe7, 0x37, 0x3f, 0x86, 0xbe, 0xa0, 0x59, 0x3c, - 0xb5, 0x5c, 0xb5, 0x18, 0x06, 0xc2, 0x9f, 0x91, 0x14, 0xd2, 0x97, 0x48, 0x89, 0xd2, 0xf3, 0x61, - 0x20, 0xbd, 0x61, 0x20, 0x6f, 0x1f, 0xb7, 0x0e, 0xdd, 0x69, 0xb3, 0x78, 0xbc, 0x27, 0x7f, 0x02, - 0x00, 0x00, 0xff, 0xff, 0x2d, 0xbb, 0xcd, 0x97, 0xfa, 0x03, 0x00, 0x00, + // 918 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x55, 0x5d, 0x6f, 0xe2, 0x46, + 0x14, 0x2d, 0x21, 0x61, 0xc3, 0x05, 0x12, 0xef, 0x10, 0x12, 0x97, 0xcd, 0x6e, 0xa8, 0xfb, 0x21, + 0xd4, 0x56, 0xa0, 0x66, 0xdb, 0xaa, 0x6a, 0x1f, 0xaa, 0x14, 0x1c, 0x11, 0xf1, 0x61, 0x6a, 0x3b, + 0x9b, 0x6e, 0x55, 0x69, 0x64, 0x60, 0x16, 0x2c, 0xc0, 0x76, 0xc6, 0x43, 0xc1, 0xaf, 0xfd, 0x27, + 0xfd, 0x97, 0x7d, 0xac, 0x3c, 0xb6, 0xc9, 0x98, 0x24, 0xfb, 0x14, 0xe7, 0x9c, 0x73, 0xcf, 0xdc, + 0xb9, 0x33, 0x73, 0x80, 0x4f, 0xd7, 0xd6, 0x62, 0x41, 0x18, 0xf5, 0xc6, 0xcd, 0xe8, 0x6b, 0x6e, + 0xb3, 0x86, 0x47, 0x5d, 0xe6, 0xa2, 0xfc, 0x96, 0xaa, 0xe6, 0xa9, 0x37, 0x8e, 0xd0, 0xea, 0x89, + 0x6f, 0x4f, 0x9d, 0x50, 0x1e, 0xfe, 0x25, 0x34, 0x42, 0x95, 0xdf, 0x21, 0xd7, 0x25, 0x81, 0x4e, + 0xee, 0x51, 0x1d, 0xa4, 0x39, 0x09, 0xf0, 0x07, 0xdb, 0x99, 0x12, 0x8a, 0x3d, 0x6a, 0x3b, 0x4c, + 0xce, 0xd4, 0x32, 0xf5, 0x03, 0xfd, 0x68, 0x4e, 0x82, 0x6b, 0x0e, 0x0f, 0x43, 0x14, 0xbd, 0x06, + 0xe0, 0x4a, 0x6b, 0x69, 0x2f, 0x02, 0x79, 0x8f, 0x6b, 0xf2, 0xa1, 0x86, 0x03, 0x4a, 0x09, 0x0a, + 0x57, 0x93, 0x09, 0xd5, 0xc9, 0xfd, 0x8a, 0xf8, 0x4c, 0x51, 0xa0, 0x18, 0xfd, 0xeb, 0x7b, 0xae, + 0xe3, 0x13, 0x84, 0x60, 0xdf, 0x9a, 0x4c, 0x28, 0xf7, 0xce, 0xeb, 0xfc, 0x5b, 0xf9, 0x02, 0x0a, + 0x26, 0xb5, 0x1c, 0xdf, 0x1a, 0x33, 0xdb, 0x75, 0x50, 0x05, 0x72, 0x6c, 0x83, 0x67, 0x64, 0xc3, + 0x45, 0x45, 0xfd, 0x80, 0x6d, 0x3a, 0x64, 0xa3, 0xfc, 0x08, 0xc7, 0xc3, 0xd5, 0x68, 0x61, 0xfb, + 0xb3, 0xad, 0xd9, 0xe7, 0x50, 0xf2, 0x22, 0x08, 0x13, 0x4a, 0xdd, 0xc4, 0xb5, 0x18, 0x83, 0x6a, + 0x88, 0x29, 0x7f, 0x01, 0x32, 0x88, 0x33, 0xd1, 0x56, 0xcc, 0x5b, 0x31, 0x3f, 0xee, 0x0b, 0x9d, + 0x03, 0xf8, 0x16, 0xc3, 0x1e, 0xa1, 0x78, 0xbe, 0xe6, 0x75, 0x59, 0xfd, 0xd0, 0xb7, 0xd8, 0x90, + 0xd0, 0xee, 0x1a, 0xd5, 0xe1, 0x85, 0x1b, 0xe9, 0xe5, 0xbd, 0x5a, 0xb6, 0x5e, 0xb8, 0x3c, 0x6a, + 0xc4, 0xf3, 0x6b, 0x98, 0x1b, 0x6d, 0xc5, 0xf4, 0x84, 0x56, 0xbe, 0x85, 0x72, 0xca, 0x3d, 0xee, + 0xac, 0x02, 0x39, 0x6a, 0xad, 0x31, 0xdb, 0xee, 0x81, 0x5a, 0x6b, 0x73, 0xa3, 0xfc, 0x00, 0x48, + 0xf5, 0x99, 0xbd, 0xb4, 0x18, 0xb9, 0x26, 0x24, 0xe9, 0xe5, 0x02, 0x0a, 0x63, 0xd7, 0xf9, 0x80, + 0x99, 0x45, 0xa7, 0x24, 0x19, 0x3b, 0x84, 0x90, 0xc9, 0x11, 0xe5, 0x2d, 0x94, 0x53, 0x65, 0xf1, + 0x22, 0x1f, 0xdd, 0x83, 0xf2, 0xef, 0x1e, 0x14, 0x87, 0xc4, 0x99, 0xd8, 0xce, 0xd4, 0x58, 0x13, + 0xe2, 0xa1, 0x6f, 0xe0, 0x30, 0xec, 0xda, 0x4d, 0x8e, 0xb6, 0x70, 0x79, 0xdc, 0x58, 0xf0, 0x3d, + 0x69, 0x2b, 0x36, 0x0c, 0x61, 0x7d, 0x2b, 0x40, 0x3f, 0x43, 0x71, 0x6d, 0x33, 0x87, 0xf8, 0x3e, + 0x66, 0x81, 0x47, 0xf8, 0x39, 0x1f, 0x5d, 0x9e, 0x36, 0xb6, 0x97, 0xab, 0x71, 0x17, 0xd1, 0x66, + 0xe0, 0x11, 0x3d, 0xa5, 0x45, 0x6f, 0x00, 0xac, 0xa5, 0xbb, 0x72, 0x18, 0xf6, 0x2d, 0x26, 0x67, + 0x6b, 0x99, 0x7a, 0x49, 0x17, 0x10, 0xa4, 0x40, 0x31, 0xe9, 0x7b, 0x14, 0x30, 0x22, 0xef, 0x73, + 0x45, 0x0a, 0x43, 0x0d, 0x40, 0x23, 0xea, 0x5a, 0x93, 0xb1, 0xe5, 0x33, 0x6c, 0x31, 0x46, 0x96, + 0x1e, 0xf3, 0xe5, 0x03, 0xae, 0x7c, 0x82, 0x41, 0xdf, 0x43, 0xc5, 0x21, 0x1b, 0x86, 0x1f, 0xa8, + 0x19, 0xb1, 0xa7, 0x33, 0x26, 0xe7, 0x78, 0xc9, 0xd3, 0xa4, 0x72, 0x0a, 0x27, 0xe2, 0x88, 0x92, + 0xdb, 0xa1, 0xfc, 0x01, 0x95, 0x1d, 0x3c, 0x1e, 0xf9, 0xaf, 0x70, 0xe4, 0x45, 0x04, 0xf6, 0x39, + 0x23, 0x67, 0xf8, 0xfd, 0x38, 0x13, 0x06, 0x23, 0x56, 0xea, 0x3b, 0xf2, 0xaf, 0xff, 0xc9, 0x42, + 0x41, 0x98, 0x1c, 0x2a, 0xc3, 0xf1, 0xed, 0xa0, 0x3b, 0xd0, 0xee, 0x06, 0xf8, 0xee, 0xc6, 0x1c, + 0xa8, 0x86, 0x21, 0x7d, 0x82, 0x64, 0x38, 0x69, 0x69, 0xfd, 0xfe, 0x8d, 0xd9, 0x57, 0x07, 0x26, + 0x36, 0x6f, 0xfa, 0x2a, 0xee, 0x69, 0xad, 0xae, 0x94, 0x41, 0x67, 0x50, 0x16, 0x98, 0x81, 0x86, + 0xdb, 0x6a, 0xef, 0xea, 0xbd, 0xb4, 0x87, 0x2a, 0xf0, 0x52, 0x20, 0x74, 0xf5, 0x9d, 0xd6, 0x55, + 0xa5, 0x6c, 0xa8, 0xef, 0x98, 0xbd, 0x16, 0xd6, 0xae, 0xaf, 0x55, 0x5d, 0x6d, 0x27, 0xc4, 0x7e, + 0xb8, 0x04, 0x27, 0xae, 0x5a, 0x2d, 0x75, 0x68, 0x3e, 0x30, 0x07, 0xe8, 0x4b, 0xf8, 0x2c, 0x55, + 0x12, 0x2e, 0xaf, 0xdd, 0x9a, 0xd8, 0x50, 0x5b, 0xda, 0xa0, 0x8d, 0x7b, 0xea, 0x3b, 0xb5, 0x27, + 0xe5, 0xd0, 0x57, 0xa0, 0xa4, 0x0d, 0x8c, 0xdb, 0x56, 0x4b, 0x35, 0x8c, 0xb4, 0xee, 0x05, 0xba, + 0x80, 0x57, 0x3b, 0x1d, 0xf4, 0x35, 0x53, 0x4d, 0x5c, 0xa5, 0x43, 0x54, 0x83, 0xf3, 0xdd, 0x4e, + 0xb8, 0x22, 0xf6, 0x93, 0xf2, 0xe8, 0x1c, 0x64, 0xae, 0x10, 0x9d, 0x93, 0x7e, 0x01, 0x9d, 0x80, + 0x14, 0x4f, 0x0e, 0x77, 0xd5, 0xf7, 0xb8, 0x73, 0x65, 0x74, 0xa4, 0x02, 0x7a, 0x05, 0x67, 0x03, + 0xd5, 0x08, 0xed, 0x1e, 0x91, 0xc5, 0xcb, 0xff, 0xb2, 0x90, 0xbf, 0xe3, 0xe7, 0xd5, 0xb5, 0xc3, + 0xab, 0x5e, 0x6a, 0x13, 0x6a, 0xff, 0x4d, 0x06, 0x64, 0xc3, 0xba, 0x24, 0x40, 0x2f, 0x85, 0xc3, + 0x8c, 0xe2, 0xb1, 0x7a, 0xba, 0x7d, 0xff, 0x5d, 0x12, 0xb4, 0x89, 0x3f, 0xa6, 0xb6, 0xc7, 0x5c, + 0x8a, 0x7e, 0x82, 0x7c, 0x54, 0x1b, 0xd6, 0x95, 0x45, 0x51, 0xcf, 0x1d, 0x5b, 0xcc, 0xa5, 0xcf, + 0x56, 0xfe, 0x02, 0x87, 0xe1, 0x7a, 0x61, 0x38, 0x22, 0xf1, 0x59, 0x09, 0xe1, 0x59, 0x3d, 0x7b, + 0x84, 0xc7, 0xd7, 0xb0, 0x03, 0x28, 0xce, 0x42, 0x31, 0x38, 0x45, 0x1b, 0x01, 0xaf, 0x56, 0xc5, + 0xcb, 0xb9, 0x13, 0xa1, 0x3d, 0x28, 0x08, 0xf9, 0x85, 0x5e, 0x0b, 0xd2, 0xc7, 0xa9, 0x59, 0x7d, + 0xf3, 0x1c, 0xfd, 0xe0, 0x26, 0x04, 0x55, 0xca, 0xed, 0x71, 0xee, 0xa5, 0xdc, 0x9e, 0xca, 0x37, + 0x1d, 0x4a, 0xa9, 0x57, 0x88, 0x2e, 0x9e, 0x79, 0x65, 0xdb, 0xfe, 0x6a, 0xcf, 0x0b, 0x22, 0xcf, + 0xdf, 0xbe, 0xfb, 0xb3, 0x39, 0xb5, 0xd9, 0x6c, 0x35, 0x6a, 0x8c, 0xdd, 0x65, 0x73, 0x11, 0xa6, + 0x80, 0x63, 0x3b, 0x53, 0x87, 0xb0, 0xb5, 0x4b, 0xe7, 0xcd, 0x85, 0x33, 0x69, 0xf2, 0x4c, 0x6c, + 0x6e, 0x8d, 0x46, 0x39, 0xfe, 0x5b, 0xf9, 0xf6, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbf, 0xc4, + 0xea, 0x93, 0x74, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -502,6 +796,16 @@ type WalletKitClient interface { //determine the fee (in sat/kw) to attach to a transaction in order to //achieve the confirmation target. EstimateFee(ctx context.Context, in *EstimateFeeRequest, opts ...grpc.CallOption) (*EstimateFeeResponse, error) + // + //PendingSweeps returns lists of on-chain outputs that lnd is currently + //attempting to sweep within its central batching engine. Outputs with similar + //fee rates are batched together in order to sweep them within a single + //transaction. + // + //NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + //remain supported. This is an advanced API that depends on the internals of + //the UtxoSweeper, so things may change. + PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error) } type walletKitClient struct { @@ -566,6 +870,15 @@ func (c *walletKitClient) EstimateFee(ctx context.Context, in *EstimateFeeReques return out, nil } +func (c *walletKitClient) PendingSweeps(ctx context.Context, in *PendingSweepsRequest, opts ...grpc.CallOption) (*PendingSweepsResponse, error) { + out := new(PendingSweepsResponse) + err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/PendingSweeps", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletKitServer is the server API for WalletKit service. type WalletKitServer interface { //* @@ -596,6 +909,16 @@ type WalletKitServer interface { //determine the fee (in sat/kw) to attach to a transaction in order to //achieve the confirmation target. EstimateFee(context.Context, *EstimateFeeRequest) (*EstimateFeeResponse, error) + // + //PendingSweeps returns lists of on-chain outputs that lnd is currently + //attempting to sweep within its central batching engine. Outputs with similar + //fee rates are batched together in order to sweep them within a single + //transaction. + // + //NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + //remain supported. This is an advanced API that depends on the internals of + //the UtxoSweeper, so things may change. + PendingSweeps(context.Context, *PendingSweepsRequest) (*PendingSweepsResponse, error) } func RegisterWalletKitServer(s *grpc.Server, srv WalletKitServer) { @@ -710,6 +1033,24 @@ func _WalletKit_EstimateFee_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _WalletKit_PendingSweeps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PendingSweepsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletKitServer).PendingSweeps(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/walletrpc.WalletKit/PendingSweeps", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletKitServer).PendingSweeps(ctx, req.(*PendingSweepsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _WalletKit_serviceDesc = grpc.ServiceDesc{ ServiceName: "walletrpc.WalletKit", HandlerType: (*WalletKitServer)(nil), @@ -738,6 +1079,10 @@ var _WalletKit_serviceDesc = grpc.ServiceDesc{ MethodName: "EstimateFee", Handler: _WalletKit_EstimateFee_Handler, }, + { + MethodName: "PendingSweeps", + Handler: _WalletKit_PendingSweeps_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "walletrpc/walletkit.proto", diff --git a/lnrpc/walletrpc/walletkit.proto b/lnrpc/walletrpc/walletkit.proto index e1b0518b..eba00344 100644 --- a/lnrpc/walletrpc/walletkit.proto +++ b/lnrpc/walletrpc/walletkit.proto @@ -1,5 +1,6 @@ syntax = "proto3"; +import "rpc.proto"; import "signrpc/signer.proto"; package walletrpc; @@ -81,6 +82,127 @@ message EstimateFeeResponse { int64 sat_per_kw = 1; } +enum WitnessType { + UNKNOWN_WITNESS = 0; + + /* + A witness that allows us to spend the output of a commitment transaction + after a relative lock-time lockout. + */ + COMMITMENT_TIME_LOCK = 1; + + /* + A witness that allows us to spend a settled no-delay output immediately on a + counterparty's commitment transaction. + */ + COMMITMENT_NO_DELAY = 2; + + /* + A witness that allows us to sweep the settled output of a malicious + counterparty's who broadcasts a revoked commitment transaction. + */ + COMMITMENT_REVOKE = 3; + + /* + A witness that allows us to sweep an HTLC which we offered to the remote + party in the case that they broadcast a revoked commitment state. + */ + HTLC_OFFERED_REVOKE = 4; + + /* + A witness that allows us to sweep an HTLC output sent to us in the case that + the remote party broadcasts a revoked commitment state. + */ + HTLC_ACCEPTED_REVOKE = 5; + + /* + A witness that allows us to sweep an HTLC output that we extended to a + party, but was never fulfilled. This HTLC output isn't directly on the + commitment transaction, but is the result of a confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 6; + + /* + A witness that allows us to sweep an HTLC output that was offered to us, and + for which we have a payment preimage. This HTLC output isn't directly on our + commitment transaction, but is the result of confirmed second-level HTLC + transaction. As a result, we can only spend this after a CSV delay. + */ + HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 7; + + /* + A witness that allows us to sweep an HTLC that we offered to the remote + party which lies in the commitment transaction of the remote party. We can + spend this output after the absolute CLTV timeout of the HTLC as passed. + */ + HTLC_OFFERED_REMOTE_TIMEOUT = 8; + + /* + A witness that allows us to sweep an HTLC that was offered to us by the + remote party. We use this witness in the case that the remote party goes to + chain, and we know the pre-image to the HTLC. We can sweep this without any + additional timeout. + */ + HTLC_ACCEPTED_REMOTE_SUCCESS = 9; + + /* + A witness that allows us to sweep an HTLC from the remote party's commitment + transaction in the case that the broadcast a revoked commitment, but then + also immediately attempt to go to the second level to claim the HTLC. + */ + HTLC_SECOND_LEVEL_REVOKE = 10; + + /* + A witness type that allows us to spend a regular p2wkh output that's sent to + an output which is under complete control of the backing wallet. + */ + WITNESS_KEY_HASH = 11; + + /* + A witness type that allows us to sweep an output that sends to a nested P2SH + script that pays to a key solely under our control. + */ + NESTED_WITNESS_KEY_HASH = 12; +} + +message PendingSweep { + // The outpoint of the output we're attempting to sweep. + lnrpc.OutPoint outpoint = 1 [json_name = "outpoint"]; + + // The witness type of the output we're attempting to sweep. + WitnessType witness_type = 2 [json_name = "witness_type"]; + + // The value of the output we're attempting to sweep. + uint32 amount_sat = 3 [json_name = "amount_sat"]; + + /* + The fee rate we'll use to sweep the output. The fee rate is only determined + once a sweeping transaction for the output is created, so it's possible for + this to be 0 before this. + */ + uint32 sat_per_byte = 4 [json_name = "sat_per_byte"]; + + // The number of broadcast attempts we've made to sweep the output. + uint32 broadcast_attempts = 5 [json_name = "broadcast_attempts"]; + + /* + The next height of the chain at which we'll attempt to broadcast the + sweep transaction of the output. + */ + uint32 next_broadcast_height = 6 [json_name = "next_broadcast_height"]; +} + +message PendingSweepsRequest { +} + +message PendingSweepsResponse { + /* + The set of outputs currently being swept by lnd's central batching engine. + */ + repeated PendingSweep pending_sweeps = 1 [json_name = "pending_sweeps"]; +} + service WalletKit { /** DeriveNextKey attempts to derive the *next* key within the key family @@ -121,4 +243,16 @@ service WalletKit { achieve the confirmation target. */ rpc EstimateFee(EstimateFeeRequest) returns (EstimateFeeResponse); + + /* + PendingSweeps returns lists of on-chain outputs that lnd is currently + attempting to sweep within its central batching engine. Outputs with similar + fee rates are batched together in order to sweep them within a single + transaction. + + NOTE: Some of the fields within PendingSweepsRequest are not guaranteed to + remain supported. This is an advanced API that depends on the internals of + the UtxoSweeper, so things may change. + */ + rpc PendingSweeps(PendingSweepsRequest) returns (PendingSweepsResponse); } diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 3e998fe2..f2e04f59 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -10,6 +10,7 @@ import ( "path/filepath" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" @@ -74,6 +75,10 @@ var ( Entity: "onchain", Action: "read", }}, + "/walletrpc.WalletKit/PendingSweeps": {{ + Entity: "onchain", + Action: "read", + }}, } // DefaultWalletKitMacFilename is the default name of the wallet kit @@ -331,3 +336,76 @@ func (w *WalletKit) EstimateFee(ctx context.Context, SatPerKw: int64(satPerKw), }, nil } + +// PendingSweeps returns lists of on-chain outputs that lnd is currently +// attempting to sweep within its central batching engine. Outputs with similar +// fee rates are batched together in order to sweep them within a single +// transaction. The fee rate of each sweeping transaction is determined by +// taking the average fee rate of all the outputs it's trying to sweep. +func (w *WalletKit) PendingSweeps(ctx context.Context, + in *PendingSweepsRequest) (*PendingSweepsResponse, error) { + + // Retrieve all of the outputs the UtxoSweeper is currently trying to + // sweep. + pendingInputs, err := w.cfg.Sweeper.PendingInputs() + if err != nil { + return nil, err + } + + // Convert them into their respective RPC format. + rpcPendingSweeps := make([]*PendingSweep, 0, len(pendingInputs)) + for _, pendingInput := range pendingInputs { + var witnessType WitnessType + switch pendingInput.WitnessType { + case input.CommitmentTimeLock: + witnessType = WitnessType_COMMITMENT_TIME_LOCK + case input.CommitmentNoDelay: + witnessType = WitnessType_COMMITMENT_NO_DELAY + case input.CommitmentRevoke: + witnessType = WitnessType_COMMITMENT_REVOKE + case input.HtlcOfferedRevoke: + witnessType = WitnessType_HTLC_OFFERED_REVOKE + case input.HtlcAcceptedRevoke: + witnessType = WitnessType_HTLC_ACCEPTED_REVOKE + case input.HtlcOfferedTimeoutSecondLevel: + witnessType = WitnessType_HTLC_OFFERED_TIMEOUT_SECOND_LEVEL + case input.HtlcAcceptedSuccessSecondLevel: + witnessType = WitnessType_HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL + case input.HtlcOfferedRemoteTimeout: + witnessType = WitnessType_HTLC_OFFERED_REMOTE_TIMEOUT + case input.HtlcAcceptedRemoteSuccess: + witnessType = WitnessType_HTLC_ACCEPTED_REMOTE_SUCCESS + case input.HtlcSecondLevelRevoke: + witnessType = WitnessType_HTLC_SECOND_LEVEL_REVOKE + case input.WitnessKeyHash: + witnessType = WitnessType_WITNESS_KEY_HASH + case input.NestedWitnessKeyHash: + witnessType = WitnessType_NESTED_WITNESS_KEY_HASH + default: + log.Warnf("Unhandled witness type %v for input %v", + pendingInput.WitnessType, pendingInput.OutPoint) + } + + op := &lnrpc.OutPoint{ + TxidBytes: pendingInput.OutPoint.Hash[:], + OutputIndex: pendingInput.OutPoint.Index, + } + amountSat := uint32(pendingInput.Amount) + satPerByte := uint32(pendingInput.LastFeeRate.FeePerKVByte() / 1000) + broadcastAttempts := uint32(pendingInput.BroadcastAttempts) + nextBroadcastHeight := uint32(pendingInput.NextBroadcastHeight) + + rpcPendingSweeps = append(rpcPendingSweeps, &PendingSweep{ + Outpoint: op, + WitnessType: witnessType, + AmountSat: amountSat, + SatPerByte: satPerByte, + BroadcastAttempts: broadcastAttempts, + NextBroadcastHeight: nextBroadcastHeight, + }) + } + + return &PendingSweepsResponse{ + PendingSweeps: rpcPendingSweeps, + }, nil +} diff --git a/rpcserver.go b/rpcserver.go index b827b65b..dca0a93a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -491,7 +491,7 @@ func newRPCServer(s *server, macService *macaroons.Service, err = subServerCgs.PopulateDependencies( s.cc, networkDir, macService, atpl, invoiceRegistry, s.htlcSwitch, activeNetParams.Params, s.chanRouter, - routerBackend, s.nodeSigner, s.chanDB, + routerBackend, s.nodeSigner, s.chanDB, s.sweeper, ) if err != nil { return nil, err diff --git a/subrpcserver_config.go b/subrpcserver_config.go index d99faeab..cb9b2076 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -18,6 +18,7 @@ import ( "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/sweep" ) // subRPCServerConfigs is special sub-config in the main configuration that @@ -72,7 +73,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, chanRouter *routing.ChannelRouter, routerBackend *routerrpc.RouterBackend, nodeSigner *netann.NodeSigner, - chanDB *channeldb.DB) error { + chanDB *channeldb.DB, + sweeper *sweep.UtxoSweeper) error { // First, we'll use reflect to obtain a version of the config struct // that allows us to programmatically inspect its fields. @@ -129,6 +131,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, subCfgValue.FieldByName("KeyRing").Set( reflect.ValueOf(cc.keyRing), ) + subCfgValue.FieldByName("Sweeper").Set( + reflect.ValueOf(sweeper), + ) case *autopilotrpc.Config: subCfgValue := extractReflectValue(subCfg)