どのノードも初めてネットワークに接続する際、取得して検証すべき最も重要なデータはブロックヘッダーである。
ブロックヘッダーをダウンロード数ルト、複数のノードに完全なブロック非同期に要求してブロックのダウンロードを並列化できる
ブロックヘッダーを取得するコマンドはgetheadersと呼ばれる
7f110100: Protocol version, 4bytes, little-endian, 70015
01: Number of hashes, varint
a35b…00: Starting block, little-endian
0000…00: Ending block, little-endian
class GetHeaderMessage: command = b'getheaders' def __init__(self, version=70015, num_hashes=1, start_block=None, end_block=None): self.version = version self.num_hashes = num_hashes if start_block is None: raise RuntimeError('a start block is required') self.start_block = start_block if end_block is None: self.end_block = b'\x00' * 32 else: self.end_block = end_block def serialize(self): result = int_to_little_endian(self.version, 4) result += encode_varint(self.num_hashes) result += self.start_block[::-1] result += self.end_block[::-1] return result
### ヘッダーレスポンス
ノードを作成し、ハンドシェイクした後、ヘッダーを要求できる
from io import BytesIO from block import Block, GENESIS_BLOCK from network import SimpleNode, GetHeadersMessage node = SimpleNone('', testnet=False) node.handshake() genesis = Block.parse(BytesIO(GENESIS_BLOCK)) getheaders = GetHeadersMessage(start_block=genesis.hash()) node.send(getheaders)
## headrsメッセージ
02: Number of block headers
00…67: Block header
00: Number of transactions (always 0)
headersメッセージはvarintのヘッダー数(1~2000)
各ブロックヘッダーは80bytes
class HeadersMessage: command = b'headers' def __init__(self, blocks): self.blocks = blocks @classmethod def parse(cls, stream): num_headers = read_varint(stream) blocks = [] for _ in range(num_headers): blocks.append(Block.parse(stream)) num_txs = read_varint(stream) if num_txs != 0: raise RuntimeError('number of txs not 0') return cls(blocks)
from io import BytesIO from network import SimpleNode, GetHeaderMessage, HeadersMessage from block import Block, GENESIS_BLOCK, LOWEST_BITS from helper import calculate_new_bits previous = Block.parse(BytesIO(GENESIS_BLOCK)) first_epoch_timestamp = previous.timestamp expected_bits = LOWEST_BITS count = 1 node = SimpleNode('', testnet=False) node.handshake() for _ in range(19): getheaders = GetHeadersMessage(start_block=previous.hash()) node.send(getheaders) headers = node.wait_for(HeadersMessage) for header in headers.blocks: if not header.check_pow(): raise RuntimeError('bad PoW at block {}'.format(count)) if header.prev_block != previous.hash(): raise RuntimeError('discontinuous block at {}'.format(count)) if count % 2016 == 0: time_diff = previous.timestamp - first_epoch_timestamp expected_bits = calculate_new_bits(previous.bits, time_diff) print(expected_bits.hex()) first_epoch_timestamp = header.timestamp if header.bits != expected_bits: raise RuntimeError('bad bits at block {}'.format(count)) previous = header count += 1
VerAckMessageは最小のネットワークメッセージ。AckはAcknoldgeの略称で確認の意味
VerAckMessage :The verack message acknowledges a previously-received version message, informing the connecting node that it can begin to send other messages.
※https://bitcoin-s.org/api/org/bitcoins/core/p2p/index.html