Хабрахабр

P2P-споры на блокчейне

Мы стараемся сделать смарт-контракты доступными для неподготовленных пользователей, предлагая простые, но практически полезные контракты. Сеть Ethereum, широко известная в узком кругу блокчейн-разработчиков, уже зарекомендовала себя как удобная и стабильная платформа для разработки смарт-контрактов. В основе контракта лежит пари (спор) двух оппонентов. Недавно мы разработали смарт-контракт спора Bet Me. Проигравший теряет деньги, а победитель забирает всё. Они подкрепляют уверенность в собственной правоте денежной ставкой. Подробнее о нём я расскажу в этой статье.

Какие задачи сложно решить при помощи устной договорённости или юридического договора, но просто — при помощи блокчейна? Для начала — вопрос, которым редко задаются авторы статей о блокчейне: а нужен ли в этой ситуации блокчейн?

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

У этого подхода есть ряд недостатков.

  • Чаще всего необходимо привлечь юриста, а то и двух, по одному с каждой стороны, иначе можно забыть важный пункт, и это приведёт к финансовым потерям.
  • Нередко в договоре прописываются только обязанности сторон, а ответственность за нарушение этих обязанностей — нет. В итоге одной стороне оказывается слишком легко не платить, а другой — крайне сложно и дорого получить свои деньги через суд.
  • А может быть так, что обе стороны настаивают на своей правоте, и даже обращение в суд не гарантирует, что победителю достанутся деньги.
  • Может оказаться и так, что у проигравшей стороны денег просто нет, и даже решение суда не заставит её расплатиться по долгам, несмотря на все обязательства.

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

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

Тот, кто создаёт экземпляр контракта в сети Ethereum, называется владельцем контракта (Owner), а его противник — оппонентом (Opponent). В споре участвуют две стороны. Оппонент делает ставку на то, что утверждение ложно. Владелец контракта задаёт текстовое утверждение, которое он считает истинным. Арбитр получает комиссию в виде процента от суммы спора. Решение о результате спора принимает независимый арбитр (Arbiter), кандидатуру которого утверждают и владелец, и оппонент.

Работа контракта разделена на несколько последовательных стадий.

  1. Переговоры. Владелец и оппонент ещё до создания контракта могут любым удобным способом вести переговоры. Совместно решив, кто будет арбитром, они присылают кандидату приглашение рассудить их спор. Получив приглашение, арбитр увидит все условия и соответствующий State. Подробнее об этом ниже, а пока важно понимать, что это число будущий арбитр должен передать в контракт, чтобы показать, на каких условиях он готов рассудить спорщиков. Если владелец установил ненулевую сумму залога (ArbiterPenaltyAmount), то, соглашаясь с условиями, арбитр должен перечислить указанную сумму эфира в контракт, после чего она блокируется, пока арбитр не рассудит спорщиков либо пока не наступит крайняя дата (Deadline) для разрешения спора. В последнем случае арбитр теряет возможность вывести залог, и эта сумма распределяется поровну между участниками спора.
  2. Инициализация. Владелец контракта создаёт экземпляр контракта и настраивает его параметры: предмет спора; дату, до которой арбитр должен принять решение (Deadline); процент комиссии арбитра (дробное число ≥ 0 и < 100); сумму залога (может быть нулевой), которую арбитр должен внести как гарантию того, что он берётся рассудить спор в имеющейся формулировке к нужному времени. Владелец также задаёт Ethereum-адрес арбитра, которому он доверяет. Только обладатель данного адреса сможет позже стать арбитром.
  3. Ставка владельца. После настройки владелец контракта делает ставку. Для этого он присылает любую сумму эфира в контракт. Эта сумма и есть ставка, она блокируется на адресе контракта.
  4. Согласие арбитра. Ставка владельца фиксирует условия спора. Теперь арбитр видит полные условия сделки: формулировку спора, время до которого нужно принять решение, и главное, может понять, сколько эфира он получит в качестве вознаграждения. Если арбитра все устраивает, он подтверждает свое участие и одновременно перечисляет страховой депозит.
  5. Поиск оппонента. После согласия арбитра начинается поиск оппонента. Владелец заранее задаёт адрес оппонента, если готов спорить только с кем-то конкретным, либо оставляет адрес пустым, и тогда оппонентом может стать владелец любого адреса в сети (кроме арбитра и владельца). Оппонент подтверждает участие в споре, вызывая отдельный метод контракта, в который он передаёт текущий номер версии данных и эфир — столько же, сколько поставил владелец. С этого момента пари считается заключённым. Теперь контракт ждёт либо решения арбитра, либо наступления даты Deadline.
  6. Исход спора. Арбитр может рассудить спор тремя способами.
    — Признать утверждение верным. В этом случае владелец контракта может вывести всю сумму эфира, кроме комиссии арбитра и залоговой суммы (если она была): эти деньги выводит арбитр, а оппоненту не достаётся ничего.
    — Признать утверждение ложным. В этом случае арбитр может вывести эфир в размере причитающейся ему комиссии и суммы залога. Оппонент забирает остальное, а владельцу не достаётся ничего.
    — Признать спор неразрешимым. Например, владелец создал спор с утверждением «Футбольный матч между командами А и B, назначенный на ближайшее воскресенье, закончится со счетом 2:1 в пользу A». Если матч отменили, арбитр не разрешит спор, но он должен иметь возможность забрать свой залог, потому что проблема возникла не по его вине. Каждая из сторон в этом случае может запросить перевод эфира в размере собственной ставки с адреса контракта на свой кошелёк.
  7. Вывод средств. Когда арбитр принял решение либо наступила дата Deadline, каждая из сторон может запросить вывод эфира. Сколько эфира выводить, рассчитает сам контракт, ориентируясь на результаты спора.
  8. Уничтожение контракта. Владелец может отправить в контракт команду самоуничтожения. Это можно сделать либо до заключения сделки (если арбитр не нашёлся), либо после её завершения (если все стороны вывели причитающиеся им средства). Такая возможность окажется полезна, если волшебным образом на адрес контракта поступило больше эфира, чем запланировано. Вероятность такого события очень низка, но всё же в Ethereum нельзя полностью заблокировать перевод эфира на адрес произвольного контракта, а бросать замороженные деньги глупо.

Это число, которое увеличивается при каждом изменении значимых условий спора, таких как формулировка спора, размеры комиссий или штрафов арбитра. Теперь немного о том, зачем нужен State Version Number. Если между этими двумя событиями одна из сторон (вероятнее всего, владелец) изменит параметр контракта, получится согласие с другой версией данных. Когда кто-то соглашается с условиями спора, он 1) видит актуальное состояние данных; 2) отправляет вызов метода контракта, который регистрирует согласие с условиями. Кандидат с радостью соглашается и отправляет транзакцию с подтверждением в сеть. Например, кандидат в арбитры заходит в интерфейс контракта на smartz.io и видит, что ему предлагают рассудить спор на 10 Ether (сегодня это около 3000 долларов) за комиссию 1 % (примерно 30 долларов). Мошенник ставит цену газа выше средней, и с некоторой долей вероятности майнеры могут обработать его транзакцию раньше. Нечестный владелец видит в пуле майнинга необработанную транзакцию арбитра и отправляет свою: меняет вознаграждение арбитра на 0 %. State Version Number защищает от неё. Такая атака называется Front running attack. Арбитр в своей транзакции послал номер версии данных на единицу меньше. Если транзакция владельца-вредителя будет обработана раньше, номер версии данных в контракте изменится. Арбитр просмотрит новые условия и либо откажется от участия, либо согласится, отправив актуальный номер версии данных. Поэтому контракт откажется выполнять транзакцию, произойдёт откат.

Что будет, если владелец контракта решил обделить арбитра? При разработке контракта, оперирующего эфиром, приходится много думать о плохих сценариях. А если оппонент на самом деле хакер? А если арбитр оказался нечестным? Кроме того, необходимо учесть любые возможности нарушения нормального хода спора, когда эфир будет заблокирован в контракте и даже владелец не получит к нему доступ. Или все трое жаждут обмануть друг друга, потому что ставки в споре достаточно высоки? По той же причине последовательность этапов спора именно такова: ставка владельца -> выбор арбитра -> ставка оппонента. Например, вариант признания спора неразрешимым возник уже в ходе реализации. Чтобы не превратиться в долгосрочного инвестора, арбитр может отказаться от участия, но только пока оппонент не сделал ставку. Может случиться так, что оппонент не подтвердил своё участие, а время Deadline установлено далеко в будущем. Хорошая новость в том, что это надо запрограммировать один раз и использовать. И таких нюансов в контракте много. Блокчейн позволяет зафиксировать условия, как в договоре, но возложить интерпретацию условий на виртуальную машину и всегда иметь один и только один результат их исполнения. Если бы спор был оформлен как бумажный договор, в случае таких пограничных ситуаций много людей раз за разом должны были бы вникать в каждый пункт и договариваться между собой, как его интерпретировать.

В нашем контракте арбитр принимает решение в одиночку. Нельзя обойти вниманием проблему заинтересованного арбитра. Один из выходов — ввести в логику контракта возможность добавлять нескольких арбитров и принимать решение путём голосования. Для простых ситуаций этого достаточно, но иногда риск личной заинтересованности арбитра недопустим. Однако хорошая новость в том, что всю логику сложного коллективного арбитража можно вынести в отдельный смарт-контракт. Это довольно сложная логика, особенно если захочется сделать её универсальной для всех возможных споров и их участников. С точки зрения интерфейса такой контракт должен иметь возможность вызывать из контракта спора несколько методов: согласие судить спор, отказ от такого согласия, три версии решения и один метод вывода эфира. Адрес контракта владелец спора пропишет в качестве арбитра. Внутри арбитражного контракта может использоваться логика принятия решения большинством арбитров наподобие того, как это сделано в контракте Multisignature Wallet, также доступном на Smartz.io в виде конструктора.

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

Все знают, что автоматизация тестирования — это хорошо. Хочется сказать пару слов о тестировании. Кое-кто использует в разработке подход TDD — давно и хорошо известный Test Driven Development. Многие на самом деле пишут тесты для своего кода. Это позволяет взглянуть на код контракта снаружи, прочувствовать возможные проблемы и заранее их решить. Ключевое отличие TDD от простого тестирования в том, что тесты пишутся раньше, чем код. Правильное использование приходит с опытом. Кроме того, TDD при правильном использовании позволяет значительно быстрее изменить логику работы, если это потребуется внезапно. В то же время вызывает беспокойство, что руководства по разработке в Truffle и Node.js вообще не демонстрируют использование TDD для разработки на Solidity. TDD не серебряная пуля, как можно подумать, читая многочисленные материалы на эту тему. Начинающие разработчики приобретают неправильные привычки и в итоге много страдают.

Например, код контракта спора занимает 325 строк, а код тестов для этого контракта состоит из 2144 строк. TDD подразумевает, что тестов в проекте много. Цикл разработки в TDD подразумевает частый прогон тестов после небольших изменений кода. В какой-то момент тестов стало достаточно много, чтобы прогон truffle test занимал больше минуты. Чтобы разработка не превратилась в мучение, пришлось научить Truffle запускать только часть тестов, совпадающих с переданным регулярным выражением.

Mocha умеет фильтровать запуск тестов по регулярке, но Truffle из коробки не умеет передавать соответствующий параметр --grep из командной строки. Под капотом Truffle использует для работы с тестами фреймворк Mocha. Конфиг, который у меня получился в итоге, доступен в GitHub проекта. Воспользовавшись тем, что конфиг Truffle — это обычный код на JavaScript, я вписал в него разбор аргументов командной строки и формирование параметров для Mocha. Реализация не слишком красивая, но это работает и экономит кучу времени.

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

Для этого потребуется расширение Metamask для десктопного браузера или Trust Wallet для мобильных устройств. Контракт спора BetMe можно протестировать и запустить с помощью готового шаблона на платформе Smartz. Также исходный код самого контракта выложен на GitHub.

Децентрализованные автономные организации (DAO) пока не стали реальностью. Стоит признать, что на сегодняшний день применение блокчейн-технологий сводится в основном к криптовалютам и выпуску токенов для ICO. После завершения споров их участники могли бы голосовать за или против арбитров, с которыми имели дело, изменяя их положение в рейтинге. Но если пофантазировать, как дальше будут развиваться системы контрактов спора, то можно представить реестр арбитров с рейтингом, например на основе Token Curated Registry.

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть