Web3.0 на Python, часть 2: advanced

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Привет, хабр! В первой части мы рассмотрели базовые операции на web3py, которые закроют большинство ваших потребностей для проектов на ранних этапах. Здесь же речь в основном пойдет про улучшение производительности и различные "фишки", которые, например, помогут вам уменьшить количество запросов и/или время на эти запросы. Скорее всего, они не будут полезны тем, кто делает какой-то pet-project или проект на хакатоне. А полезны они будут тем, кто делает реальный боевой проект и кому важна производительность.

Содержание:

  • Оцениваем газ для транзакции

  • Ускоряем инициализацию контрактов

  • Узнаем баланс для нескольких токенов за ОДИН запрос ⚡

  • Знакомимся с multicall ⚡⚡

  • Используем асинхронный web3py

  • Полезные сервисы


Оценка газа ⛽

Мы уже делали транзакцию с нативной валютой, ставя при этом "газ побольше", чтоб точно хватило. А что если хочется показать пользователю заранее, сколько именно он должен будет потратить газа на транзакцию? В данном случае нам поможет метод eth.estimate_gas. Стоит сказать, что это число подвержено флуктуации. И если вдруг предсказанного газа не хватит, то транзакция зафейлится, а пользователь потеряет какое-то количество нативной валюты. Поэтому я бы советовал увеличивать это количество на ~20%, неиспользованный газ просто не будет потрачен.

from web3 import Web3

user_address = "0x2A647559a6c5dcB76ce1751101449ebbC039b157"
rpc_url = "https://matic-mumbai.chainstacklabs.com"  # testnet Polygon
web3 = Web3(Web3.HTTPProvider(rpc_url))

amount_of_matic_to_send = 1
txn = {  # формируем транзакцию
  'chainId': web3.eth.chain_id,
  'from': user_address,
  'to': user_address,
  'value': int(Web3.toWei(amount_of_matic_to_send, 'ether')),
  'nonce': web3.eth.getTransactionCount(user_address), 
  'gasPrice': web3.eth.gas_price,
}
web3.eth.estimate_gas(txn)
# предсказанное количество газа 21000 Wei

Ускоряем инициализацию контрактов

Представим, что нам по адресу ERC-20 токена и адресу юзера нужно узнать его (юзера) баланс. Напомню, как мы делали это в первой части:

import json
from web3 import Web3

# одинаковый для всех ERC20 токенов
ERC20_ABI = json.loads('''[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_initialSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"decimals_","type":"uint8"}],"name":"setupDecimals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]''')

# USDT токен
usdt_contract_address = '0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c'

def get_balance_old(web3: Web3, token_address: str, user_address: str) -> int:
    # инициализация контракта
    token_contract = web3.eth.contract(token_address, abi=ERC20_ABI)

    # получение данных из ноды
    balance = token_contract.functions.balanceOf(user_address).call()
    return int(balance)

Напрашивается очевидный вопрос: нам нужна всего лишь одна функция balanceOf, но почему мы при этом инициализируем контракт со всеми функциями из ERC20_ABI? Да, можно просто вручную убрать все остальные функции из ABI, чтобы в итоге остался список из одной нужной функции. А можно сделать и вот так (такой способ пригодится в дальнейшем):

import json
from web3.types import ABIFunction
from eth_utils import encode_hex, function_abi_to_4byte_selector, add_0x_prefix
from web3._utils.contracts import encode_abi
from web3._utils.abi import get_abi_output_types


encode_hex_fn_abi = lambda fn_abi: encode_hex(
    function_abi_to_4byte_selector(fn_abi)
)
# словарь с функцией balanceOf. Можно посмотреть тут https://bscscan.com/token/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d#code
BALANCE_OF_ABI: ABIFunction = json.loads('{"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}'
balance_of_output_types = get_abi_output_types(BALANCE_OF_ABI)
balance_of_selector = encode_hex_fn_abi(BALANCE_OF_ABI)

def get_balance_new(web3: Web3, user_address: str, token_address: str) -> int:
    # инициализация контракта
    data = add_0x_prefix(
        encode_abi(
            web3, 
            abi=BALANCE_OF_ABI,
            arguments=(user_address,), # аргументы функции balanceOf
            data=balance_of_selector
        ),
    )
    
    tx = {"to": token_address, "data": data}
    # получение данных из ноды
    res = web3.eth.call(tx)
    
    output_data = web3.codec.decode_abi(balance_of_output_types, res)
    
    balance = output_data[0]
    return balance

Сравним, что же у нас получается по времени CPU и Wall Time (WT) в ms, усреднённо:

old WT

old CPU

new WT

new CPU

Инициализация

16

18

3

3

Вызов ноды

155

19.5

145

17

Разница не велика, скажете вы, порядка 10 ms. А я скажу вам, что тут десяток, там два десятка и вот он, прирост. И вообще, с миру по нитке

Источник: https://habr.com/ru/post/699560/


Интересные статьи

Интересные статьи

Все персонажи являются вымышленными. Любое совпадение с реально живущими или когда-либо жившими людьми случайно.- Что же теперь с нами будет, Саша?- Я не знаю.Если уйти от разбора ...
На старости лет, в свои 33 года, решил я пойти в магистратуру по компьютерным наукам. Первую свою вышку я закончил ещё в 2008 и совсем не в сфере ИТ, много воды с тех пор утекло. Как ...
Дешёвый сервер из китайских запчастей. Часть 1, железная Размытая кошка позирует на фоне настраиваемого сервера. На заднем плане – мышка на сервере Привет, Хабр! В жизни каждого человека ино...
В этой самоизоляционной статье я расскажу о разварке проволочных микровыводов (англ. wire bonding). В контексте печатных плат речь пойдёт о технологии монтажа кристаллов на печатную плату (англ. ...
Автокэширование в 1с-Битрикс — хорошо развитая и довольно сложная система, позволяющая в разы уменьшить число обращений к базе данных и ускорить выполнение страниц.