페이로드 청킹
tl;dr: EL 블록 ( =payload )을 고정된 가스 예산(예: 2**24 = 16.77M
)을 가진 여러 개의 미니 블록(" 청크 ")으로 분할합니다. 각 청크는 상태 없이 실행하는 데 필요한 사전 상태를 담고 사후 상태 diff에 커밋합니다. 청크는 순서대로 정렬되지만 완전히 독립적으로 병렬로 실행될 수 있습니다 . CL은 청크 헤더 집합에 커밋하고, 사이드카는 본문과 포함 증명을 담고 있습니다.
검증은 더욱 지속적인 흐름으로 변합니다.
동기 부여
현재 블록은 크고 단일한 객체이며, 앞으로 더욱 커질 것입니다. 검증을 위해서는 실행을 시작하기 전에 전체 블록 수신해야 합니다. 이로 인해 블록 전파 및 실행에 지연 병목 현상이 발생합니다.
P2P 네트워크를 통해 블록 수신한 후, 트랜잭션은 순차적으로 실행됩니다. 다운로드 중에는 유효성 검사를 시작하거나 실행을 병렬화할 수 없습니다.
P2P 계층의 메시지는 일반적으로 Snappy를 사용하여 압축됩니다. 이더리움에서 사용되는 Snappy의 블록 형식은 스트리밍할 수 없습니다. 따라서 압축하기 전에 블록 을 청크로 분할해야 합니다.
이더리움 개선 제안(EIP)-7928: 블록 수준 액세스 목록을 사용하면 상황이 개선되지만, 블록 검증을 시작하기 전에 다운로드가 완료될 때까지 기다려야 합니다. 코어가 4개인 경우 다음과 같은 간트 차트가 나타납니다.
대신 블록을 청크로 스트리밍 할 수 있습니다.
- 각 청크에는 ≤
2**24
가스의 거래가 포함됩니다.- 청크 크기를 기하급수적으로(
2**22
,2**23
, …,2**25
) 가스 단위로 증가시킬 수도 있습니다. 이렇게 하면 청크의 지연 시간이 다양해져 병렬 처리 성능이 향상되지만, 복잡성을 감수할 만큼의 가치가 있을지는 확신할 수 없습니다.
- 청크 크기를 기하급수적으로(
- 트랜잭션은 순서대로 유지됩니다 . 청크는 인덱싱되고 정렬되지만 서로 독립적 이므로 병렬로 검증할 수 있습니다. 하지만 청크 0의 사후 상태는 청크 1의 사전 상태와 같습니다.
- (선택 사항) 각 청크는 상태 없이 실행하는 데 필요한 상태를 담고 있습니다.
이를 통해 검증이 " 전체 블록 다운로드한 다음 처리"에서 "나머지를 받는 동안 처리"로 전환됩니다.
실행 계층 변경
청킹을 지원하기 위해 EL 블록 형식을 확장합니다.
class ELHeader :parent_hash: Hash32fee_recipient: Addressblock_number: uint64gas_limit: uint64timestamp: uint64extra_data: ByteList[MAX_EXTRA_DATA_BYTES]prev_randao: Bytes32base_fee_per_gas: uint256parent_beacon_block_root: Rootblob_gas_used: uint64excess_blob_gas: uint64transactions_root: Rootstate_root: Rootreceipts_root: Rootlogs_bloom: Bloomgas_used: Uintwithdrawals_root: Rootblock_access_list_hash: Bytes32 # New fields chunk_count: int # >= 0
EL 헤더의 개별 청크에 대한 약속은 없습니다 . 청크 수만 추가합니다. 실행 출력( state_root
, logs_bloom
, receipts_root
, gas_used
)은 마지막 청크의 값(state 루트 및 withdrawals 루트에 적용)과 동일하거나, 청크 값을 집계한 후의 루트(거래, 영수증, 로그, 사용된 가스 및 블록 액세스 목록에 적용)여야 합니다.
실행 청크
청크는 결코 체인에 올라가지 않습니다. 오직 루트만이 커밋됩니다.
청크에는 일반적으로 EL 블록 본문에서 예상되는 필드가 포함됩니다. 거래는 청크당 2**24
가스로 제한되는 청크로 나뉩니다. 출금은 마지막 청크에만 포함되어야 합니다. 블록 수준 액세스 목록을 반영하여 청크는 자체 청크 액세스 목록을 가지며, 청크에 사전 상태 값을 추가하여 무상태(stateless)를 해제할 수 있습니다.
class Chunk :header: ChunkHeadertransactions: List [Tx]withdrawals: List [Withdrawal] # only in chunk at index -1 chunk_access_list: List [ChunkAccessList]pre_state_values: List [(Key, Value)] # optional
각 청크에는 청크 인덱스를 포함하는 헤더가 있습니다. 트랜잭션은 chunk.header.index
와 청크 내 인덱스 순으로 정렬됩니다. 각 청크의 실행 결과에 대한 커밋은 헤더에 포함됩니다.
class ChunkHeader :index: int txs_root: Rootpost_state_root: Rootreceipts_root: Rootlogs_bloom: Bloomgas_used: uint64withdrawals_root: Rootchunk_access_list_root: Rootpre_state_values_root: Root # optional
제안자가 블록을 너무 많은 청크로 분할하는 것을 방지하기 위해 프로토콜은 청크가 최소한 절반 이상 채워져 있어야 한다는 것을 강제할 수 있습니다( \geq\frac{chunk\_gas \ _limit } {2} ≥ c h u n k _ g a s _ limit 2 ) 또는 chunk.header.index == len(beaconBlock.chunk_roots)
( = 해당 블록 의 마지막 청크 ).
합의 계층 변경
비콘 블록은 새로운 필드로 청크를 추적합니다.
class BeaconBlockBody :...chunk_roots: List [ChunkRoot, MAX_CHUNKS_PER_BLOCK] # SSZ roots of chunks class ExecutionPayloadHeader :...chunk_count: int
CL은 EL 헤더와 청크를 포함하는 새로운 ChunkBundle
컨테이너를 통해 EL로부터 실행 청크를 수신합니다( blob과 유사 ).
CL은 SSZ의 hash_tree_root
사용하여 청크 루트를 계산하고 이를 비콘 블록 본문에 넣습니다.
사이드카 디자인
덩어리는 사이드카 로 운반됩니다.
class ExecutionChunkSidecar :index: uint64 # chunk index chunk: ByteList[MAX_CHUNK_SIZE] # Opaque chunk data signed_block_header: SignedBeaconBlockHeaderchunk_root_inclusion_proof: Vector[Bytes32, PROOF_DEPTH]
합의 계층은 모든 청크가 사용 가능하고 chunk_roots
에 대한 Merkle 증명을 통해 비콘 블록 본문에 적절하게 연결되었는지 확인합니다( blobs와 유사 ).
네트워킹
제안자는 일반 beacon_block
주제에서 커밋( chunk_count
, chunk_headers_root
)이 있는 가벼운 비콘 블록 만 소문으로 퍼뜨리는 반면, 무거운 실행 데이터는 ExecutionChunkSidecar
로 X개의 병렬 서브넷 ( beacon_chunk_sidecar_{0..X}
)에 걸쳐 별도로 스트리밍되고 (block_root, index)
로 중복이 제거됩니다.
처음에는 모든 노드가 모든 서브넷을 구독하고 모든 청크를 보관해야 합니다. 이렇게 해도 대역폭/저장 공간 요구 사항이 아직 줄어들지는 않지만, 병렬화의 즉각적인 이점을 얻을 수 있습니다. 기본 메커니즘이 검증되거나 zk-proving이 가능해지면 향후 업그레이드를 통해 부분 보관 기능을 추가할 수 있습니다.
포크(Fork) 선택
포크(Fork) 선택은 블록 유효한 것으로 간주되기 전에 모든 사이드카가 사용 가능하고 성공적으로 검증되어야 합니다. chunk_roots
있는 비콘 블록 블록 전파되지만, 모든 청크가 수신되고 루트에 대해 포함 검증이 완료된 후에야 포크 선택 대상이 됩니다. 비콘 블록 여전히 모든 필수 커밋( 부모 블록 및 실행 출력에 대한 커밋 )이 포함된 EL 헤더를 포함합니다. 이 설계에서는 EL에서 블록 본문 으로 알려진 부분이 비어 있습니다.
이익
- 스트리밍 검증 : 블록 의 다른 부분이 아직 다운로드 중이거나 디스크에서 로딩 중인 동안에도 실행을 시작할 수 있습니다. 청크는 독립적이거나(사전 상태가 제공된 경우), 청크 접근 목록( 청크 수준 상태 차이 포함 )과 블록 이전 상태에 의존합니다. 여러 CPU/코어가 동시에 청크를 검증할 수 있으며, 슬롯 시작 부분의 버스트 대신 슬롯에 대역폭 사용량을 분산합니다.
- 간소화된 증명 : ZK Prover는 청크의 독립성을 활용하여 여러 청크를 동시에 증명하는 작업을 병렬화할 수 있습니다.
- 상태 비저장 친화성 : 단일 청크는 블록 보다 작으므로 로컬 상태 접근이 필요 없도록 사전 상태 값을 추가하는 것을 고려할 수 있습니다. 실용적인 절충안은 청크
0
에만 사전 상태 값을 포함하여 노드가 다른 청크에 필요한 상태를 디스크에서 캐시로 로드하는 동안 최소 하나의 청크가 항상 실행될 수 있도록 하는 것입니다. - 향후 확장성 : 청크에 대한 zk-proofs를 통합하거나 분할 실행을 선택하는 명확한 경로.
디자인 공간
청크 크기
2**24
가스(~16.7M)가 자연스러운 덩어리 크기로 나타났습니다.
- 최대 거래 크기 : Fusaka(이더리움 개선 제안(EIP)-7825)에 따르면 최대 거래 크기는
2**24
입니다. - 현재 블록: 45M 가스 블록이 자연스럽게 ~3개의 청크로 분할되어 즉각적인 병렬성을 제공합니다.
- 미래 블록: 확장성이 뛰어납니다. 100M 가스 블록에는 ~6개의 청크가 있습니다.
검증자
- 실행 엔진은 블록 내부적으로 청크로 분할하고(CL에서는 알 수 없음)
ExecutionChunkBundle
을 통해 CL에 전달합니다. - 제안자는 각 청크를 포함 증명을 포함한 사이드카로 감쌉니다. 또한 제안자는 각 청크의 해시 트리 루트를 계산하여 비콘 블록 바디에 넣습니다.
- 게시는 모든 서브넷에서 병렬로 수행됩니다.
- 증명자는 모든 청크를 기다리고 투표하기 전에 이를 검증합니다.
건축업자
제안자는 청크 생성이 완료되면 청크를 게시할 수 있으며, 검증자는 비콘 블록 수신하기 전에도 청크의 검증을 시작할 수 있습니다. 청크에는 서명된 비콘 블록 헤더와 이에 대한 포함 증명이 포함되어 있으므로, 청크가 수신되는 즉시 청크의 출처( =proposer )를 신뢰하여 검증( =execute )할 수 있습니다.
미해결 질문 및 향후 작업
점진적인 덩어리 크기?
청크 크기를 기하급수적으로 증가시키는 아이디어( 2**22
, 2**23
, …, 2**25
)는 유익해 보이지만 복잡성을 증가시킵니다. 첫 번째 청크는 즉시 실행을 위해 사전 상태 값을 사용하여 더 작은 크기(5M 가스)로 만들 수 있지만, 이후 청크는 더 큰 크기를 가질 수 있습니다. 이는 여전히 실험이 필요한 영역입니다.
부분 양육권 경로
초기 구현에는 전체 보관이 필요하지만 아키텍처는 자연스럽게 부분 보관을 지원합니다.
- 노드는 X개 중 Y개의 서브넷만 보관할 수 있습니다.
- 재구성 메커니즘(DAS와 유사)을 사용하면 누락된 청크를 복구할 수 있습니다.
ePBS 및 지연 실행과 호환
제안된 설계는 언뜻 보기에 이더리움 개선 제안(EIP)-7732 와 이더리움 개선 제안(EIP)-7886 모두와 호환되는 것처럼 보입니다. ePBS에서는 청크 루트가 ExecutionPayloadEnvelope
로 이동하고, 청크 루트 위에 추가 루트를 ExecutionPayloadHeader
로 배치할 것입니다. PTC는 단일 EL 페이로드의 사용 가능 여부뿐만 아니라 모든 청크의 사용 가능 여부도 확인해야 합니다. 이는 블롭(blob)과 크게 다르지 않습니다.
블록 청킹과 독립적인 검증의 장점은 더 높은 가스 한도로 확장이 가능하며, 노드 대역폭 소비의 급증을 줄이는 데 도움이 될 수 있습니다.