Skip to content

Commit

Permalink
multi: fix the preconditions on swap-in/out
Browse files Browse the repository at this point in the history
Fix pre-conditions in swap in and out,
to `wallet_balance_sat > swap_amount_sat + estimated_fee

This prevents change amounts being unexpectedly posted to dust.
I have also integrated the check into the swap
package layer as a refactor.
  • Loading branch information
YusukeShimizu committed Jul 28, 2024
1 parent 6904f99 commit a973fab
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 63 deletions.
26 changes: 4 additions & 22 deletions clightning/clightning_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,34 +362,16 @@ func (l *SwapIn) Call() (jrpc2.Result, error) {
if !l.cl.isPeerConnected(fundingChannels.Id) {
return nil, fmt.Errorf("peer is not connected")
}
if l.Asset == "lbtc" {
switch l.Asset {
case "lbtc":
if !l.cl.swaps.LiquidEnabled {
return nil, errors.New("liquid swaps are not enabled")
}
liquidBalance, err := l.cl.liquidWallet.GetBalance()
if err != nil {
return nil, err
}
if liquidBalance < l.SatAmt {
return nil, errors.New("Not enough balance on liquid liquidWallet")
}
} else if l.Asset == "btc" {
case "btc":
if !l.cl.swaps.BitcoinEnabled {
return nil, errors.New("bitcoin swaps are not enabled")
}
funds, err := l.cl.glightning.ListFunds()
if err != nil {
return nil, err
}
sats := uint64(0)
for _, v := range funds.Outputs {
sats += v.AmountMilliSatoshi.MSat() / 1000
}

if sats < l.SatAmt+2000 {
return nil, errors.New("Not enough balance on c-lightning onchain liquidWallet")
}
} else {
default:
return nil, errors.New("invalid asset (btc or lbtc)")
}

Expand Down
4 changes: 2 additions & 2 deletions clightning/clightning_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ func (cl *ClightningClient) GetOnchainBalance() (uint64, error) {
return totalBalance, nil
}

// GetFlatSwapOutFee returns an estimated size for the opening transaction. This
// GetFlatOpeningTXFee returns an estimated size for the opening transaction. This
// can be used to calculate the amount of the fee invoice and should cover most
// but not all cases. For an explanation of the estimation see comments of the
// onchain.EstimatedOpeningTxSize.
func (cl *ClightningClient) GetFlatSwapOutFee() (uint64, error) {
func (cl *ClightningClient) GetFlatOpeningTXFee() (uint64, error) {
return cl.bitcoinChain.GetFee(onchain.EstimatedOpeningTxSize)
}

Expand Down
4 changes: 2 additions & 2 deletions lnd/lnd_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,11 @@ func (l *Client) GetRefundFee() (uint64, error) {
return l.bitcoinOnChain.GetFee(250)
}

// GetFlatSwapOutFee returns an estimated size for the opening transaction. This
// GetFlatOpeningTXFee returns an estimated size for the opening transaction. This
// can be used to calculate the amount of the fee invoice and should cover most
// but not all cases. For an explanation of the estimation see comments of the
// onchain.EstimatedOpeningTxSize.
func (l *Client) GetFlatSwapOutFee() (uint64, error) {
func (l *Client) GetFlatOpeningTXFee() (uint64, error) {
return l.bitcoinOnChain.GetFee(onchain.EstimatedOpeningTxSize)
}

Expand Down
12 changes: 7 additions & 5 deletions onchain/liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import (
const (
LiquidCsv = 60
LiquidConfs = 2
// EstimatedOpeningConfidentialTxSizeBytes is the estimated size of a opening transaction.
// The size is a calculate 2672 bytes for 3 inputs and 3 ouputs of which 2 are
// blinded. An additional safety margin is added for a total of 3000 bytes.
EstimatedOpeningConfidentialTxSizeBytes = 3000
)

type LiquidOnChain struct {
Expand Down Expand Up @@ -528,11 +532,9 @@ func (l *LiquidOnChain) GetRefundFee() (uint64, error) {
return l.liquidWallet.GetFee(int64(l.getClaimTxSize()))
}

// GetFlatSwapOutFee returns an estimate of the fee for the opening transaction.
// The size is a calculate 2672 bytes for 3 inputs and 3 ouputs of which 2 are
// blinded. An additional safety margin is added for a total of 3000 bytes.
func (l *LiquidOnChain) GetFlatSwapOutFee() (uint64, error) {
return l.liquidWallet.GetFee(3000)
// GetFlatOpeningTXFee returns an estimate of the fee for the opening transaction.
func (l *LiquidOnChain) GetFlatOpeningTXFee() (uint64, error) {
return l.liquidWallet.GetFee(EstimatedOpeningConfidentialTxSizeBytes)
}

func (l *LiquidOnChain) GetAsset() string {
Expand Down
23 changes: 4 additions & 19 deletions peerswaprpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,31 +219,16 @@ func (p *PeerswapServer) SwapIn(ctx context.Context, request *SwapInRequest) (*S
return nil, errors.New("channel is not connected")
}

if request.Asset == "lbtc" {
switch request.Asset {
case "lbtc":
if !p.swaps.LiquidEnabled {
return nil, errors.New("liquid swaps are not enabled")
}

liquidBalance, err := p.liquidWallet.GetBalance()
if err != nil {
return nil, err
}
if liquidBalance < request.SwapAmount+1000 {
return nil, errors.New("Not enough balance on liquid wallet")
}
} else if request.Asset == "btc" {
case "btc":
if !p.swaps.BitcoinEnabled {
return nil, errors.New("bitcoin swaps are not enabled")
}
walletbalance, err := p.lnd.WalletBalance(ctx, &lnrpc.WalletBalanceRequest{})
if err != nil {
return nil, err
}
if uint64(walletbalance.ConfirmedBalance) < request.SwapAmount+2000 {
return nil, errors.New("Not enough balance on lnd onchain liquidWallet")
}

} else {
default:
return nil, errors.New("invalid asset (btc or lbtc)")
}

Expand Down
12 changes: 4 additions & 8 deletions swap/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,22 +372,18 @@ func (c *CreateSwapOutFromRequestAction) Execute(services *SwapServices, swap *S
return swap.HandleError(err)
}

openingFee, err := wallet.GetFlatSwapOutFee()
openingFee, err := wallet.GetFlatOpeningTXFee()
if err != nil {
swap.LastErr = err
return swap.HandleError(err)
}

// Check if onchain balance is sufficient for swap + fees + some safety net
// Check if onchain balance is sufficient for swap + fees
walletBalance, err := wallet.GetOnchainBalance()
if err != nil {
return swap.HandleError(err)
}

// TODO: this should be looked at in the future
safetynet := uint64(20000)

if walletBalance < swap.GetAmount()+openingFee+safetynet {
if walletBalance < swap.GetAmount()+openingFee {
return swap.HandleError(errors.New("insufficient walletbalance"))
}

Expand Down Expand Up @@ -612,7 +608,7 @@ func (r *PayFeeInvoiceAction) Execute(services *SwapServices, swap *SwapData) Ev

swap.OpeningTxFee = msatAmt / 1000

expectedFee, err := wallet.GetFlatSwapOutFee()
expectedFee, err := wallet.GetFlatOpeningTXFee()
if err != nil {
swap.LastErr = err
return swap.HandleError(err)
Expand Down
44 changes: 41 additions & 3 deletions swap/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,16 +455,20 @@ func (s *SwapService) SwapIn(peer string, chain string, channelId string, initia
if err != nil {
return nil, err
}

rs, err := s.swapServices.lightning.ReceivableMsat(channelId)
if err != nil {
return nil, err
}

if rs <= amtSat*1000 {
return nil, fmt.Errorf("exceeding receivable amount_msat: %d", rs)
}

maximumSwapAmountSat, err := s.estimateMaximumSwapAmountSat(chain)
if err != nil {
return nil, err
}
if amtSat > maximumSwapAmountSat {
return nil, fmt.Errorf("exceeding maximum swap amount: %d", maximumSwapAmountSat)
}
var bitcoinNetwork string
var elementsAsset string
if chain == l_btc_chain {
Expand Down Expand Up @@ -500,6 +504,40 @@ func (s *SwapService) SwapIn(peer string, chain string, channelId string, initia
return swap, nil
}

// estimateMaximumSwapAmountSat estimates the maximum swap amount
// in satoshis for the specified chain.
// This retrieves the on-chain balance and opening tx fee from the wallet,
// and calculates the maximum amount available for swapping.
func (s *SwapService) estimateMaximumSwapAmountSat(chain string) (uint64, error) {
if chain == l_btc_chain {
liquidBalance, err := s.swapServices.liquidWallet.GetOnchainBalance()
if err != nil {
return 0, err
}
// estimatedFee is the amount (in satoshis) of the fee for the opening transaction.
estimatedFee, err := s.swapServices.liquidWallet.GetFlatOpeningTXFee()
if err != nil {
return 0, err
}
// Calculate the available balance for swapping.
return liquidBalance - estimatedFee, nil

} else if chain == btc_chain {
bitcoinBalance, err := s.swapServices.bitcoinWallet.GetOnchainBalance()
if err != nil {
return 0, err
}
// estimatedFee is the amount (in satoshis) of the fee for the opening transaction.
estimatedFee, err := s.swapServices.bitcoinWallet.GetFlatOpeningTXFee()
if err != nil {
return 0, err
}
// Calculate the available balance for swapping.
return bitcoinBalance - estimatedFee, nil
}
return 0, errors.New("invalid chain")
}

// OnSwapInRequestReceived creates a new swap-in process and sends the event to the swap statemachine
func (s *SwapService) OnSwapInRequestReceived(swapId *SwapId, peerId string, message *SwapInRequestMessage) error {
err := s.swapServices.lightning.CanSpend(message.Amount * 1000)
Expand Down
2 changes: 1 addition & 1 deletion swap/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ type Wallet interface {
GetOutputScript(params *OpeningParams) ([]byte, error)
NewAddress() (string, error)
GetRefundFee() (uint64, error)
GetFlatSwapOutFee() (uint64, error)
GetFlatOpeningTXFee() (uint64, error)
GetAsset() string
GetNetwork() string
GetOnchainBalance() (uint64, error)
Expand Down
2 changes: 1 addition & 1 deletion swap/swap_out_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func (d *dummyChain) GetRefundFee() (uint64, error) {
return 100, nil
}

func (d *dummyChain) GetFlatSwapOutFee() (uint64, error) {
func (d *dummyChain) GetFlatOpeningTXFee() (uint64, error) {
return 100, nil
}

Expand Down

0 comments on commit a973fab

Please sign in to comment.