liquidity: persist manager's params to disk

This commit decodes the manager's parameters from a new config file
using gob during startup and encodes it and saves it to the config file
during shutdown, persisting the params when loopd restarts.
pull/490/head
yyforyongyu 2 years ago
parent 5205622eb2
commit 21af85c8e3
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868

@ -34,8 +34,11 @@ package liquidity
import ( import (
"context" "context"
"encoding/gob"
"errors" "errors"
"fmt" "fmt"
"io"
"os"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -438,6 +441,11 @@ func (m *Manager) Run(ctx context.Context) error {
m.cfg.AutoloopTicker.Resume() m.cfg.AutoloopTicker.Resume()
defer m.cfg.AutoloopTicker.Stop() defer m.cfg.AutoloopTicker.Stop()
// Before we start, load the parameters.
if err := m.LoadParameters(); err != nil {
log.Errorf("load parameters failed: %v", err)
}
for { for {
select { select {
case <-m.cfg.AutoloopTicker.Ticks(): case <-m.cfg.AutoloopTicker.Ticks():
@ -453,6 +461,11 @@ func (m *Manager) Run(ctx context.Context) error {
} }
case <-ctx.Done(): case <-ctx.Done():
// Before we quit, save the parameters.
if err := m.SaveParameters(); err != nil {
log.Errorf("save parameters failed: %v", err)
}
return ctx.Err() return ctx.Err()
} }
} }
@ -499,6 +512,45 @@ func (m *Manager) SetParameters(ctx context.Context, params Parameters) error {
return nil return nil
} }
// LoadParameters loads the parameters saved in the file specified by the
// config.
func (m *Manager) LoadParameters() error {
m.paramsLock.Lock()
defer m.paramsLock.Unlock()
params, err := decodeParams(m.cfg.LiquidityParamsPath)
// We will get an EOF if it's first time loading the params from the
// file. In this case, we will do nothing and let the manager use the
// default params.
if errors.Is(err, io.EOF) {
return nil
}
// Otherwise return the error as it's unexpected.
if err != nil {
return fmt.Errorf("failed to load params: %w", err)
}
// Attach the saved params.
m.params = cloneParameters(params)
return nil
}
// SaveParameters saves the manager's parameters to the file specified by the
// config.
func (m *Manager) SaveParameters() error {
m.paramsLock.Lock()
defer m.paramsLock.Unlock()
err := encodeParams(m.cfg.LiquidityParamsPath, m.params)
if err != nil {
return fmt.Errorf("failed to save params: %w", err)
}
return nil
}
// cloneParameters creates a deep clone of a parameters struct so that callers // cloneParameters creates a deep clone of a parameters struct so that callers
// cannot mutate our parameters. Although our parameters struct itself is not // cannot mutate our parameters. Although our parameters struct itself is not
// a reference, we still need to clone the contents of maps. // a reference, we still need to clone the contents of maps.
@ -527,6 +579,42 @@ func cloneParameters(params Parameters) Parameters {
return paramCopy return paramCopy
} }
// encodeParams encodes the given parameters and saves it to a file.
func encodeParams(filepath string, p Parameters) error {
// Create the file if not exists, otherwise open it.
file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return fmt.Errorf("cannot create params file: %w", err)
}
defer file.Close()
encoder := gob.NewEncoder(file)
if err := encoder.Encode(p); err != nil {
return fmt.Errorf("encoding params error: %w", err)
}
return nil
}
// decodeParams reads the file and decodes a Parameters struct.
func decodeParams(filepath string) (Parameters, error) {
var p Parameters
// Create the file or truncate the old file.
file, err := os.Create(filepath)
if err != nil {
return p, fmt.Errorf("cannot create params file: %w", err)
}
defer file.Close()
decoder := gob.NewDecoder(file)
if err := decoder.Decode(&p); err != nil {
return p, fmt.Errorf("decoding params error: %w", err)
}
return p, nil
}
// autoloop gets a set of suggested swaps and dispatches them automatically if // autoloop gets a set of suggested swaps and dispatches them automatically if
// we have automated looping enabled. // we have automated looping enabled.
func (m *Manager) autoloop(ctx context.Context) error { func (m *Manager) autoloop(ctx context.Context) error {
@ -1212,3 +1300,13 @@ func ppmToSat(amount btcutil.Amount, ppm uint64) btcutil.Amount {
func mSatToSatoshis(amount lnwire.MilliSatoshi) btcutil.Amount { func mSatToSatoshis(amount lnwire.MilliSatoshi) btcutil.Amount {
return btcutil.Amount(amount / 1000) return btcutil.Amount(amount / 1000)
} }
func init() {
// Init custom structs for gob.
//
// NOTE: we need to use pointers here to make sure the interface check
// passes.
gob.Register(&SwapRule{})
gob.Register(&FeePortion{})
gob.Register(&FeeCategoryLimit{})
}

@ -2,6 +2,7 @@ package liquidity
import ( import (
"context" "context"
"os"
"testing" "testing"
"time" "time"
@ -246,6 +247,63 @@ func TestParameters(t *testing.T) {
require.Equal(t, ErrZeroChannelID, err) require.Equal(t, ErrZeroChannelID, err)
} }
// TestPersistParameters tests loading and saving from a file.
func TestPersistParameters(t *testing.T) {
// Overwrite the filepath.
paramsFile := "liquidity_params.gob.test"
// Remove the test file when test finishes.
defer func() {
require.NoError(t, os.Remove(paramsFile))
}()
cfg, _ := newTestConfig()
cfg.LiquidityParamsPath = paramsFile
manager := NewManager(cfg)
// Load params for the first time, which should end up not touching the
// manager's params.
err := manager.LoadParameters()
require.NoError(t, err)
// Check the params are not changed.
startParams := manager.GetParameters()
require.Equal(t, defaultParameters, startParams)
// Save params should give us no error.
err = manager.SaveParameters()
require.NoError(t, err)
// We now update the manager's parameters.
chanID := lnwire.NewShortChanIDFromInt(1)
originalRule := &SwapRule{
ThresholdRule: NewThresholdRule(10, 10),
Type: swap.TypeOut,
}
// Create a new parameters struct.
expected := defaultParameters
expected.ChannelRules = map[lnwire.ShortChannelID]*SwapRule{
chanID: originalRule,
}
// Set the params.
err = manager.SetParameters(context.Background(), expected)
require.NoError(t, err)
// Now save the updated params.
err = manager.SaveParameters()
require.NoError(t, err)
// Load the params again which updates the manager's params.
err = manager.LoadParameters()
require.NoError(t, err)
// Validate that the manager has the updated params.
params := manager.GetParameters()
require.Equal(t, expected, params)
}
// TestValidateRestrictions tests validating client restrictions against a set // TestValidateRestrictions tests validating client restrictions against a set
// of server restrictions. // of server restrictions.
func TestValidateRestrictions(t *testing.T) { func TestValidateRestrictions(t *testing.T) {

@ -26,7 +26,7 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr
const ( const (
// Note: please update release_notes.md when you change these values. // Note: please update release_notes.md when you change these values.
appMajor uint = 0 appMajor uint = 0
appMinor uint = 18 appMinor uint = 19
appPatch uint = 0 appPatch uint = 0
// appPreRelease MUST only contain characters from semanticAlphabet per // appPreRelease MUST only contain characters from semanticAlphabet per

Loading…
Cancel
Save