사슬이 둘로 갈라진 날: 2013년 3월, 아무도 정한 적 없는 규칙
앞 글에서 네트워크의 컴퓨터 하나하나(노드)는 전해 받은 블록을 저마다 검사한다고 했습니다. 그러면 같은 블록을 놓고 노드들의 답이 갈리면 어떻게 될까요? 2013년 3월 11일 비트코인에서 실제로 그런 일이 있었습니다 [S1] [S3].
한 블록, 두 가지 답
그날 225,430번 블록이 만들어져 네트워크에 퍼졌습니다 [S3]. 블록에 든 거래의 입력을 모두 합친 수가 그때까지 나온 어느 블록보다 많았습니다 [S1]. 보고서를 따라 이 블록을 «큰 블록» 이라고 부르겠습니다.
비트코인 프로그램 0.8 버전을 돌리던 노드는 큰 블록을 검사해 유효하다고 받아들였습니다. 0.8 이전 버전을 돌리던 노드 가운데 일부는 받아들이지 않았습니다 [S1]. 답이 둘 나왔습니다(그림 1).
노드는 저마다 검증합니다
노드의 사슬에는 그 노드가 검증한 블록만 들어갑니다. 노드들이 같은 블록들을 갖게 하려고 함께 따르는 검증 규칙을 합의 규칙이라고 합니다 [S2].
큰 블록을 받아들이지 않은 노드의 사슬에는 그 블록이 없습니다. 그 블록 위에 이어진 블록도 붙일 자리가 없습니다. 그래서 사슬이 둘로 갈라졌습니다(그림 2) [S1]. 사슬이 이렇게 갈라지는 것을 포크라고 합니다 [S2].
이중 지불 글에서 본 포크는 다음 블록이 나오면 긴 쪽으로 모였습니다. 이번에는 큰 블록이 든 사슬 쪽에 계산력의 약 60% 가 있었습니다. 보고서는 그래서 포크가 저절로 풀리지 않았다고 적습니다 [S1]. 노드는 유효한 블록만 든 사슬 가운데서 고르는데 [S2], 그 사슬은 아무리 길어져도 옛 프로그램 노드에게는 받아들일 수 없는 사슬이기 때문입니다.
하나가 되는 길은 반대쪽에 있었습니다. 큰 블록이 없는 사슬은 새 프로그램 노드도 받아들일 수 있습니다. 그쪽이 더 길어지면(쌓인 작업이 더 많아지면) 새 프로그램 노드가 그 사슬로 옮겨 옵니다 [S1]. 그런데 계산력이 40% 인 쪽은 블록을 더 느리게 쌓습니다.
계산력을 옮겨 보기
계산력을 어느 쪽에 얼마나 두어야 할까요? 먼저 60% 로 진행해 보고, 그날처럼 과반을 반대쪽으로 옮겨 이어서 해 보세요.
여기서 견줍니다: 2가 1보다 긴가?
1큰 블록이 든 사슬 포크 뒤 블록 1개 따르는 노드 새 프로그램
2큰 블록이 없는 사슬 포크 뒤 블록 0개 따르는 노드 옛 프로그램
포크 전 블록 큰 블록 포크 뒤 블록
블록을 어느 쪽이 만드는지는 계산력 비율대로 난수로 정합니다. 60% 여도 포크 직후에는 운으로 옛 쪽이 앞지르는 판이 있습니다 (이 모형의 계산으로 블록 열 개 안에 약 34%). «그날처럼 과반을 옮기기» 는 비율을 30% 로 내립니다. 보고서에는 과반이 넘어갔다는 것까지만 있고, 30% 는 이 모형이 정한 값입니다. 60% 로 열 개를 진행하고도 둘로 남은 판은, 30% 로 내려 서른 개를 더 진행하면 약 94% 가 하나로 모입니다.
사람이 나서서 다시 하나로
실제로도 그렇게 풀렸습니다. 포크는 금방 알려졌고, 필요한 사람들과 바로 연락이 닿았습니다 [S1]. 블록을 만드는 쪽 가운데 큰 두 곳(BTCGuild 와 Slush)이 자기 노드를 0.8 에서 0.7 로 내렸습니다. 자기들도 큰 블록을 받아들이지 않게 한 것입니다 [S1]. 계산력의 과반이 큰 블록이 없는 사슬로 넘어갔고, 결국 0.8 노드들도 그 사슬로 옮겨 왔습니다1 [S1]. 보고서는 두 곳이 이 일로 적지 않은 돈을 포기했다고 적습니다 [S1].
그동안 큰 거래소들은 입금을 잠시 멈췄습니다 [S1]. 그사이 큰 이중 지불이 적어도 한 번 있었습니다2. 보고서에 따르면 되는지 실험해 본 사람이 한 일이고, 악의는 없었습니다 [S1].
아무도 정한 적 없는 규칙
옛 프로그램은 왜 큰 블록을 받아들이지 못했을까요? 0.8 이전 버전은 Berkeley DB 라는 데이터베이스를 썼습니다. 이 데이터베이스는 쓸 수 있는 잠금(lock)의 수를 미리 설정해 두어야 하는데, 그 수가 «크지만 그 밖에는 유효한» 블록을 처리하기에 모자랐습니다 [S1]. 0.8 은 데이터베이스를 LevelDB 로 바꿨고, 여기에는 같은 한도가 없었습니다 [S1]. 걸린 것은 블록의 크기가 아니라 블록을 처리하는 데 드는 잠금 수였습니다(표 1). 입력이 많은 블록이 왜 잠금을 더 쓰는지는 보고서가 풀어 적지 않습니다. 잠금이 디스크의 쪽(page)마다 잡힌다는 것만 적습니다 [S1].
| 0.8 이전 (옛 프로그램) | 0.8 (새 프로그램) | |
|---|---|---|
| 데이터베이스 | Berkeley DB | LevelDB |
| 잠금 수의 한도 | 미리 설정한 수까지 | 같은 한도 없음 |
| 큰 블록 | 일부 노드가 처리하지 못해 받아들이지 않음 | 받아들임 |
«잠금이 모자라면 그 블록은 받아들이지 않는다» 는 누가 정한 규칙이 아닙니다. 그런데 보고서는 이 설정이 «암묵적으로 네트워크의 합의 규칙이 되어 있었다» 고 적습니다 [S1]. 일관된 규칙도 아니었습니다. 필요한 잠금 수는 노드마다 디스크에 놓인 데이터의 배치에 따라 달랐습니다. 그래서 모든 노드가 같은 0.7.2 버전이었어도, 이론으로는 한 노드가 만든 블록을 다른 노드가 검증하지 못할 수 있었습니다3 [S1].
숨은 규칙을 드러내 고치기
개발자들은 숨어 있던 규칙을 드러내 적었습니다. 곧 나온 0.8.1 은 두 달 동안, 잠금을 1만 개 넘게 쓸 것 같은 블록을 받아들이지 않고 50만 바이트가 넘는 블록을 만들지 않았습니다4 [S1]. 2013년 8월 16일에는 252,451번 블록이 네트워크에 받아들여졌고, 고치지 않은 노드들은 네트워크에서 떨어져 나갔습니다 [S1].
개발자 안내서는 이 일을 두 번의 하드 포크로 적습니다5. 하나는 뜻하지 않은 하드 포크로, 업그레이드한 노드의 능력을 잠시 낮춰서 풀었습니다. 다른 하나는 그 임시 조치를 걷어 낼 때의, 의도한 하드 포크입니다 [S2].
같은 장부는 같은 규칙에서
«잠금이 모자라면 받아들이지 않는다» 는 누가 정한 적이 없지만, 옛 프로그램의 그 동작이 규칙 노릇을 했습니다 [S1]. 합의를 지키려면 모든 전체 노드가 같은 합의 규칙으로 블록을 검증해야 합니다 [S2]. 시뮬레이터 ① 의 «옛 프로그램도 큰 블록을 받아들인다면» 을 누르면, 두 프로그램의 답이 같을 때는 사슬이 처음부터 갈라지지 않습니다. «모두가 같은 장부를 갖는다» 는 «모두가 같은 규칙으로 검증한다» 에 기대고 있습니다.
이 글의 계산과 확인
사건의 사실은 사후 보고서(BIP 50) [S1] 와 당시 bitcoin.org 공지 [S3] 에서 왔습니다. 날짜(3월 11일)와 블록 번호(225,430)는 공지에, 원인과 뒤처리는 보고서에 있습니다. 포크 뒤 두 사슬에 블록이 몇 개씩 쌓였는지, 풀리기까지 걸린 시간, 이중 지불의 금액은 두 문서에 없어 쓰지 않았습니다.
시뮬레이터 ① 의 규칙은 둘입니다. 옛 프로그램 노드는 큰 블록과 그 위에 이어진 블록을 받아들이지 않습니다. 새 프로그램 노드는 두 사슬을 받아들일 수 있습니다. 더 긴 쪽을 따르되, 길이가 같으면 따르던 쪽에 남습니다. 블록마다 드는 작업이 같다고 두어 «더 긴 사슬» 이 곧 «작업이 더 많이 든 사슬» 입니다. 블록을 만드는 쪽은 자기 노드가 따르는 사슬 끝에 블록을 붙입니다. 큰 블록이 막 나온 때(한 개 앞선 때)에서 시작합니다.
«열 개 안에 약 34%» 와 «서른 개를 더 진행하면 약 94%» 는 이 모형에서 경우를 모두 따라가며 더한 값입니다. 따로 짠 계산과 난수로 20,000판씩 돌린 결과가 이 값과 맞는지 시험했습니다. 옛 프로그램 노드도 큰 블록을 받아들이게 하면 사슬이 갈라지지 않고, 진행해도 한 사슬만 자라는 것도 시험했습니다 (verify.mjs 121개 통과).
곁질문
- 보통 사용자는 무엇을 해야 했나요? 아무것도 하지 않아도 됐습니다. 당시 공지는 블록을 만들거나 결제를 받는 쪽이 아니면, 어느 버전을 쓰든 프로그램이 알아서 옳은 사슬로 옮겨 간다고 안내했습니다 [S3]. ↩
- 그 이중 지불은 어떻게 통했나요? 보고서는 두 사슬 쪽이 거래들을 같은 차례로 전해 받았는데도 이중 지불이 성공했다고 적습니다. 블록을 만드는 쪽이 노드의 버전을 내릴 때, 아직 블록에 들지 않은 거래를 모아 둔 곳(memory pool)이 비워졌기 때문일 가능성이 크다고 봅니다 [S1]. 금액은 보고서에 없습니다. ↩
- 왜 아무도 미리 몰랐나요? 허용되는 가장 큰 블록은 테스트넷(시험용 네트워크)에서 문제없이 처리됐습니다. 그래서 그보다 작은데도 잠금은 더 많이 쓰는 블록이 있을 수 있다는 생각을 아무도 하지 못했다고 보고서는 적습니다 [S1]. 0.7 이전에는 블록을 만드는 노드가 프로그램을 고쳐 쓰지 않는 한 블록 크기를 스스로 50만 바이트로 묶어 두었고, 사건 전 한 주 사이에 그 크기를 올리라는 권고가 있었습니다 [S1]. ↩
- 옛 버전은 어떻게 하기로 했나요? 보고서의 뒤처리 목록에는 옛 버전에 같은 규칙을 넣고 잠금 수를 53만 7천 개로 늘리는 패치를 내는 일이 있습니다. 업그레이드할 수 없는 사람이 설정에서 잠금 수를 직접 늘리는 방법을 bitcoin.org 에 안내하고, 옛 버전 사용자에게 두 달 동안 알림을 보내는 일도 함께 적혀 있습니다 [S1]. ↩
- 하드 포크와 소프트 포크는 어떻게 다른가요? 합의 규칙이 바뀔 때, 새 규칙을 따른 블록을 업그레이드하지 않은 노드가 받아들이지 못하면 두 쪽의 사슬이 영영 갈라질 수 있고, 이것이 하드 포크입니다. 거꾸로 업그레이드한 노드만 어떤 블록을 받아들이지 않는 경우에는, 그쪽 계산력이 과반이면 옛 노드도 받아들이는 더 강한 사슬을 만들어, 영영 갈라지는 것을 막을 수 있습니다. 이것이 소프트 포크입니다 [S2]. ↩
출처
- [S1] BIP 50 — March 2013 Chain Fork Post-Mortem (Gavin Andresen).
- [S2] Bitcoin Developer Guide — Block Chain, Block Chain Overview · Common And Uncommon Block Chain Forks · Consensus Rule Changes.
- [S3] bitcoin.org — 11/12 March 2013 Chain Fork Information (당시 공지).