Skip to content

Commit dddf020

Browse files
committed
docs: update loop.md
1 parent 5ff9cd1 commit dddf020

5 files changed

Lines changed: 156 additions & 17 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ To execute a Loop In:
6464
loop in <amt_in_satoshis>
6565
```
6666

67+
### Static Address Recovery
68+
Loop now keeps one encrypted immutable recovery backup per paid L402
69+
generation in the active network data directory. Each backup contains the raw
70+
paid `l402.token` plus the legacy static-address parameters needed to recreate
71+
the current address locally.
72+
73+
Existing static-address users get this backup backfilled on the next startup
74+
with the upgraded client. Fresh installs materialize the initial paid-L402 and
75+
legacy static-address generation during startup so the first backup can be
76+
written immediately.
77+
78+
The follow-up multi-address work is expected to keep this one-backup-per-L402
79+
model and extend it with deterministic root metadata for synthetic-xpub /
80+
BIP328-style address derivation and scanning. See
81+
[recovery/README.md](./recovery/README.md) for the full recovery model and the
82+
planned multi-address outlook.
83+
6784
### More info
6885

6986
- [Loop FAQs](./docs/faqs.md)

docs/loop.1

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,16 @@ abandon a swap with a given swap hash
403403
.PP
404404
\fB--i_know_what_i_am_doing\fP: Specify this flag if you made sure that you read and understood the following consequence of applying this command.
405405

406+
.SH recover
407+
.PP
408+
restore static address and L402 state from a local backup file
409+
410+
.PP
411+
\fB--backup_file\fP="": path to an encrypted backup file; if omitted, loopd uses the most recent immutable L402 backup file path
412+
413+
.PP
414+
\fB--help, -h\fP: show help
415+
406416
.SH reservations, r
407417
.PP
408418
manage reservations
@@ -456,7 +466,7 @@ perform on-chain to off-chain swaps using static addresses.
456466

457467
.SS new, n
458468
.PP
459-
Create a new static loop in address.
469+
Return the static loop in address.
460470

461471
.PP
462472
\fB--help, -h\fP: show help

docs/loop.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,25 @@ The following flags are supported:
418418
| `--i_know_what_i_am_doing` | Specify this flag if you made sure that you read and understood the following consequence of applying this command | bool | `false` |
419419
| `--help` (`-h`) | show help | bool | `false` |
420420

421+
### `recover` command
422+
423+
restore static address and L402 state from a local backup file.
424+
425+
Restores the local static-address state and L402 token from an encrypted backup file. If --backup_file is omitted, loopd will use the most recent immutable network-specific L402 backup file.
426+
427+
Usage:
428+
429+
```bash
430+
$ loop [GLOBAL FLAGS] recover [COMMAND FLAGS] [ARGUMENTS...]
431+
```
432+
433+
The following flags are supported:
434+
435+
| Name | Description | Type | Default value |
436+
|---------------------|----------------------------------------------------------------------------------------------------------|--------|:-------------:|
437+
| `--backup_file="…"` | path to an encrypted backup file; if omitted, loopd uses the most recent immutable L402 backup file path | string |
438+
| `--help` (`-h`) | show help | bool | `false` |
439+
421440
### `reservations` command (aliases: `r`)
422441

423442
manage reservations.
@@ -529,9 +548,9 @@ The following flags are supported:
529548

530549
### `static new` subcommand (aliases: `n`)
531550

532-
Create a new static loop in address.
551+
Return the static loop in address.
533552

534-
Requests a new static loop in address from the server. Funds that are sent to this address will be locked by a 2:2 multisig between us and the loop server, or a timeout path that we can sweep once it opens up. The funds can either be cooperatively spent with a signature from the server or looped in.
553+
Returns the current static loop in address. On a fresh installation loopd initializes the current static-address generation during startup. If the address is still missing, this call will create it on demand. Funds sent to the address will be locked by a 2:2 multisig between us and the loop server, or a timeout path that we can sweep once it opens up. The funds can either be cooperatively spent with a signature from the server or looped in.
535554

536555
Usage:
537556

staticaddr/deposit/manager.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ func (m *Manager) recoverDeposits(ctx context.Context) error {
171171

172172
log.Infof("Recovering static address parameters and deposits...")
173173

174+
// Deposits that are still in the plain Deposited state must remain
175+
// unspent across a restart. Other active states may have already spent
176+
// the deposit and rely on their owning managers to finish recovery.
177+
utxos, err := m.cfg.AddressManager.ListUnspent(ctx, 0, 0)
178+
if err != nil {
179+
return fmt.Errorf("unable to list unspent deposits for "+
180+
"recovery: %w", err)
181+
}
182+
183+
unspentOutpoints := make(map[wire.OutPoint]struct{}, len(utxos))
184+
for _, utxo := range utxos {
185+
unspentOutpoints[utxo.OutPoint] = struct{}{}
186+
}
187+
174188
// Recover deposits.
175189
deposits, err := m.cfg.Store.AllDeposits(ctx)
176190
if err != nil {
@@ -187,6 +201,15 @@ func (m *Manager) recoverDeposits(ctx context.Context) error {
187201
continue
188202
}
189203

204+
if d.GetState() == Deposited {
205+
if _, ok := unspentOutpoints[d.OutPoint]; !ok {
206+
log.Warnf("Skipping recovery of spent deposited "+
207+
"outpoint %v", d.OutPoint)
208+
209+
continue
210+
}
211+
}
212+
190213
log.Debugf("Recovering deposit %x", d.ID)
191214

192215
// Create a state machine for a given deposit.

staticaddr/deposit/manager_test.go

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,66 @@ func TestManager(t *testing.T) {
304304
}
305305
}
306306

307+
func TestRecoverDepositsSkipsSpentDeposited(t *testing.T) {
308+
ctx := context.Background()
309+
310+
id, err := GetRandomDepositID()
311+
require.NoError(t, err)
312+
313+
storedDeposit := &Deposit{
314+
ID: id,
315+
OutPoint: wire.OutPoint{
316+
Hash: chainhash.Hash{1},
317+
Index: 1,
318+
},
319+
state: Deposited,
320+
Value: btcutil.Amount(100000),
321+
ConfirmationHeight: 42,
322+
}
323+
324+
testContext := newManagerTestContextWithStoredDeposits(
325+
t, []*Deposit{storedDeposit}, nil,
326+
)
327+
328+
err = testContext.manager.recoverDeposits(ctx)
329+
require.NoError(t, err)
330+
require.Empty(t, testContext.manager.activeDeposits)
331+
332+
deposits, err := testContext.manager.GetActiveDepositsInState(Deposited)
333+
require.NoError(t, err)
334+
require.Empty(t, deposits)
335+
}
336+
337+
func TestRecoverDepositsKeepsSpentWithdrawing(t *testing.T) {
338+
ctx := context.Background()
339+
340+
id, err := GetRandomDepositID()
341+
require.NoError(t, err)
342+
343+
storedDeposit := &Deposit{
344+
ID: id,
345+
OutPoint: wire.OutPoint{
346+
Hash: chainhash.Hash{2},
347+
Index: 2,
348+
},
349+
state: Withdrawing,
350+
Value: btcutil.Amount(100000),
351+
ConfirmationHeight: 42,
352+
}
353+
354+
testContext := newManagerTestContextWithStoredDeposits(
355+
t, []*Deposit{storedDeposit}, nil,
356+
)
357+
358+
err = testContext.manager.recoverDeposits(ctx)
359+
require.NoError(t, err)
360+
361+
deposits, err := testContext.manager.GetActiveDepositsInState(Withdrawing)
362+
require.NoError(t, err)
363+
require.Len(t, deposits, 1)
364+
require.Equal(t, storedDeposit.OutPoint, deposits[0].OutPoint)
365+
}
366+
307367
// ManagerTestContext is a helper struct that contains all the necessary
308368
// components to test the reservation manager.
309369
type ManagerTestContext struct {
@@ -320,19 +380,9 @@ type ManagerTestContext struct {
320380

321381
// newManagerTestContext creates a new test context for the reservation manager.
322382
func newManagerTestContext(t *testing.T) *ManagerTestContext {
323-
mockLnd := test.NewMockLnd()
324-
lndContext := test.NewContext(t, mockLnd)
325-
326-
mockStaticAddressClient := new(mockStaticAddressClient)
327-
mockAddressManager := new(mockAddressManager)
328-
mockStore := new(mockStore)
329-
mockChainNotifier := new(MockChainNotifier)
330-
confChan := make(chan *chainntnfs.TxConfirmation)
331-
confErrChan := make(chan error)
332-
blockChan := make(chan int32)
333-
blockErrChan := make(chan error)
334-
335383
ID, err := GetRandomDepositID()
384+
require.NoError(t, err)
385+
336386
utxo := &lnwallet.Utxo{
337387
AddressType: lnwallet.TaprootPubkey,
338388
Value: btcutil.Amount(100000),
@@ -343,7 +393,7 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
343393
Index: 0xffffffff,
344394
},
345395
}
346-
require.NoError(t, err)
396+
347397
storedDeposits := []*Deposit{
348398
{
349399
ID: ID,
@@ -355,6 +405,26 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
355405
},
356406
}
357407

408+
return newManagerTestContextWithStoredDeposits(
409+
t, storedDeposits, []*lnwallet.Utxo{utxo},
410+
)
411+
}
412+
413+
func newManagerTestContextWithStoredDeposits(t *testing.T,
414+
storedDeposits []*Deposit, utxos []*lnwallet.Utxo) *ManagerTestContext {
415+
416+
mockLnd := test.NewMockLnd()
417+
lndContext := test.NewContext(t, mockLnd)
418+
419+
mockStaticAddressClient := new(mockStaticAddressClient)
420+
mockAddressManager := new(mockAddressManager)
421+
mockStore := new(mockStore)
422+
mockChainNotifier := new(MockChainNotifier)
423+
confChan := make(chan *chainntnfs.TxConfirmation)
424+
confErrChan := make(chan error)
425+
blockChan := make(chan int32)
426+
blockErrChan := make(chan error)
427+
358428
mockStore.On(
359429
"AllDeposits", mock.Anything,
360430
).Return(storedDeposits, nil)
@@ -371,7 +441,7 @@ func newManagerTestContext(t *testing.T) *ManagerTestContext {
371441

372442
mockAddressManager.On(
373443
"ListUnspent", mock.Anything, mock.Anything, mock.Anything,
374-
).Return([]*lnwallet.Utxo{utxo}, nil)
444+
).Return(utxos, nil)
375445

376446
// Define the expected return values for the mocks.
377447
mockChainNotifier.On(

0 commit comments

Comments
 (0)