Перевод статьи https://cexplorer.io/article/challenges-for-cardano-developers
Разработчикам может быть трудно в полной мере воспользоваться преимуществами модели UTxO, поскольку они должны учитывать распараллеливание. Cardano не позволяет поддерживать единое глобальное состояние приложения в ончейн части смарт контракта. Каждый UTxO может представлять часть состояния приложения и может обрабатываться независимо и параллельно. Теоретически это обеспечивает высокую пропускную способность и масштабируемость, но приложение должно иметь дело со сложностями, связанными с управлением параллельными транзакциями. С какими проблемами сталкиваются разработчики в Cardano?
Когда распараллеливание не представляет сложностей
В модели расширенных неизрасходованных выходов транзакций (eUTxO) каждое UTxO может обрабатываться независимо и параллельно. Расходование UTxO не зависят от какого-либо глобального состояния Cardano. Если условия расходования выполнены, один или несколько выходов UTxO будут созданы посредством транзакции из входа UTxO (или нескольких входов UTxO). Входы UTxO должны быть полностью израсходованы. Выходы UTxO должны иметь то же (или меньшее) количество токенов, что и входы UTxO.
Если Алиса отправляет Бобу 100 ADA, расходование UTxO зависит только от свидетельства Алисы. Если сто других пользователей отправляют аналогичную транзакцию, используются 100 других уникальных входов UTxO. Каждый отправитель транзакции является владельцем входа UTxO. Нет никакой зависимости между транзакциями и входами UTxO.
Нет никакой зависимости между транзакциями и входами UTxO, поскольку все отправители независимы друг от друга. Они приняли свое собственное независимое решение отправить 100 ADA. Все 100 транзакций могут быть вставлены в один и тот же блок и будут оценены как действительные.
Транзакция использует один или несколько UTxO в качестве входов и создает один или несколько новых UTxO в качестве выходов. Это простое правило применимо как к передаче токенов между Алисой и Бобом, так и в случае приложений, как вы увидите позже.
На картинке вы видите 3 идентичные транзакции. При 100 транзакциях это выглядело бы точно так же. Пусть вас не смущает тот факт, что отправителем всегда является Алиса, а получателем - Боб. Каждый раз это другая Алиса и другой Боб. Этот рисунок призван продемонстрировать тот факт, что синхронизация между отправителями транзакций не требуется. Если транзакции действительны, они не могут завершиться неудачей, и все они попадают в блокчейн.
Возможно, вы поймете значение рисунка позже, после того как мы объясним, как работает DEX.
Сеть Cardano может проверять транзакции в любом порядке, поскольку транзакции независимы друг от друга. Это является преимуществом с точки зрения пропускной способности сети, поскольку консенсус не зависит от последовательной обработки.
Как создать параллельное приложение?
В идеале разработчики приложений должны создать такую логику смарт контрактов, которая ведет себя аналогично с точки зрения распараллеливания, как при отправке транзакции 100 отправителями (Алисами). Однако это практически невозможно.
Давайте проясним это на примере DEX, который использует пулы ликвидности.
Пул ликвидности заполняется несколькими транзакциями, которые производят выходы UTxO. Эти UTxO представляют токены в пуле ликвидности. С каждым новым добавляемым блоком в леджер состав UTxO в пуле ликвидности может меняться.
На картинке вы видите пул ликвидности с парой токенов X и Y. В пуле ликвидности есть несколько UTxO с токенами X и Y. В следующем блоке Алиса, Боб и Кэрол поместили новые UTxO с токеном X в пул ликвидности, а Дэйв, Ева и Фрэнк поместили несколько UTxO с токеном Y. Поскольку никакого обмена (свопа) не произошло, никакие UTxO не были удалены из пула.
Чтобы произвести обмен, DEX должен использовать UTxO (или больше UTxO) из пула ликвидности в качестве входов для каждого типа токена. Эти UTxO должны быть израсходованы полностью. Если для обмена не требуются все токены в UTxO, то оставшиеся токены должны быть возвращены в пул ликвидности в качестве нового UTxO.
В случае DEX многие операции выполняются конкурентно в одно и то же время. Пользователи (провайдеры ликвидности) отправляют токены в пулы ликвидности. Провайдеры являются отправителями, а DEX - получателем. В то же время другие пользователи могут отправлять запросы на обмен. Это отправители, которые передают DEX токены X и хотят получить токены Y. Или наоборот.
Несколько участников могут отправить запрос на обмен в течение короткого периода времени. Эти свопы могут относиться к одному пулу ликвидности. Таким образом, между участниками существует взаимозависимость.
Также неплохо бы уточнить, что именно такое DEX. Сложные смарт контракты в Cardano (например, DEX) состоят из двух частей: ончейн логики, которая выполняется на блокчейне, и оффчейн логики, которая выполняется на серверах (или в локальных кошельках).
На картинке вы можете видеть DEX, который состоит из ончейн и оффчейн логики.
В то время как выполнение ончейн логики естественным образом децентрализовано, поскольку оно происходит в сети Cardano, команда отвечает за децентрализацию оффчейн логики DEX. Оффчейн часть DEX не состоит (не должна состоять) только из одного агента, а и из нескольких агентов.
В случае DEX эти агенты называются батчерами. Они отвечают за выполнение свопов. Батчеры создают транзакции, соответствующие условиям расходования UTxO в пуле ликвидности, и переводят активы в соотношении, которое запрашивали оба участника свопа.
Cardano не позволяет поддерживать единое глобальное состояние приложения в ончейн части смарт контракта. Однако технически это возможно.
Если бы разработчики хранили все состояние DApp в одном UTxO, они, по сути, создали бы глобальное состояние, аналогичное тому, что существует в аккаунт модели Ethereum. Это могло бы ограничить конкурентность и пропускную способность вашего DApp. Такой подход не позволил бы в полной мере использовать преимущества модели EUTxO.
В ончейн части DEX UTxO и связанные с ними Datum представляют состояние приложения. Таким образом, состояние распределяется по UTxO. Если DEX должен иметь единое глобальное состояние приложения, оно должно поддерживаться оффчейн через батчеры.
На картинке вы можете видеть пул ликвидности с токенами X и Y и 3 батчерами. Глобальное состояние приложения и синхронизация состояний между батчерами показаны синим цветом. Состояние приложения состоит из данных ончейн, которые представляют собой Datum, связанные с UTxO, и состояния приложения оффчейн, поддерживаемого батчерами (агентами). Батчеры взаимодействуют друг с другом для синхронизации единого глобального состояния приложения.
Это необходимо, поскольку батчеры (агенты) получают доступ к одному и тому же ресурсу, а именно к пулу ликвидности. Им необходимо использовать входы UTxO из пула ликвидности (для выполнения свопа), и может случиться так, что два (или более) агента захотят использовать один и тот же UTxO. Может возникнуть проблема конфликта.
Теперь пришло время вспомнить первое изображение в статье. Когда 100 отправителей отправляли транзакцию, они не могли конкурировать друг с другом за один и тот же UTxO, поскольку каждый отправитель использовал свой собственный UTxO. UTxO в пуле ликвидности являются общим ресурсом, т.е. ресурсом, к которому имеют доступ несколько агентов.
Как правило, конфликт имеет место быть в сценарии, когда несколько потоков или процессов (в нашем случае агентов) пытаются получить доступ к одному и тому же ресурсу таким образом, что по крайней мере один из них выполняется медленнее, чем если бы остальные не были запущены.
В нашем случае существует риск того, что два агента создадут транзакцию, в которой они будут использовать один и тот же вход UTxO из пула ликвидности. В этом случае Cardano примет только одну транзакцию. Вторая транзакция потерпит неудачу.
На рисунке вы можете видеть, что батчеры 1 и 3 пытаются использовать один и тот же UTxO с токеном X. Происходит конфликтная ситуация. Батчеры, по-видимому, плохо синхронизированы и не знают о намерении друг друга использовать этот конкретный UTxO. Если будут созданы 2 транзакции свопа, то одна из них будет успешной, а другая - неудачной.
Цель DEX состоит в том, чтобы разрешить одновременное выполнение свопов, т.е. чтобы отдельные агенты могли создавать транзакции конкурентно и при выборе UTxO не возникало конфликтов.
Чтобы предотвратить сбой транзакций, между агентами должна существовать оффчейн связь или какая-либо другая форма синхронизации. Другими словами, агенты должны поддерживать согласованное глобальное состояние DEX.
Отдельные агенты должны каким-то образом резервировать UTxO в пуле, чтобы один и тот же UTxO не использовался другим агентом. В качестве альтернативы, это может работать таким образом, что в течение каждого следующего блока (20 секунд) все транзакции будут создаваться одним (случайно выбранным) агентом. Хотя этот подход децентрализован, он менее конкурентен.
Вы можете видеть на картинке, что батчер 1 и батчер 3 выбрали UTxO с токенами X и Y эксклюзивным образом для свопа, так что разногласий не было. Свопы 1 и 2 выполняются одновременно. Разногласий не было, потому что все батчеры синхронизировали глобальное состояние друг с другом.
Обратите внимание, что рыночная стоимость токена X ровно в 2 раза превышает рыночную стоимость токена Y, и так совпало, что в пуле ликвидности были подходящие UTxO для обмена. Своп 1 использует 100 токенов X и 50 токенов Y. Своп 2 использует 200 токенов X и 100 токенов Y. Если бы в пуле не было UTxO со 100 X токенами, вторым наиболее подходящим был бы UTxO со 114 X токенами. Это означает, что 14 токенов X должны быть возвращены в пул ликвидности в качестве нового UTxO.
Одним из других челленджей, который не будет обсуждаться далее в статье, является соответствующий выбор UTxO для свопов. С Ethereum это не проблема, так как в основном обновляются только остатки на счетах.
Можно подойти к проблеме совершенно иначе, чем при использовании пула ликвидности. Вместо того чтобы помещать UTxO в один пул, можно подключать отдельных кандидатов на замену. Однако, для простоты, давайте в этой статье остановимся на пулах ликвидности.
Разработка DEX на Cardano, которое может обрабатывать UTxO параллельно при сохранении децентрализации, предполагает решение проблемы конкурентности. Это один из челленджей, стоящих перед разработчиками.
В статье мы показали одно из возможных решений, а именно использование оффчейн и ончейн компонентов. Компонент оффчейн может использоваться для корректного формирования транзакций для взаимодействия с кодом ончейн. Корректность обеспечивается за счет оффчейн связи, позволяющей синхронизировать глобальное состояние приложения.
Одним из других возможных подходов является создание алгоритма, который предоставляет пользователям эксклюзивный доступ для отправки желаемого действия. Впоследствии алгоритм может объединить все действия вместе, соблюдая тайминг и справедливость.
Разработчики могут поддерживать единое состояние в ончейн части приложения или разделить его на несколько UTxO. Иметь единое состояние ончейн легко, потому что так легче поддерживать согласованность. Все части приложения работают с одними и теми же данными. Разделение состояния ончейн на несколько UTxO может увеличить конкурентность, но это сопряжено с рядом проблем. Управление несколькими UTxO усложняет логику смарт контрактов. Необходимо обеспечить некоторую форму синхронизации, которая обеспечивает корректность (избежание конфликтов).
Суть проблемы заключается в достижении распараллеливания в децентрализованной среде.
Логика приложения всегда связана с UTxO. Каждый UTxO представляет собой независимую часть состояния, которая может обрабатываться параллельно. Как мы объясняли в статье, это возможно только в том случае, если реализована какая-либо надежная форма синхронизации.
Если бы существовал только один батчер или агент оффчейн, он мог бы управлять состоянием DEX и подготавливать транзакции, не беспокоясь о проблемах параллелизма. Это потенциально могло бы привести к ускорению обработки транзакций и повышению пропускной способности.
Однако такой подход, по сути, привел бы к централизации оффчейн части DEX, что противоречит принципу децентрализации. Таким образом, задача состоит в том, чтобы добиться оффчейн децентрализации, сохраняя при этом высокую производительность и избегая проблем с параллелизмом.
Разработчики Ethereum также сталкиваются с челленджами
Ethereum использует аккаунт модель, а смарт контракты имеют глобальное состояние, которое обновляется транзакциями. Глобальное состояние - это код (функции) и данные (состояние), которые находятся по определенному адресу в блокчейне Ethereum.
Глобальное состояние DEX может представлять текущее состояние книги заказов, включая все открытые ордера на покупку и продажу. Когда отправляется новая транзакция свопа, она представляет собой потенциальное изменение этого глобального состояния. Однако это изменение становится принятым только после того, как транзакция включена в блок и подтверждена сетью.
Транзакции в Ethereum обрабатываются последовательно, по одной за раз. Это означает, что в мире Ethereum нет параллелизма, а значит, и проблем с параллелизмом нет. Такая последовательная обработка упрощает разработку DEX в Ethereum, когда дело доходит до параллелизма и конкурентности, поскольку разработчикам не приходится сталкиваться со сложностями управления конкурентными транзакциями.
Как бы эта последовательная верификация не позволяет использовать параллелизм. Параллельное выполнение транзакций было бы небезопасно, поскольку между контрактами могли бы существовать зависимости. Если один контракт зависит от результатов другого, то эти контракты должны выполняться в одинаковом порядке каждым валидатором.
Порядок транзакций внутри блока определяется валидаторами. Они могут выбрать сортировку транзакций на основе таких факторов, как цена на газ, nonce и время первого просмотра. Следовательно, хотя DEX и может создать очередь из множества свопов, он не может быть уверен, в каком порядке эти свопы будут выполнены.
Эта неопределенность может привести к ситуациям, когда транзакции терпят неудачу из-за условий гонки, когда различные транзакции конкурируют за использование одной и той же ликвидности. Чтобы справиться с этим, некоторые DEX внедряют такие механизмы, как допуск проскальзывания и крайние сроки транзакций, чтобы увеличить вероятность успешного выполнения транзакций.
Состояние гонки может возникнуть, когда несколько пользователей пытаются осуществить своп токенов одновременно. Например, предположим, что два пользователя хотят обменять ETH на USDT, и в пуле ликвидности достаточно USDT только для осуществления одного из свопов. Оба пользователя отправляют свои транзакции свопа примерно в одно и то же время. Валидаторы Ethereum будут определять порядок, в котором эти транзакции будут включены в блок.
Если транзакция пользователя A будет включена первой, его своп будет осуществлен, и в пуле не останется достаточного количества USDT для свопа пользователя B. Когда сеть Ethereum попытается обработать транзакцию пользователя B, она завершится неудачей, поскольку не сможет выполнить обмен.
Результат зависит от относительного времени проведения двух или более операций (свопов). Несмотря на то, что Ethereum обрабатывает транзакции последовательно, условия гонки все равно могут возникать, когда несколько транзакций зависят от общего ресурса (например, пула ликвидности в DEX) и отправляются примерно в одно и то же время.
Обратите внимание, что состояние гонки может возникнуть, даже если пулом ликвидности управляет только один DEX. Это связано с тем, что состояние гонки вызвано не самим DEX, а характером обработки транзакций.
Другими словами, приложения на Ethereum могут предпочесть некоторый порядок, в котором должны обрабатываться транзакции, но это не находится под их собственным контролем. В случае с Cardano транзакции обрабатываются в порядке живой очереди. Однако необходимо отметить, что пулы не обязаны следовать этому правилу, и нет механизма, который заставлял бы пул правильно выбирать транзакции из мемпула.
Давайте кратко сравним челленджи, стоящие перед разработчиками на обеих платформах.
При создании DEX или любого другого децентрализованного приложения на Cardano одним из челленджей является управление и синхронизация фрагментов состояния между несколькими UTxO и агентами. Это обеспечивает более высокую пропускную способность и масштабируемость, но также создает дополнительные сложности, связанные с управлением конкурентными транзакциями.
Приложение может управлять своим состоянием и обновлять его оффчейн, а затем периодически фиксировать это состояние в блокчейне. Такой подход может помочь снизить нагрузку на блокчейн и увеличить скорость транзакций. Более того, оффчейн синхронизация также может способствовать параллельному выполнению транзакций ончейн. DEX может подготовить несколько транзакций параллельно, а затем отправить их в блокчейн для выполнения.
Быстрая синхронизация оффчейн потенциально может привести к высокой масштабируемости и параллелизму ончейн. Разработчики стараются добиться максимально возможного распараллеливания за счет синхронизации.
Это отличается от аккаунт модели Ethereum, где состояние всего приложения хранится в едином глобальном состоянии. Глобальное состояние может содержать все необходимые данные для DEX и изменения посредством транзакций. Поскольку Ethereum обрабатывает транзакции (вызовы) последовательно, разработчикам вообще не нужно беспокоиться о параллелизме.
Однако проблема в том, что некоторая форма параллелизма проявляется в поведении пользователей, которые могут пытаться использовать одну и ту же ликвидность в DEX на Ethereum. По сути, они борются за единый ресурс, подобно батчерам, которые, возможно, захотят использовать один и тот же UTxO.
Разработчики Ethereum DEX не могут полностью предотвратить условия гонки, связанные с тем, что несколько пользователей пытаются использовать одну и ту же ликвидность. Состояние пула ликвидности (т.е. объем доступной ликвидности) определяется транзакциями, которые были включены в блокчейн. Пользователи не знают в режиме реального времени, будут ли их транзакции успешными, потому что Ethereum обрабатывает транзакции последовательно, а порядок определяется валидаторами.
Разработчики могут попытаться создать какой-либо механизм, препятствующий отправке пользователями транзакции, которая имеет высокую вероятность сбоя. Однако это очень сложная задача.
Обратите внимание, что Cardano ведет себя детерминированно, в отличие от Ethereum.
Вывод
Чтобы в полной мере воспользоваться преимуществами модели UTxO и распараллеливания, необходимо улучшить Ouroboros Proof-of-Stake. Сеть Cardano должна быть способна проверять и предварительно одобрять большое количество транзакций одновременно. Если бы несколько десятков транзакций вставлялись в блок раз в 20 секунд, не имело бы значения то, что их можно обрабатывать параллельно. Масштабируемость по-прежнему была бы относительно низкой. Индоссанты ввода внесут необходимое улучшение консенсуса в отношении PoS, в котором модель UTxO будет значительно более эффективной.
// От переводчика: для получения дополнительных переведенных на русский язык статей о Cardano посетите русскоязычный раздел на форуме Cardano. Видеоролики о Cardano на русском языке можно найти на YouTube канале нашего замечательного амбасадора Тимура Сахабутдинова, а также на канале Чарльз Хоскинсон на русском. Хотите поговорить или задать вопрос о Cardano? Тогда приглашаем в наше уютное русскоязычное сообщество Cardano в Telegram. Оставайтесь на связи, все только начинается!