用Rust实现区块链 - 6 点对点网络(P2P)
Posted Coding到灯火阑珊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用Rust实现区块链 - 6 点对点网络(P2P)相关的知识,希望对你有一定的参考价值。
截止到目前,我们在单机上实现了区块链的几乎所有关键特性:随机生成的地址、安全、持久化、工作量证明、UTXO交易。接下来我们将使用rust-libp2p库来实现区块链的p2p网络。
P2P网络
P2P 网络拓扑结构有很多种,有些是中心化拓扑,有些是半中心化拓扑,有些是全分布式拓扑结构。
区块链网络中的全节点就是全分布式拓扑结构,即去中心化的,端到端的网络,节点直接连接到其他节点,它的拓扑结构是扁平的。
SPV节点(简单支付验证节点),随机选择一个全节点进行连接,依赖这个全节点来获取数据,更接近半中心化的拓扑结构。
在这里我们先实现全节点,后面再逐渐完善矿工节点和SPV节点,在本地网络中通过 MDNS 做节点发现,使用 Gossip协议 做消息传播。
数据结构
Node
Node节点的功能包括启动P2P网络节点的消息监听,处理来自命令行的命令消息及其他节点的请求消息。
pub struct Node<T = SledDb>
bc: Blockchain<T>,
utxos: UTXOSet<T>,
msg_receiver: mpsc::UnboundedReceiver<Messages>,
swarm: Swarm<BlockchainBehaviour>,
-
bc:区块链
-
utxos:UTXO集合
-
msg_receiver:通道的接收端,接收其他节点的请求消息。
-
swarm:rust-libp2p的Swarm
BlockchainBehaviour
接收到其他节点的请求消息
#[derive(NetworkBehaviour)]
#[behaviour(event_process = true)]
pub struct BlockchainBehaviour
pub gossipsub: Gossipsub,
pub mdns: Mdns,
#[behaviour(ignore)]
pub msg_sender: mpsc::UnboundedSender<Messages>,
-
gossipsub:使用 Gossip协议 做消息传播
-
mdns:节点发现
-
msg_sender:接收到其他节点的请求消息后,发送到通道中。
命令行消息
#[derive(Debug, Serialize, Deserialize)]
pub enum Commands
Genesis(String),
Blocks(String),
Sync(String),
CreateWallet(String),
GetAddress(String),
Trans
from: String,
to: String,
amount: String,
,
-
Genesis:创建区块链
-
Blocks:显示区块链信息
-
Sync:同步区块
-
CreateWallet:创建钱包
-
GetAddress:获取地址
-
Trans:创建交易
节点消息
#[derive(Debug, Serialize, Deserialize)]
pub enum Messages
Version
best_height: usize,
from_addr: String,
,
Blocks
blocks: Vec<Block>,
height: usize,
to_addr: String,
,
Block
block: Block,
-
Version:向其他节点发送本地节点的区块链高度,同步本地节点。
-
Blocks:向其他节点发送本地区块链信息。
-
Block:向其他节点发送新加入的区块。
消息处理
无论是命令行消息,还是节点消息都采用serde_json进行序列化处理。
创建区块链
Commands::Genesis(addr) =>
if self.bc.get_tip().is_empty()
self.bc.create_genesis_block(addr.as_str());
self.utxos.reindex(&self.bc)?;
info!("Genesis block was created success!");
else
info!("Already exists blockchain, don't need genesis block!");
continue;
,
同步区块
处理命令行的同步命令
async fn sync(&mut self) -> Result<()>
let version = Messages::Version
best_height: self.bc.get_height(),
from_addr: PEER_ID.to_string(),
;
let line = serde_json::to_vec(&version)?;
self.swarm.behaviour_mut().gossipsub
.publish(BLOCK_TOPIC.clone(), line).unwrap();
Ok(())
节点接收到Version消息,如果本地区块链的高度大于其他节点的高度,则向其发送区块链信息。
async fn process_version_msg(&mut self, best_height: usize, from_addr: String) -> Result<()>
if self.bc.get_height() > best_height
let blocks = Messages::Blocks
blocks: self.bc.get_blocks(),
height: self.bc.get_height(),
to_addr: from_addr,
;
let msg = serde_json::to_vec(&blocks)?;
self.swarm.behaviour_mut().gossipsub
.publish(BLOCK_TOPIC.clone(), msg).unwrap();
Ok(())
节点接收到区块链信息后,同步到本地节点。
async fn process_blocks_msg(&mut self, blocks: Vec<Block>, to_addr: String, height: usize) -> Result<()>
if PEER_ID.to_string() == to_addr && self.bc.get_height() < height
for block in blocks
self.bc.add_block(block)?;
self.utxos.reindex(&self.bc).unwrap();
Ok(())
创建交易、挖矿
由于是全节点,为了简便,在这里交易的创建与挖矿放在一起处理了。
async fn mine_block(&mut self, from: &str, to: &str, amount: i32) -> Result<()>
let tx = Transaction::new_utxo(from, to, amount, &self.utxos, &self.bc);
let txs = vec![tx];
let block = self.bc.mine_block(&txs);
self.utxos.reindex(&self.bc).unwrap();
let b = Messages::Block block ;
let line = serde_json::to_vec(&b)?;
self.swarm.behaviour_mut().gossipsub
.publish(BLOCK_TOPIC.clone(), line).unwrap();
Ok(())
验证
启动第一个节点
RUST_LOG=info cargo run --quiet server data
Local peer id: PeerId("12D3KooWHn6sTgQU7bwKfPQHXi2oo4dDUEneoVFWtfur7bufXuZ7")
Listening on "/ip4/127.0.0.1/tcp/53664"
执行命令后,可以看到节点已经启动,生成了唯一的节点ID,在本地53664端口监听消息。
1,查看区块链信息
"Blocks":""
INFO blockchain_rust_part_6::networks::node: tip:
INFO blockchain_rust_part_6::networks::node: height: 0
可以看到这个节点上还没有区块链。
2,创建一个用户的钱包地址
"CreateWallet":"justin"
INFO blockchain_rust_part_6::networks::node: justin's address is 1KooomKwhgPCfB2YfnKT7yMUxGcVWqS3ns
地址已经创建,我们把这个地址记录下来。
3,下面执行创建区块链命令
"Genesis":"1KooomKwhgPCfB2YfnKT7yMUxGcVWqS3ns"
INFO blockchain_rust_part_6::networks::node: Genesis block was created success!
区块链创建成功。
再次查看
"Blocks":""
INFO blockchain_rust_part_6::blocks::blockchain: Block
header: BlockHeader
......
,
tranxs: [
Transaction
id: "24cde4a1782d23fda6ec353959f9197450078a1d79126c82e83898d92015ec96",
vin: [
Txinput
txid: "",
vout: 0,
signature: [],
pub_key: [],
,
],
vout: [
Txoutput
value: 10,
pub_key_hash: [
......
],
,
],
,
],
hash: "00ca23fdb684b0ebf29eb344e2f9c1ee2ba8325aceba8a7474dbcb77549a2bc9",
INFO blockchain_rust_part_6::networks::node: tip: 00ca23fdb684b0ebf29eb344e2f9c1ee2ba8325aceba8a7474dbcb77549a2bc9
INFO blockchain_rust_part_6::networks::node: height: 1
启动第二个节点
RUST_LOG=info cargo run --quiet server data1
PeerId("12D3KooWDknz5ScSw8Ye2ULheq3DHUexhRkH9z1y7N7a27XyWphs")
Listening on "/ip4/127.0.0.1/tcp/53891"
第二个节点节点启动成功,生成了唯一的节点ID,在本地53891端口监听消息。
1,查看区块链信息
"Blocks":""
INFO blockchain_rust_part_6::networks::node: tip:
INFO blockchain_rust_part_6::networks::node: height: 0
在第二个节点上还没有区块链。
2,同步区块链
"Sync":""
INFO blockchain_rust_part_6::networks::behaviour: Got message with id: 37343836383930393039373131393933343631 from peer: PeerId("12D3KooWHn6sTgQU7bwKfPQHXi2oo4dDUEneoVFWtfur7bufXuZ7")
从第一个节点:12D3KooWHn6sTgQU7bwKfPQHXi2oo4dDUEneoVFWtfur7bufXuZ7,同步区块链成功。
再次查看
"Blocks":""
Apr 19 14:00:53.136 INFO blockchain_rust_part_6::blocks::blockchain: Block
header: BlockHeader
......
,
tranxs: [
Transaction
id: "24cde4a1782d23fda6ec353959f9197450078a1d79126c82e83898d92015ec96",
vin: [
Txinput
txid: "",
vout: 0,
signature: [],
pub_key: [],
,
],
vout: [
Txoutput
value: 10,
pub_key_hash: [
......
],
,
],
,
],
hash: "00ca23fdb684b0ebf29eb344e2f9c1ee2ba8325aceba8a7474dbcb77549a2bc9",
Apr 19 14:00:53.136 INFO blockchain_rust_part_6::networks::node: tip: 00ca23fdb684b0ebf29eb344e2f9c1ee2ba8325aceba8a7474dbcb77549a2bc9
Apr 19 14:00:53.137 INFO blockchain_rust_part_6::networks::node: height: 1
与第一个节点的区块链一致。
启动第三个节点,步骤同第二个节点
创建交易
1,在第二个节点创建一个用户的钱包地址
"CreateWallet":"Bob"
INFO blockchain_rust_part_6::networks::node: Bob's address is 1EuM1UEhJFTDR5UfWzfghzv82bCdwRWk9E
2,在第一个节点创建交易
由Justin向Bob发送4单位货币
"Trans": "from":"1KooomKwhgPCfB2YfnKT7yMUxGcVWqS3ns","to":"1EuM1UEhJFTDR5UfWzfghzv82bCdwRWk9E","amount":"4"
3,在所有节点查看区块链信息
"Blocks":""
Apr 19 14:11:38.410 INFO blockchain_rust_part_6::blocks::blockchain: Block
header: BlockHeader
......
,
tranxs: [
Transaction
id: "1578eda9b3ba5f5be584ddb65389ac5172befa1ba50cf03a90fcdafdb5ce4bea",
vin: [
Txinput
txid: "24cde4a1782d23fda6ec353959f9197450078a1d79126c82e83898d92015ec96",
vout: 0,
signature: [
......
],
pub_key: [
......
],
,
],
vout: [
Txoutput
value: 4,
pub_key_hash: [
......
],
,
Txoutput
value: 6,
pub_key_hash: [
......
],
,
],
,
],
hash: "0056d99918490fd8d650d247722234c1d17f18d9073a39d2eacb16550d9737df",
Apr 19 14:11:38.411 INFO blockchain_rust_part_6::blocks::blockchain: Block
header: BlockHeader
......
,
tranxs: [
Transaction
id: "24cde4a1782d23fda6ec353959f9197450078a1d79126c82e83898d92015ec96",
vin: [
Txinput
txid: "",
vout: 0,
signature: [],
pub_key: [],
,
],
vout: [
Txoutput
value: 10,
pub_key_hash: [
......
],
,
],
,
],
hash: "00ca23fdb684b0ebf29eb344e2f9c1ee2ba8325aceba8a7474dbcb77549a2bc9",
INFO blockchain_rust_part_6::networks::node: tip: 0056d99918490fd8d650d247722234c1d17f18d9073a39d2eacb16550d9737df
INFO blockchain_rust_part_6::networks::node: height: 2
所有节点都已经同步了区块信息。
工程结构
完整代码:
https://github.com/Justin02180218/blockchain_rust
更多文章,请关注公众号:coding到灯火阑珊
区块链相关的知识
1、哈希列表:
在点对点网络中作数据传输的时候,我们会从同时从多个机器上下载数据,而且其中很多机器可以认为是不稳定或者是不可信的。对点网络在传输数据的时候,其实都是把比较大的一个文件,切成小的数据块。这样的好处是,如果有一个小块数据在传输过程中损坏了,那我只要重新下载这一个数据块就行了,不用重新下载整个文件。这就要求每个数据块都拥有自己的哈希值。在下载真正的数据之前,我们会先下载一个哈希列表。每个小块的哈希值拼到一起,然后对整个这个长长的字符串再做一次哈希运算,最终的结果就是哈希列表的根哈希,就可以用它来校验哈希列表中的每一个哈希都是正确的,进而可以保证下载的每一个数据块的正确性了。
2、Merkle Tree特点:
Merkle Tree是基于数据HASH构建的一个树。
-
数据结构是一个树,可以是二叉树,也可以是多叉树(二叉树来分析)。
-
Merkle Tree的叶子节点的value是数据集合的单元数据或者单元数据HASH。
-
Merke Tree非叶子节点的value是其所有子节点value的HASH值。
3、Merkle树:
1)区块链中的Merkle树是二叉树,用于存储交易信息。每个交易两两配对,构成Merkle树的叶子节点,进而生成整个Merkle树。
2)Merkle树使得用户可以通过从区块头得到的Merkle树根和别的用户所提供的中间哈希值列表去验证某个交易是否包含在区块中。提供中间哈希值的用户并不需要是可信的,因为伪造区块头的代价很高, 而中间哈希值如果伪造的话会导致验证失败。
3)在区块链中, Merkle树充当着一个代表性的角色,一个区块中的所有交易信息都被它归纳总结,大大提高区块链的效率。**那么,在区块链中为什么要使用Merkle树呢?**以比特币为例来解释:比特币网络中所有产生的交易都要打包进区块中,一般情况下,一个区块中包含几百上千笔交易是很常见的。由于比特币的去中心化特性,网络中的每个节点必须是独立,自给自足的,也就是每个节点必须存储一个区块链的完整副本。所以,比特币网络中一个全节点要存储、处理所有区块的数据,随着人们的使用,数据量会越来越大。这样的规则随着日益剧增的全节点所需空间,越来越难以让人遵守,难道让每个人都去运行一个全节点吗?还有节点就是区块链网络中的完全参与者,他们要遵守节点必须验证交易和区块,再加上想要与其他节点交互、下载新区块,对网络流量也是有一定要求的,节点要做的会越来越麻烦,并且效率低下。于是中本聪在比特币白皮书中提出了对这个问题的解决方案:简化支付验证(Simplified Payment Verification, SPV)。SPV 是一
个比特币轻节点,也就是我们大部分人在电脑安装的轻量级的比特币钱包,理论上来说,要验证一笔交易,钱包需要遍历所有的区块找到和该笔交易相关的所有交易进行逐个验证才是可靠的。但有了SPV就不用这么麻烦了,它不需要同步下载整个区块链的数据即不用运行全节点就可以验证支付,也不需要验证区块和交易,用户只需要保存所有的区块头就可以了。这里需要注意的是,SPV强调的是验证支付,不是验证交易。这两
个概念是不同的。验证支付,比较简单,只需要判断用于支付的那笔交易是否被验证过,以及得到网络多少次确认(即有多少个区块叠加)。而交易验证则复杂的多,需要验证账户余额是否足够支出、是否存在双重支付、交易脚本是否通过等问题,一般这个操作是由全节点的矿工来完成。
区块头包含着区块的必要属性,仅80个字节大小,而区块体当中包含着成百上千笔交易,每笔交易一般要400多个字节大小。
4、比特币中的Merkle树:
1)区块头包含了根节点的hash值,而中间节点、叶子节点还有基础数据在放在了区块体中。
2)在比特网络中的Merkle树是二叉树,所以它需要偶数个叶子节点。如果仅有奇数个交易需要归纳,那最后的交易就会被复制一份以构成偶数个叶子节点,这种偶数个叶子节点的
树也被称为平衡树。
5、Merkle树的其他应用场景:
1)默克尔树的应用场景其实很广泛,比较典型的就是P2P下载。在点对点网络中作数据传输的时候,会同时从多个机器上下载数据,而且很多机器可以认为是不稳定或者不可信的。为了校验数据的完整性,更好的办法是把大的文件分割成小的数据块(例如,把分割成2K为单位的数据块)。这样的好处是,如果小块数据在传输过程中损坏了,那么只要重新下载这一快数据就行了,不用重新下载整个文件。
2)除了P2P下载外,默克尔树还可以被用来快速比较大量的数据,因为当两个默克尔树根相同时,则意味着所代表的数据必然相同。还有就是可以用来实现零知识证明。
零知识证明指的是证明者能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。举个例子,你要我向你证明我拥有某一把钥匙,这个时候我不需要直接拿钥匙给你看,而是用这个钥匙开锁拿出所在柜子中的某一样东西给你看以此来证明我拥有这把钥匙。
6、区块结构:
每个区块主要包含了两部分,区块头和区块体。区块头主要用来存储本区块的一些相关属性,区块体则用来存储真实的交易数据记录。一个区块前后分别连接了父区块和子区块。
7、区块体:
区块体包括当前区块经过验证的、 区块创建过程中生成的所有交易记录。这些记录通过默克尔( Merkle)树的哈希过程生成唯一的默克尔,根并记入区块头。
8、区块头包括的三组数据:
1) 第一、父区块哈希值的数据。我认为可以理解为基因。
2)第二、挖矿难度值、区块时间戳以及Nonce。这一组数据记录与挖矿有关的内容。
3)第三、Merkle树根。这是个神奇的东西,可以先理解为描述区块中所有交易的数据。
区块链之所以叫链,就是因为它的结构是一条从后向前有序连接起来的数据结构,就像是一条尾巴永远在变长的链子。
那是什么原因导致这条数据这样井然有序的从后向前的连接呢?
这就得靠父区块哈希值了。所谓的父区块哈希值,就是父区块的区块头哈希值。从表格2中可以看到,区块头中包含了各种数据,大小是80字节,而这80字节的数据经过哈希运算,会得到一个32字节的字符串,这个32字节的字符串就是区块头哈希值。
以上是关于用Rust实现区块链 - 6 点对点网络(P2P)的主要内容,如果未能解决你的问题,请参考以下文章