• 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
/lnrpc/devrpc/dev_server.go
1
//go:build dev
2
// +build dev
3

4
package devrpc
5

6
import (
7
        "context"
8
        "encoding/hex"
9
        "fmt"
10
        "strconv"
11
        "strings"
12
        "sync/atomic"
13
        "time"
14

15
        "github.com/btcsuite/btcd/btcutil"
16
        "github.com/btcsuite/btcd/chaincfg/chainhash"
17
        "github.com/btcsuite/btcd/wire"
18
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
19
        "github.com/lightningnetwork/lnd/fn/v2"
20
        "github.com/lightningnetwork/lnd/graph/db/models"
21
        "github.com/lightningnetwork/lnd/lncfg"
22
        "github.com/lightningnetwork/lnd/lnrpc"
23
        "github.com/lightningnetwork/lnd/lntypes"
24
        "github.com/lightningnetwork/lnd/lnwire"
25
        "google.golang.org/grpc"
26
        "gopkg.in/macaroon-bakery.v2/bakery"
27
)
28

29
const (
30
        // subServerName is the name of the sub rpc server. We'll use this name
31
        // to register ourselves, and we also require that the main
32
        // SubServerConfigDispatcher instance recognize tt as the name of our
33
        // RPC service.
34
        subServerName = "DevRPC"
35
)
36

37
var (
38
        // macPermissions maps RPC calls to the permissions they require.
39
        macPermissions = map[string][]bakery.Op{
40
                "/devrpc.Dev/ImportGraph": {{
41
                        Entity: "offchain",
42
                        Action: "write",
43
                }},
44
                "/devrpc.Dev/Quiesce": {{
45
                        Entity: "offchain",
46
                        Action: "write",
47
                }},
48
        }
49
)
50

51
// ServerShell is a shell struct holding a reference to the actual sub-server.
52
// It is used to register the gRPC sub-server with the root server before we
53
// have the necessary dependencies to populate the actual sub-server.
54
type ServerShell struct {
55
        DevServer
56
}
57

58
// Server is a sub-server of the main RPC server: the dev RPC. This sub
59
// RPC server allows developers to set and query LND state that is not possible
60
// during normal operation.
61
type Server struct {
62
        started  int32 // To be used atomically.
63
        shutdown int32 // To be used atomically.
64
        quit     chan struct{}
65

66
        // Required by the grpc-gateway/v2 library for forward compatibility.
67
        // Must be after the atomically used variables to not break struct
68
        // alignment.
69
        UnimplementedDevServer
70

71
        cfg *Config
72
}
73

74
// A compile time check to ensure that Server fully implements the
75
// DevServer gRPC service.
76
var _ DevServer = (*Server)(nil)
77

78
// New returns a new instance of the devrpc Dev sub-server. We also return the
79
// set of permissions for the macaroons that we may create within this method.
80
// If the macaroons we need aren't found in the filepath, then we'll create them
81
// on start up. If we're unable to locate, or create the macaroons we need, then
82
// we'll return with an error.
83
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
×
84
        // We don't create any new macaroons for this subserver, instead reuse
×
85
        // existing onchain/offchain permissions.
×
86
        server := &Server{
×
87
                quit: make(chan struct{}),
×
88
                cfg:  cfg,
×
89
        }
×
90

×
91
        return server, macPermissions, nil
×
92
}
×
93

94
// Start launches any helper goroutines required for the Server to function.
95
//
96
// NOTE: This is part of the lnrpc.SubServer interface.
97
func (s *Server) Start() error {
×
98
        if atomic.AddInt32(&s.started, 1) != 1 {
×
99
                return nil
×
100
        }
×
101

102
        return nil
×
103
}
104

105
// Stop signals any active goroutines for a graceful closure.
106
//
107
// NOTE: This is part of the lnrpc.SubServer interface.
108
func (s *Server) Stop() error {
×
109
        if atomic.AddInt32(&s.shutdown, 1) != 1 {
×
110
                return nil
×
111
        }
×
112

113
        close(s.quit)
×
114

×
115
        return nil
×
116
}
117

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

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

×
137
        log.Debugf("DEV RPC server successfully registered with root the " +
×
138
                "gRPC server")
×
139

×
140
        return nil
×
141
}
×
142

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

×
151
        // We make sure that we register it with the main REST server to ensure
×
152
        // all our methods are routed properly.
×
153
        err := RegisterDevHandlerFromEndpoint(ctx, mux, dest, opts)
×
154
        if err != nil {
×
155
                log.Errorf("Could not register DEV REST server with the root "+
×
156
                        "REST server: %v", err)
×
157
                return err
×
158
        }
×
159

160
        log.Debugf("DEV REST server successfully registered with the root " +
×
161
                "REST server")
×
162
        return nil
×
163
}
164

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

×
175
        subServer, macPermissions, err := createNewSubServer(configRegistry)
×
176
        if err != nil {
×
177
                return nil, nil, err
×
178
        }
×
179

180
        r.DevServer = subServer
×
181
        return subServer, macPermissions, nil
×
182
}
183

184
func parseOutPoint(s string) (*wire.OutPoint, error) {
×
185
        split := strings.Split(s, ":")
×
186
        if len(split) != 2 {
×
187
                return nil, fmt.Errorf("expecting outpoint to be in format of: " +
×
188
                        "txid:index")
×
189
        }
×
190

191
        index, err := strconv.ParseInt(split[1], 10, 32)
×
192
        if err != nil {
×
193
                return nil, fmt.Errorf("unable to decode output index: %w", err)
×
194
        }
×
195

196
        txid, err := chainhash.NewHashFromStr(split[0])
×
197
        if err != nil {
×
198
                return nil, fmt.Errorf("unable to parse hex string: %w", err)
×
199
        }
×
200

201
        return &wire.OutPoint{
×
202
                Hash:  *txid,
×
203
                Index: uint32(index),
×
204
        }, nil
×
205
}
206

207
func parsePubKey(pubKeyStr string) ([33]byte, error) {
×
208
        var pubKey [33]byte
×
209
        pubKeyBytes, err := hex.DecodeString(pubKeyStr)
×
210
        if err != nil || len(pubKeyBytes) != 33 {
×
211
                return pubKey, fmt.Errorf("invalid pubkey: %v", pubKeyStr)
×
212
        }
×
213

214
        copy(pubKey[:], pubKeyBytes)
×
215
        return pubKey, nil
×
216
}
217

218
// ImportGraph imports a graph dump (without auth proofs).
219
//
220
// NOTE: Part of the DevServer interface.
221
func (s *Server) ImportGraph(ctx context.Context,
222
        graph *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
×
223

×
224
        // Obtain the pointer to the global singleton channel graph.
×
225
        graphDB := s.cfg.GraphDB
×
226

×
227
        var err error
×
228
        for _, rpcNode := range graph.Nodes {
×
229
                node := &models.LightningNode{
×
230
                        HaveNodeAnnouncement: true,
×
231
                        LastUpdate: time.Unix(
×
232
                                int64(rpcNode.LastUpdate), 0,
×
233
                        ),
×
234
                        Alias: rpcNode.Alias,
×
235
                }
×
236

×
237
                node.PubKeyBytes, err = parsePubKey(rpcNode.PubKey)
×
238
                if err != nil {
×
239
                        return nil, err
×
240
                }
×
241

242
                featureBits := make([]lnwire.FeatureBit, 0, len(rpcNode.Features))
×
243
                featureNames := make(map[lnwire.FeatureBit]string)
×
244

×
245
                for featureBit, feature := range rpcNode.Features {
×
246
                        featureBits = append(
×
247
                                featureBits, lnwire.FeatureBit(featureBit),
×
248
                        )
×
249

×
250
                        featureNames[lnwire.FeatureBit(featureBit)] = feature.Name
×
251
                }
×
252

253
                featureVector := lnwire.NewRawFeatureVector(featureBits...)
×
254
                node.Features = lnwire.NewFeatureVector(
×
255
                        featureVector, featureNames,
×
256
                )
×
257

×
258
                node.Color, err = lncfg.ParseHexColor(rpcNode.Color)
×
259
                if err != nil {
×
260
                        return nil, err
×
261
                }
×
262

263
                if err := graphDB.AddLightningNode(node); err != nil {
×
264
                        return nil, fmt.Errorf("unable to add node %v: %w",
×
265
                                rpcNode.PubKey, err)
×
266
                }
×
267

268
                log.Debugf("Imported node: %v", rpcNode.PubKey)
×
269
        }
270

271
        for _, rpcEdge := range graph.Edges {
×
272
                rpcEdge := rpcEdge
×
273

×
274
                edge := &models.ChannelEdgeInfo{
×
275
                        ChannelID: rpcEdge.ChannelId,
×
276
                        ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
×
277
                        Capacity:  btcutil.Amount(rpcEdge.Capacity),
×
278
                }
×
279

×
280
                edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub)
×
281
                if err != nil {
×
282
                        return nil, err
×
283
                }
×
284

285
                edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub)
×
286
                if err != nil {
×
287
                        return nil, err
×
288
                }
×
289

290
                channelPoint, err := parseOutPoint(rpcEdge.ChanPoint)
×
291
                if err != nil {
×
292
                        return nil, err
×
293
                }
×
294
                edge.ChannelPoint = *channelPoint
×
295

×
296
                if err := graphDB.AddChannelEdge(edge); err != nil {
×
297
                        return nil, fmt.Errorf("unable to add edge %v: %w",
×
298
                                rpcEdge.ChanPoint, err)
×
299
                }
×
300

301
                makePolicy := func(rpcPolicy *lnrpc.RoutingPolicy) *models.ChannelEdgePolicy { //nolint:ll
×
302
                        policy := &models.ChannelEdgePolicy{
×
303
                                ChannelID: rpcEdge.ChannelId,
×
304
                                LastUpdate: time.Unix(
×
305
                                        int64(rpcPolicy.LastUpdate), 0,
×
306
                                ),
×
307
                                TimeLockDelta: uint16(
×
308
                                        rpcPolicy.TimeLockDelta,
×
309
                                ),
×
310
                                MinHTLC: lnwire.MilliSatoshi(
×
311
                                        rpcPolicy.MinHtlc,
×
312
                                ),
×
313
                                FeeBaseMSat: lnwire.MilliSatoshi(
×
314
                                        rpcPolicy.FeeBaseMsat,
×
315
                                ),
×
316
                                FeeProportionalMillionths: lnwire.MilliSatoshi(
×
317
                                        rpcPolicy.FeeRateMilliMsat,
×
318
                                ),
×
319
                        }
×
320
                        if rpcPolicy.MaxHtlcMsat > 0 {
×
321
                                policy.MaxHTLC = lnwire.MilliSatoshi(
×
322
                                        rpcPolicy.MaxHtlcMsat,
×
323
                                )
×
324
                                policy.MessageFlags |=
×
325
                                        lnwire.ChanUpdateRequiredMaxHtlc
×
326
                        }
×
327

328
                        return policy
×
329
                }
330

331
                if rpcEdge.Node1Policy != nil {
×
332
                        policy := makePolicy(rpcEdge.Node1Policy)
×
333
                        policy.ChannelFlags = 0
×
334
                        if err := graphDB.UpdateEdgePolicy(policy); err != nil {
×
335
                                return nil, fmt.Errorf(
×
336
                                        "unable to update policy: %v", err)
×
337
                        }
×
338
                }
339

340
                if rpcEdge.Node2Policy != nil {
×
341
                        policy := makePolicy(rpcEdge.Node2Policy)
×
342
                        policy.ChannelFlags = 1
×
343
                        if err := graphDB.UpdateEdgePolicy(policy); err != nil {
×
344
                                return nil, fmt.Errorf(
×
345
                                        "unable to update policy: %v", err)
×
346
                        }
×
347
                }
348

349
                log.Debugf("Added edge: %v", rpcEdge.ChannelId)
×
350
        }
351

352
        return &ImportGraphResponse{}, nil
×
353
}
354

355
// Quiesce initiates the quiescence process for the channel with the given
356
// channel ID. This method will block until the channel is fully quiesced.
357
func (s *Server) Quiesce(_ context.Context, in *QuiescenceRequest) (
358
        *QuiescenceResponse, error) {
×
359

×
360
        txid, err := lnrpc.GetChanPointFundingTxid(in.ChanId)
×
361
        if err != nil {
×
362
                return nil, err
×
363
        }
×
364

365
        op := wire.NewOutPoint(txid, in.ChanId.OutputIndex)
×
366
        cid := lnwire.NewChanIDFromOutPoint(*op)
×
367
        ln, err := s.cfg.Switch.GetLink(cid)
×
368
        if err != nil {
×
369
                return nil, err
×
370
        }
×
371

372
        select {
×
373
        case result := <-ln.InitStfu():
×
374
                mkResp := func(b lntypes.ChannelParty) *QuiescenceResponse {
×
375
                        return &QuiescenceResponse{Initiator: b.IsLocal()}
×
376
                }
×
377

378
                return fn.MapOk(mkResp)(result).Unpack()
×
379

380
        case <-s.quit:
×
381
                return nil, fmt.Errorf("server shutting down")
×
382
        }
383
}
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