BTC $79,167.47 -0.77%
ETH $2,494.69 +0.07%
BNB $740.71 -0.98%
XRP $1.40 -0.87%
SOL $103.94 -2.19%
TRX $0.3340 -0.34%
DOGE $0.0898 +0.95%
ADA $0.2207 +1.03%
BCH $259.98 +1.30%
LINK $12.90 +4.88%
HYPE $85.15 -3.34%
AAVE $132.36 -0.73%
SUI $0.8193 +2.95%
XLM $0.1908 +3.82%
ZEC $1,165.88 -3.60%
BTC $79,167.47 -0.77%
ETH $2,494.69 +0.07%
BNB $740.71 -0.98%
XRP $1.40 -0.87%
SOL $103.94 -2.19%
TRX $0.3340 -0.34%
DOGE $0.0898 +0.95%
ADA $0.2207 +1.03%
BCH $259.98 +1.30%
LINK $12.90 +4.88%
HYPE $85.15 -3.34%
AAVE $132.36 -0.73%
SUI $0.8193 +2.95%
XLM $0.1908 +3.82%
ZEC $1,165.88 -3.60%

Виталик Новый обзор: многомерное ценообразование Gas

Summary: Виталик говорит о многомерном ценообразовании Gas в Ethereum, как следует взвешивать и выбирать?
Виталик Бутерин
2024-05-09 20:05:19
Виталик говорит о многомерном ценообразовании Gas в Ethereum, как следует взвешивать и выбирать?

?

Автор:Vitalik Buterin

Составитель: Karen, Foreisght News

В Ethereum ресурсы до недавнего времени были ограничены и оценивались с помощью единственного ресурса, называемого «Gas». Gas — это единица измерения, которая определяет «вычислительные усилия» (computational effort), необходимые для обработки конкретной транзакции или блока. Gas объединяет различные типы «вычислительных усилий», наиболее важные из которых включают:

  1. Сырые вычисления (Raw computation, например, ADD, MULTIPLY);

  2. Чтение и запись в хранилище Ethereum (например, SSTORE, SLOAD, ETH переводы);

  3. Ширина канала передачи данных;

  4. Стоимость генерации ZK-SNARK доказательств для блока.

Например, эта транзакция, которую я отправил, в общей сложности потребила 47,085 Gas. В это входит: (i) базовая стоимость 21000 Gas, (ii) потребление байтов calldata, включенных в транзакцию, составило 1556 Gas, (iii) чтение и запись в хранилище составило 16500 Gas, (iv) генерация журналов (log) составила 2149 Gas, остальное было использовано для выполнения EVM. Транзакционные сборы, которые должен заплатить пользователь, пропорциональны потреблению Gas. Один блок может содержать максимум 30 миллионов Gas, и цена Gas постоянно корректируется с помощью механизма EIP-1559, чтобы гарантировать, что каждый блок в среднем содержит 15 миллионов Gas.

Этот подход имеет одно главное преимущество: поскольку все объединено в один виртуальный ресурс, проектирование рынка становится очень простым. Оптимизировать транзакции для минимизации затрат легко, оптимизировать блоки для взимания максимально возможных сборов относительно просто (не включая MEV), и нет странных механизмов стимулов, которые побуждают некоторые транзакции объединяться с другими для экономии на сборах.

Тем не менее, этот подход также имеет неэффективность: он рассматривает различные ресурсы как взаимозаменяемые, в то время как реальные ограничения на уровне основы различаются. Чтобы понять эту проблему, вы можете сначала взглянуть на следующий график:

Виталик Новый обзор: многомерное ценообразование Gas

Ограничение Gas накладывает ограничение:

Виталик Новый обзор: многомерное ценообразование Gas

Реальные ограничения безопасности на уровне основы обычно ближе к:

Виталик Новый обзор: многомерное ценообразование Gas

Это различие приводит к тому, что ограничение Gas либо безосновательно исключает блоки с реальной безопасностью, либо принимает блоки, которые на самом деле небезопасны, либо и то, и другое.

Если есть n типов ресурсов с различными ограничениями безопасности, то одномерный Gas может снизить пропускную способность максимум в n раз. Поэтому на протяжении долгого времени существовал интерес к концепции многомерного Gas, и с помощью EIP-4844 мы теперь фактически реализовали многомерный Gas в Ethereum. Эта статья исследует преимущества этого подхода и перспективы дальнейшего улучшения.

Blob: Многомерный Gas в Dencun

В начале этого года средний размер блока составлял 150 кБ. Значительная часть из этого — данные Rollup: протоколы второго уровня хранят данные в цепочке. Эти данные очень дороги: хотя стоимость транзакций на Rollup составляет всего 5-10 раз больше, чем соответствующие транзакции на Ethereum L1, даже такая стоимость слишком высока для многих случаев использования.

Так почему бы не снизить стоимость Gas для calldata (в настоящее время ненулевой байт стоит 16 Gas, нулевой байт — 4 Gas), чтобы сделать Rollup дешевле? Мы уже делали это раньше и можем сделать это снова. Но ответ здесь таков: максимальный размер блока составляет 30,000,000/16=1,875,000 ненулевых байтов, и сеть едва может или почти не может обрабатывать такие большие блоки. Снижение стоимости в 4 раза увеличит максимальный размер до 7.5 МБ, что создаст огромные риски для безопасности.

Эта проблема в конечном итоге решается путем введения независимого, дружелюбного к Rollup пространства данных (называемого blob) в каждом блоке.

Эти два ресурса имеют разные цены и ограничения: после хардфорка Dencun один блок Ethereum может содержать максимум (i) 30 миллионов Gas и (ii) 6 blob, каждый из которых может содержать около 125 кБ calldata. Эти два ресурса имеют отдельные цены и регулируются отдельным механизмом ценообразования, аналогичным EIP-1559, с целью использования в среднем 15 миллионов Gas и 3 blob на блок.

В результате стоимость Rollup снизилась в 100 раз, объем транзакций на Rollup увеличился более чем в 3 раза, в то время как теоретически максимальный размер блока увеличился лишь незначительно: с примерно 1.9 МБ до примерно 2.6 МБ.

Виталик Новый обзор: многомерное ценообразование Gas

Примечание: Транзакционные сборы Rollup предоставлены Growthepie.xyz. Хардфорк Dencun произошел 13 марта 2024 года и ввел многомерное ценообразование blob.

Многомерный Gas и безстатусные клиенты

В ближайшем будущем у безстатусных клиентов (stateless clients) также возникнут аналогичные проблемы с доказательствами хранения. Безстатусные клиенты — это новый тип клиентов, которые смогут проверять цепочку, не храня в локальной памяти большие объемы или вообще никакие данные. Безстатусные клиенты достигают этого, принимая доказательства конкретных частей состояния Ethereum, к которым транзакции в этом блоке должны получить доступ.

Виталик Новый обзор: многомерное ценообразование Gas

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

Одно чтение из хранилища требует 2100-2600 Gas, в зависимости от типа чтения, в то время как запись в хранилище стоит дороже. В среднем один блок выполняет около 1000 операций чтения и записи в хранилище (включая проверки баланса ETH, вызовы SSTORE и SLOAD, чтение кода контрактов и другие операции). Однако теоретически максимальное значение составляет 30,000,000/2,100=14,285 чтений. Нагрузки на пропускную способность безстатусного клиента пропорциональны этому числу.

Текущий план заключается в том, чтобы поддержать безстатусных клиентов, изменив проект дерева состояния Ethereum с Merkle Patricia tree на Verkle tree. Однако Verkle tree не обладает квантовой стойкостью и не является оптимальным выбором для более новых систем доказательства STARK. Поэтому многие заинтересованы в поддержке безстатусных клиентов с помощью двоичных Merkle tree и STARK, либо полностью пропуская Verkle, либо переходя на него через несколько лет, как только STARK станет более зрелым.

Доказательства STARK на основе двоичных хеш-деревьев имеют много преимуществ, но их ключевой недостаток заключается в том, что время генерации доказательства очень долго: Verkle tree может доказать более 100,000 значений в секунду, в то время как STARK на основе хеша обычно может доказать только несколько тысяч хешей в секунду, и доказательство каждого значения требует включения множества хешей в «ветвь» (branch).

Учитывая сегодняшние прогнозы от таких супероптимизированных систем доказательства, как Binius и Plonky3, а также специализированных хешей, таких как Vision-Mark-32, мы, похоже, находимся в пределах практического диапазона, где доказательство 1000 значений возможно, но доказательство 14,285 значений невозможно. Средний блок не будет проблемой, но потенциально худшие случаи блоков (выпущенные злоумышленником) могут разрушить сеть.

Нашим стандартным методом решения таких ситуаций является повторное ценообразование: повышение стоимости чтения из хранилища, чтобы снизить максимальное значение для каждого блока до более безопасного уровня. Однако мы уже делали это много раз, и если мы сделаем это снова, это сделает слишком много приложений слишком дорогими. Лучший способ — это многомерный Gas: ограничивать и взимать плату за доступ к хранилищу отдельно, поддерживая среднее использование на уровне 1000 доступов к хранилищу на блок, но устанавливая верхний предел для каждого блока, например, 2000 доступов.

Универсальность многомерного Gas

Еще одним ресурсом, который стоит рассмотреть, является рост размера состояния: то есть операции, увеличивающие размер состояния Ethereum, которые затем требуют от полных узлов его сохранения. Уникальность роста размера состояния заключается в том, что причина его ограничения полностью исходит от долгосрочного постоянного использования, а не от пикового.

Поэтому добавление отдельного измерения Gas для операций, увеличивающих размер состояния (например, zero-to-non-zero SSTORE, создание контрактов), может быть ценным, но цель будет другой: мы можем установить плавающую цену для конкретного среднего использования, но полностью не устанавливать ограничения для каждого блока.

Это демонстрирует одну из мощных характеристик многомерного Gas: она позволяет нам отдельно для каждого ресурса задавать вопросы (i) каков идеальный средний уровень использования? (ii) каков безопасный максимальный уровень использования для каждого блока? В отличие от установки цены Gas на основе максимального значения для каждого блока и последующего следования среднему уровню использования, у нас есть 2n степеней свободы для установки 2n параметров, которые можно регулировать в зависимости от соображений безопасности сети.

Более сложные ситуации, например, когда соображения безопасности двух ресурсов частично складываются, могут быть решены путем того, чтобы один код операции или ресурс потреблял определенное количество Gas различных типов (например, zero-to-non-zero SSTORE может потреблять 5000 Gas для доказательства безстатусного клиента и 20000 Gas для расширения хранилища).

Каждая транзакция Max (выбирая тот, который потребляет больше данных или вычислений)

Пусть 𝑥1 — это стоимость Gas для данных, 𝑥2 — стоимость Gas для вычислений, тогда в одномерной системе Gas мы можем записать стоимость Gas для транзакции:

Виталик Новый обзор: многомерное ценообразование Gas

В этой схеме мы определяем стоимость Gas для транзакции как:

Виталик Новый обзор: многомерное ценообразование Gas

То есть транзакция не взимается по сумме данных и вычислений, а по тому, какой из двух ресурсов она потребляет больше. Это можно легко расширить, чтобы охватить больше измерений (например, 𝑚𝑎𝑥(…,𝑥3∗𝑠𝑡𝑜𝑟𝑎𝑔𝑒_𝑎𝑐𝑐𝑒𝑠𝑠)).

Должно быть легко увидеть, как это может повысить пропускную способность, сохраняя безопасность. Теоретически максимальное количество данных в блоке по-прежнему составляет Gas LIMIT /𝑥1, что полностью совпадает с одномерной схемой Gas. Аналогично, теоретически максимальное количество вычислений составляет Gas LIMIT /𝑥2, что также совпадает с одномерной схемой Gas. Однако стоимость Gas для любой транзакции, потребляющей данные и вычисления, будет снижена.

Это, вероятно, схема, предложенная в EIP-7623, чтобы уменьшить максимальный размер блока, одновременно увеличивая количество blob. Точный механизм в EIP-7623 немного сложнее: он сохраняет текущую цену calldata на уровне 16 Gas за байт, но добавляет минимальную цену в 48 Gas за байт; транзакция платит большее из (16 * bytes + execution_Gas) и (48 * bytes). Таким образом, EIP-7623 уменьшает теоретически максимальные данные вызова транзакции в блоке с примерно 1.9 МБ до примерно 0.6 МБ, при этом сохраняя стоимость большинства приложений на прежнем уровне. Преимущество этого подхода заключается в том, что он вызывает очень небольшие изменения по сравнению с текущей одномерной схемой Gas, что делает его очень простым для реализации.

Однако у этого метода есть два недостатка:

  1. Даже если все остальные транзакции в блоке используют очень мало этого ресурса, транзакции, которые занимают много одного ресурса, все равно будут необоснованно взимать большие сборы;

  2. Он побуждает ресурсоемкие и вычислительно емкие транзакции объединяться в один пакет для экономии затрат.

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

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

Многомерный EIP-1559: более сложная, но идеальная стратегия

Давайте сначала вспомним, как работает обычный EIP-1559. Мы сосредоточимся на версии, введенной для blob в EIP-4844, так как она математически более элегантна.

Мы отслеживаем параметр excess_blobs. В течение каждого блока мы устанавливаем:

excessblobs \<-- max (excessblobs + len(block.blobs) - TARGET, 0)

где TARGET = 3. То есть, если в каком-то блоке количество blob превышает целевое значение, excessblobs увеличивается, если количество blob меньше целевого значения, excessblobs уменьшается. Затем мы устанавливаем blobbasefee = exp(excessblobs / 25.47), где exp — это приближенное значение экспоненциальной функции 𝑒𝑥𝑝(𝑥)=2.71828^𝑥.

Виталик Новый обзор: многомерное ценообразование Gas

То есть каждый раз, когда excessblobs увеличивается примерно на 25, базовая стоимость blob увеличивается примерно в 2.7 раза. Если blob становится слишком дорогим, среднее использование снижается, и excessblobs начинает уменьшаться, что автоматически снова снижает цену. Цена на blob постоянно корректируется, чтобы гарантировать, что в среднем блок наполовину заполнен, то есть каждый блок в среднем содержит 3 blob.

Если использование испытывает краткосрочный пик, возникает ограничение: каждый блок может содержать максимум 6 blob, в этом случае транзакции могут конкурировать друг с другом, повышая приоритетную плату. Однако в нормальных условиях каждый blob должен платить только blob_basefee плюс небольшую дополнительную плату за приоритет в качестве стимула для включения.

Это ценообразование Gas существует в Ethereum уже много лет: еще в 2020 году EIP-1559 ввел очень похожий механизм. С помощью EIP-4844 мы установили два независимых плавающих ценовых механизма для Gas и Blob.

Виталик Новый обзор: многомерное ценообразование Gas

Примечание: Базовая стоимость Gas за час 8 мая 2024 года, измеряется в gwei. Источник: ultrasound.money

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

Для пользователей этот опыт очень похож на сегодняшний: вы больше не платите одну базовую плату (basefee), а платите две базовые платы, но ваш кошелек может абстрагировать это от вас, показывая только ожидаемые сборы и максимальные сборы, которые вы можете ожидать.

Для строителей блоков в большинстве случаев лучшая стратегия остается такой же, как и сегодня: включать любое действительное содержимое. Большинство блоков не заполнены — ни Gas, ни Blob. Сложной ситуацией является, когда достаточно Gas или достаточно Blob превышает лимит блока, в этом случае строителям потенциально нужно решить многомерную задачу о рюкзаке, чтобы максимизировать свою прибыль. Тем не менее, даже с довольно хорошими приближенными алгоритмами, в этом случае выгода от оптимизации прибыли с помощью специализированного алгоритма будет значительно меньше, чем выгода от выполнения той же операции с помощью MEV.

Для разработчиков основным вызовом является необходимость переосмыслить функции EVM и его сопутствующей инфраструктуры, которые в настоящее время спроектированы на основе единой цены и единого ограничения, и теперь их необходимо преобразовать в проект, способный адаптироваться к нескольким ценам и нескольким ограничениям.

Одной из проблем, с которой сталкиваются разработчики приложений, является то, что оптимизация становится немного сложнее: в некоторых случаях вы больше не можете четко сказать, что A более эффективно, чем B, потому что если A использует больше calldata, а B использует больше выполнения, то когда calldata дешевле, A может быть дешевле, а когда calldata дороже, A может быть дороже.

Тем не менее, разработчики все еще могут добиться довольно хороших результатов, оптимизируя на основе долгосрочных исторических средних цен.

Многомерное ценообразование, EVM и подзвонки

Существует проблема, которая не возникает в blob, не возникает в EIP-7623 или даже в полном многомерном ценообразовании для calldata, но если мы попытаемся установить отдельные цены для доступа к состоянию или любому другому ресурсу, эта проблема возникнет: это ограничения Gas в подзвонках (sub-calls).

Ограничения Gas в EVM существуют в двух местах. Во-первых, каждая транзакция устанавливает ограничение Gas (Gas Limit), которое ограничивает общее количество Gas, которое может быть использовано в этой транзакции. Во-вторых, когда один контракт вызывает другой контракт, этот вызов может установить свое собственное ограничение Gas. Это позволяет контрактам вызывать другие контракты, которым они не доверяют, и все еще гарантирует, что у них останется достаточно Gas для выполнения других вычислений после вызова.

Виталик Новый обзор: многомерное ценообразование Gas

Примечание: Трассировка абстрактной транзакции, в которой один аккаунт вызывает другой аккаунт и предоставляет вызываемому только ограниченное количество Gas, чтобы гарантировать, что даже если вызываемый исчерпает весь выделенный ему Gas, внешний вызов все равно сможет продолжать выполняться.

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

Это одна из причин, по которой предложения о многомерном Gas обычно остаются на двух измерениях: данных и выполнения. Данные (будь то транзакционный calldata или blob) распределяются только вне EVM, поэтому внутри EVM не требуется никаких изменений, чтобы сделать отдельное ценообразование для calldata или blob.

Мы можем придумать «решение в стиле EIP-7623», чтобы решить эту проблему. Это простая реализация: во время выполнения взимать плату в 4 раза больше за операции с хранилищем; для упрощения анализа предположим, что каждая операция с хранилищем требует 10000 Gas. В конце транзакции возмещается min(7500 * storageoperations, executionGas). В результате, после вычета возмещения, пользователю необходимо заплатить следующую сумму:

executionGas + 10000 * storageoperations - min(7500 * storageoperations, executionGas)

Это эквивалентно:

max(executionGas + 2500 * storageoperations, 10000 * storage_operations)

Это отражает структуру EIP-7623. Другой подход заключается в том, чтобы в реальном времени отслеживать storageoperations и executionGas и взимать 2500 или 10000 в зависимости от того, насколько увеличивается max(executionGas + 2500 * storageoperations, 10000 * storage_operations) при вызове кода операции. Это позволяет избежать необходимости чрезмерного выделения Gas для транзакции, при этом этот Gas в основном возвращается через возмещение.

Мы не получили детализированного разрешения на подзвонки: подзвонки могут исчерпать все выделение Gas для выполнения дешевых операций с хранилищем.

Но мы действительно получили что-то достаточно хорошее, а именно контракты, выполняющие подзвонки, могут установить ограничение и гарантировать, что после выполнения подзвонка у основного вызова все еще будет достаточно Gas для необходимой постобработки (post-processing).

Я могу представить себе самое простое «полное решение многомерного ценообразования»: мы рассматриваем ограничения Gas подзвонков как пропорциональные. То есть, предположим, есть 𝑘 различных типов выполнения, и каждая транзакция устанавливает многомерные ограничения 𝐿1…𝐿𝑘. Предположим, в текущей точке выполнения остаток Gas составляет 𝑔1…𝑔𝑘. Предположим, вызывается операция CALL и используется ограничение Gas подзвонка 𝑆. Пусть 𝑠1=𝑆, затем 𝑠2=𝑠1/𝑔1∗𝑔2, 𝑠3=𝑠1/𝑔1∗𝑔3 и так далее.

То есть мы рассматриваем первый тип Gas (на самом деле выполнение VM) как особую «единицу аккаунта», а затем распределяем другие типы Gas, чтобы подзвонки получали одинаковый процент доступного Gas для каждого типа. Этот подход немного неуклюжий (ugly), но максимально гарантирует обратную совместимость.

Если мы хотим сделать это решение более «нейтральным» между различными типами Gas, не жертвуя обратной совместимостью, мы можем просто представить параметры ограничения Gas подзвонков как часть оставшегося Gas в текущем контексте (например, [1…63]/64).

Тем не менее, в любом случае стоит подчеркнуть, что как только мы начинаем вводить многомерный Gas выполнения, присущая сложность (ugliness) возрастает, и это, похоже, трудно избежать.

Таким образом, наша задача состоит в том, чтобы сделать сложный компромисс: принимаем ли мы определенный уровень увеличения сложности (ugliness) на уровне EVM, чтобы безопасно разблокировать значительные выгоды по масштабируемости L1, и если да, то какое конкретное предложение будет наиболее эффективным для экономии протокола и разработчиков приложений? Очень вероятно, что оба предложенных мною выше решения не являются лучшими, но все же есть пространство для более элегантных и лучших предложений.

Особая благодарность Ансгару Дитрихсу, Барнабе Монно и Давиде Крапису за их отзывы и рецензии.

warnning Предупреждение о рисках
app_icon
ChainCatcher Building the Web3 world with innovations.