EIP-2124 체인 호환성 확인용 포크 식별자 (Fork identifier for chain compatibility checks)

최종 확정 · 네트워크·노드 · 원문 상태 Final · 2019-05-03 작성

노드가 제네시스와 지나온 포크 이력을 4바이트 체크섬으로 요약한 식별자(forkid)를 주고받아 연결하기 전에 상대가 같은 체인의 호환 노드인지 가려낼 수 있게 합니다.

왜 필요한가

이더리움에는 메인넷 외에도 수많은 테스트넷과 프라이빗 네트워크가 있지만 노드가 서로를 찾는 디스커버리(discovery) 단계에서는 이를 구분하지 못합니다. 상대가 같은 체인인지 알려면 TCP 연결을 맺고 암호화 계층(RLPx)을 씌운 뒤 eth 핸드셰이크까지 마쳐야 하는데, 이 비용을 치르고도 이더리움과 이더리움 클래식조차 정확히 가려내지 못합니다. 노드 수가 적은 작은 네트워크일수록 좋은 피어를 찾기까지 시행착오가 더 많아집니다. 또 하드포크 뒤에도 소프트웨어를 업데이트하지 않은 노드가 남아 정상 노드에 붙어 있으면서 업그레이드된 블록은 받지 않아 귀중한 피어 자리와 대역폭을 낭비합니다. 테스트넷에서는 이런 노드가 몇 달씩 남기도 합니다. 이 EIP는 체인의 현재 상태를 작고 정확하게 요약하는 식별자와 그 검증 규칙을 정의해 연결하기 전에 쓸모없는 상대를 걸러낼 수 있게 합니다.

무엇이 바뀌나

지금

EIP-2124 이후

누구에게 영향이 있나

심층 설명

FORK_HASH 계산 규칙

FORK_HASH는 제네시스 해시와 이미 지나간 포크 블록 번호들의 IEEE CRC32 체크섬([4]byte)입니다. 포크 블록 번호는 오름차순으로 넣고 같은 블록에 여러 포크가 함께 적용되면 그 블록 번호는 한 번만 체크섬에 넣습니다. 블록 번호는 uint64 정수를 빅 엔디언으로 인코딩해 넣습니다. 체인이 제네시스부터 프런티어(Frontier)가 아닌 규칙으로 시작하도록 설정된 경우, 그것은 포크로 치지 않습니다. 원문의 메인넷 예시는 다음과 같습니다. 제네시스는 CRC32(<genesis-hash>) = 0xfc64ec04, 홈스테드는 CRC32(<genesis-hash> || uint64(1150000)) = 0x97c2c34c, DAO 포크는 여기에 uint64(1920000)을 이어 붙여 0x91d1f948입니다.

FORK_NEXT와 forkid 인코딩

FORK_NEXT는 다음에 올 포크의 블록 번호(uint64)이고, 알려진 다음 포크가 없으면 0입니다. 포크 식별자는 RLP([FORK_HASH, FORK_NEXT])로 정의합니다. 이 forkid는 단순 비교가 아니라 교차 검증(cross validation)으로 상대 체인의 호환성을 판정합니다. 포크 상태와 무관하게 양쪽이 같은 결론에 도달해야 하는데, 그래야 한쪽만 거절하고 다른 쪽은 계속 재연결을 시도하는 상황을 피할 수 있습니다. 원문 테스트 케이스는 FORK_HASH가 4바이트 바이너리, FORK_NEXT가 8바이트 수량이라는 점을 반영한 RLP 인코딩 결과를 함께 제시합니다. 예를 들어 {Hash: 0xdeadbeef, Next: 0xBADDCAFE}는 0xca84deadbeef84baddcafe로 인코딩됩니다.

검증 규칙 4가지

규칙 1: 양쪽 FORK_HASH가 같으면 내 헤드와 상대의 FORK_NEXT를 비교합니다. 상대가 알리고 아직 지나지 않았다는 포크가 내 쪽에서는 이미 지났으면 체인이 비호환이므로 연결을 끊습니다(1a). 상대가 알린 포크가 없거나 내 쪽에서도 아직 지나지 않았으면 연결합니다(1b). 규칙 2: 상대의 FORK_HASH가 내 과거 포크 이력의 부분집합이고 상대의 FORK_NEXT가 내 쪽에서 그다음에 오는 포크 블록 번호와 일치하면 연결합니다. 상대가 동기화 중인 경우입니다. 규칙 3: 상대의 FORK_HASH가 내 이력의 상위집합이고 내가 아는 미래 포크들로 채워 만들 수 있으면 연결합니다. 내가 동기화 중인 경우입니다. 규칙 4: 그 외에는 모두 거절합니다. 한 가지 비대칭이 있습니다. 상대의 미래 포크 목록 전체가 아니라 다음 포크 하나만 알 수 있어서, 동기화 중인 상대의 소프트웨어가 낡았다는 사실은 상대가 따라잡기 전까지 알 수 없습니다. 원문은 어차피 상대가 먼저 끊을 것이고 이는 동기화 중의 일시적 현상일 뿐이라 허용할 만하다고 설명합니다.

왜 4바이트 CRC32인가

eth 프로토콜 자체는 큰 데이터를 보낼 수 있지만, 디스커버리 프로토콜의 ENR은 전체 항목 합계가 300바이트로 제한됩니다. FORK_HASH를 4바이트 체크섬으로 줄이면 ENR에 다른 확장을 넣을 여유가 남고 실질적인 충돌 가능성 관점에서 4바이트면 임의로 많은 이더리움 네트워크를 구분하기에 충분합니다. 해시 함수 대신 CRC32를 쓰는 이유도 원문이 밝힙니다. 노드는 언제든 거짓말을 할 수 있으므로 암호학적 해시 함수를 써서 얻는 가치가 없고, 임의 데이터를 입력 손실 없이 4바이트로 접는 것이 목적이라면 Keccak256의 앞 4바이트를 자르거나 4바이트 단위로 XOR하는 것보다 그 용도로 설계된 CRC32가 낫다는 것입니다. IEEE CRC32는 이더넷, gzip, zip, png 등에서 쓰여 언어별 지원도 문제가 없습니다.

FORK_NEXT가 필요한 이유와 미래 포크를 하나만 알리는 이유

과거 포크 이력만 공유하면 상대가 단지 동기화가 덜 된 것인지, 아니면 소프트웨어가 낡아 멈춘 것인지 구분할 수 없습니다. FORK_NEXT가 그 구분을 가능하게 합니다. 반면 미래 포크를 FORK_HASH처럼 전부 묶어 알리지 않는 이유는, 이미 지나간 포크는 불변으로 볼 수 있지만 미래 포크는 아무것도 확정이 아니기 때문입니다. 아직 지나지 않은 포크는 연기될 수 있고 그것을 강제하면 네트워크가 쪼개집니다. 소프트웨어를 한동안 업데이트하지 않아 모든 미래 포크를 모르고 있을 수도 있습니다. 그래서 다음 포크 하나만 알리고 그마저도 실제로 지나기 전에는 거절 근거로 쓰지 않습니다.

범위 제한: 식별자 정의만 다룬다

이 EIP는 식별자 체계와 검증 규칙만 정의하며 기능 변경은 없습니다. 같은 체계를 eth/6x 핸드셰이크(이더리움과 이더리움 클래식 구분)와 디스커버리(연결 없이 거절) 등 여러 네트워킹 프로토콜에 넣을 수 있습니다. 같은 미래 블록에서 네트워크가 셋으로 갈라지는 3방향 포크의 깔끔한 분리는 다루지 않습니다. 포크한 쪽끼리 서로 분리하려면 사례별 특수 처리가 필요한데, 이를 다루지 않음으로써 제안을 실용적이고 단순하게 유지하고, 메인넷에서 갈라져 나가는 일을 너무 쉽게 만들지 않는다고 원문은 설명합니다.

상태와 구현

이 EIP의 상태는 Final로 확정된 표준입니다. 원문은 작성 시점(Petersburg 포크 기준)의 메인넷, Ropsten, Rinkeby, Görli에 대해 가능한 모든 포크 ID 테스트 묶음과, 메인넷 노드가 다양한 원격 식별자를 수락·거절하는 판정 테스트, RLP 인코딩 테스트를 제공합니다. 예를 들어 메인넷 Petersburg 노드(헤드 7987396)의 식별자는 {Hash: 0x668db0af, Next: 0}입니다. 구현은 go-ethereum(Geth)의 core/forkid 패키지에 있습니다.

EIP 원문 · ← EIPs