From ec52acef56f7d3d43b051323970d8360be4da3ef Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Fri, 22 Nov 2024 12:23:13 +0100 Subject: [PATCH 1/6] Add argument to use single provider in Service classs --- bitcoinlib/services/services.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index 1fe2e514..c590b30d 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -55,7 +55,7 @@ class Service(object): def __init__(self, network=DEFAULT_NETWORK, min_providers=1, max_providers=1, providers=None, timeout=TIMEOUT_REQUESTS, cache_uri=None, ignore_priority=False, exclude_providers=None, - max_errors=SERVICE_MAX_ERRORS, strict=True, wallet_name=None): + max_errors=SERVICE_MAX_ERRORS, strict=True, wallet_name=None, provider_name=None): """ Create a service object for the specified network. By default, the object connect to 1 service provider, but you can specify a list of providers or a minimum or maximum number of providers. @@ -80,6 +80,8 @@ def __init__(self, network=DEFAULT_NETWORK, min_providers=1, max_providers=1, pr :type strict: bool :param wallet_name: Name of wallet if connecting to bitcoin node :type wallet_name: str + :param provider_name: Name of specific provider to connect to. Note this is different from the providers list argument: the lists mention a type of provider such as 'blockbook' or 'bcoin', the provider name is a key in providers.json list such as 'bcoin.testnet.localhost'. + :type provider_name: str """ @@ -111,11 +113,17 @@ def __init__(self, network=DEFAULT_NETWORK, min_providers=1, max_providers=1, pr raise ServiceError("Provider '%s' not found in provider definitions" % p) self.providers = {} - for p in self.providers_defined: - if (self.providers_defined[p]['network'] == network or self.providers_defined[p]['network'] == '') and \ - (not providers or self.providers_defined[p]['provider'] in providers): - self.providers.update({p: self.providers_defined[p]}) - exclude_providers_keys = {pi: self.providers[pi]['provider'] for pi in self.providers if self.providers[pi]['provider'] in exclude_providers}.keys() + if provider_name: + if provider_name not in self.providers_defined: + raise ServiceError("Provider with name '%s' not found in provider definitions" % provider_name) + self.providers.update({provider_name: self.providers_defined[provider_name]}) + else: + for p in self.providers_defined: + if (self.providers_defined[p]['network'] == network or self.providers_defined[p]['network'] == '') and \ + (not providers or self.providers_defined[p]['provider'] in providers): + self.providers.update({p: self.providers_defined[p]}) + exclude_providers_keys = {pi: self.providers[pi]['provider'] for + pi in self.providers if self.providers[pi]['provider'] in exclude_providers}.keys() for provider_key in exclude_providers_keys: del(self.providers[provider_key]) From e0daef5841205220699e05accedd7d1032c6a3fa Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Fri, 22 Nov 2024 12:44:46 +0100 Subject: [PATCH 2/6] Measure execution time and add results to Service class --- bitcoinlib/services/services.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index c590b30d..df47225c 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -150,6 +150,7 @@ def __init__(self, network=DEFAULT_NETWORK, min_providers=1, max_providers=1, pr self.results_cache_n = 0 self.ignore_priority = ignore_priority self.strict = strict + self.execution_time = None if self.min_providers > 1: self._blockcount = Service(network=network, cache_uri=cache_uri, providers=providers, exclude_providers=exclude_providers, timeout=timeout).blockcount() @@ -161,6 +162,7 @@ def _reset_results(self): self.errors = {} self.complete = None self.resultcount = 0 + self.execution_time = None def _provider_execute(self, method, *arguments): self._reset_results() @@ -191,7 +193,9 @@ def _provider_execute(self, method, *arguments): _logger.debug("API key needed for provider %s" % sp) continue providermethod = getattr(pc_instance, method) + start_time = datetime.now() res = providermethod(*arguments) + self.execution_time = (datetime.now() - start_time).total_seconds() * 1000 if res is False: # pragma: no cover self.errors.update( {sp: 'Received empty response'} From 9ecb5cf7e5e5c59a8c35a89352ad13fe28f27c13 Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Fri, 22 Nov 2024 12:48:18 +0100 Subject: [PATCH 3/6] Include all requests in execution time --- bitcoinlib/services/services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index df47225c..42e0709c 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -171,6 +171,7 @@ def _provider_execute(self, method, *arguments): if self.ignore_priority: random.shuffle(provider_lst) + start_time = datetime.now() for sp in provider_lst: if self.resultcount >= self.max_providers: break @@ -193,9 +194,7 @@ def _provider_execute(self, method, *arguments): _logger.debug("API key needed for provider %s" % sp) continue providermethod = getattr(pc_instance, method) - start_time = datetime.now() res = providermethod(*arguments) - self.execution_time = (datetime.now() - start_time).total_seconds() * 1000 if res is False: # pragma: no cover self.errors.update( {sp: 'Received empty response'} @@ -232,6 +231,7 @@ def _provider_execute(self, method, *arguments): if self.resultcount >= self.max_providers: break + self.execution_time = (datetime.now() - start_time).total_seconds() * 1000 if not self.resultcount: raise ServiceError("No successful response from any serviceprovider: %s" % list(self.providers.keys())) return list(self.results.values())[0] From 1b0b1a1fcb5609168def4aa2e15f0c0a2f4659b5 Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Sat, 23 Nov 2024 19:09:03 +0100 Subject: [PATCH 4/6] Avoid problems wrong blockcount at service providers --- bitcoinlib/services/services.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index 42e0709c..3a1dcab5 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -508,11 +508,18 @@ def blockcount(self): current_timestamp = time.time() if self._blockcount_update < current_timestamp - BLOCK_COUNT_CACHE_TIME: new_count = self._provider_execute('blockcount') - if not self._blockcount or (new_count and new_count > self._blockcount): + if last_cache_blockcount > new_count: + _logger.warning(f"New block count ({new_count}) is lower than block count in cache " + f"({last_cache_blockcount}). Will try to find provider consensus") + blockcounts = [last_cache_blockcount] + for _ in range(5): + blockcounts.append(self._provider_execute('blockcount')) + # return third last blockcount in list, assume last 2 and first 3 could be wrong + self._blockcount = sorted(blockcounts)[-2] + self._blockcount_update = current_timestamp + elif not self._blockcount or (new_count and new_count > self._blockcount): self._blockcount = new_count self._blockcount_update = current_timestamp - if last_cache_blockcount > self._blockcount: - return last_cache_blockcount # Store result in cache if len(self.results) and list(self.results.keys())[0] != 'caching': self.cache.store_blockcount(self._blockcount) From 4849acd5efb34c3510092718575ee26dd1552c6e Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Mon, 25 Nov 2024 15:12:51 +0100 Subject: [PATCH 5/6] Raise error when provider_name network is incompatible --- bitcoinlib/services/services.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index 3a1dcab5..97b30807 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -116,6 +116,8 @@ def __init__(self, network=DEFAULT_NETWORK, min_providers=1, max_providers=1, pr if provider_name: if provider_name not in self.providers_defined: raise ServiceError("Provider with name '%s' not found in provider definitions" % provider_name) + if self.providers_defined[provider_name]['network'] != self.network: + raise ServiceError("Network from provider '%s' is different than Service network" % provider_name) self.providers.update({provider_name: self.providers_defined[provider_name]}) else: for p in self.providers_defined: From 40130f9fba5cf691d44a928e6e95b8b50a311d4d Mon Sep 17 00:00:00 2001 From: Cryp Toon Date: Tue, 26 Nov 2024 15:56:27 +0100 Subject: [PATCH 6/6] Fix typos in Service class --- bitcoinlib/services/services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitcoinlib/services/services.py b/bitcoinlib/services/services.py index 97b30807..9bb7debb 100644 --- a/bitcoinlib/services/services.py +++ b/bitcoinlib/services/services.py @@ -614,7 +614,7 @@ def mempool(self, txid=''): """ Get list of all transaction IDs in the current mempool - A full list of transactions ID's will only be returned if a bcoin or bitcoind client is available. Otherwise + A full list of transactions ID's will only be returned if a bcoin or bitcoind client is available. Otherwise, specify the txid option to verify if a transaction is added to the mempool. :param txid: Check if transaction with this hash exists in memory pool @@ -626,7 +626,7 @@ def mempool(self, txid=''): def getcacheaddressinfo(self, address): """ - Get address information from cache. I.e. balance, number of transactions, number of utox's, etc + Get address information from cache. I.e. balance, number of transactions, number of utxo's, etc Cache will only be filled after all transactions for a specific address are retrieved (with gettransactions ie)