• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14000719599

21 Mar 2025 08:54PM UTC coverage: 58.717% (-10.3%) from 68.989%
14000719599

Pull #8754

github

web-flow
Merge 29f363f18 into 5235f3b24
Pull Request #8754: Add `Outbound` Remote Signer implementation

1562 of 2088 new or added lines in 41 files covered. (74.81%)

28126 existing lines in 464 files now uncovered.

97953 of 166822 relevant lines covered (58.72%)

1.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.2
/lncfg/remotesigner.go
1
package lncfg
2

3
import (
4
        "fmt"
5
        "time"
6
)
7

8
const (
9
        // DefaultRemoteSignerRPCTimeout is the default connection timeout
10
        // that is used when connecting to the remote signer or watch-only node
11
        // through RPC.
12
        DefaultRemoteSignerRPCTimeout = 5 * time.Second
13

14
        // DefaultRequestTimeout is the default timeout used for requests to and
15
        // from the remote signer.
16
        DefaultRequestTimeout = 5 * time.Second
17

18
        // DefaultStartupTimeout is the default startup timeout used when a
19
        // watch-only node with 'remotesigner.allowinboundconnection' set to
20
        // true waits for the remote signer to connect.
21
        DefaultStartupTimeout = 5 * time.Minute
22
)
23

24
// RemoteSigner holds the configuration options for how to connect to a remote
25
// signer. Only a watch-only node specifies this config.
26
//
27
//nolint:ll
28
type RemoteSigner struct {
29
        // Enable signals if this node is a watch-only node in a remote signer
30
        // setup.
31
        Enable bool `long:"enable" description:"Use a remote signer for signing any on-chain related transactions or messages. Only recommended if local wallet is initialized as watch-only. Remote signer must use the same seed/root key as the local watch-only wallet but must have private keys."`
32

33
        // AllowInboundConnection is true if the remote signer node will connect
34
        // to this node.
35
        AllowInboundConnection bool `long:"allowinboundconnection" description:"Signals that we allow an inbound connection from a remote signer to this node."`
36

37
        // MigrateWatchOnly migrates the wallet to a watch-only wallet by
38
        // purging all private keys from the wallet after first unlock with this
39
        // flag.
40
        MigrateWatchOnly bool `long:"migrate-wallet-to-watch-only" description:"If a wallet with private key material already exists, migrate it into a watch-only wallet on first startup. WARNING: This cannot be undone! Make sure you have backed up your seed before you use this flag! All private keys will be purged from the wallet after first unlock with this flag!"`
41

42
        // ConnectionCfg holds the connection configuration options that the
43
        // watch-only node will use when setting up the connection to the remote
44
        // signer.
45
        ConnectionCfg
46

47
        // inboundWatchOnlyCfg holds the configuration options specifically
48
        // used when the watch-only node expects an inbound connection from
49
        // the remote signer.
50
        inboundWatchOnlyCfg
51
}
52

53
// DefaultRemoteSignerCfg returns the default RemoteSigner config.
54
func DefaultRemoteSignerCfg() *RemoteSigner {
3✔
55
        return &RemoteSigner{
3✔
56
                Enable:                 false,
3✔
57
                AllowInboundConnection: false,
3✔
58
                ConnectionCfg:          defaultConnectionCfg(),
3✔
59
                inboundWatchOnlyCfg: inboundWatchOnlyCfg{
3✔
60
                        StartupTimeout: DefaultStartupTimeout,
3✔
61
                },
3✔
62
        }
3✔
63
}
3✔
64

65
// Validate checks the values configured for our remote RPC signer.
66
func (r *RemoteSigner) Validate() error {
3✔
67
        if r.MigrateWatchOnly && !r.Enable {
3✔
NEW
68
                return fmt.Errorf("remote signer: cannot turn on wallet " +
×
NEW
69
                        "migration to watch-only if remote signing is not " +
×
NEW
70
                        "enabled")
×
NEW
71
        }
×
72

73
        if !r.Enable {
6✔
74
                return nil
3✔
75
        }
3✔
76

77
        if r.AllowInboundConnection {
6✔
78
                if r.StartupTimeout < time.Second {
3✔
NEW
79
                        return fmt.Errorf("remotesigner.startuptimeout of "+
×
NEW
80
                                "%v is invalid, cannot be smaller than %v",
×
NEW
81
                                r.Timeout, time.Second)
×
NEW
82
                }
×
83

84
                return nil
3✔
85
        }
86

87
        // Else, we are in outbound mode, so we verify the connection config.
88
        err := r.ConnectionCfg.Validate()
3✔
89
        if err != nil {
3✔
NEW
90
                return fmt.Errorf("remotesigner.%w", err)
×
NEW
91
        }
×
92

93
        return nil
3✔
94
}
95

96
// inboundWatchOnlyCfg holds the configuration options specific for watch-only
97
// nodes with the allowinboundconnection` option set.
98
//
99
//nolint:ll
100
type inboundWatchOnlyCfg struct {
101
        StartupTimeout time.Duration `long:"startuptimeout" description:"The time the watch-only node will wait for the remote signer to connect during startup. If the timeout expires before the remote signer connects, the watch-only node will shut down. Valid time units are {s, m, h}."`
102
}
103

104
// WatchOnlyNode holds the configuration options for how to connect to a watch
105
// only node. Only a signer node specifies this config.
106
//
107
//nolint:ll
108
type WatchOnlyNode struct {
109
        // Enable signals if this node a signer node and is expected to connect
110
        // to a watch-only node.
111
        Enable bool `long:"enable" description:"Signals that this node functions as a remote signer that will to connect with a watch-only node."`
112

113
        // ConnectionCfg holds the connection configuration options that the
114
        // remote signer node will use when setting up the connection to the
115
        // watch-only node.
116
        ConnectionCfg
117
}
118

119
// DefaultWatchOnlyNodeCfg returns the default WatchOnlyNode config.
120
func DefaultWatchOnlyNodeCfg() *WatchOnlyNode {
3✔
121
        return &WatchOnlyNode{
3✔
122
                Enable:        false,
3✔
123
                ConnectionCfg: defaultConnectionCfg(),
3✔
124
        }
3✔
125
}
3✔
126

127
// Validate checks the values set in the WatchOnlyNode config are valid.
128
func (w *WatchOnlyNode) Validate() error {
3✔
129
        if !w.Enable {
6✔
130
                return nil
3✔
131
        }
3✔
132

133
        err := w.ConnectionCfg.Validate()
3✔
134
        if err != nil {
3✔
NEW
135
                return fmt.Errorf("watchonlynode.%w", err)
×
NEW
136
        }
×
137

138
        return nil
3✔
139
}
140

141
// ConnectionCfg holds the configuration options required when setting up a
142
// connection to either a remote signer or watch-only node, depending on which
143
// side makes the outbound connection.
144
//
145
//nolint:ll
146
type ConnectionCfg struct {
147
        RPCHost        string        `long:"rpchost" description:"The RPC host:port of the remote signer or watch-only node. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's RPC host:port. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's RPC host:port."`
148
        MacaroonPath   string        `long:"macaroonpath" description:"The macaroon to use for authenticating with the remote signer or the watch-only node. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's macaroon. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's macaroon."`
149
        TLSCertPath    string        `long:"tlscertpath" description:"The TLS certificate to use for establishing the remote signer's or watch-only node's identity. For watch-only nodes with 'remotesigner.allowinboundconnection' set to false (the default value if not specifically set), this should be set to the remote signer's TLS certificate. For remote signer nodes connecting to a watch-only node with 'remotesigner.allowinboundconnection' set to true, this should be set to the watch-only node's TLS certificate."`
150
        Timeout        time.Duration `long:"timeout" description:"The timeout for making the connection to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."`
151
        RequestTimeout time.Duration `long:"requesttimeout" description:"The time we will wait when making requests to the remote signer or watch-only node, depending on whether the node acts as a watch-only node or a signer. Valid time units are {s, m, h}."`
152
}
153

154
// defaultConnectionCfg returns the default ConnectionCfg config.
155
func defaultConnectionCfg() ConnectionCfg {
3✔
156
        return ConnectionCfg{
3✔
157
                Timeout:        DefaultRemoteSignerRPCTimeout,
3✔
158
                RequestTimeout: DefaultRequestTimeout,
3✔
159
        }
3✔
160
}
3✔
161

162
// Validate checks the values set in the ConnectionCfg config are valid.
163
func (c *ConnectionCfg) Validate() error {
3✔
164
        if c.Timeout < time.Millisecond {
3✔
NEW
165
                return fmt.Errorf("timeout of %v is invalid, cannot be "+
×
NEW
166
                        "smaller than %v", c.Timeout, time.Millisecond)
×
NEW
167
        }
×
168

169
        if c.RequestTimeout < time.Second {
3✔
NEW
170
                return fmt.Errorf("requesttimeout of %v is invalid, cannot "+
×
NEW
171
                        "be smaller than %v", c.RequestTimeout, time.Second)
×
NEW
172
        }
×
173

174
        if c.RPCHost == "" {
3✔
NEW
175
                return fmt.Errorf("rpchost must be set")
×
NEW
176
        }
×
177

178
        if c.MacaroonPath == "" {
3✔
NEW
179
                return fmt.Errorf("macaroonpath must be set")
×
NEW
180
        }
×
181

182
        if c.TLSCertPath == "" {
3✔
NEW
183
                return fmt.Errorf("tlscertpath must be set")
×
UNCOV
184
        }
×
185

186
        return nil
3✔
187
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc