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

lightningnetwork / lnd / 15425411725

03 Jun 2025 06:50PM UTC coverage: 68.992%. First build
15425411725

Pull #9893

github

web-flow
Merge 4e81d8f07 into c52a6ddeb
Pull Request #9893: fix memory leak cherry pick

176 of 227 new or added lines in 21 files covered. (77.53%)

133949 of 194151 relevant lines covered (68.99%)

22065.11 hits per line

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

39.37
/lnrpc/autopilotrpc/autopilot_server.go
1
//go:build autopilotrpc
2
// +build autopilotrpc
3

4
package autopilotrpc
5

6
import (
7
        "context"
8
        "encoding/hex"
9
        "sync/atomic"
10

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

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

27
var (
28
        // macPermissions maps RPC calls to the permissions they require.
29
        macPermissions = map[string][]bakery.Op{
30
                "/autopilotrpc.Autopilot/Status": {{
31
                        Entity: "info",
32
                        Action: "read",
33
                }},
34
                "/autopilotrpc.Autopilot/ModifyStatus": {{
35
                        Entity: "onchain",
36
                        Action: "write",
37
                }, {
38
                        Entity: "offchain",
39
                        Action: "write",
40
                }},
41
                "/autopilotrpc.Autopilot/QueryScores": {{
42
                        Entity: "info",
43
                        Action: "read",
44
                }},
45
                "/autopilotrpc.Autopilot/SetScores": {{
46
                        Entity: "onchain",
47
                        Action: "write",
48
                }, {
49
                        Entity: "offchain",
50
                        Action: "write",
51
                }},
52
        }
53
)
54

55
// ServerShell is a shell struct holding a reference to the actual sub-server.
56
// It is used to register the gRPC sub-server with the root server before we
57
// have the necessary dependencies to populate the actual sub-server.
58
type ServerShell struct {
59
        AutopilotServer
60
}
61

62
// Server is a sub-server of the main RPC server: the autopilot RPC. This sub
63
// RPC server allows external callers to access the status of the autopilot
64
// currently active within lnd, as well as configuring it at runtime.
65
type Server struct {
66
        started  int32 // To be used atomically.
67
        shutdown int32 // To be used atomically.
68

69
        // Required by the grpc-gateway/v2 library for forward compatibility.
70
        // Must be after the atomically used variables to not break struct
71
        // alignment.
72
        UnimplementedAutopilotServer
73

74
        cfg *Config
75

76
        manager *autopilot.Manager
77
}
78

79
// A compile time check to ensure that Server fully implements the
80
// AutopilotServer gRPC service.
81
var _ AutopilotServer = (*Server)(nil)
82

83
// New returns a new instance of the autopilotrpc Autopilot sub-server. We also
84
// return the set of permissions for the macaroons that we may create within
85
// this method. If the macaroons we need aren't found in the filepath, then
86
// we'll create them on start up. If we're unable to locate, or create the
87
// macaroons we need, then we'll return with an error.
88
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
3✔
89
        // We don't create any new macaroons for this subserver, instead reuse
3✔
90
        // existing onchain/offchain permissions.
3✔
91
        server := &Server{
3✔
92
                cfg:     cfg,
3✔
93
                manager: cfg.Manager,
3✔
94
        }
3✔
95

3✔
96
        return server, macPermissions, nil
3✔
97
}
3✔
98

99
// Start launches any helper goroutines required for the Server to function.
100
//
101
// NOTE: This is part of the lnrpc.SubServer interface.
102
func (s *Server) Start() error {
3✔
103
        if atomic.AddInt32(&s.started, 1) != 1 {
3✔
104
                return nil
×
105
        }
×
106

107
        return s.manager.Start()
3✔
108
}
109

110
// Stop signals any active goroutines for a graceful closure.
111
//
112
// NOTE: This is part of the lnrpc.SubServer interface.
113
func (s *Server) Stop() error {
3✔
114
        if atomic.AddInt32(&s.shutdown, 1) != 1 {
3✔
115
                return nil
×
116
        }
×
117

118
        return s.manager.Stop()
3✔
119
}
120

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

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

3✔
140
        log.Debugf("Autopilot RPC server successfully registered with root " +
3✔
141
                "gRPC server")
3✔
142

3✔
143
        return nil
3✔
144
}
3✔
145

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

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

163
        log.Debugf("Autopilot REST server successfully registered with " +
3✔
164
                "root REST server")
3✔
165
        return nil
3✔
166
}
167

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

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

183
        r.AutopilotServer = subServer
3✔
184
        return subServer, macPermissions, nil
3✔
185
}
186

187
// Status returns the current status of the autopilot agent.
188
//
189
// NOTE: Part of the AutopilotServer interface.
190
func (s *Server) Status(ctx context.Context,
191
        in *StatusRequest) (*StatusResponse, error) {
3✔
192

3✔
193
        return &StatusResponse{
3✔
194
                Active: s.manager.IsActive(),
3✔
195
        }, nil
3✔
196
}
3✔
197

198
// ModifyStatus activates the current autopilot agent, if active.
199
//
200
// NOTE: Part of the AutopilotServer interface.
201
func (s *Server) ModifyStatus(ctx context.Context,
202
        in *ModifyStatusRequest) (*ModifyStatusResponse, error) {
×
203

×
204
        log.Debugf("Setting agent enabled=%v", in.Enable)
×
205

×
206
        var err error
×
207
        if in.Enable {
×
208
                err = s.manager.StartAgent()
×
209
        } else {
×
210
                err = s.manager.StopAgent()
×
211
        }
×
212
        return &ModifyStatusResponse{}, err
×
213
}
214

215
// QueryScores queries all available autopilot heuristics, in addition to any
216
// active combination of these heruristics, for the scores they would give to
217
// the given nodes.
218
//
219
// NOTE: Part of the AutopilotServer interface.
220
func (s *Server) QueryScores(ctx context.Context, in *QueryScoresRequest) (
221
        *QueryScoresResponse, error) {
×
222

×
223
        var nodes []autopilot.NodeID
×
224
        for _, pubStr := range in.Pubkeys {
×
225
                pubHex, err := hex.DecodeString(pubStr)
×
226
                if err != nil {
×
227
                        return nil, err
×
228
                }
×
229
                pubKey, err := btcec.ParsePubKey(pubHex)
×
230
                if err != nil {
×
231
                        return nil, err
×
232
                }
×
233
                nID := autopilot.NewNodeID(pubKey)
×
234
                nodes = append(nodes, nID)
×
235
        }
236

237
        // Query the heuristics.
238
        heuristicScores, err := s.manager.QueryHeuristics(
×
NEW
239
                nodes, !in.IgnoreLocalState,
×
240
        )
×
241
        if err != nil {
×
242
                return nil, err
×
243
        }
×
244

245
        resp := &QueryScoresResponse{}
×
246
        for heuristic, scores := range heuristicScores {
×
247
                result := &QueryScoresResponse_HeuristicResult{
×
248
                        Heuristic: heuristic,
×
249
                        Scores:    make(map[string]float64),
×
250
                }
×
251

×
252
                for pub, score := range scores {
×
253
                        pubkeyHex := hex.EncodeToString(pub[:])
×
254
                        result.Scores[pubkeyHex] = score
×
255
                }
×
256

257
                // Since a node not being part of the internally returned
258
                // scores imply a zero score, we add these before we return the
259
                // RPC results.
260
                for _, node := range nodes {
×
261
                        if _, ok := scores[node]; ok {
×
262
                                continue
×
263
                        }
264
                        pubkeyHex := hex.EncodeToString(node[:])
×
265
                        result.Scores[pubkeyHex] = 0.0
×
266
                }
267

268
                resp.Results = append(resp.Results, result)
×
269
        }
270

271
        return resp, nil
×
272
}
273

274
// SetScores sets the scores of the external score heuristic, if active.
275
//
276
// NOTE: Part of the AutopilotServer interface.
277
func (s *Server) SetScores(ctx context.Context,
278
        in *SetScoresRequest) (*SetScoresResponse, error) {
×
279

×
280
        scores := make(map[autopilot.NodeID]float64)
×
281
        for pubStr, score := range in.Scores {
×
282
                pubHex, err := hex.DecodeString(pubStr)
×
283
                if err != nil {
×
284
                        return nil, err
×
285
                }
×
286
                pubKey, err := btcec.ParsePubKey(pubHex)
×
287
                if err != nil {
×
288
                        return nil, err
×
289
                }
×
290
                nID := autopilot.NewNodeID(pubKey)
×
291
                scores[nID] = score
×
292
        }
293

294
        if err := s.manager.SetNodeScores(in.Heuristic, scores); err != nil {
×
295
                return nil, err
×
296
        }
×
297

298
        return &SetScoresResponse{}, nil
×
299
}
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