Любому блокчейн-проекту, который рассчитывает на успех, стоит думать, что он будет с этим успехом делать. Больше пользователей, больше транзакций, больше нагрузка на сеть — рано или поздно встанет вопрос о масштабировании. В этом посте мы расскажем, куда нас привели вопросы масштабирования, согласованности и почему мы не взяли на вооружение какой-нибудь готовый консенсус.
Атомарность важнее кластеризации
Какие есть варианты масштабирования блокчейн-сети? Самый простой — кластеризация. То есть развернуть несколько независимых блокчейн-сетей, каждая из которых будет обрабатывать свои данные, сгруппированные, допустим, по географическому признаку. Для увеличения безопасности каждого кластера можно связать их с помощью анкоринга или межкластерных транзакций — передача бизнес-данных между сегментами позволит нам реализовывать более сложные бизнес-процессы onchain. Но есть у этого подхода критический недостаток: так не получится создать систему, в которой данные в разных кластерах логически зависимы друг от друга и консистентны в любой произвольный момент времени.
Допустим, наша блокчейн-сеть — это кадастровый реестр, где ведется учет владельцев недвижимости. На смарт-контракте в кластере Уральского федерального округа есть учетная запись заслуженного горняка Михаила Петровича, желающего выйти на пенсию и приобрести дачу в Сочи. А в Южном федеральном округе — учетка Людмилы, которая вышла замуж за москвича и хочет продать свой дом на Черном море. Наши герои нашли друг друга, провели сделку. Система обновляет данные Людмилы и прекращает ее право собственности на дом в кластере Южного федерального округа, отправляет транзакцию в уральский кластер для регистрации собственности на Михаила Петровича.
Вроде бы все прошло хорошо, но потом произошел форк и откат цепочки в уральском кластере. Транзакция передачи прав собственности потерялась. Система одновременно считает, что дом не принадлежит ни Людмиле (есть данные, что она продала дом), ни Михаилу Петровичу (нет данных, что он купил дом). Даже если транзакция в уральском кластере все-таки будет добавлена в один из следующих блоков, это приводит нас к ситуации, в которой на протяжении некоторого периода дом на Черном море имеет либо сразу двух владельцев, либо ни одного.
Загвоздка здесь все-таки не в том, чтобы синхронизировать данные в двух сегментах сети, а чтобы все изменения данных, затрагивающие несколько кластеров, являлись атомарной транзакцией — то есть в один момент либо принимались, либо отклонялись системой. Так нам не придется отменять и пересобирать кучу данных в связанных сетях.
В идеале мы должны также исключить пропажу данных после сохранения их в блокчейне, чтобы максимально защититься от форков. Переходим к вопросу финальности, необратимости данных.
Необратимость данных в Bitcoin
Посмотрим, как достигается необратимость данных в крупнейших блокчейн-проектах, например, в Bitcoin. Недавно мы опубликовали и прокомментировали перевод поста, где объясняется, почему требованиям биткоина идеально подходит консенсус PoW (Proof-of-Work). Теперь взглянем на него исходя из наших требований.
Как Bitcoin обеспечивает необратимость данных? Никак. Точнее, «теоретически никак». Это не преувеличение, протокол биткоина не гарантирует необратимость данных. В его алгоритме консенсуса нет условий, закрепляющих возможность отмены конкретного блока и всех включенных транзакций. Финальность биткоина складывается из практического опыта.
Статистика сети blockchain.com, приведенная на рисунке выше, показывает, что шанс отката более пяти блоков невелик. Новые блоки выпускаются раз в 10 минут, так что для уверенности нужно прождать час. Почти наверняка транзакция не будет отменена и через пару-тройку блоков — но это не гарантия протокола, а всего лишь результат наблюдений. Bitcoin обеспечивает вероятностную финальность — чем больше блоков положили поверх блока N, тем меньше вероятность, что он будет отменен. Закладывать новую сеть для нового проекта на таких условиях… не лучшее решение.
Форки и откаты блоков в принципе естественны для большинства известных блокчейн-платформ. В реальных же кейсах, подобных описанному в начале поста, это может привести к полной неразберихе. Какими бы прекрасными фичами ни блистала блокчейн-платформа, но в энтерпрайзе это только одна часть решения бизнес-задачи. Другая часть — это собственные информационные системы заказчика, которые необходимо интегрировать с блокчейном. Бизнес не может ждать, пока данные пройдут все необходимые валидации и уж тем более не поймет, если эти данные придется откатывать.
Чтобы подружить здесь бизнес и блокчейн, приходится прибегать к костылям. Например, записывать в базу не только транзакции, но и данные по номеру и подписи блока, который эти транзакции включал. Так в случае форка прикладное решение сможет сориентироваться, удалить соответствующие транзакции или присвоить им другой статус. Но со стороны пользователя это будет выглядеть странно: документы или заявки начнут менять статус сами по себе. А если на этом блокчейне базируется сразу несколько прикладных решений? Бардак может быть потрясающий.
Доверяй или проверяй
«Вероятностная финальность» Bitcoin — это не единственный вариант. Существует множество консенсусов, где необратимость данных в том или ином виде — одно из основных условий. Например, консенсус Paxos, его оптимизированная версия Raft или семейство BFT-консенсусов. В них быстрая финальность достигается за счет того, что узлы системы согласуют между собой любое изменение данных, прежде чем принять его. Решает ли это нашу проблему? Не совсем.
Одна уязвимая точка — это сетевое окружение. Каждый отдельный узел блокчейн-сети имеет в своем распоряжении лишь набор транзакций, приходящих по сети. А что если они вдруг перестали приходить? Скажем, случился перебой с интернетом, у одного узла или у всех остальных. Что делать: продолжать работу или ждать? А сколько подождать? А если рассинхронизация по времени? А вдруг мне придут противоречивые сигналы от соседних узлов? Здесь уместно вспомнить теорему Фишера-Линча-Патерсона, которая доказывает невозможность детерминированной консистентности на асинхронной сети при сбое даже одного ее узла.
Другая уязвимая точка — обмен сообщениями. Каждое согласование изменений между узлами требует передачи дополнительных данных. Пусть даже мы меняем пару-тройку байт — для этого нужно запустить множество сообщений, где только подписи занимают десятки байт. Как масштабировать такую систему? Мы вернулись в тот тупик, из которого пытались выйти в начале.
И что дальше?
Оценив разные решения, мы выделили два основных подхода к финальности данных. Можно ждать, пока поверх нужного блока ляжет некоторое количество новых блоков, как в Bitcoin. Или построить сложную цепочку согласований, которые будут гарантировать необратимость данных, как в BFT-консенсусе. Если интересно узнать о нем подробней, обратите внимание на статью SoK: Understanding BFT Consensus in the Age of Blockchains. В итоге мы решили, что истина где-то посередине, и взялись за создание собственной версии консенсуса CFT (crash-fault tolerance). Подробнее — в будущих постах.