EIP-8189 snap/2: BAL 기반 상태 힐링 (snap/2 - BAL-Based State Healing)

콜 결정 없음 · 네트워크·노드 · 원문 상태 Last Call · 2026-03-06 작성

스냅 동기화 프로토콜을 snap/2로 올려, 머클 트라이 노드를 하나씩 받아 상태를 맞추던 힐링 단계를 블록 접근 목록(BAL)을 내려받아 적용하는 방식으로 대체합니다.

왜 필요한가

새 노드가 네트워크를 따라잡는 스냅 동기화(snap sync)는 특정 시점의 피벗 블록 기준으로 상태를 통째로 내려받는 방식입니다. 그런데 다운로드하는 동안에도 체인은 계속 전진하므로, 받아 둔 상태와 최신 상태 사이에 어긋남이 생깁니다. 지금은 이 틈을 메우는 힐링(healing) 단계에서 부족한 머클 트라이 노드를 GetTrieNodes 메시지로 하나씩 요청합니다. 어떤 노드가 더 필요한지는 받아 봐야 알 수 있어서 요청과 응답이 여러 번 오가야 합니다. EIP-7928이 도입하는 블록 접근 목록(BAL)은 블록마다 일어난 모든 상태 변경을 기록하고 그 해시를 블록 헤더에 담습니다. 그래서 동기화하는 노드는 다운로드하는 사이 지나간 블록들의 BAL만 받아 순서대로 적용하면 됩니다. 따라잡을 블록 집합을 미리 알 수 있어 반복 탐색이 사라지고, 각 BAL은 헤더에 적힌 해시로 검증할 수 있습니다. 이 EIP는 그 교환 통로를 snap 프로토콜 버전 2로 정의합니다.

무엇이 바뀌나

지금

EIP-8189 이후

누구에게 영향이 있나

심층 설명

snap/2 버전과 메시지 구성 변경

프로토콜 버전이 snap/1에서 snap/2로 올라가고 피어는 RLPx 능력(capability) 핸드셰이크에서 버전을 협상합니다. 트라이 노드 힐링에 쓰이던 GetTrieNodes(0x06)와 TrieNodes(0x07)가 제거되고 GetBlockAccessLists(0x08)와 BlockAccessLists(0x09)가 추가됩니다. 0x00부터 0x05까지의 메시지(GetAccountRange, AccountRange, GetStorageRanges, StorageRanges, GetByteCodes, ByteCodes)는 snap/1과 동일합니다. BAL 기반 힐링이 들어오면 상태 보정용 트라이 노드 요청이 필요 없어지므로, 힐링 메커니즘을 두 개 유지하지 않기 위해 기존 메시지를 제거합니다.

GetBlockAccessLists(0x08) 요청 규칙

요청 형식은 [request-id: P, [blockhash₁: B_32, blockhash₂: B_32, ...], bytes: P]입니다. bytes는 한 응답으로 받겠다는 BAL 데이터의 누적 최대 크기이고, 요청당 해시 개수 제한은 구현이 정합니다. 노드는 요청에 반드시 응답해야 하며 요청된 블록의 BAL이 없으면 그 위치에 RLP 빈 문자열(0x80)을 반환해야 합니다. bytes 소프트 리밋이나 QoS 제한 때문에 요청보다 적게 반환할 수 있지만 이때는 뒤쪽부터 잘라내고 요청 순서는 유지해야 합니다. BAL은 EIP-7928 활성화 이후 블록에 대해, 거기서 정한 보존 기간 안에서만 제공됩니다. 또 고아(비정식) 블록의 BAL도 보존 기간 안에서는 유지하는 것이 좋습니다. 요청이 블록 해시 기준이라 고아 BAL도 같은 방식으로 제공할 수 있고 이것이 있어야 피벗을 지나는 리오그에서 동기화를 처음부터 다시 하지 않고 복구할 수 있습니다.

BlockAccessLists(0x09) 응답과 검증

응답 형식은 [request-id: P, [block-access-list₁, block-access-list₂, ...]]이고 각 원소는 요청한 블록 해시와 같은 위치로 대응합니다. BAL이 없는 블록 자리에는 RLP 빈 문자열(0x80)이 들어갑니다. 응답 노드는 요청에 담긴 bytes 제한을 지키는 것이 좋고 별도 제한이 없을 때 권장 소프트 리밋은 2 MiB입니다. 받은 BAL은 keccak256(rlp.encode(bal))을 계산해 해당 블록 헤더의 block-access-list-hash와 비교하는 방식으로 반드시 검증해야 합니다. BAL 인코딩 형식은 EIP-7928을 따릅니다.

동기화 알고리즘: 힐링 대신 BAL 캐치업

헤더를 내려받아 체인 헤드를 파악한 뒤, 헤드보다 충분히 뒤(예: HEAD-64)의 피벗 블록 P를 고릅니다. P가 리오그될 가능성을 줄이면서도 서빙 피어가 그 시점 상태를 아직 메모리에 들고 있을 만큼 최근이어야 하기 때문입니다. GetAccountRange, GetStorageRanges, GetByteCodes로 P 시점 상태를 대량 다운로드하고 그 사이 체인이 전진해 피벗이 P+K(K는 보통 64)로 바뀌면 P+1부터 P+K까지의 BAL을 받아 각각 헤더 해시로 검증한 뒤 상태 디프를 적용하고 P+K를 새 동기화 목표로 삼습니다. 대량 다운로드 도중 피어들이 P 시점 상태를 더는 들고 있지 않게 된 경우에도 같은 방식으로 새 피벗 P'까지의 BAL을 적용하고 P'에서 다운로드를 이어 갑니다. 피벗이 또 전진하면 새로 생긴 블록에 반복하고 마지막에 상태 루트를 다시 계산해 최신 헤더와 대조합니다.

피벗을 지나는 리오그의 복구 경로

체인이 피벗 블록 P보다 앞에서 리오그되면, 옛 체인과 새 정식 체인의 공통 조상을 W라 할 때 이렇게 복구합니다. 옛 포크의 W+1부터 P까지 BAL을 모아, 옛 포크에서는 바뀌었지만 새 포크에서는 바뀌지 않은 상태 항목을 찾아 로컬에서 지우고 그 항목들을 GetAccountRange와 GetStorageRanges로 다시 받습니다. 그다음 새 정식 체인의 W+1부터 BAL을 적용하고 새 피벗으로 동기화를 이어 갑니다. 이때 필요한 고아 블록의 BAL을 구할 수 없으면 받아 둔 상태를 버리고 동기화를 처음부터 다시 시작해야 합니다. 피어에게 고아 BAL 보존을 권고하는 것도 이 복구 경로 때문입니다.

eth가 아니라 snap에 넣은 이유와 호환성

BAL 교환을 eth 프로토콜(예: eth/71)에 넣지 않고 snap을 따로 올린 것은 기존 snap 설계 철학을 따른 결정입니다. 동기화 프리미티브는 위성 프로토콜에 두고 eth는 장기적으로 체인 유지 전용 프로토콜로 남긴다는 방향으로, GetByteCodes가 eth/65에서 snap으로 복제된 것과 같은 이유입니다. snap은 선택적이고 독립적으로 버전 관리되므로 동기화 기능이 eth에 묶이지 않습니다. 호환성 측면에서는 새 버전 롤아웃이 필요하고 구형 클라이언트는 snap/1을 계속 씁니다. snap/2는 block-access-list-hash 헤더 필드가 있는 암스테르담 이후 블록에만 의미가 있습니다. 두 버전을 모두 지원하는 노드라도 상태 동기화에는 한쪽만 쓰는 것이 좋고 동기화를 마친 뒤에는 두 프로토콜의 요청을 모두 서빙할 수 있습니다.

보안 고려와 진행 상태

작은 요청 하나가 훨씬 큰 응답을 유발할 수 있는 증폭(amplification) 위험이 있어, 구현은 요청 빈도를 제한하고 2 MiB 소프트 응답 리밋을 지키는 것이 좋습니다. 데이터가 있을 블록에 빈 항목을 돌려주는 피어는 악의적일 수도, 정당하게 데이터를 정리했을 수도 있으므로 피어 신뢰도를 추적해 신뢰도 낮은 피어의 우선순위를 낮추는 것이 좋습니다. BAL은 반드시 블록 순서대로, 해시 검증을 거친 뒤 적용해야 하며 순서가 틀리거나 다른 포크의 BAL을 적용하면 최종 상태 루트 검증에서 잘못된 루트로 드러납니다. 이 EIP의 상태는 Last Call이고 마감(last-call-deadline)은 2026-11-01입니다. 코어 개발자 콜에서는 08.03 ACDT #90과 08.10 ACDT #91에서 논의되었고 포크 포함 등 결정은 아직 없습니다.

EIP 원문 · ← EIPs