Merge pull request #480 from tecnovert/feerate

Feerate validation
This commit is contained in:
tecnovert
2026-05-30 18:10:09 +00:00
committed by GitHub
21 changed files with 406 additions and 41 deletions
+2 -2
View File
@@ -60,7 +60,7 @@ jobs:
flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py flake8 --ignore=E203,E501,W503 --exclude=basicswap/contrib,basicswap/interface/contrib,.eggs,.tox,bin/install_certifi.py
- name: Run codespell - name: Run codespell
run: | run: |
codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static codespell
- name: Run black - name: Run black
run: | run: |
black --check --diff --exclude="contrib" . black --check --diff --exclude="contrib" .
@@ -92,7 +92,7 @@ jobs:
export PARTICL_BINDIR="$BIN_DIR/particl" export PARTICL_BINDIR="$BIN_DIR/particl"
export BITCOIN_BINDIR="$BIN_DIR/bitcoin" export BITCOIN_BINDIR="$BIN_DIR/bitcoin"
export XMR_BINDIR="$BIN_DIR/monero" export XMR_BINDIR="$BIN_DIR/monero"
pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx" pytest tests/basicswap/test_btc_xmr.py::TestBTC -k "test_003_api or test_02_a_leader_recover_a_lock_tx or test_11_fee_validation"
- name: Run test_encrypted_xmr_reload - name: Run test_encrypted_xmr_reload
id: test_encrypted_xmr_reload id: test_encrypted_xmr_reload
run: | run: |
+11
View File
@@ -4126,6 +4126,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
msg_buf.fee_rate_to = ci_to.make_int(fee_rate) msg_buf.fee_rate_to = ci_to.make_int(fee_rate)
if swap_type == SwapTypes.XMR_SWAP: if swap_type == SwapTypes.XMR_SWAP:
ci_from.validateFeeRate(msg_buf.fee_rate_from)
ci_to.validateFeeRate(msg_buf.fee_rate_to)
xmr_offer = XmrOffer() xmr_offer = XmrOffer()
chain_a_ci = ci_to if reverse_bid else ci_from chain_a_ci = ci_to if reverse_bid else ci_from
@@ -6029,6 +6032,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if offer.swap_type != SwapTypes.XMR_SWAP: if offer.swap_type != SwapTypes.XMR_SWAP:
raise ValueError(f"TODO: Unknown swap type {offer.swap_type.name}") raise ValueError(f"TODO: Unknown swap type {offer.swap_type.name}")
ci_from.validateFeeRate(xmr_offer.a_fee_rate)
ci_to.validateFeeRate(xmr_offer.b_fee_rate)
if not (self.debug and extra_options.get("debug_skip_validation", False)): if not (self.debug and extra_options.get("debug_skip_validation", False)):
self.validateBidValidTime( self.validateBidValidTime(
offer.swap_type, coin_from, coin_to, valid_for_seconds offer.swap_type, coin_from, coin_to, valid_for_seconds
@@ -10069,6 +10075,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
ensure(len(offer_data.proof_signature) == 0, "Unexpected data") ensure(len(offer_data.proof_signature) == 0, "Unexpected data")
ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data") ensure(len(offer_data.pkhash_seller) == 0, "Unexpected data")
ensure(len(offer_data.secret_hash) == 0, "Unexpected data") ensure(len(offer_data.secret_hash) == 0, "Unexpected data")
ci_from.validateFeeRate(offer_data.fee_rate_from)
ci_to.validateFeeRate(offer_data.fee_rate_to)
else: else:
raise ValueError("Unknown swap type {}.".format(offer_data.swap_type)) raise ValueError("Unknown swap type {}.".format(offer_data.swap_type))
@@ -10094,6 +10104,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
# Check for sent # Check for sent
existing_offer = self.getOffer(offer_id, cursor=cursor) existing_offer = self.getOffer(offer_id, cursor=cursor)
if existing_offer is None: if existing_offer is None:
bid_reversed: bool = ( bid_reversed: bool = (
offer_data.swap_type == SwapTypes.XMR_SWAP offer_data.swap_type == SwapTypes.XMR_SWAP
and self.is_reverse_ads_bid( and self.is_reverse_ads_bid(
+4 -1
View File
@@ -50,7 +50,7 @@ class CoinInterface:
def compareFeeRates(a, b) -> bool: def compareFeeRates(a, b) -> bool:
return abs(a - b) < 20 return abs(a - b) < 20
def __init__(self, network): def __init__(self, network, **kwargs):
self.setDefaults() self.setDefaults()
self._network = network self._network = network
self._mx_wallet = threading.Lock() self._mx_wallet = threading.Lock()
@@ -195,6 +195,9 @@ class AdaptorSigInterface:
class Secp256k1Interface(CoinInterface, AdaptorSigInterface): class Secp256k1Interface(CoinInterface, AdaptorSigInterface):
def __init__(self, **kwargs):
super().__init__(**kwargs)
@staticmethod @staticmethod
def curve_type(): def curve_type():
return Curves.secp256k1 return Curves.secp256k1
+7 -2
View File
@@ -71,8 +71,13 @@ class BCHInterface(BTCInterface):
# TODO: BCH Watchonly: Remove when BCH watchonly works. # TODO: BCH Watchonly: Remove when BCH watchonly works.
return True return True
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(BCHInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self.swap_client = swap_client self.swap_client = swap_client
def has_segwit(self) -> bool: def has_segwit(self) -> bool:
+11 -5
View File
@@ -27,6 +27,7 @@ from basicswap.basicswap_util import (
getVoutByScriptPubKey, getVoutByScriptPubKey,
) )
from basicswap.interface.base import Secp256k1Interface from basicswap.interface.base import Secp256k1Interface
from basicswap.interface.utils import FeeValidator
from basicswap.util import ( from basicswap.util import (
b2i, b2i,
ensure, ensure,
@@ -184,7 +185,7 @@ def extractScriptLockRefundScriptValues(script_bytes: bytes):
return pk1, pk2, csv_val, pk3 return pk1, pk2, csv_val, pk3
class BTCInterface(Secp256k1Interface): class BTCInterface(FeeValidator, Secp256k1Interface):
_scantxoutset_lock = threading.Lock() _scantxoutset_lock = threading.Lock()
_MAX_SCANTXOUTSET_RETRIES = 3 _MAX_SCANTXOUTSET_RETRIES = 3
@@ -278,8 +279,15 @@ class BTCInterface(Secp256k1Interface):
def depth_spendable() -> int: def depth_spendable() -> int:
return 0 return 0
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(network) self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self._rpc_host = coin_settings.get("rpchost", "127.0.0.1") self._rpc_host = coin_settings.get("rpchost", "127.0.0.1")
self._rpcport = coin_settings["rpcport"] self._rpcport = coin_settings["rpcport"]
self._rpcauth = coin_settings["rpcauth"] self._rpcauth = coin_settings["rpcauth"]
@@ -304,8 +312,6 @@ class BTCInterface(Secp256k1Interface):
self.setConfTarget(coin_settings["conf_target"]) self.setConfTarget(coin_settings["conf_target"])
self._use_segwit = coin_settings["use_segwit"] self._use_segwit = coin_settings["use_segwit"]
self._connection_type = coin_settings["connection_type"] self._connection_type = coin_settings["connection_type"]
self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
self._expect_seedid_hex = None self._expect_seedid_hex = None
self._altruistic = coin_settings.get("altruistic", True) self._altruistic = coin_settings.get("altruistic", True)
self._use_descriptors = coin_settings.get("use_descriptors", False) self._use_descriptors = coin_settings.get("use_descriptors", False)
+7 -2
View File
@@ -24,8 +24,13 @@ class DASHInterface(BTCInterface):
def coin_type(): def coin_type():
return Coins.DASH return Coins.DASH
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self._wallet_passphrase = "" self._wallet_passphrase = ""
self._have_checked_seed = False self._have_checked_seed = False
+8 -9
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2024 tecnovert # Copyright (c) 2024 tecnovert
# Copyright (c) 2024-2025 The Basicswap developers # Copyright (c) 2024-2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php. # file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@@ -20,9 +20,8 @@ from basicswap.chainparams import Coins
from basicswap.contrib.test_framework.script import ( from basicswap.contrib.test_framework.script import (
CScriptNum, CScriptNum,
) )
from basicswap.interface.base import ( from basicswap.interface.base import Secp256k1Interface
Secp256k1Interface, from basicswap.interface.utils import FeeValidator
)
from basicswap.interface.btc import ( from basicswap.interface.btc import (
extractScriptLockScriptValues, extractScriptLockScriptValues,
extractScriptLockRefundScriptValues, extractScriptLockRefundScriptValues,
@@ -181,7 +180,7 @@ def extract_sig_and_pk(sig_script: bytes) -> (bytes, bytes):
return sig, pk return sig, pk
class DCRInterface(Secp256k1Interface): class DCRInterface(FeeValidator, Secp256k1Interface):
@staticmethod @staticmethod
def coin_type(): def coin_type():
@@ -258,13 +257,13 @@ class DCRInterface(Secp256k1Interface):
def depth_spendable() -> int: def depth_spendable() -> int:
return 0 return 0
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(network) self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
super().__init__(coin_settings=coin_settings, network=network, **kwargs)
self._rpc_host = coin_settings.get("rpchost", "127.0.0.1") self._rpc_host = coin_settings.get("rpchost", "127.0.0.1")
self._rpcport = coin_settings["rpcport"] self._rpcport = coin_settings["rpcport"]
self._rpcauth = coin_settings["rpcauth"] self._rpcauth = coin_settings["rpcauth"]
self._sc = swap_client
self._log = self._sc.log if self._sc and self._sc.log else logging
self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host) self.rpc = make_rpc_func(self._rpcport, self._rpcauth, host=self._rpc_host)
if "walletrpcport" in coin_settings: if "walletrpcport" in coin_settings:
self._walletrpcport = coin_settings["walletrpcport"] self._walletrpcport = coin_settings["walletrpcport"]
+7 -2
View File
@@ -32,8 +32,13 @@ class DOGEInterface(BTCInterface):
def xmr_swap_b_lock_spend_tx_vsize() -> int: def xmr_swap_b_lock_spend_tx_vsize() -> int:
return 192 return 192
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(DOGEInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
def getScriptDest(self, script: bytearray) -> bytearray: def getScriptDest(self, script: bytearray) -> bytearray:
# P2SH # P2SH
+7 -2
View File
@@ -38,8 +38,13 @@ class FIROInterface(BTCInterface):
def coin_type(): def coin_type():
return Coins.FIRO return Coins.FIRO
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(FIROInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func( self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host self._rpcport, self._rpcauth, host=self._rpc_host
+14 -4
View File
@@ -16,8 +16,13 @@ class LTCInterface(BTCInterface):
def coin_type(): def coin_type():
return Coins.LTC return Coins.LTC
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(LTCInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb") self._rpc_wallet_mweb = coin_settings.get("mweb_wallet_name", "mweb")
self.rpc_wallet_mweb = make_rpc_func( self.rpc_wallet_mweb = make_rpc_func(
self._rpcport, self._rpcport,
@@ -265,8 +270,13 @@ class LTCInterfaceMWEB(LTCInterface):
def interface_type(self) -> int: def interface_type(self) -> int:
return Coins.LTC_MWEB return Coins.LTC_MWEB
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(LTCInterfaceMWEB, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb") self._rpc_wallet = coin_settings.get("mweb_wallet_name", "mweb")
self.rpc_wallet = make_rpc_func( self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet self._rpcport, self._rpcauth, host=self._rpc_host, wallet=self._rpc_wallet
+7 -2
View File
@@ -73,8 +73,13 @@ class NAVInterface(BTCInterface):
def txoType(): def txoType():
return CTxOut return CTxOut
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(NAVInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func( self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host self._rpcport, self._rpcauth, host=self._rpc_host
+11 -2
View File
@@ -81,8 +81,17 @@ class PARTInterface(BTCInterface):
def txoType(): def txoType():
return CTxOutPart return CTxOutPart
def __init__(self, coin_settings, network, swap_client=None): @staticmethod
super().__init__(coin_settings, network, swap_client) def defaultMaxFeeRate() -> int:
return PARTInterface.COIN() // 2
def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self.setAnonTxRingSize(int(coin_settings.get("anon_tx_ring_size", 12))) self.setAnonTxRingSize(int(coin_settings.get("anon_tx_ring_size", 12)))
def use_tx_vsize(self) -> bool: def use_tx_vsize(self) -> bool:
+7 -2
View File
@@ -10,8 +10,13 @@ from basicswap.contrib.test_framework.messages import CTxOut
class PassthroughBTCInterface(BTCInterface): class PassthroughBTCInterface(BTCInterface):
def __init__(self, coin_settings, network): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(coin_settings, network) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self.txoType = CTxOut self.txoType = CTxOut
self._network = network self._network = network
self.blocks_confirmed = coin_settings["blocks_confirmed"] self.blocks_confirmed = coin_settings["blocks_confirmed"]
+7 -2
View File
@@ -27,8 +27,13 @@ class PIVXInterface(BTCInterface):
def coin_type(): def coin_type():
return Coins.PIVX return Coins.PIVX
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super(PIVXInterface, self).__init__(coin_settings, network, swap_client) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
# No multiwallet support # No multiwallet support
self.rpc_wallet = make_rpc_func( self.rpc_wallet = make_rpc_func(
self._rpcport, self._rpcauth, host=self._rpc_host self._rpcport, self._rpcauth, host=self._rpc_host
+111
View File
@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2026 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
from basicswap.contrib.test_framework.messages import COIN
class FeeValidator:
@staticmethod
def defaultMaxFeeRate() -> int:
return COIN // 10
def makeIntFromSetting(
self, settings: dict, setting_name: str, default: int
) -> int:
# Return make_int(setting), or already integer default
if setting_name in settings:
return self.make_int(settings[setting_name])
return default
def __init__(self, **kwargs):
default_low_fee_conf_target: int = 24
default_low_fee_rate: int = 0
default_high_estimated_feerate_multiplier: float = 2.0
default_high_fee_rate: int = self.defaultMaxFeeRate()
if self._sc:
chain_client_settings = self._sc.getChainClientSettings(
self.coin_type()
) # basicswap.json
settings = self._sc.settings
default_low_fee_conf_target = int(
settings.get("low_fee_conf_target", default_low_fee_conf_target)
)
default_low_fee_rate = self.makeIntFromSetting(
settings, "low_feerate", default_low_fee_rate
)
default_high_estimated_feerate_multiplier = float(
settings.get(
"high_estimated_feerate_multiplier",
default_high_estimated_feerate_multiplier,
)
)
default_high_fee_rate = self.makeIntFromSetting(
settings, "high_feerate", default_high_fee_rate
)
else:
if kwargs.get("network") != "regtest":
raise ValueError("swapclient unset")
chain_client_settings = {}
self._low_fee_conf_target = int(
chain_client_settings.get(
"low_fee_conf_target", default_low_fee_conf_target
)
)
self._low_feerate = self.makeIntFromSetting(
chain_client_settings, "low_feerate", default_low_fee_rate
)
# Set below 1.0 to disable estimating the max feerate and use max_feerate
self._high_estimated_feerate_multiplier = float(
chain_client_settings.get(
"high_estimated_feerate_multiplier",
default_high_estimated_feerate_multiplier,
)
)
self._high_feerate = self.makeIntFromSetting(
chain_client_settings, "high_feerate", default_high_fee_rate
)
super().__init__(**kwargs)
def validateFeeRate(self, feerate: int) -> None:
if self._low_feerate > 0:
min_feerate_src = "set_value"
min_feerate = self._low_feerate
else:
min_feerate, min_feerate_src = self.get_fee_rate(self._low_fee_conf_target)
min_feerate = self.make_int(min_feerate)
if self._high_estimated_feerate_multiplier >= 1.0:
max_feerate, max_feerate_src = self.get_fee_rate()
max_feerate = (
self.make_int(max_feerate) * self._high_estimated_feerate_multiplier
)
else:
max_feerate_src = "set_value"
max_feerate = self._high_feerate
if max_feerate_src in ("estimatesmartfee", "electrum"):
if max_feerate > self._high_feerate:
max_feerate_src = "clamped_to_set_value"
max_feerate = self._high_feerate
self._log.debug(
f"Verify {self.ticker()} fee rate {feerate}, min {min_feerate} {min_feerate_src}, max {max_feerate} {max_feerate_src}"
)
if feerate < min_feerate:
err_msg: str = (
f"Fee rate too low, {feerate} < {min_feerate}, {min_feerate_src}"
)
self._log.error(err_msg)
raise ValueError(err_msg)
if feerate > max_feerate:
err_msg: str = (
f"Fee rate too high, {feerate} > {max_feerate}, {max_feerate_src}"
)
self._log.error(err_msg)
raise ValueError(err_msg)
+10 -2
View File
@@ -101,8 +101,13 @@ class XMRInterface(CoinInterface):
return True return True
return super().is_transient_error(ex) return super().is_transient_error(ex)
def __init__(self, coin_settings, network, swap_client=None): def __init__(self, coin_settings, network, swap_client=None, **kwargs):
super().__init__(network) super().__init__(
coin_settings=coin_settings,
network=network,
swap_client=swap_client,
**kwargs,
)
self._addr_prefix = self.chainparams_network()["address_prefix"] self._addr_prefix = self.chainparams_network()["address_prefix"]
@@ -857,3 +862,6 @@ class XMRInterface(CoinInterface):
except Exception as e: except Exception as e:
self._log.error(f"listWalletTransactions failed: {e}") self._log.error(f"listWalletTransactions failed: {e}")
return [] return []
def validateFeeRate(self, fee_rate: int) -> bool:
pass # Fee rate isn't used
+9 -1
View File
@@ -1,12 +1,20 @@
0.16.3 0.16.3
============== ==============
- Automatic fee validation.
- Prevent sending bids to offers
- Reject received offers, and
- Prevent sending offers where the chain feerates are out of range.
- Valid feerate range is the nodes estimated feerate for confirmation in 24 blocks to 2x the estimated feerate.
- The minimum feerate confirmation can be adjusted with the "low_fee_conf_target" setting.
- If "low_feerate" is set above 0 it is used instead of the dynamic feerate with "low_fee_conf_target"
- The maximum feerate multiplier can be adjusted with the "high_estimated_feerate_multiplier" setting.
- If "high_estimated_feerate_multiplier" is set below 1.0 the max feerate can be set with the "high_feerate" setting.
- New setting "startup_delay" - New setting "startup_delay"
- Adjusts the time waited for coin daemons to start between "startup_tries". - Adjusts the time waited for coin daemons to start between "startup_tries".
- Valid as a base setting and can be overridden per coin with chainclients settings. - Valid as a base setting and can be overridden per coin with chainclients settings.
0.14.5 0.14.5
============== ==============
+8
View File
@@ -48,3 +48,11 @@ allow-direct-references = true
[tool.ruff] [tool.ruff]
exclude = ["basicswap/contrib","basicswap/interface/contrib"] exclude = ["basicswap/contrib","basicswap/interface/contrib"]
[tool.codespell]
check-filenames = true
disable-colors = true
quiet-level = 7
dictionary = "tests/lint/spelling.extra_dictionary.txt,-"
ignore-words = "tests/lint/spelling.ignore-words.txt"
skip = ".git,.eggs,.tox,pgp,*.pyc,*basicswap/contrib,*basicswap/interface/contrib,*mnemonics.py,bin/install_certifi.py,*basicswap/static"
+1 -1
View File
@@ -741,7 +741,7 @@ class Test(BaseTest):
ci0 = cls.swap_clients[0].ci(cls.test_coin) ci0 = cls.swap_clients[0].ci(cls.test_coin)
if not cls.restore_instance: if not cls.restore_instance:
dcr_mining_addr = ci0.rpc_wallet("getnewaddress") dcr_mining_addr = ci0.rpc_wallet("getnewaddress")
assert dcr_mining_addr in cls.dcr_mining_addrs assert dcr_mining_addr == cls.dcr_mining_addr
cls.dcr_ticket_account = ci0.rpc_wallet( cls.dcr_ticket_account = ci0.rpc_wallet(
"getaccount", "getaccount",
[ [
+156
View File
@@ -2329,6 +2329,162 @@ class BasicSwapTest(TestFunctions):
"TODO" "TODO"
) # Build without xmr first for quicker test iterations ) # Build without xmr first for quicker test iterations
def test_11_fee_validation(self):
coin_from, coin_to = (self.test_coin_from, Coins.XMR)
logging.info(
f"---------- Test {coin_from.name} to {coin_to.name} expires bid stuck on accepted"
)
swap_clients = self.swap_clients
ci_from = swap_clients[0].ci(coin_from)
ci_to = swap_clients[0].ci(coin_to)
ci1_from = swap_clients[1].ci(coin_from)
ci1_to = swap_clients[1].ci(coin_to)
amt_swap = ci_from.make_int(random.uniform(0.1, 2.0), r=1)
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
offer_id = swap_clients[0].postOffer(
coin_from,
coin_to,
amt_swap,
rate_swap,
amt_swap,
SwapTypes.XMR_SWAP,
)
amt_swap_reverse = ci1_to.make_int(random.uniform(0.1, 2.0), r=1)
offer_reverse_id = swap_clients[1].postOffer(
coin_to,
coin_from,
amt_swap_reverse,
rate_swap,
amt_swap_reverse,
SwapTypes.XMR_SWAP,
)
ci_from_settings = swap_clients[0].getChainClientSettings(coin_from)
old_override_feerate = ci_from_settings.get("override_feerate", None)
ci1_from_settings = swap_clients[1].getChainClientSettings(coin_from)
old_override_feerate1 = ci1_from_settings.get("override_feerate", None)
networkinfo = ci_from.rpc("getnetworkinfo")
assert ci_from.make_int(networkinfo["relayfee"]) == ci_from.make_int(
ci_from.get_fee_rate()[0]
)
try:
# Set override_feerate to increase feerate from get_fee_rate()
ci_from_settings["override_feerate"] = ci_from.format_amount(120)
ci1_from_settings["override_feerate"] = ci1_from.format_amount(120)
try:
swap_clients[0].postXmrBid(offer_id, amt_swap)
except Exception as e:
assert "Fee rate too low, 100 < 120, override_feerate" in str(e)
else:
assert False, "Should fail"
# Test reverse bid, low fee
try:
swap_clients[1].postXmrBid(offer_reverse_id, amt_swap_reverse)
except Exception as e:
assert "Fee rate too low, 100 < 120, override_feerate" in str(e)
else:
assert False, "Should fail"
# Clear override_feerate (get_fee_rate()), set low_feerate (validateFeeRate())
ci_from_settings["override_feerate"] = None
ci1_from_settings["override_feerate"] = None
ci_from._low_feerate = 120
ci1_from._low_feerate = 120
try:
swap_clients[0].postOffer(
coin_from,
coin_to,
amt_swap,
rate_swap,
amt_swap,
SwapTypes.XMR_SWAP,
)
except Exception as e:
assert "Fee rate too low, 100 < 120, set_value" in str(e)
else:
assert False, "Should fail"
# Test reverse offer, low fee
try:
swap_clients[1].postOffer(
coin_to,
coin_from,
amt_swap_reverse,
rate_swap,
amt_swap_reverse,
SwapTypes.XMR_SWAP,
)
except Exception as e:
assert "Fee rate too low, 100 < 120, set_value" in str(e)
else:
assert False, "Should fail"
ci_from._low_feerate = 0
ci1_from._low_feerate = 0
ci_from._high_estimated_feerate_multiplier = (
0 # Disable high fee from estimate
)
ci_from._high_feerate = (
80 # ci_from_settings["high_feerate"] = ci_from.format_amount(80)
)
logging.info(f"[rm] ci_from.get_fee_rate() {ci_from.get_fee_rate()}")
try:
swap_clients[0].postXmrBid(offer_id, amt_swap)
except Exception as e:
assert "Fee rate too high, 100 > 80, set_value" in str(e)
else:
assert False, "Should fail"
# Test reverse bid, high fee
ci1_from._high_estimated_feerate_multiplier = 0
ci1_from._high_feerate = 80
try:
swap_clients[1].postXmrBid(offer_reverse_id, amt_swap_reverse)
except Exception as e:
assert "Fee rate too high, 100 > 80, set_value" in str(e)
else:
assert False, "Should fail"
try:
swap_clients[0].postOffer(
coin_from,
coin_to,
amt_swap,
rate_swap,
amt_swap,
SwapTypes.XMR_SWAP,
)
except Exception as e:
assert "Fee rate too high, 100 > 80, set_value" in str(e)
else:
assert False, "Should fail"
# Test reverse offer, high fee
try:
swap_clients[1].postOffer(
coin_to,
coin_from,
amt_swap_reverse,
rate_swap,
amt_swap_reverse,
SwapTypes.XMR_SWAP,
)
except Exception as e:
assert "Fee rate too high, 100 > 80, set_value" in str(e)
else:
assert False, "Should fail"
finally:
ci_from_settings["override_feerate"] = old_override_feerate
ci1_from_settings["override_feerate"] = old_override_feerate1
class TestBTC(BasicSwapTest): class TestBTC(BasicSwapTest):
__test__ = True __test__ = True
+1
View File
@@ -0,0 +1 @@
confimration->confirmation