Занурення в Move – мова програмування блокчейну Libra від Facebook

Далі ми розглянемо в деталях основні характеристики мови Move і в чому її ключові відмінності з іншою вже популярною мовою для смарт-контрактів — Solidity (на платформі Ethereum). Матеріал заснований на вивченні доступного он-лайн 26-сторінкового whitepaper-а.

Запровадження

Move - це мова байт-коду, що виконується, який використовується для виконання користувацьких транзакцій і смарт-контрактів. Зверніть увагу на два моменти:

  1. У той час як Move є мовою байт-коду, яка може безпосередньо виконуватися на віртуальній машині Move, Solidity (мова смарт-контрактів в Ethereum) - мова вищого рівня, яка спочатку компілюється в байт-код перед виконанням в EVM (Ethereum Virtual Machine ).
  2. Move можна використовувати не тільки для реалізації смарт-контрактів, але і для транзакцій користувача (докладніше про це буде далі), в той час як Solidity - це мова тільки для смарт-контрактів.


Переклад виконано командою проекту INDEX Protocol. Раніше ми вже перекладали великий матеріал, що описує проект Libra, тепер настала черга трохи детальніше поглянути на мову Move. Переклад виконано спільно з хабраюзером coolsiu

Ключовою особливістю Move є можливість визначати типи користувача ресурсів з семантикою, заснованої на лінійній логіці: ресурс ніколи не може бути скопійований або неявно видалений, тільки переміщений. Функціонально, це схоже на можливості мови Rust. Значення Rust можуть бути призначені тільки одному імені за раз. Надання значення іншому імені робить його недоступним під попереднім ім'ям.

Занурення в Move – мова програмування блокчейну Libra від Facebook

Наприклад, наступний фрагмент коду виведе помилку: Use of moved value 'x'. Це тому, що в Rust немає збирання сміття. Коли змінні виходять із області видимості, пам'ять, яку вони посилаються, також звільняється. Простіше кажучи, може лише один «власник» даних. У цьому прикладі x є початковим власником, а потім y стає новим власником. Детальніше про таку поведінку тут.

Подання цифрових активів у відкритих системах

Існує дві властивості фізичних активів, які важко уявити в цифровому вигляді:

  • рідкість (Дефіцитність, в оригіналі – scarcity). Кількість активів (емісія) у системі має бути контрольованою. Необхідно заборонити дублювання існуючих активів, а створення нових – це привілейована операція.
  • Контроль доступу. Учасник системи повинен мати можливість захистити активи за допомогою контролю доступу.

Ці дві характеристики, які є природними для фізичних активів, потрібно реалізувати і для цифрових об'єктів, якщо ми хочемо вважати їх активами. Наприклад, рідкісний метал має природний дефіцит, і тільки у вас є до нього доступ (тримаючи в руках, наприклад) і ви можете його продати або витратити.

Щоб проілюструвати, як ми дійшли двох цих властивостей, давайте почнемо з наступних пропозицій:

Пропозиція № 1: найпростіше правило без дефіциту та контролю доступу

Занурення в Move – мова програмування блокчейну Libra від Facebook

  • G[K]:=n позначає оновлення числа, доступного за ключем К у глобальному стані блокчейну, новим значенням n.
  • transaction ⟨Alice, 100⟩ означає встановлення балансу рахунку Аліси на 100.

Наведене вище рішення має кілька серйозних проблем:

  • Аліса може отримати необмежену кількість монет, просто відправляючи transaction ⟨Alice, 100⟩.
  • Монети, які Аліса посилає Бобу, не приносять користі, оскільки Боб міг відправляти собі необмежену кількість монет, використовуючи ту ж техніку.

Пропозиція №2: Враховуємо дефіцит

Занурення в Move – мова програмування блокчейну Libra від Facebook

Тепер ми відстежуємо ситуацію, щоб кількість монет Ka було як мінімум, одно n перед транзакцією перекладу. Тим не менш, хоча це вирішує проблему дефіциту, немає жодної інформації про те, хто може відправляти монети Аліси (поки що це може зробити кожен, головне не порушувати правило обмеження кількості).

Пропозиція № 3: Об'єднуємо дефіцит та контроль доступу

Занурення в Move – мова програмування блокчейну Libra від Facebook

Ми вирішуємо цю проблему за допомогою механізму цифрового підпису verify_sig перед перевіркою балансу, що означає, що Аліса використовує свій закритий ключ для підписання транзакції та підтвердження того, що вона є власником своїх монет.

Мови програмування блокчейна

Існуючі мови блокчейна стикаються з такими проблемами (всі вони були вирішені в Move). на жаль, автор статті апелює лише до Ethereum у своїх порівняннях, тому варто сприймати їх лише у такому контексті. Наприклад, більшість із сказаного нижче також вирішено і в EOS)):

Непряме представлення активів. Актив кодується з використанням цілого числа, але ціле чисельне значення — це не те саме, що актив. Насправді, немає типу або значення, що представляє біткойн/етер/Будь-яка Монета! Це робить важким і схильним до помилок написання програм, що використовують активи. Паттерни, такі як передача активів в/з процедур або зберігання активів у структурах, вимагають спеціальної підтримки від мови.

Дефіцит нерозширюємо. Мова представляє лише один дефіцитний актив. Крім того, засоби захисту від дефіциту жорстко зашиті безпосередньо в самій семантиці мови. Розробник, якщо він хоче створити актив користувача, повинен сам ретельно контролювати всі аспекти ресурсу. Це якраз проблеми смарт-контрактів Ethereum.

Користувачі випускають свої активи, токени стандарту ERC-20, використовуючи цілі для визначення як вартості, так і загальної емісії. Щоразу, коли створюються нові токени, код смарт-контракту повинен самостійно перевіряти дотримання правил емісії. Крім того, непряме подання активів призводить, у ряді випадків, до серйозних помилок - дублювання, подвійних витрат або навіть повної втрати активів.

Відсутність гнучкого контролю доступу. Єдина політика контролю доступу, яка зараз застосовується — це схема підпису з використанням асиметричної криптографії. Як і захист від дефіциту, політика контролю доступу глибоко закладена у семантиці мови. Але як розширити мову, щоб дозволити програмістам визначати власні політики контролю доступу — це дуже нетривіальне завдання.

Це також є правильним для Ethereum, де смарт-контракти не мають рідної підтримки криптографії для контролю доступу. Розробники повинні вручну прописати контроль доступу, наприклад, використовуючи модифікатор лишеOwner.

Незважаючи на те, що я великий шанувальник Ethereum, я вважаю, що властивості активів повинні спочатку підтримуватися мовою з метою безпеки. Зокрема, передача Ether у смарт-контракт включає динамічну диспетчеризацію, яка призвела до появи нового класу помилок, відомих як повторний вход (re-entrancy vulnerabilities). Динамічна диспетчеризація означає, що логіка виконання коду визначатиметься під час виконання (динамічна), а не під час компіляції (статична).

Таким чином, у Solidity, коли контракт A викликає функцію контракту B, контракт B може запускати код, який не був передбачений розробником контракту A, що може призвести до вразливості повторного входу (Договор A випадково виконує функцію контракту B, щоб зняти гроші до фактичного відрахування залишків з облікового запису).

Основи дизайну мови Move

Ресурси першого порядку

Якщо говорити високорівнево, то взаємодія між модулями/ресурсами/процедурами у мові Move дуже нагадують відносини між класами/об'єктами та методами в ОПЗ-мовах.
Модулі Move аналогічні смарт-контрактам в інших блокчейнах. Модуль оголошує типи ресурсів та процедури, які визначають правила для створення, знищення та оновлення оголошених ресурсів. Але це — лише умовності (“жаргон”) у Move. Трохи згодом ми проілюструємо цей момент.

Гнучкість

Move додає гнучкості Libra через скрипти. Кожна транзакція в Libra включає скрипт, який фактично є основною процедурою транзакції. Скрипт може виконувати або одну задану дію, наприклад, платежі за вказаним списком одержувачів, або перевикористовувати інші ресурси, наприклад, викликаючи процедуру, в якій встановлено загальну логіку. Ось чому скрипти транзакцій Move пропонують більшу гнучкість. Скрипт може використовувати як одноразові, так і варіанти поведінки, що повторюються, в той час як Ethereum може виконувати тільки повторювані сценарії (викликаючи один метод метод смарт-контракту). Причина, через яку він названий «багаторазовим», полягає в тому, що функції смарт-контракту можуть виконуватися кілька разів. (прим.: тут момент дуже тонкий. З одного боку, скрипти транзакцій у вигляді псевдо-байткоду є і в Bitcoin. З іншого, як я зрозумів, Move розширює цю мову, по суті, до рівня повноцінної мови смарт-контрактів.).

Безпека

Виконуваний формат Move — це байт-код, який, з одного боку, мова вищого рівня, ніж асемблер, але нижчий, ніж вихідний код. Байт-код перевіряється в ран-таймі (on-chain) на наявність ресурсів, типів та безпеки пам'яті за допомогою верифікатора байт-коду, а потім виконується інтерпретатором. Такий підхід дозволяє Move надавати безпеку, характерну для вихідного коду, але без процесу компіляції та необхідності додавати компілятор до системи. Зробити Move мовою байт-коду – це справді гарне рішення. Його немає потреби компілювати з вихідних джерел, як у випадку з Solidity, не потрібно турбуватися про можливі збої або атаки на інфраструктуру компілятора.

Верифікованість

Ми націлені на виконання якнайлегших перевірок, тому що все це йде on-chain (прим.: он-лайн, у процесі виконання кожної транзакції, тому будь-яка затримка призводить до уповільнення всієї мережі), однак спочатку дизайн мови готовий до використання та off-chain засобів статичної верифікації. Хоча це й краще, але поки що розробка засобів верифікації (як окремого тулкіта) відкладена на майбутнє, і зараз підтримується тільки динамічна верифікація в ран-таймі (on-chain).

модульність

Модулі Move забезпечують абстракцію даних та локалізують критичні операції над ресурсами. Інкапсуляція, що забезпечується модулем, у поєднанні із захистом, що забезпечується системою типів Move, гарантує, що властивості, встановлені для типів модуля, не можуть бути порушені кодом поза модулем. Це досить продуманий дизайн абстракції, що означає, що дані всередині контракту можуть змінюватися лише у рамках виконання контракту, але ззовні.

Занурення в Move – мова програмування блокчейну Libra від Facebook

Огляд Move

Приклад скрипту транзакції показує, що зловмисні або необережні дії програміста поза модулем не можуть порушити безпеку ресурсів модуля. Далі ми розглянемо приклади того, як модулі, ресурси та процедури використовуються для програмування блокчейну Libra.

Peer-to-Peer платежі

Занурення в Move – мова програмування блокчейну Libra від Facebook

Задане в amount кількість монет буде переведено з балансу відправника до одержувача.
Тут є кілька нових моментів (виділено червоними написами):

  • 0x0: адреса облікового запису, де зберігається модуль
  • Валюта: назва модуля
  • Монета: тип ресурсу
  • Значення coin, яке повертається процедурою, є значенням ресурсу, тип якого 0x0.Currency.Coin
  • move(): значення не може бути використане знову
  • copy (): значення може бути використане пізніше

Розбираємо код: на першому кроці відправник викликає процедуру з ім'ям withdraw_from_sender з модуля, що зберігається в 0x0.Currency. На другому етапі відправник переказує кошти одержувачу, переміщуючи значення ресурсу монети у процедуру депозиту модуля. 0x0.Currency.

Ось три приклади помилок у коді, які будуть відхилені перевірками:
Дублювання коштів шляхом зміни виклику move(coin) на copy(coin). Ресурси можуть бути лише переміщені. Спроба дублювати кількість ресурсу (наприклад, викликаючи copy(coin) у наведеному вище прикладі) призведе до помилки під час перевірки байт-коду.

Перевикористання коштів, вказавши move(coin) двічі . Додавання рядка 0x0.Currency.deposit (copy (some_other_payee), move (coin)) Наприклад вище дозволить відправнику "витратити" монети двічі - перший раз з одержувачем платежу, а другий з some_other_payee. Це небажана поведінка, неможлива з фізичним активом. На щастя, Move відхиляє цю програму.

Втрата коштів через відмову в move(coin). Якщо не перемістити ресурс (наприклад, вилучивши рядок, що містить move(coin)), буде викликана помилка перевірки байт-коду. Це захищає програмістів Move від випадкової чи зловмисної втрати коштів.

Модуль Currency

Занурення в Move – мова програмування блокчейну Libra від Facebook

Кожний обліковий запис може містити 0 або більше модулів (зображених у вигляді прямокутників) та одне або кілька значень ресурсів (зображених у вигляді циліндрів). Наприклад, обліковий запис на адресу 0x0 містить модуль 0x0.Currency та значення ресурсу типу 0x0.Currency.Coin. Обліковий запис на адресу 0x1 має два ресурси та один модуль; Обліковий запис на адресу 0x2 має два модулі та одне значення ресурсу.

Деякі моменти:

  • Скрипт транзакції атомарний - або повністю виконуватись, або ніяк.
  • Модуль - це довгоживучий шматок коду, глобально доступний.
  • Глобальний стан структурований як хеш-таблиця, де ключем буде адреса облікового запису
  • Облікові записи можуть містити не більше одного ресурсу даного типу і не більше одного модуля із заданим ім'ям (обліковий запис за адресою 0x0 не може містити додатковий ресурс 0x0.Currency.Coin або інший модуль з ім'ям Валюта)
  • Адреса модуля, що декларується, є частиною типу (0x0.Currency.Coin и 0x1.Currency.Coin - це окремі типи, які не можна використовувати взаємозамінно)
  • Програмісти можуть зберігати кілька екземплярів даного типу ресурсу в обліковому записі, визначаючи свій кастомний ресурс.resource TwoCoins {c1: 0x0.Currency.Coin, c2: 0x0.Currency.Coin})
  • Ви можете посилатися на ресурс за його ім'ям без конфліктів, наприклад, ви можете посилатися на два ресурси, використовуючи TwoCoins.c1 и TwoCoins.c2.

Оголошення ресурсу Coin

Занурення в Move – мова програмування блокчейну Libra від Facebook
Модуль з ім'ям Валюта та типом ресурсу з ім'ям Монета

Деякі моменти:

  • Монета - це структура з одним полем типу u64 (64-розрядне ціле без знака)
  • Тільки процедури модуля Валюта можуть створювати чи знищувати значення типу Монета.
  • Інші модулі та скрипти можуть записувати або посилатися на поле значення лише через відкриті процедури, що надаються модулем.

Реалізація депозиту

Занурення в Move – мова програмування блокчейну Libra від Facebook

Ця процедура приймає ресурс Монета як вхідні дані та об'єднує його з ресурсом Монета, що зберігаються на рахунку одержувача:

  1. Знищення вхідного ресурсу Coin та запис її значення.
  2. Отримання посилання на унікальний ресурс Coin, що зберігається на обліковому записі одержувача.
  3. Зміна значення кількості Coin на величину, передану в параметрі виклику процедури.

Деякі моменти:

  • Unpack, BorrowGlobal - Вбудовані процедури
  • Unpack це єдиний спосіб видалити ресурс типу T. Процедура приймає ресурс на вхід, знищує його та повертає значення, асоційоване з полями ресурсу.
  • BorrowGlobal приймає адресу як введення та повертає посилання на унікальний екземпляр T, опублікований (належний) цій адресі
  • &mut Coin це посилання на ресурс Монета

Реалізація withdraw_from_sender

Занурення в Move – мова програмування блокчейну Libra від Facebook

Ця процедура:

  1. Отримує посилання на унікальний ресурс Монета, прив'язаний до облікового запису відправника
  2. Зменшує значення ресурсу Монета за посиланням на вказану суму
  3. Створює та повертає новий ресурс Монета із оновленим балансом.

Деякі моменти:

  • Депозит може бути викликаний будь-ким, але withdraw_from_sender має доступ тільки до монет облікового запису
  • GetTxnSenderAddress схоже з msg.sender у Solidity
  • RejectUnless схоже з вимагати у Solidity. Якщо ця перевірка невдала, виконання транзакції зупиняється і зміни відкочуються.
  • Pack це також вбудована процедура, яка створює новий ресурс типу Т.
  • Так само як Unpack, Pack може викликатись тільки всередині модуля, де описаний ресурс T

Висновок

Ми розібрали основні характеристики мови Move, порівняли її з Ethereum, а також ознайомилися з основним синтаксисом скриптів. На завершення, я настійно рекомендую погортати оригінальний white paper. Він включає безліч деталей, що стосуються принципів проектування мови програмування, а також безліч корисних посилань.

Джерело: habr.com

Додати коментар або відгук