Skip to content

Comments

Add Outbound Remote Signer implementation#8754

Open
ViktorT-11 wants to merge 39 commits intolightningnetwork:masterfrom
ViktorT-11:2024-05-add-outbound-remote-signer
Open

Add Outbound Remote Signer implementation#8754
ViktorT-11 wants to merge 39 commits intolightningnetwork:masterfrom
ViktorT-11:2024-05-add-outbound-remote-signer

Conversation

@ViktorT-11
Copy link
Collaborator

@ViktorT-11 ViktorT-11 commented May 14, 2024

This PR introduces the functionality to utilize an alternative remote signer implementation, in which the remote signer node establishes an outbound connection to the watch-only node.

In the existing remote signer implementation, the watch-only node establishes an outbound connection to the remote signer, which accepts an inbound connection. The implementation introduced by this PR eliminates the need for the remote signer to allow any inbound connections.

To enable an outbound remote signer using the functionality introduced in this PR, please run make release-install & follow these steps:
https://github.com/ViktorT-11/lnd/blob/2024-05-add-outbound-remote-signer/docs/remote-signing.md#outbound-remote-signer-example

Note:
This PR does not address the requirement for the remote signer to remain online while the watch-only node is operational. Currently, all RPC requests sent to the Remote signer will fail if it goes offline, and the health monitor will then proceed to shutdown the watch-only node. Additionally, this PR does not implement any validation on the remote signer side, i.e. the Remote Signer will blindly sign whatever is sent to it.
These issues will be addressed in future PRs.

Final note:
I plan resolve any CI issues ASAP, so reviewers can await those fixes before starting the review if preferred. I also intend to add some review comments on a few points where feedback would be particularly helpful.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 14, 2024

Important

Review skipped

Auto reviews are limited to specific labels.

🏷️ Labels to auto review (1)
  • llm-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ViktorT-11 ViktorT-11 changed the title Add an Outbound Remote Signer implementation Add Outbound Remote Signer implementation May 14, 2024
@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch 9 times, most recently from 0525eb6 to 6abefde Compare May 17, 2024 18:11
@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch 2 times, most recently from b34174d to aabced7 Compare May 28, 2024 10:35
@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from aabced7 to cfe7fc3 Compare June 12, 2024 23:43
@ziggie1984
Copy link
Collaborator

Interesting PR 👀, does this somehow relate to the VLS project, a bit of time ago somebody told me that there is a proxy in the making to connect LND to the VLS signer project, wondering if there is some progress made in that direction ?

@ViktorT-11
Copy link
Collaborator Author

Interesting PR 👀, does this somehow relate to the VLS project?

Thanks a lot! I would say that the current PR is somewhat related to VLS in terms of future functionality it could enable. However, this PR only focuses on reversing the connection setup between the watch-only node and the signer node, so that the signer node makes the outbound connection to the watch-only node instead of the other way around. In a setup where mobile signer wallets use VLS for validation which are connected to watch-only nodes, this functionality is a necessary pre-requisite unless the mobile wallets are online constantly.

However, this PR as it currently stand does not add any validation or enable an integration with VLS yet. The goal is to address such needs in future PRs. My hope is that the RemoteSignerClient introduced in this PR can be updated in the future to support such functionality, but this is still to be determined.

a bit of time ago somebody told me that there is a proxy in the making to connect LND to the VLS signer project.

I think the project you're referring to is:
https://github.com/NYDIG/lndsigner

@guggero guggero self-requested a review July 30, 2024 14:55
Copy link
Collaborator Author

@ViktorT-11 ViktorT-11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few comments for reviewers where it'd be especially helpful with feedback :)! Might add some more comments tomorrow if I think of something else.

One more area I'd appreciate to get some feedback from reviewers for is:

  • Iit could be useful to let the sign requests that's passed over the stream from the watch-only node to the signer, also pass through the RPC interceptor on the signer node side. If that's something you think we should look into supporting, let me know!

Comment on lines 183 to 195
"/walletrpc.WalletKit/SignCoordinatorStreams": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "message",
Action: "write",
}, {
Entity: "signer",
Action: "generate",
}, {
Entity: "address",
Action: "read",
}},
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't really sure what kind of perms we'd want to require in the macaroon for the remote signer when opening the stream with the watch-only node. Therefore mimicked the perms of the inbound remote signer required permissions. Though as this is the other way around i.e. the remote signer is connecting to the watch-only node, that maybe doesn't makes sense as it's not strictly required.
Perhaps we'd like to add some new kind of entity for this specific case?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think adding a special permission just for the remote signer here probably makes sense.

Copy link
Collaborator Author

@ViktorT-11 ViktorT-11 Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool, I added a new entity called remotesigner, with the action generate (I wasn't sure which action made most sense here, so feel free to suggest using read/write if you think that makes more sense).

connects to the watch-only lnd node, to initialize a handshake between
the nodes.
*/
bool signer_registration = 2;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if it makes sense to add some kind of versioning for the signer, so that the watch-only node can know what kind of functionality the signer supports.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. I don't think that adding an independent version here gives us much. I think (at least in the beginning), we'd want the signer and watch-only to be on the same main lnd version. So maybe we should send the full version string here and initially just compare it to the one running on the watch-only, then error out if it differs?
Or don't do anything yet other than specifying this field as a string, so we can do it in the future?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or don't do anything yet other than specifying this field as a string, so we can do it in the future?

Makes sense, thanks! I agree that it probably makes sense to just enable the support so that we can define the versioning idea when we actually decide on it in the future, so I edited this to be a string instead.

@saubyk saubyk added this to the v0.19.0 milestone Jul 31, 2024
@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from cfe7fc3 to b48621e Compare July 31, 2024 10:17
Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work on this PR. The commit structure is super nice and easy to follow.

I only did a first read-through to grasp the different areas the code touches. Haven't looked at all the new code in detail yet, but will follow up with a second round soon.
So this is mainly to checkpoint my initial comments. Will dig in deeper tomorrow.

connects to the watch-only lnd node, to initialize a handshake between
the nodes.
*/
bool signer_registration = 2;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, good question. I don't think that adding an independent version here gives us much. I think (at least in the beginning), we'd want the signer and watch-only to be on the same main lnd version. So maybe we should send the full version string here and initially just compare it to the one running on the watch-only, then error out if it differs?
Or don't do anything yet other than specifying this field as a string, so we can do it in the future?

Comment on lines 183 to 195
"/walletrpc.WalletKit/SignCoordinatorStreams": {{
Entity: "onchain",
Action: "write",
}, {
Entity: "message",
Action: "write",
}, {
Entity: "signer",
Action: "generate",
}, {
Entity: "address",
Action: "read",
}},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think adding a special permission just for the remote signer here probably makes sense.

cleanUpTasks []func()
cleanUp = func() {
for _, fn := range cleanUpTasks {
if fn == nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would a cleanup function be nil? Shouldn't we be able to expect them always to be non-nil, the way we add them below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's correct, currently they'll never be nil given the current code. This was a copy of the pattern used in the BuildWalletConfig function, and I guess it could be useful if we'd ever add a remote signer implementation that doesn't have any cleanup func.

But I'll remove the nil check if you think that's just adding extra complexity :)!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'd ever add a remote signer implementation that doesn't have any cleanup func.

But in that case, that impl can just return func() {}. We can even require that via the comment in the API

I think better to have fewer nil checks where possible

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also dont think you need a cleanup return value here. Just have a Stop on the RemoteSigner which performs a disconnect of its established connection. Then you just add that Stop call to your clean-up list

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also: pre-existing, but I think that currently NewRPCKeyRing (and now Build) is the only call in BuildChainControl besides NewChainControl that does some sort of "start" functionality. I'd argue that we really should keep these New* functions as constructors and then leave any starting for later. But again this is out of scope here since it is pre-existing.

"signing node through RPC: %v", err)
}

defer func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in a defer? Since we're done now?
And not sure if we also want to report errors when not being able to disconnect?
So we might just be able to do return conn.Close() here instead?

@ellemouton ellemouton self-requested a review August 6, 2024 09:11
@guggero guggero self-requested a review August 7, 2024 15:03
Copy link
Collaborator

@guggero guggero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a deep dive into the remote signer client and sign coordinator. Looks very good, just need to do another pass to grok all layers of the mutexes and wait groups and Go channels being used...

Comment on lines +545 to +558
s.mu.Lock()
defer s.mu.Unlock()

select {
case <-s.quit:
return nil, ErrShuttingDown
default:
}

s.wg.Add(1)

return func() {
s.wg.Done()
}, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, there are a lot of layers with the mutexes and wait groups... Probably need to take another, closer look at those, to make sure we can't run into deadlocks...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do :)! I've tried to be very careful to ensure that couldn't happen, so hopefully I haven't missed any scenario where that could occur! But some extra eyes on this would indeed be very helpful 🙏

@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch 3 times, most recently from 915f062 to 6e965f8 Compare September 2, 2024 12:39
Change the `InterceptorChain` behavior to allow a remote signer to call
the `walletrpc.SignCoordinatorStreams` while the `rpcState` is set to
`allowRemoteSigner`. This state precedes the `rpcActive` state, which
allows all RPCs.

This change is necessary because `lnd` needs the remote signer to be
connected before some of the internal dependencies for RPC sub-servers
can be created. These dependencies must be inserted into the sub-servers
before moving the `rpcState` to `rpcActive`.
The `SetServerActive` moves the `rpcState` from `rpcActive` to
`serverActive`. Update the docs to correctly reflect that.
To enable an outbound remote signer to connect to lnd before all
dependencies for the RPC sub-servers are created, we need to separate
the process of adding dependencies to the sub-servers from created
the sub-servers. Prior to this commit, the RPC sub-servers were created
and enabled only after all dependencies were in place. Such a limitation
prevents accepting an incoming connection request from an outbound
remote signer (e.g., a `walletrpc.SignCoordinatorStreams` RPC call) to
the `WalletKitServer` until all dependencies for the RPC sub-servers are
created.

However, this limitation would not work, as we need the remote signer in
order to create some of the dependencies for the other RPC sub-servers.
Therefore, we need to enable calls to at least the `WalletKitServer` and
the main RPC server before creating the remaining dependencies.

This commit refactors the logic for the main RPC server and sub-servers,
allowing them to be enabled before dependencies are inserted into the
sub-servers. The `WalletState` for the `InterceptorChain` is only set to
`RpcActive` after all dependencies have been created and inserted,
ensuring that RPC requests won't be allowed into the sub-servers before
the dependencies exist. An upcoming commit will set the state to
`AllowRemoteSigner` before all dependencies are created, enabling an
outbound remote signer to connect when needed.
This commit adds the `RemoteSignerConnection` reference to the
`WalletKit` `Config`, enabling it to be accessed from the `WalletKit`
sub-server.

When a remote signer connects by calling the `SignCoordinatorStreams`
RPC endpoint, we need to pass the stream from the outbound remote signer
to the `InboundRemoteSignerConnection` `AddConnection` function. This
change ensures that the `InboundRemoteSignerConnection` `AddConnection`
function is reachable from the `SignCoordinatorStreams` RPC endpoint
implementation.

Note that this field should only to be populated when the `RPCKeyRing`
`RemoteSignerConnection` is an `InboundConnection` instance, as the
only that connection type implements the `InboundRemoteSignerConnection`
interface.
With the ability to reach the `InboundRemoteSignerConnection`
`AddConnection` function in the `WalletKit` sub-server, we now implement
the `SignCoordinatorStreams` RPC endpoint.
This commit populates the `RemoteSignerConnection` reference in the
`WalletKit` config before other dependencies are added. To ensure that
an outbound remote signer can connect before other dependencies are
created, and since we use this reference in the walletrpc
`SignCoordinatorStreams` RPC, we must populate this dependency prior to
other dependencies during the lnd startup process.
This commit populates the `RemoteSignerConnection` reference in the
`WalletKit` config before other dependencies are added. To ensure that
an outbound remote signer can connect before other dependencies are
created, and since we use this reference in the walletrpc
`SignCoordinatorStreams` RPC, we must populate this dependency prior to
other dependencies during the lnd startup process.
Previous commits added functionality to handle the incoming connection
from an outbound remote signer and ensured that the outbound remote
signer could connect before any signatures from the remote signer are
needed. However, one issue still remains: we need to ensure that we wait
for the outbound remote signer to connect when starting lnd before
executing any code that requires the remote signer to be connected.

This commit adds a `ReadySignal` function to the `WalletController` that
returns a channel, which will signal once the wallet is ready to be
used. For an `InboundConnection`, this channel will only signal once the
outbound remote signer has connected. This can then be used to ensure
that lnd waits for the outbound remote signer to connect during the
startup process.
With the functionality in place to allow an outbound remote signer to
connect before any signatures are needed and the ability to wait for
this connection, this commit enables the functionality to wait for the
remote signer to connect before proceeding with the startup process.
This includes setting the `WalletState` in the `InterceptorChain` to
`AllowRemoteSigner` before waiting for the outbound remote signer to
connect.
With all the necessary components on the watch-only node side in place
to support usage of an outbound remote signer, the `lncfg` package can
now permit the `remotesigner.allowinboundconnection` setting to be
configured.

This commit also adds support for the building `InboundConnection`
instances in the `RemoteSignerConnectionBuilder`.
With support for the outbound remote signer now added, we update the
documentation to detail how to enable the use of this new remote signer
type.
Update release notes to include information about the support for the
new outbound remote signer type.
Update the harness to allow creating a watch-only node without starting
it. This is useful for tests that need to create a watch-only node prior
to starting it, such as tests that use an outbound remote signer.
rename testRemoteSignerRadomSeedOutbound to
testRemoteSignerRandomSeedOutbound, as random was misspelled.
testOutboundRSMacaroonEnforcement tests that a valid macaroon including
the `remotesigner` entity is required to connect to a watch-only node
that uses an outbound remote signer, while the watch-only node is in the
state (WalletState_ALLOW_REMOTE_SIGNER) where it waits for the signer to
connect.
This commit fixes that word wrapping for the deriveCustomScopeAccounts
function docs, and ensures that it wraps at 80 characters or less.
Allow the remote signer to reconnect to the wallet after disconnecting,
as long as the remote signer reconnects within the timeout limit.

This is not a complete solution to the problem to allow the watch-only
node to stay online when the remote signer is disconnected, but is more
fault-tolerant than the current implementation as it allows the remote
to be temporarily disconnected.
@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from 14403fc to b89ea08 Compare February 7, 2026 01:06
@lightninglabs-deploy
Copy link
Collaborator

🔴 PR Severity: CRITICAL

62 files changed | ~5,000 substantive lines changed (excluding auto-generated proto files)

🔴 Critical (14 files)
  • lnwallet/btcwallet/btcwallet.go - Wallet operations interface changes
  • lnwallet/interface.go - Core wallet interface modifications
  • lnwallet/mock.go - Mock wallet updates
  • lnwallet/rpcwallet/healthcheck.go - RPC wallet health monitoring
  • lnwallet/rpcwallet/remote_signer_client.go - NEW: Remote signer client implementation (911 lines)
  • lnwallet/rpcwallet/remote_signer_client_builder.go - NEW: Remote signer client builder
  • lnwallet/rpcwallet/remote_signer_connection.go - NEW: Remote signer connection management (373 lines)
  • lnwallet/rpcwallet/remote_signer_connection_builder.go - NEW: Remote signer connection builder
  • lnwallet/rpcwallet/rpcwallet.go - Significant refactoring of RPC wallet (63 additions, 109 deletions)
  • lnwallet/rpcwallet/sign_coordinator.go - NEW: Central signing coordination logic (985 lines)
  • server.go - Core server coordination changes (44 additions, 15 deletions)
  • rpcserver.go - Core RPC server modifications (113 additions, 88 deletions)
  • rpcperms/interceptor.go - RPC permissions interceptor changes (58 additions, 6 deletions)
  • subrpcserver_config.go - Sub-RPC server configuration changes (32 additions, 2 deletions)
🟠 High (21 files)
  • lnrpc/walletrpc/walletkit_server.go - Wallet RPC server implementation
  • lnrpc/walletrpc/driver.go - Wallet RPC driver
  • lnrpc/walletrpc/config_active.go - Wallet RPC config
  • lnrpc/signrpc/driver.go - Signer RPC driver
  • lnrpc/signrpc/signer_server.go - Signer RPC server
  • lnrpc/routerrpc/driver.go - Router RPC driver
  • lnrpc/routerrpc/router_server.go - Router RPC server
  • lnrpc/invoicesrpc/driver.go - Invoices RPC driver
  • lnrpc/invoicesrpc/invoices_server.go - Invoices RPC server
  • lnrpc/chainrpc/chain_server.go - Chain RPC server
  • lnrpc/chainrpc/driver.go - Chain RPC driver
  • lnrpc/autopilotrpc/autopilot_server.go - Autopilot RPC server
  • lnrpc/autopilotrpc/driver.go - Autopilot RPC driver
  • lnrpc/peersrpc/driver.go - Peers RPC driver
  • lnrpc/peersrpc/peers_server.go - Peers RPC server
  • lnrpc/neutrinorpc/driver.go - Neutrino RPC driver
  • lnrpc/neutrinorpc/neutrino_server.go - Neutrino RPC server
  • lnrpc/devrpc/dev_server.go - Dev RPC server
  • lnrpc/devrpc/driver.go - Dev RPC driver
  • lnrpc/watchtowerrpc/driver.go - Watchtower RPC driver
  • lnrpc/watchtowerrpc/handler.go - Watchtower RPC handler
🟡 Medium (6 files)
  • lncfg/remotesigner.go - Remote signer configuration (165 additions, 17 deletions)
  • config.go - Main configuration changes (19 additions, 4 deletions)
  • config_builder.go - Configuration builder (37 additions, 5 deletions)
  • lnd.go - Main lnd initialization (66 additions, 8 deletions)
  • sample-lnd.conf - Sample configuration file (77 additions, 4 deletions)
  • lnrpc/sub_server.go - Sub-server management (11 additions, 10 deletions)
🟢 Low (21 files)
  • docs/release-notes/release-notes-0.21.0.md - Release notes
  • docs/remote-signing.md - Documentation (191 additions, 12 deletions)
  • itest/list_on_test.go - Test file
  • itest/lnd_remote_signer_test.go - Test file (441 additions, 84 deletions)
  • lntest/harness.go - Test harness
  • lntest/mock/walletcontroller.go - Mock wallet controller
  • lnwallet/rpcwallet/remote_signer_client_test.go - Test file (730 lines)
  • lnwallet/rpcwallet/sign_coordinator_test.go - Test file (1090 lines)
  • lnrpc/walletrpc/walletkit_server_test.go - Test file
  • Auto-generated proto files (*.pb.go, *.pb.gw.go, *.swagger.json, etc.)

Analysis

This PR introduces a comprehensive outbound remote signer implementation, which is a fundamental architectural change to how LND handles transaction signing. This warrants CRITICAL severity for the following reasons:

Primary Concerns:

  1. Core Wallet Signing Operations: The PR extensively modifies lnwallet/rpcwallet/*, introducing a new sign_coordinator.go (985 lines) that centralizes signing logic and coordinates between local and remote signers. This is critical infrastructure that touches every transaction signing operation.

  2. Multiple Critical Packages: The PR modifies multiple critical components simultaneously:

    • Core wallet operations (lnwallet/*)
    • Core server coordination (server.go)
    • Core RPC server (rpcserver.go)
    • RPC permissions system (rpcperms/interceptor.go)
  3. Scale: ~5,000 substantive lines of new/modified code (excluding auto-generated proto files and tests), with 40+ substantive files changed across critical systems.

  4. Architectural Complexity: The introduction of remote signing adds significant complexity to the signing flow, including:

    • Connection management and health checking
    • Failover logic between local and remote signers
    • Coordination of signing requests across distributed systems
    • New error handling paths for remote signing failures

Key Risk Areas:

  • Signing correctness: Any bugs in the signing coordinator could lead to invalid signatures, failed channel operations, or loss of funds
  • State synchronization: Remote signer state must stay synchronized with local state
  • Security boundaries: The RPC permissions changes and new remote signer RPC endpoints need careful security review
  • Backward compatibility: Changes affect core server initialization and configuration

Recommendation: This PR requires expert review from maintainers familiar with the wallet signing infrastructure, channel state machine, and security model. Special attention should be paid to the sign_coordinator.go implementation and the integration points in server.go and rpcserver.go.


To override, add a severity-override-{critical,high,medium,low} label.

@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from b89ea08 to 23213ea Compare February 9, 2026 10:57
@lightninglabs-deploy
Copy link
Collaborator

🔴 PR Severity: CRITICAL

PR #8754 | 62 files | 9,704 additions / 2,028 deletions

🔴 Critical (9 files)
  • lnwallet/rpcwallet/rpcwallet.go - Core wallet RPC implementation modified (63 additions, 109 deletions)
  • lnwallet/rpcwallet/remote_signer_client.go - New remote signing client implementation (911 lines)
  • lnwallet/rpcwallet/sign_coordinator.go - New sign coordination logic for remote signer (985 lines)
  • lnwallet/rpcwallet/remote_signer_connection.go - New remote signer connection management (373 lines)
  • lnwallet/interface.go - Wallet interface modifications
  • lnwallet/btcwallet/btcwallet.go - BTC wallet implementation changes
  • lnwallet/mock.go - Wallet mock updates
  • server.go - Core server coordination changes (44 additions, 15 deletions)
  • rpcserver.go - RPC server modifications (113 additions, 88 deletions)
🟠 High (28 files)
  • lnrpc/autopilotrpc/autopilot_server.go - Autopilot RPC server
  • lnrpc/autopilotrpc/driver.go - Autopilot driver
  • lnrpc/chainrpc/chain_server.go - Chain RPC server
  • lnrpc/chainrpc/driver.go - Chain driver
  • lnrpc/devrpc/dev_server.go - Dev RPC server
  • lnrpc/devrpc/driver.go - Dev driver
  • lnrpc/invoicesrpc/driver.go - Invoices driver
  • lnrpc/invoicesrpc/invoices_server.go - Invoices RPC server
  • lnrpc/neutrinorpc/driver.go - Neutrino driver
  • lnrpc/neutrinorpc/neutrino_server.go - Neutrino RPC server
  • lnrpc/peersrpc/driver.go - Peers driver
  • lnrpc/peersrpc/peers_server.go - Peers RPC server
  • lnrpc/routerrpc/driver.go - Router driver
  • lnrpc/routerrpc/router_server.go - Router RPC server
  • lnrpc/rpc_utils.go - RPC utilities
  • lnrpc/signrpc/driver.go - Sign RPC driver
  • lnrpc/signrpc/signer_server.go - Signer RPC server
  • lnrpc/stateservice.proto - State service API definition
  • lnrpc/sub_server.go - Sub-server utilities
  • lnrpc/verrpc/server.go - Version RPC server
  • lnrpc/walletrpc/config_active.go - Wallet RPC config
  • lnrpc/walletrpc/driver.go - Wallet RPC driver
  • lnrpc/walletrpc/walletkit.proto - WalletKit API definition (238 additions)
  • lnrpc/walletrpc/walletkit_server.go - WalletKit RPC server (98 additions, 28 deletions)
  • lnrpc/watchtowerrpc/driver.go - Watchtower driver
  • lnrpc/watchtowerrpc/handler.go - Watchtower handler
  • lnrpc/wtclientrpc/driver.go - Watchtower client driver
  • lnrpc/wtclientrpc/wtclient.go - Watchtower client
🟡 Medium (8 files)
  • config.go - Main configuration file
  • config_builder.go - Configuration builder
  • lncfg/remotesigner.go - Remote signer configuration (165 additions, 17 deletions)
  • lnd.go - Main entry point (66 additions, 8 deletions)
  • rpcperms/interceptor.go - RPC permissions interceptor (58 additions, 6 deletions)
  • sample-lnd.conf - Sample configuration file (77 additions, 4 deletions)
  • subrpcserver_config.go - Sub RPC server config
  • lnwallet/rpcwallet/healthcheck.go - Health check logic
🟢 Low (17 files)
  • docs/release-notes/release-notes-0.21.0.md - Release notes
  • docs/remote-signing.md - Documentation (191 additions, 12 deletions)
  • itest/list_on_test.go - Integration test
  • itest/lnd_remote_signer_test.go - Remote signer integration test (441 additions, 84 deletions)
  • lntest/harness.go - Test harness
  • lntest/mock/walletcontroller.go - Mock wallet controller
  • lnwallet/rpcwallet/remote_signer_client_test.go - Unit tests (730 lines)
  • lnwallet/rpcwallet/sign_coordinator_test.go - Unit tests (1,095 lines)
  • lnrpc/walletrpc/walletkit_server_test.go - Unit tests
  • Auto-generated protobuf files (*.pb.go, *.pb.gw.go, *.swagger.json) - 8 files with ~2,900 additions

Analysis

This PR introduces Outbound Remote Signer functionality, which enables lnd to delegate signing operations to an external signer service. This is classified as CRITICAL for the following reasons:

Primary Concerns:

  1. Core Wallet Operations Modified: The PR extensively modifies lnwallet/rpcwallet/*, which is responsible for wallet operations, channel funding, and signing. This includes:

    • A complete refactoring of the RPC wallet implementation
    • New remote signer client with 911 lines of code
    • New sign coordinator with 985 lines implementing complex signing orchestration
    • Remote connection management with health checking and reconnection logic
  2. Security-Sensitive Changes: Signing operations are fundamental to Lightning Network security. Any bugs in:

    • The sign coordinator's decision logic (local vs remote signing)
    • Connection management and failover behavior
    • RPC communication with the remote signer
    • Could lead to loss of funds or security vulnerabilities
  3. Server Coordination Changes: Both server.go and rpcserver.go are modified, affecting core server initialization and RPC handling.

  4. Scale and Complexity:

    • 62 files changed (30+ non-test, non-generated files)
    • ~3,500+ lines of substantive changes (excluding auto-generated protobuf code)
    • Multiple critical subsystems affected (lnwallet, server, RPC layer)
    • New RPC methods added to WalletKit API

Recommended Review Focus:

  • Sign coordinator logic: Ensure correct decision-making between local and remote signing
  • Connection management: Verify proper handling of disconnections, reconnections, and timeouts
  • Error handling: Ensure signing failures are handled safely and don't leave channels in inconsistent states
  • RPC security: Verify authentication/authorization for remote signer connections
  • State machine interactions: Ensure compatibility with existing wallet state transitions
  • Testing coverage: Verify comprehensive test coverage for edge cases and failure modes

Risk Assessment: This change introduces a new architectural component (remote signing) that sits in the critical path of all signing operations. Expert review from maintainers familiar with lnwallet, signing operations, and channel state management is essential.


To override, add a severity-override-{critical,high,medium,low} label.

@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from 23213ea to 967cb5d Compare February 9, 2026 11:24
@lightninglabs-deploy
Copy link
Collaborator

🔴 PR Severity: CRITICAL

Add Outbound Remote Signer implementation | 62 files | +9706/-2028 lines

🔴 Critical (12 files)
  • lnwallet/btcwallet/btcwallet.go - Wallet operations core implementation
  • lnwallet/interface.go - Wallet interface definitions affecting all implementations
  • lnwallet/mock.go - Wallet mock implementation
  • lnwallet/rpcwallet/healthcheck.go - RPC wallet health check logic
  • lnwallet/rpcwallet/remote_signer_client.go - NEW Remote signer client (+911 lines)
  • lnwallet/rpcwallet/remote_signer_client_builder.go - NEW Remote signer client builder (+69 lines)
  • lnwallet/rpcwallet/remote_signer_connection.go - NEW Remote signer connection management (+373 lines)
  • lnwallet/rpcwallet/remote_signer_connection_builder.go - NEW Remote signer connection builder (+50 lines)
  • lnwallet/rpcwallet/rpcwallet.go - RPC wallet implementation changes
  • lnwallet/rpcwallet/sign_coordinator.go - NEW Sign coordinator logic (+985 lines)
  • server.go - Core server coordination changes
  • rpcserver.go - Core RPC server modifications
🟠 High (24 files)
  • lnrpc/walletrpc/driver.go - Wallet RPC driver
  • lnrpc/walletrpc/walletkit_server.go - Wallet kit RPC server
  • lnrpc/autopilotrpc/autopilot_server.go - Autopilot RPC server
  • lnrpc/autopilotrpc/driver.go - Autopilot RPC driver
  • lnrpc/chainrpc/chain_server.go - Chain RPC server
  • lnrpc/chainrpc/driver.go - Chain RPC driver
  • lnrpc/devrpc/dev_server.go - Dev RPC server
  • lnrpc/devrpc/driver.go - Dev RPC driver
  • lnrpc/invoicesrpc/driver.go - Invoices RPC driver
  • lnrpc/invoicesrpc/invoices_server.go - Invoices RPC server
  • lnrpc/neutrinorpc/driver.go - Neutrino RPC driver
  • lnrpc/neutrinorpc/neutrino_server.go - Neutrino RPC server
  • lnrpc/peersrpc/driver.go - Peers RPC driver
  • lnrpc/peersrpc/peers_server.go - Peers RPC server
  • lnrpc/routerrpc/driver.go - Router RPC driver
  • lnrpc/routerrpc/router_server.go - Router RPC server
  • lnrpc/signrpc/driver.go - Signer RPC driver
  • lnrpc/signrpc/signer_server.go - Signer RPC server
  • lnrpc/watchtowerrpc/driver.go - Watchtower RPC driver
  • lnrpc/watchtowerrpc/handler.go - Watchtower RPC handler
  • lnrpc/wtclientrpc/driver.go - Watchtower client RPC driver
  • lnrpc/wtclientrpc/wtclient.go - Watchtower client RPC implementation
  • lnrpc/verrpc/server.go - Version RPC server
  • rpcperms/interceptor.go - RPC permissions interceptor (auth/security)
🟡 Medium (9 files)
  • lncfg/remotesigner.go - Remote signer configuration
  • config.go - Main configuration
  • config_builder.go - Configuration builder
  • lnd.go - Main application entry point
  • subrpcserver_config.go - Sub RPC server configuration
  • lnrpc/rpc_utils.go - RPC utilities
  • lnrpc/sub_server.go - Sub server implementation
  • lnrpc/walletrpc/walletkit.proto - Wallet kit proto API changes
  • lnrpc/stateservice.proto - State service proto API changes
🟢 Low (10 files - tests/docs/generated)

Documentation:

  • docs/release-notes/release-notes-0.21.0.md - Release notes
  • docs/remote-signing.md - Remote signing documentation
  • sample-lnd.conf - Sample configuration

Tests:

  • itest/list_on_test.go - Integration test
  • itest/lnd_remote_signer_test.go - Remote signer integration test
  • lntest/harness.go - Test harness
  • lntest/mock/walletcontroller.go - Mock wallet controller
  • lnwallet/rpcwallet/remote_signer_client_test.go - Remote signer client tests
  • lnwallet/rpcwallet/sign_coordinator_test.go - Sign coordinator tests
  • lnrpc/walletrpc/walletkit_server_test.go - Wallet kit server tests
⚙️ Auto-generated (7 files - excluded from severity calculation)
  • lnrpc/stateservice.pb.go
  • lnrpc/walletrpc/walletkit.pb.go
  • lnrpc/walletrpc/walletkit.pb.gw.go
  • lnrpc/walletrpc/walletkit_grpc.pb.go
  • lnrpc/stateservice.swagger.json
  • lnrpc/walletrpc/walletkit.swagger.json
  • lnrpc/walletrpc/walletkit.yaml

Analysis

This PR is classified as CRITICAL due to the following factors:

Primary Concerns:

  1. Wallet Operations Core - Introduces a complete remote signer implementation within lnwallet/*, which handles private key operations, signing, and wallet coordination. This is one of the most security-critical areas of lnd.

  2. New Signing Architecture - Adds 3,300+ lines of new code across critical signing components:

    • Sign coordinator (985 lines) - orchestrates signing operations
    • Remote signer client (911 lines) - manages communication with remote signers
    • Connection management (373 lines) - handles remote signer connectivity
  3. Multiple Critical Packages - Changes span across distinct critical areas:

    • lnwallet/* (wallet and signing operations)
    • server.go and rpcserver.go (core server coordination)
    • RPC permission interceptor (authentication/security)
  4. Scale - 45 non-test/non-generated files changed, exceeding the 20-file threshold for severity bump consideration.

  5. Security Surface - Introduces new network-based signing paths, adding remote communication to previously local-only signing operations. This significantly expands the attack surface for key management.

Recommended Review Focus:

  • Private key handling and signing logic in sign coordinator
  • Connection security and authentication in remote signer client
  • Error handling and fallback mechanisms
  • RPC permission changes and access control
  • State synchronization between local and remote signers
  • Recovery scenarios and edge cases

This PR requires expert-level review from maintainers familiar with lnd's wallet architecture, signing protocols, and security model.


To override, add a severity-override-{critical,high,medium,low} label.

@ViktorT-11
Copy link
Collaborator Author

Thanks a lot for the thorough review @yyforyongyu 🎉🙏! I've addressed your feedback with the above pushes 🎉

@ViktorT-11 ViktorT-11 force-pushed the 2024-05-add-outbound-remote-signer branch from 967cb5d to b62e7cd Compare February 9, 2026 17:38
@lightninglabs-deploy
Copy link
Collaborator

🔴 PR Severity: CRITICAL

Remote Signer Implementation | 62 files | ~11,700 lines changed (~2,500 non-test/non-generated)

🔴 Critical (9 files)
  • lnwallet/rpcwallet/sign_coordinator.go - New signing coordination logic (985 lines) - core signing infrastructure
  • lnwallet/rpcwallet/remote_signer_client.go - Remote signer client implementation (911 lines) - handles external signing requests
  • lnwallet/rpcwallet/remote_signer_connection.go - Connection management for remote signer (373 lines)
  • lnwallet/rpcwallet/rpcwallet.go - Modified core wallet RPC implementation (63 additions, 109 deletions)
  • lnwallet/rpcwallet/remote_signer_client_builder.go - Builder for remote signer client
  • lnwallet/rpcwallet/remote_signer_connection_builder.go - Builder for remote signer connection
  • lnwallet/btcwallet/btcwallet.go - Core wallet interface modifications
  • lnwallet/interface.go - Wallet interface definition changes
  • server.go - Core server coordination changes (44 additions, 15 deletions)
  • rpcserver.go - Core RPC server modifications (113 additions, 88 deletions)
🟠 High (20+ files)
  • Multiple lnrpc/*/driver.go files - RPC subsystem driver modifications
  • Multiple lnrpc/*/server.go files - RPC server implementations
  • rpcperms/interceptor.go - RPC permissions/authentication (58 additions)
  • lnrpc/walletrpc/walletkit_server.go - Wallet kit server changes
  • Other RPC subsystem files
🟡 Medium (10 files)
  • config.go, config_builder.go - Configuration changes
  • lncfg/remotesigner.go - Remote signer configuration (165 additions)
  • lnd.go - Main entry point changes
  • subrpcserver_config.go - Sub-RPC server configuration
  • lnrpc/walletrpc/walletkit.proto - New API definitions (238 additions)
  • lnrpc/stateservice.proto - State service proto changes
  • sample-lnd.conf - Configuration file updates
🟢 Low (20+ files)
  • docs/* - Documentation and release notes
  • itest/* - Integration tests
  • lntest/* - Test harness
  • *.pb.go, *.pb.gw.go, *.swagger.json - Auto-generated protobuf files
  • *_test.go - Test files

Analysis

This PR implements a major new feature: Outbound Remote Signer support. This is classified as CRITICAL because:

  1. Core Wallet Signing Infrastructure: The PR adds substantial new code to lnwallet/rpcwallet/*, including:

    • A new sign coordinator (985 lines) that manages signing operations
    • Remote signer client implementation (911 lines) that communicates with external signing services
    • Connection management logic (373 lines)
    • Significant refactoring of existing wallet RPC code
  2. Security-Critical Functionality: Remote signing involves delegating private key operations to external services, which is fundamental to wallet security. Any bugs in this implementation could lead to:

    • Incorrect signatures leading to invalid transactions
    • Channel state corruption
    • Fund loss scenarios
  3. Core Server Changes: Modifications to server.go and rpcserver.go affect the main LND server initialization and RPC handling paths

  4. Scope: The PR touches multiple critical subsystems:

    • Wallet layer (signing operations)
    • RPC layer (new APIs and modified drivers)
    • Server coordination
    • Configuration management
  5. Scale Factors:

    • 62 total files changed (40+ non-test/non-generated files)
    • ~2,500 lines of production code (excluding tests and auto-generated files)
    • Multiple distinct critical packages affected

Recommendation

This PR requires expert review with deep knowledge of:

  • LND wallet signing architecture
  • Lightning commitment transaction structure
  • Remote signing security models
  • HTLC handling and channel state machines

Reviewers should pay special attention to:

  • Sign coordinator state machine correctness
  • Error handling in remote signer communication
  • Fallback behavior when remote signer is unavailable
  • Race conditions in concurrent signing operations
  • Compatibility with existing wallet implementations

To override, add a severity-override-{critical,high,medium,low} label.

Copy link
Collaborator

@starius starius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Thanks! 🚀

I went through all the commits, left some comments. Most of them are local, but some are large scope:

  • I propose a stable naming for remote signer operation modes. Word "inbound" and "outbound" change their meaning depending to which side they are applied. I propose to use node2signer and signer2node or something similar (direction-explicit and perspective-neutral).
  • We should consider a separate RPC port for accepting remote signer connections instead of reusing the main lnd RPC port. Sharing the port creates pressure to bind on 0.0.0.0, exposing the full RPC surface. A dedicated listener would also eliminate the need for the ALLOW_REMOTE_SIGNER state machine. Fine as a follow-up, but worth deciding on before this ships.

Comment on lines +54 to +55
// Else, we are in outbound mode, so we verify the connection config.
err := r.ConnectionCfg.Validate()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this terminology is a bit confusing. Here the remote signer is in Inbound mode, but the watch-only mode is in outbound mode, IIUC. It is hard to map the word to the actual mode of operation being used.

Can we use the naming so that it is called the same on both sides? What about this one:

  • node2signer - Watch-only node connects to signer (legacy)
  • signer2node - Signer connects to watch-only node (new)

Comment on lines +552 to +557
/*
The registration challenge allows the remote signer to pass data that will
be signed by the watch-only lnd. The resulting signature will be returned in
the RegistrationResponse message.
*/
string registration_challenge = 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which key does the node use to sign the challenge? Is it the main node key used for node id? Could you add more details here, please?

If the signing scheme is not decided yet, we can drop the field for now or make it bytes, since bytes is a better type to binary data like a challenge or a signature.

Comment on lines +29 to +31
// Ready returns a channel that nil gets sent over once the remote
// signer is ready to accept requests.
Ready(ctx context.Context) chan error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens is ctx expires? Could you expand the godoc, please?

// waits for the corresponding response.
DeriveSharedKey(ctx context.Context,
in *signrpc.SharedKeyRequest,
opts ...grpc.CallOption) (*signrpc.SharedKeyResponse, error)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to remove opts ...grpc.CallOption here and in methods below to make the interface more honest? We don't pass them at least in signer-to-node mode. And they are not actually used in the legacy mode anyway.

err := remoteSigner.connect(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("error connecting to the remote "+
"signing node through RPC: %v", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use %w for the error

Comment on lines +361 to +367
/*
SignCoordinatorStreams dispatches a bi-directional streaming RPC that
allows a remote signer to connect to lnd and remotely provide the signatures
required for any on-chain related transactions or messages.
*/
rpc SignCoordinatorStreams (stream SignCoordinatorResponse)
returns (stream SignCoordinatorRequest);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to consider the following idea before shipping. Maybe in a follow-up PR.

Can we make a separate gRPC server for SignCoordinatorStreams RPC? If the main RPC server (the one used by lncli) serves this, then it is very tempting to bind the main RPC server on 0.0.0.0 to allow access for SignCoordinatorStreams from the remote signer. But opening world access to the main RPC server is not a good idea from security standpoint. It is possible to use port forwarding from remote signer but not everyone will do it. We can reduce attack surface of the configuration which is the most convenient to use.

We can actually shoot two birds with one stone: if we have a separate RPC server for SignCoordinatorStreams, we don't need ALLOW_REMOTE_SIGNER! The separate RPC server will start independently and have its own rules on when to allow connections.

reflect.ValueOf(chanStateDB),
)

kRing, ok := cc.Wallet.WalletController.(*rpcwallet.RPCKeyRing)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use an interface having RemoteSignerConnection() method instead of *rpcwallet.RPCKeyRing

// has been executed, the read lock should be held when accessing values
// from the cfg.
// The write lock should be held when setting the cfg.
sync.RWMutex
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mu sync.RWMutex

So public methods of mutex (Lock, Unlock, etc) do not propagate to WalletKit.

Comment on lines +253 to +256
**Note:** The `save_to` path should match the `remotesigner.macaroonpath`
specified in step 1. If the signer and watch-only nodes are on separate
environments, move the macaroon to the `remotesigner.macaroonpath` after baking
it instead.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be watchonlynode.macaroonpath, not remotesigner.macaroonpath

Comment on lines +276 to +291
It is also recommended to set the following parameter, which defines the
interval at which the watch-only node will check if the signer node is still
connected. If the signer node is disconnected during a check, the watch-only
node will shut down:

```text
# Set the interval for how often the watch-only node will check that the signer
# node is still connected.
healthcheck.remotesigner.interval=5s
```

Since the signer node set up in Step 1 increases the delay between connection
attempts slightly with each failed attempt, it may take some time before it
reconnects to the watch-only node after it has been started. Setting a high
value for this configuration field will help ensure that the watch-only node
does not time out when starting up.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This conflates two things. The healthcheck.remotesigner.interval controls how often the watch-only node pings the signer after connection. The startup timeout is controlled by remotesigner.startuptimeout (default 5 minutes). The health check interval has nothing to do with startup timeouts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants