Прогрев аккаунтов в EVM-сетях - self-транзакции и транзакции на случайные адреса

Содержание:

Что делает этот скрипт

Идея скрипта @S7miles. Сообщества https://t.me/sybilders и https://t.me/shitbilders. Купить готовый софт http://t.me/sybilders_bot.
Для работы используются:
  • txt файл с приватными ключами от кошельков, для которых будут осуществляться транзакции,
  • модуль chain_settings.py в котором задаются настройки для сетей, в которых будут осуществляться транзакции.
Используются два вида транзакций:
  • SELF - отправка идет на свой адрес,
  • RANDOM - генерируется случайный адрес, на него отправляется некоторое количество монет.

Для каждого кошелька из списка берется сеть, определяется рандомное количество транзакций, в рамках этого количества рандомно выбирается вид транзакции. Например, в сети BSC будет совершено 12 транзакций, из них 3 будет SELF, а 9 - RANDOM и т.п. После каждой транзакции процесс засыпает на время, которое определяется рандомно.

После завершения всех транзакций для всех адресов, экспортируется отчет в csv.

Синхронная версия скрипта

Все инструкции выполняются по очереди.

Как использовать код из статьи

Как запускать файлы .py и .ipynb написано в инструкции.

Скачайте репозиторий с Гитхаба. В нем будет:

  • chain_settings.py - файл с настройками блокчейнов, в которых будет прогрев,
  • config.py - файл с настройками для работы скрипта,
  • functions.py - модуль с функциями,
  • main.py - главная функция для запуска, точка входа,
  • private_keys.txt - текстовый файл с приватными ключами для примера,
  • report_table.csv - таблица с отчетом о работе скрипта для примера.
Весь код этой статьи (синхронная и асинхронная версии) продублирован в файле web3-warm-multi-accounts-send-random-transactions.ipynb.

Модуль chain_settings.py

В файле в виде словарей перечислены сети, в которых будут совершаться транзакции.

Настройки сетей:

  • name - название сети, использует для вывода в консоль и в отчете
  • url - используется для подключения

  • min_amount_for_sending - минимальная сумма для отправки
  • max_amount_for_sending - максимальная сумма для отправки
Из диапазона этих чисел будет определено случайное число для отправки.

  • min_transactions - минимальное количество транзакций, которое должно быть совершено в сети
  • max_transactions - максимальное
Из диапазона этих чисел будет определено случайное количество транзакций.

  • explorer_url - url эксплорера, к которому будет подставлен хэш транзакции
Можно убрать параметры, если они избыточны, или добавить новые.

Примеры настроек

polygon = {
            'name''Polygon',
            'url':'https://polygon.llamarpc.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':5,
            'max_transactions':10,
            'explorer_url':'https://polygonscan.com/tx/'
            }
bsc = {
            'name''Binance Smart Chain',
            'url':'https://bsc.publicnode.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':12,
            'max_transactions':24,
            'explorer_url':'https://bscscan.com/tx/'
            }
avalanche = {
            'name''Avalanche',
            'url':'https://avalanche-c-chain.publicnode.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':5,
            'max_transactions':10,
            'explorer_url':'https://snowtrace.io/tx/'
            }
После того, как вы задали пресеты для сетей, в список __all__ передайте названия сетей (словари), которые будут использоваться для отправки.
__all__ = ['polygon', 'bsc', 'avalanche']

Config.py

Название файла с приватниками:
TXT_WITH_PRIVATE_KEYS = 'private_keys.txt'
Название для таблицы с отчетом, которая будет создана:
REPORT_TABLE_NAME = 'report_table.csv'
Количество вариаций, которое будет в диапазоне между min_amount_for_sending и max_amount_for_sending - в настройках chain_settings.py. Из всех вариантов будет рандомно выбрана сумма для отправки:
NUMBER_OF_VARIATION_BETWEEN_VALUES = 50
Минимальная задержка между транзакциями:
MIN_TIMESLEEP_BETWEEN_TRANSACTIONS = 100
Максимальная задержка между транзакциями:
MAX_TIMESLEEP_BETWEEN_TRANSACTIONS = 250
Из этого диапазона будет выбрано рандомное значение, на которое скрипт остановится перед тем, как сделать новую транзакцию. Ставьте значения побольше, чтобы скрипт не делал несколько транзакций за минуту.

Модуль functions.py

Библиотеки

import time
import random
from datetime import datetime
 
import numpy as np
import pandas as pd
 
from web3 import Web3
 
from config import REPORT_TABLE_NAME, NUMBER_OF_VARIATIONS_BETWEEN_SENDING_AMOUNT, MIN_TIMESLEEP_BETWEEN_TRANSACTIONS, \
    MAX_TIMESLEEP_BETWEEN_TRANSACTIONS
import chain_settings
 
  • time - для задержек
  • random - для рандомизации
  • datetime - дата, которая используется в отчете
  • pandas - инструмент для работы с таблицами, используется для загрузки/создания таблицы с отчетом, для записи данных
  • numpy - используется для создания диапазона из любых видов чисел - целых или десятичных
  • web3 - подключение к rpc, создание, подпись и отправка транзакций
  • config - из файла явно импортируются переменные
  • chain_settings - модуль с настройками чейнов, в которых будут совершаться транзы

Создание отчета

Для отчетов создается пустой лист:
global_list_for_report = list()
Функция создание отчета
Принимает название чейна, публичный адрес кошелька, тип транзакции, статус транзакции, количество отправленных монет, url для просмотра транзакции, ошибку. Все записывается в словарь, затем добавляется в список global_list_for_report:
def save_report_to_global_list(chain_name, wallet_address,
                               transaction_type, transaction_status,
                               amount, transaction_explorer_url, error):
    d = dict()
    d['chain_name'] = chain_name
    d['wallet_address'] = wallet_address
    d['transaction_type'] = transaction_type
    d['transaction_status'] = transaction_status
    d['sent_amount'] = amount
    d['transaction_url'] = transaction_explorer_url
    d['date'] = datetime.now().strftime('%d-%m-%Y')
    d['error'] = error
 
    global global_list_for_report
    global_list_for_report.append(d)
Функция для экспорта отчета в csv
Из списка со словарями global_list_for_report создается датафрейм и экспортируется в csv. Возможно не эргономично использовать мощный pandas для такой задачи, но код очень удобный:)
def export_report():
    global global_list_for_report
    df = pd.DataFrame.from_dict(global_list_for_report)
    df.to_csv(REPORT_TABLE_NAME, index=False)

Рандомный выбор значений

Функция, которая возвращает рандомное значение из диапазона:
def create_random_value_from_range(min_value, max_value):
    random_value_between_range = random.choice(range(min_value, max_value))
 
    return random_value_between_range
Рандомный выбор количества монет для отправки
Принимает минимальное и максимальное значение из настроек чейнов и количество вариаций. Для этой задачи используется numpy linspace, который создает диапазон из чисел с плавающей точкой, чтобы можно было отправлять маленькие суммы:
def create_random_amount_for_sending(min_value_for_sending, max_value_for_sending,
                                     number_of_variations_between_sending_amount):
    random_position = random.choice(range(number_of_variations_between_sending_amount))
 
    random_amount_for_sending = np.linspace(min_value_for_sending,
                                            max_value_for_sending,
                                            num=number_of_variations_between_sending_amount)[random_position]
 
    return random_amount_for_sending
numpy linspace возвращает количество значений в диапазоне:
Из диапазона, который равен количеству вариаций выбирается число:
Которое используется в качестве случайного индекса для выбора числа из numpy linspace:
Такое количество монет будет использовано в транзакции.

Транзакции

Настройка транзакции
Принимает подключение, адрес отправителя, адрес получателя, количество монет для отправки.
В amount_wei конвертируется отправляемое значение из ether в wei.
В gas_price определяется цена газа, в estimated_gas оценка лимита газа для отправки крипты.
В nonce определяется номер транзакции.
В словаре transaction записываются все параметры будущей транзакции.
Функция возвращает словарь transaction.
def transaction_settings(web3, from_address, to_address, amount):
    amount_wei = web3.to_wei(amount, 'ether')
 
    gas_price = web3.eth.gas_price
 
    estimated_gas = web3.eth.estimate_gas({
        'to': to_address,
        'value': amount_wei,
    })
 
    nonce = web3.eth.get_transaction_count(from_address)
 
    transaction = {
        'chainId': web3.eth.chain_id,
        'from': from_address,
        'to': to_address,
        'value': amount_wei,
        'nonce': nonce,
        'gasPrice': gas_price,
        'gas': estimated_gas,
    }
 
    return transaction
Подпись и отправка транзакции
Принимает подключение, адрес отправителя, адрес получателя, количество монет для отправки, приватный ключ.
Настройки будущей транзы записываются в create_transaction.
В signed_transaction передаются настройки и приватный ключ, транзакция подписывается.
В transaction_hash записывается хэш отправленной транзакции.
Функция возвращает хэш.
def create_sign_send_transaction(web3, from_address, to_address, amount, private_key):
    create_transaction = transaction_settings(
        web3=web3,
        from_address=from_address,
        to_address=to_address,
        amount=amount
    )
 
    signed_transaction = web3.eth.account.sign_transaction(create_transaction, private_key)
 
    transaction_hash = web3.eth.send_raw_transaction(signed_transaction.rawTransaction)
 
    return transaction_hash.hex()
SELF транзакция
  • Принимает подключение, количество монет для отправки, приватник, минимальную и максимальную задержку между транзакциями, название чейна и url эксплорера.
  • Перед отправкой в timesleep_value генерируется рандомное число из небольшого диапазона, скрипт останавливается на это время.
  • В my_address записывается публичный адрес, который определяется из приватного ключа.
  • В create_sign_send_transaction осуществляется отправка, в качестве отправителя и получателя передается my_address. Хэш записывается в transaction_hash.
  • В save_report_to_global_list записывается отчет, в функцию передается название сети, публичный адрес, тип транзакции, статус SUCCESS, сумма, url, который получается в результате подстановки параметра из chain_settings.py и хэша транзы, ошибки нет, поэтому передается прочерк.
  • В timesleep_value генерируется рандомное число из переданных в функцию значений. Скрипт засыпает.
  • Если срабатывает блок except, в отчет записывается название сети, публичный адрес, тип транзакции, статус ERROR, сумма, вместо url передается прочерк, код ошибки.
def self_transaction(web3, amount, private_key,
                     min_timesleep_between_transactions,
                     max_timesleep_between_transactions,
                     chain_name, explorer_url):
 
    try:
        timesleep_value = create_random_value_from_range(5, 15)
        time.sleep(timesleep_value)
 
        transaction_type = 'SELF-transaction'
        print(f'Key {private_key[:6]}...{private_key[-6:]}, {transaction_type} {chain_name}')
 
        my_address = web3.eth.account.from_key(private_key).address
 
        transaction_hash = create_sign_send_transaction(web3, my_address, my_address,
                                                        amount, private_key)
 
        print(f'Hash {transaction_type} {chain_name} {transaction_hash}')
 
        print(f'Recording data in a report')
        save_report_to_global_list(chain_name, my_address,
                                   transaction_type, 'SUCCESS',
                                   amount, f'{explorer_url}{transaction_hash}', '-')
 
        timesleep_value = create_random_value_from_range(min_timesleep_between_transactions,
                                                         max_timesleep_between_transactions)
        print(f'Pause {timesleep_value} sec. after {transaction_type} {chain_name} ', '\n')
        time.sleep(timesleep_value)
 
    except Exception as error:
        print(f'{transaction_type} {chain_name}, error -  {error}', '\n')
        save_report_to_global_list(chain_name, my_address,
                                   transaction_type, 'ERROR',
                                   amount, '-', error)
RANDOM транзакция
Почти такая же функция как self_transaction, отличается наличием кода на 12-16 строках.
  • Принимает подключение, количество монет для отправки, приватник, минимальную и максимальную задержку между транзакциями, название чейна и url эксплорера.
  • Перед отправкой в timesleep_value генерируется рандомное число из небольшого диапазона, скрипт останавливается на это время.
  • В account создается новый аккаунт. В random_wallet записывается публичный адрес созданного аккаунта, на который будет осуществляться отправка.
  • В my_address записывается публичный адрес, который определяется из приватного ключа.
  • В prepared_wallet записывается созданный публичный адрес, приведенный к корректному виду с помощью to_checksum_address.
  • В create_sign_send_transaction осуществляется отправка, в качестве отправителя и получателя передается my_address. Хэш записывается в transaction_hash.
  • В save_report_to_global_list записывается отчет, в функцию передается название сети, публичный адрес, тип транзакции, статус SUCCESS, сумма, url, который получается в результате подстановки параметра из chain_settings.py и хэша транзы, ошибки нет, поэтому передается прочерк.
  • В timesleep_value генерируется рандомное число из переданных в функцию значений. Скрипт засыпает.
  • Если срабатывает блок except, в отчет записывается название сети, публичный адрес, тип транзакции, статус ERROR, сумма, вместо url передается прочерк, код ошибки.
def random_transaction(web3, amount, private_key,
                       min_timesleep_between_transactions,
                       max_timesleep_between_transactions,
                       chain_name, explorer_url):
    try:
        timesleep_value = create_random_value_from_range(5, 15)
        time.sleep(timesleep_value)
 
        transaction_type = 'RANDOM-transaction'
        print(f'Key {private_key[:6]}...{private_key[-6:]}, {transaction_type} {chain_name}', '\n')
 
        account = web3.eth.account.create()
        random_wallet = account.address
 
        my_address = web3.eth.account.from_key(private_key).address
        prepared_wallet = web3.to_checksum_address(random_wallet)
 
        transaction_hash = create_sign_send_transaction(web3, my_address, prepared_wallet,
                                                        amount, private_key)
 
        print(f'Hash {transaction_type} {chain_name} {transaction_hash}' )
 
        print(f'Recording data in a report')
        save_report_to_global_list(chain_name, my_address,
                                   transaction_type, 'SUCCESS',
                                   amount, f'{explorer_url}{transaction_hash}', '-')
 
        timesleep_value = create_random_value_from_range(min_timesleep_between_transactions,
                                                         max_timesleep_between_transactions)
        print(f'Pause {timesleep_value} sec. after {transaction_type} {chain_name} ', '\n')
        time.sleep(timesleep_value)
 
    except Exception as error:
        print(f'{transaction_type} {chain_name}, error -  {error}', '\n')
        save_report_to_global_list(chain_name, my_address,
                                   transaction_type, 'ERROR',
                                   amount, '-', error)
Главная функция
  • Принимает приватник.
  • Запускается цикл, который перебирает словари из chain_settigs.py, указанные в __all__
  • В переменную web3 записывается объект созданного подключения. Если подключение False (if not web3.is_connected()), процесс останавливается. Записывается в отчет. Для отчета в этой части кода генерируется публичный адрес my_address из приватника.
  • В transactions_quantity записывается рандомное количество транзакций, которое будет совершено в сети. Число генерируется из диапазона, указанного в настройках в chain_settigs.py.
  • Запускается цикл по количеству транзакций в значении transactions_quantity.
  • В amount определяется случайная сумма к отправке, генерируется функцией create_random_amount_for_sending, которая берет диапазон из настроек chain_settigs.py и количество вариаций внутри этого диапазона NUMBER_OF_VARIATIONS_BETWEEN_SENDING_AMOUNT из файла config.py.
  • На каждой итерации цикла выбирается SELF или RANDOM транзакция.
  • Если срабатывает блок except, записывается отчет с пустыми параметрами (прочерками), с приватным ключом и сообщением, что возникла системная ошибка.
def main_function_make_random_or_self_txns(private_key):
    try:
        for chain in map(chain_settings.__dict__.get, chain_settings.__all__):
            chain_name = chain['name']
 
            print(f'Process has been launched for {private_key[:6]}...{private_key[-6:]} on the network {chain_name}')
 
            web3 = Web3(Web3.HTTPProvider(chain['url']))
 
            if not web3.is_connected():
                print(f'Connection to {chain_name} is not established', '\n')
 
                my_address = web3.eth.account.from_key(private_key).address
                save_report_to_global_list(chain_name, my_address,
                                           '-', 'ERROR',
                                           '-', '-',
                                           'Connection is not established')
                time.sleep(5)
                continue
 
            print(f'Connection to {chain_name} was established', '\n')
 
            transactions_quantity = create_random_value_from_range(chain['min_transactions'],
                                                                   chain['max_transactions'])
 
            for _ in range(transactions_quantity):
                amount = create_random_amount_for_sending(chain['min_amount_for_sending'],
                                                          chain['max_amount_for_sending'],
                                                          NUMBER_OF_VARIATIONS_BETWEEN_SENDING_AMOUNT)
 
                random.choice([self_transaction, random_transaction])(web3, amount, private_key,
                                                                      MIN_TIMESLEEP_BETWEEN_TRANSACTIONS,
                                                                      MAX_TIMESLEEP_BETWEEN_TRANSACTIONS,
                                                                      chain_name, chain['explorer_url'])
 
    except Exception as error:
        print(error, '\n')
        save_report_to_global_list('-', private_key,
                                   '-', 'ERROR',
                                   '-', '-',
                                   'Some system error occurred during the process.')

Модуль main.py

Из модуля с functions импортируется главная функция и функция для создания отчета. Из файла config импортируется переменная с названием txt файла с приватными ключами:
from functions import main_function_make_random_or_self_txns, export_report
from config import TXT_WITH_PRIVATE_KEYS
В главной функции в список list_of_private_keys добавляются все приватные ключи из txt-файла. В цикле каждый ключ передается в функцию main_function_make_random_or_self_txns, в которой выполняется весь процесс:
def main():
    with open(TXT_WITH_PRIVATE_KEYS) as file:
        list_of_private_keys = [line.strip() for line in file.readlines()]
 
    for private_key in list_of_private_keys:
        main_function_make_random_or_self_txns(private_key)
 
    export_report()
Точка входа:
if __name__ == '__main__':
    main()
Процесс в терминале:
Отчет в csv:
В синхронном варианте скрипта все инструкции выполнились для каждого аккаунта по очереди.
В отчете видно, что в одной транзакции возникла ошибка. Можно найти эту транзакцию через эксплорер с помощью публичного адреса:
Эта транзакция на самом деле выполнилась.

Асинхронный вариант скрипта

В этом варианте для каждого вашего кошелька процессы будут выполняться одновременно. Чтобы управлять процессами реализована очередь, которая ограничивается параметром в настройках. Очередь заполняется продюсерами (producer), которые одновременно с другими процессами наполняют очередь до указанных в настройках размеров. Процессы с транзакциями выполняются консюмерами (consumer), или их можно назвать воркерами. Их количество тоже ограничивается в настройках.

Как использовать код из статьи

Как запускать файлы .py и .ipynb написано в инструкции.

Скачайте репозиторий с Гитхаба. В нем будет:

  • chain_settings.py - файл с настройками блокчейнов, в которых будет прогрев,
  • config.py - файл с настройками для работы скрипта,
  • functions.py - модуль с функциями,
  • main.py - главная функция для запуска, точка входа,
  • private_keys.txt - текстовый файл с приватными ключами для примера,
  • report_table.csv - таблица с отчетом о работе скрипта для примера.
Весь код этой статьи (синхронная и асинхронная версии) продублирован в файле web3-warm-multi-accounts-send-random-transactions.ipynb.

Модуль chain_settings.py

В файле в виде словарей перечислены сети, в которых будут совершаться транзакции.

Настройки сетей:

  • name - название сети, использует для вывода в консоль и в отчете
  • url - используется для подключения

  • min_amount_for_sending - минимальная сумма для отправки
  • max_amount_for_sending - максимальная сумма для отправки
Из диапазона этих чисел будет определено случайное число для отправки.

  • min_transactions - минимальное количество транзакций, которое должно быть совершено в сети
  • max_transactions - максимальное
Из диапазона этих чисел будет определено случайное количество транзакций.

  • explorer_url - url эксплорера, к которому будет подставлен хэш транзакции
Можно убрать параметры, если они избыточны, или добавить новые.

Примеры настроек

polygon = {
            'name''Polygon',
            'url':'https://polygon.llamarpc.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':5,
            'max_transactions':10,
            'explorer_url':'https://polygonscan.com/tx/'
            }
bsc = {
            'name''Binance Smart Chain',
            'url':'https://bsc.publicnode.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':12,
            'max_transactions':24,
            'explorer_url':'https://bscscan.com/tx/'
            }
avalanche = {
            'name''Avalanche',
            'url':'https://avalanche-c-chain.publicnode.com',
            'min_amount_for_sending':0.0001,
            'max_amount_for_sending':0.001,
            'min_transactions':5,
            'max_transactions':10,
            'explorer_url':'https://snowtrace.io/tx/'
            }
После того, как вы задали пресеты для сетей, в список __all__ передайте названия сетей (словари), которые будут использоваться для отправки.
__all__ = ['polygon', 'bsc', 'avalanche']

Config.py

В отличии от синхронного варианта содержит дополнительные настройки для управления асинхронными процессами.
Название файла с приватниками:
TXT_WITH_PRIVATE_KEYS = 'private_keys.txt'
Название для таблицы с отчетом, которая будет создана:
REPORT_TABLE_NAME = 'report_table.csv'
Количество вариаций, которое будет в диапазоне между min_amount_for_sending и max_amount_for_sending - в настройках chain_settings.py. Из всех вариантов будет рандомно выбрана сумма для отправки:
NUMBER_OF_VARIATION_BETWEEN_VALUES = 50
Минимальная задержка между транзакциями:
MIN_TIMESLEEP_BETWEEN_TRANSACTIONS = 100
Максимальная задержка между транзакциями:
MAX_TIMESLEEP_BETWEEN_TRANSACTIONS = 250
Из этого диапазона будет выбрано рандомное значение, на которое скрипт остановится перед тем, как сделать новую транзакцию. Ставьте значения побольше, чтобы скрипт не делал несколько транзакций за минуту.
Количество одновременно работающих процессов (консюмеров/воркеров):
NUMBER_OF_PROCESSES = 4
Перед стартом каждого процесса есть небольшая задержка. Число рандомно генерируется из диапазона:
MIN_TIMESLEEP_BETWEEN_PROCESSES = 50
MAX_TIMESLEEP_BETWEEN_PROCESSES = 100
Список кодов цветов, которыми будут подсвечены разные процессы в терминале, чтобы чуть легче было их наблюдать:
colors = ['\033[32m',
          '\033[33m',
          '\033[34m',
          '\033[35m',
          '\033[36m',
          '\033[37m']

Модуль functions.py

Библиотеки

import asyncio
import random
from datetime import datetime
 
import numpy as np
import pandas as pd
 
from config import REPORT_TABLE_NAME
  • asyncio - библиотека для асинхронного программирования
  • random - для рандомизации
  • datetime - дата, которая используется в отчете
  • pandas - инструмент для работы с таблицами, используется для загрузки/создания таблицы с отчетом, для записи данных
  • numpy - используется для создания диапазона из любых видов чисел - целых или десятичных
  • config - из файла явно импортируется название csv для отчета

Создание отчета

Для отчетов создается пустой лист:
global_list_for_report = list()
Создается замок:
lock = asyncio.Lock()
Создание отчета
Принимает название чейна, публичный адрес кошелька, тип транзакции, статус транзакции, количество отправленных монет, url для просмотра транзакции, ошибку. Все записывается в словарь, затем добавляется в список global_list_for_report.
Запись блокируется замком (async with lock), чтобы предотвратить одновременное записывание данных асинхронными функциями (корутинами) в список для отчета global_list_for_report:
async def save_report_to_global_list(chain_name, wallet_address,
                                     transaction_type, transaction_status,
                                     amount, transaction_explorer_url, error):
    async with lock:
        d = dict()
        d['chain_name'] = chain_name
        d['wallet_address'] = wallet_address
        d['transaction_type'] = transaction_type
        d['transaction_status'] = transaction_status
        d['sent_amount'] = amount
        d['transaction_url'] = transaction_explorer_url
        d['date'] = datetime.now().strftime('%d-%m-%Y')
        d['error'] = error
 
        global global_list_for_report
        global_list_for_report.append(d)
Экспорт отчета в csv
Из списка со словарями global_list_for_report создается датафрейм и экспортируется в csv. Возможно не эргономично использовать мощный pandas для такой задачи, но код очень удобный:) Функция остается обычной (синхронной):
def export_report():
    global global_list_for_report
    df = pd.DataFrame.from_dict(global_list_for_report)
    df.to_csv(REPORT_TABLE_NAME, index=False)

Рандомный выбор значений

Эти функции остались такими же как в варианте с синхронным кодом, описанным выше. Эти функции остались синхронными, т.к. осуществляют вычисления и не занимаются вводом выводом данных. Так же я посчитал, что их не нужно помещать в отдельный поток, т.к. они очень быстро исполняются.
Функция, которая возвращает рандомное значение из диапазона:
def create_random_value_from_range(min_value, max_value):
    random_value_between_range = random.choice(range(min_value, max_value))
 
    return random_value_between_range

Рандомный выбор количества монет для отправки

Принимает минимальное и максимальное значение из настроек чейнов и количество вариаций. Для этой задачи используется numpy linspace, который создает диапазон из чисел с плавающей точкой, чтобы можно было отправлять маленькие суммы:
def create_random_amount_for_sending(min_value_for_sending, max_value_for_sending,
                                     number_of_variations_between_sending_amount):
    random_position = random.choice(range(number_of_variations_between_sending_amount))
 
    random_amount_for_sending = np.linspace(min_value_for_sending,
                                            max_value_for_sending,
                                            num=number_of_variations_between_sending_amount)[random_position]
 
    return random_amount_for_sending
В синхронной версии скрипта чуть подробнее было написано, как работает numpy linspace.

Транзакции

Настройка транзакции
Принимает подключение, адрес отправителя, адрес получателя, количество монет для отправки.
В amount_wei конвертируется отправляемое значение из ether в wei. await не нужен.
В gas_price определяется цена газа, в estimated_gas оценка лимита газа для отправки крипты.
В nonce определяется номер транзакции.
В словаре transaction записываются все параметры будущей транзакции.
Функция возвращает словарь transaction.
async def transaction_settings(web3, from_address, to_address, amount):
    amount_wei = web3.to_wei(amount, 'ether')
 
    gas_price = await web3.eth.gas_price
 
    estimated_gas = await web3.eth.estimate_gas({
        'to': to_address,
        'value': amount_wei,
    })
 
    nonce = await web3.eth.get_transaction_count(from_address)
 
    transaction = {
        'chainId'await web3.eth.chain_id,
        'from': from_address,
        'to': to_address,
        'value': amount_wei,
        'nonce': nonce,
        'gasPrice': gas_price,
        'gas': estimated_gas,
    }
 
    return transaction
Подпись и отправка транзакции
Принимает подключение, адрес отправителя, адрес получателя, количество монет для отправки, приватный ключ.
Настройки будущей транзы записываются в create_transaction.
В signed_transaction передаются настройки и приватный ключ, транзакция подписывается. await не нужен.
В transaction_hash записывается хэш отправленной транзакции.
Функция возвращает хэш.
async def create_sign_send_transaction(web3, from_address, to_address, amount, private_key):
    create_transaction = await transaction_settings(
        web3=web3,
        from_address=from_address,
        to_address=to_address,
        amount=amount
    )
 
    signed_transaction = web3.eth.account.sign_transaction(create_transaction, private_key)
 
    transaction_hash = await web3.eth.send_raw_transaction(signed_transaction.rawTransaction)
 
    return transaction_hash.hex()
SELF транзакция
  • Принимает подключение, количество монет для отправки, приватник, минимальную и максимальную задержку между транзакциями, название чейна и url эксплорера. В отличии от синхронного варианта принимает номер процесса и кодировку цвета, чтобы обозначить исполняемый процесс в терминале.
  • Перед отправкой в timesleep_value генерируется рандомное число из небольшого диапазона, скрипт останавливается на это время.
  • В my_address записывается публичный адрес, который определяется из приватного ключа. await не нужен.
  • В create_sign_send_transaction осуществляется отправка, в качестве отправителя и получателя передается my_address. Хэш записывается в transaction_hash.
  • В save_report_to_global_list записывается отчет, в функцию передается название сети, публичный адрес, тип транзакции, статус SUCCESS, сумма, url, который получается в результате подстановки параметра из chain_settings.py и хэша транзы, ошибки нет, поэтому передается прочерк.
  • В timesleep_value генерируется рандомное число из переданных в функцию значений. Скрипт засыпает.
  • Если срабатывает блок except, в отчет записывается название сети, публичный адрес, тип транзакции, статус ERROR, сумма, вместо url передается прочерк, код ошибки.
async def self_transaction(web3, amount, private_key,
                           min_timesleep_between_transactions,
                           max_timesleep_between_transactions,
                           chain_name, explorer_url,
                           number_of_process, color):
 
    try:
        timesleep_value = create_random_value_from_range(5, 15)
        await asyncio.sleep(timesleep_value)
 
        transaction_type = 'SELF-transaction'
        print(f'{color}Process №{number_of_process}, key {private_key[:6]}...{private_key[-6:]}, {transaction_type} {chain_name}')
 
        my_address = web3.eth.account.from_key(private_key).address
 
        transaction_hash = await create_sign_send_transaction(web3, my_address, my_address,
                                                              amount, private_key)
 
        print(f'{color}Process №{number_of_process}, hash {transaction_type} {chain_name} {transaction_hash}')
 
        print(f'{color}Process №{number_of_process}, recording data in a report')
        await save_report_to_global_list(chain_name, my_address,
                                         transaction_type, 'SUCCESS',
                                         amount, f'{explorer_url}{transaction_hash}', '-')
 
        timesleep_value = create_random_value_from_range(min_timesleep_between_transactions,
                                                         max_timesleep_between_transactions)
        print(f'{color}Process №{number_of_process}, pause {timesleep_value} sec. after {transaction_type} {chain_name} ', '\n')
        await asyncio.sleep(timesleep_value)
 
    except Exception as error:
        print(f'{color}Process №{number_of_process}, {transaction_type} {chain_name}, error -  {error}', '\n')
        await save_report_to_global_list(chain_name, my_address,
                                         transaction_type, 'ERROR',
                                         amount, '-', error)
RANDOM транзакция
Почти такая же функция как self_transaction, отличается наличием кода на 13-17 строках.
  • Принимает подключение, количество монет для отправки, приватник, минимальную и максимальную задержку между транзакциями, название чейна и url эксплорера. В отличии от синхронного варианта принимает номер процесса и кодировку цвета, чтобы обозначить исполняемый процесс в терминале.
  • Перед отправкой в timesleep_value генерируется рандомное число из небольшого диапазона, скрипт останавливается на это время.
  • В account создается новый аккаунт. В random_wallet записывается публичный адрес созданного аккаунта, на который будет осуществляться отправка. await не нужен.
  • В my_address записывается публичный адрес, который определяется из приватного ключа. await не нужен.
  • В prepared_wallet записывается созданный публичный адрес, приведенный к корректному виду с помощью to_checksum_address. await не нужен.
  • В create_sign_send_transaction осуществляется отправка, в качестве отправителя и получателя передается my_address. Хэш записывается в transaction_hash.
  • В save_report_to_global_list записывается отчет, в функцию передается название сети, публичный адрес, тип транзакции, статус SUCCESS, сумма, url, который получается в результате подстановки параметра из chain_settings.py и хэша транзы, ошибки нет, поэтому передается прочерк.
  • В timesleep_value генерируется рандомное число из переданных в функцию значений. Скрипт засыпает.
  • Если срабатывает блок except, в отчет записывается название сети, публичный адрес, тип транзакции, статус ERROR, сумма, вместо url передается прочерк, код ошибки.
async def random_transaction(web3, amount, private_key,
                             min_timesleep_between_transactions,
                             max_timesleep_between_transactions,
                             chain_name, explorer_url,
                             number_of_process, color):
    try:
        timesleep_value = create_random_value_from_range(5, 15)
        await asyncio.sleep(timesleep_value)
 
        transaction_type = 'RANDOM-transaction'
        print(f'{color}Process №{number_of_process}, key {private_key[:6]}...{private_key[-6:]}, {transaction_type} {chain_name}', '\n')
 
        account = web3.eth.account.create()
        random_wallet = account.address
 
        my_address = web3.eth.account.from_key(private_key).address
        prepared_wallet = web3.to_checksum_address(random_wallet)
 
        transaction_hash = await create_sign_send_transaction(web3, my_address, prepared_wallet,
                                                              amount, private_key)
 
        print(f'{color}Process №{number_of_process}, hash {transaction_type} {chain_name} {transaction_hash}' )
 
        print(f'{color}Process №{number_of_process}, recording data in a report')
        await save_report_to_global_list(chain_name, my_address,
                                         transaction_type, 'SUCCESS',
                                         amount, f'{explorer_url}{transaction_hash}', '-')
 
        timesleep_value = create_random_value_from_range(min_timesleep_between_transactions,
                                                         max_timesleep_between_transactions)
        print(f'{color}Process №{number_of_process}, pause {timesleep_value} sec. after {transaction_type} {chain_name} ', '\n')
        await asyncio.sleep(timesleep_value)
 
    except Exception as error:
        print(f'{color}Process №{number_of_process}, {transaction_type} {chain_name}, error -  {error}', '\n')
        await save_report_to_global_list(chain_name, my_address,
                                         transaction_type, 'ERROR',
                                         amount, '-', error)

Модуль main.py

Библиотеки

import asyncio
 
from web3 import AsyncWeb3
 
from functions import *
from config import *
import chain_settings
  • asyncio - библиотека для асинхронного программирования
  • web3 - подключение к rpc, осуществление транзакций на блокчейнах.
  • config - импорт всех настроек из модуля
  • functions - импорт всех функций из модуля
  • chain_settings - импорт настроек для блокчейнов из модуля

Функция продюсера

Принимает очередь, приватный ключ, номер.
Добавляет кортеж с приватным ключом и номером в очередь. На печать выводит сообщение, что приватный ключ добавлен в очередь под каким-то номером. Надпись выводится красным цветом на белом фоне:
async def producer(queue, private_key, number):
    await queue.put((private_key, number))
    print('\n', f'\033[31;47mPrivate key {private_key[:6]}...{private_key[-6:]} has been added to the queue. Queue №{number}\033[0m',
          '\n')

Функция консюмера

Почти полностью повторяет код функции main_function_make_random_or_self_txns в синхронном варианте, описанном выше.
  • Принимает очередь и цвет.
  • Запускается бесконечный цикл.
  • queue.get() получает первый объект из очереди. Из кортежа в private_key записывается приватный ключ, в process_number записывается номер в очереди.
  • В timesleep_value записывается рандомное значение, выбранное из диапазона, который указывается в config.py. Процесс останавливается перед стартом. Таким образом все процессы стартуют в разное время.
  • Запускается цикл, который перебирает словари из chain_settigs.py, указанные в __all__
  • В переменную web3 записывается объект созданного асинхронного подключения. Если подключение False (if not web3.is_connected()), процесс останавливается. Записывается в отчет. Для отчета в этой части кода генерируется публичный адрес my_address из приватника.
  • В transactions_quantity записывается рандомное количество транзакций, которое будет совершено в сети. Число генерируется из диапазона, указанного в настройках в chain_settigs.py.
  • Запускается цикл по количеству транзакций в значении transactions_quantity.
  • В amount определяется случайная сумма к отправке, генерируется функцией create_random_amount_for_sending, которая берет диапазон из настроек chain_settigs.py и количество вариаций внутри этого диапазона NUMBER_OF_VARIATIONS_BETWEEN_SENDING_AMOUNT из файла config.py.
  • На каждой итерации цикла выбирается SELF или RANDOM транзакция.
  • queue.task_done() уведомляет очередь о том, что задача была выполнена, обращается к приватной переменной очереди Queue._unfinished_tasks, уменьшая ее значение.
  • Если срабатывает блок except, записывается отчет с пустыми параметрами (прочерками), с приватным ключом и сообщением, что возникла системная ошибка. queue.task_done() уведомляет очередь о том, что задача была выполнена.
async def consumer(queue, color):
    while True:
        item = await queue.get()
        private_key, process_number = item
 
        timesleep_value = create_random_value_from_range(MIN_TIMESLEEP_BETWEEN_PROCESSES,
                                                         MAX_TIMESLEEP_BETWEEN_PROCESSES)
 
        print(f'{color} Before the start of the process №{process_number} {timesleep_value} sec.', '\n')
 
        await asyncio.sleep(timesleep_value)
 
        try:
            for chain in map(chain_settings.__dict__.get, chain_settings.__all__):
                chain_name = chain['name']
 
                print(f'{color}Process №{process_number} has been launched for {private_key[:6]}...{private_key[-6:]} on the network {chain_name}')
 
                web3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(chain['url']))
 
                if not await web3.is_connected():
                    print(f'{color}Process №{process_number} connection to {chain_name} is not established', '\n')
 
                    my_address = web3.eth.account.from_key(private_key).address
                    await save_report_to_global_list(chain_name, my_address,
                                                     '-', 'ERROR',
                                                     '-', '-',
                                                     'Connection is not established')
                    await asyncio.sleep(5)
                    continue
 
                print(f'{color}Process №{process_number} connection to {chain_name} was established', '\n')
 
                transactions_quantity = create_random_value_from_range(chain['min_transactions'],
                                                                       chain['max_transactions'])
 
                for _ in range(transactions_quantity):
                    amount = create_random_amount_for_sending(chain['min_amount_for_sending'],
                                                              chain['max_amount_for_sending'],
                                                              NUMBER_OF_VARIATIONS_BETWEEN_SENDING_AMOUNT)
 
                    await random.choice([self_transaction, random_transaction])(web3, amount, private_key,
                                                                                MIN_TIMESLEEP_BETWEEN_TRANSACTIONS,
                                                                                MAX_TIMESLEEP_BETWEEN_TRANSACTIONS,
                                                                                chain_name, chain['explorer_url'],
                                                                                process_number, color)
 
            queue.task_done()
 
        except Exception as error:
            print(error, '\n')
            await save_report_to_global_list('-', private_key,
                                             '-', 'ERROR',
                                             '-', '-',
                                             'Some system error occurred during the process.')
            queue.task_done()
Если соединение с rpc не будет устанавливаться для всех сетей, будет выполняться часть кода "if not await web3.is_connected():" с выводом в терминал и отчет "connection to {chain_name} is not established", возможно есть ошибка "aiohttp ClientConnectorCertificateError", попробуйте выполнить в теминале команду "/Applications/Python\ 3.9/Install\ Certificates.command", подставив версию питона, которая у вас установлена.

Главная функция - событийный цикл

  • В queue создается очередь. В параметре maxsize задается максимальный размер очереди. В очередь помещается указанное количество элементов, когда элементы взяты в работу, в очередь снова добавляется указанное количество элементов. В моем примере размер очереди равен количеству одновременно запущенных процессов. Поэкспериментируйте с этим параметром, можете вообще его убрать.
  • pended равен нулю, будет увеличиваться на 1 по мере добавления приватных ключей в очередь.
  • В список list_of_private_keys добавляются приватные ключи из txt файла.
  • Создается пустой список продюсеров producers. Запускается цикл по списку с приватными ключами. pended увеличивается на 1. В task записывается объект класса Task, т.е в событийный цикл помещается задача с результатом работы функции producer, в которую передаю очередь, приватный ключ и pended - номер задачи в очереди.
  • Создается пустой список продюсеров consumers. Количество консюмеров/воркеров ограничено параметром NUMBER_OF_PROCESSES в config.py. В task записывается объект класса Task, т.е в событийный цикл помещается задача с результатом работы функции consumer, в которую передаю очередь, код цвета, который определяется по индексу, который в свою очередь определяется случайно.
  • В функции gather распаковывается список задач producers. Продюсеры начинают создавать очередь. При этом в функции consumer запущен бесконечный цикл, который ожидает очередь и находится в событийном цикле благодаря create_task.
  • queue.join() обращается к приватной переменной Queue._unfinished_tasks и ждет, когда очередь опустеет. Выше я писал, что в функции consumer queue.task_done() уменьшает значение этой переменной.
  • Дополнительно, чтобы скрипт не повис, можно отменить все задачи консюмеров в событийном цикле. Цикл проходит список задач consumers и отменяет их cancel().
  • В конце функция export_report создает из списка global_list_for_report датафрейм и записывает его в csv.
async def main():
    queue = asyncio.Queue(maxsize=NUMBER_OF_PROCESSES)
    pended = 0
 
    with open(TXT_WITH_PRIVATE_KEYS) as file:
        list_of_private_keys = [line.strip() for line in file.readlines()]
 
    producers = []
    for private_key in list_of_private_keys:
        pended += 1
        task = asyncio.create_task(producer(queue, private_key, pended))
        producers.append(task)
 
    consumers = []
    for _ in range(NUMBER_OF_PROCESSES):
        task = asyncio.create_task(consumer(queue, colors[random.choice(range(len(colors)))]))
        consumers.append(task)
 
    await asyncio.gather(*producers)
    await queue.join()
 
    for c in consumers:
        c.cancel()
 
    export_report()
Точка входа:
if __name__ == '__main__':
    asyncio.run(main())
Как отображается процесс в терминале

Начало работы. Продюсеры создали очередь из четырех приватных ключей - размер очереди был ограничен значением, которое равно количество консюмеров. Вывод на печать красными буквами на белом фоне.

"Слушающие" очередь в бесконечном цикле консюмеры взяли приватники в работу одновременно. У каждого процесса свой отложенный старт, определенный рандомно. У каждого процесса свой цвет.

Как только из очереди были взяты 4 приватника, продюсеры сразу же добавили следующую порцию - 2 приватных ключа, потому что в списке их было всего 6:

Процессы написали, что соединение установлено. На скрине видно, что процессы идут не по порядку, у каждого был разный отложенный старт:
Процессы принтят сообщения о выполняемых инструкциях:
Как только один из процессов освободился, взял в работу новую задачу - эмодзи и надпись на скрине "Before the start...":
Помимо цветов процессы можно маркировать с помощью эмодзи, чтобы удобнее было следить. Поэкспериментируйте. Копируйте на сайте https://smiley.cool/emoji-list.php и вставляйте прямо в строки в своем коде. Для примера я оставил эмодзи только в одном месте:
В отчете видно как транзакции для разных кошельков совершали не по порядку:
# Теги

Пишу статьи с Python кодом для автоматизации крипты. Телеграм - @rukidablkliki

© Crypto-Py.com