Skip to content

[4/?] StaticAddr: Instant deposit withdrawals #719

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions cmd/loop/staticaddr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ package main

import (
"context"
"encoding/hex"
"errors"
"fmt"
"strconv"
"strings"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli"
)
Expand All @@ -16,6 +21,7 @@ var staticAddressCommands = cli.Command{
Subcommands: []cli.Command{
newStaticAddressCommand,
listUnspentCommand,
withdrawalCommand,
},
}

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

return nil
}

var withdrawalCommand = cli.Command{
Name: "withdraw",
ShortName: "w",
Usage: "Withdraw from static address deposits.",
Description: `
Withdraws from all or selected static address deposits by sweeping them
back to our lnd wallet.
`,
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "utxo",
Usage: "specify utxos as outpoints(tx:idx) which will" +
"be withdrawn.",
},
cli.BoolFlag{
Name: "all",
Usage: "withdraws all static address deposits.",
},
},
Action: withdraw,
}

func withdraw(ctx *cli.Context) error {
if ctx.NArg() > 0 {
return cli.ShowCommandHelp(ctx, "withdraw")
}

client, cleanup, err := getClient(ctx)
if err != nil {
return err
}
defer cleanup()

var (
isAllSelected = ctx.IsSet("all")
isUtxoSelected = ctx.IsSet("utxo")
outpoints []*looprpc.OutPoint
ctxb = context.Background()
)

switch {
case isAllSelected == isUtxoSelected:
return errors.New("must select either all or some utxos")

case isAllSelected:
case isUtxoSelected:
utxos := ctx.StringSlice("utxo")
outpoints, err = utxosToOutpoints(utxos)
if err != nil {
return err
}

default:
return fmt.Errorf("unknown withdrawal request")
}

resp, err := client.WithdrawDeposits(ctxb, &looprpc.WithdrawDepositsRequest{
Outpoints: outpoints,
All: isAllSelected,
})
if err != nil {
return err
}

printRespJSON(resp)

return nil
}

func utxosToOutpoints(utxos []string) ([]*looprpc.OutPoint, error) {
outpoints := make([]*looprpc.OutPoint, 0, len(utxos))
if len(utxos) == 0 {
return nil, fmt.Errorf("no utxos specified")
}
for _, utxo := range utxos {
outpoint, err := NewProtoOutPoint(utxo)
if err != nil {
return nil, err
}
outpoints = append(outpoints, outpoint)
}

return outpoints, nil
}

// NewProtoOutPoint parses an OutPoint into its corresponding lnrpc.OutPoint
// type.
func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) {
parts := strings.Split(op, ":")
if len(parts) != 2 {
return nil, errors.New("outpoint should be of the form " +
"txid:index")
}
txid := parts[0]
if hex.DecodedLen(len(txid)) != chainhash.HashSize {
return nil, fmt.Errorf("invalid hex-encoded txid %v", txid)
}
outputIndex, err := strconv.Atoi(parts[1])
if err != nil {
return nil, fmt.Errorf("invalid output index: %v", err)
}

return &looprpc.OutPoint{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an empty line before return.

TxidStr: txid,
OutputIndex: uint32(outputIndex),
}, nil
}
41 changes: 41 additions & 0 deletions loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
loop_looprpc "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/staticaddr/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/withdraw"
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightningnetwork/lnd/clock"
Expand Down Expand Up @@ -508,6 +509,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {

staticAddressManager *address.Manager
depositManager *deposit.Manager
withdrawalManager *withdraw.Manager
)
// Create the reservation and instantout managers.
if d.cfg.EnableExperimental {
Expand Down Expand Up @@ -569,6 +571,18 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
Signer: d.lnd.Signer,
}
depositManager = deposit.NewManager(depoCfg)

// Static address deposit withdrawal manager setup.
withdrawalCfg := &withdraw.ManagerConfig{
StaticAddressServerClient: staticAddressClient,
AddressManager: staticAddressManager,
DepositManager: depositManager,
WalletKit: d.lnd.WalletKit,
ChainParams: d.lnd.ChainParams,
ChainNotifier: d.lnd.ChainNotifier,
Signer: d.lnd.Signer,
}
withdrawalManager = withdraw.NewManager(withdrawalCfg)
}

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

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

// Start the static address deposit withdrawal manager.
if withdrawalManager != nil {
d.wg.Add(1)
go func() {
defer d.wg.Done()

// Lnd's GetInfo call supplies us with the current block
// height.
info, err := d.lnd.Client.GetInfo(d.mainCtx)
if err != nil {
d.internalErrChan <- err
return
}

log.Info("Starting static address deposit withdrawal " +
"manager...")
err = withdrawalManager.Run(d.mainCtx, info.BlockHeight)
if err != nil && !errors.Is(context.Canceled, err) {
d.internalErrChan <- err
}
log.Info("Static address deposit withdrawal manager " +
"stopped")
}()
withdrawalManager.WaitInitComplete()
}

// Last, start our internal error handler. This will return exactly one
// error or nil on the main error channel to inform the caller that
// something went wrong or that shutdown is complete. We don't add to
Expand Down
4 changes: 4 additions & 0 deletions loopd/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/lightninglabs/loop/instantout/reservation"
"github.com/lightninglabs/loop/liquidity"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/staticaddr"
"github.com/lightninglabs/loop/sweepbatcher"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/build"
Expand Down Expand Up @@ -38,6 +39,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
lnd.AddSubLogger(root, "LNDC", intercept, lndclient.UseLogger)
lnd.AddSubLogger(root, "STORE", intercept, loopdb.UseLogger)
lnd.AddSubLogger(root, l402.Subsystem, intercept, l402.UseLogger)
lnd.AddSubLogger(
root, staticaddr.Subsystem, intercept, staticaddr.UseLogger,
)
lnd.AddSubLogger(
root, liquidity.Subsystem, intercept, liquidity.UseLogger,
)
Expand Down
7 changes: 7 additions & 0 deletions loopd/perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ var RequiredPermissions = map[string][]bakery.Op{
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/WithdrawDeposits": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/GetLsatTokens": {{
Entity: "auth",
Action: "read",
Expand Down
64 changes: 64 additions & 0 deletions loopd/swapclient_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/aperture/l402"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
Expand All @@ -26,6 +27,7 @@ import (
clientrpc "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/staticaddr/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/withdraw"
"github.com/lightninglabs/loop/swap"
looprpc "github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
Expand Down Expand Up @@ -87,6 +89,7 @@ type swapClientServer struct {
instantOutManager *instantout.Manager
staticAddressManager *address.Manager
depositManager *deposit.Manager
withdrawalManager *withdraw.Manager
swaps map[lntypes.Hash]loop.SwapInfo
subscribers map[int]chan<- interface{}
statusChan chan loop.SwapInfo
Expand Down Expand Up @@ -1348,6 +1351,67 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context,
return &clientrpc.ListUnspentDepositsResponse{Utxos: respUtxos}, nil
}

// WithdrawDeposits tries to obtain a partial signature from the server to spend
// the selected deposits to the client's wallet.
func (s *swapClientServer) WithdrawDeposits(ctx context.Context,
req *clientrpc.WithdrawDepositsRequest) (
*clientrpc.WithdrawDepositsResponse, error) {

var (
isAllSelected = req.All
isUtxoSelected = len(req.Outpoints) > 0
outpoints []wire.OutPoint
err error
)

switch {
case isAllSelected == isUtxoSelected:
return nil, fmt.Errorf("must select either all or some utxos")

case isAllSelected:
deposits, err := s.depositManager.GetActiveDepositsInState(
deposit.Deposited,
)
if err != nil {
return nil, err
}

for _, d := range deposits {
outpoints = append(outpoints, d.OutPoint)
}

case isUtxoSelected:
outpoints, err = toServerOutpoints(req.Outpoints)
if err != nil {
return nil, err
}
}

err = s.withdrawalManager.WithdrawDeposits(ctx, outpoints)
if err != nil {
return nil, err
}

return &clientrpc.WithdrawDepositsResponse{}, err
}

func toServerOutpoints(outpoints []*clientrpc.OutPoint) ([]wire.OutPoint,
error) {

var serverOutpoints []wire.OutPoint
for _, o := range outpoints {
outpointStr := fmt.Sprintf("%s:%d", o.TxidStr, o.OutputIndex)
newOutpoint, err := wire.NewOutPointFromString(outpointStr)
if err != nil {
return nil, err
}

serverOutpoints = append(serverOutpoints, *newOutpoint)
}

return serverOutpoints, nil
}

func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {
switch reason {
case liquidity.ReasonNone:
Expand Down
6 changes: 5 additions & 1 deletion loopdb/sqlc/migrations/000010_static_address_deposits.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ CREATE TABLE IF NOT EXISTS deposits (
timeout_sweep_pk_script BYTEA NOT NULL,

-- expiry_sweep_txid is the transaction id of the expiry sweep.
expiry_sweep_txid BLOB
expiry_sweep_txid BLOB,

-- withdrawal_sweep_address is the address that will be used to sweep the
-- deposit cooperatively with the server before it has expired.
withdrawal_sweep_address TEXT
);

-- deposit_updates contains all the updates to a deposit.
Expand Down
17 changes: 9 additions & 8 deletions loopdb/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading