From 18222796d2abda19b9ab60e0e5c22589c473228d Mon Sep 17 00:00:00 2001 From: Kristaps Kaupe Date: Sun, 3 Dec 2023 22:34:27 +0200 Subject: [PATCH] Warn user if higher priority confirmation target than requested is provided by blockchain source --- src/jmclient/blockchaininterface.py | 29 ++++++++++++++++++++--------- test/jmclient/commontest.py | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/jmclient/blockchaininterface.py b/src/jmclient/blockchaininterface.py index 6409b3e..663dce8 100644 --- a/src/jmclient/blockchaininterface.py +++ b/src/jmclient/blockchaininterface.py @@ -183,11 +183,12 @@ class BlockchainInterface(ABC): """ @abstractmethod - def _estimate_fee_basic(self, conf_target: int) -> Optional[int]: + def _estimate_fee_basic(self, + conf_target: int) -> Optional[Tuple[int, int]]: """Returns basic fee estimation for confirmation target in blocks. Additional JoinMarket fee logic is added on top, see - `estimate_fee_per_kb` for details. Returns feerate in sats per vB - or None in case of error. + `estimate_fee_per_kb` for details. Returns tuple of feerate in sats + per kvB and actual used conf_target or None in case of error. """ def yield_transactions(self) -> Generator[dict, None, None]: @@ -292,9 +293,18 @@ class BlockchainInterface(ABC): btc.fee_per_kb_to_str(fallback_fee_randomized) + ".") return int(fallback_fee_randomized) - retval = random.uniform(retval, retval * float(1 + tx_fees_factor)) + feerate, blocks = retval + # 1 block difference is tolerated with intent, Core will often return + # 2 block target for `estimatesmartfee 1`. + if tx_fees - blocks > 1: + log.warning( + f"Fee estimation for {tx_fees} block confirmation target " + f"was requested, but {blocks} block target was provided by " + "blockchain source. Tx fee may be higher then expected.") + + feerate = random.uniform(feerate, feerate * float(1 + tx_fees_factor)) - if retval < mempoolminfee_in_sat: + if feerate < mempoolminfee_in_sat: msg = "Using this mempool min fee as tx feerate" if tx_fees_factor != 0: msg = msg + " (randomized for privacy)" @@ -306,8 +316,8 @@ class BlockchainInterface(ABC): " block confirmation target" if tx_fees_factor != 0: msg = msg + " (randomized for privacy)" - log.info(msg + ": " + btc.fee_per_kb_to_str(retval)) - return int(retval) + log.info(msg + ": " + btc.fee_per_kb_to_str(feerate)) + return int(feerate) def core_proof_to_merkle_branch(self, core_proof: str) -> bytes: core_proof = binascii.unhexlify(core_proof) @@ -577,7 +587,8 @@ class BitcoinCoreInterface(BlockchainInterface): rpc_result = self._getmempoolinfo() return 'fullrbf' in rpc_result and rpc_result['fullrbf'] - def _estimate_fee_basic(self, conf_target: int) -> Optional[int]: + def _estimate_fee_basic(self, + conf_target: int) -> Optional[Tuple[int, int]]: # Special bitcoin core case: sometimes the highest priority # cannot be estimated in that case the 2nd highest priority # should be used instead of falling back to hardcoded values @@ -593,7 +604,7 @@ class BitcoinCoreInterface(BlockchainInterface): # if it is not able to make an estimate. We insist that # the 'feerate' key is found and contains a positive value: if estimate and estimate > 0: - return btc.btc_to_sat(estimate) + return (btc.btc_to_sat(estimate), rpc_result.get('blocks')) # cannot get a valid estimate after `tries` tries: log.warn("Could not source a fee estimate from Core") return None diff --git a/test/jmclient/commontest.py b/test/jmclient/commontest.py index ededb2d..c383e75 100644 --- a/test/jmclient/commontest.py +++ b/test/jmclient/commontest.py @@ -84,7 +84,7 @@ class DummyBlockchainInterface(BlockchainInterface): pass def _get_mempool_min_fee(self) -> Optional[int]: pass - def _estimate_fee_basic(self, conf_target: int) -> Optional[int]: + def _estimate_fee_basic(self, conf_target: int) -> Optional[Tuple[int, int]]: pass def get_wallet_rescan_status(self) -> Tuple[bool, Optional[Decimal]]: pass