Skip to content

Commit

Permalink
liquid: drop the flat fee for mempool policy patch
Browse files Browse the repository at this point in the history
elements releases have the mempool policy patch.
To apply ct discount on peerswap, we uses An
"Ask forgiveness not permission" approach,
where we attempt to broadcast with the discounted fee,
and if that fails, we retry with the non-discounted fee.

The discount is achieved by calculating the fee
based on a discounted vsize
(equivalent to 1/4 of the original tx size).
  • Loading branch information
YusukeShimizu committed Aug 21, 2024
1 parent 5935fb4 commit 6ff417c
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 59 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/btcsuite/btcd/btcutil v1.1.2
github.com/btcsuite/btcd/btcutil/psbt v1.1.5
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
github.com/elementsproject/glightning v0.0.0-20240224063423-55240d61b52a
github.com/elementsproject/glightning v0.0.0-20240802020216-b4e19b004ca4
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3
github.com/jessevdk/go-flags v1.5.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6 h1:sE4tvxWw01v7K3M
github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/elementsproject/glightning v0.0.0-20240224063423-55240d61b52a h1:xnVQmVqGmSs3m8zPQF4iYEYiUAmJx8MlT9vJ3lAaOjc=
github.com/elementsproject/glightning v0.0.0-20240224063423-55240d61b52a/go.mod h1:YAdIeSyx8VEhDCtEaGOJLmWNpPaQ3x4vYSAj9Vrppdo=
github.com/elementsproject/glightning v0.0.0-20240802020216-b4e19b004ca4 h1:7CXEOi0uTeMrwLfFmHsbBS5yRfpSAHALwa9k9Rtl1Vw=
github.com/elementsproject/glightning v0.0.0-20240802020216-b4e19b004ca4/go.mod h1:YAdIeSyx8VEhDCtEaGOJLmWNpPaQ3x4vYSAj9Vrppdo=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down
37 changes: 37 additions & 0 deletions lwk/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package lwk

import (
"encoding/json"
"errors"
"fmt"
"regexp"
)

// electrumRPCError represents the structure of an RPC error response
type electrumRPCError struct {
Code int `json:"code"`
Message string `json:"message"`
}

// Regular expression to match RPC error messages with any prefix
var re = regexp.MustCompile(`^(.*) RPC error: (.*)$`)

// parseRPCError parses an error and extracts the RPC error code and message if present
func parseRPCError(err error) (*electrumRPCError, error) {
var rpcErr electrumRPCError
errStr := err.Error()

matches := re.FindStringSubmatch(errStr)

if len(matches) == 3 { // Prefix and JSON payload extracted successfully
errJSON := matches[2]
if jerr := json.Unmarshal([]byte(errJSON), &rpcErr); jerr != nil {
return nil, fmt.Errorf("error parsing rpc error: %v", jerr)
}
} else {
// If no RPC error pattern is found, return the original error
return nil, errors.New(errStr)
}

return &rpcErr, nil
}
54 changes: 54 additions & 0 deletions lwk/error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package lwk

import (
"errors"
"testing"
)

func TestParseRPCError(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
err error
expectedCode int
expectedMsg string
wantErr bool
}{
"Valid RPC error": {
err: errors.New("sendrawtransaction RPC error: {\"code\":-26,\"message\":\"min relay fee not met\"}"),
expectedCode: -26,
expectedMsg: "min relay fee not met",
wantErr: false,
},
"Invalid JSON payload": {

err: errors.New("RPC error: {invalid json}"),
expectedCode: 0,
expectedMsg: "",
wantErr: true,
},
"No RPC error pattern": {
err: errors.New("Some other error"),
expectedCode: 0,
expectedMsg: "",
wantErr: true,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()
rpcErr, err := parseRPCError(tc.err)
if (err != nil) != tc.wantErr {
t.Errorf("wantErr: %v, got error: %v", tc.wantErr, err)
}
if err == nil {
if rpcErr.Code != tc.expectedCode {
t.Errorf("expected code: %d, got: %d", tc.expectedCode, rpcErr.Code)
}
if rpcErr.Message != tc.expectedMsg {
t.Errorf("expected message: %s, got: %s", tc.expectedMsg, rpcErr.Message)
}
}
})
}
}
8 changes: 8 additions & 0 deletions lwk/lwkwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lwk
import (
"context"
"errors"
"fmt"

"math"
"strings"
Expand Down Expand Up @@ -258,6 +259,13 @@ func (r *LWKRpcWallet) SendRawTx(txHex string) (string, error) {
defer cancel()
res, err := r.electrumClient.BroadcastTransaction(ctx, txHex)
if err != nil {
rpcErr, pErr := parseRPCError(err)
if pErr != nil {
return "", fmt.Errorf("error parsing rpc error: %v", pErr)
}
if rpcErr.Code == -26 {
return "", wallet.MinRelayFeeNotMetError
}
return "", err
}
return res, nil
Expand Down
123 changes: 100 additions & 23 deletions onchain/liquid.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,38 @@ func (l *LiquidOnChain) CreateOpeningTransaction(swapParams *swap.OpeningParams)
return txHex, blindedScriptAddr, txId, fee, vout, nil
}

// feeAmountPlaceholder is a placeholder for the fee amount
const feeAmountPlaceholder = uint64(500)

func (l *LiquidOnChain) CreatePreimageSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams) (string, string, string, error) {
fee, err := l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindPreimageSpending, true)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
txId, txHex, newAddr, err := l.createPreimageSpendingTransaction(swapParams, claimParams, fee)
if err == nil {
return txId, txHex, newAddr, nil
}
if !errors.Is(err, wallet.MinRelayFeeNotMetError) {
return "", "", "", err
}
fee, err = l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindPreimageSpending, false)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
return l.createPreimageSpendingTransaction(swapParams, claimParams, fee)
}

func (l *LiquidOnChain) createPreimageSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams, fee uint64) (string, string, string, error) {
newAddr, err := l.liquidWallet.GetAddress()
if err != nil {
return "", "", "", err
}
l.AddBlindingRandomFactors(claimParams)

tx, sigBytes, redeemScript, err := l.prepareSpendingTransaction(swapParams, claimParams, newAddr, 0, 0)
tx, sigBytes, redeemScript, err := l.prepareSpendingTransaction(swapParams, claimParams, newAddr, 0, fee)
if err != nil {
return "", "", "", err
}
Expand Down Expand Up @@ -118,12 +142,33 @@ func (l *LiquidOnChain) CreatePreimageSpendingTransaction(swapParams *swap.Openi
}

func (l *LiquidOnChain) CreateCsvSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams) (txId, txHex, address string, error error) {
fee, err := l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindPreimageSpending, true)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
txId, txHex, newAddr, err := l.createCsvSpendingTransaction(swapParams, claimParams, fee)
if err == nil {
return txId, txHex, newAddr, nil
}
if !errors.Is(err, wallet.MinRelayFeeNotMetError) {
return "", "", "", err
}
fee, err = l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindPreimageSpending, false)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
return l.createCsvSpendingTransaction(swapParams, claimParams, fee)
}

func (l *LiquidOnChain) createCsvSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams, fee uint64) (txId, txHex, address string, error error) {
newAddr, err := l.liquidWallet.GetAddress()
if err != nil {
return "", "", "", err
}
l.AddBlindingRandomFactors(claimParams)
tx, sigBytes, redeemScript, err := l.prepareSpendingTransaction(swapParams, claimParams, newAddr, LiquidCsv, 0)
tx, sigBytes, redeemScript, err := l.prepareSpendingTransaction(swapParams, claimParams, newAddr, LiquidCsv, fee)
if err != nil {
return "", "", "", err
}
Expand All @@ -140,11 +185,28 @@ func (l *LiquidOnChain) CreateCsvSpendingTransaction(swapParams *swap.OpeningPar
}

func (l *LiquidOnChain) CreateCoopSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams, takerSigner swap.Signer) (txId, txHex, address string, error error) {
refundAddr, err := l.NewAddress()
fee, err := l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindCoop, true)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
txId, txHex, newAddr, err := l.createCoopSpendingTransaction(swapParams, claimParams, takerSigner, fee)
if err == nil {
return txId, txHex, newAddr, nil
}
if !errors.Is(err, wallet.MinRelayFeeNotMetError) {
return "", "", "", err
}
refundFee, err := l.liquidWallet.GetFee(int64(l.getCoopClaimTxSize()))
fee, err = l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindCoop, false)))
if err != nil {
log.Infof("error getting fee %v", err)
fee = feeAmountPlaceholder
}
return l.createCoopSpendingTransaction(swapParams, claimParams, takerSigner, fee)
}

func (l *LiquidOnChain) createCoopSpendingTransaction(swapParams *swap.OpeningParams, claimParams *swap.ClaimParams, takerSigner swap.Signer, fee uint64) (txId, txHex, address string, error error) {
refundAddr, err := l.NewAddress()
if err != nil {
return "", "", "", err
}
Expand All @@ -156,7 +218,7 @@ func (l *LiquidOnChain) CreateCoopSpendingTransaction(swapParams *swap.OpeningPa
if err != nil {
return "", "", "", err
}
spendingTx, sigHash, err := l.createSpendingTransaction(claimParams.OpeningTxHex, swapParams.Amount, 0, l.asset, redeemScript, refundAddr, refundFee, swapParams.BlindingKey, claimParams.EphemeralKey, claimParams.OutputAssetBlindingFactor, claimParams.BlindingSeed)
spendingTx, sigHash, err := l.createSpendingTransaction(claimParams.OpeningTxHex, swapParams.Amount, 0, l.asset, redeemScript, refundAddr, fee, swapParams.BlindingKey, claimParams.EphemeralKey, claimParams.OutputAssetBlindingFactor, claimParams.BlindingSeed)
if err != nil {
return "", "", "", err
}
Expand Down Expand Up @@ -235,6 +297,9 @@ func (l *LiquidOnChain) prepareSpendingTransaction(swapParams *swap.OpeningParam

// CreateSpendingTransaction returns the spendningTransaction for the swap
func (l *LiquidOnChain) createSpendingTransaction(openingTxHex string, swapAmount uint64, csv uint32, asset, redeemScript []byte, redeemAddr string, preparedFee uint64, blindingKey, ephemeralPrivKey *btcec.PrivateKey, outputAbf, seed []byte) (tx *transaction.Transaction, sigHash [32]byte, err error) {
if preparedFee == 0 {
return nil, [32]byte{}, errors.New("fee must be set other than 0")
}
firstTx, err := transaction.NewTxFromHex(openingTxHex)
if err != nil {
log.Infof("error creating first tx %s, %v", openingTxHex, err)
Expand Down Expand Up @@ -263,16 +328,7 @@ func (l *LiquidOnChain) createSpendingTransaction(openingTxHex string, swapAmoun
return nil, [32]byte{}, errors.New(fmt.Sprintf("Tx value is not equal to the swap contract expected: %v, tx: %v", swapAmount, ubRes.Value))
}

feeAmountPlaceholder := uint64(500)
fee := preparedFee
if preparedFee == 0 {
fee, err = l.liquidWallet.GetFee(int64(l.getClaimTxSize()))
if err != nil {
fee = feeAmountPlaceholder
}
}

outputValue := ubRes.Value - fee
outputValue := ubRes.Value - preparedFee

finalVbfArgs := confidential.FinalValueBlindingFactorArgs{
InValues: []uint64{ubRes.Value},
Expand Down Expand Up @@ -366,7 +422,7 @@ func (l *LiquidOnChain) createSpendingTransaction(openingTxHex string, swapAmoun
spendingTx.Outputs = append(spendingTx.Outputs, receiverOutput)

// add feeoutput
feeValue, _ := elementsutil.ValueToBytes(fee)
feeValue, _ := elementsutil.ValueToBytes(preparedFee)
feeScript := []byte{}
feeOutput := transaction.NewTxOutput(asset, feeValue, feeScript)
spendingTx.Outputs = append(spendingTx.Outputs, feeOutput)
Expand All @@ -378,12 +434,33 @@ func (l *LiquidOnChain) createSpendingTransaction(openingTxHex string, swapAmoun
return spendingTx, sigHash, nil
}

func (l *LiquidOnChain) getClaimTxSize() int {
return 1350
}
type transactionKind string

const (
transactionKindPreimageSpending transactionKind = "preimage"
transactionKindCoop transactionKind = "coop"
transactionKindOpening transactionKind = "open"
transactionKindCSV transactionKind = "csv"
)

func (l *LiquidOnChain) getCoopClaimTxSize() int {
return 1360
func getEstimatedTxSize(t transactionKind, ctDiscount bool) int {
txsize := 0
switch t {
case transactionKindPreimageSpending:
txsize = 1350
case transactionKindCoop:
txsize = 1360
case transactionKindOpening:
txsize = EstimatedOpeningConfidentialTxSizeBytes
case transactionKindCSV:
txsize = 1350
default:
return 1360
}
if ctDiscount {
return txsize / 4
}
return txsize
}

func (l *LiquidOnChain) TxIdFromHex(txHex string) (string, error) {
Expand Down Expand Up @@ -529,12 +606,12 @@ func (l *LiquidOnChain) Blech32ToScript(blech32Addr string) ([]byte, error) {
}

func (l *LiquidOnChain) GetRefundFee() (uint64, error) {
return l.liquidWallet.GetFee(int64(l.getClaimTxSize()))
return l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindCoop, false)))
}

// GetFlatOpeningTXFee returns an estimate of the fee for the opening transaction.
func (l *LiquidOnChain) GetFlatOpeningTXFee() (uint64, error) {
return l.liquidWallet.GetFee(EstimatedOpeningConfidentialTxSizeBytes)
return l.liquidWallet.GetFee(int64(getEstimatedTxSize(transactionKindOpening, true)))
}

func (l *LiquidOnChain) GetAsset() string {
Expand Down
2 changes: 1 addition & 1 deletion test/lwk_cln_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func Test_ClnCln_LWK_SwapIn(t *testing.T) {
bitcoind, liquidd, lightningds, scid, electrs, lwk := clnclnLWKSetup(t, uint64(math.Pow10(9)))
defer func() {
if t.Failed() {
filter := os.Getenv("PEERSWAP_TEST_FILTER")
filter := ""
pprintFail(
tailableProcess{
p: bitcoind.DaemonProcess,
Expand Down
Loading

0 comments on commit 6ff417c

Please sign in to comment.