Bitcoin P2P Network
https://blog.naver.com/chunjein의 교육자료 "비트코인 네트워크와 블록체인 기술의 이해" 6장을 참고하여 정리
Last updated
https://blog.naver.com/chunjein의 교육자료 "비트코인 네트워크와 블록체인 기술의 이해" 6장을 참고하여 정리
Last updated
비트코인 네트워크에 처음 참여한 노드 A는 아래와 같은 과정을 거치고, 연결된 노드들은 TCP 소켓을 통해 서로 통신한다. (mainnet port num = 8333, testnet port num = 18333)
네트워크 동작 과정을 설명할 때 노드들은 full 노드임을 전제로 함
노드 A는 DNS seed에게 (https://bitcoin.org/en/glossary/dns-seed) 주소 요청 쿼리를 보내 현재 연결 가능한 Full 노드들의 주소를 리턴받음
노드 A는 리턴받은 주소로 Version 메시지를 전송, Version 메시지를 수신한 노드들은 Version 메시지로 응답
노드 A는 다른 노드들의 주소를 알아보기 위해 이미 알고 있는 노드의 주소로 getaddr 메시지를 전송, 상대방은 addr 메시지로 자신이 알고 있는 노드들의 주소를 알려줌
알아낸 노드들과 서로 통신하면서 주기적으로 ping/pong 메시지를 교환하여 서로 통신이 가능한 상태인지 확인
블록체인 데이터를 동기화
동기화 이후 새로 발생하는 블록 데이터를 서로 주고 받음, 각 노드에서는 수신한 TX 내역을 검증하고 다른 노드로 전달
통신 전에 상대방과 프로토콜 버전, 사용하는 Client S/W 버전, IP 주소, 보유하고 있는 블록 높이를 서로 교환한다. Version 메시지를 보내면 상대방도 Version 메시지를 보내고 서로 확인했다는 의미로 VerAck 메시지를 보낸다.
노드 A는 getaddr 메시지를 통해 노드 B에게 노드 B가 알고 있는 다른 노드의 네트워크 주소를 요청한다.노드 B는 addr 메시지를 통해 자신이 알고 있는 다른 노드들의 주소를 노드 A에게 알려준다.
위 과정을 통해 연결된 각 노드들은 주기적으로 메시지를 보내 자신이 네트워크 상에 연결되어 있음을 상대방에게 알려준다. Ping 메시지에는 랜덤한 nonce가 들어있고, ping 메시지를 수신한 노드는 pong 메시지에 자신이 받은 nonce를 그대로 넣어서 되돌려 준다.
새로운 블록을 전달받기 전에 이전에 만들어진 블록 데이터를 동기화한다. 초기 데이터 동기화 과정을 IBD (Initial Block Download)라고 하는데 Blocks-First 방식과 Headers-First 방식으로 나눠진다.
Blocks-First 방식은 동기화를 요청하는 노드가 자신이 가지고 있는 Genesis Block을 보여주고, 하나의 Sync node로부터 전체 블록을 다운로드받는 방법이다. 왼쪽 노드 A가 IBD Node이고, 오른쪽 노드 B가 Sync Node라면 아래와 같은 순서로 블록 데이터를 동기화한다.
getblocks: 노드 A는 Payload에 Genesis block (블록 #0)의 블록 헤더 해시를 넣어서 노드 B에 전달
inv: 자신이 가지고 있는 블록 헤더 해시를 전달, 블록 #1부터 최대 #501까지 보낼 수 있음
getdata: 노드 B가 가지고 있는 블록 데이터를 요청
block: 노드 A가 요청한 블록 데이터 전송
Genesis block 정보: https://www.blockchain.com/ko/btc/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
Headers-First 방식은 Blocks-First 방식이 매우 단순하지만 하나의 노드에만 의존하기 때문에 나타나는 문제를 해결하기 위해 Bitcoin Core v0.10부터 적용된 동기화 방법이다.
동기화를 위해 접속한 노드가 전체 블록 데이터를 가지고 있지 않거나 다운로드 중 접속이 끊어진다면?
다른 노드를 찾아서 동기화를 재요청해야 함
동기화를 위해 접속한 노드가 시스템 환경이 안좋아서 전송 속도가 늦다면? 블록 동기화 속도가 느릴 것
블록의 헤더 정보만 먼저 받고 헤더 정보를 이용해서 다른 여러 노드들로부터 동시에 블록 데이터를 다운로드하는 방식이다. 왼쪽 노드 A가 IBD Node이고, 오른쪽 노드 B가 Sync Node라면 아래와 같은 메시지 교환으로 동기화가 일어난다.
getheaders: 노드 A는 노드 B에게 자신이 가지고 있는 최근의 블록 헤더 해시 30개를 보냄
headers: 노드 B는 노드 A가 알고 있는 블록 다음의 블록 데이터가 자신의 블록체인에 있는지 검색하고 노드 A가 가지고 있지 않은 블록의 헤더 해시를 전달, 최대 2000개까지 가능
1번과 2번 과정을 여러 노드와 동시에 수행하여 최신의 블록 헤더까지 다운로드
getdata: 여러 Sync Node 들에게 각 헤더에 해당하는 실제 블록 데이터를 요청
block: Sync Node는요청받은 블록 데이터를 노드 A에게 전송
노드 A가 신규로 거래를 발행하거나 자신이 몰랐던 새로운 거래 데이터를 받으면 노드 B에게 inv 메시지를 통해 새로운 TX 데이터 발생을 통보한다. (새로운 거래 데이터의 해시 값만 보냄)
노드 B는 해시 값을 확인하여 자신이 모르는 TX이면 getdata 메시지를 통해 TX 데이터를 보내달라고 요청하고, 노드 A는 TX 데이터를 전송한다. 데이터를 전송받은 노드 B는 검증 후 다른 노드에게 inv 메시지로 새로운 TX가 발생했다고 알려준다.
Bitcoin Core v0.13.0부터 IBD 이후 지속적으로 발생하는 블록 데이터는 Compact Block Relay 방식에 따라 각 노드에게 전달된다. (BIP-152) 아래 과정은 통신 환경이 발전하면서 빠른 데이터 전달을 위해 고안된 High Bandwidth Relaying 동작 순서이다.
노드 B는 노드 A에게 sendcmpct 메시지를 전달: inv, getdata 메시지 교환없이 새로운 블록 데이터를 보내달라고 요청
노드 A가 새로운 블록 데이터를 받으면 기본적인 포맷 검증만 완료하고 cmpctblock 메시지를 통해 노드 B에게 블록을 전달
노드 B가 cmpctblock 메시지를 받으면 블록 데이터를 재구성하고 부족한 TX 데이터가 있을 때, 노드 A에게 getblocktxn 메시지를 보내서 추가로 데이터를 요청함
노드 A가 getblocktxn 메시지를 받으면 추가로 보낼 TX 데이터가 있을 때 blocktxn 메시지를 통해 노드 B에게 빠진 TX 데이터를 전달
각 노드는 TX 데이터를 수신하면 타당성을 검증하고 다른 노드로 전달하는데, 이 때 잘못된 내용이 발견되면 발신 노드에게 Reject 메시지를 전달한다. Reject 메시지를 수신한 발신 노드는 참고용으로 사용하여 선택적으로 데이터를 재확인하거나 무시하거나 할 수 있다. (Reject 기능은 Bitcoin Core v0.9.0부터 적용됨)
비트코인 네트워크를 구성하는 노드는 Full 노드와 SPV 노드로 구분할 수 있다.
Full 노드: 블록체인 데이터 전체를 다운받아 운용하는 노드, 모든 거래의 유효성을 검증할 수 있음
SPV 노드: 블록체인의 헤더와 머클 루트만 다운로드 받아 운용하는 노드, 거래나 블록의 유효성을 검증할 수 없고 다른 노드로 데이터를 전달할 수 없음 (블록 데이터는 수신만 가능, 정보를 다른 노드에게 알려주는 건 가)
네트워크 내 모든 노드들은는 거래 내역을 검증하는 기능을 가지고 있다. 하지만 SPV 노드는 모든 거래 내역을 갖고 있지 않기 때문에 보다 간편화된 검증 방법이 필요하다.
Full 노드만으로 비트코인 네트워크를 운영한다면 2018년 11월 기준 약 220GB의 블록 데이터를 다운받아야 하기 때문에 네트워크에 참여하기 위한 블록 동기화 (IBD)시간도 오래걸리고 데이터 저장 문제도 발생한다. SPV는 블록 헤더와 머클 루트 (Full 노드의 약 1/1000 크기)만 저장 하면 되기 때문에 스마트폰과 같은 디바이스에 적당한 운용 방법이 될 수 있다.
SPV 노드는 Full 노드로부터 선별적으로 자신과 관련된 TX 내역을 받을 수 있다. 하지만 내용을 요청할 때 자신의 IP 주소가 노출되어 프라이버시 문제가 생길 수 있다.
Bloom Filter (BIP-37, https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki)를 이용하면 SPV 노드 주소 이외에 다른 주소들과 관련된 거래 내역을 요청하여 자신의 주소가 직접 노출되는 것을 방지할 수 있다. Full 노드는 Bloom Filter를 통과하는 모든 거래 내역을 SPV 노드로 보내주고, SPV 노드는 자신의 주소에 해당하는 것만 이용하고 나머지는 버린다.
SPV 노드: "나를 포함한 다른 사람들의 정보를 같이 줄래? 정보는 내가 알아서 사용할게."
getheaders: SPV 노드는 자신이 가지고 있는 블록의 헤더 정보를 수집하여 최근 블록의 해더 해시를 Full 노드에 전송
headers: Full 노드는 SPV 노드가 가지고 있는 최근 블록 이후의 블록 헤더 정보를 전달
inv: 신규 블록이 생성되면 SPV 노드에게 알려줌
getdata: Full 노드에게 신규 블록에 포함된 거래 내역 중 Bloom Filter를 통과하는 TX들과 Merkle path 정보를 요청
getdata 메시지를 수신한 Full 노드는 SPV 노드가 Partial Merkle Tree를 만들어 Merkle Root를 계산할 수 있도록 관련 재료들과 필터를 통과한 거래 내역들을 보낸다. 유효성 검증을 위한 재료들은 merkleblock 메시지에 담겨져서 전달된다.
SPV 노드는 수신한 정보로 Merkle Root를 만들고 블록 헤더의 Merkle Root와 비교하여 필터링된 TX가 블록에 유효하게 등록되어 있는지 확인할 수 있다.
Transaction Count: 블록 안에 있는 TX의 개수, leaf-node의 수를 결정하는데 사용
Hash #1~4: Partial Merkle Tree에서 노드의 값으로 사용되는 TX 해시값
Flags: Hash #1~4의 위치를 지정하기 위한 플래그
필터링된 TX에 자신의 거래 내역이 있다면 SPV 노드는 거래 내역이 성공적으로 마이닝되었다는 것을 확인할 수 있고, 향후 6개의 블록이 더 마이닝되면 block depth를 이용해 finality까지 확인할 수 있다.
Bloom Filter는 어떤 원소가 특정 집합이 속해 있는지 여부를 확인할 때 사용되는 확률적 자료구조이다. 비트코인을 위해 새로 고안된 것이 아니라 1970년 Bloom이라는 과학자에 의해 개발되었고 현재 메일 서비스에서 스팸필터나 웹 브라우저에서 악성 URL을 검출할 때도 사용된다.
장점: 알고리즘이 단순하다. 굉장히 빠르고 메모리 효율적이다.
단점: 해시 함수를 사용하는 확률적 자료구조이기 때문에 오탐이 발생할 수 있다. (속하지 않은 원소를 가끔 속한다고 말할 수도 있다.)
Bloom filter는 N개의 해시 함수와 M개의 비트를 가지는 1차원 배열로 구성된다. N개의 해시 함수는 1에서 M 사이의 출력값을 가지고, 해당 출력값에 해당하는 인덱스의 비트 데이터를 1로 설정하여 필터를 만든다. 패턴 A에 대한 필터를 만드는 방법은 아래 그림과 같다.
SPV 노드는 자신이 원하는 여러 개의 패턴으로 필터를 만들 수 있다. 이렇게 만들어진 필터를 사용하여 패턴 X가 존재하는지 검증하기 위해 M개의 해시 함수로 연산한 해시값을 통해 배열의 인덱스를 출력한다. 각 인덱스에 해당하는 배열의 값이 모두 1로 설정되어 있다면 패턴 X는 존재할 가능성이 있다. (반드시 존재한다는 것은 아니다.) 만약 패턴 Y을 이 필터에 통과시켜 출력한 인덱스 값이 하나라도 일치하지 않는다면 패턴 Y는 절대 존재하지 않는다.
https://llimllib.github.io/bloomfilter-tutorial에서 직접 string data를 패턴으로 넣고 bloom filter를 만들어볼 수 있다.
version: 일반 tx는 전달하지 말라고 요청 (filter를 사용한 tx만 전달받겠다.)
filterload: 사전에 자신의 거래 정보를 패턴으로 만든 블룸 필터를 제작하고 Full 노드에게 전달
inv, getdata, tx: 블룸 필터만 통과한 거래 내역을 SPV에 전달
filteradd: 필터에 패턴을 추가로 등록할 경우 사용
filterclear: 필터를 해제
filterload 메시지에는 블룸 필터를 구성하는 정보가 들어있다. 만약 nFlags가 설정되어 있으면 Full 노드는 필터를 통과하는 거래들의 output 정보를 필터에 자동으로 추가한다. Full 노드가 자동으로 필터를 업데이트하면 False positive error가 점차 증가하기 때문에 SPV 노드는 지속적으로 관찰하면서 새로운 필터 정보를 전달해야 한다.
노드 A는 Feefilter 메시지를 통해 노드 B에게 TX fee가 얼마 이상인 TX만 전달해줄 것을 요청할 수 있다. (Spam TX를 방지하기 위해 사용하거나, 마이너가 블록에 자신이 원하는 fee 이상을 가지는 TX로 블록을 구성할 때도 유용하게 사용 가능)
만약, 노드 A가 SPV 노드이고 Bloom Filter가 세팅된 상태에서 feefilter를 요청하면 Bloom Filter를 우선 적용, Merkle path를 이용한 TX 검증이 더 중요하기 때문
아직 마이닝되지 않고 상대방의 memory pool에 쌓여있는 TX들을 받고 싶을 때는 mempool 메시지를 사용한다. 노드 A가 mempool 메시지로 TX 목록을 요청하면 노드 B는 자신의 memory pool에 저장된 TX 목록을 보내주고 노드 A는 자신의 memory pool에 없는 TX 들만 골라서 요청한다.
mempool 메시지는 노드가 네트워크에 초기 접속하여 TX 목록을 받아올 때나, 마이너들이 자신의 memory pool에 존재하지 않는 TX들을 받아올 때 사용할 수 있다.
Bitcoin Developer Guide-P2P Network: https://bitcoin.org/en/developer-guide#p2p-network
Bitcoin Wiki-Network: https://en.bitcoin.it/wiki/Network
Bitcoin Developer reference-Message Format: https://bitcoin.org/en/developer-reference#data-messages