EIP-7928 블록 수준 접근 목록 (Block-Level Access Lists)
콜 결정 없음 · 확장성·데이터 · 원문 상태 Last Call · 2025-03-31 작성
블록이 접근한 모든 계정과 스토리지 슬롯, 실행 후 값을 담은 블록 접근 목록(BAL)을 의무화하고 그 해시를 블록 헤더에 넣어 디스크 읽기와 거래 검증을 병렬로 처리할 수 있게 합니다.
왜 필요한가
지금 이더리움 노드는 블록을 받으면 거래를 하나씩 순서대로 실행합니다. 어떤 거래가 어떤 계정과 저장 공간을 건드릴지 실행해 보기 전에는 알 수 없기 때문입니다. 거래마다 접근할 주소를 미리 적는 EIP-2930 접근 목록이 있지만 선택 사항이라 강제되지 않습니다. 이 제안은 접근 목록을 블록 단위로 의무화합니다. 블록이 접근한 모든 주소와 저장 위치, 실행 후 값이 목록으로 함께 오면 노드는 필요한 데이터를 미리 병렬로 읽고 서로 겹치지 않는 거래를 동시에 실행하고 실행 후 상태 루트 계산도 병렬화할 수 있습니다. 거래를 직접 실행하지 않고 목록만으로 상태를 갱신하는 것도 가능해집니다. 원문은 실행 시간이 '병렬 IO + 병렬 EVM' 수준으로 줄어든다고 설명합니다.
무엇이 바뀌나
지금
- 어떤 거래가 어떤 상태를 건드릴지 실행 전에는 알 수 없어 거래를 순서대로 실행합니다
- EIP-2930 접근 목록은 선택 사항이라 병렬화의 근거로 쓸 수 없습니다
- 상태를 복원하거나 동기화하려면 거래를 직접 다시 실행해야 합니다
EIP-7928 이후
- 블록마다 접근한 주소·슬롯과 실행 후 값이 목록으로 함께 제공되고 헤더의 해시로 검증됩니다
- 필요한 데이터를 미리 읽고 겹치지 않는 거래를 동시에 실행할 수 있습니다
- 거래를 실행하지 않고 목록의 상태 디프만으로 상태를 갱신할 수 있습니다
누구에게 영향이 있나
- 클라이언트 팀: 블록 헤더 필드 추가, BAL 생성·검증, 엔진 API 신규 메서드(engine_newPayloadV5, engine_getPayloadV6 등)를 구현해야 합니다. 하드포크가 필요한 변경입니다.
- 검증자·노드 운영자: 블록 처리에 병렬 IO와 병렬 실행을 쓸 수 있게 되고 그 대가로 평균 약 92.1KiB(압축)의 BAL 전파·보관 부담이 생깁니다.
- 블록 빌더·제안자: 블록을 만들 때 정확한 BAL을 함께 만들어야 하며 빠지거나 가짜인 항목이 있으면 블록 전체가 무효가 됩니다.
- 일반 사용자: 직접 할 일은 없지만 노드의 블록 검증이 빨라지는 혜택을 간접적으로 받습니다.
심층 설명
헤더에는 해시만, BAL은 블록 밖에
블록 헤더에 block_access_list_hash 필드를 추가합니다. 값은 RLP로 인코딩한 BAL의 Keccak-256 해시이고 빈 목록이면 keccak256(rlp.encode([])) = 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347입니다. BAL 자체는 블록 본문에 들어가지 않습니다. 실행 레이어가 따로 저장해 엔진 API의 ExecutionPayload 필드로 전달하고 합의 레이어는 SSZ ExecutionPayload 컨테이너의 필드로 보관합니다. 장기 보관 시에는 전체 바이트를 지우고 해당 필드의 SSZ hash_tree_root만 남겨 머클 커밋을 유지할 수 있습니다.
RLP 구조, 블록 접근 인덱스, 정렬 규칙
BAL은 주소 → 필드 → 블록 접근 인덱스 → 변경 순서를 따르는 RLP 리스트입니다. 계정마다 AccountChanges = [address, storage_changes, storage_reads, balance_changes, nonce_changes, code_changes]를 기록합니다. BlockAccessIndex는 사전 실행 시스템 콜이 0, 거래가 블록 순서대로 1..n, 출금을 포함한 사후 실행이 n+1입니다. 정수 필드(StorageKey, StorageValue, Balance, Nonce, BlockAccessIndex)는 앞자리 0이 없는 최소 빅엔디언으로 인코딩해야 하고 0은 빈 바이트열(0x80)로 인코딩합니다. 해시가 이 바이트에 커밋되므로 같은 값이라도 32바이트 패딩 같은 다른 인코딩을 쓰면 블록이 무효가 됩니다. 정렬도 강제됩니다. 계정은 주소 사전순, 슬롯과 읽기 키는 키 오름차순, 각 변경 목록은 인덱스 오름차순이고 각 주소는 목록에 정확히 한 번만 나타나야 하며 같은 슬롯이 storage_changes와 storage_reads에 동시에 올 수 없습니다.
무엇이 목록에 들어가는가
상태가 바뀐 주소뿐 아니라 접근만 된 주소도 빈 변경 목록과 함께 들어갑니다. BALANCE·EXTCODESIZE·EXTCODECOPY·EXTCODEHASH의 대상, CALL·CALLCODE·DELEGATECALL·STATICCALL의 대상(되돌려져도 포함), 거래 발신자와 수신자(0원 송금 포함), SELFDESTRUCT 수혜자, 프리컴파일, 출금 수신자, 접근된 시스템 컨트랙트가 모두 대상입니다. CREATE/CREATE2의 대상 주소는 EVM이 계산된 주소에 실제로 접근했을 때만 포함하고 주소 계산만으로는 포함하지 않으며 배포 거래(to가 빈 거래)의 배포 주소는 항상 포함합니다. 시스템 호출자 주소 SYSTEM_ADDRESS(0xfffffffffffffffffffffffffffffffffffffffe)는 자체 상태 접근이 없으면 넣지 않습니다. EIP-2930 거래 접근 목록의 항목은 자동으로 들어가지 않고 실제로 접근된 것만 기록합니다.
크기 제한: 가스 한도에 연동
목록 크기는 고정 상한 대신 블록 가스 한도로 묶습니다. bal_items <= block_gas_limit // ITEM_COST이고 ITEM_COST = 2000입니다. bal_items는 전체 스토리지 키 수와 고유 주소 수의 합입니다. EIP-7981 적용 시 항목 하나를 가장 싸게 추가하는 방법이 콜드 SLOAD(COLD_SLOAD_COST = 2100)이므로, ITEM_COST를 일부러 그보다 낮게 잡아 약 block_gas_limit / 42000개의 여유분을 만들었습니다. 이 여유분이 블록 가스를 소비하지 않는 시스템 컨트랙트 실행(EIP-2935, EIP-4788, EIP-7002, EIP-7251)과 출금 수신자(EIP-4895) 항목을 흡수합니다.
가스 검증 2단계와 포함 판정
상태에 접근하는 명령어의 가스 검증을 두 단계로 나눕니다. 상태를 보지 않고 알 수 있는 비용(기본 비용, 메모리 확장, warm/cold 접근 비용)은 사전 검증, 계정 존재 여부나 EIP-7702 위임 해석처럼 상태가 필요한 비용은 사후 검증입니다. 사전 검증에 실패하면 대상에 접근하지 않은 것이므로 BAL에 넣으면 안 됩니다. 통과하면 이후 사후 비용에서 실패해도 BAL에 남습니다. SSTORE는 가스 계산을 위해 현재 값을 암묵적으로 읽는데, 그 전에 슬롯의 access_cost와 GAS_CALL_STIPEND 센트리(gas_left > GAS_CALL_STIPEND)를 모두 통과해야 하며 실패하면 그 슬롯은 storage_reads에도 storage_changes에도 넣지 않습니다. EIP-7702 위임이 걸린 대상을 부르면 원래 대상과 위임된 주소가 모두 포함되지만 위임 주소 자체의 access_cost 가스 검사에 실패하면 위임 주소는 넣지 않습니다.
변경 유형별 기록 규칙
스토리지는 값이 실제로 바뀐 쓰기(0으로 만들기 포함)만 storage_changes에 넣습니다. 읽기만 한 슬롯과 같은 값을 다시 쓴 no-op 쓰기는 storage_reads에 넣습니다. 쓰기 여부는 현재 블록 접근 인덱스 직전까지의 누적 상태와 비교해 판정합니다. 잔액은 거래 후 최종 잔액(uint256)을 기록하되, 거래 안에서 바뀌었다가 거래 전과 같아졌으면 balance_changes에 넣지 않고 주소만 포함합니다. 0원 블록 보상은 그 자체로 잔액 변경이나 주소 포함을 만들지 않습니다. 논스는 EOA 발신자, CREATE/CREATE2에 성공한 컨트랙트, 배포된 컨트랙트, EIP-7702 권한자의 거래 후 값을 기록하고 코드는 배포·수정된 컨트랙트의 거래 후 런타임 바이트코드와 성공한 위임 지시자를 기록합니다. 빠진 항목이든 가짜 항목이든 하나라도 있으면 블록이 무효입니다.
엔진 API 확장과 보관 기간
ExecutionPayloadV3를 확장한 ExecutionPayloadV4에 blockAccessList 필드가 붙습니다. engine_newPayloadV5는 받은 BAL이 형식에 어긋나거나 실행 결과와 다르면 INVALID를 반환하고 engine_getPayloadV6는 실행 중 수집한 접근으로 blockAccessList를 채워 페이로드를 만듭니다. 과거 BAL은 engine_getPayloadBodiesByHashV2와 engine_getPayloadBodiesByRangeV2로 조회하며 암스테르담 이전 블록이거나 데이터가 지워졌으면 null입니다. 실행 레이어는 블록 본문 보관 기간인 HISTORY_PRUNE_EPOCHS(14299 에폭) 이상 BAL을 이 메서드들로 제공해야 합니다.
크기·보안 분석, 현재 상태와 콜 결정
2026년 10월 메인넷 1000개 블록 분석 기준 평균 BAL 크기는 압축 후 약 92.1KiB입니다(60m 가스 한도). 거래의 60~80%는 서로 겹치지 않는 슬롯에 접근해 바로 병렬화되고 나머지 20~40%도 거래 후 상태 디프로 병렬화할 수 있습니다. 보안상으로는 storage_reads가 특정 거래 인덱스에 매이지 않아 모든 거래를 실행해야 검증되므로, 악의적 제안자가 접근되지 않을 가짜 읽기를 선언해 불필요한 I/O를 유발할 수 있습니다. bal_items 제한이 그 개수를 묶고 클라이언트는 실행 중 감지 장치를 추가로 둘 수 있으나 유효한 블록을 거부해서는 안 됩니다. 원문 상태는 Last Call(마감 2026-11-01)입니다. 콜 기록으로는 07.09 ACDC #182에서 EIP-7928 변경을 데브넷8로 미루지 않고 데브넷7에 포함하기로 확정했습니다. 09.21 ACDT #97에서는 글램스테르담 CL 블록 서빙 윈도우와 BAL 보관 기간을 정하지 않고 비동기 논의와 ACDE로 넘겼습니다(33,024 에폭 유지, 약 14,299로 축소, 8,192로 축소 세 안이 제시됐고 테스트넷 출시는 막지 않습니다).