EOS.IO RAM: иллюстрированное руководство

Warning: автор статьи рассчитывает, что читатель обладает неким минимальным уровнем познаний в информатике как то: что такое переменная и как приблизительно она хранится в памяти (на уровне языков высокого уровня). Будет еще лучше, если читатель знаком с понятием Архитектура фон Неймана и понимает, что при запуске программы её код и данные загружаются рядом в оперативную память компьютера. Знание о том, что такое EOS и какую роль в нем выполняют блок-продюсеры так же не будут лишними.

Статья описывает поведение EOS.IO версии 1.2.5. В будущем возможно что то изменится. Но это не точно.

Введение

Заходя на какой либо блокчейн-обозреватель EOS.IO (будь то eosflare.io, eoscanner.io, bloks.io или любой вам удобный) на страницу вашего аккаунта вы могли видеть примерно следующую картину:

Типичный вид аккаунта

Среди принадлежащих аккаунту ресурсов мы видим количество выделенной и занятой памяти RAM. Когда ваш аккаунт был только создан он уже занимал какое то количество RAM. Возможно вы замечали, что после использования какой то игры на блокчейне EOS.IO количество свободной RAM несколько уменьшалось. Иногда, когда вы участвовали в airdrop и вам начисляли токены количество памяти так же уменьшалось, а при последующих транзакциях с этим токеном оставалось неизменным. А начисление других токенов не приводило к изменению используемой памяти вовсе. Почему так происходит? Где хранится эта память аккаунта, почему она убывает или не убывает, что такое токен и где он хранится? Давайте разбираться.

NODEOS и все-все-все

При виде прогрессбара, отображающего свободную и занятую RAM, а так же CPU и Net, можно представить, что аккаунт является эдаким маленьким компьютером, со своими характеристиками, который выполняет блокчейн контракты. Характеристики можно улучшать, докупая больше оперативной памяти, мощнее процессор и килобайты трафика, прямо как в обычной жизни. Аналогия является простой для понимания, но плохо отражающей действительность с точки зрения архитектуры EOS.IO, если мы захотим копнуть несколько глубже.

Действительно, мы обладаем специальным блокчейн-компьютером, но в несколько ином виде. На каждом блок продюсере запущена копия программы nodeos, являющаяся серверной частью программного обеспечения EOS (Сервер — в терминологии компьютерных сетей — программа, предоставляющая некие данные, сервис, в отличие от клиента — эти данные получающие и использующие). nodeos предоставляет несколько разных функций (которые различаются в зависимости от того, с какими плагинами nodeos запущен), но для узла EOS наибольшую важность представляют функции:

  • обмена новыми сообщениями для блокчейна;

  • вычисления транзакций на специальной виртуальной машине, изменения состояния смарт-контрактов и обновления базы данных в оперативной памяти;

  • записи блокчейна на жесткий диск;

Т.е. на каждом полном узле (к которым относятся и блок продюсеры), запущена специализированная программа — виртуальная машина, маленький компьютер внутри компьютера, со своей архитектурой (она называется WebAssembly). Понятие архитектура подразумевает, что у этого компьютера есть свой виртуальный процессор, понимающий свою систему команд, и своё пространство оперативной памяти. Именно на этом, весьма ограниченном в своей свободе, процессоре и выполняются смарт-контракты. И именно в его оперативной памяти они хранятся.

И вот тут начинается самое интересное. Вместе с запуском nodeos на виртуальную машину устанавливаются системные смарт-контракты. Это специальные смарт-контракты, написанные программистами block.one, разработчиками EOS.IO, и выполняющие специальные системные функции. Большая часть логики работы EOS реализована за счет этих смарт-контрактов. Среди них есть такие функции как:

  • покупка памяти и стейкинг EOS-токенов;

  • голосование за блок продюсеров;

  • создание аккаунтов

и много чего другого. Разработчики EOS.IO нам не соврали и действительно разработали некое подобие операционной системы для блокчейна, где системные контракты выполняют функции папки Windows.

Мы начинаем потихоньку подходить к такому понятию, как память аккаунта. Аккаунт является одним из базовых понятий блокчейна и виртуальной машины EOS. Ни одна транзакция, ни одно действие смарт-контракта не может существовать без указания от кого и кому предназначена транзакция. И смарт-контракты никогда не загружаются в сеть EOS сами по себе, они всегда привязываются к какому то аккаунту. Именно обращаясь к имени аккаунта мы вызываем смарт-контракт, к которому он привязан. (К одному аккаунту можно привязать только один контракт.) Соответственно, когда мы привязываем код смарт контракта к аккаунту, можно представить это как загрузку контракта в оперативную память RAM аккаунта, что приводит к увеличению занятой оперативной памяти аккаунта. Возникает резонный вопрос: если я не разработчик, не загружаю на свой аккаунт смарт-контракты, то на что тратится моя оперативная память? Токены в оперативной памяти хранятся? Не торопитесь…

Разбираем контракт по косточкам

Давайте посмотрим по-подробнее на один из системных контрактов eosio.token. Он выполняет функции создания, выпуска, передачи и учета токенов. Именно на его основе создается системный токен EOS, выполняющий функции не только передачи ценности, но и управления на блокчейне EOS, как то: выделение системных ресурсов (стейкинг и покупка памяти), сила голоса при голосовании за блок продюсеров и т.д.

Обратите внимание: токен — это свойство конкретного смарт-контракта. Он не существует “где то в блокчейне”, а всегда привязан к конкретному смарт-контракту и без него не существует.

Представить это можно следующим образом:

В памяти смарт-контракта при его первом запуске создаются 2 таблицы (с++ программисты называют их мульти-индексом, что является интерфейсом доступа к различным свойствам упорядоченного набора гомогенных структур, но не будем о грустном). Таблица stats содержит такие столбцы как: символ токена (например ‘EOS’, ‘ETH’), максимальное количество токенов, выпускающий аккаунт. Таблица accounts содержит столбцы: владелец токена и количество токенов. Т.е. перед тем, как выпустить токены мы в таблицу stats заносим строку, в которой записано, что я, аккаунт vasyaivanov1, создал токены ‘MYEOS’, коих можно выпустить 1000 штук. Токены созданы, но не выпущены (их количество не определено). После этого мы производим выпуск — записываем в таблицу accounts строки, каждая из которых описывает уникального для этой таблицы владельца токена (имя аккаунта) и количество наших токенов у него на счету.

Внимание, важный момент:

Это и есть токен, как он есть. 2 таблицы — первая с его описанием и вторая с балансом. На самом деле точное определение структуры токена не определено в системе понятий EOS и может различаться в зависимости от того, как его захотел описать программист. Но мы разобрали каноничный случай, токен, созданный программистами block.one. Программисты — люди ленивые и без веских на то причин что-то свое придумывать не будут. Поэтому мы можем допустить, что 90% всех токенов, если не будут идентичны, то по крайней мере, очень похожи.

Но функционал работы программы определяется не только используемыми данными, но и её алгоритмами. Алгоритмы контракта описываются в такой сущности, как “действия”.

Действия — actions — это понятия экосистемы EOS. Обычно такие вещи называют ‘функциями класса’ или ‘методами’. Упрощенно, если представить контракт как черный ящик, производящий внутри какие то одному ему ведомые вычисления, то действие — это ручка, позволяющая запустить тот или иной алгоритм. В эти действия можно передавать ‘аргументы’ — некие ручки-крутилки, которыми мы можем регулировать алгоритм действия.

Перед вами вызов типичного действия с именем “create”:

Со смарт-контрактами можно взаимодействовать разными способами, один из них — при помощи утилиты cleos. Это консольный клиент, который поставляется в комплекте с nodeos. Выглядит страшно, но на самом деле все просто, давайте разберем команду по частям:

  1. cleos. Имя программы (для её запуска вы каким либо образом должны установить дистрибутив EOS);

  2. push action. Подкоманда, говорящая, что нужно запустить действие указанного далее смарт-контракта;

  3. eosio.token. Имя аккаунта, к которому привязан контракт, действие которого мы хотим вызвать;

  4. create. Имя действия. Имя зависит от того, какие действия добавил разработчик в свой контракт. От контракта к контракту количество действий и их имена меняются. В данном случае мы вызываем действие create, которое создает новый токен (упрощенно — записывает переданные аргументы как строку в таблице stats);

  5. Аргументы действия. Это данные, с которыми действие будет работать. Они так же зависят от конкретного действия. Здесь важно всё: тип аргумента (строка, число), количество аргументов, их порядок следования. В данном действии мы передаем 2 аргумента: имя аккаунта, который будет считаться создателем токенов ‘eosio’ и максимальное количество токенов и его символ ‘1000 SOE’;

  6. -p eosio.token каждая команда в EOS должна выполняться от чьего то имени. Для авторизации аккаунта в EOS используется приватный ключ. Этой командой мы говорим cleos-у использовать для авторизации приватный ключ аккаунта eosio.token (для этого мы его должны знать и он должен быть установлен в системе).

Алгоритм этого действия примерно следующий:

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

  2. Проверяем переданные аргументы: максимальное количество токенов не должно быть больше определённого заранее числа и не должно быть отрицательным, да и обозначение токена должно быть адекватным;

  3. Убеждаемся, что в таблице stats еще нет токена с таким символом;

  4. Если все проверки пройдены успешно — вызываем, предоставленную нам разработчиками block.one, функцию “emplace”, в которую передаём таблицу, в которую нужно записать строку (в данной ситуации это таблица stats), саму строку таблицы (т.е. набор переменных, который мы передали в действие) и имя аккаунта плательщика (в этом действии за размещение всегда платит аккаунт контракта).

Функция “emplace” обращается к еще одной внутренней сущности архитектуры EOS — Chainbase. Это система управления базами данных, расположенными в оперативной памяти узла где то на уровне виртуальной машины. По аналогии, если EOS — это ОС Windows, то chainbase — это файловая система, только очень быстрая (потому как расположена в оперативной памяти). В ней, в разных таблицах хранятся — аккаунты, токены, смарт-контракты — вообще все конечные данные вычислений. Сохранность данных обеспечивается блокчейном. Т.е. если nodeos по какой то причине упадет (выключили свет, например), то данные в chainbase конечно пропадут (оперативная помять является энергозависимой и хранит данные пока на неё подаётся питание), но при старте nodeos создаст в памяти пустые таблицы chainbase и будет по очереди выполнять одну транзакцию из блокчейна за другой попутно наполняя таблицы chainbase. Когда весь существующий на текущий момент блокчейн будет обработан (а это процесс не быстрый) все последующие транзакции будут выполняться с поразительной быстротой, т.к. у нас все необходимые данные хранятся под рукой — в оперативной памяти. Chainbase — одно из ключевых отличий EOS от других платформ, основанных на блокчейне.

Итак, возвращаясь к нашим контрактам. Если вы, по какой-то причине, простили мне упрощение мультииндекса до таблицы, то вот вам еще одно упрощение. Представим, что к каждой таблице, которую мы упомянали в прошлом, добавили еще один столбец — payer (плательщик).

Он хранится где то там в глубинах chainbase, сейчас это не важно. Для каждой в записи в каждой таблице мы можем (и должны) указать кто ею владеет и, следовательно, платит. Чем платит? Свободной RAM. Не хватает RAM — или докупи, или некуда будет токены “сложить”.

Но погодите, вот я смотрю на свой аккаунт, у меня куча разных токенов, а памяти занято не много, как же так? Всё верно, вопрос в том как программист написал контракт и кого он указал в качестве плательщика. Большинство токенов, существующих на данный момент (сентябрь 2018), было распределено посредством airdrop-а. Чтобы позволить записать данные токена в память пользователя нужно спросить у этого пользователя разрешения, чтобы он собственноручно подписал транзакцию. Так поступали как правило те, кто организовывал т.н. airgrab. В случае airdrop-а же необходима была массовая бесплатная раздача токенов. Но бесплатной она быть не может — кто то должен заплатить. И заплатили в данной ситуации владельцы контрактов.

Я сейчас немного пофантазирую в плане трактовки понятия “владения” в системе понятий EOS, предположив, что по настоящему пользователь владеет своими токенами только тогда, когда он заплатил за место под этот токен. Потому как, вне зависимости от логики контракта, контракт не может произвольно манипулировать объемом токенов пользователя не спросив об этом владельца памяти. Это ограничение вводится уже на уровне chainbase и криптографии. Если контракт сам заплатил за память, то, как в случае с ситуацией с Trybe, в зависимости от набора функций контракта, он фактически владеет этими токенами и может делать с ними что хочет.

Основные концепции мы разобрали, осталось ответить на факультативные вопросы в рамках этого базиса.

В: Почему объем занятой памяти меняется при первом получении токенов и не меняется при последующих транзакциях?

О: Если у аккаунта не было определённых токенов контракт заводит на него новую строку в таблице с именем аккаунта и балансом токенов. Если контракт написан так, что за место платит пользователь, то в качестве владельца записи в chainbase указывается пользователь и у аккаунта отнимается столько свободной памяти, сколько требуется для хранения этих данных (с тем учетом, что количество токенов может измениться и мы должны в будущем вместить в эту строку максимальное количество токенов). При последующих транзакциях будет использоваться уже выделенная память и расходоваться будут только CPU и NET. Опять же, это касается только самых примитивных токенов. Контракты разнятся. Так, при заведении нового персонажа в wizards.one придется под каждого занимать новую область памяти.

В: Так что такое аккаунт и почему сразу после создания его память уже чем то занята?

О: Если вы подумаете об аккаунте в разрезе смарт-контрактов, все станет яснее. Представьте, что аккаунт — это такая же строка в таблице какого-то контракта (или точнее группы контрактов), у которой указано, что владеет этими строками сам аккаунт, а в памяти хранятся открытые ключи и всякие статистические параметры, типа количества памяти.

Вот в общем-то и всё. Вместо заключения хочу оставить ссылку на неплохой разбор кода контракта eosio.token где вы можете познакомится уже с настоящими примерами кода:

Understanding The eosio.token Contract

updatedupdated2021-06-202021-06-20