Skip to content

Commit 385f3ea

Browse files
authored
Merge pull request #719 from hieblmi/static-addr-4
[4/?] StaticAddr: Instant deposit withdrawals
2 parents c4012d0 + 848cf09 commit 385f3ea

31 files changed

+2379
-531
lines changed

cmd/loop/staticaddr.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package main
22

33
import (
44
"context"
5+
"encoding/hex"
6+
"errors"
57
"fmt"
8+
"strconv"
9+
"strings"
610

11+
"github.com/btcsuite/btcd/chaincfg/chainhash"
712
"github.com/lightninglabs/loop/looprpc"
813
"github.com/urfave/cli"
914
)
@@ -16,6 +21,7 @@ var staticAddressCommands = cli.Command{
1621
Subcommands: []cli.Command{
1722
newStaticAddressCommand,
1823
listUnspentCommand,
24+
withdrawalCommand,
1925
},
2026
}
2127

@@ -105,3 +111,111 @@ func listUnspent(ctx *cli.Context) error {
105111

106112
return nil
107113
}
114+
115+
var withdrawalCommand = cli.Command{
116+
Name: "withdraw",
117+
ShortName: "w",
118+
Usage: "Withdraw from static address deposits.",
119+
Description: `
120+
Withdraws from all or selected static address deposits by sweeping them
121+
back to our lnd wallet.
122+
`,
123+
Flags: []cli.Flag{
124+
cli.StringSliceFlag{
125+
Name: "utxo",
126+
Usage: "specify utxos as outpoints(tx:idx) which will" +
127+
"be withdrawn.",
128+
},
129+
cli.BoolFlag{
130+
Name: "all",
131+
Usage: "withdraws all static address deposits.",
132+
},
133+
},
134+
Action: withdraw,
135+
}
136+
137+
func withdraw(ctx *cli.Context) error {
138+
if ctx.NArg() > 0 {
139+
return cli.ShowCommandHelp(ctx, "withdraw")
140+
}
141+
142+
client, cleanup, err := getClient(ctx)
143+
if err != nil {
144+
return err
145+
}
146+
defer cleanup()
147+
148+
var (
149+
isAllSelected = ctx.IsSet("all")
150+
isUtxoSelected = ctx.IsSet("utxo")
151+
outpoints []*looprpc.OutPoint
152+
ctxb = context.Background()
153+
)
154+
155+
switch {
156+
case isAllSelected == isUtxoSelected:
157+
return errors.New("must select either all or some utxos")
158+
159+
case isAllSelected:
160+
case isUtxoSelected:
161+
utxos := ctx.StringSlice("utxo")
162+
outpoints, err = utxosToOutpoints(utxos)
163+
if err != nil {
164+
return err
165+
}
166+
167+
default:
168+
return fmt.Errorf("unknown withdrawal request")
169+
}
170+
171+
resp, err := client.WithdrawDeposits(ctxb, &looprpc.WithdrawDepositsRequest{
172+
Outpoints: outpoints,
173+
All: isAllSelected,
174+
})
175+
if err != nil {
176+
return err
177+
}
178+
179+
printRespJSON(resp)
180+
181+
return nil
182+
}
183+
184+
func utxosToOutpoints(utxos []string) ([]*looprpc.OutPoint, error) {
185+
outpoints := make([]*looprpc.OutPoint, 0, len(utxos))
186+
if len(utxos) == 0 {
187+
return nil, fmt.Errorf("no utxos specified")
188+
}
189+
for _, utxo := range utxos {
190+
outpoint, err := NewProtoOutPoint(utxo)
191+
if err != nil {
192+
return nil, err
193+
}
194+
outpoints = append(outpoints, outpoint)
195+
}
196+
197+
return outpoints, nil
198+
}
199+
200+
// NewProtoOutPoint parses an OutPoint into its corresponding lnrpc.OutPoint
201+
// type.
202+
func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
203+
parts := strings.Split(op, ":")
204+
if len(parts) != 2 {
205+
return nil, errors.New("outpoint should be of the form " +
206+
"txid:index")
207+
}
208+
txid := parts[0]
209+
if hex.DecodedLen(len(txid)) != chainhash.HashSize {
210+
return nil, fmt.Errorf("invalid hex-encoded txid %v", txid)
211+
}
212+
outputIndex, err := strconv.Atoi(parts[1])
213+
if err != nil {
214+
return nil, fmt.Errorf("invalid output index: %v", err)
215+
}
216+
217+
return &looprpc.OutPoint{
218+
TxidStr: txid,
219+
OutputIndex: uint32(outputIndex),
220+
}, nil
221+
}

loopd/daemon.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
loop_looprpc "github.com/lightninglabs/loop/looprpc"
2424
"github.com/lightninglabs/loop/staticaddr/address"
2525
"github.com/lightninglabs/loop/staticaddr/deposit"
26+
"github.com/lightninglabs/loop/staticaddr/withdraw"
2627
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
2728
"github.com/lightninglabs/loop/sweepbatcher"
2829
"github.com/lightningnetwork/lnd/clock"
@@ -508,6 +509,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
508509

509510
staticAddressManager *address.Manager
510511
depositManager *deposit.Manager
512+
withdrawalManager *withdraw.Manager
511513
)
512514
// Create the reservation and instantout managers.
513515
if d.cfg.EnableExperimental {
@@ -569,6 +571,18 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
569571
Signer: d.lnd.Signer,
570572
}
571573
depositManager = deposit.NewManager(depoCfg)
574+
575+
// Static address deposit withdrawal manager setup.
576+
withdrawalCfg := &withdraw.ManagerConfig{
577+
StaticAddressServerClient: staticAddressClient,
578+
AddressManager: staticAddressManager,
579+
DepositManager: depositManager,
580+
WalletKit: d.lnd.WalletKit,
581+
ChainParams: d.lnd.ChainParams,
582+
ChainNotifier: d.lnd.ChainNotifier,
583+
Signer: d.lnd.Signer,
584+
}
585+
withdrawalManager = withdraw.NewManager(withdrawalCfg)
572586
}
573587

574588
// Now finally fully initialize the swap client RPC server instance.
@@ -586,6 +600,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
586600
instantOutManager: instantOutManager,
587601
staticAddressManager: staticAddressManager,
588602
depositManager: depositManager,
603+
withdrawalManager: withdrawalManager,
589604
}
590605

591606
// Retrieve all currently existing swaps from the database.
@@ -717,6 +732,32 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
717732
depositManager.WaitInitComplete()
718733
}
719734

735+
// Start the static address deposit withdrawal manager.
736+
if withdrawalManager != nil {
737+
d.wg.Add(1)
738+
go func() {
739+
defer d.wg.Done()
740+
741+
// Lnd's GetInfo call supplies us with the current block
742+
// height.
743+
info, err := d.lnd.Client.GetInfo(d.mainCtx)
744+
if err != nil {
745+
d.internalErrChan <- err
746+
return
747+
}
748+
749+
log.Info("Starting static address deposit withdrawal " +
750+
"manager...")
751+
err = withdrawalManager.Run(d.mainCtx, info.BlockHeight)
752+
if err != nil && !errors.Is(context.Canceled, err) {
753+
d.internalErrChan <- err
754+
}
755+
log.Info("Static address deposit withdrawal manager " +
756+
"stopped")
757+
}()
758+
withdrawalManager.WaitInitComplete()
759+
}
760+
720761
// Last, start our internal error handler. This will return exactly one
721762
// error or nil on the main error channel to inform the caller that
722763
// something went wrong or that shutdown is complete. We don't add to

loopd/log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/lightninglabs/loop/instantout/reservation"
1111
"github.com/lightninglabs/loop/liquidity"
1212
"github.com/lightninglabs/loop/loopdb"
13+
"github.com/lightninglabs/loop/staticaddr"
1314
"github.com/lightninglabs/loop/sweepbatcher"
1415
"github.com/lightningnetwork/lnd"
1516
"github.com/lightningnetwork/lnd/build"
@@ -38,6 +39,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
3839
lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger)
3940
lnd.AddSubLogger(root, "STORE", intercept, loopdb.UseLogger)
4041
lnd.AddSubLogger(root, l402.Subsystem, intercept, l402.UseLogger)
42+
lnd.AddSubLogger(
43+
root, staticaddr.Subsystem, intercept, staticaddr.UseLogger,
44+
)
4145
lnd.AddSubLogger(
4246
root, liquidity.Subsystem, intercept, liquidity.UseLogger,
4347
)

loopd/perms/perms.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ var RequiredPermissions = map[string][]bakery.Op{
8787
Entity: "loop",
8888
Action: "in",
8989
}},
90+
"/looprpc.SwapClient/WithdrawDeposits": {{
91+
Entity: "swap",
92+
Action: "execute",
93+
}, {
94+
Entity: "loop",
95+
Action: "in",
96+
}},
9097
"/looprpc.SwapClient/GetLsatTokens": {{
9198
Entity: "auth",
9299
Action: "read",

loopd/swapclient_server.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/btcsuite/btcd/btcec/v2"
1616
"github.com/btcsuite/btcd/btcutil"
1717
"github.com/btcsuite/btcd/chaincfg"
18+
"github.com/btcsuite/btcd/wire"
1819
"github.com/lightninglabs/aperture/l402"
1920
"github.com/lightninglabs/lndclient"
2021
"github.com/lightninglabs/loop"
@@ -26,6 +27,7 @@ import (
2627
clientrpc "github.com/lightninglabs/loop/looprpc"
2728
"github.com/lightninglabs/loop/staticaddr/address"
2829
"github.com/lightninglabs/loop/staticaddr/deposit"
30+
"github.com/lightninglabs/loop/staticaddr/withdraw"
2931
"github.com/lightninglabs/loop/swap"
3032
looprpc "github.com/lightninglabs/loop/swapserverrpc"
3133
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
@@ -87,6 +89,7 @@ type swapClientServer struct {
8789
instantOutManager *instantout.Manager
8890
staticAddressManager *address.Manager
8991
depositManager *deposit.Manager
92+
withdrawalManager *withdraw.Manager
9093
swaps map[lntypes.Hash]loop.SwapInfo
9194
subscribers map[int]chan<- interface{}
9295
statusChan chan loop.SwapInfo
@@ -1348,6 +1351,67 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
13481351
return &clientrpc.ListUnspentDepositsResponse{Utxos: respUtxos}, nil
13491352
}
13501353

1354+
// WithdrawDeposits tries to obtain a partial signature from the server to spend
1355+
// the selected deposits to the client's wallet.
1356+
func (s *swapClientServer) WithdrawDeposits(ctx context.Context,
1357+
req *clientrpc.WithdrawDepositsRequest) (
1358+
*clientrpc.WithdrawDepositsResponse, error) {
1359+
1360+
var (
1361+
isAllSelected = req.All
1362+
isUtxoSelected = len(req.Outpoints) > 0
1363+
outpoints []wire.OutPoint
1364+
err error
1365+
)
1366+
1367+
switch {
1368+
case isAllSelected == isUtxoSelected:
1369+
return nil, fmt.Errorf("must select either all or some utxos")
1370+
1371+
case isAllSelected:
1372+
deposits, err := s.depositManager.GetActiveDepositsInState(
1373+
deposit.Deposited,
1374+
)
1375+
if err != nil {
1376+
return nil, err
1377+
}
1378+
1379+
for _, d := range deposits {
1380+
outpoints = append(outpoints, d.OutPoint)
1381+
}
1382+
1383+
case isUtxoSelected:
1384+
outpoints, err = toServerOutpoints(req.Outpoints)
1385+
if err != nil {
1386+
return nil, err
1387+
}
1388+
}
1389+
1390+
err = s.withdrawalManager.WithdrawDeposits(ctx, outpoints)
1391+
if err != nil {
1392+
return nil, err
1393+
}
1394+
1395+
return &clientrpc.WithdrawDepositsResponse{}, err
1396+
}
1397+
1398+
func toServerOutpoints(outpoints []*clientrpc.OutPoint) ([]wire.OutPoint,
1399+
error) {
1400+
1401+
var serverOutpoints []wire.OutPoint
1402+
for _, o := range outpoints {
1403+
outpointStr := fmt.Sprintf("%s:%d", o.TxidStr, o.OutputIndex)
1404+
newOutpoint, err := wire.NewOutPointFromString(outpointStr)
1405+
if err != nil {
1406+
return nil, err
1407+
}
1408+
1409+
serverOutpoints = append(serverOutpoints, *newOutpoint)
1410+
}
1411+
1412+
return serverOutpoints, nil
1413+
}
1414+
13511415
func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {
13521416
switch reason {
13531417
case liquidity.ReasonNone:

loopdb/sqlc/migrations/000010_static_address_deposits.up.sql

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ CREATE TABLE IF NOT EXISTS deposits (
2424
timeout_sweep_pk_script BYTEA NOT NULL,
2525

2626
-- expiry_sweep_txid is the transaction id of the expiry sweep.
27-
expiry_sweep_txid BLOB
27+
expiry_sweep_txid BLOB,
28+
29+
-- withdrawal_sweep_address is the address that will be used to sweep the
30+
-- deposit cooperatively with the server before it has expired.
31+
withdrawal_sweep_address TEXT
2832
);
2933

3034
-- deposit_updates contains all the updates to a deposit.

loopdb/sqlc/models.go

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)