From a7f2f2d8c8f87caedcb5898e4f7f3361766b2cc9 Mon Sep 17 00:00:00 2001 From: Slyghtning Date: Mon, 6 May 2024 14:19:43 +0200 Subject: [PATCH] loopd: static address withdrawals support --- loopd/daemon.go | 41 ++++++++++++++++++++++ loopd/perms/perms.go | 7 ++++ loopd/swapclient_server.go | 69 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/loopd/daemon.go b/loopd/daemon.go index 0749aab1..50405613 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -24,6 +24,7 @@ import ( "github.com/lightninglabs/loop/notifications" "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" @@ -539,6 +540,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { staticAddressManager *address.Manager depositManager *deposit.Manager + withdrawalManager *withdraw.Manager ) // Create the reservation and instantout managers. @@ -604,6 +606,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. @@ -621,6 +635,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { instantOutManager: instantOutManager, staticAddressManager: staticAddressManager, depositManager: depositManager, + withdrawalManager: withdrawalManager, } // Retrieve all currently existing swaps from the database. @@ -804,6 +819,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 diff --git a/loopd/perms/perms.go b/loopd/perms/perms.go index 08e40f1c..10d2ec15 100644 --- a/loopd/perms/perms.go +++ b/loopd/perms/perms.go @@ -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", diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 00fd12d1..fa60267a 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -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" @@ -26,6 +27,7 @@ import ( "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" "github.com/lightninglabs/loop/swapserverrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" @@ -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 @@ -1363,6 +1366,72 @@ func (s *swapClientServer) ListUnspentDeposits(ctx context.Context, return &looprpc.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 *looprpc.WithdrawDepositsRequest) ( + *looprpc.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 + } + } + + txhash, pkScript, err := s.withdrawalManager.DeliverWithdrawalRequest( + ctx, outpoints, + ) + if err != nil { + return nil, err + } + + return &looprpc.WithdrawDepositsResponse{ + WithdrawalTxHash: txhash, + PkScript: pkScript, + }, err +} + +func toServerOutpoints(outpoints []*looprpc.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) (looprpc.AutoReason, error) { switch reason { case liquidity.ReasonNone: