• [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]


    [易学易懂系列|rustlang语言|零基础|快速入门|(27)|实战4:从零实现BTC区块链]

    项目实战

    实战4:从零实现BTC区块链

    我们今天来开发我们的BTC区块链系统。

    简单来说,从数据结构的角度上来说,区块链,就是区块组成的链。

    以下就是BTC区块链典型的结构:

    See the source image

    那最小单元就是区块:block。

    这个block包含两部分:区块头,区块体。

    我们先忽略Merkle树,先简化所有数据结构,只保留最基本的数据结构。

    那区块头,就包含:区块高度,时间截;前一个区块地址;工作证明;

    区块体,就包含交易数据,我们用一个vector来存储。

    代码如下 :

    ///交易结构体
    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
    pub struct Transaction {
        sender: String,    //发送者
        recipient: String, //接收者
        amount: i64,       //交易数量
    }
    /// 区块结构体
    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
    pub struct Block {
        pub index: u64,                     //区块高度
        timestamp: DateTime<Utc>,           //时间截
        pub transactions: Vec<Transaction>, //交易集合
        pub proof: u64,                     //证明
        pub previous_hash: String,          //上一个区块哈希地址
    }
    //区块链结构体
    #[derive(Default)]
    pub struct Blockchain {
        pub chain: Vec<Block>,                  //区块链帐本
        current_transactions: Vec<Transaction>, //当前交易集合
        pub nodes: HashSet<String>,             //节点集合
    }
    
    
    

    现在我们创建了一个基本的区块数据结构,现在我们来让矿工来创建区块吧。

    怎么让不同的矿工,积极地创建区块呢?

    我们引入一个机制叫:POW共识机制。

    什么叫POW?简单来说,就是大家根据给定的一个数值proof,进行hash计算,谁最先算出来的结果值符合某个条件,就拥有创建新的区块,并把这个区块连接到原来的区块链上的权力。

    比如,困难程度为5,那有个矿工用proof数据进行SHA哈希计算,出如下结果:

    0x0000010000000000000000000000000000000000000000000000000000000000

    这个结果,前面的0(除了0x外)是5个,则这就是结果值。

    如果,没有计算出上面的结果值,矿工将proof自增1,再进行SHA哈希计算,直到计算出这个符合条件的结果值为止。

    而那个给定的数据值proof,也要放在区块头,这个值在每次创建新区块的时候由矿工产生并写入区块头。

    当然,如果 两个节点都算出结果并加入了新区块,这时,会产生链的分叉,这时如何决定冲突呢?

    我们用最长链原则,即给定周期内,哪个节点拥有的链最长,就用哪个。

    所以我们的共识机制是:POW+最长链原则

    这个共识机制核心 代码如下:

    impl Blockchain {
        //创建创世区块
        pub fn new() -> Blockchain {
            let mut blockchain = Blockchain {
                chain: vec![],
                current_transactions: vec![],
                nodes: HashSet::new(),
            };
            blockchain.new_block(100, Some("1"));
            blockchain
        }
        /// Create a new Block in the Blockchain
        ///
        /// :param proof: The proof given by the Proof of Work algorithm
        /// :param previous_hash: (Optional) hash of previous Block
        /// :return: New Bloc
        /// 创建新区块
        pub fn new_block(&mut self, proof: u64, previous_hash: Option<&str>) -> Block {
            let block = Block {
                index: (self.chain.len() + 1) as u64,
                timestamp: Utc::now(),
                transactions: self.current_transactions.drain(0..).collect(),
                proof,
                previous_hash: previous_hash.unwrap_or("0").to_string(),
            };
    
            self.chain.push(block.clone());
            block
        }
        /// Creates a new transaction to go into the next mined Block
        ///
        /// :param sender: Address of the śender
        /// :param recipient: Address of the recipient
        /// :param amount: Amount
        /// :return: The index of the Block that will hold this transaction
        /// 发起一个新交易,将写入下一个区块
        pub fn new_transaction(&mut self, sender: &str, recipient: &str, amount: i64) -> u64 {
            self.current_transactions.push(Transaction {
                sender: sender.to_string(),
                recipient: recipient.to_string(),
                amount,
            });
            self.last_block().unwrap().index + 1
        }
        /// Simple Proof of Work Algorithm:
        /// - Find a number p' such that hash(pp') contains 4 leading zeroes,
        ///   where p is the previous proof, and p' is the new proof
        /// POW工作量证明共识机制算法
        pub fn proof_of_work(last_block: &Block) -> u64 {
            let mut proof = 0;
            let last_proof = last_block.proof;
            let last_hash = &last_block.previous_hash;
            while !Self::valid_proof(last_proof, proof, last_hash) {
                proof += 1;
            }
            proof
        }
        /// Validates the Proof: Does hash(last_proof, proof, last_hash) containt 4 leading zeroes
        //验证工作证明数字
        fn valid_proof(last_proof: u64, proof: u64, last_hash: &String) -> bool {
            let guess = format!("{}{}{}", last_proof, proof, last_hash);
            let guess_hash = hex_digest(Algorithm::SHA256, guess.as_bytes());
            guess_hash.ends_with("00000") //困难度为5
        }
    
        /// Creates a SHA-256 hash of a Block
        ///
        /// :param block: Block
        /// :return hash for the block
        /// 创建一个区块 的哈希值,基SHA-256算法
        pub fn hash(block: &Block) -> String {
            let serialized = serde_json::to_string(&block).unwrap();
            hex_digest(Algorithm::SHA256, serialized.as_bytes())
        }
        /// Returns the last Block in the chain
        /// 返回最后一个区块
        pub fn last_block(&self) -> Option<&Block> {
            self.chain.last()
        }
    
        /// Add a new node to the list of nodes
        ///
        /// :param address: Address of the node. Eg. 'http://192.168.0.5:5000'
        ///
        /// 节点注册,即新节点加入区块链网络,注册地址参数为节点服务器地址,如:'http://192.168.0.5:5000‘
        pub fn register_node(&mut self, address: &str) {
            let parsed_url = urlparse(address);
            self.nodes.insert(parsed_url.netloc);
        }
    
        /// Determine if a given blockchain is valid
        /// 链的验证
        fn valid_chain(&self, chain: &[Block]) -> bool {
            let mut last_block = &chain[0];
            let mut current_index: usize = 1;
            while current_index < chain.len() {
                let block = &chain[current_index];
                println!("{:?}", last_block);
                println!("{:?}", block);
                println!("-----------");
                if block.previous_hash != Blockchain::hash(last_block) {
                    return false;
                }
                if !Blockchain::valid_proof(last_block.proof, block.proof, &last_block.previous_hash) {
                    return false;
                }
    
                last_block = block;
                current_index += 1;
            }
            true
        }
    
        /// This is our Consensus Algorithm, it resolves conflicts
        /// by replacing our chain with the longest one in the network.
        ///
        /// :return True if our chain was replaced and false otherwise
        /// 解决冲突的机制,即共识机制,最长链原则处理逻辑,即共识机制为(POw+最长链原则)
        pub fn resolve_conflicts(&mut self) -> bool {
            let mut max_length = self.chain.len();
            let mut new_chain: Option<Vec<Block>> = None;
    
            // Grab and verify the chains from all the nodes in our network
            for node in &self.nodes {
                let mut response = reqwest::get(&format!("http://{}/chain", node)).unwrap();
                if response.status().is_success() {
                    let node_chain: Chain = response.json().unwrap();
                    if node_chain.length > max_length && self.valid_chain(&node_chain.chain) {
                        max_length = node_chain.length;
                        new_chain = Some(node_chain.chain);
                    }
                }
            }
            // Replace our chain if we discovered a new, valid chain longer than ours
            match new_chain {
                Some(x) => {
                    self.chain = x;
                    true
                }
                None => false,
            }
        }
    }
    
    

    以上代码,我们放在当前工程目录下的src/blockchain.rs,完整代码如下 :

    use crate::api::Chain;
    use chrono::{DateTime, Utc};
    use crypto_hash::{hex_digest, Algorithm};
    use reqwest;
    use serde::{Deserialize, Serialize};
    use std::collections::HashSet;
    use urlparse::urlparse;
    ///交易结构体
    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
    pub struct Transaction {
        sender: String,    //发送者
        recipient: String, //接收者
        amount: i64,       //交易数量
    }
    /// 区块结构体
    #[derive(Clone, Hash, Serialize, Deserialize, Debug)]
    pub struct Block {
        pub index: u64,                     //区块高度
        timestamp: DateTime<Utc>,           //时间截
        pub transactions: Vec<Transaction>, //交易
        pub proof: u64,                     //证明
        pub previous_hash: String,          //上一个区块哈希地址
    }
    //区块链结构体
    #[derive(Default)]
    pub struct Blockchain {
        pub chain: Vec<Block>,                  //区块链帐本
        current_transactions: Vec<Transaction>, //交易集合
        pub nodes: HashSet<String>,             //节点集合
    }
    
    impl Blockchain {
        //创建创世区块
        pub fn new() -> Blockchain {
            let mut blockchain = Blockchain {
                chain: vec![],
                current_transactions: vec![],
                nodes: HashSet::new(),
            };
            blockchain.new_block(100, Some("1"));
            blockchain
        }
        /// Create a new Block in the Blockchain
        ///
        /// :param proof: The proof given by the Proof of Work algorithm
        /// :param previous_hash: (Optional) hash of previous Block
        /// :return: New Bloc
        /// 创建新区块
        pub fn new_block(&mut self, proof: u64, previous_hash: Option<&str>) -> Block {
            let block = Block {
                index: (self.chain.len() + 1) as u64,
                timestamp: Utc::now(),
                transactions: self.current_transactions.drain(0..).collect(),
                proof,
                previous_hash: previous_hash.unwrap_or("0").to_string(),
            };
    
            self.chain.push(block.clone());
            block
        }
        /// Creates a new transaction to go into the next mined Block
        ///
        /// :param sender: Address of the śender
        /// :param recipient: Address of the recipient
        /// :param amount: Amount
        /// :return: The index of the Block that will hold this transaction
        /// 发起一个新交易,将写入下一个区块
        pub fn new_transaction(&mut self, sender: &str, recipient: &str, amount: i64) -> u64 {
            self.current_transactions.push(Transaction {
                sender: sender.to_string(),
                recipient: recipient.to_string(),
                amount,
            });
            self.last_block().unwrap().index + 1
        }
        /// Simple Proof of Work Algorithm:
        /// - Find a number p' such that hash(pp') contains 4 leading zeroes,
        ///   where p is the previous proof, and p' is the new proof
        /// POW工作量证明共识机制算法
        pub fn proof_of_work(last_block: &Block) -> u64 {
            let mut proof = 0;
            let last_proof = last_block.proof;
            let last_hash = &last_block.previous_hash;
            while !Self::valid_proof(last_proof, proof, last_hash) {
                proof += 1;
            }
            proof
        }
        /// Validates the Proof: Does hash(last_proof, proof, last_hash) containt 4 leading zeroes
        //验证工作证明数字
        fn valid_proof(last_proof: u64, proof: u64, last_hash: &String) -> bool {
            let guess = format!("{}{}{}", last_proof, proof, last_hash);
            let guess_hash = hex_digest(Algorithm::SHA256, guess.as_bytes());
            guess_hash.ends_with("00000") //困难度为5
        }
    
        /// Creates a SHA-256 hash of a Block
        ///
        /// :param block: Block
        /// :return hash for the block
        /// 创建一个区块 的哈希值,基SHA-256算法
        pub fn hash(block: &Block) -> String {
            let serialized = serde_json::to_string(&block).unwrap();
            hex_digest(Algorithm::SHA256, serialized.as_bytes())
        }
        /// Returns the last Block in the chain
        /// 返回最后一个区块
        pub fn last_block(&self) -> Option<&Block> {
            self.chain.last()
        }
    
        /// Add a new node to the list of nodes
        ///
        /// :param address: Address of the node. Eg. 'http://192.168.0.5:5000'
        ///
        /// 节点注册,即新节点加入区块链网络,注册地址参数为节点服务器地址,如:'http://192.168.0.5:5000‘
        pub fn register_node(&mut self, address: &str) {
            let parsed_url = urlparse(address);
            self.nodes.insert(parsed_url.netloc);
        }
    
        /// Determine if a given blockchain is valid
        /// 链的验证
        fn valid_chain(&self, chain: &[Block]) -> bool {
            let mut last_block = &chain[0];
            let mut current_index: usize = 1;
            while current_index < chain.len() {
                let block = &chain[current_index];
                println!("{:?}", last_block);
                println!("{:?}", block);
                println!("-----------");
                if block.previous_hash != Blockchain::hash(last_block) {
                    return false;
                }
                if !Blockchain::valid_proof(last_block.proof, block.proof, &last_block.previous_hash) {
                    return false;
                }
    
                last_block = block;
                current_index += 1;
            }
            true
        }
    
        /// This is our Consensus Algorithm, it resolves conflicts
        /// by replacing our chain with the longest one in the network.
        ///
        /// :return True if our chain was replaced and false otherwise
        /// 最长链原则处理逻辑,即共识机制为(POw+最长链原则)
        pub fn resolve_conflicts(&mut self) -> bool {
            let mut max_length = self.chain.len();
            let mut new_chain: Option<Vec<Block>> = None;
    
            // Grab and verify the chains from all the nodes in our network
            for node in &self.nodes {
                let mut response = reqwest::get(&format!("http://{}/chain", node)).unwrap();
                if response.status().is_success() {
                    let node_chain: Chain = response.json().unwrap();
                    if node_chain.length > max_length && self.valid_chain(&node_chain.chain) {
                        max_length = node_chain.length;
                        new_chain = Some(node_chain.chain);
                    }
                }
            }
            // Replace our chain if we discovered a new, valid chain longer than ours
            match new_chain {
                Some(x) => {
                    self.chain = x;
                    true
                }
                None => false,
            }
        }
    }
    
    

    现在 我们向外界提供一些可用的API。

    我们新建一个文件:src/api.rs,代码如下 :

    use crate::blockchain::{Block, Blockchain, Transaction};
    
    use actix_web::{web, HttpRequest, HttpResponse};
    use serde::{Deserialize, Serialize};
    use std::sync::Mutex;
    ///返回消息体
    #[derive(Serialize, Deserialize)]
    pub struct MessageResponse {
        message: String,
    }
    //交易请求信息
    #[derive(Serialize, Deserialize)]
    pub struct TransactionRequest {
        sender: String,
        recipient: String,
        amount: i64,
    }
    ///挖矿响应消息
    #[derive(Serialize)]
    pub struct MiningRespose {
        message: String,
        index: u64,
        transactions: Vec<Transaction>,
        proof: u64,
        previous_hash: String,
    }
    ///链结构体,代表现在网络上的最长链
    #[derive(Serialize, Deserialize)]
    pub struct Chain {
        pub chain: Vec<Block>,
        pub length: usize,
    }
    ///节点注册请求信息
    #[derive(Deserialize)]
    pub struct RegisterRequest {
        nodes: Vec<String>,
    }
    ///节点注册响应信息
    #[derive(Serialize)]
    pub struct RegisterResponse {
        message: String,
        total_nodes: Vec<String>,
    }
    //解决冲突响应信息
    #[derive(Serialize)]
    pub struct ResolveResponse {
        message: String,
        chain: Vec<Block>,
    }
    ///发起新交易
    pub fn new_transaction(
        state: web::Data<Mutex<Blockchain>>,
        req: web::Json<TransactionRequest>,
    ) -> HttpResponse {
        let sender = req.sender.to_owned();
        let recipient = req.recipient.to_owned();
        let index = state
            .lock()
            .unwrap()
            .new_transaction(&sender, &recipient, req.amount);
        HttpResponse::Created().json(MessageResponse {
            message: format! {"Transaction will be added to Block {}", index},
        })
    }
    ///矿工挖矿
    pub fn mine(
        node_identifier: web::Data<String>,
        state: web::Data<Mutex<Blockchain>>,
        _req: HttpRequest,
    ) -> HttpResponse {
        let (proof, previous_hash) = {
            let blockchain = state.lock().unwrap();
            let last_block = blockchain.last_block().unwrap();
            let proof = Blockchain::proof_of_work(&last_block);
            let previous_hash = Blockchain::hash(last_block);
            (proof, previous_hash)
        };
        let mut blockchain = state.lock().unwrap();
        blockchain.new_transaction("0", &*node_identifier, 1);
        let block = blockchain.new_block(proof, Some(&previous_hash));
        HttpResponse::Ok().json(MiningRespose {
            message: "New Block Forged".to_string(),
            index: block.index,
            transactions: block.transactions,
            proof,
            previous_hash,
        })
    }
    ///当前最新链的信息
    pub fn chain(state: web::Data<Mutex<Blockchain>>, _req: HttpRequest) -> HttpResponse {
        let length = state.lock().unwrap().chain.len();
        HttpResponse::Ok().json(Chain {
            chain: state.lock().unwrap().chain.clone(),
            length,
        })
    }
    ///节点注册
    pub fn register_node(
        state: web::Data<Mutex<Blockchain>>,
        req: web::Json<RegisterRequest>,
    ) -> HttpResponse {
        if req.nodes.is_empty() {
            return HttpResponse::BadRequest().json(MessageResponse {
                message: "Error: Please supply a valid list of nodes".to_string(),
            });
        }
        let mut blockchain = state.lock().unwrap();
        for node in req.nodes.iter() {
            blockchain.register_node(node)
        }
        HttpResponse::Created().json(RegisterResponse {
            message: "New nodes have been added".to_string(),
            total_nodes: blockchain.nodes.iter().cloned().collect(),
        })
    }
    ///跟网络上其他节点达成共识,即解决冲突
    pub fn resolve_nodes(state: web::Data<Mutex<Blockchain>>, _req: HttpRequest) -> HttpResponse {
        let mut blockchain = state.lock().unwrap();
        let replaced = blockchain.resolve_conflicts();
        let message = if replaced {
            "Our chain was replaced"
        } else {
            "Our chain is authorative"
        };
        HttpResponse::Ok().json(ResolveResponse {
            message: message.to_string(),
            chain: blockchain.chain.clone(),
        })
    }
    
    

    当然,我们要用到一些好用的库,在我们的Cargo.toml文件,我们加入依赖,完整代码如下:

    [dependencies]
    chrono = { version = "0.4.6", features = ["serde"] }
    crypto-hash = "0.3.3"
    serde = { version = "1.0.90", features = ["derive"] }
    serde_json = "1.0"
    actix-web = "1.0"
    uuid = { version = "0.7", features = ["v4"] }
    urlparse = "0.7.3"
    reqwest = "=0.9.17"
    

    最后我们的主程序 src/main.rs如下:

    pub mod api;
    pub mod blockchain;
    
    use actix_web::{web, App, HttpServer};
    use std::env;
    use std::sync::Mutex;
    use uuid::Uuid;
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let port = match args.as_slice() {
            [_, key, value] => {
                if key == "--p" {
                    value
                } else {
                    panic!("Illegal arguments passed to the program.");
                }
            }
            _ => "5000",
        };
        // TODO: make chain shared across threads
        let sharedchain = web::Data::new(Mutex::new(blockchain::Blockchain::new()));
        let node_identifier = web::Data::new(Uuid::new_v4().to_simple().to_string());
    
        HttpServer::new(move || {
            App::new()
                .register_data(sharedchain.clone())
                .register_data(node_identifier.clone())
                .data(web::JsonConfig::default().limit(4096))
                .service(web::resource("/mine").route(web::get().to(api::mine)))
                .service(web::resource("/transactions/new").route(web::post().to(api::new_transaction)))
                .service(web::resource("/chain").route(web::get().to(api::chain)))
                .service(web::resource("/nodes/register").route(web::post().to(api::register_node)))
                .service(web::resource("/nodes/resolve").route(web::get().to(api::resolve_nodes)))
        })
        .bind(format!("127.0.0.1:{}", port))
        .unwrap()
        .run();
    }
    
    

    然后我们可以用以下命令来调用 :

    挖矿:

    curl http://localhost:5000/mine
    

    创建新交易:

    curl -H "Content-Type: application/json" --request POST --data '{"sender":"e79fcabd1d70433191701d17c4d13112", "recipient":"some-other-address", "amount":5}' http://localhost:5000/transactions/new
    

    查看整条链信息:

    curl http://localhost:5000/chain
    
    

    注册节点:

    curl -H "Content-Type: application/json" --request POST --data '{"nodes":["http://localhost:5001"]}' http://localhost:5000/nodes/register
    
    

    与其他节点达成共识(共识机制):

    curl http://localhost:5000/nodes/resolve
    
    

    以上,希望对你有用。

    如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
    
    

    https://asymmetric.github.io/2018/02/11/blockchain-rust/

    https://jeiwan.net/posts/building-blockchain-in-go-part-1/

    https://freestartupkits.com/articles/technology/cryptocurrency-news-and-tips/ultimate-rust-blockchain-tutorial/

    https://hackernoon.com/learn-blockchains-by-building-one-117428612f46

    https://medium.com/@vanflymen/learn-blockchains-by-building-one-117428612f46?

    https://github.com/Koura/blockchain-example

  • 相关阅读:
    利用outlook发送邮件的代码,其实就一句话,哈哈~~
    一个或多个数据库无法访问,因而不会在数据库访问选项卡中显示
    窗口碰壁弹回的浮动广告代码
    DataFormatString格式化数据及格式化时间失效的问题
    弹出并转向代码
    常用运行命令
    post和get
    自己总结的手动生成gridview导出excel的方法
    Spring boot download file
    RESTful WebService 入门实例
  • 原文地址:https://www.cnblogs.com/gyc567/p/12079503.html
Copyright © 2020-2023  润新知