0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Как не остаться без газа в Ethereum, или как научиться писать масштабируемые смарт-контракты

Как не остаться без газа в Ethereum, или как научиться писать масштабируемые смарт-контракты

«Человеческое отношение к неосязаемым вещам переменчиво, но для всего есть предел». — Г. Ф. Лавкрафт

Я уже несколько лет занимаюсь разработкой и написанием кодов для блокчейн-решений на базе Ethereum. Если вы следите за новостями в сообществе, то должны были заметить, что здесь мы все делаем по-другому. Мы одержимы идеей минимализма в кодировании, миримся с нелепыми ограничениями и всегда действуем так, будто результат нашей хаотичной деятельности появится в новостях.

В этой статье я расскажу об одном из тех ограничений, которые являются ключевыми сдерживающими факторами для разработчиков Ethereum, а именно: о размере блока. Помимо неизменности, это чуть ли не самое большое ограничение для развития блокчейна.

В отличие от обычного компьютера, сеть Ethereum вычисляется в блоках, а каждый блок может выполнять ограниченный объем кода. Точный лимит варьируется, но в настоящее время речь идет о 10 миллионах газа (г.). Каждая операция EVM Ethereum имеет разную стоимость газа, но главное, о чем вам нужно помнить: чтение одного элемента данных из хранилища составляет 200 г., а запись одного элемента данных в хранилище – 20 000 г.

Включив математический расчет, вы обнаружите, что один блок может иметь около 50 000 операций чтения, 500 операций записи или комбинацию их двух.

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

Если я разверну контракт в основной сети Ethereum, то любому, кто отправит мне криптокотика, я «отчеканю» и отправлю криптопесика. Таков порядок: за действием следует детерминированная реакция.

Чего блокчейн Ethereum не делает, так это не запускает алгоритм смарт-контракта, который не заканчивается.

Попробуйте сделать это, и вы получите ужасное сообщение о том, что «газ кончился» (ошибка «out of gas»). Для каждого действия мой смарт-контракт может выдать реакцию стоимостью не более 10 миллионов газа. Это означает, что мы должны использовать наши вычислительные ресурсы экономно или разбить задачи на этапы. Если я хочу распределить дивиденды между акционерами с помощью смарт-контракта, то каждый из них должен будет прийти и попросить о них.

Если я запущу цикл для распределения дивидендов, у меня закончится газ, прежде чем я доберусь до 500-го акционера.

Мне нравится, когда написание кодов для приложений приводит меня к математическим расчетам и основным структурам данных. Это все равно что вернуться в университет. Веселые были времена.

Каковы же эти пределы?

При кодировании смарт-контракта вы должны быть очень осторожны с циклами (loops). Цикл – это прямой путь к ошибке «out of gas», разрушающей ваше приложение.

Но кодировать полностью без циклов тоже неприкольно.

Ключ к кодированию смарт-контрактов с максимальной пользой заключается в том, чтобы проявлять особую осторожность со структурами данных, которые мы используем; знать вычислительные пределы, присущие газовым ограничениям блока, и разбивать работу на отдельные вызовы, когда все остальное терпит неудачу.

Чтобы говорить о вычислительных ограничениях, нам нужно будет воспользоваться О-нотацией. Если вам нужно освежить знания, зайдите на сайт википедии и почитайте о O(1), O(log N) и O(N) – ничто другое нам больше не понадобится на данный момент. Я дам вам подсказку: все смарт-контракты Ethereum должны запускаться в крошечном отрезке Excellent, как показано ниже на графике (выделено зеленым):

Если мы рассмотрим затраты газа в размере 200 для операции чтения и 20000 для операции записи, а также предел газа на блок в размере 10 миллионов газа, то мы сможем сделать определенные умозаключения об алгоритмах, применяемых в блоке.

Линейные алгоритмы

Основные структуры данных зависят от алгоритмов, которые имеют тенденцию быть O (N). Это, к примеру, подразумевало бы хранение данных в массиве и циклирование через него, чтобы выполнять операции.

Любой алгоритм O (N), считывающий структуру данных, исчерпает газ, если структура данных вырастет примерно до N = 50000. Для простоты допустим, что это включает в себя только одну операцию записи. Мы можем рассматривать это как операцию поиска и записи.

Любой алгоритм O (N), записанный на всех элементах структуры данных, исчерпает газ, если структура данных вырастет примерно до N = 500. Я назову это операцией множественной записи.

Когда объясняешь на реальном примере, все становится более понятным. Если у вас есть токен, который отслеживает всех держателей токенов, и для какой-то операции вам нужно проверить все их перед обновлением одной переменной контракта, то у вас не может быть более 50 000 держателей токенов. Если ваш токен дает вознаграждение держателям токенов и вы обновляете все балансы в одном и том же вызове, то максимальное число держателей токенов составляет около 500.

Логарифмические алгоритмы

Существуют более сложные структуры данных, в которых алгоритмы, управляющие данными, являются O(log N). Это означает, что для поиска определенного значения в структуре данных, содержащей N элементов, вам нужно всего лишь выполнить log N шагов, что является гораздо меньшим числом.

Любой алгоритм O(log2 N), считывающий структуру данных, израсходует газ, если структура данных вырастет примерно до N = 2**50000. Для простоты допустим, что это включает в себя только одну операцию записи. Мы можем рассматривать это как операцию поиска и записи; и это означает, что если ваш алгоритм поиска в структуре данных равен O(log2 N), то ваш смарт-контракт будет масштабироваться. Максимальное число, которое может быть представлено в Solidity, равно 2* * 256, и это также максимальное число элементов, которые можно хранить в любой структуре данных Solidity.

Это означает, что если ваш алгоритм поиска в структуре данных равен O(log2 N), то ваш смарт-контракт будет масштабироваться.

Любой алгоритм O(log2 N), пишущий на структуре данных, не будет писать на всех N элементах структуры данных, максимум он будет делать это на log2 N этих данных. Это означает, что алгоритм O(log2 N) с несколькими записями исчерпает газ, если структура данных вырастет до N = 2**500, что все еще больше, чем максимальное число, которое существует в Solidity.

Это означает, что если ваш алгоритм записи в структуру данных равен O(log2 N), то ваш смарт-контракт будет масштабироваться.

Какой алгоритм использовать?

Мне нравится облегчать жизнь себе и другим. Теперь, когда мы знаем общие пределы и их причины, мы можем «вернуться в университет» и наметить все, что мы можем и не можем сделать:

В информатике существует в основном четыре структуры данных:

  1. Массивы (Arrays);
  2. Связанные списки (Linked Lists);
  3. Хэш-таблицы (Hash Tables);
  4. Деревья (Trees).

Хэш-таблица

Хэш-таблицы представляют собой довольно развитую структуру данных в большинстве вычислительных языков, за исключением языка Solidity. В Solidity все, что относится к хэш-таблице, мы называем мэппингом (mapping). Даже массивы реализованы в виде мэппингов. При реализации связанных списков я использую мэппинги. Когда мэппинги – это все, что у вас есть, то это по сути и все, что вы используете.

Чтение из мэппинга всегда O(1), запись в мэппинг всегда O (1). Тут нет встроенной функции поиска, для этого вам нужно реализовать одну из других структур данных. Все это означает, что вы должны по возможности использовать только мэппинги, и так будет безопасно. Предел размера для мэппинга в Solidity составляет 2* * 256.

Массивы

Массивы – это забавная штука в Solidity, но они также являются единственной встроенной структурой данных, которая может содержать несколько элементов и поддерживает функцию поиска.

Массивы могут содержать 2 * * 256 элементов, если вам не нужно проходить все позиции в одном вызове, и в конце вы только вставляете или удаляете элементы. Если вам нужно проверить все позиции в массиве перед записью в хранилище, вам нужно будет ограничить длину своего массива до примерно 50 тысяч, возможно, и меньше. Массивы следует вставлять только в конце и нигде больше.

Связанные списки

Связанные списки – это ваш выбор структуры данных, когда вам нужно сохранить порядок вставок, а также когда вы хотите вставлять данные в произвольные позиции. Вы можете использовать их для хранения 2 * * 256 элементов, как, например, массивы, для доступа к и произвольной вставки данных. Как и в случае с массивами данных, если вам нужно посетить все позиции в одном вызове, вам нужно ограничить их длину до 50 тысяч элементов.

Вы можете использовать связанные списки, чтобы сохранить элементы данных в определенном порядке, тем самым вставки будут происходить в соответствующей позиции. Такая вставка будет иметь стоимость O(N) чтения и O (1) записи, поэтому она ограничит длину вашего списка до нескольких десятков тысяч без дальнейшей доработки.

Деревья

Деревья – это структура данных, которую вы используете в Solidity, если вам нужно эффективно выполнять поиск в упорядоченном наборе данных. Они сложны, но есть несколько реализаций (дерево порядковой статистики, красно-черное дерево), которые вы можете использовать, если чувствуете себя достаточно продвинутым специалистом. Все операции в дереве имеют сложность O (log N), что означает, что вы можете поддерживать дерево астрономического размера.

Если вы используете дерево для хранения данных, то у вас практически нет ограничений в размере структуры для операций поиска и записи, а также ограничения в тысячу миллионов элементов для операций множественной записи. Тем не менее, вы никогда не сделаете больше, чем несколько сотен операций записи в одном вызове.

Однако использование деревьев имеет свои собственные недостатки. Они сложны для написания и тестирования. Их развертывание сопряжено с большими затратами. На мой взгляд, использование такой сложной структуры данных является огромным риском для вашего проекта.

У вас есть какие-нибудь доказательства всего этого?

Не верьте мне, проверьте сами. Этот небольшой контракт можно использовать для проверки того, сколько операций чтения или записи помещается в блок:

Существуют некоторые дополнительные затраты газа для управления этими циклами и для того, чтобы сделать операцию чтения транзакцией изменения состояния за счет выпуска события, но, как мне кажется, эти функции закончатся после нескольких сотен операций записи или десятков тысяч операций чтения. Вот, что будет, если я ограничу размер блока до 10 миллионов:

Вывод

Мне потребовалось некоторое время, чтобы наконец понять вычислительные пределы смарт-контрактов. В этой статье я дал четкое и краткое руководство того, что вы можете и не можете сделать в вызове к смарт-контракту Ethereum.

  1. Все является мэппингом, используйте их всегда, если вам не нужно искать по содержимому.
  2. Используйте массив, если вам нужно выполнить поиск и вы можете принять вставку и удаление только в конце его.
  3. Используйте связанные списки, если вам нужно выполнить поиск и вставить данные в произвольные позиции или сохранить упорядоченные данные.
  4. Используйте дерево, если вам необходимо эффективно выполнять поиск в больших наборах данных.

Для всего остального вам нужно будет разбить свой код на различные вызовы и построить машину состояний. Вам также следует подумать, нужно ли вообще кодировать это в блокчейн.

Если вам нужно посетить все элементы структуры данных, ваш предельный размер составляет несколько десятков тысяч. Если вы должны написать для каждого элемента в структуре данных, то ваш предельный размера будет несколько сотен.

Как работают смарт контракты в сети Эфириум?

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

Но биткоин может быть использован только как валюта.

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

С помощью Ethereum можно создавать собственные смарт контракты или так называемые «автономные агенты». Данный язык является «Тьюринг-полным», что означает, что с его помощью можно реализовать практически любую функцию.

Смарт-контракты Эфириум могут:

  • Функционировать, как учетные записи с несколькими подписями, так что средства переводятся только тогда, когда это будет согласовано с определенным процентом участников
  • Управлять соглашениями между пользователями, например, если один покупает страховку у другого
  • Быть полезными для других контрактов (подобно тому, как работает библиотека программного обеспечения)
  • Хранить информацию о приложении, например, информацию о регистрации домена или записи о членстве

Сила в цифрах

Экстраполируя этот последний пункт, смарт контракты Эфириума, вероятно, нуждаются в помощи от других умных контрактов.

Когда кто-то ставит ставку на температуру в жаркий летний день, это может вызвать последовательность связанных контрактов.

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

Запуск каждого контракта требует оплаты транзакций в валюте Эфириума — Эфире, сумма которых зависит от требуемой вычислительной мощностти.

Как упоминается в статье «Как работает Эфириум?», ethereum запускает код смарт-контракта, когда пользователь или другой контракт отправляет ему сообщение с достаточной комиссией.

Затем виртуальная машина Ethereum выполняет умные контракты в «байт-коде», представляющих собой серии единиц и нулей, которые могут быть прочитаны и интерпретированы сетью.

Какие проблемы таят в себе смарт-контракты Ethereum

Концепция смарт-контрактов была впервые описана Ником Сабо в 1996 году.

Но не все так гладко: столь масштабные идеи неизменно влекут за собой ошибки и неточности. Какие проблемы в смарт-контрактах одного из самых больших и старых блокчейнов уже обнаружены?

Что не так с Ethereum?

Спустя 20 с лишним лет абстрактная идея смарт-контрактов воплотилась в реальность. Потенциал, присущий смарт-контрактам, огромен. Зарождающаяся технология может использоваться для проверки подлинности, безопасного обмена данными, а также для управления токенами и привлечения средств при ICO. Так, блокчейн Ethereum, одна из первых площадок, реализовавших смарт-контракты в своем блокчейне, имеет более 1500 децентрализованных приложений, каждое из которых использует смарт-контракты для выполнения самых разных задач. Однако проблема со смарт-контрактами заключается в том, что они основаны на коде, и некоторые ошибки в нем могут стать в буквальном смысле катастрофическими.

По мнению ряда экспертов, у Ethereum есть «родовая травма», поскольку его блокчейн в значительной степени построен в Solidity — продвинутом языке программирования. Таким образом, многие разработчики должны изучить совершенно новый язык, что увеличивает вероятность человеческой ошибки.

Ошибки BatchOverflow

И такого рода ошибки не заставили себя ждать. 23 апреля 2018 года компания PeckShield, занимающаяся безопасностью на блокчейне, заявила о нахождении ошибки BatchOverflow сразу в нескольких смарт-контрактах ERC20.

Разработчики создали аналитический алгоритм переносов токенов ERC-20. Система предназначена для автоматического уведомления о подозрительных транзакциях. В итоге «улов» не заставил себя долго ждать — программа издала тревожный сигнал, увидев странную транзакцию токена BEC. В этой сделке было перечислено запредельное количество токенов BEC — 0x8000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,00)

Такая необычная транзакция побудила разработчиков посмотреть код смарт-контракта. В ходе исследования выяснилось, что такая передача может стать следствием атаки «in-the-wild», которая воспользовалась ранее неизвестной уязвимостью в контракте (batchOverflow).

Уязвимая функция располагалась в batchTransfer. В строке 257 видно, что локальная переменная суммы находится с помощью умножения cnt и _value. Вторая же переменная (_value) может и вовсе быть рандомным 256-битным целым числом (к примеру, 0x8000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,0000,00).

Таким образом, имея в своем распоряжении два _receivers, отправленных в batchTransfer (), с этим чрезвычайно большим значением, мы можем переполнить сумму и сделать ее равной нулю. В случае обнуления злоумышленник может спокойно пройти проверки работоспособности в строках 258−259, после чего сделать вычитание в строке 261 абсолютно неактуальным.

В итоге, как продемонстрировано в строках 262−265, баланс двух кошельков пополнится огромной суммой. Интересно, что, по словам разработчиков, на тот момент более десятка контрактов ERC20 были также уязвимы для batchOverflow.

По словам команды, тогда они уже перепробовали все возможные способы связи с разработчиками, однако после введения принципа «кодекс-закон» в блокчейне Ethereum больше не было общеизвестного механизма защиты безопасности для устранения этих уязвимых контрактов.

Блудные, жадные, суицидальные — основные характеристики смарт-контрактов эфира?

В начале 2018 года пятеро исследователей из Великобритании и Сингапура с помощью созданного ими инструмента MAIAN, служащего для обнаружения уязвимостей непосредственно через байт-код и не требующего доступ к исходному коду, нашли 34,200 смарт-контрактов, которые могут иметь потенциальные баги и хранят в себе информацию о транзакциях на сумму в несколько миллионов долларов в эфире.

Они разделили уязвимые контракты на условные три группы: суицидальные, блудные и жадные.

Блудные контракты

Функция tap блокирует эфир, поскольку условие в строке 4 никогда не может быть выполнено. Тем не менее оптимизация компилятора Solidity позволяет этому произойти, когда для вызова функции используется вход более 20 байтов. На уровне байтового кода EVM сможет загрузить только куски из 32 байт входящих данных. В строке 3 при нажатии первые 20 байт присваиваются переменной prev, а остальные 12 байт просто игнорируются. Такая ошибка возникает, поскольку EVM в строке 4 аккуратно сводит на нет 12 байт prev. Этот контракт потерял 5.0001 эфира с разных адресов в блокчейне Ethereum.

Суицидальные контракты

Контракт может быть убит путем использования незащищенной инструкции SUICIDE. Тривиальный пример — это функция общественного уничтожения, в которой размещается инструкция suicide. Иногда SUICIDE защищен слабым условием. Этот контракт позволяет пользователям покупать токены или выводить свои средства. Логика вывода средств осуществляется функцией вывода. Однако эта функция имеет инструкцию self_destruct, которая может быть выполнена в случае, если последние средства были внесены в него более 4 недель назад. Следовательно, если «инвестор» вызывает эту функцию через 4 недели после последнего вложения, все средства идут к владельцу контракта, и все записи «инвесторов» стираются с блокчейна.

Жадные контракты

Контракт SimpleStorage является примером контракта, который блокирует эфир в неограниченных объемах. Если произвольный адрес отправляет эфир вместе с транзакцией, которая вызывает функцию set, баланс контракта увеличивается пропорционально количеству отправленного эфира. Когда произвольный адрес отправляет эфир вместе с транзакцией, вызывающей функцию set, баланс контракта увеличивается на количество отправленного эфира. Однако в контракте нет инструкций по выпуску эфира, и, таким образом, он блокирует его на блокчейне.

Ключевое слово payable было введено в Solidity не так давно, чтобы предотвратить принятие функциями эфира по умолчанию — функции, не имеющие ключевого слова payable, не выполняются, если в ходе транзакции пересылается эфир. Однако, хотя этот контракт не имеет никакой функции, связанной с payable, он принимает эфир, поскольку он был скомпилирован более старой версией компилятора Solidity (без поддержки payable).

Делая выводы своей работы, исследователи пришли к довольно неутешительному итогу.

Будут ли решены проблемы?

Очевидно, что из-за человеческого фактора, связанного с применением в Ethereum Solidity, и рядом других проблем, которые, скорее всего, не исчезнут даже после нахождения и устранения всех багов, недостатки у смарт-контрактов Ethereum, конечно же, останутся. Тем не менее минимизировать их вполне реально. Команда уже начала вводить Vyper — похожий на Solidity, но более легкий в использовании язык. К тому же, Ethereum 2.0 уже не за горами — в 2018 году блокчейн-сообщество увидит «вторую фазу» взросления системы. Может быть, именно эти меры приведут к решению основных проблем ее смарт-контрактов?

Будь в курсе! Подписывайся на Криптовалюта.Tech в Telegram.
Обсудить актуальные новости и события на Форуме

Ссылка на основную публикацию
Статьи c упоминанием слов:

Adblock
detector