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

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

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

0.0
/watchtower/standalone.go
1
package watchtower
2

3
import (
4
        "net"
5
        "sync/atomic"
6
        "time"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/lightningnetwork/lnd/brontide"
10
        "github.com/lightningnetwork/lnd/lnencrypt"
11
        "github.com/lightningnetwork/lnd/tor"
12
        "github.com/lightningnetwork/lnd/watchtower/lookout"
13
        "github.com/lightningnetwork/lnd/watchtower/wtserver"
14
)
15

16
// Standalone encapsulates the server-side functionality required by watchtower
17
// clients. A Standalone couples the two primary subsystems such that, as a
18
// unit, this instance can negotiate sessions with clients, accept state updates
19
// for active sessions, monitor the chain for breaches matching known breach
20
// hints, publish reconstructed justice transactions on behalf of tower clients.
21
type Standalone struct {
22
        started uint32 // to be used atomically
23
        stopped uint32 // to be used atomically
24

25
        cfg *Config
26

27
        // listeners is a reference to the wtserver's listeners.
28
        listeners []net.Listener
29

30
        // server is the client endpoint, used for negotiating sessions and
31
        // uploading state updates.
32
        server wtserver.Interface
33

34
        // lookout is a service that monitors the chain and inspects the
35
        // transactions found in new blocks against the state updates received
36
        // by the server.
37
        lookout lookout.Service
38
}
39

40
// New validates the passed Config and returns a fresh Standalone instance if
41
// the tower's subsystems could be properly initialized.
42
func New(cfg *Config) (*Standalone, error) {
×
43
        // The tower must have listening address in order to accept new updates
×
44
        // from clients.
×
45
        if len(cfg.ListenAddrs) == 0 {
×
46
                return nil, ErrNoListeners
×
47
        }
×
48

49
        // Assign the default read timeout if none is provided.
50
        if cfg.ReadTimeout == 0 {
×
51
                cfg.ReadTimeout = DefaultReadTimeout
×
52
        }
×
53

54
        // Assign the default write timeout if none is provided.
55
        if cfg.WriteTimeout == 0 {
×
56
                cfg.WriteTimeout = DefaultWriteTimeout
×
57
        }
×
58

59
        punisher := lookout.NewBreachPunisher(&lookout.PunisherConfig{
×
60
                PublishTx: cfg.PublishTx,
×
61
        })
×
62

×
63
        // Initialize the lookout service with its required resources.
×
64
        lookout := lookout.New(&lookout.Config{
×
65
                BlockFetcher:   cfg.BlockFetcher,
×
66
                DB:             cfg.DB,
×
67
                EpochRegistrar: cfg.EpochRegistrar,
×
68
                Punisher:       punisher,
×
69
                MinBackoff:     time.Second,
×
70
                MaxBackoff:     time.Minute,
×
71
                MaxNumRetries:  5,
×
72
        })
×
73

×
74
        // Create a brontide listener on each of the provided listening
×
75
        // addresses. Client should be able to connect to any of open ports to
×
76
        // communicate with this Standalone instance.
×
77
        listeners := make([]net.Listener, 0, len(cfg.ListenAddrs))
×
78
        for _, listenAddr := range cfg.ListenAddrs {
×
79
                listener, err := brontide.NewListener(
×
80
                        cfg.NodeKeyECDH, listenAddr.String(),
×
81
                        brontide.DisabledBanClosure,
×
82
                )
×
83
                if err != nil {
×
84
                        return nil, err
×
85
                }
×
86

87
                listeners = append(listeners, listener)
×
88
        }
89

90
        // Initialize the server with its required resources.
91
        server, err := wtserver.New(&wtserver.Config{
×
92
                ChainHash:     cfg.ChainHash,
×
93
                DB:            cfg.DB,
×
94
                NodeKeyECDH:   cfg.NodeKeyECDH,
×
95
                Listeners:     listeners,
×
96
                ReadTimeout:   cfg.ReadTimeout,
×
97
                WriteTimeout:  cfg.WriteTimeout,
×
98
                NewAddress:    cfg.NewAddress,
×
99
                DisableReward: true,
×
100
        })
×
101
        if err != nil {
×
102
                return nil, err
×
103
        }
×
104

105
        return &Standalone{
×
106
                cfg:       cfg,
×
107
                listeners: listeners,
×
108
                server:    server,
×
109
                lookout:   lookout,
×
110
        }, nil
×
111
}
112

113
// Start idempotently starts the Standalone, an error is returned if the
114
// subsystems could not be initialized.
115
func (w *Standalone) Start() error {
×
116
        if !atomic.CompareAndSwapUint32(&w.started, 0, 1) {
×
117
                return nil
×
118
        }
×
119

120
        log.Infof("Starting watchtower")
×
121

×
122
        // If a tor controller exists in the config, then automatically create a
×
123
        // hidden service for the watchtower to accept inbound connections from.
×
124
        if w.cfg.TorController != nil {
×
125
                log.Infof("Creating watchtower hidden service")
×
126
                if err := w.createNewHiddenService(); err != nil {
×
127
                        return err
×
128
                }
×
129
        }
130

131
        if err := w.lookout.Start(); err != nil {
×
132
                return err
×
133
        }
×
134
        if err := w.server.Start(); err != nil {
×
135
                w.lookout.Stop()
×
136
                return err
×
137
        }
×
138

139
        log.Infof("Watchtower started successfully")
×
140

×
141
        return nil
×
142
}
143

144
// Stop idempotently stops the Standalone and blocks until the subsystems have
145
// completed their shutdown.
146
func (w *Standalone) Stop() error {
×
147
        if !atomic.CompareAndSwapUint32(&w.stopped, 0, 1) {
×
148
                return nil
×
149
        }
×
150

151
        log.Infof("Stopping watchtower")
×
152

×
153
        w.server.Stop()
×
154
        w.lookout.Stop()
×
155

×
156
        log.Infof("Watchtower stopped successfully")
×
157

×
158
        return nil
×
159
}
160

161
// createNewHiddenService automatically sets up a v2 or v3 onion service in
162
// order to listen for inbound connections over Tor.
163
func (w *Standalone) createNewHiddenService() error {
×
164
        // Get all the ports the watchtower is listening on. These will be used to
×
165
        // map the hidden service's virtual port.
×
166
        listenPorts := make([]int, 0, len(w.listeners))
×
167
        for _, listener := range w.listeners {
×
168
                port := listener.Addr().(*net.TCPAddr).Port
×
169
                listenPorts = append(listenPorts, port)
×
170
        }
×
171

172
        encrypter, err := lnencrypt.KeyRingEncrypter(w.cfg.KeyRing)
×
173
        if err != nil {
×
174
                return err
×
175
        }
×
176

177
        // Once we've created the port mapping, we can automatically create the
178
        // hidden service. The service's private key will be saved on disk in order
179
        // to persistently have access to this hidden service across restarts.
180
        onionCfg := tor.AddOnionConfig{
×
181
                VirtualPort: DefaultPeerPort,
×
182
                TargetPorts: listenPorts,
×
183
                Store: tor.NewOnionFile(
×
184
                        w.cfg.WatchtowerKeyPath, 0600, w.cfg.EncryptKey,
×
185
                        encrypter,
×
186
                ),
×
187
                Type: w.cfg.Type,
×
188
        }
×
189

×
190
        addr, err := w.cfg.TorController.AddOnion(onionCfg)
×
191
        if err != nil {
×
192
                return err
×
193
        }
×
194

195
        // Append this address to ExternalIPs so that it will be exposed in
196
        // tower info calls.
197
        w.cfg.ExternalIPs = append(w.cfg.ExternalIPs, addr)
×
198

×
199
        return nil
×
200
}
201

202
// PubKey returns the public key for the watchtower used to authentication and
203
// encrypt traffic with clients.
204
//
205
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
206
func (w *Standalone) PubKey() *btcec.PublicKey {
×
207
        return w.cfg.NodeKeyECDH.PubKey()
×
208
}
×
209

210
// ListeningAddrs returns the listening addresses where the watchtower server
211
// can accept client connections.
212
//
213
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
214
func (w *Standalone) ListeningAddrs() []net.Addr {
×
215
        addrs := make([]net.Addr, 0, len(w.listeners))
×
216
        for _, listener := range w.listeners {
×
217
                addrs = append(addrs, listener.Addr())
×
218
        }
×
219

220
        return addrs
×
221
}
222

223
// ExternalIPs returns the addresses where the watchtower can be reached by
224
// clients externally.
225
//
226
// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface.
227
func (w *Standalone) ExternalIPs() []net.Addr {
×
228
        addrs := make([]net.Addr, 0, len(w.cfg.ExternalIPs))
×
229
        addrs = append(addrs, w.cfg.ExternalIPs...)
×
230

×
231
        return addrs
×
232
}
×
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