Автор: Tia, Techub News
Блокчейн жертвует эффективностью из-за своего децентрализованного дизайна, поэтому повышение скорости выполнения всегда было одной из неотложных задач. «Исполнительный уровень» блокчейна является ключевой частью обработки каждой транзакции и добавления ее в цепь. Для ускорения обработки увеличение производительности на исполнительном уровне стало одним из основных стратегий, а параллельное выполнение является важным прорывом в этом аспекте.
Традиционные блокчейны обычно обрабатывают транзакции последовательно, что значительно ограничивает скорость транзакций, особенно в сетях с высокой плотностью транзакций, что может вызвать перегрузку. Однако благодаря параллельному выполнению несколько транзакций могут обрабатываться одновременно, что значительно повышает эффективность выполнения и снижает нагрузку на цепь.
Чтобы лучше понять, что такое параллельность, мы сначала начнем с выполнения и объясним на примере Ethereum в режиме PBS после Merge, что такое выполнение, одновременно показывая, где выполнение находится на протяжении всего жизненного цикла транзакции.
Конкретные этапы выполнения транзакции
Транзакции попадают в память и проходят фильтрацию и сортировку: это этап предварительной обработки после подачи транзакции, включает взаимодействие Mempool, Searcher и Builder, завершение фильтрации и сортировки транзакций.
Builder создает блок (но не выполняет): Builder упорядочивает выгодные транзакции в блок для завершения упаковки и сортировки транзакций.
Предложитель проверяет и отправляет блок: после завершения построения блока Builder отправляет предложение блока Предложителю. Предложитель проверяет структуру блока и содержание транзакций, а затем официально отправляет блок в сеть для начала выполнения.
Выполнение транзакции: после подачи блока узлы выполняют транзакции внутри блока одну за одной. Это ключевой этап обновления состояния, каждая транзакция вызывает вызов смарт-контракта, изменения баланса аккаунта или изменения состояния.
Свидетели подтверждают: валидаторы подтверждают результаты выполнения блока и корень состояния и используют это как окончательное подтверждение. Это обеспечивает подлинность и действительность блока на исполнительном уровне и предотвращает несоответствия.
Синхронизация состояния: каждый узел будет синхронизировать результаты выполнения блока (например, обновления баланса аккаунтов, состояния контрактов и т.д.) в свое локальное состояние, после выполнения каждой транзакции узел вычисляет и сохраняет новый корень состояния, чтобы использовать его в следующем блоке в качестве начального состояния.
Конечно, это только синхронизация состояния транзакций по блокам, чтобы поддерживать актуальное состояние на цепи, обычно узлы синхронизируют данные по блокам один за другим и постоянно проверяют блоки и состояния. Но для достижения окончательности в механизме POS также необходимо, чтобы агрегаторы объединяли подписи свидетелей из каждого слота в единую подпись и передавали ее предложителю следующего слота, и валидаторы должны по прошествии одной эпохи на основе количества голосов подтвердить состояние всех блоков в этой эпохе, сформировав временную точку консенсуса. Когда два последовательных эпох получают поддержку большинства валидаторов, тогда блоки и транзакции достигают окончательности.
С точки зрения всего жизненного цикла транзакции выполнение происходит после того, как Предложитель проверяет структуру блока и содержание транзакции, отправленных Builder. Фактический процесс выполнения требует последовательной обработки транзакций и обновления соответствующих состояний аккаунтов или контрактов. После завершения выполнения всех транзакций Предложитель вычисляет новый корень состояния (корень Меркла), который является резюме результатов выполнения всех транзакций текущего блока и окончательного глобального состояния. Проще говоря, полный процесс выполнения блока включает в себя серию вычислений, необходимых для преобразования Ethereum из предыдущего состояния в следующее, от выполнения каждой транзакции до вычисления корня Меркла.
Последовательное выполнение
Противоположностью параллельному является последовательное выполнение, то есть текущий более общий способ выполнения в блокчейне. Обычно транзакции выполняются последовательно, шаг за шагом. После завершения выполнения транзакции Ethereum обновляет состояние аккаунта и соответствующую информацию (например, баланс, данные хранения контракта) в дереве состояния аккаунтов, новый хэш состояния аккаунта генерируется. После завершения обновления всех деревьев состояния аккаунтов образуется корневой хэш дерева состояния, называемый корнем Меркла состояния. После завершения корней состояния Меркла, корней транзакций и корней квитанций, заголовок блока проходит хэширование, чтобы сгенерировать хэш блока.
При этом порядок выполнения транзакций имеет решающее значение. Поскольку дерево Меркла является двоичным деревом хэшей, корневые значения Меркла, образованные в разном порядке, будут различаться.
Параллельное выполнение
В условиях параллельного выполнения узлы будут пытаться обрабатывать транзакции в блоке параллельно. Они не выполняют транзакции одну за другой, а распределяют их по различным «путям выполнения», чтобы они могли выполняться одновременно. За счет параллельного выполнения система может более эффективно обрабатывать транзакции в блоках, повышая пропускную способность.
После завершения выполнения всех транзакций узлы собирают результаты выполнения (то есть обновления состояния, на которое повлияли транзакции) и формируют новое состояние блока. Это состояние будет добавлено в блокчейн, представляя собой последнее глобальное состояние на цепи.
Конфликт состояния
Поскольку параллельность обрабатывает транзакции одновременно по различным путям, одной из основных трудностей является конфликт состояния. То есть могут возникать ситуации, когда несколько транзакций одновременно читают или записывают данные (состояние) в одной и той же части блокчейна. Если такие ситуации не обрабатывать должным образом, это приведет к неопределенности результатов выполнения. Из-за различий в порядке обновления состояния конечные вычислительные результаты также могут различаться. Например,
Предположим, есть две транзакции, транзакция A и транзакция B, которые обе пытаются обновить баланс одного и того же аккаунта:
Транзакция A: увеличить баланс аккаунта на 10.
Транзакция B: увеличить баланс аккаунта на 20.
Начальный баланс аккаунта составляет 100.
Если мы выполняем последовательно, результат порядка выполнения определен:
1. Сначала выполнить транзакцию A, затем выполнить транзакцию B:
Баланс аккаунта сначала увеличивается на 10, становится 110.
Добавьте еще 20, в итоге получится 130.
2. Сначала выполнить транзакцию B, затем выполнить транзакцию A:
Баланс аккаунта сначала увеличивается на 20, становится 120.
Добавьте еще 10, и в итоге получится 130.
В обоих этих порядках конечный баланс составляет 130, поскольку система обеспечивает последовательную согласованность выполнения транзакций.
Но в условиях параллельного выполнения транзакция A и транзакция B могут одновременно прочитать начальный баланс 100 и провести свои вычисления:
Транзакция A читает баланс 100, после вычислений обновляет его до 110.
Транзакция B также читает баланс 100, после вычислений обновляет его до 120.
В этом случае, поскольку транзакции выполняются одновременно, конечный баланс обновляется только до 120, а не до 130, потому что операции транзакции A и транзакции B «перекрыли» результаты друг друга, что привело к конфликту состояния.
Эти проблемы конфликтов состояния обычно называются «перекрытием данных», то есть когда транзакции пытаются одновременно изменить одни и те же данные, они могут перекрывать результаты вычислений друг друга, что приводит к неправильному окончательному состоянию. Другой проблемой, возникающей из конфликтов состояния, может быть невозможность гарантировать порядок выполнения. Поскольку несколько транзакций завершают операции в разные временные промежутки, это может привести к различным порядкам выполнения. Разный порядок может привести к различным вычислительным результатам, что делает результаты неопределенными.
Чтобы избежать такой неопределенности, системы параллельного выполнения блокчейнов обычно вводят некоторые механизмы обнаружения конфликтов и отката, или заранее проводят анализ зависимости транзакций, обеспечивая их параллельное выполнение без воздействия на окончательную согласованность состояния.
Оптимистичная параллельность и детерминированная параллельность
Существует два подхода к возможным конфликтам состояния: детерминированная параллельность и оптимистичная параллельность. Эти два режима имеют свои компромиссы в эффективности и сложности дизайна.
Детерминированная параллельность требует предварительного заявления о доступе к состоянию, валидаторы или секвенсеры проверяют заявленный доступ к состоянию при сортировке транзакций. Если несколько транзакций пытаются записать в одно и то же состояние, эти транзакции помечаются как конфликтующие, чтобы избежать одновременного выполнения. Конкретные реализации различных цепей для предварительного заявления о доступе к состоянию различаются, но обычно включают следующие формы:
С помощью контрактных спецификаций: разработчики напрямую определяют диапазон доступа к состоянию в смарт-контрактах. Например, для передачи токенов ERC-20 требуется доступ к полям баланса отправителя и получателя.
С помощью структурированных данных в транзакциях: добавление специальных полей в транзакцию для обозначения доступа к состоянию.
С помощью анализа компилятора: компиляторы высокоуровневых языков могут статически анализировать код контракта и автоматически генерировать наборы доступа к состоянию.
С помощью принудительного объявления в рамках: некоторые структуры требуют от разработчиков явно указывать состояние, к которому нужно получить доступ при вызове функции
Оптимистичная параллельность сначала обрабатывает транзакции с оптимистичным предположением, и только когда возникает конфликт, затронутые транзакции повторно выполняются в порядке. Чтобы максимально избежать конфликтов, основная идея оптимистичной параллельности заключается в быстром предположении и гипотезах о состоянии на основе исторических данных и статического анализа. То есть система в условиях неполной проверки предполагает, что определенные операции или обновления состояния допустимы, стараясь избежать ожидания всех процессов верификации, таким образом повышая производительность и пропускную способность.
Хотя оптимистичная параллельность может пытаться избежать конфликтов, основываясь на быстром прогнозировании и предположениях о состоянии, все еще существуют некоторые неизбежные трудности, особенно связанные с выполнением контрактов или межцепочечными транзакциями. Если конфликты происходят часто, повторное выполнение может значительно замедлить производительность системы и увеличить потребление вычислительных ресурсов.
Детерминированная параллельность также избегает конфликтов, которые могут возникнуть при оптимистичной параллельности, проверяя зависимости состояния перед транзакцией, но поскольку необходимо точно заявить о зависимостях состояния перед подачей транзакции, это ставит более высокие требования к разработчикам, что увеличивает сложность реализации.
Проблемы параллельного EVM
Обработка конфликтов состояния требует внимания не только к детерминированной и оптимистичной параллельности, но также необходимо рассмотреть архитектуру базы данных цепи. Проблема конфликтов состояния в параллельности особенно сложна в EVM, основанном на структуре дерева Меркла. Дерево Меркла является иерархической хэш-структурой, и каждый раз, когда транзакция изменяет некоторые данные состояния, корневой хэш дерева Меркла также должен быть обновлен. Этот процесс обновления является рекурсивным, начиная с листовых узлов и поднимаясь вверх к корневому узлу. Поскольку хэширование является необратимым, то вычислить верхний уровень можно только после завершения изменений на нижнем уровне, такая особенность делает параллельное обновление очень сложным.
Если две транзакции выполняются параллельно и обращаются к одному и тому же состоянию (например, к балансу аккаунта), это приведет к конфликту узлов дерева Меркла. А решение таких конфликтов обычно требует дополнительных механизмов управления транзакциями, чтобы гарантировать получение одного и того же корневого хэш-значения во всех ветвях. Это не легко реализовать для EVM, поскольку это требует компромисса между параллелизацией и согласованностью состояния.
Решения для не-EVM параллельности
Solana
В отличие от глобального дерева состояния Ethereum, Solana использует модель аккаунтов. Каждый аккаунт является независимым хранилищем, хранящимся в реестре, что предотвращает проблемы с конфликтами путей.
Solana является детерминированной параллельностью. В Solana для каждой транзакции необходимо явно заявить аккаунты и требуемые разрешения доступа (только для чтения или чтение-запись) в момент подачи. Этот дизайн позволяет узлам блокчейна заранее анализировать ресурсы, которые будут необходимы каждой транзакции перед ее выполнением. Поскольку все зависимости аккаунтов ясно указаны перед началом выполнения, узлы могут определить, какие транзакции будут обращаться к одним и тем же аккаунтам, а какие транзакции могут безопасно выполняться параллельно, таким образом осуществляя интеллектуальное распределение, избегая конфликтов, что является основой для параллельного распределения.
Поскольку для каждой транзакции заранее объявлены необходимые для доступа аккаунты и разрешения, Solana может проверять наличие зависимостей между транзакциями (модель Sealevel). Если между транзакциями нет общих читаемых или записываемых аккаунтов, система может распределять их по различным процессорам для параллельного выполнения.
Aptos
Дизайн параллельного выполнения Aptos значительно отличается от Ethereum, он включает в себя некоторые ключевые инновации в архитектуре и механизме, в частности, в модели аккаунтов и хранении состояния.
Ethereum требует частого обновления глобального дерева состояния (MPT) при выполнении транзакций. Все состояния аккаунтов и контрактов хранятся в общем дереве состояния, и любая транзакция требует доступа и обновления части этого дерева состояния. В то время как Aptos делит аккаунты на независимые единицы состояния, каждый объект является независимой парой ключ-значение, объекты могут существовать независимо друг от друга и не влияют друг на друга, и только при явных ссылках они будут связаны. Между объектами нет общих путей в дереве, и не возникает конкуренции за замки, что позволяет полностью параллелить.
Основная структура данных Aptos — это Jellyfish Merkle Tree. Состояние каждого объекта в конечном итоге хранится в JMT как независимая пара пара ключ-значение. В отличие от MPT Ethereum, Jellyfish Merkle Tree имеет форму полностью двоичного дерева, что значительно упрощает пути хранения и запросов для узлов, значительно снижая время проверки. Более того, позиция каждого аккаунта в дереве фиксирована, а узлы в дереве хранятся независимо, что позволяет обновлениям и поискам нескольких аккаунтов производиться параллельно.
Aptos использует оптимистичную параллельность, которая не требует предварительного предоставления зависимости всех заявленных аккаунтов. Для этого Aptos использует Block-STM, который использует заданный порядок транзакций для оценки зависимости, тем самым уменьшая количество прерываний.
Параллельный EVM
По сравнению с не-EVM параллельностью, параллельный EVM сталкивается с большими техническими сложностями при обработке зависимостей состояния, обнаружении конфликтов, управлении газом и механизмах отката. Чтобы лучше понять это, мы можем обратиться к тому, как некоторые проекты параллельного EVM (такие как Sui, Monad, Canto) решают эти проблемы.
Sui
Sui и Aptos также используют объектную модель для обработки состояния, принимая каждый объект (например, аккаунт, состояние смарт-контракта) как независимый ресурс, эти объекты различаются по уникальному идентификатору объекта. Когда транзакции касаются разных объектов, их можно обрабатывать параллельно, поскольку они работают с различными состояниями и не вызывают прямых конфликтов.
Хотя Sui использует объектную модель для управления состоянием, чтобы быть совместимым с EVM, архитектура Sui использует дополнительный адаптерный слой или абстрактные механизмы для соединения объектной модели и модели аккаунтов EVM.
В Sui планирование транзакций использует стратегию оптимистичной параллельности, предполагая, что между транзакциями нет конфликтов. Если конфликт происходит, система использует механизм отката для восстановления состояния.
Sui использует объектную модель и технологии изоляции состояния, что позволяет эффективно избегать проблем с зависимостями состояния. Каждый объект является независимым ресурсом, различные транзакции могут выполняться параллельно, что повышает пропускную способность и эффективность. Но такой подход имеет свои недостатки: сложность объектной модели и затраты на механизм отката. Если между транзакциями происходят конфликты, необходимо откатить часть состояния, что увеличивает нагрузку на систему и может повлиять на эффективность параллельной обработки. В отличие от не-EVM параллельных систем (таких как Solana), Sui требует больше вычислительных и хранилищных ресурсов для поддержания эффективной параллельности.
Монад
Как и Sui, Monad также использует оптимистичную параллельность. Но оптимистичная параллельность Monad предшествует фактическому выполнению транзакций, предсказывая некоторые транзакции с зависимостями, делая это в основном через статический анализатор кода Monad. Прогнозирование требует доступа к состоянию, а способ хранения состояния в базе данных Ethereum делает доступ к состоянию очень сложным. Чтобы повысить эффективность параллельного чтения состояния, Monad также переработал базу данных.
Дерево состояния Monad делится на разделы, каждый из которых поддерживает свое собственное поддерево состояния. При обновлении необходимо изменить только соответствующую часть, не перестраивая все дерево состояния. Быстрое локализование состояния в разделе осуществляется с помощью таблицы индексов состояния, что снижает взаимодействие между разделами.
Краткое резюме
Суть параллельности заключается в повышении эффективности выполнения на исполнительном уровне путем выполнения по нескольким путям, а для реализации многопутевого выполнения необходимо провести ряд операций, таких как обнаружение конфликтов и механизмы отката, чтобы обеспечить их параллельное выполнение без влияния на окончательную согласованность состояния, а также улучшить базу данных.
Конечно, повышение эффективности на исполнительном уровне не ограничивается только параллельностью. Оптимизация на этапе выполнения может также быть достигнута путем уменьшения количества операций чтения и записи, необходимых для одной транзакции в базе данных. В то время как повышение скорости всей цепи охватывает более широкий спектр, включая улучшение эффективности уровня консенсуса.
Каждая технология имеет свои специфические ограничения. Параллельность — это всего лишь один из способов повышения эффективности, окончательное решение о использовании этой технологии должно также учитывать, насколько она дружелюбна к разработчикам и возможно ли ее реализация без ущерба для децентрализации и т.д. Увеличение количества технологий не всегда является наилучшим решением, по крайней мере, для Ethereum, параллельность не так уж и привлекательна, если рассматривать только повышение эффективности; добавление параллельности для Ethereum не является оптимальным решением, независимо от того, рассматриваем ли мы простоту или текущую дорожную карту Ethereum с фокусом на Rollup.