EIP-150: когда газ стал параметром безопасности
В 2016 году атакующий обнаружил, что самые дорогие операции Ethereum — чтение хранилища, загрузка аккаунтов — были оценены намного ниже того, чего они реально стоили узлу. Несколько дешёвых транзакций могли поставить каждый узел на Земле на колени. EIP-150 переоценил их — и тем самым превратил газ из номинальной платы в линию обороны.
- EVM и газ
- хранилище и доступ к состоянию
Глава про EVM продала газ как то, что делает вычисление оплаченным и ограниченным. У этой формулировки есть скрытое допущение: что цена каждого опкода действительно отражает работу, которую он заставляет проделать узел. В октябре 2016 года, на форке Tangerine Whistle, Ethereum узнал на собственном горьком опыте, что происходит, когда это допущение ложно. Некоторые операции — те, что тянутся в состояние и на диск — были оценены в малую долю своей реальной цены, и атакующий превратил этот разрыв в оружие, чуть не остановившее цепочку. EIP-150 — это исправление, и именно на нём сообщество усвоило, что газ — это не тарифная сетка, а параметр безопасности. Давайте выведем его.
Проблема: ценник не соответствовал работе
Большинство опкодов EVM — это чистые вычисления: сложение, сравнение, хеширование — и их цена честна. Но опкоды, которые касаются состояния, отличаются: SLOAD гонится за ключом через дерево состояния и на диск; CALL, BALANCE, EXTCODESIZE и EXTCODECOPY каждый загружает данные другого аккаунта. Это самые медленные вещи, которые делает узел — а в тарифной сетке газа 2015 года они были почти бесплатны: SLOAD стоил 50 газа, опкоды, касающиеся аккаунтов — 20–40. Ценник сидел намного ниже реального бремени.
Этот разрыв — атака на истощение ресурсов Создание дешёвых транзакций, которые провоцируют непропорционально дорогую работу на каждом валидирующем узле — здесь: спам недооценёнными опкодами доступа к состоянию, из-за которого обработка блоков замедляется до ползания, угрожая живости сети. (resource-exhaustion attack). В сентябре–октябре 2016 года DoS-атаки Shanghai Серия атак типа «отказ в обслуживании» 2016 года, забрасывавших спамом недооценённые IO-тяжёлые опкоды (сначала EXTCODESIZE, затем варианты SUICIDE/SLOAD), из-за которых обработка некоторых блоков доходила до десятков секунд, угрожая остановить Ethereum. (Shanghai DoS attacks) сделали именно это: транзакции, долбящие эти опкоды, растянули обработку блока до десятков секунд, и сеть подошла вплотную к полной остановке.
→ Шаг 2: поднимаем цены до соответствия стоимости.
Газ — параметр безопасности, так что выравниваем его по реальной цене
Исправление не хитроумное, а корректирующее: поднять цену IO-тяжёлых опкодов, пока она не станет отражать то, что они реально заставляют делать узел. EIP-150 поднял SLOAD с 50 до 200, BALANCE, EXTCODESIZE и опкоды загрузки аккаунтов — до 400–700, CALL — до 700, и так далее. Как только цена соответствует бремени, та самая атака, что остановила цепочку, теперь стоит атакующему реального газа — и попросту вытесняется из блока по цене.
Долговременный урок больше, чем цифры. газ как параметр безопасности Принцип, закреплённый EIP-150: цены газа на опкоды должны отслеживать реальное бремя CPU, диска и роста состояния, которое каждая операция накладывает на узлы — потому что любая операция, оценённая ниже своей истинной цены, это потенциальный вектор отказа в обслуживании, а не просто выгодная сделка. (gas as a security parameter) означает, что тарифная сетка газа — часть модели угроз Ethereum, а не только его тарифной модели. Каждая последующая переоценка — а их было много — восходит к этому осознанию.
→ Шаг 3: никогда не передавать последнюю каплю газа.
Правило 63/64: всегда оставляем немного про запас
EIP-150 привёз ещё одно, более тонкое изменение вместе с переоценкой. Когда контракт вызывает другой, он может передать не больше 63/64 своего оставшегося газа — он всегда оставляет себе как минимум 1/64. Так что независимо от того, насколько глубоко вкладываются вызовы или насколько жадно вызываемый сжигает то, что ему дали, вызывающий гарантированно переживёт под-вызов с небольшим запасом газа: достаточным, чтобы заметить сбой, прибраться и вернуть ошибку вместо того, чтобы просто умереть.