EIP-8253 논스 0 저장소 계정의 논스 올리기 (Bump nonce of zero-nonce storage accounts)
Hegota 포함 검토 (CFI) · EVM·실행 · 원문 상태 Draft · 2026-05-05 작성
코드가 없고 논스가 0인데 저장소가 비어 있지 않은 메인넷 계정 28개의 논스를 포크 블록 시작 시점에 1로 올립니다. 그러면 EIP-684 규칙만으로 이 주소에 새 컨트랙트가 생기는 일을 막습니다.
왜 필요한가
이더리움은 새 컨트랙트를 만들 때 목적지 주소의 논스(nonce, 계정이 거래나 컨트랙트 생성을 한 횟수)가 0이고 코드가 없는지만 확인합니다(EIP-684). 논스가 0이고 코드가 없으면 저장소도 비어 있을 것이라고 전제한 규칙입니다. 그런데 EIP-161(스퓨리어스 드래곤 포크) 전에는 컨트랙트를 만들 때 초기화 코드가 돌기 전에 논스를 올리지 않았습니다. 그래서 생성자가 저장소에 값을 쓰고 코드는 반환하지 않으면 논스 0, 코드 없음, 저장소 있음인 계정이 남았습니다. 이 형태는 그 전제가 깨지는 유일한 경우입니다. 이런 주소에 새 컨트랙트가 생성되면 옛 저장소를 그대로 물려받습니다. 컨트랙트를 만들 때마다 저장소가 비었는지 더 확인하는 방법도 있지만 더 늘어나지 않는 주소 몇 개를 지키려고 모든 컨트랙트 생성에 검사 비용과 디스크 읽기를 영원히 치르게 됩니다. 이 EIP는 해당 계정의 논스를 한 번만 1로 올려 문제의 계정 형태 자체를 없앱니다.
무엇이 바뀌나
지금
- 코드 없음, 논스 0, 저장소 있음인 계정이 메인넷에 28개 남아 있습니다.
- 이 주소로 CREATE가 성공하면 새 컨트랙트가 기존 저장소를 그대로 물려받습니다.
- 뒤이은 EIP마다 이 예외 형태를 따로 분석하고 테스트해야 합니다.
EIP-8253 이후
- 포크 블록 시작 시 28개 계정의 논스가 1이 됩니다. 잔액, 코드, 저장소는 그대로입니다.
- 이 주소로 CREATE나 CREATE2를 시도하면 EIP-684에 따라 실패합니다.
- 논스 0이고 코드가 없으면 저장소도 비어 있다는 규칙이 다시 성립합니다.
누구에게 영향이 있나
- 사용자: 눈에 보이는 변화가 없습니다. 잔액과 공급량이 바뀌지 않고 대상 계정은 코드가 없어 호출해도 아무 일이 일어나지 않습니다.
- 클라이언트 팀: 포크 블록 시작 시 체인 설정의 계정 목록대로 논스를 1로 쓰는 비정규 상태 전이를 구현합니다. 컨트랙트 생성 경로에 저장소 검사를 추가할 필요는 없습니다.
- 스펙·테스트 작성자: 이 계정 형태가 상태에서 사라져 이후 EIP의 분석과 테스트 부담이 줄어듭니다.
- L2·다른 체인 운영자: 메인넷 목록을 그대로 쓰면 안 됩니다. 자기 체인 상태에서 목록을 다시 뽑아야 합니다. 제네시스부터 EIP-161이 적용된 체인은 목록이 비어 아무 일도 하지 않습니다.
심층 설명
대상 계정의 조건과 목록
목록은 체인 설정 파라미터이며 시스템 컨트랙트 주소와 같은 방식으로 관리합니다. 한 체인의 목록은 포크 블록 직전 상태(pre-state)에서 다음 세 조건을 모두 만족하는 계정을 정확히 담아야 합니다(MUST). nonce == 0, codeHash == keccak256("")(EmptyCodeHash), storageRoot != keccak256(RLP(""))(EmptyRootHash). 이런 계정은 EIP-161 이전의 컨트랙트 생성이나 제네시스 할당으로만 만들어집니다. 제네시스부터 EIP-161이 켜져 있고 제네시스에 이런 계정을 넣지 않은 체인은 목록이 비어 이 EIP는 아무 일도 하지 않습니다. 한 체인의 목록을 다른 체인 상태에 대해 다시 뽑지 않고 재사용하는 것은 금지됩니다(MUST NOT).
메인넷 목록 28개와 공개 자료
메인넷 대상 계정은 targeted-accounts.json에 28개 항목으로 공개됩니다. 각 항목은 20바이트 주소와 그 keccak256 해시를 담습니다. 파일에는 각 계정의 0이 아닌 저장소 슬롯(슬롯 키, 키 해시, 값)도 실려 있어 파일만으로 저장소 루트를 다시 계산해 보고된 루트와 비교할 수 있습니다. 슬롯 정보는 상태 전이에는 쓰이지 않습니다. 목록을 만든 절차와 eth_getProof(latest 기준) 검증은 methodology.md에 적혀 있습니다. 스퓨리어스 드래곤 블록 시점의 EIP-161 삭제 이전 상위 집합 224개는 zero-nonce-matches.jsonl로 함께 공개됩니다. 28개 가운데 26개는 외부 소유 계정 12개가 보낸 컨트랙트 생성 거래로 만들어졌고 나머지 2개는 팩토리 컨트랙트가 논스 0에서 CREATE로 만들었습니다. 공개된 목록은 포크 블록 높이에서 같은 절차를 메인넷에 돌린 결과와 바이트 단위로 같아야 합니다(MUST).
상태 전이 규칙
포크 블록 시작 시점에, EIP-2935·EIP-4788·EIP-7002·EIP-7251 같은 실행 전 시스템 컨트랙트 호출보다 먼저, 그리고 어떤 거래보다도 먼저 목록의 각 계정 A의 nonce를 1로 설정합니다(MUST). balance, codeHash, storageRoot는 바꾸지 않습니다. 전이는 무조건적입니다. A를 먼저 읽어 조건을 검사하지 않고 그냥 씁니다. 목록 조건이 nonce가 0임을 보장하므로 실제로는 0에서 1로 올리는 것과 같습니다. 목록이 비어 있으면 아무 일도 하지 않습니다. 이 전이는 거래, 영수증, 로그를 만들지 않고 가스도 쓰지 않습니다. 참조 구현은 각 항목에 대해 nonce == 0을 assert한 뒤 set_nonce(address, 1)을 호출하는 반복문입니다. assert 실패는 전이가 처리할 상황이 아니라 목록 설정 오류입니다.
EIP-7928 블록 접근 목록(BAL) 연동
포크 블록에서 EIP-7928이 켜져 있으면 BAL은 이 변경을 block_access_index = 0에 기록해야 합니다(MUST). 실행 전 시스템 컨트랙트 호출과 같은 인덱스입니다. 대상 계정 A의 AccountChanges 항목에 nonce_changes로 NonceChange [0, 1](block_access_index = 0, new_nonce = 1) 하나만 더합니다. storage_changes, storage_reads, balance_changes, code_changes에는 아무것도 더하지 않습니다. 같은 블록에서 A를 건드리는 것이 없으면 항목은 [A, [], [], [], [[0, 1]], []]입니다. 같은 블록의 거래와 시스템 컨트랙트 호출은 같은 항목에 자기 변경을 덧붙입니다. 목록이 비면 BAL에 아무것도 더하지 않습니다. 포크 블록 직전 상태 루트에 BAL을 다시 적용하면 포크 블록 이후 상태 루트가 나와야 합니다(MUST). 논스 변경이 계정당 NonceChange 하나뿐이므로 블록 빌더는 비정규 전이를 다시 실행하지 않고도 인덱스 0에 이 변경을 내보낼 수 있습니다.
왜 런타임 검사나 저장소 삭제가 아니라 논스 올리기인가
컨트랙트 생성마다 저장소 검사를 넣는 방식은 문제 상태를 그대로 두고 닫힌 주소 집합을 지키려고 생성마다 비용을 영원히 냅니다. 이 EIP는 대신 한 번의 합의 변경을 택합니다. 빈 계정을 정리한 EIP-7523과 같은 패턴입니다. 대상 계정의 저장소를 비우는 방안도 검토됐지만 더 침습적입니다. 계정당 최소 하나, 많게는 여러 슬롯을 지워야 하고 storageRoot를 다시 계산해야 합니다. 메인넷 기준으로 28개 계정의 저장소 항목 129개를 지워야 하는데, 논스 올리기는 28번의 논스 변경으로 끝납니다. 논스 1, 코드 없음, 저장소 있음은 이더리움에 이미 존재할 수 있는 형태입니다. 조건부 전이(조건을 아직 만족할 때만 올리기)도 검토 후 기각됐습니다. 그러면 메인넷 목록을 다른 체인에 그대로 적용할 수 있게 됩니다. 그 체인은 건드릴 필요가 없던 주소의 BAL 항목을 떠안으며 BAL 내용이 런타임 검사에 따라 달라집니다. 조건을 목록 쪽에 두면 전이와 BAL이 무조건적이 되고 검사는 포크 전 상태에 대해 오프라인에서 검증할 수 있습니다.
보안 고려: 목록 정확성과 닫힌 집합
목록의 누락과 잘못된 항목 모두 합의에 영향을 줍니다. 누락은 그 계정의 충돌 위험을 남깁니다. 전이가 무조건적이므로 존재하지 않는 계정을 넣으면 논스 1인 계정이 새로 생기고(EIP-161 기준으로 빈 계정이 아니라 남습니다), 논스가 0이 아닌 계정을 넣으면 그 논스를 덮어씁니다. 조건을 어긴 목록은 잘못된 시스템 컨트랙트 주소와 같은 체인 설정 오류입니다. 이 EIP는 그 경우의 동작을 정의하지 않습니다. 포크 일정을 잡기 전에 여러 클라이언트와 외부의 검증이 필요하며 eth_getProof 생존 확인은 포크 전 상태에 대해 다시 돌려야 합니다(SHOULD). 메인넷 집합은 닫혀 있습니다. 논스는 그 계정이 서명한 거래, 그 계정이 서명한 EIP-7702 승인, 그 주소에 떨어지는 컨트랙트 생성으로만 바뀝니다. 대상 주소는 알려진 키가 없는 컨트랙트 주소라 앞의 둘은 불가능합니다. CREATE는 원래 배포자가 원래 논스에서만 도달할 수 있는데 그 논스는 이미 지났습니다. CREATE2는 CREATE로 만든 주소에 닿지 못합니다. 팩토리가 만든 2개는 팩토리 논스 0이 필요한데 EIP-161 이후 컨트랙트는 논스 1에서 시작하므로 원리상 불가능합니다. 코드가 없어 자기 파괴도 못 하고 잔액은 늘어나기만 합니다. 공급량은 바뀌지 않습니다.
다른 체인 적용과 테스트 케이스
스퓨리어스 드래곤 이후 메인넷 상태를 이어받은 체인(메인넷 섀도 포크 포함)은 메인넷 목록을 적용합니다. 제네시스부터 EIP-161이 켜진 체인은 목록이 빕니다. 현재 공개 테스트넷은 모두 여기에 해당합니다. 그 밖의 체인은 methodology.md 절차로 자기 상태에서 주소 원본과 해시를 모두 담은 목록을 만듭니다. 메인넷 목록을 재사용하면 안 되는 이유는 이렇습니다. 배포자 12개의 논스가 메인넷에서 쓴 값보다 낮은 체인에서는 키 소유자가 같은 주소를 임의의 초기화 코드로 다시 만들 수 있습니다. 작성 시점 세폴리아의 배포자 12개 모두가 그렇습니다. 제네시스 할당으로 생긴 대상 계정은 블록 재실행으로는 못 찾지만 방법론의 스냅샷 스캔으로 찾습니다. 테스트는 제네시스에 대상 형태의 계정을 넣고 체인 설정에 이름을 올려 돌립니다. mainnet-prestate-alloc.json은 메인넷 28개 계정(잔액, 논스 0, 저장소)을 제네시스 alloc 조각으로 제공합니다. 테스트 케이스는 7개입니다. 논스 올림, CREATE 되돌림(포크 블록 첫 거래), CALL 변화 없음(새 계정 가스 없음), 비대상 계정 무변경, BAL 정확성, BAL 병합(같은 블록 거래의 BalanceChange와 한 항목에 합쳐짐), 빈 목록.
상태와 포크 일정
원문 상태는 Draft(초안)입니다. 명세, 목록, 공개 자료의 세부는 아직 바뀔 수 있습니다. 코어 개발자 콜 기록을 보면 08.17 ACDT #92에서 이 EIP를 헤고타(Hegota) 목표로 삼았습니다(PFI). 08.20 ACDC #185에서는 EIP-7610을 대체하는 제안으로 헤고타 포함 제안이 접수됐습니다. 09.07 ACDT #95에서 글램스테르담(Glamsterdam)에 넣기엔 너무 늦었다는 대략적 합의로 헤고타로 미뤄졌습니다. 09.10 ACDE #245에서 글램스테르담에 뒤늦게 추가하지 않기로 확정했습니다. 보안상 급할 게 없다는 이유였습니다. 헤고타 즉시 SFI 요청은 스코핑 블록으로 미뤄 후보로만 남았습니다. 09.14 ACDT #96에서는 BAL의 충돌 처리를 이 EIP가 결론 날 때까지 정의하지 않고 남겨 두기로 했습니다. 09.24 ACDE #246에서 다른 후보들과 함께 헤고타 포함 검토(CFI)로 올라갔습니다. 10.12 ACDT #100에서는 논의만 있었고 결정은 없었습니다. 10.22 ACDE #248에서 bogota-devnet-2 범위를 EIP-8253, EIP-4758, EIP-8298, EIP-8151 네 개로 확정하고 다음 주 Nethermind, Geth, ethrex 세 클라이언트로 출시하기로 했습니다.