From c52b870c8a9f6a4715f26607d6e384cb41452c2d Mon Sep 17 00:00:00 2001 From: Ihor Diachenko Date: Tue, 3 Mar 2026 17:22:02 +0200 Subject: [PATCH 1/5] Added signature field to proto --- libs/gl-client-py/glclient/greenlight.proto | 1 + libs/gl-client-py/glclient/greenlight_pb2.py | 68 +- libs/gl-client-py/glclient/greenlight_pb2.pyi | 5 +- libs/gl-client-py/glclient/scheduler_pb2.pyi | 993 +++++++++--------- .../glclient/scheduler_pb2_grpc.py | 2 +- .../glclient/scheduler_pb2_grpc.pyi | 368 +++---- .../proto/glclient/greenlight.proto | 1 + libs/gl-client/src/persist.rs | 1 + .../proto/glclient/greenlight.proto | 1 + libs/gl-plugin/src/node/mod.rs | 3 + libs/gl-sdk/glsdk/glsdk.py | 16 +- .../proto/glclient/greenlight.proto | 78 +- libs/proto/glclient/greenlight.proto | 1 + 13 files changed, 790 insertions(+), 748 deletions(-) diff --git a/libs/gl-client-py/glclient/greenlight.proto b/libs/gl-client-py/glclient/greenlight.proto index 67c3fafe7..b11d03ae2 100644 --- a/libs/gl-client-py/glclient/greenlight.proto +++ b/libs/gl-client-py/glclient/greenlight.proto @@ -156,6 +156,7 @@ message SignerStateEntry { uint64 version = 1; string key = 2; bytes value = 3; + bytes signature = 4; } // This represents a grpc request that is currently pending, along diff --git a/libs/gl-client-py/glclient/greenlight_pb2.py b/libs/gl-client-py/glclient/greenlight_pb2.py index 62bae322a..d729b43ed 100644 --- a/libs/gl-client-py/glclient/greenlight_pb2.py +++ b/libs/gl-client-py/glclient/greenlight_pb2.py @@ -24,7 +24,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19glclient/greenlight.proto\x12\ngreenlight\"H\n\x11HsmRequestContext\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x0c\n\x04\x64\x62id\x18\x02 \x01(\x04\x12\x14\n\x0c\x63\x61pabilities\x18\x03 \x01(\x04\"q\n\x0bHsmResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\r\x12\x0b\n\x03raw\x18\x02 \x01(\x0c\x12\x32\n\x0csigner_state\x18\x05 \x03(\x0b\x32\x1c.greenlight.SignerStateEntry\x12\r\n\x05\x65rror\x18\x06 \x01(\t\"\xbf\x01\n\nHsmRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\r\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.greenlight.HsmRequestContext\x12\x0b\n\x03raw\x18\x03 \x01(\x0c\x12\x32\n\x0csigner_state\x18\x04 \x03(\x0b\x32\x1c.greenlight.SignerStateEntry\x12,\n\x08requests\x18\x05 \x03(\x0b\x32\x1a.greenlight.PendingRequest\"\x07\n\x05\x45mpty\"l\n\x06\x41mount\x12\x16\n\x0cmillisatoshi\x18\x01 \x01(\x04H\x00\x12\x11\n\x07satoshi\x18\x02 \x01(\x04H\x00\x12\x11\n\x07\x62itcoin\x18\x03 \x01(\x04H\x00\x12\r\n\x03\x61ll\x18\x04 \x01(\x08H\x00\x12\r\n\x03\x61ny\x18\x05 \x01(\x08H\x00\x42\x06\n\x04unit\"\x16\n\x14StreamIncomingFilter\"\'\n\x08TlvField\x12\x0c\n\x04type\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c\"\xa5\x01\n\x0fOffChainPayment\x12\r\n\x05label\x18\x01 \x01(\t\x12\x10\n\x08preimage\x18\x02 \x01(\x0c\x12\"\n\x06\x61mount\x18\x03 \x01(\x0b\x32\x12.greenlight.Amount\x12\'\n\textratlvs\x18\x04 \x03(\x0b\x32\x14.greenlight.TlvField\x12\x14\n\x0cpayment_hash\x18\x05 \x01(\x0c\x12\x0e\n\x06\x62olt11\x18\x06 \x01(\t\"M\n\x0fIncomingPayment\x12/\n\x08offchain\x18\x01 \x01(\x0b\x32\x1b.greenlight.OffChainPaymentH\x00\x42\t\n\x07\x64\x65tails\"\x12\n\x10StreamLogRequest\"\x18\n\x08LogEntry\x12\x0c\n\x04line\x18\x01 \x01(\t\"?\n\x10SignerStateEntry\x12\x0f\n\x07version\x18\x01 \x01(\x04\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x0c\"r\n\x0ePendingRequest\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x11\n\tsignature\x18\x03 \x01(\x0c\x12\x0e\n\x06pubkey\x18\x04 \x01(\x0c\x12\x11\n\ttimestamp\x18\x05 \x01(\x04\x12\x0c\n\x04rune\x18\x06 \x01(\x0c\"=\n\nNodeConfig\x12/\n\x0bstartupmsgs\x18\x01 \x03(\x0b\x32\x1a.greenlight.StartupMessage\"!\n\x08GlConfig\x12\x15\n\rclose_to_addr\x18\x01 \x01(\t\"3\n\x0eStartupMessage\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x10\n\x08response\x18\x02 \x01(\x0c\"\x18\n\x16StreamCustommsgRequest\"-\n\tCustommsg\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xa4\x01\n\x14TrampolinePayRequest\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x1a\n\x12trampoline_node_id\x18\x02 \x01(\x0c\x12\x13\n\x0b\x61mount_msat\x18\x03 \x01(\x04\x12\r\n\x05label\x18\x04 \x01(\t\x12\x15\n\rmaxfeepercent\x18\x05 \x01(\x02\x12\x10\n\x08maxdelay\x18\x06 \x01(\r\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\"\xd5\x01\n\x15TrampolinePayResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x12\n\ncreated_at\x18\x03 \x01(\x01\x12\r\n\x05parts\x18\x04 \x01(\r\x12\x13\n\x0b\x61mount_msat\x18\x05 \x01(\x04\x12\x18\n\x10\x61mount_sent_msat\x18\x06 \x01(\x04\x12\x13\n\x0b\x64\x65stination\x18\x07 \x01(\x0c\"%\n\tPayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x02\"k\n\x11LspInvoiceRequest\x12\x0e\n\x06lsp_id\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x13\n\x0b\x61mount_msat\x18\x03 \x01(\x04\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\r\n\x05label\x18\x05 \x01(\t\"\x97\x01\n\x12LspInvoiceResponse\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x15\n\rcreated_index\x18\x02 \x01(\r\x12\x12\n\nexpires_at\x18\x03 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x04 \x01(\x0c\x12\x16\n\x0epayment_secret\x18\x05 \x01(\x0c\x12\x18\n\x10opening_fee_msat\x18\x06 \x01(\x04\"\x13\n\x11NodeEventsRequest\"E\n\tNodeEvent\x12/\n\x0cinvoice_paid\x18\x01 \x01(\x0b\x32\x17.greenlight.InvoicePaidH\x00\x42\x07\n\x05\x65vent\"\x92\x01\n\x0bInvoicePaid\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x0e\n\x06\x62olt11\x18\x02 \x01(\t\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\x12\r\n\x05label\x18\x04 \x01(\t\x12\x13\n\x0b\x61mount_msat\x18\x05 \x01(\x04\x12\'\n\textratlvs\x18\x06 \x03(\x0b\x32\x14.greenlight.TlvField2\xa6\x05\n\x04Node\x12M\n\nLspInvoice\x12\x1d.greenlight.LspInvoiceRequest\x1a\x1e.greenlight.LspInvoiceResponse\"\x00\x12S\n\x0eStreamIncoming\x12 .greenlight.StreamIncomingFilter\x1a\x1b.greenlight.IncomingPayment\"\x00\x30\x01\x12\x43\n\tStreamLog\x12\x1c.greenlight.StreamLogRequest\x1a\x14.greenlight.LogEntry\"\x00\x30\x01\x12P\n\x0fStreamCustommsg\x12\".greenlight.StreamCustommsgRequest\x1a\x15.greenlight.Custommsg\"\x00\x30\x01\x12L\n\x10StreamNodeEvents\x12\x1d.greenlight.NodeEventsRequest\x1a\x15.greenlight.NodeEvent\"\x00\x30\x01\x12\x42\n\x11StreamHsmRequests\x12\x11.greenlight.Empty\x1a\x16.greenlight.HsmRequest\"\x00\x30\x01\x12\x41\n\x11RespondHsmRequest\x12\x17.greenlight.HsmResponse\x1a\x11.greenlight.Empty\"\x00\x12\x36\n\tConfigure\x12\x14.greenlight.GlConfig\x1a\x11.greenlight.Empty\"\x00\x12V\n\rTrampolinePay\x12 .greenlight.TrampolinePayRequest\x1a!.greenlight.TrampolinePayResponse\"\x00\x32s\n\x03Hsm\x12<\n\x07Request\x12\x16.greenlight.HsmRequest\x1a\x17.greenlight.HsmResponse\"\x00\x12.\n\x04Ping\x12\x11.greenlight.Empty\x1a\x11.greenlight.Empty\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19glclient/greenlight.proto\x12\ngreenlight\"H\n\x11HsmRequestContext\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x0c\n\x04\x64\x62id\x18\x02 \x01(\x04\x12\x14\n\x0c\x63\x61pabilities\x18\x03 \x01(\x04\"q\n\x0bHsmResponse\x12\x12\n\nrequest_id\x18\x01 \x01(\r\x12\x0b\n\x03raw\x18\x02 \x01(\x0c\x12\x32\n\x0csigner_state\x18\x05 \x03(\x0b\x32\x1c.greenlight.SignerStateEntry\x12\r\n\x05\x65rror\x18\x06 \x01(\t\"\xbf\x01\n\nHsmRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\r\x12.\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x1d.greenlight.HsmRequestContext\x12\x0b\n\x03raw\x18\x03 \x01(\x0c\x12\x32\n\x0csigner_state\x18\x04 \x03(\x0b\x32\x1c.greenlight.SignerStateEntry\x12,\n\x08requests\x18\x05 \x03(\x0b\x32\x1a.greenlight.PendingRequest\"\x07\n\x05\x45mpty\"l\n\x06\x41mount\x12\x16\n\x0cmillisatoshi\x18\x01 \x01(\x04H\x00\x12\x11\n\x07satoshi\x18\x02 \x01(\x04H\x00\x12\x11\n\x07\x62itcoin\x18\x03 \x01(\x04H\x00\x12\r\n\x03\x61ll\x18\x04 \x01(\x08H\x00\x12\r\n\x03\x61ny\x18\x05 \x01(\x08H\x00\x42\x06\n\x04unit\"\x16\n\x14StreamIncomingFilter\"\'\n\x08TlvField\x12\x0c\n\x04type\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c\"\xa5\x01\n\x0fOffChainPayment\x12\r\n\x05label\x18\x01 \x01(\t\x12\x10\n\x08preimage\x18\x02 \x01(\x0c\x12\"\n\x06\x61mount\x18\x03 \x01(\x0b\x32\x12.greenlight.Amount\x12\'\n\textratlvs\x18\x04 \x03(\x0b\x32\x14.greenlight.TlvField\x12\x14\n\x0cpayment_hash\x18\x05 \x01(\x0c\x12\x0e\n\x06\x62olt11\x18\x06 \x01(\t\"M\n\x0fIncomingPayment\x12/\n\x08offchain\x18\x01 \x01(\x0b\x32\x1b.greenlight.OffChainPaymentH\x00\x42\t\n\x07\x64\x65tails\"\x12\n\x10StreamLogRequest\"\x18\n\x08LogEntry\x12\x0c\n\x04line\x18\x01 \x01(\t\"R\n\x10SignerStateEntry\x12\x0f\n\x07version\x18\x01 \x01(\x04\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\x0c\x12\x11\n\tsignature\x18\x04 \x01(\x0c\"r\n\x0ePendingRequest\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x0b\n\x03uri\x18\x02 \x01(\t\x12\x11\n\tsignature\x18\x03 \x01(\x0c\x12\x0e\n\x06pubkey\x18\x04 \x01(\x0c\x12\x11\n\ttimestamp\x18\x05 \x01(\x04\x12\x0c\n\x04rune\x18\x06 \x01(\x0c\"=\n\nNodeConfig\x12/\n\x0bstartupmsgs\x18\x01 \x03(\x0b\x32\x1a.greenlight.StartupMessage\"!\n\x08GlConfig\x12\x15\n\rclose_to_addr\x18\x01 \x01(\t\"3\n\x0eStartupMessage\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x10\n\x08response\x18\x02 \x01(\x0c\"\x18\n\x16StreamCustommsgRequest\"-\n\tCustommsg\x12\x0f\n\x07peer_id\x18\x01 \x01(\x0c\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xa4\x01\n\x14TrampolinePayRequest\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x1a\n\x12trampoline_node_id\x18\x02 \x01(\x0c\x12\x13\n\x0b\x61mount_msat\x18\x03 \x01(\x04\x12\r\n\x05label\x18\x04 \x01(\t\x12\x15\n\rmaxfeepercent\x18\x05 \x01(\x02\x12\x10\n\x08maxdelay\x18\x06 \x01(\r\x12\x13\n\x0b\x64\x65scription\x18\x07 \x01(\t\"\xd5\x01\n\x15TrampolinePayResponse\x12\x18\n\x10payment_preimage\x18\x01 \x01(\x0c\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\x12\n\ncreated_at\x18\x03 \x01(\x01\x12\r\n\x05parts\x18\x04 \x01(\r\x12\x13\n\x0b\x61mount_msat\x18\x05 \x01(\x04\x12\x18\n\x10\x61mount_sent_msat\x18\x06 \x01(\x04\x12\x13\n\x0b\x64\x65stination\x18\x07 \x01(\x0c\"%\n\tPayStatus\x12\x0c\n\x08\x43OMPLETE\x10\x00\x12\n\n\x06\x46\x41ILED\x10\x02\"k\n\x11LspInvoiceRequest\x12\x0e\n\x06lsp_id\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x13\n\x0b\x61mount_msat\x18\x03 \x01(\x04\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\x12\r\n\x05label\x18\x05 \x01(\t\"\x97\x01\n\x12LspInvoiceResponse\x12\x0e\n\x06\x62olt11\x18\x01 \x01(\t\x12\x15\n\rcreated_index\x18\x02 \x01(\r\x12\x12\n\nexpires_at\x18\x03 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x04 \x01(\x0c\x12\x16\n\x0epayment_secret\x18\x05 \x01(\x0c\x12\x18\n\x10opening_fee_msat\x18\x06 \x01(\x04\"\x13\n\x11NodeEventsRequest\"E\n\tNodeEvent\x12/\n\x0cinvoice_paid\x18\x01 \x01(\x0b\x32\x17.greenlight.InvoicePaidH\x00\x42\x07\n\x05\x65vent\"\x92\x01\n\x0bInvoicePaid\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x0e\n\x06\x62olt11\x18\x02 \x01(\t\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\x12\r\n\x05label\x18\x04 \x01(\t\x12\x13\n\x0b\x61mount_msat\x18\x05 \x01(\x04\x12\'\n\textratlvs\x18\x06 \x03(\x0b\x32\x14.greenlight.TlvField2\xa6\x05\n\x04Node\x12M\n\nLspInvoice\x12\x1d.greenlight.LspInvoiceRequest\x1a\x1e.greenlight.LspInvoiceResponse\"\x00\x12S\n\x0eStreamIncoming\x12 .greenlight.StreamIncomingFilter\x1a\x1b.greenlight.IncomingPayment\"\x00\x30\x01\x12\x43\n\tStreamLog\x12\x1c.greenlight.StreamLogRequest\x1a\x14.greenlight.LogEntry\"\x00\x30\x01\x12P\n\x0fStreamCustommsg\x12\".greenlight.StreamCustommsgRequest\x1a\x15.greenlight.Custommsg\"\x00\x30\x01\x12L\n\x10StreamNodeEvents\x12\x1d.greenlight.NodeEventsRequest\x1a\x15.greenlight.NodeEvent\"\x00\x30\x01\x12\x42\n\x11StreamHsmRequests\x12\x11.greenlight.Empty\x1a\x16.greenlight.HsmRequest\"\x00\x30\x01\x12\x41\n\x11RespondHsmRequest\x12\x17.greenlight.HsmResponse\x1a\x11.greenlight.Empty\"\x00\x12\x36\n\tConfigure\x12\x14.greenlight.GlConfig\x1a\x11.greenlight.Empty\"\x00\x12V\n\rTrampolinePay\x12 .greenlight.TrampolinePayRequest\x1a!.greenlight.TrampolinePayResponse\"\x00\x32s\n\x03Hsm\x12<\n\x07Request\x12\x16.greenlight.HsmRequest\x1a\x17.greenlight.HsmResponse\"\x00\x12.\n\x04Ping\x12\x11.greenlight.Empty\x1a\x11.greenlight.Empty\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -54,37 +54,37 @@ _globals['_LOGENTRY']._serialized_start=875 _globals['_LOGENTRY']._serialized_end=899 _globals['_SIGNERSTATEENTRY']._serialized_start=901 - _globals['_SIGNERSTATEENTRY']._serialized_end=964 - _globals['_PENDINGREQUEST']._serialized_start=966 - _globals['_PENDINGREQUEST']._serialized_end=1080 - _globals['_NODECONFIG']._serialized_start=1082 - _globals['_NODECONFIG']._serialized_end=1143 - _globals['_GLCONFIG']._serialized_start=1145 - _globals['_GLCONFIG']._serialized_end=1178 - _globals['_STARTUPMESSAGE']._serialized_start=1180 - _globals['_STARTUPMESSAGE']._serialized_end=1231 - _globals['_STREAMCUSTOMMSGREQUEST']._serialized_start=1233 - _globals['_STREAMCUSTOMMSGREQUEST']._serialized_end=1257 - _globals['_CUSTOMMSG']._serialized_start=1259 - _globals['_CUSTOMMSG']._serialized_end=1304 - _globals['_TRAMPOLINEPAYREQUEST']._serialized_start=1307 - _globals['_TRAMPOLINEPAYREQUEST']._serialized_end=1471 - _globals['_TRAMPOLINEPAYRESPONSE']._serialized_start=1474 - _globals['_TRAMPOLINEPAYRESPONSE']._serialized_end=1687 - _globals['_TRAMPOLINEPAYRESPONSE_PAYSTATUS']._serialized_start=1650 - _globals['_TRAMPOLINEPAYRESPONSE_PAYSTATUS']._serialized_end=1687 - _globals['_LSPINVOICEREQUEST']._serialized_start=1689 - _globals['_LSPINVOICEREQUEST']._serialized_end=1796 - _globals['_LSPINVOICERESPONSE']._serialized_start=1799 - _globals['_LSPINVOICERESPONSE']._serialized_end=1950 - _globals['_NODEEVENTSREQUEST']._serialized_start=1952 - _globals['_NODEEVENTSREQUEST']._serialized_end=1971 - _globals['_NODEEVENT']._serialized_start=1973 - _globals['_NODEEVENT']._serialized_end=2042 - _globals['_INVOICEPAID']._serialized_start=2045 - _globals['_INVOICEPAID']._serialized_end=2191 - _globals['_NODE']._serialized_start=2194 - _globals['_NODE']._serialized_end=2872 - _globals['_HSM']._serialized_start=2874 - _globals['_HSM']._serialized_end=2989 + _globals['_SIGNERSTATEENTRY']._serialized_end=983 + _globals['_PENDINGREQUEST']._serialized_start=985 + _globals['_PENDINGREQUEST']._serialized_end=1099 + _globals['_NODECONFIG']._serialized_start=1101 + _globals['_NODECONFIG']._serialized_end=1162 + _globals['_GLCONFIG']._serialized_start=1164 + _globals['_GLCONFIG']._serialized_end=1197 + _globals['_STARTUPMESSAGE']._serialized_start=1199 + _globals['_STARTUPMESSAGE']._serialized_end=1250 + _globals['_STREAMCUSTOMMSGREQUEST']._serialized_start=1252 + _globals['_STREAMCUSTOMMSGREQUEST']._serialized_end=1276 + _globals['_CUSTOMMSG']._serialized_start=1278 + _globals['_CUSTOMMSG']._serialized_end=1323 + _globals['_TRAMPOLINEPAYREQUEST']._serialized_start=1326 + _globals['_TRAMPOLINEPAYREQUEST']._serialized_end=1490 + _globals['_TRAMPOLINEPAYRESPONSE']._serialized_start=1493 + _globals['_TRAMPOLINEPAYRESPONSE']._serialized_end=1706 + _globals['_TRAMPOLINEPAYRESPONSE_PAYSTATUS']._serialized_start=1669 + _globals['_TRAMPOLINEPAYRESPONSE_PAYSTATUS']._serialized_end=1706 + _globals['_LSPINVOICEREQUEST']._serialized_start=1708 + _globals['_LSPINVOICEREQUEST']._serialized_end=1815 + _globals['_LSPINVOICERESPONSE']._serialized_start=1818 + _globals['_LSPINVOICERESPONSE']._serialized_end=1969 + _globals['_NODEEVENTSREQUEST']._serialized_start=1971 + _globals['_NODEEVENTSREQUEST']._serialized_end=1990 + _globals['_NODEEVENT']._serialized_start=1992 + _globals['_NODEEVENT']._serialized_end=2061 + _globals['_INVOICEPAID']._serialized_start=2064 + _globals['_INVOICEPAID']._serialized_end=2210 + _globals['_NODE']._serialized_start=2213 + _globals['_NODE']._serialized_end=2891 + _globals['_HSM']._serialized_start=2893 + _globals['_HSM']._serialized_end=3008 # @@protoc_insertion_point(module_scope) diff --git a/libs/gl-client-py/glclient/greenlight_pb2.pyi b/libs/gl-client-py/glclient/greenlight_pb2.pyi index 65e182744..6d9096681 100644 --- a/libs/gl-client-py/glclient/greenlight_pb2.pyi +++ b/libs/gl-client-py/glclient/greenlight_pb2.pyi @@ -284,17 +284,20 @@ class SignerStateEntry(_message.Message): VERSION_FIELD_NUMBER: _builtins.int KEY_FIELD_NUMBER: _builtins.int VALUE_FIELD_NUMBER: _builtins.int + SIGNATURE_FIELD_NUMBER: _builtins.int version: _builtins.int key: _builtins.str value: _builtins.bytes + signature: _builtins.bytes def __init__( self, *, version: _builtins.int = ..., key: _builtins.str = ..., value: _builtins.bytes = ..., + signature: _builtins.bytes = ..., ) -> None: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["key", b"key", "value", b"value", "version", b"version"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["key", b"key", "signature", b"signature", "value", b"value", "version", b"version"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... Global___SignerStateEntry: _TypeAlias = SignerStateEntry # noqa: Y015 diff --git a/libs/gl-client-py/glclient/scheduler_pb2.pyi b/libs/gl-client-py/glclient/scheduler_pb2.pyi index aac68c5d0..507b0ec95 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2.pyi +++ b/libs/gl-client-py/glclient/scheduler_pb2.pyi @@ -3,29 +3,34 @@ isort:skip_file """ -import builtins -import collections.abc -import glclient.greenlight_pb2 -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message +from collections import abc as _abc +from glclient import greenlight_pb2 as _greenlight_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +import builtins as _builtins import sys -import typing +import typing as _typing if sys.version_info >= (3, 10): - import typing as typing_extensions + from typing import TypeAlias as _TypeAlias else: - import typing_extensions + from typing_extensions import TypeAlias as _TypeAlias -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor +if sys.version_info >= (3, 13): + from warnings import deprecated as _deprecated +else: + from typing_extensions import deprecated as _deprecated + +DESCRIPTOR: _descriptor.FileDescriptor class _ChallengeScope: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 -class _ChallengeScopeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ChallengeScope.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor +class _ChallengeScopeEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_ChallengeScope.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor REGISTER: _ChallengeScope.ValueType # 0 RECOVER: _ChallengeScope.ValueType # 1 @@ -34,236 +39,246 @@ class ChallengeScope(_ChallengeScope, metaclass=_ChallengeScopeEnumTypeWrapper): REGISTER: ChallengeScope.ValueType # 0 RECOVER: ChallengeScope.ValueType # 1 -global___ChallengeScope = ChallengeScope +Global___ChallengeScope: _TypeAlias = ChallengeScope # noqa: Y015 -@typing.final -class AddOutgoingWebhookRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class AddOutgoingWebhookRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - URI_FIELD_NUMBER: builtins.int - node_id: builtins.bytes - uri: builtins.str + NODE_ID_FIELD_NUMBER: _builtins.int + URI_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes + uri: _builtins.str def __init__( self, *, - node_id: builtins.bytes = ..., - uri: builtins.str = ..., + node_id: _builtins.bytes = ..., + uri: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id", "uri", b"uri"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id", "uri", b"uri"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___AddOutgoingWebhookRequest = AddOutgoingWebhookRequest +Global___AddOutgoingWebhookRequest: _TypeAlias = AddOutgoingWebhookRequest # noqa: Y015 -@typing.final -class AddOutgoingWebhookResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class AddOutgoingWebhookResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - ID_FIELD_NUMBER: builtins.int - SECRET_FIELD_NUMBER: builtins.int - id: builtins.int - secret: builtins.str + ID_FIELD_NUMBER: _builtins.int + SECRET_FIELD_NUMBER: _builtins.int + id: _builtins.int + secret: _builtins.str def __init__( self, *, - id: builtins.int = ..., - secret: builtins.str = ..., + id: _builtins.int = ..., + secret: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["id", b"id", "secret", b"secret"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["id", b"id", "secret", b"secret"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___AddOutgoingWebhookResponse = AddOutgoingWebhookResponse +Global___AddOutgoingWebhookResponse: _TypeAlias = AddOutgoingWebhookResponse # noqa: Y015 -@typing.final -class ListOutgoingWebhooksRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ListOutgoingWebhooksRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - node_id: builtins.bytes + NODE_ID_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes def __init__( self, *, - node_id: builtins.bytes = ..., + node_id: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ListOutgoingWebhooksRequest = ListOutgoingWebhooksRequest +Global___ListOutgoingWebhooksRequest: _TypeAlias = ListOutgoingWebhooksRequest # noqa: Y015 -@typing.final -class Webhook(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class Webhook(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - ID_FIELD_NUMBER: builtins.int - URI_FIELD_NUMBER: builtins.int - id: builtins.int - uri: builtins.str + ID_FIELD_NUMBER: _builtins.int + URI_FIELD_NUMBER: _builtins.int + id: _builtins.int + uri: _builtins.str def __init__( self, *, - id: builtins.int = ..., - uri: builtins.str = ..., + id: _builtins.int = ..., + uri: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["id", b"id", "uri", b"uri"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["id", b"id", "uri", b"uri"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___Webhook = Webhook +Global___Webhook: _TypeAlias = Webhook # noqa: Y015 -@typing.final -class ListOutgoingWebhooksResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ListOutgoingWebhooksResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - OUTGOING_WEBHOOKS_FIELD_NUMBER: builtins.int - @property - def outgoing_webhooks(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___Webhook]: ... + OUTGOING_WEBHOOKS_FIELD_NUMBER: _builtins.int + @_builtins.property + def outgoing_webhooks(self) -> _containers.RepeatedCompositeFieldContainer[Global___Webhook]: ... def __init__( self, *, - outgoing_webhooks: collections.abc.Iterable[global___Webhook] | None = ..., + outgoing_webhooks: _abc.Iterable[Global___Webhook] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["outgoing_webhooks", b"outgoing_webhooks"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["outgoing_webhooks", b"outgoing_webhooks"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ListOutgoingWebhooksResponse = ListOutgoingWebhooksResponse +Global___ListOutgoingWebhooksResponse: _TypeAlias = ListOutgoingWebhooksResponse # noqa: Y015 -@typing.final -class DeleteOutgoingWebhooksRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class DeleteOutgoingWebhooksRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - IDS_FIELD_NUMBER: builtins.int - node_id: builtins.bytes - @property - def ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: ... + NODE_ID_FIELD_NUMBER: _builtins.int + IDS_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes + @_builtins.property + def ids(self) -> _containers.RepeatedScalarFieldContainer[_builtins.int]: ... def __init__( self, *, - node_id: builtins.bytes = ..., - ids: collections.abc.Iterable[builtins.int] | None = ..., + node_id: _builtins.bytes = ..., + ids: _abc.Iterable[_builtins.int] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["ids", b"ids", "node_id", b"node_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["ids", b"ids", "node_id", b"node_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___DeleteOutgoingWebhooksRequest = DeleteOutgoingWebhooksRequest +Global___DeleteOutgoingWebhooksRequest: _TypeAlias = DeleteOutgoingWebhooksRequest # noqa: Y015 -@typing.final -class RotateOutgoingWebhookSecretRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class RotateOutgoingWebhookSecretRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - WEBHOOK_ID_FIELD_NUMBER: builtins.int - node_id: builtins.bytes - webhook_id: builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + WEBHOOK_ID_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes + webhook_id: _builtins.int def __init__( self, *, - node_id: builtins.bytes = ..., - webhook_id: builtins.int = ..., + node_id: _builtins.bytes = ..., + webhook_id: _builtins.int = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id", "webhook_id", b"webhook_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id", "webhook_id", b"webhook_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___RotateOutgoingWebhookSecretRequest = RotateOutgoingWebhookSecretRequest +Global___RotateOutgoingWebhookSecretRequest: _TypeAlias = RotateOutgoingWebhookSecretRequest # noqa: Y015 -@typing.final -class WebhookSecretResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class WebhookSecretResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - SECRET_FIELD_NUMBER: builtins.int - secret: builtins.str + SECRET_FIELD_NUMBER: _builtins.int + secret: _builtins.str def __init__( self, *, - secret: builtins.str = ..., + secret: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["secret", b"secret"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["secret", b"secret"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___WebhookSecretResponse = WebhookSecretResponse +Global___WebhookSecretResponse: _TypeAlias = WebhookSecretResponse # noqa: Y015 -@typing.final -class ChallengeRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ChallengeRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - SCOPE_FIELD_NUMBER: builtins.int - NODE_ID_FIELD_NUMBER: builtins.int - scope: global___ChallengeScope.ValueType - node_id: builtins.bytes + SCOPE_FIELD_NUMBER: _builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + scope: Global___ChallengeScope.ValueType + node_id: _builtins.bytes def __init__( self, *, - scope: global___ChallengeScope.ValueType = ..., - node_id: builtins.bytes = ..., + scope: Global___ChallengeScope.ValueType = ..., + node_id: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id", "scope", b"scope"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id", "scope", b"scope"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ChallengeRequest = ChallengeRequest +Global___ChallengeRequest: _TypeAlias = ChallengeRequest # noqa: Y015 -@typing.final -class ChallengeResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ChallengeResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - CHALLENGE_FIELD_NUMBER: builtins.int - challenge: builtins.bytes + CHALLENGE_FIELD_NUMBER: _builtins.int + challenge: _builtins.bytes def __init__( self, *, - challenge: builtins.bytes = ..., + challenge: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["challenge", b"challenge"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["challenge", b"challenge"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ChallengeResponse = ChallengeResponse +Global___ChallengeResponse: _TypeAlias = ChallengeResponse # noqa: Y015 -@typing.final -class RegistrationRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class RegistrationRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - BIP32_KEY_FIELD_NUMBER: builtins.int - NETWORK_FIELD_NUMBER: builtins.int - CHALLENGE_FIELD_NUMBER: builtins.int - SIGNATURE_FIELD_NUMBER: builtins.int - SIGNER_PROTO_FIELD_NUMBER: builtins.int - INIT_MSG_FIELD_NUMBER: builtins.int - CSR_FIELD_NUMBER: builtins.int - INVITE_CODE_FIELD_NUMBER: builtins.int - STARTUPMSGS_FIELD_NUMBER: builtins.int - node_id: builtins.bytes + NODE_ID_FIELD_NUMBER: _builtins.int + BIP32_KEY_FIELD_NUMBER: _builtins.int + NETWORK_FIELD_NUMBER: _builtins.int + CHALLENGE_FIELD_NUMBER: _builtins.int + SIGNATURE_FIELD_NUMBER: _builtins.int + SIGNER_PROTO_FIELD_NUMBER: _builtins.int + INIT_MSG_FIELD_NUMBER: _builtins.int + CSR_FIELD_NUMBER: _builtins.int + INVITE_CODE_FIELD_NUMBER: _builtins.int + STARTUPMSGS_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes """33 bytes node public key.""" - bip32_key: builtins.bytes + bip32_key: _builtins.bytes """DEPRECATED: The `init_msg` subsumes this field""" - network: builtins.str + network: _builtins.str """Which network is this node going to run on? Options are bitcoin, testnet, and regtest. """ - challenge: builtins.bytes + challenge: _builtins.bytes """An previously unused challenge as retrieved from `Scheduler.GetChallenge() with `scope=REGISTER`. In combination with the `signature` below this is used to authenticate the caller and ensure the caller has access to the secret keys corresponding to the `node_id`. """ - signature: builtins.bytes + signature: _builtins.bytes """A signature for the `challenge` signed by the secret key corresponding to the `node_id`. Please refer to the documentation of `Scheduler.GetChallenge()` for details on how to create this signature. """ - signer_proto: builtins.str + signer_proto: _builtins.str """The signer_proto is required in order to determine which version the node should run. If these don't match the signer may not be able to sign incoming requests. """ - init_msg: builtins.bytes + init_msg: _builtins.bytes """The fuil init message returned by the `libhsmd`, this supersedes the bip32_key field which was a misnomer. Notice that this includes the prefix 0x006F which is the message type. """ - csr: builtins.bytes + csr: _builtins.bytes """The certificate signing request that will be signed by the greenlight backend. Notice that this must have the valid CN corresponding to the node_id e.g. /users/{node_id} set. """ - invite_code: builtins.str + invite_code: _builtins.str """An optional invite code. We may want to throttle the registration rate. Therefore we might check that a registration request has a valid invite code. """ - @property - def startupmsgs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___StartupMessage]: + @_builtins.property + def startupmsgs(self) -> _containers.RepeatedCompositeFieldContainer[Global___StartupMessage]: """Messages stashed at the scheduler to allow signerless startups. """ @@ -271,44 +286,45 @@ class RegistrationRequest(google.protobuf.message.Message): def __init__( self, *, - node_id: builtins.bytes = ..., - bip32_key: builtins.bytes = ..., - network: builtins.str = ..., - challenge: builtins.bytes = ..., - signature: builtins.bytes = ..., - signer_proto: builtins.str = ..., - init_msg: builtins.bytes = ..., - csr: builtins.bytes = ..., - invite_code: builtins.str = ..., - startupmsgs: collections.abc.Iterable[global___StartupMessage] | None = ..., + node_id: _builtins.bytes = ..., + bip32_key: _builtins.bytes = ..., + network: _builtins.str = ..., + challenge: _builtins.bytes = ..., + signature: _builtins.bytes = ..., + signer_proto: _builtins.str = ..., + init_msg: _builtins.bytes = ..., + csr: _builtins.bytes = ..., + invite_code: _builtins.str = ..., + startupmsgs: _abc.Iterable[Global___StartupMessage] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["bip32_key", b"bip32_key", "challenge", b"challenge", "csr", b"csr", "init_msg", b"init_msg", "invite_code", b"invite_code", "network", b"network", "node_id", b"node_id", "signature", b"signature", "signer_proto", b"signer_proto", "startupmsgs", b"startupmsgs"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["bip32_key", b"bip32_key", "challenge", b"challenge", "csr", b"csr", "init_msg", b"init_msg", "invite_code", b"invite_code", "network", b"network", "node_id", b"node_id", "signature", b"signature", "signer_proto", b"signer_proto", "startupmsgs", b"startupmsgs"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___RegistrationRequest = RegistrationRequest +Global___RegistrationRequest: _TypeAlias = RegistrationRequest # noqa: Y015 -@typing.final -class RegistrationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class RegistrationResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_CERT_FIELD_NUMBER: builtins.int - DEVICE_KEY_FIELD_NUMBER: builtins.int - RUNE_FIELD_NUMBER: builtins.int - CREDS_FIELD_NUMBER: builtins.int - device_cert: builtins.str + DEVICE_CERT_FIELD_NUMBER: _builtins.int + DEVICE_KEY_FIELD_NUMBER: _builtins.int + RUNE_FIELD_NUMBER: _builtins.int + CREDS_FIELD_NUMBER: _builtins.int + device_cert: _builtins.str """Upon registering the user receives back the signed certificate that belongs to the certificate signing request the that was sent in the registration request, so they can authenticate themselves in the future. """ - device_key: builtins.str + device_key: _builtins.str """The private key that was used to create the certificate with. This key is used to sign the requests to the node. """ - rune: builtins.str + rune: _builtins.str """A master rune that is returned if the device that is registered has its own signer. The signer is necessary as the response is intercepted on the client side and appends the rune to the registratrion response. """ - creds: builtins.bytes + creds: _builtins.bytes """Creds contains a serialized version of the device_cert, the device_key and the rune that are used to authenticate a device at the backend, and to authorize a request at the signer. @@ -316,17 +332,18 @@ class RegistrationResponse(google.protobuf.message.Message): def __init__( self, *, - device_cert: builtins.str = ..., - device_key: builtins.str = ..., - rune: builtins.str = ..., - creds: builtins.bytes = ..., + device_cert: _builtins.str = ..., + device_key: _builtins.str = ..., + rune: _builtins.str = ..., + creds: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_key", b"device_key", "rune", b"rune"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_key", b"device_key", "rune", b"rune"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___RegistrationResponse = RegistrationResponse +Global___RegistrationResponse: _TypeAlias = RegistrationResponse # noqa: Y015 -@typing.final -class ScheduleRequest(google.protobuf.message.Message): +@_typing.final +class ScheduleRequest(_message.Message): """Ask the scheduler to schedule the node to be run on an available nodelet. This will always cause the scheduler to kick into action. If you'd @@ -334,76 +351,79 @@ class ScheduleRequest(google.protobuf.message.Message): wait for one to start please use the """ - DESCRIPTOR: google.protobuf.descriptor.Descriptor + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - node_id: builtins.bytes + NODE_ID_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes def __init__( self, *, - node_id: builtins.bytes = ..., + node_id: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ScheduleRequest = ScheduleRequest +Global___ScheduleRequest: _TypeAlias = ScheduleRequest # noqa: Y015 -@typing.final -class NodeInfoRequest(google.protobuf.message.Message): +@_typing.final +class NodeInfoRequest(_message.Message): """Discovery request asking the scheduler if a nodelet is currently assigned the specified node_id, or wait for one to be assigned. If `wait` is set to `true` the scheduler will keep the request pending until a nodelet is assigned. """ - DESCRIPTOR: google.protobuf.descriptor.Descriptor + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - WAIT_FIELD_NUMBER: builtins.int - node_id: builtins.bytes - wait: builtins.bool + NODE_ID_FIELD_NUMBER: _builtins.int + WAIT_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes + wait: _builtins.bool def __init__( self, *, - node_id: builtins.bytes = ..., - wait: builtins.bool = ..., + node_id: _builtins.bytes = ..., + wait: _builtins.bool = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["node_id", b"node_id", "wait", b"wait"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["node_id", b"node_id", "wait", b"wait"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___NodeInfoRequest = NodeInfoRequest +Global___NodeInfoRequest: _TypeAlias = NodeInfoRequest # noqa: Y015 -@typing.final -class NodeInfoResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class NodeInfoResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - NODE_ID_FIELD_NUMBER: builtins.int - GRPC_URI_FIELD_NUMBER: builtins.int - SESSION_ID_FIELD_NUMBER: builtins.int - node_id: builtins.bytes - grpc_uri: builtins.str - session_id: builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + GRPC_URI_FIELD_NUMBER: _builtins.int + SESSION_ID_FIELD_NUMBER: _builtins.int + node_id: _builtins.bytes + grpc_uri: _builtins.str + session_id: _builtins.int def __init__( self, *, - node_id: builtins.bytes = ..., - grpc_uri: builtins.str = ..., - session_id: builtins.int = ..., + node_id: _builtins.bytes = ..., + grpc_uri: _builtins.str = ..., + session_id: _builtins.int = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["grpc_uri", b"grpc_uri", "node_id", b"node_id", "session_id", b"session_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["grpc_uri", b"grpc_uri", "node_id", b"node_id", "session_id", b"session_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___NodeInfoResponse = NodeInfoResponse +Global___NodeInfoResponse: _TypeAlias = NodeInfoResponse # noqa: Y015 -@typing.final -class RecoveryRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class RecoveryRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - CHALLENGE_FIELD_NUMBER: builtins.int - SIGNATURE_FIELD_NUMBER: builtins.int - NODE_ID_FIELD_NUMBER: builtins.int - CSR_FIELD_NUMBER: builtins.int - challenge: builtins.bytes - signature: builtins.bytes - node_id: builtins.bytes - csr: builtins.bytes + CHALLENGE_FIELD_NUMBER: _builtins.int + SIGNATURE_FIELD_NUMBER: _builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + CSR_FIELD_NUMBER: _builtins.int + challenge: _builtins.bytes + signature: _builtins.bytes + node_id: _builtins.bytes + csr: _builtins.bytes """The certificate signing request that will be signed by the greenlight backend. Notice that this must have the valid CN corresponding to the node_id e.g. /users/{node_id} set. @@ -411,31 +431,32 @@ class RecoveryRequest(google.protobuf.message.Message): def __init__( self, *, - challenge: builtins.bytes = ..., - signature: builtins.bytes = ..., - node_id: builtins.bytes = ..., - csr: builtins.bytes = ..., + challenge: _builtins.bytes = ..., + signature: _builtins.bytes = ..., + node_id: _builtins.bytes = ..., + csr: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["challenge", b"challenge", "csr", b"csr", "node_id", b"node_id", "signature", b"signature"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["challenge", b"challenge", "csr", b"csr", "node_id", b"node_id", "signature", b"signature"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___RecoveryRequest = RecoveryRequest +Global___RecoveryRequest: _TypeAlias = RecoveryRequest # noqa: Y015 -@typing.final -class RecoveryResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class RecoveryResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_CERT_FIELD_NUMBER: builtins.int - DEVICE_KEY_FIELD_NUMBER: builtins.int - RUNE_FIELD_NUMBER: builtins.int - CREDS_FIELD_NUMBER: builtins.int - device_cert: builtins.str - device_key: builtins.str - rune: builtins.str + DEVICE_CERT_FIELD_NUMBER: _builtins.int + DEVICE_KEY_FIELD_NUMBER: _builtins.int + RUNE_FIELD_NUMBER: _builtins.int + CREDS_FIELD_NUMBER: _builtins.int + device_cert: _builtins.str + device_key: _builtins.str + rune: _builtins.str """A master rune that is returned if the device that is registered has its own signer. The signer is necessary as the response is intercepted on the client side and appends the rune to the registratrion response. """ - creds: builtins.bytes + creds: _builtins.bytes """Creds contains a serialized version of the device_cert, the device_key and the rune that are used to authenticate a device at the backend, and to authorize a request at the signer. @@ -443,66 +464,80 @@ class RecoveryResponse(google.protobuf.message.Message): def __init__( self, *, - device_cert: builtins.str = ..., - device_key: builtins.str = ..., - rune: builtins.str = ..., - creds: builtins.bytes = ..., + device_cert: _builtins.str = ..., + device_key: _builtins.str = ..., + rune: _builtins.str = ..., + creds: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_key", b"device_key", "rune", b"rune"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_key", b"device_key", "rune", b"rune"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___RecoveryResponse = RecoveryResponse +Global___RecoveryResponse: _TypeAlias = RecoveryResponse # noqa: Y015 -@typing.final -class UpgradeRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class UpgradeRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - SIGNER_VERSION_FIELD_NUMBER: builtins.int - INITMSG_FIELD_NUMBER: builtins.int - STARTUPMSGS_FIELD_NUMBER: builtins.int - signer_version: builtins.str + SIGNER_VERSION_FIELD_NUMBER: _builtins.int + INITMSG_FIELD_NUMBER: _builtins.int + STARTUPMSGS_FIELD_NUMBER: _builtins.int + signer_version: _builtins.str """The version of the signer, i.e., the maximum version of the protocol that this signer can understand. """ - initmsg: builtins.bytes - """The new initmsg matching the above version. Necessary to - schedule the node without a signer present. - Deprecated: Replaced by the more generic `startupmsgs` - """ - @property - def startupmsgs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___StartupMessage]: + @_builtins.property + @_deprecated("""This field has been marked as deprecated using proto field options.""") + def initmsg(self) -> _builtins.bytes: + """The new initmsg matching the above version. Necessary to + schedule the node without a signer present. + Deprecated: Replaced by the more generic `startupmsgs` + """ + + @initmsg.setter + @_deprecated("""This field has been marked as deprecated using proto field options.""") + def initmsg(self, value: _builtins.bytes) -> None: + """The new initmsg matching the above version. Necessary to + schedule the node without a signer present. + Deprecated: Replaced by the more generic `startupmsgs` + """ + + @_builtins.property + def startupmsgs(self) -> _containers.RepeatedCompositeFieldContainer[Global___StartupMessage]: """Messages stashed at the scheduler to allow signerless startups.""" def __init__( self, *, - signer_version: builtins.str = ..., - initmsg: builtins.bytes = ..., - startupmsgs: collections.abc.Iterable[global___StartupMessage] | None = ..., + signer_version: _builtins.str = ..., + initmsg: _builtins.bytes = ..., + startupmsgs: _abc.Iterable[Global___StartupMessage] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["initmsg", b"initmsg", "signer_version", b"signer_version", "startupmsgs", b"startupmsgs"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["initmsg", b"initmsg", "signer_version", b"signer_version", "startupmsgs", b"startupmsgs"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___UpgradeRequest = UpgradeRequest +Global___UpgradeRequest: _TypeAlias = UpgradeRequest # noqa: Y015 -@typing.final -class UpgradeResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class UpgradeResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - OLD_VERSION_FIELD_NUMBER: builtins.int - old_version: builtins.str + OLD_VERSION_FIELD_NUMBER: _builtins.int + old_version: _builtins.str """The version of the node before the upgrade request has been processed. """ def __init__( self, *, - old_version: builtins.str = ..., + old_version: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["old_version", b"old_version"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["old_version", b"old_version"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___UpgradeResponse = UpgradeResponse +Global___UpgradeResponse: _TypeAlias = UpgradeResponse # noqa: Y015 -@typing.final -class StartupMessage(google.protobuf.message.Message): +@_typing.final +class StartupMessage(_message.Message): """A message that we know will be requested by `lightningd` at startup, and that we stash a response to on the scheduler. This allows the scheduler to start a node without requiring the signer @@ -510,192 +545,199 @@ class StartupMessage(google.protobuf.message.Message): prefix, but without the length prefix. """ - DESCRIPTOR: google.protobuf.descriptor.Descriptor + DESCRIPTOR: _descriptor.Descriptor - REQUEST_FIELD_NUMBER: builtins.int - RESPONSE_FIELD_NUMBER: builtins.int - request: builtins.bytes - response: builtins.bytes + REQUEST_FIELD_NUMBER: _builtins.int + RESPONSE_FIELD_NUMBER: _builtins.int + request: _builtins.bytes + response: _builtins.bytes def __init__( self, *, - request: builtins.bytes = ..., - response: builtins.bytes = ..., + request: _builtins.bytes = ..., + response: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["request", b"request", "response", b"response"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["request", b"request", "response", b"response"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___StartupMessage = StartupMessage +Global___StartupMessage: _TypeAlias = StartupMessage # noqa: Y015 -@typing.final -class ListInviteCodesRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ListInviteCodesRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor def __init__( self, ) -> None: ... -global___ListInviteCodesRequest = ListInviteCodesRequest +Global___ListInviteCodesRequest: _TypeAlias = ListInviteCodesRequest # noqa: Y015 -@typing.final -class ListInviteCodesResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ListInviteCodesResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - INVITE_CODE_LIST_FIELD_NUMBER: builtins.int - @property - def invite_code_list(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___InviteCode]: ... + INVITE_CODE_LIST_FIELD_NUMBER: _builtins.int + @_builtins.property + def invite_code_list(self) -> _containers.RepeatedCompositeFieldContainer[Global___InviteCode]: ... def __init__( self, *, - invite_code_list: collections.abc.Iterable[global___InviteCode] | None = ..., + invite_code_list: _abc.Iterable[Global___InviteCode] | None = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["invite_code_list", b"invite_code_list"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["invite_code_list", b"invite_code_list"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ListInviteCodesResponse = ListInviteCodesResponse +Global___ListInviteCodesResponse: _TypeAlias = ListInviteCodesResponse # noqa: Y015 -@typing.final -class InviteCode(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class InviteCode(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - CODE_FIELD_NUMBER: builtins.int - IS_REDEEMED_FIELD_NUMBER: builtins.int - code: builtins.str - is_redeemed: builtins.bool + CODE_FIELD_NUMBER: _builtins.int + IS_REDEEMED_FIELD_NUMBER: _builtins.int + code: _builtins.str + is_redeemed: _builtins.bool def __init__( self, *, - code: builtins.str = ..., - is_redeemed: builtins.bool = ..., + code: _builtins.str = ..., + is_redeemed: _builtins.bool = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["code", b"code", "is_redeemed", b"is_redeemed"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["code", b"code", "is_redeemed", b"is_redeemed"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___InviteCode = InviteCode +Global___InviteCode: _TypeAlias = InviteCode # noqa: Y015 -@typing.final -class ExportNodeRequest(google.protobuf.message.Message): +@_typing.final +class ExportNodeRequest(_message.Message): """Empty message for now, node identity is extracted from the mTLS certificate used to authenticate against the Scheduler. """ - DESCRIPTOR: google.protobuf.descriptor.Descriptor + DESCRIPTOR: _descriptor.Descriptor def __init__( self, ) -> None: ... -global___ExportNodeRequest = ExportNodeRequest +Global___ExportNodeRequest: _TypeAlias = ExportNodeRequest # noqa: Y015 -@typing.final -class ExportNodeResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ExportNodeResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - URL_FIELD_NUMBER: builtins.int - url: builtins.str + URL_FIELD_NUMBER: _builtins.int + url: _builtins.str """URL where the encrypted backup can be retrieved from.""" def __init__( self, *, - url: builtins.str = ..., + url: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["url", b"url"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["url", b"url"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ExportNodeResponse = ExportNodeResponse +Global___ExportNodeResponse: _TypeAlias = ExportNodeResponse # noqa: Y015 -@typing.final -class SignerRejection(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class SignerRejection(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - MSG_FIELD_NUMBER: builtins.int - REQUEST_FIELD_NUMBER: builtins.int - GIT_VERSION_FIELD_NUMBER: builtins.int - NODE_ID_FIELD_NUMBER: builtins.int - msg: builtins.str + MSG_FIELD_NUMBER: _builtins.int + REQUEST_FIELD_NUMBER: _builtins.int + GIT_VERSION_FIELD_NUMBER: _builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + msg: _builtins.str """A human-readable description of what went wrong""" - git_version: builtins.str - node_id: builtins.bytes - @property - def request(self) -> glclient.greenlight_pb2.HsmRequest: ... + git_version: _builtins.str + node_id: _builtins.bytes + @_builtins.property + def request(self) -> _greenlight_pb2.HsmRequest: ... def __init__( self, *, - msg: builtins.str = ..., - request: glclient.greenlight_pb2.HsmRequest | None = ..., - git_version: builtins.str = ..., - node_id: builtins.bytes = ..., + msg: _builtins.str = ..., + request: _greenlight_pb2.HsmRequest | None = ..., + git_version: _builtins.str = ..., + node_id: _builtins.bytes = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["request", b"request"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["git_version", b"git_version", "msg", b"msg", "node_id", b"node_id", "request", b"request"]) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["request", b"request"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["git_version", b"git_version", "msg", b"msg", "node_id", b"node_id", "request", b"request"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___SignerRejection = SignerRejection +Global___SignerRejection: _TypeAlias = SignerRejection # noqa: Y015 -@typing.final -class PairDeviceRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class PairDeviceRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - CSR_FIELD_NUMBER: builtins.int - DEVICE_NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - RESTRICTIONS_FIELD_NUMBER: builtins.int - device_id: builtins.str + DEVICE_ID_FIELD_NUMBER: _builtins.int + CSR_FIELD_NUMBER: _builtins.int + DEVICE_NAME_FIELD_NUMBER: _builtins.int + DESCRIPTION_FIELD_NUMBER: _builtins.int + RESTRICTIONS_FIELD_NUMBER: _builtins.int + device_id: _builtins.str """The tls public key of the new device.""" - csr: builtins.bytes + csr: _builtins.bytes """The certificate signing request that will be signed by the greenlight backend if the pairing succeeds. Notice that the CN here is irrelevant. """ - device_name: builtins.str + device_name: _builtins.str """The name of the device that will be part of the tls certificate subjects CN field: CN=/users//. """ - description: builtins.str + description: _builtins.str """A human readable description of the device, this can be a purpose or something similar. Can be used to display to the user on the old device. """ - restrictions: builtins.str + restrictions: _builtins.str """A set of restrictions that the new devices requests for the rune. This might in the future get upgraded for easier naming. """ def __init__( self, *, - device_id: builtins.str = ..., - csr: builtins.bytes = ..., - device_name: builtins.str = ..., - description: builtins.str = ..., - restrictions: builtins.str = ..., + device_id: _builtins.str = ..., + csr: _builtins.bytes = ..., + device_name: _builtins.str = ..., + description: _builtins.str = ..., + restrictions: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["csr", b"csr", "description", b"description", "device_id", b"device_id", "device_name", b"device_name", "restrictions", b"restrictions"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["csr", b"csr", "description", b"description", "device_id", b"device_id", "device_name", b"device_name", "restrictions", b"restrictions"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___PairDeviceRequest = PairDeviceRequest +Global___PairDeviceRequest: _TypeAlias = PairDeviceRequest # noqa: Y015 -@typing.final -class PairDeviceResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class PairDeviceResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - DEVICE_CERT_FIELD_NUMBER: builtins.int - DEVICE_KEY_FIELD_NUMBER: builtins.int - RUNE_FIELD_NUMBER: builtins.int - CREDS_FIELD_NUMBER: builtins.int - device_id: builtins.str + DEVICE_ID_FIELD_NUMBER: _builtins.int + DEVICE_CERT_FIELD_NUMBER: _builtins.int + DEVICE_KEY_FIELD_NUMBER: _builtins.int + RUNE_FIELD_NUMBER: _builtins.int + CREDS_FIELD_NUMBER: _builtins.int + device_id: _builtins.str """device_id is the public key of the new device used for the tls cert. """ - device_cert: builtins.str + device_cert: _builtins.str """Upon a pairing request, the device receives back the signed certificate that belongs to the certificate signing request the that was sent with the pairing request, so they can authenticate themselves in the future. """ - device_key: builtins.str + device_key: _builtins.str """The private key that was used to create the certificate with. This key is used to sign the requests to the node. """ - rune: builtins.str + rune: _builtins.str """A rune that is returned if the device that is created by the signer that the device pairs to. """ - creds: builtins.bytes + creds: _builtins.bytes """Creds contains a serialized version of the device certificate, the device key and the rune that are used to authenticate a device at the backend, and to authorize a request at the signer. @@ -703,184 +745,197 @@ class PairDeviceResponse(google.protobuf.message.Message): def __init__( self, *, - device_id: builtins.str = ..., - device_cert: builtins.str = ..., - device_key: builtins.str = ..., - rune: builtins.str = ..., - creds: builtins.bytes = ..., + device_id: _builtins.str = ..., + device_cert: _builtins.str = ..., + device_key: _builtins.str = ..., + rune: _builtins.str = ..., + creds: _builtins.bytes = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_id", b"device_id", "device_key", b"device_key", "rune", b"rune"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["creds", b"creds", "device_cert", b"device_cert", "device_id", b"device_id", "device_key", b"device_key", "rune", b"rune"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___PairDeviceResponse = PairDeviceResponse +Global___PairDeviceResponse: _TypeAlias = PairDeviceResponse # noqa: Y015 -@typing.final -class GetPairingDataRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class GetPairingDataRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - device_id: builtins.str + DEVICE_ID_FIELD_NUMBER: _builtins.int + device_id: _builtins.str """The device_id that the client got from the qr-code.""" def __init__( self, *, - device_id: builtins.str = ..., + device_id: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["device_id", b"device_id"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["device_id", b"device_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___GetPairingDataRequest = GetPairingDataRequest +Global___GetPairingDataRequest: _TypeAlias = GetPairingDataRequest # noqa: Y015 -@typing.final -class GetPairingDataResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class GetPairingDataResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - CSR_FIELD_NUMBER: builtins.int - DEVICE_NAME_FIELD_NUMBER: builtins.int - DESCRIPTION_FIELD_NUMBER: builtins.int - RESTRICTIONS_FIELD_NUMBER: builtins.int - device_id: builtins.str - csr: builtins.bytes + DEVICE_ID_FIELD_NUMBER: _builtins.int + CSR_FIELD_NUMBER: _builtins.int + DEVICE_NAME_FIELD_NUMBER: _builtins.int + DESCRIPTION_FIELD_NUMBER: _builtins.int + RESTRICTIONS_FIELD_NUMBER: _builtins.int + device_id: _builtins.str + csr: _builtins.bytes """The certificate signing request that will be signed by the greenlight backend if the pairing succeeds. Notice that the CN here is irrelevant. """ - device_name: builtins.str + device_name: _builtins.str """The name of the device that will be part of the tls certificate subjects CN field: CN=/users//. """ - description: builtins.str + description: _builtins.str """A human readable description of the device, this can be a purpose or something similar. Can be used to display to the user on the old device. """ - restrictions: builtins.str + restrictions: _builtins.str """A set of restrictions that the new devices requests for the rune. This might in the future get upgraded for easier naming. """ def __init__( self, *, - device_id: builtins.str = ..., - csr: builtins.bytes = ..., - device_name: builtins.str = ..., - description: builtins.str = ..., - restrictions: builtins.str = ..., + device_id: _builtins.str = ..., + csr: _builtins.bytes = ..., + device_name: _builtins.str = ..., + description: _builtins.str = ..., + restrictions: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["csr", b"csr", "description", b"description", "device_id", b"device_id", "device_name", b"device_name", "restrictions", b"restrictions"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["csr", b"csr", "description", b"description", "device_id", b"device_id", "device_name", b"device_name", "restrictions", b"restrictions"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___GetPairingDataResponse = GetPairingDataResponse +Global___GetPairingDataResponse: _TypeAlias = GetPairingDataResponse # noqa: Y015 -@typing.final -class ApprovePairingRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ApprovePairingRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - TIMESTAMP_FIELD_NUMBER: builtins.int - DEVICE_NAME_FIELD_NUMBER: builtins.int - RESTRICTIONS_FIELD_NUMBER: builtins.int - SIG_FIELD_NUMBER: builtins.int - PUBKEY_FIELD_NUMBER: builtins.int - RUNE_FIELD_NUMBER: builtins.int - device_id: builtins.str - timestamp: builtins.int + DEVICE_ID_FIELD_NUMBER: _builtins.int + TIMESTAMP_FIELD_NUMBER: _builtins.int + DEVICE_NAME_FIELD_NUMBER: _builtins.int + RESTRICTIONS_FIELD_NUMBER: _builtins.int + SIG_FIELD_NUMBER: _builtins.int + PUBKEY_FIELD_NUMBER: _builtins.int + RUNE_FIELD_NUMBER: _builtins.int + device_id: _builtins.str + timestamp: _builtins.int """The time that the old device approved the pairing request. This is used by the signer to restrict the duration an approval request is valid. """ - device_name: builtins.str + device_name: _builtins.str """The name of the device that will be part of the tls certificate subjects CN field: CN=/users//. """ - restrictions: builtins.str + restrictions: _builtins.str """The restrictions need a pubkey set.""" - sig: builtins.bytes + sig: _builtins.bytes """The signature of the above to ensure data integrity.""" - pubkey: builtins.bytes + pubkey: _builtins.bytes """The public key corresponding to the private key that was used to sign the request and that is part of the rune; """ - rune: builtins.str + rune: _builtins.str """The rune of the old device with a pubkey field corresponding to the signature above. Used to authorize the approval request. """ def __init__( self, *, - device_id: builtins.str = ..., - timestamp: builtins.int = ..., - device_name: builtins.str = ..., - restrictions: builtins.str = ..., - sig: builtins.bytes = ..., - pubkey: builtins.bytes = ..., - rune: builtins.str = ..., + device_id: _builtins.str = ..., + timestamp: _builtins.int = ..., + device_name: _builtins.str = ..., + restrictions: _builtins.str = ..., + sig: _builtins.bytes = ..., + pubkey: _builtins.bytes = ..., + rune: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["device_id", b"device_id", "device_name", b"device_name", "pubkey", b"pubkey", "restrictions", b"restrictions", "rune", b"rune", "sig", b"sig", "timestamp", b"timestamp"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["device_id", b"device_id", "device_name", b"device_name", "pubkey", b"pubkey", "restrictions", b"restrictions", "rune", b"rune", "sig", b"sig", "timestamp", b"timestamp"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ApprovePairingRequest = ApprovePairingRequest +Global___ApprovePairingRequest: _TypeAlias = ApprovePairingRequest # noqa: Y015 -@typing.final -class ApprovePairingResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class ApprovePairingResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - DEVICE_ID_FIELD_NUMBER: builtins.int - NODE_ID_FIELD_NUMBER: builtins.int - RUNE_FIELD_NUMBER: builtins.int - device_id: builtins.str - node_id: builtins.bytes - rune: builtins.str + DEVICE_ID_FIELD_NUMBER: _builtins.int + NODE_ID_FIELD_NUMBER: _builtins.int + RUNE_FIELD_NUMBER: _builtins.int + device_id: _builtins.str + node_id: _builtins.bytes + rune: _builtins.str def __init__( self, *, - device_id: builtins.str = ..., - node_id: builtins.bytes = ..., - rune: builtins.str = ..., + device_id: _builtins.str = ..., + node_id: _builtins.bytes = ..., + rune: _builtins.str = ..., ) -> None: ... - def ClearField(self, field_name: typing.Literal["device_id", b"device_id", "node_id", b"node_id", "rune", b"rune"]) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["device_id", b"device_id", "node_id", b"node_id", "rune", b"rune"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... -global___ApprovePairingResponse = ApprovePairingResponse +Global___ApprovePairingResponse: _TypeAlias = ApprovePairingResponse # noqa: Y015 -@typing.final -class SignerRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class SignerRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - REQUEST_ID_FIELD_NUMBER: builtins.int - APPROVE_PAIRING_FIELD_NUMBER: builtins.int - request_id: builtins.int - @property - def approve_pairing(self) -> global___ApprovePairingRequest: ... + REQUEST_ID_FIELD_NUMBER: _builtins.int + APPROVE_PAIRING_FIELD_NUMBER: _builtins.int + request_id: _builtins.int + @_builtins.property + def approve_pairing(self) -> Global___ApprovePairingRequest: ... def __init__( self, *, - request_id: builtins.int = ..., - approve_pairing: global___ApprovePairingRequest | None = ..., + request_id: _builtins.int = ..., + approve_pairing: Global___ApprovePairingRequest | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["approve_pairing", b"approve_pairing", "request", b"request"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["approve_pairing", b"approve_pairing", "request", b"request", "request_id", b"request_id"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["approve_pairing"] | None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["approve_pairing", b"approve_pairing", "request", b"request"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["approve_pairing", b"approve_pairing", "request", b"request", "request_id", b"request_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_request: _TypeAlias = _typing.Literal["approve_pairing"] # noqa: Y015 + _WhichOneofArgType_request: _TypeAlias = _typing.Literal["request", b"request"] # noqa: Y015 + def WhichOneof(self, oneof_group: _WhichOneofArgType_request) -> _WhichOneofReturnType_request | None: ... -global___SignerRequest = SignerRequest +Global___SignerRequest: _TypeAlias = SignerRequest # noqa: Y015 -@typing.final -class SignerResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor +@_typing.final +class SignerResponse(_message.Message): + DESCRIPTOR: _descriptor.Descriptor - REQUEST_ID_FIELD_NUMBER: builtins.int - EMPTY_FIELD_NUMBER: builtins.int - APPROVE_PAIRING_FIELD_NUMBER: builtins.int - request_id: builtins.int - @property - def empty(self) -> glclient.greenlight_pb2.Empty: ... - @property - def approve_pairing(self) -> global___ApprovePairingResponse: ... + REQUEST_ID_FIELD_NUMBER: _builtins.int + EMPTY_FIELD_NUMBER: _builtins.int + APPROVE_PAIRING_FIELD_NUMBER: _builtins.int + request_id: _builtins.int + @_builtins.property + def empty(self) -> _greenlight_pb2.Empty: ... + @_builtins.property + def approve_pairing(self) -> Global___ApprovePairingResponse: ... def __init__( self, *, - request_id: builtins.int = ..., - empty: glclient.greenlight_pb2.Empty | None = ..., - approve_pairing: global___ApprovePairingResponse | None = ..., + request_id: _builtins.int = ..., + empty: _greenlight_pb2.Empty | None = ..., + approve_pairing: Global___ApprovePairingResponse | None = ..., ) -> None: ... - def HasField(self, field_name: typing.Literal["approve_pairing", b"approve_pairing", "empty", b"empty", "response", b"response"]) -> builtins.bool: ... - def ClearField(self, field_name: typing.Literal["approve_pairing", b"approve_pairing", "empty", b"empty", "request_id", b"request_id", "response", b"response"]) -> None: ... - def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["empty", "approve_pairing"] | None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["approve_pairing", b"approve_pairing", "empty", b"empty", "response", b"response"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["approve_pairing", b"approve_pairing", "empty", b"empty", "request_id", b"request_id", "response", b"response"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_response: _TypeAlias = _typing.Literal["empty", "approve_pairing"] # noqa: Y015 + _WhichOneofArgType_response: _TypeAlias = _typing.Literal["response", b"response"] # noqa: Y015 + def WhichOneof(self, oneof_group: _WhichOneofArgType_response) -> _WhichOneofReturnType_response | None: ... -global___SignerResponse = SignerResponse +Global___SignerResponse: _TypeAlias = SignerResponse # noqa: Y015 diff --git a/libs/gl-client-py/glclient/scheduler_pb2_grpc.py b/libs/gl-client-py/glclient/scheduler_pb2_grpc.py index 7f7d122f8..97a4bd5bd 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2_grpc.py +++ b/libs/gl-client-py/glclient/scheduler_pb2_grpc.py @@ -6,7 +6,7 @@ from glclient import greenlight_pb2 as glclient_dot_greenlight__pb2 from glclient import scheduler_pb2 as glclient_dot_scheduler__pb2 -GRPC_GENERATED_VERSION = '1.76.0' +GRPC_GENERATED_VERSION = '1.78.0' GRPC_VERSION = grpc.__version__ _version_not_supported = False diff --git a/libs/gl-client-py/glclient/scheduler_pb2_grpc.pyi b/libs/gl-client-py/glclient/scheduler_pb2_grpc.pyi index 1e19a1758..d2c12cfd6 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2_grpc.pyi +++ b/libs/gl-client-py/glclient/scheduler_pb2_grpc.pyi @@ -3,21 +3,30 @@ isort:skip_file """ -import abc -import collections.abc -import glclient.greenlight_pb2 -import glclient.scheduler_pb2 -import grpc -import grpc.aio -import typing +from collections import abc as _abc +from glclient import greenlight_pb2 as _greenlight_pb2 +from glclient import scheduler_pb2 as _scheduler_pb2 +from grpc import aio as _aio +import abc as _abc_1 +import grpc as _grpc +import sys +import typing as _typing -_T = typing.TypeVar("_T") +if sys.version_info >= (3, 11): + from typing import Self as _Self +else: + from typing_extensions import Self as _Self -class _MaybeAsyncIterator(collections.abc.AsyncIterator[_T], collections.abc.Iterator[_T], metaclass=abc.ABCMeta): ... +_T = _typing.TypeVar("_T") -class _ServicerContext(grpc.ServicerContext, grpc.aio.ServicerContext): # type: ignore[misc, type-arg] +class _MaybeAsyncIterator(_abc.AsyncIterator[_T], _abc.Iterator[_T], metaclass=_abc_1.ABCMeta): ... + +class _ServicerContext(_grpc.ServicerContext, _aio.ServicerContext): # type: ignore[misc, type-arg] ... +GRPC_GENERATED_VERSION: str +GRPC_VERSION: str + class SchedulerStub: """The scheduler service is the endpoint which allows users to register a new node with greenlight, recover access to an existing @@ -53,11 +62,11 @@ class SchedulerStub: to use the node-specific mTLS keypair. """ - def __init__(self, channel: typing.Union[grpc.Channel, grpc.aio.Channel]) -> None: ... - Register: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RegistrationRequest, - glclient.scheduler_pb2.RegistrationResponse, - ] + @_typing.overload + def __new__(cls, channel: _grpc.Channel) -> _Self: ... + @_typing.overload + def __new__(cls, channel: _aio.Channel) -> SchedulerAsyncStub: ... + Register: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.RegistrationRequest, _scheduler_pb2.RegistrationResponse] """A user may register a new node with greenlight by providing some basic metadata and proving that they have access to the corresponding private key (see challenge-response @@ -77,11 +86,7 @@ class SchedulerStub: recover the credentials. Notice that this also means that the same node_id cannot be reused for different networks. """ - - Recover: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RecoveryRequest, - glclient.scheduler_pb2.RecoveryResponse, - ] + Recover: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.RecoveryRequest, _scheduler_pb2.RecoveryResponse] """Should a user have lost its credentials (mTLS keypair) for any reason, they may regain access to their node using the Recover RPC method. Similar to the initial registration the @@ -94,11 +99,7 @@ class SchedulerStub: forward. Existing keypairs are not revoked, in order to avoid locking out other authenticated applications. """ - - GetChallenge: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ChallengeRequest, - glclient.scheduler_pb2.ChallengeResponse, - ] + GetChallenge: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ChallengeRequest, _scheduler_pb2.ChallengeResponse] """Challenges are one-time values issued by the server, used to authenticate a user/device against the server. A user or device can authenticate to the server by signing the @@ -110,11 +111,7 @@ class SchedulerStub: or use a challenge with a different scope will result in an error being returned. """ - - Schedule: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ScheduleRequest, - glclient.scheduler_pb2.NodeInfoResponse, - ] + Schedule: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ScheduleRequest, _scheduler_pb2.NodeInfoResponse] """Scheduling takes a previously registered node, locates a free slot in greenlight's infrastructure and allocates it to run the node. The node then goes through the startup @@ -129,11 +126,7 @@ class SchedulerStub: application must use the grpc details and its node-specific mTLS keypair to interact with the node directly. """ - - GetNodeInfo: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.NodeInfoRequest, - glclient.scheduler_pb2.NodeInfoResponse, - ] + GetNodeInfo: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.NodeInfoRequest, _scheduler_pb2.NodeInfoResponse] """Much like `Schedule` this call is used to retrieve the metadata and grpc details of a node. Unlike the other call however it is passive, and will not result in the node @@ -143,11 +136,7 @@ class SchedulerStub: that signs off on changes, but doesn't want to keep the node itself scheduled). """ - - MaybeUpgrade: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.UpgradeRequest, - glclient.scheduler_pb2.UpgradeResponse, - ] + MaybeUpgrade: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.UpgradeRequest, _scheduler_pb2.UpgradeResponse] """The signer may want to trigger an upgrade of the node before waiting for the node to be scheduled. This ensures that the signer version is in sync with the node @@ -158,20 +147,12 @@ class SchedulerStub: non-functional UpgradeRequest may result in unschedulable nodes. """ - - ListInviteCodes: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ListInviteCodesRequest, - glclient.scheduler_pb2.ListInviteCodesResponse, - ] + ListInviteCodes: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ListInviteCodesRequest, _scheduler_pb2.ListInviteCodesResponse] """This call is used to fetch a list of invite codes associated with the node id of the client. These invite codes can be used for further registration of new nodes. """ - - ExportNode: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ExportNodeRequest, - glclient.scheduler_pb2.ExportNodeResponse, - ] + ExportNode: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ExportNodeRequest, _scheduler_pb2.ExportNodeResponse] """Exporting a node allows users to take control of their node This method initiates the node export on Greenlight, @@ -197,31 +178,11 @@ class SchedulerStub: being replayed) and loss of funds (see CLN Backups documentation for more information) """ - - AddOutgoingWebhook: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.AddOutgoingWebhookRequest, - glclient.scheduler_pb2.AddOutgoingWebhookResponse, - ] - - ListOutgoingWebhooks: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ListOutgoingWebhooksRequest, - glclient.scheduler_pb2.ListOutgoingWebhooksResponse, - ] - - DeleteWebhooks: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.DeleteOutgoingWebhooksRequest, - glclient.greenlight_pb2.Empty, - ] - - RotateOutgoingWebhookSecret: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RotateOutgoingWebhookSecretRequest, - glclient.scheduler_pb2.WebhookSecretResponse, - ] - - SignerRequestsStream: grpc.StreamStreamMultiCallable[ - glclient.scheduler_pb2.SignerResponse, - glclient.scheduler_pb2.SignerRequest, - ] + AddOutgoingWebhook: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.AddOutgoingWebhookRequest, _scheduler_pb2.AddOutgoingWebhookResponse] + ListOutgoingWebhooks: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ListOutgoingWebhooksRequest, _scheduler_pb2.ListOutgoingWebhooksResponse] + DeleteWebhooks: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.DeleteOutgoingWebhooksRequest, _greenlight_pb2.Empty] + RotateOutgoingWebhookSecret: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.RotateOutgoingWebhookSecretRequest, _scheduler_pb2.WebhookSecretResponse] + SignerRequestsStream: _grpc.StreamStreamMultiCallable[_scheduler_pb2.SignerResponse, _scheduler_pb2.SignerRequest] """Attaches a Signer via a bidirectional stream to the scheduler. This is a communication channel between greenlight and the signing device that is used for requests that are not part of the node api. @@ -230,7 +191,8 @@ class SchedulerStub: the signer answers with a ApprovePairingResponse. """ -class SchedulerAsyncStub: +@_typing.type_check_only +class SchedulerAsyncStub(SchedulerStub): """The scheduler service is the endpoint which allows users to register a new node with greenlight, recover access to an existing node if the owner lost its credentials, schedule the node to be run @@ -265,10 +227,8 @@ class SchedulerAsyncStub: to use the node-specific mTLS keypair. """ - Register: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RegistrationRequest, - glclient.scheduler_pb2.RegistrationResponse, - ] + def __init__(self, channel: _aio.Channel) -> None: ... + Register: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.RegistrationRequest, _scheduler_pb2.RegistrationResponse] # type: ignore[assignment] """A user may register a new node with greenlight by providing some basic metadata and proving that they have access to the corresponding private key (see challenge-response @@ -288,11 +248,7 @@ class SchedulerAsyncStub: recover the credentials. Notice that this also means that the same node_id cannot be reused for different networks. """ - - Recover: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RecoveryRequest, - glclient.scheduler_pb2.RecoveryResponse, - ] + Recover: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.RecoveryRequest, _scheduler_pb2.RecoveryResponse] # type: ignore[assignment] """Should a user have lost its credentials (mTLS keypair) for any reason, they may regain access to their node using the Recover RPC method. Similar to the initial registration the @@ -305,11 +261,7 @@ class SchedulerAsyncStub: forward. Existing keypairs are not revoked, in order to avoid locking out other authenticated applications. """ - - GetChallenge: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ChallengeRequest, - glclient.scheduler_pb2.ChallengeResponse, - ] + GetChallenge: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ChallengeRequest, _scheduler_pb2.ChallengeResponse] # type: ignore[assignment] """Challenges are one-time values issued by the server, used to authenticate a user/device against the server. A user or device can authenticate to the server by signing the @@ -321,11 +273,7 @@ class SchedulerAsyncStub: or use a challenge with a different scope will result in an error being returned. """ - - Schedule: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ScheduleRequest, - glclient.scheduler_pb2.NodeInfoResponse, - ] + Schedule: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ScheduleRequest, _scheduler_pb2.NodeInfoResponse] # type: ignore[assignment] """Scheduling takes a previously registered node, locates a free slot in greenlight's infrastructure and allocates it to run the node. The node then goes through the startup @@ -340,11 +288,7 @@ class SchedulerAsyncStub: application must use the grpc details and its node-specific mTLS keypair to interact with the node directly. """ - - GetNodeInfo: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.NodeInfoRequest, - glclient.scheduler_pb2.NodeInfoResponse, - ] + GetNodeInfo: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.NodeInfoRequest, _scheduler_pb2.NodeInfoResponse] # type: ignore[assignment] """Much like `Schedule` this call is used to retrieve the metadata and grpc details of a node. Unlike the other call however it is passive, and will not result in the node @@ -354,11 +298,7 @@ class SchedulerAsyncStub: that signs off on changes, but doesn't want to keep the node itself scheduled). """ - - MaybeUpgrade: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.UpgradeRequest, - glclient.scheduler_pb2.UpgradeResponse, - ] + MaybeUpgrade: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.UpgradeRequest, _scheduler_pb2.UpgradeResponse] # type: ignore[assignment] """The signer may want to trigger an upgrade of the node before waiting for the node to be scheduled. This ensures that the signer version is in sync with the node @@ -369,20 +309,12 @@ class SchedulerAsyncStub: non-functional UpgradeRequest may result in unschedulable nodes. """ - - ListInviteCodes: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ListInviteCodesRequest, - glclient.scheduler_pb2.ListInviteCodesResponse, - ] + ListInviteCodes: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ListInviteCodesRequest, _scheduler_pb2.ListInviteCodesResponse] # type: ignore[assignment] """This call is used to fetch a list of invite codes associated with the node id of the client. These invite codes can be used for further registration of new nodes. """ - - ExportNode: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ExportNodeRequest, - glclient.scheduler_pb2.ExportNodeResponse, - ] + ExportNode: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ExportNodeRequest, _scheduler_pb2.ExportNodeResponse] # type: ignore[assignment] """Exporting a node allows users to take control of their node This method initiates the node export on Greenlight, @@ -408,31 +340,11 @@ class SchedulerAsyncStub: being replayed) and loss of funds (see CLN Backups documentation for more information) """ - - AddOutgoingWebhook: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.AddOutgoingWebhookRequest, - glclient.scheduler_pb2.AddOutgoingWebhookResponse, - ] - - ListOutgoingWebhooks: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ListOutgoingWebhooksRequest, - glclient.scheduler_pb2.ListOutgoingWebhooksResponse, - ] - - DeleteWebhooks: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.DeleteOutgoingWebhooksRequest, - glclient.greenlight_pb2.Empty, - ] - - RotateOutgoingWebhookSecret: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.RotateOutgoingWebhookSecretRequest, - glclient.scheduler_pb2.WebhookSecretResponse, - ] - - SignerRequestsStream: grpc.aio.StreamStreamMultiCallable[ - glclient.scheduler_pb2.SignerResponse, - glclient.scheduler_pb2.SignerRequest, - ] + AddOutgoingWebhook: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.AddOutgoingWebhookRequest, _scheduler_pb2.AddOutgoingWebhookResponse] # type: ignore[assignment] + ListOutgoingWebhooks: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ListOutgoingWebhooksRequest, _scheduler_pb2.ListOutgoingWebhooksResponse] # type: ignore[assignment] + DeleteWebhooks: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.DeleteOutgoingWebhooksRequest, _greenlight_pb2.Empty] # type: ignore[assignment] + RotateOutgoingWebhookSecret: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.RotateOutgoingWebhookSecretRequest, _scheduler_pb2.WebhookSecretResponse] # type: ignore[assignment] + SignerRequestsStream: _aio.StreamStreamMultiCallable[_scheduler_pb2.SignerResponse, _scheduler_pb2.SignerRequest] # type: ignore[assignment] """Attaches a Signer via a bidirectional stream to the scheduler. This is a communication channel between greenlight and the signing device that is used for requests that are not part of the node api. @@ -441,7 +353,7 @@ class SchedulerAsyncStub: the signer answers with a ApprovePairingResponse. """ -class SchedulerServicer(metaclass=abc.ABCMeta): +class SchedulerServicer(metaclass=_abc_1.ABCMeta): """The scheduler service is the endpoint which allows users to register a new node with greenlight, recover access to an existing node if the owner lost its credentials, schedule the node to be run @@ -476,12 +388,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): to use the node-specific mTLS keypair. """ - @abc.abstractmethod + @_abc_1.abstractmethod def Register( self, - request: glclient.scheduler_pb2.RegistrationRequest, + request: _scheduler_pb2.RegistrationRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.RegistrationResponse, collections.abc.Awaitable[glclient.scheduler_pb2.RegistrationResponse]]: + ) -> _typing.Union[_scheduler_pb2.RegistrationResponse, _abc.Awaitable[_scheduler_pb2.RegistrationResponse]]: """A user may register a new node with greenlight by providing some basic metadata and proving that they have access to the corresponding private key (see challenge-response @@ -502,12 +414,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): the same node_id cannot be reused for different networks. """ - @abc.abstractmethod + @_abc_1.abstractmethod def Recover( self, - request: glclient.scheduler_pb2.RecoveryRequest, + request: _scheduler_pb2.RecoveryRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.RecoveryResponse, collections.abc.Awaitable[glclient.scheduler_pb2.RecoveryResponse]]: + ) -> _typing.Union[_scheduler_pb2.RecoveryResponse, _abc.Awaitable[_scheduler_pb2.RecoveryResponse]]: """Should a user have lost its credentials (mTLS keypair) for any reason, they may regain access to their node using the Recover RPC method. Similar to the initial registration the @@ -521,12 +433,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): avoid locking out other authenticated applications. """ - @abc.abstractmethod + @_abc_1.abstractmethod def GetChallenge( self, - request: glclient.scheduler_pb2.ChallengeRequest, + request: _scheduler_pb2.ChallengeRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.ChallengeResponse, collections.abc.Awaitable[glclient.scheduler_pb2.ChallengeResponse]]: + ) -> _typing.Union[_scheduler_pb2.ChallengeResponse, _abc.Awaitable[_scheduler_pb2.ChallengeResponse]]: """Challenges are one-time values issued by the server, used to authenticate a user/device against the server. A user or device can authenticate to the server by signing the @@ -539,12 +451,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): error being returned. """ - @abc.abstractmethod + @_abc_1.abstractmethod def Schedule( self, - request: glclient.scheduler_pb2.ScheduleRequest, + request: _scheduler_pb2.ScheduleRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.NodeInfoResponse, collections.abc.Awaitable[glclient.scheduler_pb2.NodeInfoResponse]]: + ) -> _typing.Union[_scheduler_pb2.NodeInfoResponse, _abc.Awaitable[_scheduler_pb2.NodeInfoResponse]]: """Scheduling takes a previously registered node, locates a free slot in greenlight's infrastructure and allocates it to run the node. The node then goes through the startup @@ -560,12 +472,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): mTLS keypair to interact with the node directly. """ - @abc.abstractmethod + @_abc_1.abstractmethod def GetNodeInfo( self, - request: glclient.scheduler_pb2.NodeInfoRequest, + request: _scheduler_pb2.NodeInfoRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.NodeInfoResponse, collections.abc.Awaitable[glclient.scheduler_pb2.NodeInfoResponse]]: + ) -> _typing.Union[_scheduler_pb2.NodeInfoResponse, _abc.Awaitable[_scheduler_pb2.NodeInfoResponse]]: """Much like `Schedule` this call is used to retrieve the metadata and grpc details of a node. Unlike the other call however it is passive, and will not result in the node @@ -576,12 +488,12 @@ class SchedulerServicer(metaclass=abc.ABCMeta): node itself scheduled). """ - @abc.abstractmethod + @_abc_1.abstractmethod def MaybeUpgrade( self, - request: glclient.scheduler_pb2.UpgradeRequest, + request: _scheduler_pb2.UpgradeRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.UpgradeResponse, collections.abc.Awaitable[glclient.scheduler_pb2.UpgradeResponse]]: + ) -> _typing.Union[_scheduler_pb2.UpgradeResponse, _abc.Awaitable[_scheduler_pb2.UpgradeResponse]]: """The signer may want to trigger an upgrade of the node before waiting for the node to be scheduled. This ensures that the signer version is in sync with the node @@ -593,23 +505,23 @@ class SchedulerServicer(metaclass=abc.ABCMeta): nodes. """ - @abc.abstractmethod + @_abc_1.abstractmethod def ListInviteCodes( self, - request: glclient.scheduler_pb2.ListInviteCodesRequest, + request: _scheduler_pb2.ListInviteCodesRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.ListInviteCodesResponse, collections.abc.Awaitable[glclient.scheduler_pb2.ListInviteCodesResponse]]: + ) -> _typing.Union[_scheduler_pb2.ListInviteCodesResponse, _abc.Awaitable[_scheduler_pb2.ListInviteCodesResponse]]: """This call is used to fetch a list of invite codes associated with the node id of the client. These invite codes can be used for further registration of new nodes. """ - @abc.abstractmethod + @_abc_1.abstractmethod def ExportNode( self, - request: glclient.scheduler_pb2.ExportNodeRequest, + request: _scheduler_pb2.ExportNodeRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.ExportNodeResponse, collections.abc.Awaitable[glclient.scheduler_pb2.ExportNodeResponse]]: + ) -> _typing.Union[_scheduler_pb2.ExportNodeResponse, _abc.Awaitable[_scheduler_pb2.ExportNodeResponse]]: """Exporting a node allows users to take control of their node This method initiates the node export on Greenlight, @@ -636,40 +548,40 @@ class SchedulerServicer(metaclass=abc.ABCMeta): documentation for more information) """ - @abc.abstractmethod + @_abc_1.abstractmethod def AddOutgoingWebhook( self, - request: glclient.scheduler_pb2.AddOutgoingWebhookRequest, + request: _scheduler_pb2.AddOutgoingWebhookRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.AddOutgoingWebhookResponse, collections.abc.Awaitable[glclient.scheduler_pb2.AddOutgoingWebhookResponse]]: ... + ) -> _typing.Union[_scheduler_pb2.AddOutgoingWebhookResponse, _abc.Awaitable[_scheduler_pb2.AddOutgoingWebhookResponse]]: ... - @abc.abstractmethod + @_abc_1.abstractmethod def ListOutgoingWebhooks( self, - request: glclient.scheduler_pb2.ListOutgoingWebhooksRequest, + request: _scheduler_pb2.ListOutgoingWebhooksRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.ListOutgoingWebhooksResponse, collections.abc.Awaitable[glclient.scheduler_pb2.ListOutgoingWebhooksResponse]]: ... + ) -> _typing.Union[_scheduler_pb2.ListOutgoingWebhooksResponse, _abc.Awaitable[_scheduler_pb2.ListOutgoingWebhooksResponse]]: ... - @abc.abstractmethod + @_abc_1.abstractmethod def DeleteWebhooks( self, - request: glclient.scheduler_pb2.DeleteOutgoingWebhooksRequest, + request: _scheduler_pb2.DeleteOutgoingWebhooksRequest, context: _ServicerContext, - ) -> typing.Union[glclient.greenlight_pb2.Empty, collections.abc.Awaitable[glclient.greenlight_pb2.Empty]]: ... + ) -> _typing.Union[_greenlight_pb2.Empty, _abc.Awaitable[_greenlight_pb2.Empty]]: ... - @abc.abstractmethod + @_abc_1.abstractmethod def RotateOutgoingWebhookSecret( self, - request: glclient.scheduler_pb2.RotateOutgoingWebhookSecretRequest, + request: _scheduler_pb2.RotateOutgoingWebhookSecretRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.WebhookSecretResponse, collections.abc.Awaitable[glclient.scheduler_pb2.WebhookSecretResponse]]: ... + ) -> _typing.Union[_scheduler_pb2.WebhookSecretResponse, _abc.Awaitable[_scheduler_pb2.WebhookSecretResponse]]: ... - @abc.abstractmethod + @_abc_1.abstractmethod def SignerRequestsStream( self, - request_iterator: _MaybeAsyncIterator[glclient.scheduler_pb2.SignerResponse], + request_iterator: _MaybeAsyncIterator[_scheduler_pb2.SignerResponse], context: _ServicerContext, - ) -> typing.Union[collections.abc.Iterator[glclient.scheduler_pb2.SignerRequest], collections.abc.AsyncIterator[glclient.scheduler_pb2.SignerRequest]]: + ) -> _typing.Union[_abc.Iterator[_scheduler_pb2.SignerRequest], _abc.AsyncIterator[_scheduler_pb2.SignerRequest]]: """Attaches a Signer via a bidirectional stream to the scheduler. This is a communication channel between greenlight and the signing device that is used for requests that are not part of the node api. @@ -678,16 +590,16 @@ class SchedulerServicer(metaclass=abc.ABCMeta): the signer answers with a ApprovePairingResponse. """ -def add_SchedulerServicer_to_server(servicer: SchedulerServicer, server: typing.Union[grpc.Server, grpc.aio.Server]) -> None: ... +def add_SchedulerServicer_to_server(servicer: SchedulerServicer, server: _typing.Union[_grpc.Server, _aio.Server]) -> None: ... class DebugStub: """A service to collect debugging information from clients.""" - def __init__(self, channel: typing.Union[grpc.Channel, grpc.aio.Channel]) -> None: ... - ReportSignerRejection: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.SignerRejection, - glclient.greenlight_pb2.Empty, - ] + @_typing.overload + def __new__(cls, channel: _grpc.Channel) -> _Self: ... + @_typing.overload + def __new__(cls, channel: _aio.Channel) -> DebugAsyncStub: ... + ReportSignerRejection: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.SignerRejection, _greenlight_pb2.Empty] """The signer is designed to fail closed, i.e., we reject requests that do not resolve or that go against one of its policies. This comes with some issues, such as false negatives, where we reject @@ -697,13 +609,12 @@ class DebugStub: fine-tine the signer's ruleset. """ -class DebugAsyncStub: +@_typing.type_check_only +class DebugAsyncStub(DebugStub): """A service to collect debugging information from clients.""" - ReportSignerRejection: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.SignerRejection, - glclient.greenlight_pb2.Empty, - ] + def __init__(self, channel: _aio.Channel) -> None: ... + ReportSignerRejection: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.SignerRejection, _greenlight_pb2.Empty] # type: ignore[assignment] """The signer is designed to fail closed, i.e., we reject requests that do not resolve or that go against one of its policies. This comes with some issues, such as false negatives, where we reject @@ -713,15 +624,15 @@ class DebugAsyncStub: fine-tine the signer's ruleset. """ -class DebugServicer(metaclass=abc.ABCMeta): +class DebugServicer(metaclass=_abc_1.ABCMeta): """A service to collect debugging information from clients.""" - @abc.abstractmethod + @_abc_1.abstractmethod def ReportSignerRejection( self, - request: glclient.scheduler_pb2.SignerRejection, + request: _scheduler_pb2.SignerRejection, context: _ServicerContext, - ) -> typing.Union[glclient.greenlight_pb2.Empty, collections.abc.Awaitable[glclient.greenlight_pb2.Empty]]: + ) -> _typing.Union[_greenlight_pb2.Empty, _abc.Awaitable[_greenlight_pb2.Empty]]: """The signer is designed to fail closed, i.e., we reject requests that do not resolve or that go against one of its policies. This comes with some issues, such as false negatives, where we reject @@ -731,103 +642,86 @@ class DebugServicer(metaclass=abc.ABCMeta): fine-tine the signer's ruleset. """ -def add_DebugServicer_to_server(servicer: DebugServicer, server: typing.Union[grpc.Server, grpc.aio.Server]) -> None: ... +def add_DebugServicer_to_server(servicer: DebugServicer, server: _typing.Union[_grpc.Server, _aio.Server]) -> None: ... class PairingStub: """A service to pair signer-less clients with an existing signer.""" - def __init__(self, channel: typing.Union[grpc.Channel, grpc.aio.Channel]) -> None: ... - PairDevice: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.PairDeviceRequest, - glclient.scheduler_pb2.PairDeviceResponse, - ] + @_typing.overload + def __new__(cls, channel: _grpc.Channel) -> _Self: ... + @_typing.overload + def __new__(cls, channel: _aio.Channel) -> PairingAsyncStub: ... + PairDevice: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.PairDeviceRequest, _scheduler_pb2.PairDeviceResponse] """Initiates a new Pairing Sessions. This is called by the new device that wants to request a pairing from an existing device. The session lifetime is bound to the stream so closing the stream destroys the session. """ - - GetPairingData: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.GetPairingDataRequest, - glclient.scheduler_pb2.GetPairingDataResponse, - ] + GetPairingData: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.GetPairingDataRequest, _scheduler_pb2.GetPairingDataResponse] """Returns the pairing related data that belongs to a pairing session. This is meant to be called from a device that can approve a pairing request, we sometimes call it "old device". """ - - ApprovePairing: grpc.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ApprovePairingRequest, - glclient.greenlight_pb2.Empty, - ] + ApprovePairing: _grpc.UnaryUnaryMultiCallable[_scheduler_pb2.ApprovePairingRequest, _greenlight_pb2.Empty] """Approves a pairing request. The ApprovePairingRequest is forwarded to a signer for further processing. """ -class PairingAsyncStub: +@_typing.type_check_only +class PairingAsyncStub(PairingStub): """A service to pair signer-less clients with an existing signer.""" - PairDevice: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.PairDeviceRequest, - glclient.scheduler_pb2.PairDeviceResponse, - ] + def __init__(self, channel: _aio.Channel) -> None: ... + PairDevice: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.PairDeviceRequest, _scheduler_pb2.PairDeviceResponse] # type: ignore[assignment] """Initiates a new Pairing Sessions. This is called by the new device that wants to request a pairing from an existing device. The session lifetime is bound to the stream so closing the stream destroys the session. """ - - GetPairingData: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.GetPairingDataRequest, - glclient.scheduler_pb2.GetPairingDataResponse, - ] + GetPairingData: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.GetPairingDataRequest, _scheduler_pb2.GetPairingDataResponse] # type: ignore[assignment] """Returns the pairing related data that belongs to a pairing session. This is meant to be called from a device that can approve a pairing request, we sometimes call it "old device". """ - - ApprovePairing: grpc.aio.UnaryUnaryMultiCallable[ - glclient.scheduler_pb2.ApprovePairingRequest, - glclient.greenlight_pb2.Empty, - ] + ApprovePairing: _aio.UnaryUnaryMultiCallable[_scheduler_pb2.ApprovePairingRequest, _greenlight_pb2.Empty] # type: ignore[assignment] """Approves a pairing request. The ApprovePairingRequest is forwarded to a signer for further processing. """ -class PairingServicer(metaclass=abc.ABCMeta): +class PairingServicer(metaclass=_abc_1.ABCMeta): """A service to pair signer-less clients with an existing signer.""" - @abc.abstractmethod + @_abc_1.abstractmethod def PairDevice( self, - request: glclient.scheduler_pb2.PairDeviceRequest, + request: _scheduler_pb2.PairDeviceRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.PairDeviceResponse, collections.abc.Awaitable[glclient.scheduler_pb2.PairDeviceResponse]]: + ) -> _typing.Union[_scheduler_pb2.PairDeviceResponse, _abc.Awaitable[_scheduler_pb2.PairDeviceResponse]]: """Initiates a new Pairing Sessions. This is called by the new device that wants to request a pairing from an existing device. The session lifetime is bound to the stream so closing the stream destroys the session. """ - @abc.abstractmethod + @_abc_1.abstractmethod def GetPairingData( self, - request: glclient.scheduler_pb2.GetPairingDataRequest, + request: _scheduler_pb2.GetPairingDataRequest, context: _ServicerContext, - ) -> typing.Union[glclient.scheduler_pb2.GetPairingDataResponse, collections.abc.Awaitable[glclient.scheduler_pb2.GetPairingDataResponse]]: + ) -> _typing.Union[_scheduler_pb2.GetPairingDataResponse, _abc.Awaitable[_scheduler_pb2.GetPairingDataResponse]]: """Returns the pairing related data that belongs to a pairing session. This is meant to be called from a device that can approve a pairing request, we sometimes call it "old device". """ - @abc.abstractmethod + @_abc_1.abstractmethod def ApprovePairing( self, - request: glclient.scheduler_pb2.ApprovePairingRequest, + request: _scheduler_pb2.ApprovePairingRequest, context: _ServicerContext, - ) -> typing.Union[glclient.greenlight_pb2.Empty, collections.abc.Awaitable[glclient.greenlight_pb2.Empty]]: + ) -> _typing.Union[_greenlight_pb2.Empty, _abc.Awaitable[_greenlight_pb2.Empty]]: """Approves a pairing request. The ApprovePairingRequest is forwarded to a signer for further processing. """ -def add_PairingServicer_to_server(servicer: PairingServicer, server: typing.Union[grpc.Server, grpc.aio.Server]) -> None: ... +def add_PairingServicer_to_server(servicer: PairingServicer, server: _typing.Union[_grpc.Server, _aio.Server]) -> None: ... diff --git a/libs/gl-client/.resources/proto/glclient/greenlight.proto b/libs/gl-client/.resources/proto/glclient/greenlight.proto index 67c3fafe7..b11d03ae2 100644 --- a/libs/gl-client/.resources/proto/glclient/greenlight.proto +++ b/libs/gl-client/.resources/proto/glclient/greenlight.proto @@ -156,6 +156,7 @@ message SignerStateEntry { uint64 version = 1; string key = 2; bytes value = 3; + bytes signature = 4; } // This represents a grpc request that is currently pending, along diff --git a/libs/gl-client/src/persist.rs b/libs/gl-client/src/persist.rs index 624fb75de..6bd97e1cf 100644 --- a/libs/gl-client/src/persist.rs +++ b/libs/gl-client/src/persist.rs @@ -435,6 +435,7 @@ impl Into> for State { key: k.to_owned(), value: serde_json::to_vec(&v.1).unwrap(), version: v.0, + signature: vec![], }) .collect() } diff --git a/libs/gl-plugin/.resources/proto/glclient/greenlight.proto b/libs/gl-plugin/.resources/proto/glclient/greenlight.proto index 67c3fafe7..b11d03ae2 100644 --- a/libs/gl-plugin/.resources/proto/glclient/greenlight.proto +++ b/libs/gl-plugin/.resources/proto/glclient/greenlight.proto @@ -156,6 +156,7 @@ message SignerStateEntry { uint64 version = 1; string key = 2; bytes value = 3; + bytes signature = 4; } // This represents a grpc request that is currently pending, along diff --git a/libs/gl-plugin/src/node/mod.rs b/libs/gl-plugin/src/node/mod.rs index f31d62e72..2ed8890ad 100644 --- a/libs/gl-plugin/src/node/mod.rs +++ b/libs/gl-plugin/src/node/mod.rs @@ -427,6 +427,7 @@ impl Node for PluginNodeServer { key: s.key, version: s.version, value: s.value, + signature: s.signature, }) .collect(); trace!( @@ -493,6 +494,7 @@ impl Node for PluginNodeServer { key: s.key, version: s.version, value: s.value, + signature: s.signature, }) .collect(); @@ -568,6 +570,7 @@ impl Node for PluginNodeServer { key: i.key.to_owned(), value: i.value.to_owned(), version: i.version, + signature: i.signature.to_owned(), }) .collect(); let new_state: gl_client::persist::State = signer_state.into(); diff --git a/libs/gl-sdk/glsdk/glsdk.py b/libs/gl-sdk/glsdk/glsdk.py index 340fbee8b..a2ea7cb2a 100644 --- a/libs/gl-sdk/glsdk/glsdk.py +++ b/libs/gl-sdk/glsdk/glsdk.py @@ -1921,15 +1921,24 @@ def write(value, buf): class ReceiveResponse: bolt11: "str" - def __init__(self, *, bolt11: "str"): + opening_fee_msat: "int" + """ + The fee charged by the LSP for opening a JIT channel, in + millisatoshi. This is 0 if no JIT channel was needed. + """ + + def __init__(self, *, bolt11: "str", opening_fee_msat: "int"): self.bolt11 = bolt11 + self.opening_fee_msat = opening_fee_msat def __str__(self): - return "ReceiveResponse(bolt11={})".format(self.bolt11) + return "ReceiveResponse(bolt11={}, opening_fee_msat={})".format(self.bolt11, self.opening_fee_msat) def __eq__(self, other): if self.bolt11 != other.bolt11: return False + if self.opening_fee_msat != other.opening_fee_msat: + return False return True class _UniffiConverterTypeReceiveResponse(_UniffiConverterRustBuffer): @@ -1937,15 +1946,18 @@ class _UniffiConverterTypeReceiveResponse(_UniffiConverterRustBuffer): def read(buf): return ReceiveResponse( bolt11=_UniffiConverterString.read(buf), + opening_fee_msat=_UniffiConverterUInt64.read(buf), ) @staticmethod def check_lower(value): _UniffiConverterString.check_lower(value.bolt11) + _UniffiConverterUInt64.check_lower(value.opening_fee_msat) @staticmethod def write(value, buf): _UniffiConverterString.write(value.bolt11, buf) + _UniffiConverterUInt64.write(value.opening_fee_msat, buf) class SendResponse: diff --git a/libs/gl-signerproxy/.resources/proto/glclient/greenlight.proto b/libs/gl-signerproxy/.resources/proto/glclient/greenlight.proto index a75224793..b11d03ae2 100644 --- a/libs/gl-signerproxy/.resources/proto/glclient/greenlight.proto +++ b/libs/gl-signerproxy/.resources/proto/glclient/greenlight.proto @@ -14,9 +14,14 @@ package greenlight; // // Deprecated methods are being replaced by the standardized and // automatically managed cln-grpc protocol you can find in -// `node.proto` +// `node.proto`. This interface consists mostly of +// Greenlight-specific, and backported functionality. service Node { - // Stream incoming payments + // Create an invoice to request an incoming payment. Includes LSP + // negotiation to open a channel on-demand when needed. + rpc LspInvoice(LspInvoiceRequest) returns (LspInvoiceResponse) {} + + // Stream incoming payments // // Currently includes off-chain payments received matching an // invoice or spontaneus paymens through keysend. @@ -38,6 +43,14 @@ service Node { // replayed if the stream is interrupted. rpc StreamCustommsg(StreamCustommsgRequest) returns (stream Custommsg) {} + // Stream node events in real-time. + // + // This is a unified event stream that delivers various node events + // as they occur, including invoice updates, peer changes, channel + // state changes, and balance updates. Events are not persisted and + // will not be replayed if the stream is interrupted. + rpc StreamNodeEvents(NodeEventsRequest) returns (stream NodeEvent) {} + //////////////////////////////// HSM Messages //////////////////////// // // The following messages are related to communicating HSM @@ -143,6 +156,7 @@ message SignerStateEntry { uint64 version = 1; string key = 2; bytes value = 3; + bytes signature = 4; } // This represents a grpc request that is currently pending, along @@ -174,8 +188,8 @@ message NodeConfig { // The `GlConfig` is used to pass greenlight-specific startup parameters -// to the node. The `gl-plugin` will look for a serialized config object in -// the node's datastore to load these values from. Please refer to the +// to the node. The `gl-plugin` will look for a serialized config object in +// the node's datastore to load these values from. Please refer to the // individual fields to learn what they do. message GlConfig { string close_to_addr = 1; @@ -221,3 +235,59 @@ message TrampolinePayResponse { uint64 amount_sent_msat = 6; bytes destination = 7; } + +message LspInvoiceRequest { + string lsp_id = 1; // len=0 => None, let the server decide. + // Optional: for discounts/API keys + string token = 2; // len=0 => None + // Pass-through of cln invoice rpc params + uint64 amount_msat = 3; // 0 => Any + string description = 4; + string label = 5; +} +message LspInvoiceResponse { + string bolt11 = 1; + uint32 created_index = 2; + uint32 expires_at = 3; + bytes payment_hash = 4; + bytes payment_secret = 5; + // The fee charged by the LSP for opening a JIT channel, in + // millisatoshi. This is 0 if the node already had sufficient + // incoming capacity and no JIT channel was needed. + uint64 opening_fee_msat = 6; +} + +// Request for streaming node events. Currently empty but defined as +// its own message type to allow adding filters in the future (e.g., +// filter by event type, invoice label, etc.) +message NodeEventsRequest { +} + +// A real-time event from the node. Uses oneof to discriminate between +// different event types. +message NodeEvent { + oneof event { + InvoicePaid invoice_paid = 1; + // Future event types: + // PeerConnected peer_connected = 2; + // PeerDisconnected peer_disconnected = 3; + // ChannelStateChanged channel_state_changed = 4; + // BalanceChanged balance_changed = 5; + } +} + +// Event emitted when an invoice is paid. +message InvoicePaid { + // The payment hash of the paid invoice. + bytes payment_hash = 1; + // The bolt11 invoice string. + string bolt11 = 2; + // The preimage that proves payment. + bytes preimage = 3; + // The label assigned to the invoice. + string label = 4; + // Amount received in millisatoshis. + uint64 amount_msat = 5; + // Extra TLV fields included in the payment. + repeated TlvField extratlvs = 6; +} diff --git a/libs/proto/glclient/greenlight.proto b/libs/proto/glclient/greenlight.proto index 67c3fafe7..b11d03ae2 100644 --- a/libs/proto/glclient/greenlight.proto +++ b/libs/proto/glclient/greenlight.proto @@ -156,6 +156,7 @@ message SignerStateEntry { uint64 version = 1; string key = 2; bytes value = 3; + bytes signature = 4; } // This represents a grpc request that is currently pending, along From decbfc9a953612baee535242e7fa96a69035f34c Mon Sep 17 00:00:00 2001 From: Ihor Diachenko Date: Tue, 3 Mar 2026 18:31:59 +0200 Subject: [PATCH 2/5] Added signature persistence --- libs/gl-client/src/persist.rs | 434 +++++++++++++++++++++++++--------- 1 file changed, 323 insertions(+), 111 deletions(-) diff --git a/libs/gl-client/src/persist.rs b/libs/gl-client/src/persist.rs index 6bd97e1cf..fae12e00a 100644 --- a/libs/gl-client/src/persist.rs +++ b/libs/gl-client/src/persist.rs @@ -9,8 +9,11 @@ use lightning_signer::persist::{Error, Persist, SignerId}; use lightning_signer::policy::validator::ValidatorFactory; use lightning_signer::SendSync; use log::{trace, warn}; -use serde::{Deserialize, Serialize}; +use serde::de::{self, SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::BTreeMap; +use std::fmt; use std::str::FromStr; use std::sync::Arc; use std::sync::Mutex; @@ -22,9 +25,88 @@ const ALLOWLIST_PREFIX: &str = "allowlists"; const TRACKER_PREFIX: &str = "trackers"; const TOMBSTONE_VERSION: u64 = u64::MAX; +#[derive(Clone, Debug, PartialEq)] +struct StateEntry { + version: u64, + value: serde_json::Value, + signature: Vec, +} + +impl StateEntry { + fn new(version: u64, value: serde_json::Value) -> Self { + Self { + version, + value, + signature: vec![], + } + } +} + +impl Serialize for StateEntry { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = if self.signature.is_empty() { + serializer.serialize_seq(Some(2))? + } else { + serializer.serialize_seq(Some(3))? + }; + + seq.serialize_element(&self.version)?; + seq.serialize_element(&self.value)?; + if !self.signature.is_empty() { + seq.serialize_element(&self.signature)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for StateEntry { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct StateEntryVisitor; + + impl<'de> Visitor<'de> for StateEntryVisitor { + type Value = StateEntry; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a tuple [version, value] or [version, value, signature]") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let version = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let value = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let signature = seq.next_element()?.unwrap_or_default(); + + if seq.next_element::()?.is_some() { + return Err(de::Error::invalid_length(4, &self)); + } + + Ok(StateEntry { + version, + value, + signature, + }) + } + } + + deserializer.deserialize_seq(StateEntryVisitor) + } +} + #[derive(Clone, Serialize, Deserialize)] pub struct State { - values: BTreeMap, + values: BTreeMap, } impl State { @@ -41,11 +123,16 @@ impl State { let node_version = self.next_version(&node_key); let state_version = self.next_version(&state_key); - self.values - .insert(node_key, (node_version, serde_json::to_value(node_entry).unwrap())); + self.values.insert( + node_key, + StateEntry::new(node_version, serde_json::to_value(node_entry).unwrap()), + ); self.values.insert( state_key, - (state_version, serde_json::to_value(node_state_entry).unwrap()), + StateEntry::new( + state_version, + serde_json::to_value(node_state_entry).unwrap(), + ), ); Ok(()) } @@ -65,7 +152,7 @@ impl State { .values .get_mut(&key) .expect("attempting to update non-existent node"); - *v = (v.0 + 1, serde_json::to_value(node_state).unwrap()); + *v = StateEntry::new(v.version + 1, serde_json::to_value(node_state).unwrap()); Ok(()) } @@ -100,8 +187,10 @@ impl State { let key = format!("{CHANNEL_PREFIX}/{key}"); self.ensure_not_tombstone(&key)?; let version = self.next_version(&key); - self.values - .insert(key, (version, serde_json::to_value(channel_entry).unwrap())); + self.values.insert( + key, + StateEntry::new(version, serde_json::to_value(channel_entry).unwrap()), + ); Ok(()) } @@ -122,7 +211,7 @@ impl State { .values .get_mut(&key) .expect("attempting to update non-existent channel"); - *v = (v.0 + 1, serde_json::to_value(channel_entry).unwrap()); + *v = StateEntry::new(v.version + 1, serde_json::to_value(channel_entry).unwrap()); Ok(()) } @@ -139,7 +228,7 @@ impl State { .get(&key) .ok_or_else(|| Error::Internal(format!("missing channel state for key {}", key)))?; let entry: vls_persist::model::ChannelEntry = - serde_json::from_value(value.1.clone()).unwrap(); + serde_json::from_value(value.value.clone()).unwrap(); Ok(entry.into()) } @@ -165,7 +254,7 @@ impl State { let key = vls_persist::model::NodeChannelId(hex::decode(&key).unwrap()); let value: vls_persist::model::ChannelEntry = - serde_json::from_value(v.1.clone()).unwrap(); + serde_json::from_value(v.value.clone()).unwrap(); (key.channel_id(), value.into()) }) .collect()) @@ -183,8 +272,10 @@ impl State { let tracker: vls_persist::model::ChainTrackerEntry = tracker.into(); - self.values - .insert(key, (version, serde_json::to_value(tracker).unwrap())); + self.values.insert( + key, + StateEntry::new(version, serde_json::to_value(tracker).unwrap()), + ); Ok(()) } @@ -194,21 +285,23 @@ impl State { } fn put_tombstone(&mut self, live_key: &str) { - self.values - .insert(live_key.to_owned(), (TOMBSTONE_VERSION, serde_json::Value::Null)); + self.values.insert( + live_key.to_owned(), + StateEntry::new(TOMBSTONE_VERSION, serde_json::Value::Null), + ); } fn next_version(&self, live_key: &str) -> u64 { self.values .get(live_key) - .map(|v| v.0.saturating_add(1)) + .map(|v| v.version.saturating_add(1)) .unwrap_or(0) } fn is_tombstone(&self, live_key: &str) -> bool { self.values .get(live_key) - .map(|v| v.0 == TOMBSTONE_VERSION) + .map(|v| v.version == TOMBSTONE_VERSION) .unwrap_or(false) } @@ -218,14 +311,13 @@ impl State { } Ok(()) } - } #[derive(Debug)] pub struct StateChange { key: String, - old: Option<(u64, serde_json::Value)>, - new: (u64, serde_json::Value), + old: Option, + new: StateEntry, } #[derive(Debug, Default)] @@ -248,16 +340,16 @@ impl Display for StateChange { Some(o) => f.write_str(&format!( "StateChange[{}]: old_version={}, new_version={}, old_value={}, new_value={}", self.key, - o.0, - self.new.0, - serde_json::to_string(&o.1).unwrap(), - serde_json::to_string(&self.new.1).unwrap() + o.version, + self.new.version, + serde_json::to_string(&o.value).unwrap(), + serde_json::to_string(&self.new.value).unwrap() )), None => f.write_str(&format!( "StateChange[{}]: old_version=null, new_version={}, old_value=null, new_value={}", self.key, - self.new.0, - serde_json::to_string(&self.new.1).unwrap() + self.new.version, + serde_json::to_string(&self.new.value).unwrap() )), } } @@ -280,54 +372,67 @@ impl State { /// tombstone knowledge. Callers may use this signal to trigger a full sync. pub fn merge(&mut self, other: &State) -> anyhow::Result { let mut res = MergeResult::default(); - for (key, (newver, newval)) in other.values.iter() { - let incoming_is_tombstone = *newver == TOMBSTONE_VERSION; + for (key, incoming) in other.values.iter() { + let newver = incoming.version; + let incoming_is_tombstone = newver == TOMBSTONE_VERSION; match self.values.get_mut(key) { None => { trace!("Insert new key {}: version={}", key, newver); - res.changes.push((key.to_owned(), None, *newver)); - let value = if incoming_is_tombstone { - serde_json::Value::Null - } else { - newval.clone() - }; - self.values.insert(key.clone(), (*newver, value)); + res.changes.push((key.to_owned(), None, newver)); + let mut inserted = incoming.clone(); + if incoming_is_tombstone { + inserted.value = serde_json::Value::Null; + inserted.signature = vec![]; + } + self.values.insert(key.clone(), inserted); } Some(v) => { if incoming_is_tombstone { - if v.0 == TOMBSTONE_VERSION { + if v.version == TOMBSTONE_VERSION { continue; } - trace!("Tombstoning key {}: version={} => version={}", key, v.0, newver); - res.changes.push((key.to_owned(), Some(v.0), *newver)); - *v = (*newver, serde_json::Value::Null); + trace!( + "Tombstoning key {}: version={} => version={}", + key, + v.version, + newver + ); + res.changes.push((key.to_owned(), Some(v.version), newver)); + *v = StateEntry::new(newver, serde_json::Value::Null); continue; } - if v.0 == TOMBSTONE_VERSION { + if v.version == TOMBSTONE_VERSION { trace!( "Ignoring live key {} version={} because local key is tombstoned", - key, newver + key, + newver ); res.conflict_count += 1; continue; } - if v.0 == *newver { + if v.version == newver { continue; - } else if v.0 > *newver { - warn!("Ignoring outdated state version newver={}, we have oldver={}: newval={:?} vs oldval={:?}", newver, v.0, serde_json::to_string(newval), serde_json::to_string(&v.1)); + } else if v.version > newver { + warn!( + "Ignoring outdated state version newver={}, we have oldver={}: newval={:?} vs oldval={:?}", + newver, + v.version, + serde_json::to_string(&incoming.value), + serde_json::to_string(&v.value) + ); res.conflict_count += 1; continue; } else { trace!( "Updating key {}: version={} => version={}", key, - v.0, - *newver + v.version, + newver ); - res.changes.push((key.to_owned(), Some(v.0), *newver)); - *v = (*newver, newval.clone()); + res.changes.push((key.to_owned(), Some(v.version), newver)); + *v = incoming.clone(); } } } @@ -339,15 +444,15 @@ impl State { Ok(other .values .iter() - .map(|(key, (ver, val))| (key, self.values.get(key), (ver, val))) + .map(|(key, new)| (key, self.values.get(key), new)) .map(|(key, old, new)| StateChange { key: key.clone(), old: old.map(|o| o.clone()), - new: (*new.0, new.1.clone()), + new: new.clone(), }) .filter(|c| match (&c.old, &c.new) { (None, _) => true, - (Some((oldver, _)), (newver, _)) => oldver < newver, + (Some(old), new) => old.version < new.version, }) .collect()) } @@ -356,13 +461,13 @@ impl State { /// This is useful for sending compact state diffs. pub fn diff_state(&self, other: &State) -> State { let mut values = BTreeMap::new(); - for (key, (newver, newval)) in other.values.iter() { + for (key, new_entry) in other.values.iter() { match self.values.get(key) { None => { - values.insert(key.clone(), (*newver, newval.clone())); + values.insert(key.clone(), new_entry.clone()); } - Some((oldver, _)) if oldver < newver => { - values.insert(key.clone(), (*newver, newval.clone())); + Some(old_entry) if old_entry.version < new_entry.version => { + values.insert(key.clone(), new_entry.clone()); } _ => {} } @@ -379,7 +484,7 @@ impl State { let values = self .values .iter() - .filter(|(_, (version, _))| *version != TOMBSTONE_VERSION) + .filter(|(_, value)| value.version != TOMBSTONE_VERSION) .map(|(key, value)| (key.clone(), value.clone())) .collect(); State { values } @@ -404,21 +509,21 @@ impl StateSketch { /// Apply versions from `state` without clearing existing entries. pub fn apply_state(&mut self, state: &State) { - for (key, (ver, _)) in state.values.iter() { - self.versions.insert(key.clone(), *ver); + for (key, value) in state.values.iter() { + self.versions.insert(key.clone(), value.version); } } /// Build a `State` containing entries newer than those recorded in the sketch. pub fn diff_state(&self, state: &State) -> State { let mut values = BTreeMap::new(); - for (key, (newver, newval)) in state.values.iter() { + for (key, new_entry) in state.values.iter() { match self.versions.get(key) { None => { - values.insert(key.clone(), (*newver, newval.clone())); + values.insert(key.clone(), new_entry.clone()); } - Some(oldver) if oldver < newver => { - values.insert(key.clone(), (*newver, newval.clone())); + Some(oldver) if *oldver < new_entry.version => { + values.insert(key.clone(), new_entry.clone()); } _ => {} } @@ -433,9 +538,9 @@ impl Into> for State { .iter() .map(|(k, v)| crate::pb::SignerStateEntry { key: k.to_owned(), - value: serde_json::to_vec(&v.1).unwrap(), - version: v.0, - signature: vec![], + value: serde_json::to_vec(&v.value).unwrap(), + version: v.version, + signature: v.signature.clone(), }) .collect() } @@ -447,7 +552,11 @@ impl From> for State { let values = BTreeMap::from_iter(v.iter().map(|v| { ( v.key.to_owned(), - (v.version, serde_json::from_slice(&v.value).unwrap()), + StateEntry { + version: v.version, + value: serde_json::from_slice(&v.value).unwrap(), + signature: v.signature.clone(), + }, ) })); @@ -588,7 +697,7 @@ impl Persist for MemoryPersister { state.ensure_not_tombstone(&key)?; let v = state.values.get_mut(&key).unwrap(); let tracker: vls_persist::model::ChainTrackerEntry = tracker.into(); - *v = (v.0 + 1, serde_json::to_value(tracker).unwrap()); + *v = StateEntry::new(v.version + 1, serde_json::to_value(tracker).unwrap()); Ok(()) } @@ -610,16 +719,15 @@ impl Persist for MemoryPersister { if state.is_tombstone(&key) { return Err(Error::Internal(format!("tracker {} has been deleted", key))); } - let v: vls_persist::model::ChainTrackerEntry = - serde_json::from_value( - state - .values - .get(&key) - .ok_or_else(|| Error::Internal(format!("missing tracker state {}", key)))? - .1 - .clone(), - ) - .unwrap(); + let v: vls_persist::model::ChainTrackerEntry = serde_json::from_value( + state + .values + .get(&key) + .ok_or_else(|| Error::Internal(format!("missing tracker state {}", key)))? + .value + .clone(), + ) + .unwrap(); Ok(v.into_tracker(node_id, validator_factory)) } @@ -643,13 +751,14 @@ impl Persist for MemoryPersister { state.ensure_not_tombstone(&key)?; match state.values.get_mut(&key) { Some(v) => { - *v = (v.0 + 1, serde_json::to_value(allowlist).unwrap()); + *v = StateEntry::new(v.version + 1, serde_json::to_value(allowlist).unwrap()); } None => { let version = state.next_version(&key); - state - .values - .insert(key, (version, serde_json::to_value(allowlist).unwrap())); + state.values.insert( + key, + StateEntry::new(version, serde_json::to_value(allowlist).unwrap()), + ); } } Ok(()) @@ -665,7 +774,7 @@ impl Persist for MemoryPersister { // If allowlist doesn't exist (e.g., node created before VLS 0.14), default to empty let allowlist: Vec = match state.values.get(&key) { - Some(value) => serde_json::from_value(value.1.clone()).unwrap_or_default(), + Some(value) => serde_json::from_value(value.value.clone()).unwrap_or_default(), None => Vec::new(), }; @@ -697,11 +806,11 @@ impl Persist for MemoryPersister { } let node: vls_persist::model::NodeEntry = match state.values.get(&node_key) { - Some(value) => serde_json::from_value(value.1.clone()).unwrap(), + Some(value) => serde_json::from_value(value.value.clone()).unwrap(), None => continue, }; let state_e: vls_persist::model::NodeStateEntry = match state.values.get(&state_key) { - Some(value) => serde_json::from_value(value.1.clone()).unwrap(), + Some(value) => serde_json::from_value(value.value.clone()).unwrap(), None => continue, }; @@ -710,7 +819,7 @@ impl Persist for MemoryPersister { Vec::new() } else { match state.values.get(&allowlist_key) { - Some(value) => serde_json::from_value(value.1.clone()).unwrap_or_default(), + Some(value) => serde_json::from_value(value.value.clone()).unwrap_or_default(), None => Vec::new(), } }; @@ -722,13 +831,11 @@ impl Persist for MemoryPersister { let allowlist: Vec = allowlist_strings .into_iter() - .filter_map(|s| { - match Allowable::from_str(&s, network) { - Ok(a) => Some(a), - Err(e) => { - warn!("Failed to parse allowlist entry '{}': {}", s, e); - None - } + .filter_map(|s| match Allowable::from_str(&s, network) { + Ok(a) => Some(a), + Err(e) => { + warn!("Failed to parse allowlist entry '{}': {}", s, e); + None } }) .collect(); @@ -776,36 +883,143 @@ mod tests { use crate::persist::TOMBSTONE_VERSION; use super::{ - State, StateSketch, ALLOWLIST_PREFIX, CHANNEL_PREFIX, NODE_PREFIX, NODE_STATE_PREFIX, - TRACKER_PREFIX, + State, StateEntry, StateSketch, ALLOWLIST_PREFIX, CHANNEL_PREFIX, NODE_PREFIX, + NODE_STATE_PREFIX, TRACKER_PREFIX, }; + use crate::pb::SignerStateEntry; use serde_json::json; use std::collections::BTreeMap; fn mk_state(entries: Vec<(&str, u64, serde_json::Value)>) -> State { let mut values = BTreeMap::new(); for (key, version, value) in entries { - values.insert(key.to_string(), (version, value)); + values.insert(key.to_string(), StateEntry::new(version, value)); } State { values } } - fn assert_entry(state: &State, key: &str, expected_version: u64, expected_value: serde_json::Value) { - let (actual_version, actual_value) = state.values.get(key).unwrap_or_else(|| { - panic!("expected state to include key: {key}") - }); - assert_eq!(*actual_version, expected_version); - assert_eq!(actual_value, &expected_value); + fn assert_entry( + state: &State, + key: &str, + expected_version: u64, + expected_value: serde_json::Value, + ) { + let actual = state + .values + .get(key) + .unwrap_or_else(|| panic!("expected state to include key: {key}")); + assert_eq!(actual.version, expected_version); + assert_eq!(actual.value, expected_value); } fn assert_entry_absent(state: &State, key: &str) { - assert!(state.values.get(key).is_none(), "expected state to omit key {key}"); + assert!( + state.values.get(key).is_none(), + "expected state to omit key {key}" + ); } fn assert_tombstone(state: &State, key: &str) { assert_entry(state, key, TOMBSTONE_VERSION, serde_json::Value::Null); } + #[test] + fn state_deserialize_legacy_tuple_defaults_empty_signature() { + let raw = r#"{"values":{"k":[1,{"v":1}]}}"#; + let state: State = serde_json::from_str(raw).unwrap(); + let entry = state.values.get("k").unwrap(); + assert_eq!(entry.version, 1); + assert_eq!(entry.value, json!({"v": 1})); + assert!(entry.signature.is_empty()); + } + + #[test] + fn state_deserialize_extended_tuple_preserves_signature() { + let raw = r#"{"values":{"k":[2,{"v":2},[1,2,3]]}}"#; + let state: State = serde_json::from_str(raw).unwrap(); + let entry = state.values.get("k").unwrap(); + assert_eq!(entry.version, 2); + assert_eq!(entry.value, json!({"v": 2})); + assert_eq!(entry.signature, vec![1, 2, 3]); + } + + #[test] + fn state_serialize_empty_signature_emits_legacy_tuple() { + let state = mk_state(vec![("k", 3, json!({"v": 3}))]); + let v: serde_json::Value = + serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap(); + let values = v.get("values").unwrap().as_object().unwrap(); + let tuple = values.get("k").unwrap().as_array().unwrap(); + assert_eq!(tuple.len(), 2); + assert_eq!(tuple[0], json!(3)); + assert_eq!(tuple[1], json!({"v": 3})); + } + + #[test] + fn state_serialize_non_empty_signature_emits_extended_tuple() { + let mut values = BTreeMap::new(); + values.insert( + "k".to_string(), + StateEntry { + version: 4, + value: json!({"v": 4}), + signature: vec![7, 8, 9], + }, + ); + let state = State { values }; + let v: serde_json::Value = + serde_json::from_slice(&serde_json::to_vec(&state).unwrap()).unwrap(); + let values = v.get("values").unwrap().as_object().unwrap(); + let tuple = values.get("k").unwrap().as_array().unwrap(); + assert_eq!(tuple.len(), 3); + assert_eq!(tuple[0], json!(4)); + assert_eq!(tuple[1], json!({"v": 4})); + assert_eq!(tuple[2], json!([7, 8, 9])); + } + + #[test] + fn signer_state_entry_conversions_preserve_signature() { + let entries = vec![SignerStateEntry { + version: 5, + key: "k".to_string(), + value: serde_json::to_vec(&json!({"v": 5})).unwrap(), + signature: vec![11, 12], + }]; + + let state: State = entries.clone().into(); + let entry = state.values.get("k").unwrap(); + assert_eq!(entry.version, 5); + assert_eq!(entry.value, json!({"v": 5})); + assert_eq!(entry.signature, vec![11, 12]); + + let roundtrip: Vec = state.into(); + assert_eq!(roundtrip, entries); + } + + #[test] + fn merge_newer_entry_propagates_signature() { + let mut base = mk_state(vec![("k", 1, json!({"v": 1}))]); + let mut incoming_values = BTreeMap::new(); + incoming_values.insert( + "k".to_string(), + StateEntry { + version: 2, + value: json!({"v": 2}), + signature: vec![21, 22, 23], + }, + ); + let incoming = State { + values: incoming_values, + }; + + let res = base.merge(&incoming).unwrap(); + assert_eq!(res.conflict_count, 0); + let merged = base.values.get("k").unwrap(); + assert_eq!(merged.version, 2); + assert_eq!(merged.value, json!({"v": 2})); + assert_eq!(merged.signature, vec![21, 22, 23]); + } + #[test] fn omit_tombstones_omits_tombstoned_entries() { let state = mk_state(vec![ @@ -850,10 +1064,7 @@ mod tests { #[test] fn sate_diff_with_empty_old_state_includes_all_entries() { let old = State::new(); - let new = mk_state(vec![ - ("k1", 1, json!({"v": 1})), - ("k2", 2, json!({"v": 2})), - ]); + let new = mk_state(vec![("k1", 1, json!({"v": 1})), ("k2", 2, json!({"v": 2}))]); let diff = old.diff_state(&new); @@ -884,10 +1095,7 @@ mod tests { #[test] fn sketch_diff_with_empty_sketch_includes_all_entries() { - let state = mk_state(vec![ - ("a", 1, json!(1)), - ("b", 2, json!(2)), - ]); + let state = mk_state(vec![("a", 1, json!(1)), ("b", 2, json!(2))]); let sketch = StateSketch::new(); let diff = sketch.diff_state(&state); @@ -903,7 +1111,11 @@ mod tests { let mut sketch = StateSketch::new(); sketch.apply_state(&base); - let next = mk_state(vec![("a", 2, json!(10)), ("b", 2, json!(20)), ("c", 0, json!(30))]); + let next = mk_state(vec![ + ("a", 2, json!(10)), + ("b", 2, json!(20)), + ("c", 0, json!(30)), + ]); let first_diff = sketch.diff_state(&next); assert_eq!(first_diff.values.len(), 2); assert_entry(&first_diff, "a", 2, json!(10)); From 7dedc4000da621301885807e2843b57c55e8ea4c Mon Sep 17 00:00:00 2001 From: Ihor Diachenko Date: Wed, 4 Mar 2026 18:53:27 +0200 Subject: [PATCH 3/5] Implemented signing modes --- libs/gl-cli/src/signer.rs | 110 ++++++++- libs/gl-client/src/persist.rs | 290 +++++++++++++++++++++-- libs/gl-client/src/signer/mod.rs | 383 ++++++++++++++++++++++++++++--- 3 files changed, 723 insertions(+), 60 deletions(-) diff --git a/libs/gl-cli/src/signer.rs b/libs/gl-cli/src/signer.rs index d103ec33f..b217a2eaf 100644 --- a/libs/gl-cli/src/signer.rs +++ b/libs/gl-cli/src/signer.rs @@ -1,8 +1,8 @@ use crate::error::{Error, Result}; use crate::util; -use clap::Subcommand; +use clap::{Subcommand, ValueEnum}; use core::fmt::Debug; -use gl_client::signer::Signer; +use gl_client::signer::{Signer, SignerConfig, StateSignatureMode}; use lightning_signer::bitcoin::Network; use std::path::Path; use tokio::{join, signal}; @@ -13,22 +13,53 @@ pub struct Config> { pub network: Network, } +#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)] +pub enum StateSignatureModeArg { + Off, + Soft, + Hard, +} + +impl Default for StateSignatureModeArg { + fn default() -> Self { + Self::Soft + } +} + +impl From for StateSignatureMode { + fn from(value: StateSignatureModeArg) -> Self { + match value { + StateSignatureModeArg::Off => StateSignatureMode::Off, + StateSignatureModeArg::Soft => StateSignatureMode::Soft, + StateSignatureModeArg::Hard => StateSignatureMode::Hard, + } + } +} + #[derive(Subcommand, Debug)] pub enum Command { /// Starts a signer that connects to greenlight - Run, + Run { + #[arg(long, value_enum, default_value_t = StateSignatureModeArg::Soft)] + state_signature_mode: StateSignatureModeArg, + }, /// Prints the version of the signer used Version, } pub async fn command_handler>(cmd: Command, config: Config

) -> Result<()> { match cmd { - Command::Run => run_handler(config).await, + Command::Run { + state_signature_mode, + } => run_handler(config, state_signature_mode).await, Command::Version => version(config).await, } } -async fn run_handler>(config: Config

) -> Result<()> { +async fn run_handler>( + config: Config

, + state_signature_mode: StateSignatureModeArg, +) -> Result<()> { // Check if we can find a seed file, if we can not find one, we need to register first. let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME); let seed = util::read_seed(&seed_path); @@ -53,8 +84,15 @@ async fn run_handler>(config: Config

) -> Result<()> { ))) } }; - let signer = Signer::new(seed, config.network, creds.clone()) - .map_err(|e| Error::custom(format!("Failed to create signer: {}", e)))?; + let signer = Signer::new_with_config( + seed, + config.network, + creds.clone(), + SignerConfig { + state_signature_mode: state_signature_mode.into(), + }, + ) + .map_err(|e| Error::custom(format!("Failed to create signer: {}", e)))?; let (tx, rx) = tokio::sync::mpsc::channel(1); let handle = tokio::spawn(async move { @@ -69,6 +107,64 @@ async fn run_handler>(config: Config

) -> Result<()> { Ok(()) } +#[cfg(test)] +mod tests { + use super::{Command, StateSignatureModeArg}; + use clap::{Parser, Subcommand}; + + #[derive(Parser, Debug)] + struct TestCli { + #[command(subcommand)] + cmd: Command, + } + + #[derive(Subcommand, Debug)] + enum RootCommand { + #[command(subcommand)] + Signer(Command), + } + + #[test] + fn parse_run_mode_flag() { + let cli = TestCli::parse_from(["test", "run", "--state-signature-mode", "hard"]); + match cli.cmd { + Command::Run { + state_signature_mode, + } => assert_eq!(state_signature_mode, StateSignatureModeArg::Hard), + _ => panic!("expected run command"), + } + } + + #[test] + fn run_mode_defaults_to_soft() { + let cli = TestCli::parse_from(["test", "run"]); + match cli.cmd { + Command::Run { + state_signature_mode, + } => assert_eq!(state_signature_mode, StateSignatureModeArg::Soft), + _ => panic!("expected run command"), + } + } + + #[test] + fn signer_subcommand_parses_mode_flag() { + #[derive(Parser, Debug)] + struct WrapperCli { + #[command(subcommand)] + cmd: RootCommand, + } + + let cli = + WrapperCli::parse_from(["test", "signer", "run", "--state-signature-mode", "off"]); + match cli.cmd { + RootCommand::Signer(Command::Run { + state_signature_mode, + }) => assert_eq!(state_signature_mode, StateSignatureModeArg::Off), + _ => panic!("expected signer run"), + } + } +} + async fn version>(config: Config

) -> Result<()> { // Check if we can find a seed file, if we can not find one, we need to register first. let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME); diff --git a/libs/gl-client/src/persist.rs b/libs/gl-client/src/persist.rs index fae12e00a..98f20df6b 100644 --- a/libs/gl-client/src/persist.rs +++ b/libs/gl-client/src/persist.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use lightning_signer::bitcoin::secp256k1::PublicKey; use lightning_signer::chain::tracker::ChainTracker; use lightning_signer::channel::ChannelId; @@ -382,7 +383,6 @@ impl State { let mut inserted = incoming.clone(); if incoming_is_tombstone { inserted.value = serde_json::Value::Null; - inserted.signature = vec![]; } self.values.insert(key.clone(), inserted); } @@ -398,7 +398,11 @@ impl State { newver ); res.changes.push((key.to_owned(), Some(v.version), newver)); - *v = StateEntry::new(newver, serde_json::Value::Null); + *v = StateEntry { + version: newver, + value: serde_json::Value::Null, + signature: incoming.signature.clone(), + }; continue; } @@ -413,6 +417,11 @@ impl State { } if v.version == newver { + if v.value == incoming.value && v.signature != incoming.signature { + trace!("Updating signature for key {} at version={}", key, newver); + res.changes.push((key.to_owned(), Some(v.version), newver)); + v.signature = incoming.signature.clone(); + } continue; } else if v.version > newver { warn!( @@ -469,12 +478,37 @@ impl State { Some(old_entry) if old_entry.version < new_entry.version => { values.insert(key.clone(), new_entry.clone()); } + Some(old_entry) + if old_entry.version == new_entry.version + && old_entry.signature != new_entry.signature => + { + values.insert(key.clone(), new_entry.clone()); + } _ => {} } } State { values } } + /// Sign entries missing signatures and return how many signatures were added. + pub fn resign_signatures(&mut self, mut signer: F) -> anyhow::Result + where + F: FnMut(&str, u64, &[u8]) -> anyhow::Result>, + { + let mut changed = 0usize; + for (key, entry) in self.values.iter_mut() { + if !entry.signature.is_empty() { + continue; + } + let value = serde_json::to_vec(&entry.value) + .map_err(|e| anyhow!("failed to serialize state value for key {key}: {e}"))?; + let signature = signer(key, entry.version, &value)?; + entry.signature = signature; + changed += 1; + } + Ok(changed) + } + pub fn sketch(&self) -> StateSketch { StateSketch::from_state(self) } @@ -491,9 +525,15 @@ impl State { } } +#[derive(Clone, Serialize, Deserialize, Debug, Default)] +struct SketchEntry { + version: u64, + signature: Vec, +} + #[derive(Clone, Serialize, Deserialize, Debug, Default)] pub struct StateSketch { - versions: BTreeMap, + versions: BTreeMap, } impl StateSketch { @@ -510,7 +550,13 @@ impl StateSketch { /// Apply versions from `state` without clearing existing entries. pub fn apply_state(&mut self, state: &State) { for (key, value) in state.values.iter() { - self.versions.insert(key.clone(), value.version); + self.versions.insert( + key.clone(), + SketchEntry { + version: value.version, + signature: value.signature.clone(), + }, + ); } } @@ -522,7 +568,12 @@ impl StateSketch { None => { values.insert(key.clone(), new_entry.clone()); } - Some(oldver) if *oldver < new_entry.version => { + Some(old) if old.version < new_entry.version => { + values.insert(key.clone(), new_entry.clone()); + } + Some(old) + if old.version == new_entry.version && old.signature != new_entry.signature => + { values.insert(key.clone(), new_entry.clone()); } _ => {} @@ -546,21 +597,40 @@ impl Into> for State { } } +impl TryFrom<&[crate::pb::SignerStateEntry]> for State { + type Error = anyhow::Error; + + fn try_from(v: &[crate::pb::SignerStateEntry]) -> Result { + let values = v + .iter() + .map(|entry| -> anyhow::Result<(String, StateEntry)> { + let value = serde_json::from_slice(&entry.value).map_err(|e| { + anyhow!( + "failed to decode signer state value for key {}: {}", + entry.key, + e + ) + })?; + + Ok(( + entry.key.to_owned(), + StateEntry { + version: entry.version, + value, + signature: entry.signature.clone(), + }, + )) + }) + .collect::>>()?; + + Ok(State { values }) + } +} + impl From> for State { fn from(v: Vec) -> State { - use std::iter::FromIterator; - let values = BTreeMap::from_iter(v.iter().map(|v| { - ( - v.key.to_owned(), - StateEntry { - version: v.version, - value: serde_json::from_slice(&v.value).unwrap(), - signature: v.signature.clone(), - }, - ) - })); - - State { values } + State::try_from(v.as_slice()) + .expect("signer state entries must contain valid JSON payloads") } } @@ -1020,6 +1090,77 @@ mod tests { assert_eq!(merged.signature, vec![21, 22, 23]); } + #[test] + fn merge_same_version_updates_signature_when_value_matches() { + let mut base_values = BTreeMap::new(); + base_values.insert( + "k".to_string(), + StateEntry { + version: 2, + value: json!({"v": 1}), + signature: vec![1], + }, + ); + let mut base = State { + values: base_values, + }; + + let mut incoming_values = BTreeMap::new(); + incoming_values.insert( + "k".to_string(), + StateEntry { + version: 2, + value: json!({"v": 1}), + signature: vec![9, 9], + }, + ); + let incoming = State { + values: incoming_values, + }; + + let res = base.merge(&incoming).unwrap(); + assert_eq!(res.conflict_count, 0); + let merged = base.values.get("k").unwrap(); + assert_eq!(merged.version, 2); + assert_eq!(merged.value, json!({"v": 1})); + assert_eq!(merged.signature, vec![9, 9]); + } + + #[test] + fn merge_same_version_does_not_overwrite_value() { + let mut base_values = BTreeMap::new(); + base_values.insert( + "k".to_string(), + StateEntry { + version: 2, + value: json!({"v": 1}), + signature: vec![1], + }, + ); + let mut base = State { + values: base_values, + }; + + let mut incoming_values = BTreeMap::new(); + incoming_values.insert( + "k".to_string(), + StateEntry { + version: 2, + value: json!({"v": 999}), + signature: vec![9, 9], + }, + ); + let incoming = State { + values: incoming_values, + }; + + let _ = base.merge(&incoming).unwrap(); + let merged = base.values.get("k").unwrap(); + assert_eq!(merged.version, 2); + assert_eq!(merged.value, json!({"v": 1})); + assert_eq!(merged.signature, vec![1]); + } + #[test] fn omit_tombstones_omits_tombstoned_entries() { let state = mk_state(vec![ @@ -1061,6 +1202,37 @@ mod tests { assert_entry_absent(&diff, "k3"); } + #[test] + fn diff_state_includes_signature_only_changes() { + let mut old_values = BTreeMap::new(); + old_values.insert( + "k".to_string(), + StateEntry { + version: 5, + value: json!({"v": 5}), + signature: vec![1], + }, + ); + let old = State { values: old_values }; + + let mut new_values = BTreeMap::new(); + new_values.insert( + "k".to_string(), + StateEntry { + version: 5, + value: json!({"v": 5}), + signature: vec![2, 3], + }, + ); + let new = State { values: new_values }; + + let diff = old.diff_state(&new); + assert_eq!(diff.values.len(), 1); + let entry = diff.values.get("k").unwrap(); + assert_eq!(entry.version, 5); + assert_eq!(entry.signature, vec![2, 3]); + } + #[test] fn sate_diff_with_empty_old_state_includes_all_entries() { let old = State::new(); @@ -1127,6 +1299,37 @@ mod tests { assert!(second_diff.values.is_empty()); } + #[test] + fn sketch_diff_includes_signature_only_changes() { + let mut old_values = BTreeMap::new(); + old_values.insert( + "k".to_string(), + StateEntry { + version: 8, + value: json!({"v": 8}), + signature: vec![1], + }, + ); + let old = State { values: old_values }; + + let mut new_values = BTreeMap::new(); + new_values.insert( + "k".to_string(), + StateEntry { + version: 8, + value: json!({"v": 8}), + signature: vec![4, 5, 6], + }, + ); + let new = State { values: new_values }; + + let diff = old.sketch().diff_state(&new); + assert_eq!(diff.values.len(), 1); + let entry = diff.values.get("k").unwrap(); + assert_eq!(entry.version, 8); + assert_eq!(entry.signature, vec![4, 5, 6]); + } + #[test] fn merge_tombstone_deletes_older_live_entry() { let live_key = format!("{CHANNEL_PREFIX}/abc"); @@ -1174,6 +1377,57 @@ mod tests { assert_entry(&state, "k1", 5, json!({"v": 5})); } + #[test] + fn signer_state_entry_try_from_rejects_invalid_json_value() { + let entries = vec![SignerStateEntry { + version: 1, + key: "bad".to_string(), + value: b"not-json".to_vec(), + signature: vec![], + }]; + + let res = State::try_from(entries.as_slice()); + assert!(res.is_err()); + } + + #[test] + fn resign_signatures_only_signs_missing_entries() { + let mut values = BTreeMap::new(); + values.insert( + "signed".to_string(), + StateEntry { + version: 1, + value: json!({"v": 1}), + signature: vec![9, 9], + }, + ); + values.insert( + "unsigned".to_string(), + StateEntry { + version: 2, + value: json!({"v": 2}), + signature: vec![], + }, + ); + + let mut state = State { values }; + let mut calls = 0usize; + let changed = state + .resign_signatures(|_, _, _| { + calls += 1; + Ok(vec![1, 2, 3, 4]) + }) + .unwrap(); + + assert_eq!(calls, 1); + assert_eq!(changed, 1); + assert_eq!(state.values.get("signed").unwrap().signature, vec![9, 9]); + assert_eq!( + state.values.get("unsigned").unwrap().signature, + vec![1, 2, 3, 4] + ); + } + #[test] fn delete_channel_marks_channel_with_tombstone_version() { let live_key = format!("{CHANNEL_PREFIX}/abc"); diff --git a/libs/gl-client/src/signer/mod.rs b/libs/gl-client/src/signer/mod.rs index f79a80d5c..30cff4b34 100644 --- a/libs/gl-client/src/signer/mod.rs +++ b/libs/gl-client/src/signer/mod.rs @@ -1,18 +1,16 @@ use crate::credentials::{RuneProvider, TlsConfigProvider}; +use crate::metrics::{savings_percent, signer_state_response_wire_bytes}; use crate::pb::scheduler::{scheduler_client::SchedulerClient, NodeInfoRequest, UpgradeRequest}; use crate::pb::scheduler::{ signer_request, signer_response, ApprovePairingRequest, ApprovePairingResponse, SignerResponse, }; -use crate::pb::PendingRequest; /// The core signer system. It runs in a dedicated thread or using the /// caller thread, streaming incoming requests, verifying them, /// signing if ok, and then shipping the response to the node. use crate::pb::{node_client::NodeClient, Empty, HsmRequest, HsmRequestContext, HsmResponse}; +use crate::pb::{PendingRequest, SignerStateEntry}; use crate::runes; use crate::signer::resolve::Resolver; -use crate::metrics::{ - signer_state_response_wire_bytes, savings_percent, -}; use crate::tls::TlsConfig; use crate::{node, node::Client}; use anyhow::{anyhow, Result}; @@ -21,12 +19,15 @@ use base64::Engine; use bytes::BufMut; use http::uri::InvalidUri; use lightning_signer::bitcoin::hashes::Hash; -use lightning_signer::bitcoin::secp256k1::PublicKey; +use lightning_signer::bitcoin::secp256k1::{ + ecdsa::Signature as SecpSignature, Message as SecpMessage, PublicKey, Secp256k1, SecretKey, +}; use lightning_signer::bitcoin::Network; use lightning_signer::node::NodeServices; use lightning_signer::policy::filter::FilterRule; use lightning_signer::util::crypto_utils; use log::{debug, error, info, trace, warn}; +use ring::digest::{digest, SHA256}; use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED}; use runeauth::{Condition, Restriction, Rune, RuneError}; use std::convert::{TryFrom, TryInto}; @@ -55,10 +56,34 @@ const GITHASH: &str = env!("GIT_HASH"); const RUNE_VERSION: &str = "gl0"; // This is the same derivation key that is used by core lightning itself. const RUNE_DERIVATION_SECRET: &str = "gl-commando"; +const STATE_DERIVATION_SECRET: &str = "greenlight/state-signing/v1"; +const STATE_SIGNING_DOMAIN: &[u8] = b"greenlight/state-signing/v1\0"; +const COMPACT_SIGNATURE_LEN: usize = 64; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StateSignatureMode { + Off, + Soft, + Hard, +} + +impl Default for StateSignatureMode { + fn default() -> Self { + Self::Soft + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct SignerConfig { + pub state_signature_mode: StateSignatureMode, +} #[derive(Clone)] pub struct Signer { secret: [u8; 32], + state_signing_secret: SecretKey, + state_signing_pubkey: PublicKey, + state_signature_mode: StateSignatureMode, master_rune: Rune, services: NodeServices, tls: TlsConfig, @@ -109,6 +134,18 @@ pub enum Error { impl Signer { pub fn new(secret: Vec, network: Network, creds: T) -> Result + where + T: TlsConfigProvider, + { + Self::new_with_config(secret, network, creds, SignerConfig::default()) + } + + pub fn new_with_config( + secret: Vec, + network: Network, + creds: T, + config: SignerConfig, + ) -> Result where T: TlsConfigProvider, { @@ -198,9 +235,8 @@ impl Signer { // we need for the rest of the run. let init = Signer::initmsg(&mut handler)?; - let init = HsmdInitReplyV4::from_vec(init).map_err(|e| { - anyhow!("Failed to parse init message as HsmdInitReplyV4: {:?}", e) - })?; + let init = HsmdInitReplyV4::from_vec(init) + .map_err(|e| anyhow!("Failed to parse init message as HsmdInitReplyV4: {:?}", e))?; let id = init.node_id.0.to_vec(); use vls_protocol::msgs::SerBolt; @@ -210,10 +246,16 @@ impl Signer { // seed by deriving a hardened key tagged with "rune secret". let rune_secret = crypto_utils::hkdf_sha256(&sec, RUNE_DERIVATION_SECRET.as_bytes(), &[]); let mr = Rune::new_master_rune(&rune_secret, vec![], None, Some(RUNE_VERSION.to_string()))?; + let state_signing_secret = Self::derive_state_signing_secret(&sec)?; + let state_signing_pubkey = + PublicKey::from_secret_key(&Secp256k1::signing_only(), &state_signing_secret); trace!("Initialized signer for node_id={}", hex::encode(&id)); Ok(Signer { secret: sec, + state_signing_secret, + state_signing_pubkey, + state_signature_mode: config.state_signature_mode, master_rune: mr, services, tls: creds.tls_config(), @@ -287,6 +329,90 @@ impl Signer { Ok(response.map(|a| a.as_vec()).unwrap_or_default()) } + fn derive_state_signing_secret(secret: &[u8; 32]) -> Result { + let key = crypto_utils::hkdf_sha256(secret, STATE_DERIVATION_SECRET.as_bytes(), &[]); + SecretKey::from_slice(&key).map_err(|e| anyhow!("failed to derive state signing key: {e}")) + } + + fn state_signature_digest(key: &str, version: u64, value: &[u8]) -> [u8; 32] { + let mut payload = + Vec::with_capacity(STATE_SIGNING_DOMAIN.len() + 4 + key.len() + 8 + 4 + value.len()); + payload.extend_from_slice(STATE_SIGNING_DOMAIN); + payload.extend_from_slice(&(key.len() as u32).to_be_bytes()); + payload.extend_from_slice(key.as_bytes()); + payload.extend_from_slice(&version.to_be_bytes()); + payload.extend_from_slice(&(value.len() as u32).to_be_bytes()); + payload.extend_from_slice(value); + let hash = digest(&SHA256, &payload); + let mut out = [0u8; 32]; + out.copy_from_slice(hash.as_ref()); + out + } + + fn sign_state_payload( + &self, + key: &str, + version: u64, + value: &[u8], + ) -> Result, anyhow::Error> { + let digest = Self::state_signature_digest(key, version, value); + let msg = SecpMessage::from_digest_slice(&digest).map_err(|e| { + anyhow!( + "failed to build state signature digest for key {}: {}", + key, + e + ) + })?; + let sig = Secp256k1::signing_only().sign_ecdsa(&msg, &self.state_signing_secret); + Ok(sig.serialize_compact().to_vec()) + } + + fn verify_state_entry_signature(&self, entry: &SignerStateEntry) -> Result<(), anyhow::Error> { + if entry.signature.len() != COMPACT_SIGNATURE_LEN { + return Err(anyhow!( + "expected {} signature bytes, got {}", + COMPACT_SIGNATURE_LEN, + entry.signature.len() + )); + } + let digest = Self::state_signature_digest(&entry.key, entry.version, &entry.value); + let msg = SecpMessage::from_digest_slice(&digest) + .map_err(|e| anyhow!("failed to build digest message: {}", e))?; + let sig = SecpSignature::from_compact(&entry.signature) + .map_err(|e| anyhow!("invalid compact signature: {}", e))?; + Secp256k1::verification_only() + .verify_ecdsa(&msg, &sig, &self.state_signing_pubkey) + .map_err(|e| anyhow!("signature verification failed: {}", e))?; + Ok(()) + } + + fn verify_incoming_state_signatures(&self, entries: &[SignerStateEntry]) -> Result<(), Error> { + if self.state_signature_mode == StateSignatureMode::Off { + return Ok(()); + } + + for entry in entries { + if entry.signature.is_empty() { + if self.state_signature_mode == StateSignatureMode::Hard { + return Err(Error::Other(anyhow!( + "missing state signature for key {}", + entry.key + ))); + } + continue; + } + + self.verify_state_entry_signature(entry).map_err(|e| { + Error::Other(anyhow!( + "invalid state signature for key {}: {}", + entry.key, + e + )) + })?; + } + Ok(()) + } + /// Filter out any request that is not signed, such that the /// remainder is the minimal set to reconcile state changes /// against. @@ -488,6 +614,7 @@ impl Signer { async fn process_request(&self, req: HsmRequest) -> Result { debug!("Processing request {:?}", req); + self.verify_incoming_state_signatures(&req.signer_state)?; let incoming_state: crate::persist::State = req.signer_state.clone().into(); // Create sketch from incoming state (nodelet's view) so we can @@ -600,7 +727,10 @@ impl Signer { node_id: self.node_id(), }) .await; - return Err(Error::Other(anyhow!("Failed to update state from context: {:?}", e))); + return Err(Error::Other(anyhow!( + "Failed to update state from context: {:?}", + e + ))); } log::trace!("State updated"); @@ -613,7 +743,10 @@ impl Signer { Some(c) => { let node_id_len = c.node_id.len(); let pk: [u8; 33] = c.node_id.try_into().map_err(|_| { - Error::Other(anyhow!("Invalid node_id length in context: expected 33 bytes, got {}", node_id_len)) + Error::Other(anyhow!( + "Invalid node_id length in context: expected 33 bytes, got {}", + node_id_len + )) })?; let pk = vls_protocol::model::PubKey(pk); root_handler @@ -638,9 +771,17 @@ impl Signer { let signer_state: Vec = { debug!("Serializing state changes to report to node"); - let state = self.state.lock().map_err(|e| { - Error::Other(anyhow!("Failed to acquire state lock for serialization: {:?}", e)) + let mut state = self.state.lock().map_err(|e| { + Error::Other(anyhow!( + "Failed to acquire state lock for serialization: {:?}", + e + )) })?; + state + .resign_signatures(|key, version, value| { + self.sign_state_payload(key, version, value) + }) + .map_err(|e| Error::Other(anyhow!("Failed to sign signer state entries: {e}")))?; let full_wire_bytes = { let full_entries: Vec = state.clone().into(); signer_state_response_wire_bytes(&full_entries) @@ -788,7 +929,10 @@ impl Signer { }; if initmsg.len() <= 35 { - error!("Legacy init message too short: expected >35 bytes, got {}", initmsg.len()); + error!( + "Legacy init message too short: expected >35 bytes, got {}", + initmsg.len() + ); return vec![]; } initmsg[35..].to_vec() @@ -1225,13 +1369,11 @@ fn update_state_from_context( log::debug!("Updating state from {} context request", requests.len()); let node = handler.node(); - requests - .iter() - .for_each(|r| { - if let Err(e) = update_state_from_request(r, &node) { - log::warn!("Failed to update state from request: {:?}", e); - } - }); + requests.iter().for_each(|r| { + if let Err(e) = update_state_from_request(r, &node) { + log::warn!("Failed to update state from request: {:?}", e); + } + }); Ok(()) } @@ -1244,23 +1386,21 @@ fn update_state_from_request( match request { model::Request::SendPay(model::cln::SendpayRequest { bolt11: Some(inv), .. - }) => { - match Invoice::from_str(inv) { - Ok(invoice) => { - log::debug!( - "Adding invoice {:?} as side-effect of this sendpay {:?}", - invoice, - request - ); - if let Err(e) = node.add_invoice(invoice) { - log::warn!("Failed to add invoice to node state: {:?}", e); - } - } - Err(e) => { - log::warn!("Failed to parse invoice from sendpay request: {:?}", e); + }) => match Invoice::from_str(inv) { + Ok(invoice) => { + log::debug!( + "Adding invoice {:?} as side-effect of this sendpay {:?}", + invoice, + request + ); + if let Err(e) = node.add_invoice(invoice) { + log::warn!("Failed to add invoice to node state: {:?}", e); } } - } + Err(e) => { + log::warn!("Failed to parse invoice from sendpay request: {:?}", e); + } + }, _ => {} } @@ -1307,6 +1447,33 @@ mod tests { use super::*; use crate::credentials; use crate::pb; + use serde_json::json; + use vls_protocol::msgs::SerBolt; + + fn mk_signer(mode: StateSignatureMode) -> Signer { + Signer::new_with_config( + vec![0u8; 32], + Network::Bitcoin, + credentials::Nobody::default(), + SignerConfig { + state_signature_mode: mode, + }, + ) + .unwrap() + } + + fn heartbeat_raw() -> Vec { + vls_protocol::msgs::GetHeartbeat {}.as_vec() + } + + fn mk_state_entry(key: &str, version: u64, value: serde_json::Value) -> SignerStateEntry { + SignerStateEntry { + version, + key: key.to_string(), + value: serde_json::to_vec(&value).unwrap(), + signature: vec![], + } + } /// We should not sign messages that we get from the node, since /// we're using the sign_message RPC message to create TLS @@ -1361,6 +1528,152 @@ mod tests { ) } + #[test] + fn test_state_signature_roundtrip() { + let signer = mk_signer(StateSignatureMode::Soft); + let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + entry.signature = signer + .sign_state_payload(&entry.key, entry.version, &entry.value) + .unwrap(); + assert!(signer.verify_state_entry_signature(&entry).is_ok()); + } + + #[tokio::test] + async fn test_soft_mode_accepts_missing_signature_and_repairs() { + let signer = mk_signer(StateSignatureMode::Soft); + let key = "nodes/test".to_string(); + let req = HsmRequest { + request_id: 42, + context: None, + raw: heartbeat_raw(), + signer_state: vec![mk_state_entry(&key, 1, json!({"v": 1}))], + requests: vec![], + }; + let response = signer.process_request(req).await.unwrap(); + let repaired = response + .signer_state + .iter() + .find(|e| e.key == key) + .expect("expected repaired entry in diff"); + assert_eq!(repaired.signature.len(), COMPACT_SIGNATURE_LEN); + } + + #[tokio::test] + async fn test_soft_mode_rejects_invalid_signature() { + let signer = mk_signer(StateSignatureMode::Soft); + let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + entry.signature = vec![1u8; COMPACT_SIGNATURE_LEN]; + let err = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![entry], + requests: vec![], + }) + .await + .unwrap_err() + .to_string(); + assert!(err.contains("invalid state signature")); + } + + #[tokio::test] + async fn test_hard_mode_rejects_missing_signature() { + let signer = mk_signer(StateSignatureMode::Hard); + let err = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![mk_state_entry("nodes/test", 1, json!({"v": 1}))], + requests: vec![], + }) + .await + .unwrap_err() + .to_string(); + assert!(err.contains("missing state signature")); + } + + #[tokio::test] + async fn test_hard_mode_rejects_invalid_signature() { + let signer = mk_signer(StateSignatureMode::Hard); + let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + entry.signature = vec![2u8; COMPACT_SIGNATURE_LEN]; + let err = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![entry], + requests: vec![], + }) + .await + .unwrap_err() + .to_string(); + assert!(err.contains("invalid state signature")); + } + + #[tokio::test] + async fn test_hard_mode_accepts_valid_signature() { + let signer = mk_signer(StateSignatureMode::Hard); + let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + entry.signature = signer + .sign_state_payload(&entry.key, entry.version, &entry.value) + .unwrap(); + + let res = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![entry], + requests: vec![], + }) + .await; + assert!(res.is_ok()); + } + + #[tokio::test] + async fn test_off_mode_accepts_invalid_and_missing_signatures() { + let signer = mk_signer(StateSignatureMode::Off); + let mut invalid = mk_state_entry("nodes/invalid", 1, json!({"v": 1})); + invalid.signature = vec![3u8; COMPACT_SIGNATURE_LEN]; + let missing = mk_state_entry("nodes/missing", 1, json!({"v": 2})); + let res = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![invalid, missing], + requests: vec![], + }) + .await; + assert!(res.is_ok()); + } + + #[tokio::test] + async fn test_malformed_state_value_returns_error() { + let signer = mk_signer(StateSignatureMode::Soft); + let entry = SignerStateEntry { + version: 1, + key: "nodes/bad".to_string(), + value: b"{".to_vec(), + signature: vec![], + }; + let err = signer + .process_request(HsmRequest { + request_id: 0, + context: None, + raw: heartbeat_raw(), + signer_state: vec![entry], + requests: vec![], + }) + .await + .unwrap_err() + .to_string(); + assert!(err.contains("Failed to decode signer state")); + } + #[test] fn test_sign_message_max_size() { let signer = Signer::new( From 4b2a0a3657c73fae7867b49a77fc9ddd58c21c0f Mon Sep 17 00:00:00 2001 From: Ihor Diachenko Date: Thu, 5 Mar 2026 18:29:58 +0200 Subject: [PATCH 4/5] Added state override mode --- libs/gl-cli/src/signer.rs | 90 ++++++- libs/gl-client/src/signer/mod.rs | 373 ++++++++++++++++++++++++++-- libs/gl-client/src/signer/report.rs | 104 ++++++++ 3 files changed, 535 insertions(+), 32 deletions(-) diff --git a/libs/gl-cli/src/signer.rs b/libs/gl-cli/src/signer.rs index b217a2eaf..767d30556 100644 --- a/libs/gl-cli/src/signer.rs +++ b/libs/gl-cli/src/signer.rs @@ -2,7 +2,9 @@ use crate::error::{Error, Result}; use crate::util; use clap::{Subcommand, ValueEnum}; use core::fmt::Debug; -use gl_client::signer::{Signer, SignerConfig, StateSignatureMode}; +use gl_client::signer::{ + Signer, SignerConfig, StateSignatureMode, StateSignatureOverrideConfig, +}; use lightning_signer::bitcoin::Network; use std::path::Path; use tokio::{join, signal}; @@ -42,6 +44,10 @@ pub enum Command { Run { #[arg(long, value_enum, default_value_t = StateSignatureModeArg::Soft)] state_signature_mode: StateSignatureModeArg, + #[arg(long = "state-override")] + state_override: Option, + #[arg(long = "state-override-note")] + state_override_note: Option, }, /// Prints the version of the signer used Version, @@ -51,7 +57,17 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> match cmd { Command::Run { state_signature_mode, - } => run_handler(config, state_signature_mode).await, + state_override, + state_override_note, + } => { + run_handler( + config, + state_signature_mode, + state_override, + state_override_note, + ) + .await + } Command::Version => version(config).await, } } @@ -59,6 +75,8 @@ pub async fn command_handler>(cmd: Command, config: Config

) -> async fn run_handler>( config: Config

, state_signature_mode: StateSignatureModeArg, + state_override: Option, + state_override_note: Option, ) -> Result<()> { // Check if we can find a seed file, if we can not find one, we need to register first. let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME); @@ -84,12 +102,27 @@ async fn run_handler>( ))) } }; + + if state_override.is_none() && state_override_note.is_some() { + return Err(Error::custom( + "--state-override-note requires --state-override", + )); + } + + let state_signature_override = state_override.map(|ack| { + StateSignatureOverrideConfig { + ack, + note: state_override_note, + } + }); + let signer = Signer::new_with_config( seed, config.network, creds.clone(), SignerConfig { state_signature_mode: state_signature_mode.into(), + state_signature_override, }, ) .map_err(|e| Error::custom(format!("Failed to create signer: {}", e)))?; @@ -130,7 +163,13 @@ mod tests { match cli.cmd { Command::Run { state_signature_mode, - } => assert_eq!(state_signature_mode, StateSignatureModeArg::Hard), + state_override, + state_override_note, + } => { + assert_eq!(state_signature_mode, StateSignatureModeArg::Hard); + assert!(state_override.is_none()); + assert!(state_override_note.is_none()); + } _ => panic!("expected run command"), } } @@ -141,7 +180,13 @@ mod tests { match cli.cmd { Command::Run { state_signature_mode, - } => assert_eq!(state_signature_mode, StateSignatureModeArg::Soft), + state_override, + state_override_note, + } => { + assert_eq!(state_signature_mode, StateSignatureModeArg::Soft); + assert!(state_override.is_none()); + assert!(state_override_note.is_none()); + } _ => panic!("expected run command"), } } @@ -159,10 +204,45 @@ mod tests { match cli.cmd { RootCommand::Signer(Command::Run { state_signature_mode, - }) => assert_eq!(state_signature_mode, StateSignatureModeArg::Off), + state_override, + state_override_note, + }) => { + assert_eq!(state_signature_mode, StateSignatureModeArg::Off); + assert!(state_override.is_none()); + assert!(state_override_note.is_none()); + } _ => panic!("expected signer run"), } } + + #[test] + fn parse_override_flags() { + let cli = TestCli::parse_from([ + "test", + "run", + "--state-signature-mode", + "hard", + "--state-override", + "I_ACCEPT_OPERATOR_ASSISTED_STATE_OVERRIDE", + "--state-override-note", + "debug session", + ]); + match cli.cmd { + Command::Run { + state_signature_mode, + state_override, + state_override_note, + } => { + assert_eq!(state_signature_mode, StateSignatureModeArg::Hard); + assert_eq!( + state_override.as_deref(), + Some("I_ACCEPT_OPERATOR_ASSISTED_STATE_OVERRIDE") + ); + assert_eq!(state_override_note.as_deref(), Some("debug session")); + } + _ => panic!("expected run command"), + } + } } async fn version>(config: Config

) -> Result<()> { diff --git a/libs/gl-client/src/signer/mod.rs b/libs/gl-client/src/signer/mod.rs index 30cff4b34..37c7eb9cd 100644 --- a/libs/gl-client/src/signer/mod.rs +++ b/libs/gl-client/src/signer/mod.rs @@ -58,6 +58,7 @@ const RUNE_VERSION: &str = "gl0"; const RUNE_DERIVATION_SECRET: &str = "gl-commando"; const STATE_DERIVATION_SECRET: &str = "greenlight/state-signing/v1"; const STATE_SIGNING_DOMAIN: &[u8] = b"greenlight/state-signing/v1\0"; +const STATE_SIGNATURE_OVERRIDE_ACK: &str = "I_ACCEPT_OPERATOR_ASSISTED_STATE_OVERRIDE"; const COMPACT_SIGNATURE_LEN: usize = 64; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -73,9 +74,28 @@ impl Default for StateSignatureMode { } } -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StateSignatureOverrideConfig { + pub ack: String, + pub note: Option, +} + +#[derive(Clone, Debug, Default)] pub struct SignerConfig { pub state_signature_mode: StateSignatureMode, + pub state_signature_override: Option, +} + +#[derive(Debug, Default)] +struct OverrideSignatureUsage { + missing_keys: Vec, + invalid_keys: Vec, +} + +impl OverrideSignatureUsage { + fn is_used(&self) -> bool { + !self.missing_keys.is_empty() || !self.invalid_keys.is_empty() + } } #[derive(Clone)] @@ -84,6 +104,8 @@ pub struct Signer { state_signing_secret: SecretKey, state_signing_pubkey: PublicKey, state_signature_mode: StateSignatureMode, + state_signature_override_enabled: bool, + state_signature_override_note: Option, master_rune: Rune, services: NodeServices, tls: TlsConfig, @@ -156,6 +178,37 @@ impl Signer { use lightning_signer::util::clock::StandardClock; info!("Initializing signer for {VERSION} ({GITHASH}) (VLS)"); + let state_signature_mode = config.state_signature_mode; + let (state_signature_override_enabled, state_signature_override_note) = + match config.state_signature_override { + Some(override_config) => { + if state_signature_mode == StateSignatureMode::Off { + return Err(anyhow!( + "state signature override is incompatible with state signature mode off" + )); + } + + if override_config.ack != STATE_SIGNATURE_OVERRIDE_ACK { + return Err(anyhow!( + "invalid state signature override ack, expected {}", + STATE_SIGNATURE_OVERRIDE_ACK + )); + } + + let note = override_config + .note + .and_then(|n| { + let trimmed = n.trim().to_string(); + if trimmed.is_empty() { + None + } else { + Some(trimmed) + } + }); + (true, note) + } + None => (false, None), + }; let mut sec: [u8; 32] = [0; 32]; sec.copy_from_slice(&secret[0..32]); @@ -255,7 +308,9 @@ impl Signer { secret: sec, state_signing_secret, state_signing_pubkey, - state_signature_mode: config.state_signature_mode, + state_signature_mode, + state_signature_override_enabled, + state_signature_override_note, master_rune: mr, services, tls: creds.tls_config(), @@ -386,31 +441,98 @@ impl Signer { Ok(()) } - fn verify_incoming_state_signatures(&self, entries: &[SignerStateEntry]) -> Result<(), Error> { + fn state_signature_mode_label(&self) -> &'static str { + match self.state_signature_mode { + StateSignatureMode::Off => "off", + StateSignatureMode::Soft => "soft", + StateSignatureMode::Hard => "hard", + } + } + + fn inspect_incoming_state_signatures( + &self, + entries: &[SignerStateEntry], + ) -> Result { if self.state_signature_mode == StateSignatureMode::Off { - return Ok(()); + return Ok(OverrideSignatureUsage::default()); } - for entry in entries { + let mut usage = OverrideSignatureUsage::default(); + let mut first_error: Option = None; + + for entry in entries.iter() { if entry.signature.is_empty() { if self.state_signature_mode == StateSignatureMode::Hard { - return Err(Error::Other(anyhow!( - "missing state signature for key {}", - entry.key - ))); + usage.missing_keys.push(entry.key.clone()); + if first_error.is_none() { + first_error = Some(anyhow!("missing state signature for key {}", entry.key)); + } } continue; } - self.verify_state_entry_signature(entry).map_err(|e| { - Error::Other(anyhow!( - "invalid state signature for key {}: {}", - entry.key, - e - )) - })?; + if let Err(e) = self.verify_state_entry_signature(entry) { + usage.invalid_keys.push(entry.key.clone()); + if first_error.is_none() { + first_error = Some(anyhow!("invalid state signature for key {}: {}", entry.key, e)); + } + } } - Ok(()) + + if usage.is_used() && !self.state_signature_override_enabled { + return Err(Error::Other( + first_error + .unwrap_or_else(|| anyhow!("state signature verification failed unexpectedly")), + )); + } + + Ok(usage) + } + + async fn report_state_signature_override_enabled(&self) { + if !self.state_signature_override_enabled { + return; + } + + let message = report::build_state_signature_override_enabled_message( + self.state_signature_mode_label(), + &self.id, + self.state_signature_override_note.as_deref(), + ); + warn!("{}", message); + report::Reporter::report(crate::pb::scheduler::SignerRejection { + msg: message, + request: None, + git_version: GITHASH.to_string(), + node_id: self.node_id(), + }) + .await; + } + + async fn report_state_signature_override_usage( + &self, + req: &HsmRequest, + usage: &OverrideSignatureUsage, + ) { + if !usage.is_used() || !self.state_signature_override_enabled { + return; + } + + let message = report::build_state_signature_override_used_message( + self.state_signature_mode_label(), + req.request_id as u64, + &usage.missing_keys, + &usage.invalid_keys, + self.state_signature_override_note.as_deref(), + ); + warn!("{}", message); + report::Reporter::report(crate::pb::scheduler::SignerRejection { + msg: message, + request: Some(req.clone()), + git_version: GITHASH.to_string(), + node_id: self.node_id(), + }) + .await; } /// Filter out any request that is not signed, such that the @@ -614,8 +736,17 @@ impl Signer { async fn process_request(&self, req: HsmRequest) -> Result { debug!("Processing request {:?}", req); - self.verify_incoming_state_signatures(&req.signer_state)?; - let incoming_state: crate::persist::State = req.signer_state.clone().into(); + let req = req; + + let signature_usage = self.inspect_incoming_state_signatures(&req.signer_state)?; + if signature_usage.is_used() { + self.report_state_signature_override_usage(&req, &signature_usage) + .await; + } + + let incoming_state = crate::persist::State::try_from(req.signer_state.as_slice()) + .map_err(|e| Error::Other(anyhow!("Failed to decode signer state: {e}")))?; + // Create sketch from incoming state (nodelet's view) so we can // send back any entries the nodelet doesn't know about yet, @@ -945,6 +1076,7 @@ impl Signer { pub async fn run_forever(&self, shutdown: mpsc::Receiver<()>) -> Result<(), anyhow::Error> { let scheduler_uri = crate::utils::scheduler_uri(); debug!("Starting signer run loop"); + self.report_state_signature_override_enabled().await; let res = Self::run_forever_with_uri(&self, shutdown, scheduler_uri).await; debug!("Exited signer run loop"); res @@ -1450,6 +1582,13 @@ mod tests { use serde_json::json; use vls_protocol::msgs::SerBolt; + fn test_override_config(note: Option<&str>) -> StateSignatureOverrideConfig { + StateSignatureOverrideConfig { + ack: STATE_SIGNATURE_OVERRIDE_ACK.to_string(), + note: note.map(str::to_string), + } + } + fn mk_signer(mode: StateSignatureMode) -> Signer { Signer::new_with_config( vec![0u8; 32], @@ -1457,6 +1596,20 @@ mod tests { credentials::Nobody::default(), SignerConfig { state_signature_mode: mode, + state_signature_override: None, + }, + ) + .unwrap() + } + + fn mk_signer_with_override(mode: StateSignatureMode, note: Option<&str>) -> Signer { + Signer::new_with_config( + vec![0u8; 32], + Network::Bitcoin, + credentials::Nobody::default(), + SignerConfig { + state_signature_mode: mode, + state_signature_override: Some(test_override_config(note)), }, ) .unwrap() @@ -1531,7 +1684,7 @@ mod tests { #[test] fn test_state_signature_roundtrip() { let signer = mk_signer(StateSignatureMode::Soft); - let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + let mut entry = mk_state_entry("state/test", 1, json!({"v": 1})); entry.signature = signer .sign_state_payload(&entry.key, entry.version, &entry.value) .unwrap(); @@ -1541,7 +1694,7 @@ mod tests { #[tokio::test] async fn test_soft_mode_accepts_missing_signature_and_repairs() { let signer = mk_signer(StateSignatureMode::Soft); - let key = "nodes/test".to_string(); + let key = "state/test".to_string(); let req = HsmRequest { request_id: 42, context: None, @@ -1561,7 +1714,7 @@ mod tests { #[tokio::test] async fn test_soft_mode_rejects_invalid_signature() { let signer = mk_signer(StateSignatureMode::Soft); - let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + let mut entry = mk_state_entry("state/test", 1, json!({"v": 1})); entry.signature = vec![1u8; COMPACT_SIGNATURE_LEN]; let err = signer .process_request(HsmRequest { @@ -1585,7 +1738,7 @@ mod tests { request_id: 0, context: None, raw: heartbeat_raw(), - signer_state: vec![mk_state_entry("nodes/test", 1, json!({"v": 1}))], + signer_state: vec![mk_state_entry("state/test", 1, json!({"v": 1}))], requests: vec![], }) .await @@ -1597,7 +1750,7 @@ mod tests { #[tokio::test] async fn test_hard_mode_rejects_invalid_signature() { let signer = mk_signer(StateSignatureMode::Hard); - let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + let mut entry = mk_state_entry("state/test", 1, json!({"v": 1})); entry.signature = vec![2u8; COMPACT_SIGNATURE_LEN]; let err = signer .process_request(HsmRequest { @@ -1616,7 +1769,7 @@ mod tests { #[tokio::test] async fn test_hard_mode_accepts_valid_signature() { let signer = mk_signer(StateSignatureMode::Hard); - let mut entry = mk_state_entry("nodes/test", 1, json!({"v": 1})); + let mut entry = mk_state_entry("state/test", 1, json!({"v": 1})); entry.signature = signer .sign_state_payload(&entry.key, entry.version, &entry.value) .unwrap(); @@ -1636,9 +1789,9 @@ mod tests { #[tokio::test] async fn test_off_mode_accepts_invalid_and_missing_signatures() { let signer = mk_signer(StateSignatureMode::Off); - let mut invalid = mk_state_entry("nodes/invalid", 1, json!({"v": 1})); + let mut invalid = mk_state_entry("state/invalid", 1, json!({"v": 1})); invalid.signature = vec![3u8; COMPACT_SIGNATURE_LEN]; - let missing = mk_state_entry("nodes/missing", 1, json!({"v": 2})); + let missing = mk_state_entry("state/missing", 1, json!({"v": 2})); let res = signer .process_request(HsmRequest { request_id: 0, @@ -1651,6 +1804,172 @@ mod tests { assert!(res.is_ok()); } + #[tokio::test] + async fn test_soft_mode_override_accepts_invalid_across_requests() { + let signer = mk_signer_with_override(StateSignatureMode::Soft, Some("test override")); + + let mut invalid1 = mk_state_entry("state/invalid1", 1, json!({"v": 1})); + invalid1.signature = vec![4u8; COMPACT_SIGNATURE_LEN]; + signer + .process_request(HsmRequest { + request_id: 1, + context: None, + raw: heartbeat_raw(), + signer_state: vec![invalid1], + requests: vec![], + }) + .await + .unwrap(); + let snapshot1: Vec = { + let state_guard = signer.state.lock().unwrap(); + state_guard.clone().into() + }; + let persisted1 = snapshot1.iter().find(|e| e.key == "state/invalid1").unwrap(); + assert_eq!(persisted1.signature, vec![4u8; COMPACT_SIGNATURE_LEN]); + + let mut invalid2 = mk_state_entry("state/invalid2", 1, json!({"v": 2})); + invalid2.signature = vec![5u8; COMPACT_SIGNATURE_LEN]; + let res2 = signer + .process_request(HsmRequest { + request_id: 2, + context: None, + raw: heartbeat_raw(), + signer_state: vec![invalid2], + requests: vec![], + }) + .await; + assert!(res2.is_ok()); + + let snapshot2: Vec = { + let state_guard = signer.state.lock().unwrap(); + state_guard.clone().into() + }; + let persisted2 = snapshot2.iter().find(|e| e.key == "state/invalid2").unwrap(); + assert_eq!(persisted2.signature, vec![5u8; COMPACT_SIGNATURE_LEN]); + } + + #[tokio::test] + async fn test_hard_mode_override_accepts_missing_across_requests() { + let signer = mk_signer_with_override(StateSignatureMode::Hard, Some("test override")); + + for (key, request_id) in [("state/missing1", 10u32), ("state/missing2", 11u32)] { + signer + .process_request(HsmRequest { + request_id, + context: None, + raw: heartbeat_raw(), + signer_state: vec![mk_state_entry(key, 1, json!({"v": request_id}))], + requests: vec![], + }) + .await + .unwrap(); + let snapshot: Vec = { + let state_guard = signer.state.lock().unwrap(); + state_guard.clone().into() + }; + let persisted = snapshot.iter().find(|entry| entry.key == key).unwrap(); + assert_eq!(persisted.signature.len(), COMPACT_SIGNATURE_LEN); + } + } + + #[tokio::test] + async fn test_hard_mode_override_accepts_invalid_signature() { + let signer = mk_signer_with_override(StateSignatureMode::Hard, Some("test override")); + let mut invalid = mk_state_entry("state/invalid-hard", 1, json!({"v": 3})); + invalid.signature = vec![6u8; COMPACT_SIGNATURE_LEN]; + + signer + .process_request(HsmRequest { + request_id: 12, + context: None, + raw: heartbeat_raw(), + signer_state: vec![invalid], + requests: vec![], + }) + .await + .unwrap(); + + let snapshot: Vec = { + let state_guard = signer.state.lock().unwrap(); + state_guard.clone().into() + }; + let persisted = snapshot + .iter() + .find(|entry| entry.key == "state/invalid-hard") + .unwrap(); + assert_eq!(persisted.signature, vec![6u8; COMPACT_SIGNATURE_LEN]); + } + + #[tokio::test] + async fn test_override_preserves_invalid_signature_without_touching_valid_signature() { + let signer = mk_signer_with_override(StateSignatureMode::Soft, Some("repair")); + + let mut valid = mk_state_entry("state/valid", 1, json!({"v": 1})); + valid.signature = signer + .sign_state_payload(&valid.key, valid.version, &valid.value) + .unwrap(); + let mut invalid = mk_state_entry("state/invalid", 1, json!({"v": 2})); + invalid.signature = vec![7u8; COMPACT_SIGNATURE_LEN]; + + signer + .process_request(HsmRequest { + request_id: 13, + context: None, + raw: heartbeat_raw(), + signer_state: vec![valid.clone(), invalid], + requests: vec![], + }) + .await + .unwrap(); + + let snapshot: Vec = { + let state_guard = signer.state.lock().unwrap(); + state_guard.clone().into() + }; + + let persisted_valid = snapshot.iter().find(|entry| entry.key == "state/valid").unwrap(); + let preserved_invalid = snapshot + .iter() + .find(|entry| entry.key == "state/invalid") + .unwrap(); + + assert_eq!(persisted_valid.signature, valid.signature); + assert_eq!(preserved_invalid.signature, vec![7u8; COMPACT_SIGNATURE_LEN]); + } + + #[test] + fn test_override_rejected_when_mode_off() { + let signer = Signer::new_with_config( + vec![0u8; 32], + Network::Bitcoin, + credentials::Nobody::default(), + SignerConfig { + state_signature_mode: StateSignatureMode::Off, + state_signature_override: Some(test_override_config(Some("test"))), + }, + ); + let err = signer.err().unwrap().to_string(); + assert!(err.contains("incompatible with state signature mode off")); + } + + #[test] + fn test_override_rejected_for_invalid_ack() { + let signer = Signer::new_with_config( + vec![0u8; 32], + Network::Bitcoin, + credentials::Nobody::default(), + SignerConfig { + state_signature_mode: StateSignatureMode::Soft, + state_signature_override: Some(StateSignatureOverrideConfig { + ack: "WRONG".to_string(), + note: None, + }), + }, + ); + let err = signer.err().unwrap().to_string(); + assert!(err.contains("invalid state signature override ack")); + } + #[tokio::test] async fn test_malformed_state_value_returns_error() { let signer = mk_signer(StateSignatureMode::Soft); diff --git a/libs/gl-client/src/signer/report.rs b/libs/gl-client/src/signer/report.rs index b63e413f0..02e2ab5cf 100644 --- a/libs/gl-client/src/signer/report.rs +++ b/libs/gl-client/src/signer/report.rs @@ -13,6 +13,11 @@ //! collation by capturing the full context. use crate::pb; + +pub const STATE_SIGNATURE_OVERRIDE_ENABLED_PREFIX: &str = "STATE_SIGNATURE_OVERRIDE_ENABLED"; +pub const STATE_SIGNATURE_OVERRIDE_USED_PREFIX: &str = "STATE_SIGNATURE_OVERRIDE_USED"; +const KEY_LOG_LIMIT: usize = 8; + pub struct Reporter {} impl Reporter { @@ -33,3 +38,102 @@ impl Reporter { } } } + +fn summarize_keys(keys: &[String]) -> String { + if keys.is_empty() { + return "-".to_string(); + } + + let mut listed = keys + .iter() + .take(KEY_LOG_LIMIT) + .map(String::as_str) + .collect::>() + .join(","); + let hidden = keys.len().saturating_sub(KEY_LOG_LIMIT); + if hidden > 0 { + listed.push_str(&format!(",+{}more", hidden)); + } + listed +} + +fn format_note(note: Option<&str>) -> String { + note.map(|s| format!("{:?}", s)) + .unwrap_or_else(|| "null".to_string()) +} + +pub fn build_state_signature_override_enabled_message( + mode: &str, + node_id: &[u8], + note: Option<&str>, +) -> String { + format!( + "{} mode={} node_id={} note={}", + STATE_SIGNATURE_OVERRIDE_ENABLED_PREFIX, + mode, + hex::encode(node_id), + format_note(note), + ) +} + +pub fn build_state_signature_override_used_message( + mode: &str, + request_id: u64, + missing_keys: &[String], + invalid_keys: &[String], + note: Option<&str>, +) -> String { + format!( + "{} mode={} request_id={} missing_count={} invalid_count={} missing_keys={} invalid_keys={} note={}", + STATE_SIGNATURE_OVERRIDE_USED_PREFIX, + mode, + request_id, + missing_keys.len(), + invalid_keys.len(), + summarize_keys(missing_keys), + summarize_keys(invalid_keys), + format_note(note), + ) +} + +#[cfg(test)] +mod tests { + use super::{ + build_state_signature_override_enabled_message, build_state_signature_override_used_message, + STATE_SIGNATURE_OVERRIDE_ENABLED_PREFIX, STATE_SIGNATURE_OVERRIDE_USED_PREFIX, + }; + + #[test] + fn override_enabled_message_contains_required_fields() { + let msg = build_state_signature_override_enabled_message( + "soft", + &[0x02, 0xab, 0xcd], + Some("operator assisted"), + ); + assert!(msg.starts_with(STATE_SIGNATURE_OVERRIDE_ENABLED_PREFIX)); + assert!(msg.contains("mode=soft")); + assert!(msg.contains("node_id=02abcd")); + assert!(msg.contains("note=\"operator assisted\"")); + } + + #[test] + fn override_used_message_contains_required_fields() { + let missing = vec!["nodes/a".to_string()]; + let invalid = vec!["nodes/b".to_string(), "nodes/c".to_string()]; + let msg = build_state_signature_override_used_message( + "hard", + 7, + &missing, + &invalid, + Some("manual fix"), + ); + assert!(msg.starts_with(STATE_SIGNATURE_OVERRIDE_USED_PREFIX)); + assert!(msg.contains("mode=hard")); + assert!(msg.contains("request_id=7")); + assert!(msg.contains("missing_count=1")); + assert!(msg.contains("invalid_count=2")); + assert!(msg.contains("missing_keys=nodes/a")); + assert!(msg.contains("invalid_keys=nodes/b,nodes/c")); + assert!(msg.contains("note=\"manual fix\"")); + } +} From a01653baf365ffca27c5945a1cecd25f9be73b74 Mon Sep 17 00:00:00 2001 From: Ihor Diachenko Date: Tue, 10 Mar 2026 03:09:31 +0200 Subject: [PATCH 5/5] Conanicalize state values --- libs/gl-client/src/persist.rs | 45 ++++++++++++++++++++++--- libs/gl-client/src/persist/canonical.rs | 41 ++++++++++++++++++++++ libs/gl-client/src/signer/mod.rs | 2 +- 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 libs/gl-client/src/persist/canonical.rs diff --git a/libs/gl-client/src/persist.rs b/libs/gl-client/src/persist.rs index 98f20df6b..dbbc37ed0 100644 --- a/libs/gl-client/src/persist.rs +++ b/libs/gl-client/src/persist.rs @@ -1,3 +1,5 @@ +mod canonical; + use anyhow::anyhow; use lightning_signer::bitcoin::secp256k1::PublicKey; use lightning_signer::chain::tracker::ChainTracker; @@ -19,6 +21,8 @@ use std::str::FromStr; use std::sync::Arc; use std::sync::Mutex; +use self::canonical::{canonical_json_bytes, CanonicalJsonValue}; + const NODE_PREFIX: &str = "nodes"; const NODE_STATE_PREFIX: &str = "nodestates"; const CHANNEL_PREFIX: &str = "channels"; @@ -41,6 +45,10 @@ impl StateEntry { signature: vec![], } } + + fn canonical_value_bytes(&self) -> anyhow::Result> { + canonical_json_bytes(&self.value) + } } impl Serialize for StateEntry { @@ -55,7 +63,7 @@ impl Serialize for StateEntry { }; seq.serialize_element(&self.version)?; - seq.serialize_element(&self.value)?; + seq.serialize_element(&CanonicalJsonValue(&self.value))?; if !self.signature.is_empty() { seq.serialize_element(&self.signature)?; } @@ -500,8 +508,7 @@ impl State { if !entry.signature.is_empty() { continue; } - let value = serde_json::to_vec(&entry.value) - .map_err(|e| anyhow!("failed to serialize state value for key {key}: {e}"))?; + let value = entry.canonical_value_bytes()?; let signature = signer(key, entry.version, &value)?; entry.signature = signature; changed += 1; @@ -589,7 +596,9 @@ impl Into> for State { .iter() .map(|(k, v)| crate::pb::SignerStateEntry { key: k.to_owned(), - value: serde_json::to_vec(&v.value).unwrap(), + value: v + .canonical_value_bytes() + .expect("canonical signer state value"), version: v.version, signature: v.signature.clone(), }) @@ -1047,6 +1056,18 @@ mod tests { assert_eq!(tuple[2], json!([7, 8, 9])); } + #[test] + fn state_entry_canonical_value_bytes_sorts_nested_object_keys() { + let entry = StateEntry::new(0, json!({ + "z": {"b": 1, "a": 2}, + "a": [{"d": 4, "c": 3}] + })); + + let bytes = entry.canonical_value_bytes().unwrap(); + + assert_eq!(bytes, br#"{"a":[{"c":3,"d":4}],"z":{"a":2,"b":1}}"#); + } + #[test] fn signer_state_entry_conversions_preserve_signature() { let entries = vec![SignerStateEntry { @@ -1066,6 +1087,22 @@ mod tests { assert_eq!(roundtrip, entries); } + #[test] + fn signer_state_entry_conversions_emit_canonical_value_bytes() { + let entries = vec![SignerStateEntry { + version: 6, + key: "k".to_string(), + value: br#"{ "b": 1, "a": 2 }"#.to_vec(), + signature: vec![11, 12], + }]; + + let state: State = entries.into(); + let roundtrip: Vec = state.into(); + assert_eq!(roundtrip.len(), 1); + assert_eq!(roundtrip[0].value, br#"{"a":2,"b":1}"#); + assert_eq!(roundtrip[0].signature, vec![11, 12]); + } + #[test] fn merge_newer_entry_propagates_signature() { let mut base = mk_state(vec![("k", 1, json!({"v": 1}))]); diff --git a/libs/gl-client/src/persist/canonical.rs b/libs/gl-client/src/persist/canonical.rs new file mode 100644 index 000000000..4047cc010 --- /dev/null +++ b/libs/gl-client/src/persist/canonical.rs @@ -0,0 +1,41 @@ +use anyhow::anyhow; +use serde::ser::{SerializeMap, SerializeSeq}; +use serde::{Serialize, Serializer}; + +pub struct CanonicalJsonValue<'a>(pub &'a serde_json::Value); + +impl Serialize for CanonicalJsonValue<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self.0 { + serde_json::Value::Null => serializer.serialize_unit(), + serde_json::Value::Bool(v) => serializer.serialize_bool(*v), + serde_json::Value::Number(v) => v.serialize(serializer), + serde_json::Value::String(v) => serializer.serialize_str(v), + serde_json::Value::Array(values) => { + let mut seq = serializer.serialize_seq(Some(values.len()))?; + for value in values { + seq.serialize_element(&CanonicalJsonValue(value))?; + } + seq.end() + } + serde_json::Value::Object(values) => { + let mut entries: Vec<_> = values.iter().collect(); + entries.sort_unstable_by(|(left, _), (right, _)| left.cmp(right)); + + let mut map = serializer.serialize_map(Some(entries.len()))?; + for (key, value) in entries { + map.serialize_entry(key, &CanonicalJsonValue(value))?; + } + map.end() + } + } + } +} + +pub fn canonical_json_bytes(value: &serde_json::Value) -> anyhow::Result> { + serde_json::to_vec(&CanonicalJsonValue(value)) + .map_err(|e| anyhow!("failed to serialize canonical signer state value: {e}")) +} diff --git a/libs/gl-client/src/signer/mod.rs b/libs/gl-client/src/signer/mod.rs index 37c7eb9cd..1c9286c2e 100644 --- a/libs/gl-client/src/signer/mod.rs +++ b/libs/gl-client/src/signer/mod.rs @@ -410,7 +410,7 @@ impl Signer { version: u64, value: &[u8], ) -> Result, anyhow::Error> { - let digest = Self::state_signature_digest(key, version, value); + let digest = Self::state_signature_digest(key, version, &value); let msg = SecpMessage::from_digest_slice(&digest).map_err(|e| { anyhow!( "failed to build state signature digest for key {}: {}",