О смартконтрактах

2021-07-24

Вастрик уже всё рассказал (в 2017). Про блокчейн и Биткойн. Про смартконтракты и Эфириум. Кратко перескажу эти замечательные статьи. И добавлю немного про смартконтракты от Binance.

Биткойн

Все эти наши блокчейны начались с Сатоши Накамото. В 2009 году. Он (или они) не просто описал, какими могут быть децентрализованные цифровые деньги, но и запустил Bitcoin. Намайнил первые блоки.

Начинается Биткойн довольно очевидно. Есть кошельки. Есть ассиметричная криптография. Перевод денег с кошелька на кошелёк оформляется в виде транзакции, подписанной приватными ключами.

В Биткойне отсутствует баланс счёта. Нет такой одной красивой цифры. Вместо этого каждая транзакция, которая тратит деньги этого кошелька, должна сослаться (указать хэш) на транзакции, где на этот кошелёк поступала достаточная сумма. И направить полученную сумму нужному получателю. А «сдачу» завернуть обратно себе.

Инпуты — прошлые транзакции. Аутпуты — адреса кошельков.

транзакция Bitcoin

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

А так как никто никому не доверяет, то проводят эти проверки все. А подтверждением транзакции является её включение в блок.

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

А сгенерировать блок не так уж и просто. Требуется, чтобы хэш блока был меньше определённого числа. Сложности. То есть, нужно, чтобы хэш «начинался с нулей». Чтобы был «красивым».

Чтобы управлять значением хэша, в блоке есть специальные nonce числа, которые ничего не значат, но их можно менять. Подсчитать хэш по содержимому блока можно быстро. А вот подобрать такой nonce, чтобы получить «красивый» хэш — непросто. Настолько непросто, что всем компьютерам Bitcoin сети приходится трудиться в среднем десять минут, чтобы родить следующий блок. Сложность вычисления хэша блока, то есть, степень его «красивости», может варьироваться со временем, подстраиваясь под текущую мощность сети, чтобы на создание нового блока всегда уходило приблизительно десять минут.

Это и называется «майнинг». А сама концепция, что нужно хорошенько повычислять, чтобы родить очередной блок, называется «Proof-of-Work».

Майнеры трудятся не просто так. Они получают вознаграждение. 6.25 BTC (в настоящий момент, это значение со временем уменьшается) берутся «из воздуха». Просто выдаются тому, кто создаст блок. Специальная транзакция в начале блока. Это и есть эмиссия биткойнов.

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

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

Поэтому есть ещё одно правило. Истиной считается самая длинная цепочка блоков (и с наиболее «красивыми» хэшами). Да, где-то произошло разветвление. Но потом, после развилки, каждая веточка будет прирастать новыми блоками. И рано или поздно (в случае Биткойна, скорее рано), одна из веток станет длиннее. А остальные побочные ответвления будут отброшены.

В этом весь смысл «доверия» в блокчейне. Какой-нибудь злобный майнер может захотеть подправить пару транзакций в прошлом. Но ему нужно нагенерировать последующие блоки, чтобы сравняться по длине всей цепочки с «настоящей» цепочкой. А пока он это делает, «настоящая» цепочка тоже прирастает новыми блоками. Получается, злобному майнеру нужно тягаться вычислительной мощностью (в случае Proof-of-Work), со всей остальной сетью. Что, вроде как, затруднительно, потому что участников сети много, и они каждый сам за себя.

Кто хочет разобраться, может сам посмотреть на настоящий живой блок Биткойна.

Ethereum

В Биткойне для выполнения транзакций запрятана целая простенькая виртуальная машина. И вот, в очередной реализации блокчейна, созданной в 2015 году Виталиком Бутериным, «канадско-российским программистом», эту идею развили дальше. Так появился Ethereum.

В Ethereum, кроме «кошельков» настоящих человеческих пользователей, отправлять и получать деньги (эфиры, ETH) могут контракты (смартконтракты).

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

Методы контрактов пишутся на специальном языке Solidity (или Vyper, или Serpent, или LLL, или Mutan). И компилируются в байткод для виртуальной машины Эфириума.

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

Каждая операция байткода виртуальной машины имеет свою «стоимость» в Gas. Это «топливо» виртуальной машины. У каждой транзакции установлен лимит этого Газа. Транзакция прерывается с ошибкой, если Газ закончится. Это защита от бесконечных циклов и ошибок программистов, которые могли бы привести к бесконечному потреблению ресурсов на узлах сети.

Транзакция также объявляет стоимость Gas уже в настоящей внутрисетевой валюте, в ETH. Таким образом определяется вознаграждение майнеров за выполнение транзакции, то есть за включение её результатов в блок. Gas всегда имеет свою цену.

Виртуальная машина что-то делает. Контракты могут вызывать методы других контрактов. Даже создавать другие контракты. Пересылать ETH другим аккаунтам. И самое главное: они могут менять состояние сети.

Зато контракты не могут ничего делать во внешнем мире. Никаких HTTP запросов. Никаких побочных эффектов. Выполнение контракта — строго детерминированный процесс. Каждый узел сети должен иметь возможность удостовериться, что вызов данного метода данного контракта с данными входными данными в данный момент времени (то есть в указанном блоке) выдаст конкретный результат (который и записан в этом блоке). Внешний мир — слишком непредсказуемая штука, чтобы как-то ему доверять.

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

Да, в сети Ethereum есть состояние. В отличие от Bitcoin здесь денежная транзакция — это изменения свойства balance аккаунтов. Но и каждый контракт может иметь своё внутреннее состояние. Которое тоже хранится в сети.

Представьте себе большую распределённую виртуальную машину. В неё загружены объекты. Обычные объекты с точки зрения ООП, с методами. Только вызов метода — это не посылка сообщения (хотя такой термин тоже здесь встречается), а транзакция. Которая записывается раз и навсегда в надёжном распределённом хранилище. Так же как и изменения состояний объектов, которые эта транзакция вызвала. Вот это вот и будет Ethereum. Смартконтракты — это объекты. Создание контракта — оператор new.

Это состояние в Ethereum хранится в виде дерева Меркла. Это такое дерево, где данные хранятся в листьях, в ветках хранятся хэши его листьев, и веток, и веток,.. а в корне хранится хэш всего дерева. Изменение дерева приводит к пересчёту его хэшей. И формируется новый хэш корня дерева. Нельзя изменить ни одного значения в дереве, чтобы не изменился хэш корня. Таким образом один хэш корня гарантирует подлинность всех значений в дереве. Этот хэш записывается в блок. Хэш корня дерева Меркла со всеми изменениями состояния, произведёнными всеми транзакциями, включёнными в блок.

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

блок Ethereum

В Ethereum сейчас тоже действует Proof-of-Work. Но сложность поменьше, блок тоже поменьше. Здесь новый блок создаётся примерно каждые 15 секунд. Можно позалипать почти в реальном времени, как это происходит. Можно посмотреть на настоящий живой блок Ethereum. Ну или так.

Binance

Но не Эфириумом единым. Есть такая компания Binance. Нынче она считается крупнейшей биржей по обмену криптовалют. Надо же как-то разные крипто деньги друг на друга менять, и на настоящие деньги. Потому и существуют биржи. (А ещё есть инструменты децентрализованного обмена).

В июне 2017 Binance взяли и запустили свою криптовалюту под названием Binance Coin (BNB). А в сентябре 2020 они снова взяли, и запустили свой блокчейн со смартконтрактами под названием Binance Smart Chain (BSC). И что-то так им хайпануло, что BSC сейчас, похоже, самая быстрорастущая сеть смартконтрактов. (Знатоки говорят, что в Ethereum как-то сильно подорожал Gas, и пользоваться им стало просто дорого, и многие проекты переехали на другие совместимые с Ethereum VM блокчейны, включая BSC).

Технически, BSC — это форк Ethereum. Форк не блокчейна, когда разветвляется цепочка блоков, и новые деньги начинают жить самостоятельно. Такое было и в истории Bitcoin, и в истории Ethereum. Печальные истории. А форк исходных кодов Эфириума.

Клиент (то есть, узел) BSC — это форк клиента для Ethereum, написанного на Go, и известного как Geth.

В BSC не используется Proof-of-Work. А присутствует комбинация Proof-of-Authority и Proof-of-Stake. То есть, майнит лишь ограниченная группа узлов, выбранная среди тех, у кого больше денег. Если я правильно понимаю. Очень похоже на реальную жизнь :)

Все обычные инструменты работы с Ethereum, включая библиотеку Web3.js, включая её питоновые и джавовые вкусы, работают и с BSC. Binance сильно позаботились о совместимости.

ERC-20

Какие можно делать смартконтракты? Похоже, самый популярный вариант использования — выпуск токенов. ICO — Initial coin offering. За «настоящие» ETH или BNB вы продаёте «попугайские» токены. В обмен на какие-нибудь обещания в будущем. Можете даже красивое имя и буквенное обозначение для своего токена придумать.

Любопытно, что в BSC обмен разных криптовалют и даже реальных денег, похоже, делается через токены. Например, вот обмен ETH на BNB. А вот обмен на американские доллары.

Для токенов даже нарисован свой стандарт: ERC-20. Ethereum Request for Comments, ага. Или, более формально, как EIP-20. Ethereum Improvement Proposal. В BSC есть свой аналогичный BEP-20.

Интерфейс ERC-20 в Solidity выглядит так:

function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

Имена методов. Типы и имена аргументов. address — это специальный тип для обозначения адреса Ethereum аккаунта, человека или контракта. Технически это 256-битное число. uint256 — это беззнаковое 256-битное целое. В данном случае это количество токенов, на балансе, или которые нужно передать. Это целое значение нужно поделить на decimals. Да, деньги тут принято считать с фиксированной точкой.

В Ethereum любят 256-битные числа. Там всюду используется хэш KECCAK-256 /ˈkɛtʃæk/, который выдаёт 256-битные числа. Даже виртуальная машина Ethereum оперирует 256-разрядными регистрами.

Область видимости, раз интерфейс, то public, логично. Про view чуть попозже. Тип и, иногда, имя возвращаемого значения. Метод контракта может вернуть и более одного значения, как в Go.

view. Так помечены методы, которые не изменяют состояния контракта. Справочные методы. Только для чтения. Геттеры.

name() возвращает имя токена. symbol() — красивое буквенное обозначение. decimals() — количество знаков после запятой в числе с фиксированной точкой для обозначения количества токенов. Чаще всего 18, потому что именно с такой точностью торгуется сам ETH (или BNB). Это вообще константы.

totalSupply() — общее количество выпущенных токенов в этом контракте. balanceOf(address _owner) — сколько токенов на балансе у аккаунта с этим адресом. Эти значения могут меняться со временем. То есть, при выполнении транзакций. Но на какой-то момент в прошлом, близком или дальнем, то есть для блока за определённым номером, эти значения всегда определены и неизменны. В этом и суть блокчейна. Это — состояние данного контракта.

Вызов view метода не меняет состояния. И не порождает транзакции. И не требует Gas. Его может выполнить любой узел сети. Технически это просто чтение того самого состояния контракта. Состояния виртуальной машины Ethereum.

А вот метод типа transfer(address _to, uint256 _value) уже инициирует транзакцию. Тот, кто его вызывает, должен предоставить достаточное количество Gas. А вызов метода может завершиться с ошибкой, если, например, вызывающий попытается передать токенов больше, чем есть у него на балансе. Даже в случае ошибки Gas будет потрачен и вызывающий заплатит за это. А сама ошибка будет записана в блокчейне. Ошибочный результат тоже результат.

Кроме методов ERC-20 определяет также события.

event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)

Оказывается, в блок записывается не только результат вызова метода, но и логи. Вот эти вот события в Solidity — это логи.

Самое прекрасное, что логи индексируются. Сигнатура события (аналогичная сигнатуре метода), и параметры, помеченные indexed, не просто сохраняются в блокчейне. По ним можно искать.

Можно найти все транзакции, где появилось событие Transfer, и где токены были переданы от одного держателя токенов другому. Именно так, сканируя историю блокчейна в поисках событий, а также подписываясь на новые события (так тоже можно), и формируются каталоги токенов. В Ethereum: cписок самых популярных ERC-20 токенов. И в BSC: список самых дорогих BEP-20 токенов.