Skip to content

Commit c5a63c1

Browse files
committed
liquidity: integrate scriptable autoloop mode
Add scriptable autoloop as a new mode alongside easy autoloop and threshold rules. This allows users to provide custom Starlark scripts that have full control over swap decisions. Key changes: - scriptable.go: ScriptableManager that evaluates user scripts on each tick, builds context from current channel/peer state, and converts script decisions to swap suggestions - parameters.go: Add ScriptableAutoloop, ScriptableScript, and ScriptableTickInterval parameters with validation to ensure scriptable mode is mutually exclusive with easy autoloop and rules - liquidity.go: Wire up scriptable manager to the autoloop system - script_equivalence_test.go: Tests verifying Starlark scripts can replicate easy-autoloop behavior plus advanced scenarios The scriptable mode gives operators fine-grained control with readable Python-like syntax: def autoloop(): eligible = [c for c in channels if c.active] eligible = sorted(eligible, key=lambda c: -c.local_balance) if eligible and total_local > 100000: return [loop_out(50000, [eligible[0].channel_id])] return [] decisions = autoloop()
1 parent 317bea4 commit c5a63c1

4 files changed

Lines changed: 1025 additions & 7 deletions

File tree

liquidity/liquidity.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,15 @@ func (m *Manager) Run(ctx context.Context) error {
293293
for {
294294
select {
295295
case <-m.cfg.AutoloopTicker.Ticks():
296-
if m.params.EasyAutoloop {
296+
// Check which autoloop mode to use.
297+
// Priority: scriptable > easy > threshold rules.
298+
if m.params.ScriptableAutoloop {
299+
err := m.scriptableAutoLoop(ctx)
300+
if err != nil {
301+
log.Errorf("scriptable autoloop "+
302+
"failed: %v", err)
303+
}
304+
} else if m.params.EasyAutoloop {
297305
err := m.easyAutoLoop(ctx)
298306
if err != nil {
299307
log.Errorf("easy autoloop failed: %v",

liquidity/parameters.go

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ type Parameters struct {
128128
// swaps. If set to true, the deadline is set to immediate publication.
129129
// If set to false, the deadline is set to 30 minutes.
130130
FastSwapPublication bool
131+
132+
// ScriptableAutoloop enables CEL-based scriptable autoloop mode.
133+
// This mode is mutually exclusive with EasyAutoloop and threshold rules.
134+
ScriptableAutoloop bool
135+
136+
// ScriptableScript is the CEL expression to evaluate on each tick.
137+
// Required when ScriptableAutoloop is true.
138+
ScriptableScript string
139+
140+
// ScriptableTickInterval overrides the default tick interval for
141+
// scriptable mode. Zero means use DefaultAutoloopTicker.
142+
ScriptableTickInterval time.Duration
131143
}
132144

133145
// AssetParams define the asset specific autoloop parameters.
@@ -195,6 +207,27 @@ func (p Parameters) haveRules() bool {
195207
func (p Parameters) validate(minConfs int32, openChans []lndclient.ChannelInfo,
196208
server *Restrictions) error {
197209

210+
// Check scriptable autoloop constraints first.
211+
if p.ScriptableAutoloop {
212+
// Scriptable autoloop is mutually exclusive with easy autoloop.
213+
if p.EasyAutoloop {
214+
return errors.New("scriptable_autoloop and easy_autoloop " +
215+
"are mutually exclusive")
216+
}
217+
218+
// Scriptable autoloop is mutually exclusive with rules.
219+
if len(p.ChannelRules) > 0 || len(p.PeerRules) > 0 {
220+
return errors.New("scriptable_autoloop cannot be used " +
221+
"with channel/peer rules")
222+
}
223+
224+
// Script is required when scriptable autoloop is enabled.
225+
if p.ScriptableScript == "" {
226+
return errors.New("scriptable_script is required when " +
227+
"scriptable_autoloop is enabled")
228+
}
229+
}
230+
198231
// First, we check that the rules on a per peer and per channel do not
199232
// overlap, since this could lead to contractions.
200233
for _, channel := range openChans {
@@ -475,8 +508,11 @@ func RpcToParameters(req *clientrpc.LiquidityParameters) (*Parameters,
475508
EasyAutoloopTarget: btcutil.Amount(
476509
req.EasyAutoloopLocalTargetSat,
477510
),
478-
AssetAutoloopParams: easyAssetParams,
479-
FastSwapPublication: req.FastSwapPublication,
511+
AssetAutoloopParams: easyAssetParams,
512+
FastSwapPublication: req.FastSwapPublication,
513+
ScriptableAutoloop: req.ScriptableAutoloop,
514+
ScriptableScript: req.ScriptableScript,
515+
ScriptableTickInterval: time.Duration(req.ScriptableTickIntervalSec) * time.Second,
480516
}
481517

482518
if req.AutoloopBudgetRefreshPeriodSec != 0 {
@@ -617,10 +653,13 @@ func ParametersToRpc(cfg Parameters) (*clientrpc.LiquidityParameters,
617653
HtlcConfTarget: cfg.HtlcConfTarget,
618654
EasyAutoloop: cfg.EasyAutoloop,
619655
EasyAutoloopLocalTargetSat: uint64(cfg.EasyAutoloopTarget),
620-
Account: cfg.Account,
621-
AccountAddrType: addrType,
622-
EasyAssetParams: easyAssetMap,
623-
FastSwapPublication: cfg.FastSwapPublication,
656+
Account: cfg.Account,
657+
AccountAddrType: addrType,
658+
EasyAssetParams: easyAssetMap,
659+
FastSwapPublication: cfg.FastSwapPublication,
660+
ScriptableAutoloop: cfg.ScriptableAutoloop,
661+
ScriptableScript: cfg.ScriptableScript,
662+
ScriptableTickIntervalSec: uint64(cfg.ScriptableTickInterval / time.Second),
624663
}
625664
// Set excluded peers for easy autoloop.
626665
rpcCfg.EasyAutoloopExcludedPeers = make(

0 commit comments

Comments
 (0)