EIP-8188 계정·슬롯의 마지막 기록 블록 (Last-Written Block for Accounts and Slots)

Hegota 포함 제외 (DFI) · 확장성·데이터 · 원문 상태 Draft · 2026-03-10 작성

모든 계정과 스토리지 슬롯에 마지막으로 수정된 블록 번호(last_written_block)를 합의 상태로 기록합니다. 쓰기 때만 갱신되고 가스 비용은 바뀌지 않으며 클라이언트가 저장소 계층화에 쓸 신호가 됩니다.

왜 필요한가

이더리움의 상태(state), 곧 모든 계정 잔액과 컨트랙트 저장값은 계속 늘어나지만, 각 데이터가 언제 마지막으로 수정됐는지는 프로토콜 어디에도 기록되지 않습니다. 그래서 노드 소프트웨어(클라이언트)는 캐시나 스냅샷 같은 자체 추정에 의존하는데, 이 방식은 노드마다 결과가 달라 합의 신호로 쓸 수 없습니다. 마지막 수정 블록이 합의 수준에 기록되면 모든 노드가 같은 기준으로 최근에 수정된 활성 상태를 가려낼 수 있습니다. 클라이언트는 이를 바탕으로 자주 바뀌는 데이터와 오랫동안 그대로인 데이터를 서로 다른 저장 계층에 나눠 두는 최적화를 할 수 있습니다. 목표는 트라이 갱신, 상태 루트 계산, 컴팩션처럼 상태를 반복해서 다시 쓰는 작업의 비용을 줄이는 것입니다. 이 제안은 메타데이터 기록까지만 다루며 가스 비용은 바꾸지 않습니다.

무엇이 바뀌나

지금

EIP-8188 이후

누구에게 영향이 있나

심층 설명

계정과 슬롯의 인코딩 변경

계정 인코딩은 RLP([nonce, balance, storageRoot, codeHash])에서 다섯 번째 필드를 더한 RLP([nonce, balance, storageRoot, codeHash, last_written_block])로 바뀝니다. 요소가 4개뿐인 기존 형식을 디코딩할 때는 last_written_block을 0으로 간주해야 합니다(MUST). 스토리지 슬롯은 값 하나였던 RLP(value)가 리스트 RLP([value, last_written_block])로 바뀝니다. 두 형식은 RLP 접두 바이트로 구분합니다. 단일 바이트열은 접두가 0x80대, 리스트는 0xc0대이므로 충돌이 없고 기존 형식 슬롯도 last_written_block = 0으로 디코딩합니다. 포크 이전 상태는 포크 후 처음 수정될 때까지 기존 인코딩을 그대로 유지하므로 별도의 트리 전환은 필요 없습니다.

쓰기 때만 갱신되는 블록 번호

last_written_block은 상태가 수정될 때 현재 block_number로 설정되며 같은 블록 안에서 다시 써도 이미 같은 값이라 변화가 없습니다. 슬롯 규칙은 네 가지입니다. SSTORE로 0이 아닌 값이 다른 값으로 바뀌면 슬롯과 소유 계정의 필드를 모두 갱신합니다(storageRoot가 다시 계산되기 때문). 슬롯을 0으로 지우면 슬롯은 트라이에서 제거되고 계정 필드만 갱신합니다. 같은 값을 다시 쓰면 아무것도 바꾸지 않습니다. 새 슬롯은 현재 블록 번호를 담고 생성됩니다. 계정 쪽에서는 0이 아닌 금액의 잔액 이동이 보내는 쪽과 받는 쪽을 모두 갱신하고 논스 증가와 새 계정 생성도 갱신 대상입니다. SELFDESTRUCT는 EIP-6780의 잔액 이동 규칙을 따릅니다. 같은 트랜잭션에서 생성되지 않은 컨트랙트는 제거되지 않으므로 잔액이 바뀔 때 자기 자신과 수취 계정을 갱신하며 수취인이 자기 자신이면 잔액이 바뀌지 않아 갱신하지 않습니다. 같은 트랜잭션에서 생성되고 파괴된 컨트랙트는 트라이에서 제거되어 이 필드를 갖지 않습니다.

읽기는 갱신하지 않고, revert는 되돌립니다

SLOAD, BALANCE, EXTCODESIZE, EXTCODECOPY, EXTCODEHASH와 상태를 바꾸지 않는 호출 명령어 같은 순수 읽기는 last_written_block을 갱신해서는 안 됩니다(MUST NOT). 이유는 세 가지입니다. 읽기가 필드를 바꾸면 모든 오래된 읽기가 트라이 리프 재작성과 상태 루트 재계산으로 이어져 읽기에 쓰기 수준의 비용이 붙습니다. 또 안정 계층의 데이터가 읽기만으로 가변 계층으로 옮겨져 쓰기 증폭이 생기고 가변 경로를 분리한다는 목표와 어긋납니다. 마지막으로 이 필드는 상태 루트에 반영되는 합의 상태이므로, 읽기가 이를 바꾸면 STATICCALL 동안 상태가 변하지 않는다는 EIP-214의 보장이 깨집니다. 호출 프레임이 revert되면 필드는 balance, nonce, 스토리지와 똑같이 프레임 이전 값으로 복원되며 롤백된 상태 변경에 딸린 갱신을 남겨 두어서는 안 됩니다(MUST NOT).

상태 크기에 미치는 영향

필드는 계정당 5바이트, 슬롯당 6바이트를 더합니다. 블록 번호는 현재 4바이트이고 RLP가 길이 접두 1바이트(0x84)를 붙여 5바이트가 됩니다. 계정은 이미 리스트라 그대로 5바이트만 늘지만 슬롯은 단일 바이트열을 리스트로 감싸면서 리스트 접두 1바이트가 더 붙어 6바이트가 됩니다. 4바이트 필드는 2^32 - 1, 약 42억 9천만 블록까지 담습니다. 현재 12초 슬롯 기준 약 1,600년 뒤에야 넘치고 슬롯이 6초로 줄어도 약 800년 뒤입니다. 2026년 초 상태 규모로 어림하면 계정 3억 6천만 개 × 5바이트 ≈ 1.8GB, 슬롯 15억 개 × 6바이트 ≈ 9.0GB, 합계 약 10.8GB입니다. 이는 모든 상태가 포크 후 한 번 이상 쓰였다고 가정한 최악치이며 포크 후 수정되지 않는 상태는 기존 인코딩을 유지해 비용이 없습니다. 실제 영향은 클라이언트별 벤치마크로 정량화해야 한다고 원문은 적습니다.

EIP-8037 상태 생성 비용과의 연계

이 제안은 모든 상태 항목에 바이트를 더하므로 EIP-8037의 바이트당 상태 생성 비용과 맞물립니다. EIP-8037은 새 상태 1바이트마다 CPSB(cost per state byte) 가스를 물리며 그 기준값이 새 계정의 디스크 크기 STATE_BYTES_PER_NEW_ACCOUNT = 120과 새 슬롯의 STATE_BYTES_PER_STORAGE_SET = 64입니다. 생성 가스가 실제 디스크 증가를 계속 반영하려면 두 값을 각각 125와 70으로 올려야 합니다(SHOULD). 올리지 않으면 새 계정과 슬롯이 실제 인코딩 크기보다 싸게 책정됩니다. 보안 측면에서 새 쓰기 연산이 생기지는 않아 DoS 위험은 기존 쓰기 가스 규칙 안에 묶이지만 쓰기가 건드리는 데이터 양과 갱신 항목의 증인·증명 크기는 늘어납니다.

클라이언트가 이 신호로 할 수 있는 것

원문이 드는 활용처는 세 가지입니다. 첫째, 최근 쓰인 상태와 오래 쓰이지 않은 상태를 결정적으로 나눠 서로 다른 저장 계층에 둘 수 있습니다. 가변 계층은 쓰기 처리량에, 안정 계층은 압축과 읽기 처리량에 맞춰 튜닝합니다. 둘째, 블록마다 더럽혀진 트라이 경로를 다시 해시하고 저장하는 커밋 경로에서, 활성 상태를 좁은 키 공간에 모으면 공간 지역성이 좋아져 블록당 쓰기 비용이 전체 상태 크기가 아니라 활성 집합 크기에 비례하게 됩니다. LSM 트리의 컴팩션이든 B 트리의 copy-on-write 페이지 재작성이든 효과는 같습니다. 셋째, 노드 재시작 때 최근에 쓰인 항목만 골라 미리 읽어 캐시를 복구할 수 있습니다. 이 중 어느 것도 의무가 아닙니다. 블록 번호를 가공 없이 기록하는 이유는, 갱신 주기 같은 더 굵은 단위는 블록 번호에서 결정적으로 유도할 수 있지만 반대는 불가능하기 때문입니다. 덕분에 가격 정책이 정해지기 전에 메타데이터만 먼저 확정해도 되고 정책이 나중에 바뀌어도 인코딩을 다시 바꿀 필요가 없습니다.

진행 상태와 포크 결정

계정과 슬롯 인코딩이 바뀌어 트라이 해싱과 상태 루트 계산에 영향을 주므로 적용에는 하드포크가 필요합니다. 가스 비용에 차등을 두는 실제 상태 계층화는 이 필드를 사용하는 별도 제안의 몫으로 남겨 두었습니다. 이 제안은 Draft 상태라 명세가 아직 바뀔 수 있습니다. 포크 반영 쪽은 09.10 ACDE #245 콜에서 EIP-8358, EIP-8115와 함께 Hegota 대상에서 제외됐습니다. 세 건 모두 낮은 평점을 받았습니다.

EIP 원문 · ← EIPs