파이썬으로 배우는 블록체인 구조와 이론-6장 비트코인 P2P 프로토콜(1)

ImHyunbin
Quantum Ant
Published in
11 min readAug 12, 2019

비트코인 노드들은 서로 트랜잭션과 블록 데이터를 끊임없이 주고받는다.

데이터가 중복되지 않고 필요한 노드에만 전달될 수 있는 일종의 질서를 프로토콜(Protocol, 통신규약)이라 하며, 비트코인도 프로토콜이 정의돼 있다. 이것을 비트코인의 P2P 프로토콜이라 한다.

비트코인도 일반적이 인터넷처럼 TCP/IP 네트워크를 통해 메시지를 주고받는다. 비트코인 메시지는 TCP 계층에 실려 전송된다.

01.패킷 분석기 소개-와이어샤크(WireShark)

비트코인 네트워크에 참여하면 수많은 노드들과 메시지를 주고받는다. 이때 어떤 메시지들이 오가는지 확인하려면 패킷 데이터를 모니터링할 수 있는 패킷 분석기(packet analyzer)가 필요하다. 패킷 분석기는 여러 종류가 있지만, 일반적으로 와이어샤크를 많이 사용한다.

와이어샤크는 TCP/IP 프로토콜의 모든 메시지를 캡처해 화면에 보여준다. 비트코인 메시지만 확인하려면 화면 상단의 녹색 부분에 bitcoin이라고 입력하면 전체 메시지 중 비트코인 관련 메시지만 필터링해서 보여준다.

와이어샤크 실행 화면(출처: http://www.csharpstudy.com/bitcoin/article/6-%EB%B9%84%ED%8A%B8%EC%BD%94%EC%9D%B8-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%A0%91%EC%86%8D)

02.노드 관리 메시지

네트워크에 참여한 노드들은 주변 노드의 상황을 파악해야 한다. 노드들의 IP 주소도 알아야 하고 각 노드가 지원하는 기능도 알아야 한다. 그리고 노드들이 계속 활동 중인지도 알아야 한다. 이를 위해 version, verack, getaddr, addr, ping, pong 메시지를 주고받는다.

노드 관리 메시지 교환

version, verack 메시지

어떤 노드가 처음 네트워크에 참여하면 주변에 어떤 노드들이 있는지 알 수 없고, 다른 노드들도 내가 참여했다는 사실을 알 수가 없다. 따라서 처음에는 내가 네트워크에 들어왔다는 사실을 주변에 알려야 한다. 이런 목적으로 주고받는 메시지는 version과 verack다.

노드 A가 네트워크에 참여하면 노드 B에게 version 메시지를 보낸다.

(최초 노드 B의 주소는 비트코인 코어 소프트웨어에 기본으로 등록돼 있다.)

노드 A는 version 메시지 안에 자신이 사용하는 비트코인 소프트웨어 버전과 현자 가지고 있는 블록체인의 블록 높이(block height)를 보낸다.

노드 B는 이 메시지의 응답으로 verack(version acknowledgement)를 보낸다.

노드 B도 곧바로 A에게 version 메시지를 보내고, A는 verack로 응답한다.

이 절차를 거치면 A와 B는 서로의 IP 주소뿐만 아니라 서로의 기능, 블록 보유 상황 등을 알 수 있게 된다.

getaddr, addr 메시지

처음 네트워크에 참여한 노드는 다른 노드들의 IP 주소를 확보해야 한다. 그래서 트랜잭션이나 블록 데이터를 다른 노드들에 보낼 수 있다.

노드 A는 자신이 알고 있는 최초 노드 B에게 getaddr 메시지를 보내 B가 알고 있는 노드들의 IP주소를 알려 달라고 요청한다.

노드 B는 자신이 알고 있는 노드들의 IP 주소를 최대 1,000개까지 보낸다.

ping, pong 메시지

각 노드들은 서로 주기적으로 ping, pong 메시지를 보내 자신이 현재 네트워크상에 접속돼 있다는 것을 알려준다.

노드 A는 B에게 ping 메시지를 보내고 B는 A에게 응답의 표시로 pong 메시지를 보낸다.

ping 메시지를 보낼 때는 랜덤하게 부여한 논스 값을 기록하고, pong 메시지를 보낼 때는 상대방이 보낸 논스 값을 되돌려 보낸다.

pong 메시지의 논스 값을 확인하면 자신이 보낸 ping에 대한 응답임을 확인할 수 있다.

03.블록 데이터 동기화

블록체인 데이터가 전혀 없거나, 일부만 가지고 있는 풀 노드가 네트워크에 접속하면 다른 풀 노드와 블록체인을 맞춰야 한다. 다른 풀 노드에게 부족한 블록 데이터를 요청해 블록체인을 최신 상태로 유지한다. 그래야 트랜잭션을 검증하고 전파할 수 있으며, 새로 생성되는 블록을 검증하고 체인에 연결할 수 있다. 이를 블록 데이터 동기화 과정이라 한다.

초기 데이터 동기화 과정을 IBD(Initial Block Download)라 한다. IBD 방식은 블록 우선(Block-first) 방식과 헤더 우선(Header-first) 방식이 있다. 초기에는 블록 우선 방식을 사용했으나 현재는 헤더 우선 방식을 사용한다.

블록 우선(Block-first) 동기화 방식

단순히 다른 풀 노드에 부족한 블록 데이터를 요청해 전달받는 방식

블록 우선 방식의 동기화 과정

1. 노드 A는 B에게 getblocks 메시지를 보낸다. A는 getblocks 메시지에 현재 자신이 가지고 있는 최상위 블록의 해시 값(Header Hashes 필드)을 기록해서 B에게 보낸다. (이것은 내가 가진 블록은 여기까지이므로 이후의 블록이 더 있으면 보내 달라는 의미) A가 블록체인 데이터를 전혀 가지고 있지 않은 상태라면 제네시스 블록의 해시 값을 보낸다.

getblocks 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#blocks-first)

2. 노드 B는 inv(Inventory) 메시지에 A가 요청한 블록 이후의 블록 해시 값을 최대 500개까지 기록해서 A에게 보낸다. 블록 해시 값이 500개 이하면 여기가 마지막이라는 의미다.

Inv 메시지는 블록 데이터뿐만 아니라 트랜잭션 데이터를 보낼 때도 사용된다. 이때 inv가 블록에 대한 것인지 트랜잭션에 대한 것인지 구분하기 위해 타입(type)을 사용한다.

타입 필드에 ‘MSG_BLOCK’을 기록한다.

inv 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#blocks-first)

3. 노드 A는 inv 메시지에 있는 블록 해시 값을 보고 getdata 메시지를 보내서 최대 128개의 블록 데이터를 요청한다. 구조의 마지막 필드에 128개의 블록 해시가 기록된다.

getdata 메시지는 블록을 요구할 때도 사용되고, 트랜잭션을 요구할 때도 사용된다.

타입 필드에 ‘block’을 표시

getdata 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#blocks-first)

4. 노드 B는 block 메시지를 통해 A가 요청한 블록 해시에 해당되는 블록 바디 데이터를 순차적으로 보낸다.

block 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#blocks-first)

5. 노드 A는 inv에 있는 500개의 블록 해시에 해당하는 블록을 다 받을 때까지 getdata를 보내서 block 메시지를 받는다. inv에 있는 500개의 블록 해시를 모두 받으면 1번의 getblocks 메시지부터 다시 시작한다. inv 메시지에 500개 이하의 블록 해시가 기록되면 그 이후로는 블록이 더 이상 없다는 것으로 인식한다.

두번째 getblocks 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#blocks-first)

블록 우선 방식의 문제점

블록 우선 방식은 단순하다는 장점이 있지만 전적으로 싱크 노드에 의존해야 한다는 단점이 있다.

ㆍ속도 제한 : 만약 싱크 노드의 업로드 속도가 늦다면 IBD노드도 내려받는 속도가 늦어진다.

ㆍ다운로드 재시작 : 싱크 노드가 블록체인 데이터 전체를 가지고 있지 않거나 내려 받는 중 연결이 끊어지면 IBD 노드는 다른 싱크 노드를 찾아서 다시 요청해야 한다.

ㆍ디스크나 메모리 점유 공격 : 싱크 노드가 부적절한 블록 데이터를 전송한다면 이를 곧바로 검증하기 어렵기 때문에 나중에 다른 싱크 노드로부터 다시 내려받아야 할 수도 있다.

헤더 우선(Header-first) 동기화 방식

블록 우선 방식의 문제점을 보완하기 위해 현재는 헤더 우선 방식을 사용한다.

헤더 우선 방식은 싱크 노드로부터 블록체인의 모든 블록 헤더를 먼저 받고, 헤더 정보를 이용해 다른 여러 싱크 노드들로부터 블록 데이터를 받는 방식이다. 여러 싱크 노드로부터 데이터를 받기 때문에 특정 싱크 노드에 의존하지 않아도 되고, 블록을 내려받는 속도도 빠르다.

헤더 우선 방식의 동기화 과정

1. 노드 A는 B에게 getheaders 메시지를 보낸다. A는 getheaders 메시지에 현재 자신이 가지고 있는 최상위 블록의 해시 값(Header Hashes 필드) 몇 개를 기록해서 B에게 보낸다. (이것은 내가 가진 블록 헤더는 여기까지이므로 이후의 블록이 더 있으면 그 헤더를 보내 달라는 의미) A가 블록체인 데이터를 전혀 가지고 있지 않은 상태라면 제네시스 블록의 해시 값을 보낸다.

getheaders 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#headers-first)

2. 노드 B는 headers 메시지에 A가 요청한 블록 이후의 블록 헤더를 최대 2,000개까지 기록해서 보낸다. 블록 헤더의 크기는 80바이트로 크기가 작으므로 해시 값을 먼저 보내지 않고 헤더 데이터를 직접 보낸다.

headers 메시지 구조(출처 : https://bitcoin.org/en/p2p-network-guide#headers-first)

3. 노드 A는 B가 보낸 2,000개의 헤더를 받으면 이후의 헤더를 추가로 요청한다.

4. 노드 B는 A가 요청할 때마다 헤더를 2,000개씩 계속 보낸다. headers 메시지에 헤더가 2,000개 이하면 여기가 마지막이고 더 이상 없다는 의미다. 이때 노드 A는 다른 싱크 노드로 getheaders 메시지를 보내서 여기가 정말 마지막인지 확인할 수도 있다.

5. 노드 A가 블록 헤더를 모두 수집했으면 여러 싱크 노드들로 동시에 getdata를 보내서 해당 블록 데이터를 달라고 요청한다. getdata에는 자신이 알고 있는 헤더의 해시 값을 기록한다.

데이터 타입에는 ‘MSG_BLOCK’ 혹은 ‘MSG_WITNESS_BLOCK’을 기록한다. (MSG_BLOCK은 세그윗 보완 이전의 블록을 의미하고 MSG_WITNESS_BLOCK은 세그윗 보완 이후의 블록을 의미한다. 현재는 MSG_WITNESS_BLOCK이 쓰인다.)

지금까지 설명한 절차에 따라 노드 A는 다른 풀 노드들과 블록체인 데이터를 일치시킬 수 있다.

SPV(Simplified Payment Verification) 노드는 블록의 헤더 체인만 가지고 있으므로 1~4번 절차만 수행하면 된다.

헤더 우선 동기화 방식은 여러 싱크 노드들로부터 블록 데이터를 받기 때문에 고아 블록이 자주 발생한다.

고아 블록 발생과 처리(출처 : https://bitcoin.org/en/p2p-network-guide#headers-first)

IBD 노드가 싱크 노드 A와 B로부터 블록 데이터를 받는 상황이다.

IBD 노드는 A로부터 1번 블록을 받았고, B로부터 2번 블록을 받았다.

그리고 A에게 3번 블록을 요청했으나 아직 받지 못했고, B로부터 4번 블록을 받았다.

이때 4번 블록은 부모 블록인 3번이 없으므로 고아 블록이 되고 아직 체인에 연결할 수 없다.

이때는 4번 블록을 임시 저장소에 저장한 후 3번 블록이 도착하면 3번과 4번을 체인에 연결한다.

--

--