Попробуем определить, что нам для этого понадобится. Очевидно, нам понадобится для начала копия желаемого блокчейна, причем эта копия должна сохранять актуальность (синхронизироваться с соответствующей сетью). Последнее намекает, что либо нам нужно реализовать полностью соответствующий протокол (что очевидно будет избыточно и крайне дорого) либо — установить соответствующее программное обеспечение, что более рационально. Для Ethereum это будет например Geth (go-ethreum), bitcoind или btcd (реализация на golang) для Bitcoin, или любое соответствующее программное обеспечение. Главное условие — доступ к полному блокчейну (или той части, которую вы хотите отслеживать).
Для ясности давайте вспомним, как должна хранится информация в блокчейн. Bitcoin и его потомки (назовем их “классическими”) используют концепцию UTXO (Unspent Transaction Output). Результат транзакции можно назвать “выходом” (Out), И каждая транзакция за одним исключением должна ссылаться на “Вход” (“In”). Таким образом каждая танзакция содержит адрес-отправитель и адерс получатель в той или иной форме (не будем сейчас вдаваться в детали, на достаточно самого факта наличия такой информации). Побочный эффект — из этой информации мы можем так же построить дерево транзакций, которое позволит отслеживать каждый прошедший по сети сатоши.
Сеть Ethereum обращается с информацией несколько иначе. Как я упомянул в прошлой статье, концепция Ethereum отличается, и хранится информация непосредственно о балансах адресов. Однако информация о транзакциях по прежнему находится в блоках и хранится в сети. Таким образом индексация транзакций и их взаимосвязь с адресами по прежнему возможна. В этой статье я намеренно не буду касаться смартконтрактов и операций с токенами (ERC20/ERC721/ERC1155) и так называемых “Internal Transactions” — рассмотрение этой темы требует отдельной статьи.
А что нам не доступно? Блокчейны, использующие алгоритмы “доказательства с нулевым разглашением” (“Zero-knowledge proof”), такие как Monero и ZCash не могут быть индексированы таким образом, что следует из их концепции.
Далее процесс будет прямолинеен и прост, хотя крайне затратен по времени и по объему дискового пространства, которе вам понадобится.
Шаг первый — запрашиваем начальный блок через RPC (реализация зависит от конкретной сети) и перебирая транзакции по одной, декодируем их и выделяем информацию, которую хотим индексировать. Вероятно нас будут интересовать хеш транзакции, использованные адреса, направление транзакции, время и номер блока. Полный перечень зависит от ваших задач и от доступных вам ресурсов (в основном бутылочным горлышком будет дисковое пространство).
Далее мы сохраняем интересующую нас информацию в каком либо варианте базы данных, берем следующий блок и повторяем описанные действия. Не слишком изящно, однако другого рецепта к сожалению нет.
И возникает логичный вопрос — а почему собственно разработчики блокчейн не дают доступа к такой очевидно полезной информации? Что мешает сразу, при синхронизации с сетью сохранить и индексировать такие данные?
Ответ будет пересекаться с объяснением, почему именно дисковое пространство будет бутылочным горлышком.
Для начала маленький пример. Те, кто разворачивал node для Ethereum знают, что есть несколько вариантов синхронизации Geth. В документации, раздел “Sync modes” упомянуты Full nodes и Light nodes. Последние нас не интересуют, а вот Full nodes в свою очередь условно разделены на Snap, Full и Archive. И вот тут начинается интересное, особенно если рассмотреть внимательно иллюстрацию, кто собственно из них кто. Snap-нода хранит подробную информацию о последних 128 блоках и пару контрольных точек (checkpoint) в относительно недавнем прошлом. Full нода, как следует из иллюстрации хранит контрольные точки (checkpoint) с некоей периодичностью практически до “начала времен”. Archive хранит уже полную информацию, за все время существования Ethereum. Если задаться целью и выяснить, какой объем данных хранит полная (Full) нода, то окажется что это около 1Tb (на момент написания статьи). Не будем вдаваться в механизмы “реорганизации сети”, и прочие ухищрения. Нам интересно другое, объем данных, хранимых Archive нодой — уже около 16Tb(!). Получается, что нам не доступно более 90% информации о блокчейн?
Если поставить один несложный эксперимент — многое становится на свои места.
Воспользуемся сервисом etherscan.io и найдем случайную транзакцию “из начала времен”, например 0x7d7062d6f865931e0bbbccea46551a73d5d58a6ef618d5592c35b5256a65e9ba (примерно за август 2015 года) и попробуем получить информацию о ней с помощью консоли локального Geth, синхронизированного в режиме full. Мы же можем получить такую информацию, да?
Welcome to the Geth JavaScript console! instance: Geth/v1.12.0-stable-e501b3b0/linux-amd64/go1.20.3 at block: 19122581 (Tue Jan 30 2024 23:24:11 GMT+0000 (UTC)) datadir: /home/eth/ethereum modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0 To exit, press ctrl-d or type exit > eth.getTransaction("0x7d7062d6f865931e0bbbccea46551a73d5d58a6ef618d5592c35b5256a65e9ba") null
Эм… То есть, информации о транзакции нет? Не совсем… Давайте попробуем просмотреть содержимое блока, в котором находится искомая транзакция, благо мы знаем номер блока:
> eth.getBlockByNumber(122546) { … hash: "0xc58aa38cf7df6050d3be43f1557d61e3a28e3f34d7818b1644e6a9972003e80a", … number: "0x1deb2", … transactions: ["0x7d7062d6f865931e0bbbccea46551a73d5d58a6ef618d5592c35b5256a65e9ba"], … }
Так вот же она, на месте! Если выполнить eth.getBlockByNumber(122546, true) (дополнительный параметр, указывающий запрос на выдачу всей информации о транзакциях в блоке), то мы получим и содержимое транзакции, и например узнаем, что отправителем был адрес 0x5685620dce626248ccb7121e87fbc098fd5310bd а получателем 0x9b0a028eafdecde3afc0fd00b7937098388b7c8a, как и всю сопутствующую информацию. Почему же мы не смогли получить эту информацию, запрашивая напрямую?
Не вдаваясь в технические детали — только архивная нода полностью индексирует весь блокчейн и все связи между блоками, транзакциями и адресами. И “вес” этих индексов — это как раз те самые недостающие 15 террабайт информации.
Это дает нам понимание, к каким объемам данных нам стоит готовится. Однако совсем не обязательно, что ваши конкретные задачи требуют такой подробной индексации, вполне возможно, что ваша база данных будет несколько компактнее.
Что касается bitcoin — там есть подобные механизмы, но объем данных несравнимо меньше. На момент написания статьи, объем блокчейн bitcoin оставляет смехотворные 545Gb, так что проблем с ним будет значительно меньше.
В заключении пару слов, о том, как можно ускорить процесс индексации. Совсем не обязательно обрабатывать все запросы через обращение к клиенту соответствующей сети. Чаще всего программное обеспечение для работы с блокчейн является открытым, а форматы баз данных достаточно стандартизированы. Возможно стоит рассмотреть вариант работы непосредственно сданными на диске уже синхронизированной ноды, и лишь потом — синхронизацию новых блоков.
В следующей статье мы попробуем собрать из подручных средств минимальную реализацию для подготовки базы поиска по адресам для разных блокчейнов.