# 读书笔记:比特币的网络协议 **Published by:** [Aulee](https://paragraph.com/@aulee/) **Published on:** 2023-06-04 **URL:** https://paragraph.com/@aulee/WUUIsvD6JLPsshoKgRVd ## Content 本文是Programming Bitcoin第10章的读书笔记,同时涉及第9章的内容。第10章讨论比特币的网络协议。比特币网络为P2P网络。网络的各个节点公布不同的交易、区块和其所知道的其他节点。注意,比特币网络协议对于达成共识并不是关键的。同样的数据可以使用其他协议从一个节点发送到另一个节点,而不影响比特币本身。 比特币网络使用socket协议进行节点间的通讯。本章代码示例如何使用socket通讯请求、接收和验证区块的头部信息(block headers)。 如何查找可访问的结点 原书中给出的网络结点’mainet.programmingbitcoin.com’和’testnet.programmingbitcoin.com’已经停止运行。可以到bitnodes.io查找其他可访问的网络结点(reachable nodes)。 网络握手 要获得比特币网络节点的数据,要首先通过socket实现与结点的握手。握手的目的是与结点交换协议的版本信息。握手的具体过程如下(来自这里): L -> R: Send version message with the local peer's version R -> L: Send version message back R -> L: Send verack message R: Sets version to the minimum of the 2 versions L -> R: Send verack message after receiving version message from R L: Sets version to the minimum of the 2 versions L向R发送版本(version)信息,R向L发回自己的版本信息,并发送版本接收信息(VerAck)。R将协议版本定为两个版本之较小者。L在接收到R的版本信息后向R发送VerAck信息。L也将协议版本设定为两个版本之较小者。 由此可知,原书第183页的代码有个小错误: host = "xx.xx.xx.xx" node = SimpleNode(host, testnet=False) version = VersionMessage() node.send(version) verack_received = False version_received = False while not verack_received and not version_received: message = node.wait_for(VersionMessage, VerAckMessage) if message.command == VerAckMessage.command: verack_received = True else: version_received = True 在上述代码的while not verack_received and not version_received:一行中的and应该为or。否则不能完成全部握手过程。 请求、接收和验证区块头部信息 在完成握手后,就可以获得区块头部(block headers)信息了。区块数据由头部和交易(tx)数据组成。头部数据为每个区块的原数据,由如下部分组成: Version Previous block Merkle root timestamp bits nonce 我们依据上述区块头部信息验证区块是否达成proof of work。我们计算该区块头部数据的哈希值,以验证其是否达到了一定的工作量标准。该值一定要足够的小才符合标准。为达此目的,区块的编纂者(即挖矿者)必须通过不断试错Merkel root和nonce值来找到足够小的hash值。找到这样的小数的概率是很低的,因此需要进行大量次的哈希运算,如同为找到一点金子要淘选大量的泥沙。故名挖矿。 可以设想,随着投入挖矿的算力不同,区块产生的速度会发生波动。这对于保证交易的及时清算是非常不利的。因此,比特币通过动态调整proof of work难度的办法,把区块挖出的速度控制在每10分钟一个左右。这样,在两周时间里,就可产生约6×24×14=2016个区块。为达成此目标,比特币每生成2016个区块就调整一次哈希运算的目标值(哈希值必须小于这个目标值),以适应算力的变化。这个值放在区块头部的bits字段里。 在区块的头部数据里,还包括其所指向的上一个区块的哈希,即previous block字段。此即区块链之称谓的由来。区块就是这样连缀起来的。 因此在验证一个区块头部时,应包含有以下三方面的内容: 1.区块所指向的上一个区块是否真实地是其上一个区块。 2.区块给自己设定的bits是否是根据上一个2016个区块的周期的速度调整的目标值。区块是不能自降挖矿难度的。 3.区块是否达成了proof of work的标准。 因为验证区块头部数据需要我们使用区块生成速度的历史数据,因此完整的验证过程必须从第一个区块(genesis block)开始,并依次验证所有区块。书中的代码如下: host = "xx.xx.xx.xx" node = SimpleNode(host, testnet=False) node.handshake() previous = (Block.parse(BytesIO(GENESIS_BLOCK))) first_epoch_timestamp = previous.timestamp expected_bits = LOWEST_BITS count = 1 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 注意要想成功运行上述代码,需注意其中函数calculate_new_bits定义的细节: def calculate_new_bits(previous_bits, time_differential): if time_differential > TWO_WEEKS * 4: time_differential = TWO_WEEKS * 4 if time_differential < TWO_WEEKS // 4: time_differential = TWO_WEEKS // 4 new_target = bits_to_target(previous_bits) * time_differential // TWO_WEEKS if new_target > MAX_TARGET: new_target = MAX_TARGET return target_to_bits(new_target) 该函数出现在书中第175页的代码和176页的Exercise 13,及278页的答案中。注意书中忽略了 if new_target > MAX_TARGET: new_target = MAX_TARGET 导致程序报错。这段代码是说当新调整的哈希目标超过最大目标时,使用最大目标。 参考书目 Song, Jimmy. Programming bitcoin: Learn how to program bitcoin from scratch. O'Reilly Media, 2019. ## Publication Information - [Aulee](https://paragraph.com/@aulee/): Publication homepage - [All Posts](https://paragraph.com/@aulee/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@aulee): Subscribe to updates