Skip to content

Commit 425a007

Browse files
committed
loopd+liquidity: persist parameters on disk
This commit saves the RPC request used to construct the `Parameters` on disk. Since it's a proto message, an easy way to read/write it is to rely on the proto marshal/unmarshal methods. A side effect is that migration also becomes easy as proto message have its own internal mechanism to keep track of the compatibility.
1 parent 26edd21 commit 425a007

File tree

5 files changed

+145
-4
lines changed

5 files changed

+145
-4
lines changed

cmd/loop/liquidity.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,12 @@ func setRule(ctx *cli.Context) error {
223223
}
224224

225225
var setParamsCommand = cli.Command{
226-
Name: "setparams",
227-
Usage: "update the parameters set for the liquidity manager",
228-
Description: "Updates the parameters set for the liquidity manager.",
226+
Name: "setparams",
227+
Usage: "update the parameters set for the liquidity manager",
228+
Description: "Updates the parameters set for the liquidity manager. " +
229+
"Note the parameters are persisted in db to save the trouble " +
230+
"of setting them again upon loopd restart. To get the " +
231+
"default values, use `getparams` before any `setparams`.",
229232
Flags: []cli.Flag{
230233
cli.IntFlag{
231234
Name: "sweeplimit",

liquidity/autoloop_testcontext_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ func newAutoloopTestCtx(t *testing.T, parameters Parameters,
166166
MinimumConfirmations: loop.DefaultSweepConfTarget,
167167
Lnd: &testCtx.lnd.LndServices,
168168
Clock: testCtx.testClock,
169+
PutLiquidityParams: func(_ []byte) error {
170+
return nil
171+
},
172+
FetchLiquidityParams: func() ([]byte, error) {
173+
return nil, nil
174+
},
169175
}
170176

171177
// SetParameters needs to make a call to our mocked restrictions call,

liquidity/liquidity.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
"github.com/lightningnetwork/lnd/lnwire"
5353
"github.com/lightningnetwork/lnd/routing/route"
5454
"github.com/lightningnetwork/lnd/ticker"
55+
"google.golang.org/protobuf/proto"
5556

5657
clientrpc "github.com/lightninglabs/loop/looprpc"
5758
)
@@ -181,6 +182,18 @@ type Config struct {
181182
// MinimumConfirmations is the minimum number of confirmations we allow
182183
// setting for sweep target.
183184
MinimumConfirmations int32
185+
186+
// PutLiquidityParams writes the serialized `Parameters` into db.
187+
//
188+
// NOTE: the params are encoded using `proto.Marshal` over an RPC
189+
// request.
190+
PutLiquidityParams func(params []byte) error
191+
192+
// FetchLiquidityParams reads the serialized `Parameters` from db.
193+
//
194+
// NOTE: the params are decoded using `proto.Unmarshal` over a
195+
// serialized RPC request.
196+
FetchLiquidityParams func() ([]byte, error)
184197
}
185198

186199
// Manager contains a set of desired liquidity rules for our channel
@@ -205,6 +218,19 @@ func (m *Manager) Run(ctx context.Context) error {
205218
m.cfg.AutoloopTicker.Resume()
206219
defer m.cfg.AutoloopTicker.Stop()
207220

221+
// Before we start the main loop, load the params from db.
222+
req, err := m.loadParams()
223+
if err != nil {
224+
return err
225+
}
226+
227+
// Set the params if there's one.
228+
if req != nil {
229+
if err := m.SetParameters(ctx, req); err != nil {
230+
return err
231+
}
232+
}
233+
208234
for {
209235
select {
210236
case <-m.cfg.AutoloopTicker.Ticks():
@@ -251,7 +277,18 @@ func (m *Manager) SetParameters(ctx context.Context,
251277
return err
252278
}
253279

254-
return m.setParameters(ctx, *params)
280+
if err := m.setParameters(ctx, *params); err != nil {
281+
return err
282+
}
283+
284+
// Save the params on disk.
285+
//
286+
// NOTE: alternatively we can save the bytes in memory and persist them
287+
// on disk during shutdown to save us some IO cost from hitting the db.
288+
// Since setting params is NOT a frequent action, it's should put
289+
// little pressure on our db. Only when performance becomes an issue,
290+
// we can then apply the alternative.
291+
return m.saveParams(req)
255292
}
256293

257294
// SetParameters updates our current set of parameters if the new parameters
@@ -280,9 +317,49 @@ func (m *Manager) setParameters(ctx context.Context,
280317
defer m.paramsLock.Unlock()
281318

282319
m.params = cloneParameters(params)
320+
321+
return nil
322+
}
323+
324+
// saveParams marshals an RPC request and saves it to db.
325+
func (m *Manager) saveParams(req proto.Message) error {
326+
// Marshal the params.
327+
paramsBytes, err := proto.Marshal(req)
328+
if err != nil {
329+
return err
330+
}
331+
332+
// Save the params on disk.
333+
if err := m.cfg.PutLiquidityParams(paramsBytes); err != nil {
334+
return fmt.Errorf("failed to save params: %v", err)
335+
}
336+
283337
return nil
284338
}
285339

340+
// loadParams unmarshals a serialized RPC request from db and returns the RPC
341+
// request.
342+
func (m *Manager) loadParams() (*clientrpc.LiquidityParameters, error) {
343+
paramsBytes, err := m.cfg.FetchLiquidityParams()
344+
if err != nil {
345+
return nil, fmt.Errorf("failed to read params: %v", err)
346+
}
347+
348+
// Return early if there's nothing saved.
349+
if paramsBytes == nil {
350+
return nil, nil
351+
}
352+
353+
// Unmarshal the params.
354+
req := &clientrpc.LiquidityParameters{}
355+
err = proto.Unmarshal(paramsBytes, req)
356+
if err != nil {
357+
return nil, fmt.Errorf("failed to unmarshal params: %v", err)
358+
}
359+
360+
return req, nil
361+
}
362+
286363
// autoloop gets a set of suggested swaps and dispatches them automatically if
287364
// we have automated looping enabled.
288365
func (m *Manager) autoloop(ctx context.Context) error {

liquidity/liquidity_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/lightninglabs/loop"
1111
"github.com/lightninglabs/loop/labels"
1212
"github.com/lightninglabs/loop/loopdb"
13+
clientrpc "github.com/lightninglabs/loop/looprpc"
1314
"github.com/lightninglabs/loop/swap"
1415
"github.com/lightninglabs/loop/test"
1516
"github.com/lightningnetwork/lnd/clock"
@@ -246,6 +247,58 @@ func TestParameters(t *testing.T) {
246247
require.Equal(t, ErrZeroChannelID, err)
247248
}
248249

250+
// TestPersistParams tests reading and writing of parameters for our manager.
251+
func TestPersistParams(t *testing.T) {
252+
rpcParams := &clientrpc.LiquidityParameters{
253+
FeePpm: 100,
254+
AutoMaxInFlight: 10,
255+
HtlcConfTarget: 2,
256+
}
257+
cfg, _ := newTestConfig()
258+
manager := NewManager(cfg)
259+
260+
var paramsBytes []byte
261+
262+
// Mock the read method to return empty data.
263+
manager.cfg.FetchLiquidityParams = func() ([]byte, error) {
264+
return paramsBytes, nil
265+
}
266+
267+
// Test the nil params is returned.
268+
req, err := manager.loadParams()
269+
require.Nil(t, req)
270+
require.NoError(t, err)
271+
272+
// Mock the write method to return no error.
273+
manager.cfg.PutLiquidityParams = func(data []byte) error {
274+
paramsBytes = data
275+
return nil
276+
}
277+
278+
// Test save the message.
279+
err = manager.saveParams(rpcParams)
280+
require.NoError(t, err)
281+
282+
// Test the nil params is returned.
283+
req, err = manager.loadParams()
284+
require.NoError(t, err)
285+
286+
// Check the specified fields are set as expected.
287+
require.Equal(t, rpcParams.FeePpm, req.FeePpm)
288+
require.Equal(t, rpcParams.AutoMaxInFlight, req.AutoMaxInFlight)
289+
require.Equal(t, rpcParams.HtlcConfTarget, req.HtlcConfTarget)
290+
291+
// Check the unspecified fields are using empty values.
292+
require.False(t, req.Autoloop)
293+
require.Empty(t, req.Rules)
294+
require.Zero(t, req.AutoloopBudgetSat)
295+
296+
// Finally, check the loaded request can be used to set params without
297+
// error.
298+
err = manager.SetParameters(context.Background(), req)
299+
require.NoError(t, err)
300+
}
301+
249302
// TestRestrictedSuggestions tests getting of swap suggestions when we have
250303
// other in-flight swaps. We setup our manager with a set of channels and rules
251304
// that require a loop out swap, focusing on the filtering our of channels that

loopd/utils.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager {
7272
ListLoopOut: client.Store.FetchLoopOutSwaps,
7373
ListLoopIn: client.Store.FetchLoopInSwaps,
7474
MinimumConfirmations: minConfTarget,
75+
PutLiquidityParams: client.Store.PutLiquidityParams,
76+
FetchLiquidityParams: client.Store.FetchLiquidityParams,
7577
}
7678

7779
return liquidity.NewManager(mngrCfg)

0 commit comments

Comments
 (0)