mirror of
https://github.com/basicswap/basicswap.git
synced 2026-06-08 04:01:41 +02:00
@@ -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: |
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
confimration->confirmation
|
||||||
Reference in New Issue
Block a user