Приключения с абстракцией счета — риски и их снижение в __validate__
Starknet находится в авангарде абстракции счетов, заложив ее непосредственно в протокол.
Одним из существенных преимуществ абстракции счета является возможность предоставления пользовательской логики проверки счета пользователя, например, биометрической проверки транзакций и ежедневных лимитов на снятие средств.
Работает это следующим образом: когда транзакция отправляется в сеть, она проверяется с помощью точки входа __validate__ контракта счета-отправителя, где, помимо прочего, проверяется подпись.
После успешного прохождения валидации вызывается точка входа __execute__, которая запускает собственно транзакцию.
Риски при использовании __validate__
В идеале произвольную логику проверки следует реализовать в коде __validate__, но это открывает потенциальные векторы DOS-атак.
Поскольку транзакции, не прошедшие проверку, отклоняются перед исполнением, они не влекут за собой никаких расходов. Это означает, что злоумышленник может развернуть очень дорогую реализацию __validate__, которая тратит много ресурсов, а затем просто выходит из строя, неся большие вычислительные затраты без какой-либо платы за это.
Снижение рисков
Starknet предпринимает ряд контрмер, направленных на ослабление вышеуказанного вектора DOS.
Вычисление предельных значений
Наиболее простая и интуитивно понятная мера — ограничить вычисления в __validate__. В частности, ограничить количество шагов, которые может выполнить проверка транзакции.
При достижении этого предела валидация не выполняется. Это ограничение ограничивает потенциальный объем вычислений, которые могут быть выполнены без взимания платы.
ОТКЛЮЧИТЬ транзакции на шлюзе
Другая мера противодействия — не включать в очередь секвенсора даже те транзакции, которые не прошли __validate__. Фактически это изменение реализовано в версии Starknet 0.12.1.
При отправке транзакции на шлюзе будет вызвана точка входа __validate__ контракта счета-эмитента. В случае неудачи проверки транзакция будет немедленно отклонена без дальнейшей обработки.
Это выгодно, так как после того, как транзакция пройдет проверку шлюза, она будет обработана секвенсором, вычислительные мощности которого являются гораздо более дефицитным ресурсом, чем вычислительные мощности шлюза.
Но подождите: как шлюз может убедиться в том, что транзакция действительно недействительна? А если проверка транзакции зависит от некоторого внешнего состояния?
Предотвратить call_contract
Другим, менее тривиальным ограничением является блокирование системных вызовов call_contract внутри __validate__.
Теперь вы можете спросить себя: зачем нам нужно это ограничение, если вычисления ограничены и недействительные транзакции отклоняются на шлюзе?
Чтобы объяснить это ограничение, необходимо предположить, что:
- Системный вызов call_contract разрешен в __validate__.
- В Starknet возможно использование MEV (на момент написания статьи в версии 0.12.1 Starknet это еще не реализовано, так как транзакции обрабатываются последовательно).
- Отклонять транзакции на шлюзе гораздо дешевле, чем на секвенсоре.
Given these assumptions, consider the following attack:
- Злоумышленник развертывает N учетных контрактов, каждый из которых реализует функцию __validate__, находящуюся на пределе допустимых вычислений.
- В конце вредоносной логики __validate__ злоумышленник запрашивает некоторое состояние в другом контракте, используя call_contract. Это приводит к тому, что __validate__ становится либо успешным, либо неуспешным в соответствии с ответом true / false соответственно.
- Теперь злоумышленник отправляет T транзакций с каждого из N контрактов вредоносного счета, то есть всего NxT транзакций. Все они проходят шлюз, так как злоумышленник еще во время отправки транзакции убедился, что call_contract вернет true.
- Сразу после этого злоумышленник отправляет транзакцию, которая изменяет ответ call_contract на false, пытаясь использовать MEV таким образом, чтобы эта транзакция была выполнена до NxT-транзакций от вредоносных контрактов счета.
- Если злоумышленнику удастся изменить состояние с true на false до начала NxT-транзакций, то с помощью одной транзакции он сможет создать NxT REJECTed-транзакций, используя максимум вычислений, не заплатив при этом никакой комиссии, поскольку транзакции не пройдут __validate__ во время выполнения. REJECT будет происходить на секвенсоре, который, как уже говорилось, является более ограниченным и дефицитным ресурсом в сети.
Таким образом, к ОТКАЗАТЬтранзакций на уровне шлюза, а также предотвратить зависимость __validate__ на внешнее состояние через call_contractТаким образом, мы снижаем риск того, что злоумышленник сможет собрать в очередь большое количество транзакций, проходящих через шлюз, и ОТКАЗАТЬ на секвенирование, что приводит к большим накладным расходам в сети, не приносящим никакой платы.
В следующей части этой серии мы расскажем, как реализовать современный поток валидации счета нескольких владельцев — еще одну ключевую возможность абстракции счетов — с учетом этих ограничений. Так что присоединяйтесь к нам в нашем следующем приключении Абстракции Аккаунта.
Йоав Газиэль является соучредителем компании Braavos, первого кошелька, разработанного специально для Starknet, а до этого в течение 10 лет работал техническим директором в двух известных израильских стартапах.
Если вы хотите узнать больше об абстракции счетов, посетите наше специальное руководство здесь. А если вы хотите глубже изучить наш код, посетите наш профиль на GitHub.