EIP-2929 상태 접근 명령어 가스비 인상 (Gas cost increases for state access opcodes)

최종 확정 · 수수료·가스 · 원문 상태 Final · 2020-09-01 작성

트랜잭션에서 계정이나 저장소를 처음 읽을 때 내는 가스비를 약 3배로 올리고 같은 트랜잭션 안에서 다시 읽을 때는 100가스만 내도록 바꿉니다. 2021년 베를린 하드포크에 포함되어 이미 적용된 규칙입니다.

왜 필요한가

이더리움에서 가스비는 그 작업을 처리하는 데 걸리는 시간을 돈으로 환산한 값입니다. 그런데 디스크에서 데이터를 읽어 오는 명령어들, 즉 저장소를 읽는 SLOAD나 다른 계정의 잔액과 코드를 조회하는 명령어들은 실제 비용보다 훨씬 싸게 책정되어 있었습니다. 2016년 상하이 DoS 공격 때 공격자는 이 허점을 노려 수많은 계정을 건드리는 트랜잭션을 보내는 수법으로 네트워크를 느리게 만들었습니다. 이후 가스비를 올렸지만 충분하지 않았습니다. 연구에 따르면 악의적으로 만든 트랜잭션 하나가 평균 트랜잭션은 몇 밀리초면 끝나는데 20초에서 80초까지 걸렸습니다. 이 제안은 해당 명령어들의 비용을 약 3배로 올려 최악의 처리 시간을 7초에서 27초 수준으로 줄입니다. 동시에 한 번 읽은 곳을 다시 읽을 때는 아주 싸게 받아 정직하게 쓰는 컨트랙트가 손해 보지 않게 합니다.

무엇이 바뀌나

지금

EIP-2929 이후

누구에게 영향이 있나

심층 설명

상수와 적용 시점

FORK_BLOCK은 12244000이며 이 블록 번호 이상에서 규칙이 적용됩니다. 상수는 COLD_SLOAD_COST = 2100, COLD_ACCOUNT_ACCESS_COST = 2600, WARM_STORAGE_READ_COST = 100입니다. 대상 명령어는 SLOAD(0x54), CALL 계열(0xF1, 0xF2, 0xF4, 0xFA), BALANCE(0x31), EXT* 계열(0x3B, 0x3C, 0x3F)입니다. 프리컴파일과 같은 트랜잭션에서 이미 접근한 주소·슬롯은 인상에서 제외됩니다.

accessed_addresses와 accessed_storage_keys 집합

트랜잭션 실행 동안 accessed_addresses: Set[Address]와 accessed_storage_keys: Set[Tuple[Address, Bytes32]]를 유지합니다. 이 집합은 트랜잭션 컨텍스트 전체 범위이며 self-destruct-list나 전역 refund 카운터처럼 구현합니다. 어떤 범위가 revert되면 집합은 그 범위에 들어가기 전 상태로 되돌립니다. 실행 시작 시 accessed_storage_keys는 비어 있고 accessed_addresses에는 tx.sender, tx.to(컨트랙트 생성 트랜잭션이면 생성될 주소), 모든 프리컴파일이 들어갑니다. CREATE/CREATE2가 호출되면 주소가 비어 있는지 검사하기 전에 생성될 주소를 즉시 집합에 추가합니다. 이후 initcode 실행이 실패하거나 코드 저장 가스가 부족해도 그 주소 자체는 집합에 남습니다. 다만 내부 범위에서 추가된 다른 항목은 되돌려집니다. CREATE/CREATE2 자체의 가스 비용은 바뀌지 않습니다.

계정 접근과 SLOAD 가격 계산

EXTCODESIZE(0x3B), EXTCODECOPY(0x3C), EXTCODEHASH(0x3F), BALANCE(0x31) 또는 CALL(0xF1), CALLCODE(0xF2), DELEGATECALL(0xF4), STATICCALL(0xFA)의 대상 주소가 accessed_addresses에 없으면 COLD_ACCOUNT_ACCESS_COST를 부과하고 주소를 집합에 추가합니다. 이미 있으면 WARM_STORAGE_READ_COST를 부과합니다. 비용 부과와 집합 갱신은 명령어가 호출되는 시점에 일어납니다. 호출 계열에서 100/2600 비용은 기존 700처럼 즉시, 즉 호출에 쓸 수 있는 63/64 가스를 계산하기 전에 부과됩니다. SLOAD는 (address, storage_key) 쌍이 accessed_storage_keys에 없으면 COLD_SLOAD_COST를 내고 쌍을 추가합니다. 이미 있으면 WARM_STORAGE_READ_COST를 냅니다. 콜드 계정의 콜드 슬롯을 읽는 경우는 현재 실행이 이미 그 계정 안에 있어야만 슬롯을 읽을 수 있으므로 존재하지 않으며 이 EIP에서는 동작이 정의되지 않습니다. 원격 읽기·쓰기를 추가하는 미래 EIP가 그 가격을 정의해야 합니다.

SSTORE 변경

SSTORE 호출 시 (address, storage_key) 쌍이 accessed_storage_keys에 없으면 COLD_SLOAD_COST를 추가로 부과하고 쌍을 집합에 추가합니다. EIP-2200의 파라미터도 바꿉니다. SLOAD_GAS는 800에서 WARM_STORAGE_READ_COST로, SSTORE_RESET_GAS는 5000에서 5000 - COLD_SLOAD_COST로 바뀌며 나머지는 그대로입니다. SLOAD_GAS는 EIP-2200의 여러 곳에서 SSTORE_SET_GAS - SLOAD_GAS처럼 합성 정의에 쓰이므로 구현체는 그 정의들도 갱신해야 합니다. 이 변경이 필요한 이유는 값이 0인 임의의 슬롯에 0을 다시 쓰는 공격 때문입니다. 이 공격은 800가스만 내면서 사실상 저장소 읽기를 강제합니다. SSTORE_RESET_GAS를 줄인 덕분에 COLD_SLOAD_COST를 더한 SSTORE 총비용은 그대로이고 SLOAD 후 SSTORE하는 패턴(예: storage_variable += x)은 오히려 싸집니다.

SELFDESTRUCT 변경

SELFDESTRUCT의 ETH 수신자가 accessed_addresses에 없으면 보내는 금액이 0이든 아니든 기존 비용에 COLD_ACCOUNT_ACCESS_COST를 추가로 부과하고 수신자를 집합에 추가합니다. 다른 호출 계열과 달리 수신자가 이미 웜 상태여도 WARM_STORAGE_READ_COST를 부과하지 않습니다. SELFDESTRUCT는 이미 5K 가스가 들고 두 번 이상 호출하면 아무 일도 하지 않으므로, 변경을 작게 유지하려는 선택입니다.

평균 비용은 얼마나 오르나

증인 크기 기반 추산으로는 평균 블록의 증인이 약 1000kB이고 그중 약 750kB가 머클 증명입니다. 머클 가지당 2000바이트로 보수적으로 잡으면 블록당 약 375회 접근입니다. 블록당 약 160개 트랜잭션의 tx.sender와 tx.to는 인상 대상에서 빠지므로 과금 대상 접근은 블록당 약 50~375회입니다. 접근당 1900가스가 오르면 95000~712500가스이므로 가스 한도를 약 1~6% 올리면 상쇄됩니다. 더 정밀하게는 Martin Swende가 괴를리 트랜잭션을 분석한 결과 평균 가스 비용이 약 2.36% 오르는 것으로 나왔습니다. 많은 컨트랙트가 같은 슬롯을 비효율적으로 여러 번 읽기 때문에 일부 트랜잭션은 10% 넘게 절감됩니다.

무상태 이더리움과 증명 생성에 주는 이점

이 EIP는 무상태 증인(stateless witness) 크기를 수용 가능한 수준으로 만드는 작업의 대부분을 수행합니다. 바이너리 트라이로 전환한다고 가정하면 코드를 제외한 이론상 최대 증인 크기가 12500000 / 700 * 800 ≈ 14.3M바이트에서 12500000 / 2600 * 800 ≈ 3.85M바이트로 줄어듭니다. 코드 접근 가격은 코드 머클화가 구현될 때 바꿀 수 있습니다. SNARK/STARK 증인에도 비슷한 이점이 있습니다. Starkware 수치 기준으로 일반 데스크톱에서 초당 10000개의 Rescue 해시를 증명할 수 있다고 하면, 상태 접근으로 가득 찬 블록의 증인 생성이 현재 약 44.64초에서 이 EIP 이후 약 12.5초로 줄어 데스크톱 한 대로도 제시간에 증인을 만들 수 있습니다.

호환성 위험과 완화책

가스비를 올리는 EIP가 늘 그렇듯 세 경우에서 기존 컨트랙트가 깨질 수 있습니다. 하위 호출에 고정 가스를 주는 경우, 가스 한도에 가깝게 쓰는 호출에 의존하는 경우, ETH 송금 호출이 수신자에게 주는 기본 2300가스에 의존하는 경우입니다. 앞선 인상인 EIP-1884는 실제로 소수의 컨트랙트를 깨뜨렸습니다. 다만 이 EIP에는 EIP-1884에 없던 접근 기록 집합이 있어 복구 수단이 생깁니다. 컨트랙트 A가 주소 B에 송금해야 한다면, 먼저 B를 별도로 호출해 캐시에 올린 뒤 A를 호출하면 B의 SLOAD가 100가스만 들게 됩니다. 원문은 이를 더 일반화한 방안으로 주소와 저장소 키를 미리 데워 두는 POKE 프리컴파일 아이디어와, EOA에서 컨트랙트를 거쳐 다른 컨트랙트로 가는 경우까지 포함해 알려진 모든 깨짐 사례에 통하는 EIP-2930 접근 목록을 제시합니다. 이 EIP는 Final 상태로 확정된 명세입니다.

EIP 원문 · ← EIPs