jjzjj

用Rust实现区块链 - 6 点对点网络(P2P)

Coding到灯火阑珊 2024-07-02 原文

截止到目前,我们在单机上实现了区块链的几乎所有关键特性:随机生成的地址、安全、持久化、工作量证明、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到灯火阑珊

有关用Rust实现区块链 - 6 点对点网络(P2P)的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  6. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  7. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  8. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  9. ruby - 实现k最近邻需要哪些数据? - 2

    我目前有一个reddit克隆类型的网站。我正在尝试根据我的用户之前喜欢的帖子推荐帖子。看起来K最近邻或k均值是执行此操作的最佳方法。我似乎无法理解如何实际实现它。我看过一些数学公式(例如k表示维基百科页面),但它们对我来说并没有真正意义。有人可以推荐一些伪代码,或者可以查看的地方,以便我更好地了解如何执行此操作吗? 最佳答案 K最近邻(又名KNN)是一种分类算法。基本上,您采用包含N个项目的训练组并对它们进行分类。如何对它们进行分类完全取决于您的数据,以及您认为该数据的重要分类特征是什么。在您的示例中,这可能是帖子类别、谁发布了该项

  10. ruby-on-rails - 使用 Ruby 正确处理 Stripe 错误和异常以实现一次性收费 - 2

    我查看了Stripedocumentationonerrors,但我仍然无法正确处理/重定向这些错误。基本上无论发生什么,我都希望他们返回到edit操作(通过edit_profile_path)并向他们显示一条消息(无论成功与否)。我在edit操作上有一个表单,它可以POST到update操作。使用有效的信用卡可以正常工作(费用在Stripe仪表板中)。我正在使用Stripe.js。classExtrasController5000,#amountincents:currency=>"usd",:card=>token,:description=>current_user.email)

随机推荐