diff --git a/docs/release-notes/release-notes-0.22.0.md b/docs/release-notes/release-notes-0.22.0.md index 260c099f1f2..dc784cfb65e 100644 --- a/docs/release-notes/release-notes-0.22.0.md +++ b/docs/release-notes/release-notes-0.22.0.md @@ -77,6 +77,14 @@ ## RPC Updates +* The `ChainNotifier` confirmation and spend notification streams are now + [richer at the re-org boundary](https://github.com/lightningnetwork/lnd/pull/10943): + the `Reorg` message carries the re-org `depth` (populated for confirmation + notifications), and a new `Done` event is sent once a watch reaches the + backend's re-org safety depth. Previously the re-org depth was dropped and the + done signal was conveyed only by closing the stream, which callers could not + distinguish from an unrelated teardown. + ## lncli Updates ## Breaking Changes diff --git a/lnrpc/chainrpc/chain_server.go b/lnrpc/chainrpc/chain_server.go index 690cd817727..1b1ba135b6c 100644 --- a/lnrpc/chainrpc/chain_server.go +++ b/lnrpc/chainrpc/chain_server.go @@ -439,27 +439,40 @@ func (s *Server) RegisterConfirmationsNtfn(in *ConfRequest, } // The transaction satisfying the request has been reorged out - // of the chain, so we'll send an event describing it. - case _, ok := <-confEvent.NegativeConf: + // of the chain, so we'll send an event describing it. The depth + // of the re-org is forwarded so reorg-safety-aware callers can + // reason about how far the chain rewound. + case depth, ok := <-confEvent.NegativeConf: if !ok { return chainntnfs.ErrChainNotifierShuttingDown } reorg := &ConfEvent{ - Event: &ConfEvent_Reorg{Reorg: &Reorg{}}, + Event: &ConfEvent_Reorg{ + Reorg: &Reorg{Depth: uint32(depth)}, + }, } if err := confStream.Send(reorg); err != nil { return err } // The transaction satisfying the request has confirmed and is - // no longer under the risk of being reorged out of the chain, - // so we can safely exit. + // no longer under the risk of being reorged out of the chain. + // Send an explicit Done event before exiting so callers can + // distinguish reaching re-org safety depth from the stream + // being torn down for any other reason. case _, ok := <-confEvent.Done: if !ok { return chainntnfs.ErrChainNotifierShuttingDown } + done := &ConfEvent{ + Event: &ConfEvent_Done{Done: &Done{}}, + } + if err := confStream.Send(done); err != nil { + return err + } + return nil // The response stream's context for whatever reason has been @@ -565,12 +578,21 @@ func (s *Server) RegisterSpendNtfn(in *SpendRequest, // The spending transaction of the requests has confirmed // on-chain and is no longer under the risk of being reorged out - // of the chain, so we can safely exit. + // of the chain. Send an explicit Done event before exiting so + // callers can distinguish reaching re-org safety depth from the + // stream being torn down for any other reason. case _, ok := <-spendEvent.Done: if !ok { return chainntnfs.ErrChainNotifierShuttingDown } + done := &SpendEvent{ + Event: &SpendEvent_Done{Done: &Done{}}, + } + if err := spendStream.Send(done); err != nil { + return err + } + return nil // The response stream's context for whatever reason has been diff --git a/lnrpc/chainrpc/chainnotifier.pb.go b/lnrpc/chainrpc/chainnotifier.pb.go index 41785b60581..721a6e47e32 100644 --- a/lnrpc/chainrpc/chainnotifier.pb.go +++ b/lnrpc/chainrpc/chainnotifier.pb.go @@ -195,7 +195,12 @@ func (x *ConfDetails) GetRawBlock() []byte { } type Reorg struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // The depth of the re-org, i.e. the number of blocks by which the + // transaction/spend was reorged out of the chain. Only populated for + // confirmation notifications; spend notifications do not currently track + // re-org depth and leave this field as 0. + Depth uint32 `protobuf:"varint,1,opt,name=depth,proto3" json:"depth,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -230,12 +235,56 @@ func (*Reorg) Descriptor() ([]byte, []int) { return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{2} } +func (x *Reorg) GetDepth() uint32 { + if x != nil { + return x.Depth + } + return 0 +} + +type Done struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Done) Reset() { + *x = Done{} + mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Done) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Done) ProtoMessage() {} + +func (x *Done) ProtoReflect() protoreflect.Message { + mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Done.ProtoReflect.Descriptor instead. +func (*Done) Descriptor() ([]byte, []int) { + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{3} +} + type ConfEvent struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Event: // // *ConfEvent_Conf // *ConfEvent_Reorg + // *ConfEvent_Done Event isConfEvent_Event `protobuf_oneof:"event"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -243,7 +292,7 @@ type ConfEvent struct { func (x *ConfEvent) Reset() { *x = ConfEvent{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -255,7 +304,7 @@ func (x *ConfEvent) String() string { func (*ConfEvent) ProtoMessage() {} func (x *ConfEvent) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[3] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -268,7 +317,7 @@ func (x *ConfEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfEvent.ProtoReflect.Descriptor instead. func (*ConfEvent) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{3} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{4} } func (x *ConfEvent) GetEvent() isConfEvent_Event { @@ -296,6 +345,15 @@ func (x *ConfEvent) GetReorg() *Reorg { return nil } +func (x *ConfEvent) GetDone() *Done { + if x != nil { + if x, ok := x.Event.(*ConfEvent_Done); ok { + return x.Done + } + } + return nil +} + type isConfEvent_Event interface { isConfEvent_Event() } @@ -312,10 +370,19 @@ type ConfEvent_Reorg struct { Reorg *Reorg `protobuf:"bytes,2,opt,name=reorg,proto3,oneof"` } +type ConfEvent_Done struct { + // An event sent once the confirmation is no longer under the risk of + // being reorged out of the chain (the watch has reached re-org safety + // depth). + Done *Done `protobuf:"bytes,3,opt,name=done,proto3,oneof"` +} + func (*ConfEvent_Conf) isConfEvent_Event() {} func (*ConfEvent_Reorg) isConfEvent_Event() {} +func (*ConfEvent_Done) isConfEvent_Event() {} + type Outpoint struct { state protoimpl.MessageState `protogen:"open.v1"` // The hash of the transaction. @@ -328,7 +395,7 @@ type Outpoint struct { func (x *Outpoint) Reset() { *x = Outpoint{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -340,7 +407,7 @@ func (x *Outpoint) String() string { func (*Outpoint) ProtoMessage() {} func (x *Outpoint) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[4] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -353,7 +420,7 @@ func (x *Outpoint) ProtoReflect() protoreflect.Message { // Deprecated: Use Outpoint.ProtoReflect.Descriptor instead. func (*Outpoint) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{4} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{5} } func (x *Outpoint) GetHash() []byte { @@ -394,7 +461,7 @@ type SpendRequest struct { func (x *SpendRequest) Reset() { *x = SpendRequest{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -406,7 +473,7 @@ func (x *SpendRequest) String() string { func (*SpendRequest) ProtoMessage() {} func (x *SpendRequest) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[5] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -419,7 +486,7 @@ func (x *SpendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SpendRequest.ProtoReflect.Descriptor instead. func (*SpendRequest) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{5} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{6} } func (x *SpendRequest) GetOutpoint() *Outpoint { @@ -461,7 +528,7 @@ type SpendDetails struct { func (x *SpendDetails) Reset() { *x = SpendDetails{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -473,7 +540,7 @@ func (x *SpendDetails) String() string { func (*SpendDetails) ProtoMessage() {} func (x *SpendDetails) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[6] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -486,7 +553,7 @@ func (x *SpendDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use SpendDetails.ProtoReflect.Descriptor instead. func (*SpendDetails) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{6} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{7} } func (x *SpendDetails) GetSpendingOutpoint() *Outpoint { @@ -530,6 +597,7 @@ type SpendEvent struct { // // *SpendEvent_Spend // *SpendEvent_Reorg + // *SpendEvent_Done Event isSpendEvent_Event `protobuf_oneof:"event"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -537,7 +605,7 @@ type SpendEvent struct { func (x *SpendEvent) Reset() { *x = SpendEvent{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -549,7 +617,7 @@ func (x *SpendEvent) String() string { func (*SpendEvent) ProtoMessage() {} func (x *SpendEvent) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[7] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -562,7 +630,7 @@ func (x *SpendEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use SpendEvent.ProtoReflect.Descriptor instead. func (*SpendEvent) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{7} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{8} } func (x *SpendEvent) GetEvent() isSpendEvent_Event { @@ -590,6 +658,15 @@ func (x *SpendEvent) GetReorg() *Reorg { return nil } +func (x *SpendEvent) GetDone() *Done { + if x != nil { + if x, ok := x.Event.(*SpendEvent_Done); ok { + return x.Done + } + } + return nil +} + type isSpendEvent_Event interface { isSpendEvent_Event() } @@ -606,10 +683,18 @@ type SpendEvent_Reorg struct { Reorg *Reorg `protobuf:"bytes,2,opt,name=reorg,proto3,oneof"` } +type SpendEvent_Done struct { + // An event sent once the spend is no longer under the risk of being + // reorged out of the chain (the watch has reached re-org safety depth). + Done *Done `protobuf:"bytes,3,opt,name=done,proto3,oneof"` +} + func (*SpendEvent_Spend) isSpendEvent_Event() {} func (*SpendEvent_Reorg) isSpendEvent_Event() {} +func (*SpendEvent_Done) isSpendEvent_Event() {} + type BlockEpoch struct { state protoimpl.MessageState `protogen:"open.v1"` // The hash of the block. @@ -622,7 +707,7 @@ type BlockEpoch struct { func (x *BlockEpoch) Reset() { *x = BlockEpoch{} - mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +719,7 @@ func (x *BlockEpoch) String() string { func (*BlockEpoch) ProtoMessage() {} func (x *BlockEpoch) ProtoReflect() protoreflect.Message { - mi := &file_chainrpc_chainnotifier_proto_msgTypes[8] + mi := &file_chainrpc_chainnotifier_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +732,7 @@ func (x *BlockEpoch) ProtoReflect() protoreflect.Message { // Deprecated: Use BlockEpoch.ProtoReflect.Descriptor instead. func (*BlockEpoch) Descriptor() ([]byte, []int) { - return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{8} + return file_chainrpc_chainnotifier_proto_rawDescGZIP(), []int{9} } func (x *BlockEpoch) GetHash() []byte { @@ -682,11 +767,14 @@ const file_chainrpc_chainnotifier_proto_rawDesc = "" + "block_hash\x18\x02 \x01(\fR\tblockHash\x12!\n" + "\fblock_height\x18\x03 \x01(\rR\vblockHeight\x12\x19\n" + "\btx_index\x18\x04 \x01(\rR\atxIndex\x12\x1b\n" + - "\traw_block\x18\x05 \x01(\fR\brawBlock\"\a\n" + - "\x05Reorg\"j\n" + + "\traw_block\x18\x05 \x01(\fR\brawBlock\"\x1d\n" + + "\x05Reorg\x12\x14\n" + + "\x05depth\x18\x01 \x01(\rR\x05depth\"\x06\n" + + "\x04Done\"\x90\x01\n" + "\tConfEvent\x12+\n" + "\x04conf\x18\x01 \x01(\v2\x15.chainrpc.ConfDetailsH\x00R\x04conf\x12'\n" + - "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorgB\a\n" + + "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorg\x12$\n" + + "\x04done\x18\x03 \x01(\v2\x0e.chainrpc.DoneH\x00R\x04doneB\a\n" + "\x05event\"4\n" + "\bOutpoint\x12\x12\n" + "\x04hash\x18\x01 \x01(\fR\x04hash\x12\x14\n" + @@ -701,11 +789,12 @@ const file_chainrpc_chainnotifier_proto_rawDesc = "" + "\x0fraw_spending_tx\x18\x02 \x01(\fR\rrawSpendingTx\x12(\n" + "\x10spending_tx_hash\x18\x03 \x01(\fR\x0espendingTxHash\x120\n" + "\x14spending_input_index\x18\x04 \x01(\rR\x12spendingInputIndex\x12'\n" + - "\x0fspending_height\x18\x05 \x01(\rR\x0espendingHeight\"n\n" + + "\x0fspending_height\x18\x05 \x01(\rR\x0espendingHeight\"\x94\x01\n" + "\n" + "SpendEvent\x12.\n" + "\x05spend\x18\x01 \x01(\v2\x16.chainrpc.SpendDetailsH\x00R\x05spend\x12'\n" + - "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorgB\a\n" + + "\x05reorg\x18\x02 \x01(\v2\x0f.chainrpc.ReorgH\x00R\x05reorg\x12$\n" + + "\x04done\x18\x03 \x01(\v2\x0e.chainrpc.DoneH\x00R\x04doneB\a\n" + "\x05event\"8\n" + "\n" + "BlockEpoch\x12\x12\n" + @@ -728,36 +817,39 @@ func file_chainrpc_chainnotifier_proto_rawDescGZIP() []byte { return file_chainrpc_chainnotifier_proto_rawDescData } -var file_chainrpc_chainnotifier_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_chainrpc_chainnotifier_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_chainrpc_chainnotifier_proto_goTypes = []any{ (*ConfRequest)(nil), // 0: chainrpc.ConfRequest (*ConfDetails)(nil), // 1: chainrpc.ConfDetails (*Reorg)(nil), // 2: chainrpc.Reorg - (*ConfEvent)(nil), // 3: chainrpc.ConfEvent - (*Outpoint)(nil), // 4: chainrpc.Outpoint - (*SpendRequest)(nil), // 5: chainrpc.SpendRequest - (*SpendDetails)(nil), // 6: chainrpc.SpendDetails - (*SpendEvent)(nil), // 7: chainrpc.SpendEvent - (*BlockEpoch)(nil), // 8: chainrpc.BlockEpoch + (*Done)(nil), // 3: chainrpc.Done + (*ConfEvent)(nil), // 4: chainrpc.ConfEvent + (*Outpoint)(nil), // 5: chainrpc.Outpoint + (*SpendRequest)(nil), // 6: chainrpc.SpendRequest + (*SpendDetails)(nil), // 7: chainrpc.SpendDetails + (*SpendEvent)(nil), // 8: chainrpc.SpendEvent + (*BlockEpoch)(nil), // 9: chainrpc.BlockEpoch } var file_chainrpc_chainnotifier_proto_depIdxs = []int32{ - 1, // 0: chainrpc.ConfEvent.conf:type_name -> chainrpc.ConfDetails - 2, // 1: chainrpc.ConfEvent.reorg:type_name -> chainrpc.Reorg - 4, // 2: chainrpc.SpendRequest.outpoint:type_name -> chainrpc.Outpoint - 4, // 3: chainrpc.SpendDetails.spending_outpoint:type_name -> chainrpc.Outpoint - 6, // 4: chainrpc.SpendEvent.spend:type_name -> chainrpc.SpendDetails - 2, // 5: chainrpc.SpendEvent.reorg:type_name -> chainrpc.Reorg - 0, // 6: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:input_type -> chainrpc.ConfRequest - 5, // 7: chainrpc.ChainNotifier.RegisterSpendNtfn:input_type -> chainrpc.SpendRequest - 8, // 8: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:input_type -> chainrpc.BlockEpoch - 3, // 9: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:output_type -> chainrpc.ConfEvent - 7, // 10: chainrpc.ChainNotifier.RegisterSpendNtfn:output_type -> chainrpc.SpendEvent - 8, // 11: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:output_type -> chainrpc.BlockEpoch - 9, // [9:12] is the sub-list for method output_type - 6, // [6:9] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 1, // 0: chainrpc.ConfEvent.conf:type_name -> chainrpc.ConfDetails + 2, // 1: chainrpc.ConfEvent.reorg:type_name -> chainrpc.Reorg + 3, // 2: chainrpc.ConfEvent.done:type_name -> chainrpc.Done + 5, // 3: chainrpc.SpendRequest.outpoint:type_name -> chainrpc.Outpoint + 5, // 4: chainrpc.SpendDetails.spending_outpoint:type_name -> chainrpc.Outpoint + 7, // 5: chainrpc.SpendEvent.spend:type_name -> chainrpc.SpendDetails + 2, // 6: chainrpc.SpendEvent.reorg:type_name -> chainrpc.Reorg + 3, // 7: chainrpc.SpendEvent.done:type_name -> chainrpc.Done + 0, // 8: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:input_type -> chainrpc.ConfRequest + 6, // 9: chainrpc.ChainNotifier.RegisterSpendNtfn:input_type -> chainrpc.SpendRequest + 9, // 10: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:input_type -> chainrpc.BlockEpoch + 4, // 11: chainrpc.ChainNotifier.RegisterConfirmationsNtfn:output_type -> chainrpc.ConfEvent + 8, // 12: chainrpc.ChainNotifier.RegisterSpendNtfn:output_type -> chainrpc.SpendEvent + 9, // 13: chainrpc.ChainNotifier.RegisterBlockEpochNtfn:output_type -> chainrpc.BlockEpoch + 11, // [11:14] is the sub-list for method output_type + 8, // [8:11] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_chainrpc_chainnotifier_proto_init() } @@ -765,13 +857,15 @@ func file_chainrpc_chainnotifier_proto_init() { if File_chainrpc_chainnotifier_proto != nil { return } - file_chainrpc_chainnotifier_proto_msgTypes[3].OneofWrappers = []any{ + file_chainrpc_chainnotifier_proto_msgTypes[4].OneofWrappers = []any{ (*ConfEvent_Conf)(nil), (*ConfEvent_Reorg)(nil), + (*ConfEvent_Done)(nil), } - file_chainrpc_chainnotifier_proto_msgTypes[7].OneofWrappers = []any{ + file_chainrpc_chainnotifier_proto_msgTypes[8].OneofWrappers = []any{ (*SpendEvent_Spend)(nil), (*SpendEvent_Reorg)(nil), + (*SpendEvent_Done)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -779,7 +873,7 @@ func file_chainrpc_chainnotifier_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_chainrpc_chainnotifier_proto_rawDesc), len(file_chainrpc_chainnotifier_proto_rawDesc)), NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/lnrpc/chainrpc/chainnotifier.proto b/lnrpc/chainrpc/chainnotifier.proto index 36e66a279ef..1c1e5c99000 100644 --- a/lnrpc/chainrpc/chainnotifier.proto +++ b/lnrpc/chainrpc/chainnotifier.proto @@ -102,7 +102,22 @@ message ConfDetails { } message Reorg { - // TODO(wilmer): need to know how the client will use this first. + /* + The depth of the re-org, i.e. the number of blocks by which the + transaction/spend was reorged out of the chain. Only populated for + confirmation notifications; spend notifications do not currently track + re-org depth and leave this field as 0. + */ + uint32 depth = 1; +} + +message Done { + /* + Done is sent once the confirmation/spend notification is no longer under + the risk of being reorged out of the chain, i.e. it has reached the + backend's re-org safety depth. It carries no payload: receiving it means + the notification stream is complete and no further events will be sent. + */ } message ConfEvent { @@ -118,6 +133,13 @@ message ConfEvent { chain. */ Reorg reorg = 2; + + /* + An event sent once the confirmation is no longer under the risk of + being reorged out of the chain (the watch has reached re-org safety + depth). + */ + Done done = 3; } } @@ -188,6 +210,12 @@ message SpendEvent { reorged out of the chain. */ Reorg reorg = 2; + + /* + An event sent once the spend is no longer under the risk of being + reorged out of the chain (the watch has reached re-org safety depth). + */ + Done done = 3; } } diff --git a/lnrpc/chainrpc/chainnotifier.swagger.json b/lnrpc/chainrpc/chainnotifier.swagger.json index b8e2924cdb7..9b151dbc654 100644 --- a/lnrpc/chainrpc/chainnotifier.swagger.json +++ b/lnrpc/chainrpc/chainnotifier.swagger.json @@ -202,6 +202,10 @@ "reorg": { "$ref": "#/definitions/chainrpcReorg", "description": "An event send when the transaction of the request is reorged out of the\nchain." + }, + "done": { + "$ref": "#/definitions/chainrpcDone", + "description": "An event sent once the confirmation is no longer under the risk of\nbeing reorged out of the chain (the watch has reached re-org safety\ndepth)." } } }, @@ -234,6 +238,10 @@ } } }, + "chainrpcDone": { + "type": "object", + "description": "Done is sent once the confirmation/spend notification is no longer under\nthe risk of being reorged out of the chain, i.e. it has reached the\nbackend's re-org safety depth. It carries no payload: receiving it means\nthe notification stream is complete and no further events will be sent." + }, "chainrpcOutpoint": { "type": "object", "properties": { @@ -251,7 +259,13 @@ }, "chainrpcReorg": { "type": "object", - "description": "TODO(wilmer): need to know how the client will use this first." + "properties": { + "depth": { + "type": "integer", + "format": "int64", + "description": "The depth of the re-org, i.e. the number of blocks by which the\ntransaction/spend was reorged out of the chain. Only populated for\nconfirmation notifications; spend notifications do not currently track\nre-org depth and leave this field as 0." + } + } }, "chainrpcSpendDetails": { "type": "object", @@ -292,6 +306,10 @@ "reorg": { "$ref": "#/definitions/chainrpcReorg", "description": "An event sent when the spending transaction of the request was\nreorged out of the chain." + }, + "done": { + "$ref": "#/definitions/chainrpcDone", + "description": "An event sent once the spend is no longer under the risk of being\nreorged out of the chain (the watch has reached re-org safety depth)." } } },