Skip to content
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

StaticAddr: Withdraw arbitrary amounts #860

Open
wants to merge 72 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
92f2d9f
sqlc: static address migrations, models, queries
hieblmi Nov 9, 2023
c9da71a
looprpc: static address creation
hieblmi Feb 14, 2024
a7cbcbd
log: static address sub logger
hieblmi Nov 9, 2023
7e22d20
loopdb: static address store
hieblmi Nov 9, 2023
8ed9cb7
staticaddr: static address manager
hieblmi Nov 9, 2023
5b9f050
staticaddr: static address server
hieblmi Feb 14, 2024
1fc8818
daemon: integrate static address manager and sql store
hieblmi Nov 9, 2023
cf53844
perms: static address creation permission
hieblmi Nov 9, 2023
b4e4ef8
loop: static address creation client command
hieblmi Nov 9, 2023
b6f8d4d
looprpc: list unspent static address
hieblmi Nov 9, 2023
8d13cf0
staticaddr: track and list spends in manager
hieblmi Oct 25, 2023
9551390
staticaddr: list unspent outputs in server
hieblmi Oct 25, 2023
4679230
perms: list unspent outputs
hieblmi Oct 25, 2023
c3fae72
loop: list unspent static address outputs
hieblmi Oct 25, 2023
fd63b62
looprpc: include static address in swap client
hieblmi Feb 26, 2024
2ec9c78
sqlc: deposit queries, models and migrations
hieblmi Mar 7, 2024
7f1e5d6
staticaddr: interfaces
hieblmi Mar 7, 2024
24e7b55
staticaddr: deposit logger
hieblmi Mar 7, 2024
6d449b5
staticaddr: deposit manager and fsm
hieblmi Mar 7, 2024
909ec27
staticaddr: deposits for server and daemon
hieblmi Mar 7, 2024
39f77fe
staticaddr: add swap client to cmd
hieblmi Mar 7, 2024
843ef4b
unit: manager tests
hieblmi Apr 23, 2024
d0d480a
staticaddr: public deposit method scope
hieblmi Apr 25, 2024
8e0c23a
sqlc: withdrawal address for deposits
hieblmi May 6, 2024
7487b90
looprpc: deposit withdrawal endpoint
hieblmi May 6, 2024
e2446ca
swapserverrpc: server withdrawal endpoint
hieblmi May 6, 2024
d460e5e
log: unified static address logger
hieblmi May 6, 2024
0a1f78b
staticaddr: address and deposit adjustments for withdrawals
hieblmi May 6, 2024
8bdade6
staticaddr: withdrawal manager and interface
hieblmi May 6, 2024
69cf9f1
loopd: static address withdrawals support
hieblmi May 6, 2024
1fd8238
loop: static address withdrawals support
hieblmi May 6, 2024
00f8d5f
looprpc: static address summary
hieblmi Jun 5, 2024
633ca67
staticaddr: expose static address to client summary
hieblmi Jun 5, 2024
ac4f9ba
loopd: static address summary
hieblmi Jun 5, 2024
b10a422
looprpc: deposit outpoints for QuoteRequest
hieblmi Jun 6, 2024
31750d5
swapserverrpc: number of deposits for ServerLoopInQuoteRequest
hieblmi Jun 6, 2024
2cd8145
loopd: quoting for static address loop-ins
hieblmi Jun 6, 2024
3482993
cmd: quote for static address loop-ins
hieblmi Jun 6, 2024
39963b2
swapserverrpc: static address loop-ins
hieblmi Nov 19, 2024
d2012e7
looprpc: static address loop-ins
hieblmi Nov 19, 2024
c724604
sqlc: loop-in tables and queries
hieblmi Jul 30, 2024
f773985
log: static address loop-in logging
hieblmi Jul 30, 2024
edf5f60
staticaddr: enable deposits for swaps
hieblmi Jul 30, 2024
fcff53b
loopin: public scope for ValidateLoopInContract
hieblmi Sep 26, 2024
fb1305e
interface: add StaticAddressLoopInRequest interface
hieblmi Sep 26, 2024
ea3c5e5
staticaddr: configurable max htlc tx fee
hieblmi Oct 17, 2024
e72d40b
staticaddr: loopin
hieblmi Oct 30, 2024
32690c5
staticaddr: sql_store
hieblmi Oct 30, 2024
22b22e0
staticaddr: swap manager and fsm
hieblmi Oct 24, 2024
bb5a6a5
unit: manager mocks for static address loop-in
hieblmi Jul 30, 2024
d8ec87d
loopd: static address loop-in support
hieblmi Jul 30, 2024
701b187
cmd: static address loop-in
hieblmi Jul 30, 2024
dc21d43
staticaddr: cmd listdeposits and listswaps
hieblmi Nov 5, 2024
b8698e2
staticaddr: separate file for rpc service
hieblmi Nov 5, 2024
5e30133
staticaddr: change min deposit confs from 3 to 6
hieblmi Nov 5, 2024
ad122d2
staticaddr: fix fsm logging
hieblmi Nov 5, 2024
023d593
swapserverrpc: add new functions
sputn1ck Nov 14, 2024
bdfc16d
notifications: add support for new ntfn
sputn1ck Nov 14, 2024
fc90aa0
staticaddr/sql_store: Add fetching loop in by hash
sputn1ck Nov 14, 2024
c0e9d7a
staticaddr/loopin: add listening for sweep request
sputn1ck Nov 14, 2024
f8862c7
staticaddr/loopin: update fsm for server ntfn sigs
sputn1ck Nov 14, 2024
f8599ba
loopd/staticaddr: add ntfn manager
sputn1ck Nov 14, 2024
c1f3f75
staticaddr/loopin: remove unused code
sputn1ck Nov 14, 2024
c2281b8
loopcli/staticaddr: explicit loop in command
sputn1ck Nov 18, 2024
6982adb
cmd/staticaddr: hide behind build flag
sputn1ck Nov 19, 2024
0d30733
looprpc: addr and fee rate for withdrawals
hieblmi Nov 29, 2024
fb9562b
staticaddr: dest addr and fee rate for withdrawals
hieblmi Nov 29, 2024
0d4fbae
staticaddr: add build tag
hieblmi Dec 4, 2024
57c9171
looprpc: static address amount for withdrawals
hieblmi Dec 3, 2024
90057d8
swapserverrpc: static address amount and change amount for withdrawals
hieblmi Dec 3, 2024
b646ed1
staticaddr: arbitrary withdrawal amount
hieblmi Dec 3, 2024
827a65b
loopd: arbitrary withdrawal amount for static address deposits
hieblmi Dec 3, 2024
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
194 changes: 148 additions & 46 deletions staticaddr/withdraw/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"reflect"
"strings"

"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
Expand Down Expand Up @@ -75,6 +76,7 @@ type newWithdrawalRequest struct {
respChan chan *newWithdrawalResponse
destAddr string
satPerVbyte int64
amount int64
}

// newWithdrawalResponse is used to return withdrawal info and error to the
Expand Down Expand Up @@ -156,10 +158,10 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
err)
}

case request := <-m.newWithdrawalRequestChan:
case req := <-m.newWithdrawalRequestChan:
txHash, pkScript, err = m.WithdrawDeposits(
ctx, request.outpoints, request.destAddr,
request.satPerVbyte,
ctx, req.outpoints, req.destAddr,
req.satPerVbyte, req.amount,
)
if err != nil {
log.Errorf("Error withdrawing deposits: %v",
Expand All @@ -174,7 +176,7 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error {
err: err,
}
select {
case request.respChan <- resp:
case req.respChan <- resp:

case <-ctx.Done():
// Notify subroutines that the main loop has
Expand Down Expand Up @@ -261,8 +263,8 @@ func (m *Manager) WaitInitComplete() {

// WithdrawDeposits starts a deposits withdrawal flow.
func (m *Manager) WithdrawDeposits(ctx context.Context,
outpoints []wire.OutPoint, destAddr string, satPerVbyte int64) (string,
string, error) {
outpoints []wire.OutPoint, destAddr string, satPerVbyte int64,
amount int64) (string, string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I propose to add to godoc, that amount=0 means "withdraw everything".


if len(outpoints) == 0 {
return "", "", fmt.Errorf("no outpoints selected to " +
Expand All @@ -272,7 +274,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
// Ensure that the deposits are in a state in which they can be
// withdrawn.
deposits, allActive := m.cfg.DepositManager.AllOutpointsActiveDeposits(
outpoints, deposit.Deposited)
outpoints, deposit.Deposited,
)

if !allActive {
return "", "", ErrWithdrawingInactiveDeposits
Expand Down Expand Up @@ -303,7 +306,7 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,
}

finalizedTx, err := m.createFinalizedWithdrawalTx(
ctx, deposits, withdrawalAddress, satPerVbyte,
ctx, deposits, withdrawalAddress, satPerVbyte, amount,
)
if err != nil {
return "", "", err
Expand Down Expand Up @@ -355,7 +358,8 @@ func (m *Manager) WithdrawDeposits(ctx context.Context,

func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
deposits []*deposit.Deposit, withdrawalAddress btcutil.Address,
satPerVbyte int64) (*wire.MsgTx, error) {
satPerVbyte int64, selectedWithdrawalAmount int64) (*wire.MsgTx,
error) {

// Create a musig2 session for each deposit.
withdrawalSessions, clientNonces, err := m.createMusig2Sessions(
Expand All @@ -380,32 +384,55 @@ func (m *Manager) createFinalizedWithdrawalTx(ctx context.Context,
).FeePerKWeight()
}

outpoints := toOutpoints(deposits)
resp, err := m.cfg.StaticAddressServerClient.ServerWithdrawDeposits(
ctx, &staticaddressrpc.ServerWithdrawRequest{
Outpoints: toPrevoutInfo(outpoints),
ClientNonces: clientNonces,
ClientSweepAddr: withdrawalAddress.String(),
TxFeeRate: uint64(withdrawalSweepFeeRate),
},
params, err := m.cfg.AddressManager.GetStaticAddressParameters(
ctx,
)
if err != nil {
return nil, err
return nil, fmt.Errorf("couldn't get confirmation height for "+
"deposit, %w", err)
}

addressParams, err := m.cfg.AddressManager.GetStaticAddressParameters(
ctx,
// Send change back to the static address.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I propose this change to make it more clear that address is the same.

s/to the static address/to the same static address/

staticAddress, err := m.cfg.AddressManager.GetStaticAddress(ctx)
if err != nil {
log.Warnf("error retrieving taproot address %w", err)

return nil, fmt.Errorf("withdrawal failed")
}

changeAddress, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(staticAddress.TaprootKey),
m.cfg.ChainParams,
)
if err != nil {
return nil, fmt.Errorf("couldn't get confirmation height for "+
"deposit, %w", err)
return nil, err
}

prevOuts := m.toPrevOuts(deposits, addressParams.PkScript)
outpoints := toOutpoints(deposits)
prevOuts := m.toPrevOuts(deposits, params.PkScript)
totalValue := withdrawalValue(prevOuts)
withdrawalTx, err := m.createWithdrawalTx(
outpoints, totalValue, withdrawalAddress,
withdrawalSweepFeeRate,
withdrawalTx, withdrawAmount, changeAmount, err := m.createWithdrawalTx(
outpoints, totalValue, btcutil.Amount(selectedWithdrawalAmount),
withdrawalAddress, changeAddress, withdrawalSweepFeeRate,
)
if err != nil {
return nil, err
}

// Request the server to sign the withdrawal transaction.
//
// The withdrawal and change amount are sent to the server with the
// expectation that the server just signs the transaction, without
// performing fee calculations and dust considerations. The client is
// responsible for that.
resp, err := m.cfg.StaticAddressServerClient.ServerWithdrawDeposits(
ctx, &staticaddressrpc.ServerWithdrawRequest{
Outpoints: toPrevoutInfo(outpoints),
ClientNonces: clientNonces,
ClientWithdrawalAddr: withdrawalAddress.String(),
WithdrawAmount: int64(withdrawAmount),
ChangeAmount: int64(changeAmount),
},
)
if err != nil {
return nil, err
Expand Down Expand Up @@ -613,9 +640,10 @@ func byteSliceTo66ByteSlice(b []byte) ([musig2.PubNonceSize]byte, error) {
}

func (m *Manager) createWithdrawalTx(outpoints []wire.OutPoint,
withdrawlAmount btcutil.Amount, clientSweepAddress btcutil.Address,
feeRate chainfee.SatPerKWeight) (*wire.MsgTx,
error) {
totalWithdrawalAmount btcutil.Amount,
selectedWithdrawalAmount btcutil.Amount, withdrawAddr btcutil.Address,
changeAddress *btcutil.AddressTaproot, feeRate chainfee.SatPerKWeight) (
*wire.MsgTx, btcutil.Amount, btcutil.Amount, error) {

// First Create the tx.
msgTx := wire.NewMsgTx(2)
Expand All @@ -628,33 +656,101 @@ func (m *Manager) createWithdrawalTx(outpoints []wire.OutPoint,
})
}

// Estimate the fee.
weight, err := withdrawalFee(len(outpoints), clientSweepAddress)
var (
hasChange bool
dustLimit = lnwallet.DustLimitForSize(input.P2TRSize)
withdrawalAmount btcutil.Amount
changeAmount btcutil.Amount
)

// Estimate the transaction weight without change.
weight, err := withdrawalTxWeight(len(outpoints), withdrawAddr, false)
if err != nil {
return nil, err
return nil, 0, 0, err
}
feeWithoutChange := feeRate.FeeForWeight(weight)

pkscript, err := txscript.PayToAddrScript(clientSweepAddress)
if err != nil {
return nil, err
// If the user selected a fraction of the sum of the selected deposits
// to withdraw, check if a change output is needed.
if selectedWithdrawalAmount > 0 {
// Estimate the transaction weight with change.
weight, err = withdrawalTxWeight(
len(outpoints), withdrawAddr, true,
)
if err != nil {
return nil, 0, 0, err
}
feeWithChange := feeRate.FeeForWeight(weight)

// The available change that can cover fees is the total
// selected deposit amount minus the selected withdrawal amount.
change := totalWithdrawalAmount - selectedWithdrawalAmount

switch {
case change-feeWithChange >= dustLimit:
// If the change can cover the fees without turning into
// dust, add a non-dust change output.
hasChange = true
changeAmount = change - feeWithChange
withdrawalAmount = selectedWithdrawalAmount

case change-feeWithChange >= 0:
// If the change is dust, we give it to the miners.
hasChange = false
withdrawalAmount = selectedWithdrawalAmount

default:
// If the fees eat into our withdrawal amount, we fail
Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, the feeWithChange fees eat into our withdrawal amount, but this doesn't mean that feeWithoutChange do, since it is lower than feeWithChange. This means, that some amounts fail, even though it is possible to withdraw without change. And we fallback to withdrawing without change in general case when it is not enough funds for the change. So I think for consistency we should do the same here. Can we replace case change-feeWithChange >= 0: with case change-feeWithoutChange >= 0: above?

// the withdrawal.
return nil, 0, 0, fmt.Errorf("the change doesn't " +
"cover for fees. Consider lowering the fee " +
"rate or increase the withdrawal amount")
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/increase/decrease/ IIUC

}
} else {
// If the user wants to withdraw the full amount, we don't need
// a change output.
hasChange = false
withdrawalAmount = totalWithdrawalAmount - feeWithoutChange
}

fee := feeRate.FeeForWeight(weight)
if withdrawalAmount < dustLimit {
return nil, 0, 0, fmt.Errorf("withdrawal amount is below " +
"dust limit")
}

if changeAmount < 0 {
return nil, 0, 0, fmt.Errorf("change amount is negative")
}

// Create the sweep output
sweepOutput := &wire.TxOut{
Value: int64(withdrawlAmount) - int64(fee),
PkScript: pkscript,
withdrawScript, err := txscript.PayToAddrScript(withdrawAddr)
if err != nil {
return nil, 0, 0, err
}

msgTx.AddTxOut(sweepOutput)
// Create the withdrawal output.
msgTx.AddTxOut(&wire.TxOut{
Value: int64(withdrawalAmount),
PkScript: withdrawScript,
})

if hasChange {
changeScript, err := txscript.PayToAddrScript(changeAddress)
if err != nil {
return nil, 0, 0, err
}

return msgTx, nil
msgTx.AddTxOut(&wire.TxOut{
Value: int64(changeAmount),
PkScript: changeScript,
})
}

return msgTx, withdrawalAmount, changeAmount, nil
}

// withdrawalFee returns the weight for the withdrawal transaction.
func withdrawalFee(numInputs int,
sweepAddress btcutil.Address) (lntypes.WeightUnit, error) {
func withdrawalTxWeight(numInputs int, sweepAddress btcutil.Address,
hasChange bool) (lntypes.WeightUnit, error) {

var weightEstimator input.TxWeightEstimator
for i := 0; i < numInputs; i++ {
Expand All @@ -676,6 +772,11 @@ func withdrawalFee(numInputs int,
sweepAddress)
}

// If there's a change output add the weight of the static address.
if hasChange {
weightEstimator.AddP2TROutput()
}

return weightEstimator.Weight(), nil
}

Expand Down Expand Up @@ -814,13 +915,14 @@ func (m *Manager) republishWithdrawals(ctx context.Context) error {
// DeliverWithdrawalRequest forwards a withdrawal request to the manager main
// loop.
func (m *Manager) DeliverWithdrawalRequest(ctx context.Context,
outpoints []wire.OutPoint, destAddr string, satPerVbyte int64) (string,
string, error) {
outpoints []wire.OutPoint, destAddr string, satPerVbyte int64,
amount int64) (string, string, error) {

request := newWithdrawalRequest{
outpoints: outpoints,
destAddr: destAddr,
satPerVbyte: satPerVbyte,
amount: amount,
respChan: make(chan *newWithdrawalResponse),
}

Expand Down