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

lightningnetwork / lnd / 13558005087

27 Feb 2025 03:04AM UTC coverage: 58.834% (-0.001%) from 58.835%
13558005087

Pull #8453

github

Roasbeef
lnwallet/chancloser: increase test coverage of state machine
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

1079 of 1370 new or added lines in 23 files covered. (78.76%)

578 existing lines in 40 files now uncovered.

137063 of 232965 relevant lines covered (58.83%)

19205.84 hits per line

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

19.63
/lnrpc/neutrinorpc/neutrino_server.go
1
//go:build neutrinorpc
2
// +build neutrinorpc
3

4
package neutrinorpc
5

6
import (
7
        "context"
8
        "errors"
9
        "fmt"
10

11
        "github.com/btcsuite/btcd/blockchain"
12
        "github.com/btcsuite/btcd/chaincfg/chainhash"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
15
        "github.com/lightningnetwork/lnd/lnrpc"
16
        "google.golang.org/grpc"
17
        "gopkg.in/macaroon-bakery.v2/bakery"
18
)
19

20
const (
21
        // subServerName is the name of the sub rpc server. We'll use this name
22
        // to register ourselves, and we also require that the main
23
        // SubServerConfigDispatcher instance recognize it as the name of our
24
        // RPC service.
25
        subServerName = "NeutrinoKitRPC"
26
)
27

28
var (
29
        // macPermissions maps RPC calls to the permissions they require.
30
        macPermissions = map[string][]bakery.Op{
31
                "/neutrinorpc.NeutrinoKit/Status": {{
32
                        Entity: "info",
33
                        Action: "read",
34
                }},
35
                "/neutrinorpc.NeutrinoKit/AddPeer": {{
36
                        Entity: "peers",
37
                        Action: "write",
38
                }},
39
                "/neutrinorpc.NeutrinoKit/DisconnectPeer": {{
40
                        Entity: "peers",
41
                        Action: "write",
42
                }},
43
                "/neutrinorpc.NeutrinoKit/IsBanned": {{
44
                        Entity: "info",
45
                        Action: "read",
46
                }},
47
                "/neutrinorpc.NeutrinoKit/GetBlock": {{
48
                        Entity: "onchain",
49
                        Action: "read",
50
                }},
51
                "/neutrinorpc.NeutrinoKit/GetBlockHeader": {{
52
                        Entity: "onchain",
53
                        Action: "read",
54
                }},
55
                "/neutrinorpc.NeutrinoKit/GetCFilter": {{
56
                        Entity: "onchain",
57
                        Action: "read",
58
                }},
59
                "/neutrinorpc.NeutrinoKit/GetBlockHash": {{
60
                        Entity: "onchain",
61
                        Action: "read",
62
                }},
63
        }
64

65
        // ErrNeutrinoNotActive is an error returned when there is no running
66
        // neutrino light client instance.
67
        ErrNeutrinoNotActive = errors.New("no active neutrino instance")
68
)
69

70
// ServerShell is a shell struct holding a reference to the actual sub-server.
71
// It is used to register the gRPC sub-server with the root server before we
72
// have the necessary dependencies to populate the actual sub-server.
73
type ServerShell struct {
74
        NeutrinoKitServer
75
}
76

77
// Server is a sub-server of the main RPC server: the neutrino RPC. This sub
78
// RPC server allows external callers to access the status of the neutrino
79
// currently active within lnd, as well as configuring it at runtime.
80
type Server struct {
81
        // Required by the grpc-gateway/v2 library for forward compatibility.
82
        // Must be after the atomically used variables to not break struct
83
        // alignment.
84
        UnimplementedNeutrinoKitServer
85

86
        cfg *Config
87
}
88

89
// A compile time check to ensure that NeutrinoKit fully implements the
90
// NeutrinoServer gRPC service.
91
var _ NeutrinoKitServer = (*Server)(nil)
92

93
// New returns a new instance of the neutrinorpc Neutrino sub-server. We also
94
// return the set of permissions for the macaroons that we may create within
95
// this method. If the macaroons we need aren't found in the filepath, then
96
// we'll create them on start up. If we're unable to locate, or create the
97
// macaroons we need, then we'll return with an error.
98
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
1✔
99
        // We don't create any new macaroons for this subserver, instead reuse
1✔
100
        // existing onchain/offchain permissions.
1✔
101
        server := &Server{
1✔
102
                cfg: cfg,
1✔
103
        }
1✔
104

1✔
105
        return server, macPermissions, nil
1✔
106
}
1✔
107

108
// Start launches any helper goroutines required for the Server to function.
109
//
110
// NOTE: This is part of the lnrpc.SubServer interface.
111
func (s *Server) Start() error {
1✔
112
        return nil
1✔
113
}
1✔
114

115
// Stop signals any active goroutines for a graceful closure.
116
//
117
// NOTE: This is part of the lnrpc.SubServer interface.
118
func (s *Server) Stop() error {
1✔
119
        return nil
1✔
120
}
1✔
121

122
// Name returns a unique string representation of the sub-server. This can be
123
// used to identify the sub-server and also de-duplicate them.
124
//
125
// NOTE: This is part of the lnrpc.SubServer interface.
126
func (s *Server) Name() string {
1✔
127
        return subServerName
1✔
128
}
1✔
129

130
// RegisterWithRootServer will be called by the root gRPC server to direct a
131
// sub RPC server to register itself with the main gRPC root server. Until this
132
// is called, each sub-server won't be able to have
133
// requests routed towards it.
134
//
135
// NOTE: This is part of the lnrpc.GrpcHandler interface.
136
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
1✔
137
        // We make sure that we register it with the main gRPC server to ensure
1✔
138
        // all our methods are routed properly.
1✔
139
        RegisterNeutrinoKitServer(grpcServer, r)
1✔
140

1✔
141
        log.Debugf("Neutrino RPC server successfully registered with root " +
1✔
142
                "gRPC server")
1✔
143

1✔
144
        return nil
1✔
145
}
1✔
146

147
// RegisterWithRestServer will be called by the root REST mux to direct a sub
148
// RPC server to register itself with the main REST mux server. Until this is
149
// called, each sub-server won't be able to have requests routed towards it.
150
//
151
// NOTE: This is part of the lnrpc.GrpcHandler interface.
152
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
153
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
1✔
154

1✔
155
        // We make sure that we register it with the main REST server to ensure
1✔
156
        // all our methods are routed properly.
1✔
157
        err := RegisterNeutrinoKitHandlerFromEndpoint(ctx, mux, dest, opts)
1✔
158
        if err != nil {
1✔
159
                log.Errorf("Could not register Neutrino REST server "+
×
160
                        "with root REST server: %v", err)
×
161
                return err
×
162
        }
×
163

164
        log.Debugf("Neutrino REST server successfully registered with " +
1✔
165
                "root REST server")
1✔
166
        return nil
1✔
167
}
168

169
// CreateSubServer populates the subserver's dependencies using the passed
170
// SubServerConfigDispatcher. This method should fully initialize the
171
// sub-server instance, making it ready for action. It returns the macaroon
172
// permissions that the sub-server wishes to pass on to the root server for all
173
// methods routed towards it.
174
//
175
// NOTE: This is part of the lnrpc.GrpcHandler interface.
176
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
177
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
1✔
178

1✔
179
        subServer, macPermissions, err := createNewSubServer(configRegistry)
1✔
180
        if err != nil {
1✔
181
                return nil, nil, err
×
182
        }
×
183

184
        r.NeutrinoKitServer = subServer
1✔
185
        return subServer, macPermissions, nil
1✔
186
}
187

188
// Status returns the current status, best block height and connected peers
189
// of the neutrino node.
190
//
191
// NOTE: Part of the NeutrinoServer interface.
192
func (s *Server) Status(ctx context.Context,
UNCOV
193
        in *StatusRequest) (*StatusResponse, error) {
×
UNCOV
194

×
UNCOV
195
        if s.cfg.NeutrinoCS == nil {
×
196
                return nil, ErrNeutrinoNotActive
×
197
        }
×
198

UNCOV
199
        bestBlock, err := s.cfg.NeutrinoCS.BestBlock()
×
UNCOV
200
        if err != nil {
×
201
                return nil, fmt.Errorf("could not get best block: %w", err)
×
202
        }
×
203

UNCOV
204
        peers := s.cfg.NeutrinoCS.Peers()
×
UNCOV
205
        var Peers = make([]string, len(peers))
×
UNCOV
206
        for i, p := range peers {
×
UNCOV
207
                Peers[i] = p.Addr()
×
UNCOV
208
        }
×
209

UNCOV
210
        return &StatusResponse{
×
UNCOV
211
                Active:      s.cfg.NeutrinoCS != nil,
×
UNCOV
212
                BlockHeight: bestBlock.Height,
×
UNCOV
213
                BlockHash:   bestBlock.Hash.String(),
×
UNCOV
214
                Synced:      s.cfg.NeutrinoCS.IsCurrent(),
×
UNCOV
215
                Peers:       Peers,
×
UNCOV
216
        }, nil
×
217
}
218

219
// AddPeer adds a new peer that has already been connected to the server.
220
//
221
// NOTE: Part of the NeutrinoKitServer interface.
222
func (s *Server) AddPeer(ctx context.Context,
UNCOV
223
        in *AddPeerRequest) (*AddPeerResponse, error) {
×
UNCOV
224

×
UNCOV
225
        if s.cfg.NeutrinoCS == nil {
×
226
                return nil, ErrNeutrinoNotActive
×
227
        }
×
228

UNCOV
229
        peer := s.cfg.NeutrinoCS.PeerByAddr(in.PeerAddrs)
×
UNCOV
230
        if peer == nil {
×
231
                return nil,
×
232
                        fmt.Errorf("could not found peer: %s", in.PeerAddrs)
×
233
        }
×
UNCOV
234
        s.cfg.NeutrinoCS.AddPeer(peer)
×
UNCOV
235

×
UNCOV
236
        return &AddPeerResponse{}, nil
×
237
}
238

239
// DisconnectPeer disconnects a peer by target address. Both outbound and
240
// inbound nodes will be searched for the target node. An error message will
241
// be returned if the peer was not found.
242
//
243
// NOTE: Part of the NeutrinoKitServer interface.
244
func (s *Server) DisconnectPeer(ctx context.Context,
245
        in *DisconnectPeerRequest) (*DisconnectPeerResponse, error) {
×
246

×
247
        if s.cfg.NeutrinoCS == nil {
×
248
                return nil, ErrNeutrinoNotActive
×
249
        }
×
250

251
        peer := s.cfg.NeutrinoCS.PeerByAddr(in.PeerAddrs)
×
252
        if peer == nil {
×
253
                return nil,
×
254
                        fmt.Errorf("could not found peer: %s", in.PeerAddrs)
×
255
        }
×
256

257
        err := s.cfg.NeutrinoCS.DisconnectNodeByAddr(peer.Addr())
×
258
        if err != nil {
×
259
                return nil, err
×
260
        }
×
261

262
        return &DisconnectPeerResponse{}, nil
×
263
}
264

265
// IsBanned returns true if the peer is banned, otherwise false.
266
//
267
// NOTE: Part of the NeutrinoKitServer interface.
268
func (s *Server) IsBanned(ctx context.Context,
269
        in *IsBannedRequest) (*IsBannedResponse, error) {
×
270

×
271
        if s.cfg.NeutrinoCS == nil {
×
272
                return nil, ErrNeutrinoNotActive
×
273
        }
×
274

275
        return &IsBannedResponse{
×
276
                Banned: s.cfg.NeutrinoCS.IsBanned(in.PeerAddrs),
×
277
        }, nil
×
278
}
279

280
// GetBlockHeader returns a block header with a particular block hash. If the
281
// block header is found in the cache, it will be returned immediately.
282
// Otherwise a block will  be requested from the network, one peer at a time,
283
// until one answers.
284
//
285
// NOTE: Part of the NeutrinoKitServer interface.
286
func (s *Server) GetBlockHeader(ctx context.Context,
287
        in *GetBlockHeaderRequest) (*GetBlockHeaderResponse, error) {
×
288

×
289
        if s.cfg.NeutrinoCS == nil {
×
290
                return nil, ErrNeutrinoNotActive
×
291
        }
×
292

293
        var hash chainhash.Hash
×
294
        if err := chainhash.Decode(&hash, in.Hash); err != nil {
×
295
                return nil, err
×
296
        }
×
297

298
        resp, err := s.getBlock(hash)
×
299
        if err != nil {
×
300
                return nil, err
×
301
        }
×
302

303
        return &GetBlockHeaderResponse{
×
304
                Hash:              resp.Hash,
×
305
                Confirmations:     resp.Confirmations,
×
306
                StrippedSize:      resp.StrippedSize,
×
307
                Size:              resp.Size,
×
308
                Weight:            resp.Weight,
×
309
                Height:            resp.Height,
×
310
                Version:           resp.Version,
×
311
                VersionHex:        resp.VersionHex,
×
312
                Merkleroot:        resp.Merkleroot,
×
313
                Time:              resp.Time,
×
314
                Nonce:             resp.Nonce,
×
315
                Bits:              resp.Bits,
×
316
                Ntx:               resp.Ntx,
×
317
                PreviousBlockHash: resp.PreviousBlockHash,
×
318
                RawHex:            resp.RawHex,
×
319
        }, nil
×
320
}
321

322
// GetBlock returns a block with a particular block hash. If the block is
323
// found in the cache, it will be returned immediately. Otherwise a block will
324
// be requested from the network, one peer at a time, until one answers.
325
//
326
// NOTE: Part of the NeutrinoKitServer interface.
327
func (s *Server) GetBlock(ctx context.Context,
328
        in *GetBlockRequest) (*GetBlockResponse, error) {
×
329

×
330
        if s.cfg.NeutrinoCS == nil {
×
331
                return nil, ErrNeutrinoNotActive
×
332
        }
×
333

334
        var hash chainhash.Hash
×
335
        if err := chainhash.Decode(&hash, in.Hash); err != nil {
×
336
                return nil, err
×
337
        }
×
338

339
        return s.getBlock(hash)
×
340
}
341

342
// GetCFilter returns a compact filter of a particular block.
343
// If found, only regular filters will be returned.
344
//
345
// NOTE: Part of the NeutrinoKitServer interface.
346
func (s *Server) GetCFilter(ctx context.Context,
UNCOV
347
        in *GetCFilterRequest) (*GetCFilterResponse, error) {
×
UNCOV
348

×
UNCOV
349
        if s.cfg.NeutrinoCS == nil {
×
350
                return nil, ErrNeutrinoNotActive
×
351
        }
×
352

UNCOV
353
        var hash chainhash.Hash
×
UNCOV
354
        if err := chainhash.Decode(&hash, in.Hash); err != nil {
×
355
                return nil, err
×
356
        }
×
357

358
        // GetCFilter returns a compact filter from the database. If it is
359
        // missing, it requests the compact filter from the network.
UNCOV
360
        filter, err := s.cfg.NeutrinoCS.GetCFilter(hash, wire.GCSFilterRegular)
×
UNCOV
361
        if err != nil {
×
362
                return nil, err
×
363
        }
×
364

UNCOV
365
        filterlBytes, err := filter.Bytes()
×
UNCOV
366
        if err != nil {
×
367
                return nil, err
×
368
        }
×
369

UNCOV
370
        return &GetCFilterResponse{Filter: filterlBytes}, nil
×
371
}
372

373
func (s *Server) getBlock(hash chainhash.Hash) (*GetBlockResponse, error) {
×
374
        block, err := s.cfg.NeutrinoCS.GetBlock(hash)
×
375
        if err != nil {
×
376
                return nil, err
×
377
        }
×
378

379
        header, _, err := s.cfg.NeutrinoCS.BlockHeaders.FetchHeader(&hash)
×
380
        if err != nil {
×
381
                return nil, err
×
382
        }
×
383

384
        blockData, err := block.Bytes()
×
385
        if err != nil {
×
386
                return nil, err
×
387
        }
×
388

389
        strippedData, err := block.BytesNoWitness()
×
390
        if err != nil {
×
391
                return nil, err
×
392
        }
×
393

394
        bestBlock, err := s.cfg.NeutrinoCS.BestBlock()
×
395
        if err != nil {
×
396
                return nil, err
×
397
        }
×
398

399
        // Convert txids to a string array.
400
        transactions := block.Transactions()
×
401
        tx := make([]string, len(transactions))
×
402
        for i := range transactions {
×
403
                tx[i] = transactions[i].Hash().String()
×
404
        }
×
405

406
        return &GetBlockResponse{
×
407
                Hash:          block.Hash().String(),
×
408
                Confirmations: int64(1 + bestBlock.Height - block.Height()),
×
409
                StrippedSize:  int64(len(strippedData)),
×
410
                Size:          int64(len(blockData)),
×
411
                Weight:        blockchain.GetBlockWeight(block),
×
412
                Height:        block.Height(),
×
413
                Version:       header.Version,
×
414
                VersionHex:    fmt.Sprintf("%0x", header.Version),
×
415
                Merkleroot:    header.MerkleRoot.String(),
×
416
                Tx:            tx,
×
417
                Time:          header.Timestamp.Unix(),
×
418
                Nonce:         header.Nonce,
×
419
                // Format bits as a hex.
×
420
                Bits:              fmt.Sprintf("%0x", header.Bits),
×
421
                Ntx:               int32(len(block.Transactions())),
×
422
                PreviousBlockHash: header.PrevBlock.String(),
×
423
                RawHex:            blockData,
×
424
        }, nil
×
425
}
426

427
// GetBlockHash returns the header hash of a block at a given height.
428
//
429
// NOTE: Part of the NeutrinoKitServer interface.
430
func (s *Server) GetBlockHash(ctx context.Context,
431
        in *GetBlockHashRequest) (*GetBlockHashResponse, error) {
×
432

×
433
        if s.cfg.NeutrinoCS == nil {
×
434
                return nil, ErrNeutrinoNotActive
×
435
        }
×
436

437
        hash, err := s.cfg.NeutrinoCS.GetBlockHash(int64(in.Height))
×
438
        if err != nil {
×
439
                return nil, err
×
440
        }
×
441

442
        return &GetBlockHashResponse{Hash: hash.String()}, nil
×
443
}
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