Ошибка с библиотекой blockcypher
Помогите, пожалуйста, с ошибкой. Вот код:
import blockcypher
class check():
# считываем данные из файла с текущими данными
with open(r'current_data.txt', mode='r', encoding='utf-8') as cur_fl:
# получим список всех строк
cur_data_lines = cur_fl.readlines()
# на основе списка создадим словарь ключ:значение
cur_data_dict = {
ln.split(';')[0]:ln.split(';')[1] # разделение строки по ';' - левая часть в ключ, правая в значение
for ln in cur_data_lines
}
# то же самое, но для файла с предыдущими значениями
with open(r'prev_data.txt', mode='r', encoding='utf-8') as prev_fl:
prev_data_lines = prev_fl.readlines()
prev_data_dict = {ln.split(';')[0]:ln.split(';')[1] for ln in prev_data_lines}
# обход по циклу всех пар ключ:значение из файла с текущими данными
for k, v in cur_data_dict.items():
# если ключ есть в предыдущем файле и значение не равно предыдущему
if (k in prev_data_dict.keys()) and (v != prev_data_dict[k]):
balance = blockcypher.get_total_balance(k)
if k[0:2] == '0x':
balance_in_eth = balance / 2864966.76
# prev_balance_in_eth = blockcypher.from_satoshis(prev_balance, 'eth')
# telegram_bot.send_message(f'Изменение количества транзакций в адресе {k}. Значение изменилось на {v}.'+"\n"+'Текущий баланс ' + balance_in_eth + ' ETH')
print("---ETH")
print(k)
print(v)
print(balance_in_btc)
else:
balance_in_btc = balance / 100000000
# prev_balance_in_btc = blockcypher.from_satoshis(prev_balance, 'btc')
# telegram_bot.send_message(f'Изменение количества транзакций в адресе {k}. Значение изменилось на {v}.'+"\n"+'Текущий баланс ' + balance_in_btc + ' BTC')
print("---BTC")
print(k)
print(v)
print(balance_in_btc)
# переносим значения текущего файла в файл с предыдущими данными
# открывает файл с предыдущими данными на запись
with open(r'prev_data.txt', mode='w', encoding='utf-8') as prev_fl:
# открываем файл с текущими данными на чтение
with open(r'current_data.txt', mode='r', encoding='utf-8') as cur_fl:
# записываем в файл с предыдущими файлами то, что прочли из файла с текущими
prev_fl.write(cur_fl.read())
Вот ошибка:
Traceback (most recent call last):
File "C:/Users/user/Desktop/task/storage.py", line 22, in <module>
class check():
File "C:/Users/user/Desktop/task/storage.py", line 46, in check
balance = blockcypher.get_total_balance(k)
File "D:\Other\pycharm\venv\lib\site-packages\blockcypher\api.py", line 310, in get_total_balance
return get_address_overview(address=address,
File "D:\Other\pycharm\venv\lib\site-packages\blockcypher\api.py", line 292, in get_address_overview
assert is_valid_address_for_coinsymbol(b58_address=address,
AssertionError
Потом добавил try/except вот так:
for k, v in cur_data_dict.items():
# если ключ есть в предыдущем файле и значение не равно предыдущему
if (k in prev_data_dict.keys()) and (v != prev_data_dict[k]):
try:
balance = blockcypher.get_total_balance(k)
except:
print('error_in_get_total_balance')
if k[0:2] == '0x':
balance_in_eth = balance / 2864966.76
try:
print("---ETH")
print(k)
print(v)
print(balance_in_eth)
except:
print('error_in_print')
else:
balance_in_btc = balance / 100000000
try:
print("---BTC")
print(k)
print(v)
print(balance_in_btc)
except:
print('error_in_print')
# переносим значения текущего файла в файл с предыдущими данными
# открывает файл с предыдущими данными на запись
with open(r'prev_data.txt', mode='w', encoding='utf-8') as prev_fl:
# открываем файл с текущими данными на чтение
with open(r'current_data.txt', mode='r', encoding='utf-8') as cur_fl:
# записываем в файл с предыдущими файлами то, что прочли из файла с текущими
prev_fl.write(cur_fl.read())
Ответ такой:
D:\Other\pycharm\venv\Scripts\python.exe C:/Users/user/Desktop/task/storage.py
---BTC
14CaBzRbZDuGJCAdu9CZsbrCfyv4K7f7sf
31
0.0
error_in_get_total_balance
---ETH
0xd4FD252d7D2C9479a8d616F510eAC6243B5DDdf9
8
0.0
error_in_get_total_balance
---BTC
8Hx9uDuDO98idDfCAdu9CZsbrCfyv4K7f7sf
12
0.0
Process finished with exit code 0
Ответы (1 шт):
Посмотрел в код blockcypher-python и могу объяснить, что пошло не так в:
balance = blockcypher.get_total_balance(k)
Вот описание get_total_balance:
def get_total_balance(address, coin_symbol='btc', api_key=None):
return get_address_overview(address=address, coin_symbol=coin_symbol)['final_balance']
Вызов get_address_overview ведет в:
def get_address_overview(address, coin_symbol='btc', api_key=None):
assert is_valid_address_for_coinsymbol(b58_address=address, coin_symbol=coin_symbol)
...
А там уже происходит проверка в is_valid_address_for_coinsymbol, которую ваши данные не прошли:
def is_valid_address_for_coinsymbol(b58_address, coin_symbol):
assert is_valid_coin_symbol(coin_symbol)
if b58_address.startswith(COIN_SYMBOL_MAPPINGS[coin_symbol]['bech32_prefix']):
return True
if coin_symbol == 'eth':
return is_valid_eth_address(b58_address)
if b58_address[0] in COIN_SYMBOL_MAPPINGS[coin_symbol]['address_first_char_list']:
if is_valid_address(b58_address):
return True
return False
Теперь подробнее:
Первая проверка с is_valid_coin_symbol должна быть успешной, т.к. вы не передавали свой coin_symbol, а значение btc должно успешно пройти проверку, т.к. в COIN_SYMBOL_SET есть значения btc, eth, ...:
def is_valid_coin_symbol(coin_symbol):
return coin_symbol in COIN_SYMBOL_SET
Тут видно использование словаря COIN_SYMBOL_MAPPINGS, у него ключом является coin_symbol (значения btc, eth, ...), а значением словарь из значений другого словаря COIN_SYMBOL_ODICT_LIST. Поэтому, при вызове COIN_SYMBOL_MAPPINGS[coin_symbol]['bech32_prefix'] получится значение bc1, а b58_address.startswith('bc1') провалит проверку, т.к. у вас в b58_address = "14CaBzRbZDuGJCAdu9CZsbrCfyv4K7f7sf".
Следующая проверка is_valid_eth_address не наступит, т.к. coin_symbol у вас всегда btc. Но если бы для eth вы бы заполнили coin_symbol, то проверка не прошла бы успешно.
Смотрите, у вас там значение адреса 0xd4FD252d7D2C9479a8d616F510eAC6243B5DDdf9, и оно пройдет проверку длины (без 0x как раз 40 символов), но завалит проверку uses_only_hash_chars, т.к. в ней учитываются только символы в нижнем регистре, а в у вас есть в верхнем (по мне это чушь, нужно было сделать проверку регистронезависимой, скорее всего это бага в библиотеки, можно создать в библиотеке issue). Поэтому, я бы рекомендовал адрес для eth приводить в нижний регистр перед тем как вызывать get_total_balance.
Код проверок:
HEX_CHARS_RE = re.compile('^[0-9a-f]*$')
def uses_only_hash_chars(string):
return bool(HEX_CHARS_RE.match(string))
def is_valid_eth_address(addr):
if addr.startswith('0x'):
addr = addr[2:].strip()
if len(addr) != 40:
return False
return uses_only_hash_chars(addr)
И последняя проверка:
if b58_address[0] in COIN_SYMBOL_MAPPINGS[coin_symbol]['address_first_char_list']:
if is_valid_address(b58_address):
return True
Давайте посмотрим значения из COIN_SYMBOL_MAPPINGS[coin_symbol]['address_first_char_list'] для coin_symbol:
btc:('1', '3', '4', 'b')eth:('0')У вас дляbtcзначение адреса14CaBzRbZDuGJCAdu9CZsbrCfyv4K7f7sf, а первый символ1, что проходит проверку (ethпри заполненномcoin_symbolтоже бы прошел проверку, т.к. начинается на0x)
Значит проверка дойдет до is_valid_address:
def is_valid_address(b58_address):
if b58_address.startswith('bc1') or b58_address.startswith('ltc1') or b58_address.startswith('tltc1') or b58_address.startswith('tb1'):
return True
try:
return crypto_address_valid(b58_address)
except:
return False
Сразу видно, что адрес проверку с .startswith( код не пройдет, значит будет проверка в crypto_address_valid:
DIGITS58 = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
def decode_base58(bc, length):
n = 0
for char in bc:
n = n * 58 + DIGITS58.index(char)
try:
return n.to_bytes(length, 'big')
except AttributeError:
return _long_to_bytes(n, length, 'big')
def crypto_address_valid(bc):
bcbytes = decode_base58(bc, 25)
return bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4]
Использовал адрес btc с b58_address = '14CaBzRbZDuGJCAdu9CZsbrCfyv4K7f7sf' с crypto_address_valid, получил успешный результат и это логично, по вашим логам у того адреса проблем не было:
b58_address = '14CaBzRbZDuGJCAdu9CZsbrCfyv4K7f7sf'
bcbytes = decode_base58(b58_address, 25)
print(bcbytes)
# b'\x00#\x18\xa8z[.\xb3\x19;"T\xe3Lo\x0f.\x9fw\xda\xbf\x9e\xd9\xf7"'
print(bcbytes[-4:], sha256(sha256(bcbytes[:-4]).digest()).digest()[:4])
# b'\x9e\xd9\xf7"' b'\x9e\xd9\xf7"'
print(bcbytes[-4:] == sha256(sha256(bcbytes[:-4]).digest()).digest()[:4])
# True
Теперь используем адрес, что проверку не прошел:
b58_address = '8Hx9uDuDO98idDfCAdu9CZsbrCfyv4K7f7sf'
bcbytes = decode_base58(b58_address, 25)
# n = n * 58 + DIGITS58.index(char)
# ValueError: substring not found
А причина в символе O, которого нет среди DIGITS58, возможно это опечатка, т.к. в DIGITS58 есть o, да и в принципе, там похоже каждому латинскому символу предоставляется две формы: в верхнем регистре и нижнем. Предположительно, это бага в этой библиотеки (можно попробовать в вашем окружении зайти в файл DIGITS58 и добавить туда O, чтобы проверить, и если все ок, то создать issue в репозитории библиотеки).
Исключение ValueError будет поймано в is_valid_address и это сразу приведет к return False.
Поэтому адрес 8Hx9uDuDO98idDfCAdu9CZsbrCfyv4K7f7sf не прошел проверку.
PS.
Аналогично вы сами можете проверить ошибки для других адресов.
И, конечно, для адресов etc нужно передавать соответствующее значение в get_total_balance:
balance = blockcypher.get_total_balance(k, coin_symbol='etc')