どのノードも初めてネットワークに接続する際、取得して検証すべき最も重要なデータはブロックヘッダーである。
ブロックヘッダーをダウンロード数ルト、複数のノードに完全なブロック非同期に要求してブロックのダウンロードを並列化できる
ブロックヘッダーを取得するコマンドは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