Вечное хранилище + топология концентратора и спиц = ♥

Контракты Ethereum - удивительное изобретение; автономный неизменяемый код, который может выступать в роли судьи, присяжного и палача для множества различных сервисов. Однако после развертывания в блокчейне Ethereum они, по сути, остаются в камне. Это означает, что если появится серьезная ошибка или проблема и ваши контракты не разработаны таким образом, чтобы их можно было легко обновить в вашем Dapp, вы можете быть рады, что купили эти 5 пакетов коричневых трусов.

260 миллионов причин

Существует множество причин, взломов и небрежного кодирования, которые привели к потере эфира на сумму более 260 миллионов долларов за последние годы. Двумя наиболее известными из них были взлом DAO, который потерял около 60 миллионов долларов, и гораздо более свежий взлом Parity Multisig Wallet, в результате которого эфир и токены на сумму около 162 миллионов долларов оказались в подвешенном состоянии в блокчейне на возможную вечность.

Не все эти инциденты можно было предотвратить, сделав контракты обновляемыми, поскольку некоторые из них были обнаружены только после того, как был нанесен ущерб. Самая большая и самая последняя из них, взлом Parity Multisig Wallet, безусловно, могла быть исправлена ​​в кратчайшие сроки, если бы не этот жестко закодированный адрес контракта библиотеки в кошельке с мультиподписью в строке 451 ...

// FIELDS
address constant _walletLibrary = 0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4;

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

address _walletLibrary = 0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4;
/// @dev Set a new wallet library contract address
function setLibraryAddress(address _newAddress) external onlyOwner {
      _walletLibrary = _newAddress;
}

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

Наш подход

При проектировании и создании Rocket Pool сложного многопользовательского Dapp-приложения на ранней стадии было сделано два основных дизайнерских решения.

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

Первый не требует пояснений: все контракты должны обновляться в сети. Если у нас есть проблема с одним контрактом, его адрес должен быть редактируемым, чтобы мы могли заменить ошибочный контракт новой его копией без ошибок, а все остальные контракты в Dapp теперь должны взаимодействовать с новой версией. Хорошо, звучит великолепно! Но многие новые разработчики не понимают, что все данные, которые хранятся в контракте с ошибками, теперь исчезли ...

Представьте, что у вас есть контракт под названием Users.sol, в котором хранится список всех пользователей, которые подписываются на ваше Dapp, но оказывается, что Users.sol содержит серьезную ошибку. , ваш разработчик Гарри спешит заменить этот контракт с ошибками на новый, при этом он развертывает новый фиксированный Users.sol и обновляет его адрес, чтобы все контракты в вашем Dapp теперь говорят с фиксированным контрактом. Гарри очень счастлив, горд, что он починил его так быстро, и скоро ему захочется также купить 5 пачек коричневых трусов.

Поскольку Гарри скоро осознает, что хранилище контрактов не может быть так легко заменено, он только что исправил ошибку в коде Users.sol, и теперь все контракты в вашем Dapp общаются с новый фиксированный контракт, но все записи их пользователей в Dapp исчезли, вместо этого они застряли в хранилище старых контрактов… ой.

Вторая цель состоит в том, чтобы создать способ изолировать хранилище данных от ваших контрактов и сделать его максимально гибким, чтобы вряд ли когда-либо потребовалось обновление.

Вечное хранилище

Изучая различные методы хранения для блокчейна Ethereum, я наткнулся на статью Елены из Colony о Вечном хранилище. Это относительно старая статья, но в этой статье подробно описывается создание простого единого контракта, в котором хранятся все постоянные данные, которые нужны вашему Dapp. Многие из вас могут распознать эту технику хранения как хранилище пар ключ / значение, простой и расширяемый способ хранения любых данных от простых значений до массивов и данных сложного типа объекта.

Когда Елена писала эту статью, другой контракт не мог использовать некоторые методы из ее контракта на хранение, такие как:

function setStringValue(bytes32 record, string value)

Это было связано с тем, что отправка данных переменной длины (строки, массивы) была невозможна в период между контрактами. Благодаря последнему хардфорку, который произошел совсем недавно в октябре в качестве первого этапа обновления Metropolis до сети Ethereum, это теперь возможно.

Ракетное хранилище

Ниже представлена ​​обновленная версия исходного Вечного хранилища, которое теперь используется для хранения любых постоянных данных в Rocket Pool.

Для простоты существует единственный модификатор, ограничивающий доступ к методам set и delete. Это позволит только владельцу контракта на хранилище напрямую обращаться к этим методам, чтобы установить некоторые начальные адреса контракта во время развертывания, после развертывания их доступ будет удален, чтобы гарантировать, что только видимые методы контракта могут записывать в хранилище. С этого момента только зарегистрированные контракты в сети Rocket Pool могут записывать в хранилище.

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

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

Проектирование топологии концентратора и луча

Хорошо, теперь у нас есть специальный контракт на постоянное хранилище, уууу! Следующим шагом будет использование этого контракта на хранение в качестве концентратора в нашем Dapp. Если вы когда-либо были в мире сетевых технологий, вы, несомненно, знакомы с топологией концентратора и луча. Это метод, при котором все коммуникации проходят через один объект. Поскольку наш контракт на хранение никогда не будет нуждаться в обновлении, мы всегда знаем, где он будет, поэтому давайте использовать его в качестве нашего контрактного центра Dapps.

Теперь предположим, что у нас есть Dapp с тремя контрактами: Users.sol, Organisations.sol и Storage.sol. Контракт Users.sol теперь будет хранить пользователей Dapps на Storage.sol, а не сам, как в примере с плохим Гарри. Это означает, что если он будет обновлен, все текущие пользователи останутся в Dapp и не будут потеряны из-за ошибочного контракта.

Users.sol также должен будет периодически получать доступ к контракту Organisations.sol, чтобы узнать, к какой организации принадлежит конкретный пользователь. Вместо того, чтобы контракт User.sol напрямую взаимодействовал с контрактом Organisation.sol при стандартном подходе, теперь он будет запрашивать у контракта Storage.sol (Hub) адрес контракта Organisation.sol, когда он получает этот адрес, он затем связывается с договором Organisation.sol, используя адрес, предоставленный Storage.sol.

Это позволяет нам заменять адрес адреса контракта Organisation.sol в любое время, когда он нуждается в обновлении, когда новый обновленный контракт развертывается, мы просто обновляем адрес в Storage.sol (Hub) новым адресом обновленный Organisation.sol и в следующий раз, когда контракту User.sol потребуется связаться с Organisation.sol, он получит адрес нового обновленного контракта. Это позволяет легко обновиться, не требуя коричневых трусов.

Как хранить и читать адреса контрактов в хабе

Сохранить адрес контракта в хабе довольно просто, в конце концов, это всего лишь контракт на хранение. Когда Rocket Pool развертывается через трюфель, он автоматически регистрирует все контрактные адреса в сети в хабе при развертывании. Ниже приведен пример контракта о том, как зарегистрировать имя и адрес контракта в хабе + как прочитать адрес другого контракта из хаба перед взаимодействием с этим контрактом.

Преимущества

Есть много преимуществ в создании такой системы хранения и концентратора для вашего Dapp, два самых больших преимущества, на мой взгляд, заключаются в следующем:

  • Гибкость. Вы можете добавлять в свое Dapp совершенно новые контракты с различными типами данных и структурами, которые могут храниться в контракте хранения без необходимости его обновления.
  • Безопасность / Обновления. Если обнаружены ошибки, вы можете отказаться от старого контракта, развернуть новый, не теряя постоянного хранилища, и этот новый контракт будет автоматически мгновенно использоваться каждым контрактом в вашем Dapp.
  • Трусы. Возможно, вам больше не потребуется покупать 5 пакетов коричневых трусов для развертывания / обновления Dapps. Если вам не нравятся коричневые трусы, тогда вышибите себя.

Недостатки

Конечно, ни одно решение не является идеальным, когда речь идет о платформах нового поколения. Основные недостатки, с которыми мы столкнулись до сих пор:

  • Размер контракта при развертывании будет увеличиваться из-за хеширования данных, требуемого контрактом на хранение. Это будет проблемой при развертывании контрактов, особенно действительно крупных контрактов, которые уже близки к пределу газового блока.
  • Дополнительные звонки. Поскольку связь между контрактами теперь осуществляется через концентратор, это приводит к дополнительному вызову. То же самое для хранения и извлечения общих данных из контракта на хранение.
  • Сложность. Это добавит сложности вашему Dapp.

Заключение

Существует множество способов создания обновляемых контрактов в Ethereum, это только наш любимый вариант и комбинация двух существующих методов, которые, как мы обнаружили на собственном опыте, действительно хороши для обеспечения простого, гибкого и обновляемого пути для любого Dapp. Мы коснулись здесь только поверхностных возможностей, для более полного представления, пожалуйста, посетите Rocket Pool Github.