Или как сделать вечный симулятор Game of Life

Одно из преимуществ WebAssembly, о котором мы постоянно говорим, - это возможность повторно использовать существующий код. Было опубликовано несколько статей, демонстрирующих, насколько легко создавать приложения WebAssembly, которые работают на децентрализованной платформе виртуальных машин / смарт-контрактов Wavelet, но не было показано, насколько легко это сделать. портировать существующие приложения пока нет.

В этой статье мы возьмем великолепную реализацию Game of Life из Rust and WebAssembly Book и рассмотрим, что вам нужно изменить, чтобы она работала на Wavelet, и закончим со своей собственной вечной вселенной клеточных автоматов.

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

Настраивать

Мы можем использовать тот же процесс настройки, который описан в этапах настройка и привет, мир в руководстве по Rust Wasm, с небольшой разницей в добавлении двух зависимостей к Cargo.toml, а именно:

smart-contract = "0.2.0"
smart-contract-macros = "0.2.0"

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

Взаимодействие с Javascript

Первая реализация WebAssembly, в которую входит руководство Rust Wasm, подробно здесь, описывает, как JavaScript взаимодействует с WebAssembly и наоборот - это отличается в приложениях Wavelet и значительно меняет способ написания интерфейса.

  • Rust Wasm использует макрос #[wasm_bindgen] для экспорта ссылок способом, который может быть вызван из JavaScript, и предпочитает прямой доступ к памяти к виртуальной машине WebAssembly для высокопроизводительных обновлений.
  • Вейвлет использует макрос #[smart_contract], чтобы сделать функции в единой структуре доступными для библиотеки wavelet-client. Прямой доступ к памяти не рекомендуется, поскольку узкое место для производительности вейвлет-приложения находится не на уровне сериализации / десериализации, а на уровне консенсуса вейвлетов.

Взаимодействие с Game of Life

Реализация Книги использует единый массив для хранения вселенной в линейной памяти WebAssembly - мы сделаем то же самое. Мы также будем использовать тот же строковый формат для рендеринга игры. Прямой доступ к памяти также возможен, но явно не поддерживается, поэтому мы не получаем тех же легко импортируемых привязок, которые мы видим в учебнике Rust Wasm, поэтому доступ к нему немного сложнее.

Реализация Rust

Это круто - мы можем скопировать весь код напрямую! Нам нужно сделать только две незначительные модификации - удалить макрос #[wasm-bindgen] и добавить структуру смарт-контракта, которая будет предоставлять функции Universe вейвлету:

Рендеринг с помощью JavaScript

На данный момент реализация значительно отличается. В учебнике Rust Wasm они использовали привязки wasm-pack для прямого импорта скомпилированного кода Rust в свой интерфейс HTML + Javascript; нам нужно пройти еще несколько шагов; сначала загрузить наш скомпилированный wasm в распределенную виртуальную машину, а затем написать код коннектора, который создаст экземпляр виртуальной машины локально и синхронизирует его с глобальным состоянием.

Мы постараемся приблизиться к их реализации и выделить основные отличия.

Во-первых, нам нужно загрузить контракт, чтобы мы могли получить к нему доступ во внешнем интерфейсе. Мы можем выполнить те же шаги, которые подробно описаны в руководстве по децентрализованному чату, просто используйте команду wasm-pack build для создания вашего .wasm файла и используйте файл wasm_game_of_life_bg.wasm в папке pkg в корне вашего проекта.

Чтобы взаимодействовать с загруженной программой в JavaScript, нам нужно добавить зависимости wavelet-client & jsbi с npm i -s wavelet-client jsbi или yarn add wavelet-client. Имея эти зависимости, мы можем приступить к программированию.

Наша реализация довольно проста, всего 40 строк js и html..

HTML остается почти таким же, за исключением того, что мы импортируем файл index.js вместо файла bootstrap.js:

Код JavaScript немного более подробный по сравнению с 9 строками кода в реализации Rust Wasm.

В файл index.js мы сначала импортируем зависимости wavelet-client и jsbi:

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

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

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

Как только он будет завершен, мы можем взаимодействовать с ним двумя способами: call добавляя функции или test добавляя их.

Вызывающие функции выполняют их на распределенной виртуальной машине и изменяют глобальное состояние, а также требуют комиссий за транзакции и обработку. Поскольку вы можете довольно легко запустить сеть Wavelet локально или получить PERL тестовой сети бесплатно, это ничего вам не будет стоить, но все же побудит вас писать эффективный код. На данный момент мы рекомендуем 10 миллионов PERL тестовой сети, поскольку Wavelet еще не установил десятичную точность - в действительности для работы этого алгоритма потребуется крошечная часть PERL основной сети, что приведет к затратам, сравнимым с выполнением функции AWS Lambda.

Функции тестирования выполняют их на локальной виртуальной машине, используя текущую память виртуальной машины. Это бесплатно, но не приводит к постоянным изменениям состояния ВМ.

Мы используем test для вызова render функции смарт-контракта, которая вернет строку в результирующих журналах. Вся сериализация / десериализация происходит через этот интерфейс журнала, аналогично приложениям CLI:

Затем мы устанавливаем textContent тега pre как результирующее значение, которое даст нам визуальное представление вселенной Game of Life.

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

Мы регистрируемся на событиях консенсуса и синхронизируем память контракта в каждом раунде. Затем мы визуализируем текущее состояние, как и раньше, и, наконец, call функцию step, которая продвигает состояние вселенной на один тик.

Вот и все! Рабочий симулятор Game of Life, который может развиваться до тех пор, пока к распределенной виртуальной машине подключены узлы.

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

Добавление интерактивности

Для этого нам нужно изменить как код Rust, так и интерфейс HTML + JavaScript.

Следуя руководству, мы сначала добавим кнопку для запуска и приостановки моделирования.

Мы добавляем кнопку непосредственно перед элементом <pre> в index.html:

Затем мы добавляем переменную в index.js, чтобы отслеживать, должна ли игра запускаться, и некоторую логику для изменения переменной и проверки ее при попытке продвинуть вселенную:

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

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

Переключение ячеек

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

После внесения изменений нам нужно повторно развернуть контракт и обновить код JavaScript с новым адресом контракта.

Наша реализация внешнего интерфейса очень похожа на реализацию учебника Rust Wasm, за исключением того, что мы по-прежнему используем реализацию тега <pre> и вызываем контракт Wavelet вместо импортированного кода Wasm.

И все, теперь вы всемогущий правитель вселенной, способный убивать и создавать клетки где угодно!

Заключительные шаги

Заключительные части обложки книги rust-wasm временное профилирование, уменьшение размера файла .wasm и публикация в npm . Все эти шаги также относятся к приложениям Wavelet.

Профилирование времени

Возможно, вы заметили, что для моделирования Вселенной требуется много PERL тестовой сети, около 2 миллионов за шаг! Мы можем применить ту же оптимизацию, которая подробно описана в руководстве по Rust Wasm, чтобы сократить это примерно до 700 тысяч PERL.

Уменьшение размера .wasm

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

Публикация в npm

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

Соображения по вейвлетам

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

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

Чтобы этого избежать, нам нужно сделать кошелек динамичным - для этого есть несколько способов, а именно:

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

Вейвлет еще не имеет расширения для браузера, поэтому мы не можем использовать вариант 3. Вариант 2 был бы самым простым для реализации, но он усложнил бы жизнь нашим пользователям. Остается вариант 1 - давайте посмотрим, как мы это сделаем.

Чтобы сгенерировать случайный кошелек, мы можем вызвать удобную функцию generateNewWallet.

Это даст нам кошелек, который сможет визуализировать вселенную, но не будет ее моделировать или изменять.

Чтобы заполнить кошельки токенами тестовой сети, мы можем использовать наш бесплатный REST API крана для пополнения кошелька.

Кран обеспечивает 500 тыс. PERL на запрос - это означает, что нам нужно дважды нажать на сборщик, чтобы смоделировать один шаг. Это не идеально, потому что сборщик будет ограничивать нас одним звонком каждые 10 секунд, а Game of Life не будет очень интересной, если мы сможем обновлять ее только три раза в минуту!

К счастью, Wavelet позволяет нам вносить газ в смарт-контракт, а это означает, что пользователям не нужно платить комиссию за газ - только гораздо меньшую комиссию за транзакцию в 2 PERL. Мы можем внести в контракт несколько миллиардов PERL за газ (присоединяйтесь к нашему разногласию, и мы с радостью пришлем вам немного), что позволит любому пользователю продвинуть моделирование в 250 тысяч раз.

Вы можете внести газ через Lens (веб-приложение, используемое для загрузки контракта) или через клиент JavaScript, вызвав функцию с установленным значением депозита:

Мы также должны убедиться, что мы уменьшаем значения газа, указанные в настоящее время в существующих contract.call параметрах - в настоящее время они равны 1e7, чтобы обеспечить достаточно газа для выполнения дорогостоящих вызовов - мы можем уменьшить это значение до 1 PERL.

Теперь, когда мы больше не храним конфиденциальную информацию в коде и у нас есть контракт со значительным количеством депонированного газа, мы готовы опубликовать приложение!

Вы можете увидеть наш экземпляр вселенной, работающий здесь: https://ryanswart.github.io/decentralized-game-of-life и последний код, который мы реализовали здесь: https://github.com/ryanswart/ децентрализованная игра жизни

Первоначально опубликовано на http://github.com.