[Easy to understand series | rustlang language | zero-based | Getting Started | (27) | combat 4: Zero realized from BTC block chain]

[Easy to understand series | rustlang language | zero-based | Getting Started | (27) | combat 4: Zero realized from BTC block chain]

Project combat

Combat 4: BTC block chains from zero to achieve

Let's develop our BTC block chain system today.

Briefly, from the perspective of the data structure, the block chain is composed of chain block.

The following is a typical BTC block chain structure:

See the source image

The smallest unit is the block: block.

This block contains two parts: header area, the block body.

Let's ignore the Merkle tree, first of all to simplify the data structure, leaving only the most basic data structure.

That area header, contains: cut-off time; before a block address

Block body contains transaction data, we use a vector to store.

code show as below:

///交易结构体
#[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>,             //节点集合
}

Now we have created a basic block data structure, let's let the miners to create a block it.

How to make different miners, actively create block it?

We introduce a mechanism called: POW consensus mechanism.

What is the POW? Simply put, all according to a set of values proof, the hash calculation, who first calculated the resulting values meet a certain criteria, you have to create a new block, this block and the connection to the original block authority in the chain.

For example, the degree of difficulty is 5, and that there is hashed with SHA miners proof data, the following results:

0ksh00000l0000000000000000000000000000000000000000000000000000000000

Results, leading zeros (except the outer 0x) is 5, then this is the result value.

If the result value is not calculated above, the proof miners increment 1, then SHA hash calculation until the calculated values ​​meet the criteria.

And that given data value proof, but also on the header area, the value produced and written by the miners in the header area each time you create a new block.

Of course, if both nodes are calculated result and joined the new block, this time, will have forked chain, then decide how the conflict it?

We longest chain principle that within a given period, which node owns the longest chain, to use which.

So our consensus mechanism is: POW + longest chain principle

The consensus mechanism core code is as follows:

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,
        }
    }
}

The above code, we put in the current project directory src / blockchain.rs, complete code is as follows:

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,
        }
    }
}

We now offer some of the available API to the outside world.

We create a new file: src / api.rs, code is as follows:

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(),
    })
}

Of course, we have to use some nice library, in our Cargo.toml file, we add dependency, complete code is as follows:

[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"

Finally, our main src / main.rs as follows:

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();
}

Then we can use the following command to call:

Mining:

curl http://localhost:5000/mine

Create a new deal:

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

View the whole chain information:

curl http://localhost:5000/chain

Registration nodes:

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

Consensus (consensus mechanism) with other nodes:

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

Above, I hope useful to you.

如果遇到什么问题,欢迎加入: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

Guess you like

Origin www.cnblogs.com/gyc567/p/12079503.html