加密电子货币——比特币
今天给他家分享的是当下炙手可热的比特币背后的技术—区块链。大家在百度输入区块链几个字,就能获得成千上万条有关的消息。想必大家或多或少对区块链有所了解。即是并不关心科技,也多少会知道有区块链这个词。我也在通过不断的学习,来不断刷新自己对区块链的认识。作为一名程序员,自然而然地也想探究一下区块链背后的技术。学习 rust 也有一段时间了,有时候想用 rust 写点东西,在网上学习一些资料,正好用 rust 来实现一个简单区块链,同时也可以来检验一下自己的 rust 学习成果
区块链
可以简单将区块链简单地按时间序列排列的区块
区块包含以下信息
- Index(索引): 表示当前区块在区块链的位置(序号)
- Payload(数据): 在块中发生的任何相关信息或事件
- Timestamp(时间戳): 时间戳记录区块生成时间
- Nonce: 存储工作量验证(PoW)数值
- Pervious block hash(上一区块链)
- Hash: 我们将上面信息进行组合生成 hash 编码
我们给出的工作量要求是,可以在这个字符串后面添加一个叫做nonce的整数值,对变更后(添加nonce)的字符串进行SHA256哈希运算,如果得到的哈希结果(以16进制的形式表示)是以"0000"开头的,则验证通过。
工作量证明(Proof Of Work,简称POW),简单理解就是一份证明,用来确认你做过一定量的工作。
hash 算法
既然叫加密电子货币,那么这里就会采用一定加密算法。那么什么又是 hash 算法,简而言之,hash 算法由一组不可逆的计算组成,这些计算可以在数据上执行以生成(通常)唯一的字节序列。
- MD5
- SHA-1
- SHA-256
有关加密学,这里就不多说了,这也是一门学科,要展开来说就会滔滔不绝了。
接下来我们就开始通过代码来实现一个简单的区块链,今天我们只是通过数据结构来分析以及实现如何创建区块链以及如何加入区块。
创建工作空间
mkdir simple_blockchain
在创建好的文件夹下创建
touch Cargo.toml
Cargo.toml
也就是 rust 项目管理工具 Cargo 配置文件,在Corgo.toml
文件下进行工作空间配置
[workspace]
members = [
"main",
"core",
"utils",
]
我们项目需要包含三个包,他们分别是
- main 应用的入口
- core 放置区块链逻辑
- utils 工具,例如哈希算法和序列化等工具
接下来工作就是用命令行来分别创建上面 3 包
cargo new main
cargo new core --lib
cargo new utils --lib
屏幕快照 2020-04-18 下午6.47.54.png
定义数据结构
创建文件 core/src/block.rs
,在文件定义区块链的数据结构。在数据结构包括两个部分分别是区块头和区块体
区块头
pub struct BlockHeader{
pub time: i64,
pub tx_hash:String,
pub pre_hash:String
}
因为是简单所以我们这里只保留几个数据
- time 对应时间戳
- tx_hash 交易hash,用 merkle hash 所有交易数据
- pre_hash 上一个区块 hash
区块体
pub struct Block{
pub header:BlockHeader,
pub hash:String,
pub data:String
}
- hash: 当前区块头的 hash
- data: 交易数据
区块链
pub struct BlockChain{
pub blocks:Vec<block::Block>,
}
也就是区块的集合,通过区块中 pre_hash 指向前一个块 hash 来生成一种链条。
序列化和反序列化
我们序列化的工作都是在 utils 包下完成的,所以 utils 文件夹下创建一个coder.rs
文件,在这里来写序列化和反序列化的代码。
我们要对区块链数据进行序列化和反序列化我们需要用到一些外部库,这里介绍一个好用的外部库
(https://crates.io/)[crates.io]
这里注意我们依赖是放置在 utils 包下的Cargo.toml
内并发工作空间上
[dependencies]
serde = "1.0.106"
bincode = "1.2.1"
配置好依赖在
use bincode;
use serde::{Deserialize,Serialize};
实现序列化的函数
pub fn sc_serialize<T: ?Sized>(value: &T) -> Vec<u8>
where T: Serialize,
{
let serialize = bincode::serialize(value).unwrap();
serialize
}
代码解释
-
<T: ?Sized>
使用泛型类型,在 size 前面?表示在编译时对于T大小是不确定的, - 返回值
Vec<u8>
是字节列表 -
where T: Serialize
约束类型泛型需要实现 Serialize 特征 - 因为
bincode::serialize(value)
返回是 Result 类型所以这里是使用unwrap()
实现反序列化的函数
pub fn sc_deserialize<'a, T>(bytes: &'a [u8]) -> T
where T: Deserialize<'a>,
{
let deserialize = bincode::deserialize(bytes).unwrap()
deserialize
}
- 这里需要用到生命周期
- 参数是引用
编写测试用例
写好序列化和反序列的函数,我们需要写函数测试用例,通过编写这个测试用例,我们学习如何在 rust 中编写测试用例以及编写测试用例需要注意哪些
导出模块
写好了代码,需要对代码进行测试,我们需要先到处 coder 模块具体做法是utils/lib.rs
文件中去掉 Cargo 自动生成的代码然后添加到处 coder 的代码
pub mod coder;
编写测试用例
我们定义 Point 结构体,然后调用序列化方法将结构体序列化为字节,然后在对序列化后 Point 进行反序列化的得到 Point 结构体和开始定义结构体进行对比来测试序列化和反序列化函数。
struct Point {
x: i32,
y: i32,
}
只是定义一个结构体还远远不够,因为我们的函数需要接受一个实现了 Serialize 和 Deserialize 特征的结构,这个两个特征需要由库serde
提供,所以在 Cargo.toml 文件中进行修改 serde 为如下形式
serde = {version = "1.0.106",features = ["derive"]}
这样我们就可以在 Point 的通过宏的形式为结构体 Point 添加 Serialize
和 Deserialize
特征。因为需要比较所以我们还需要添加PartialEq,Eq
特征。
#[derive(Serialize,Deserialize,PartialEq,Eq,Debug)]
struct Point {
x: i32,
y: i32,
}
#[cfg(test)]
mod tests {
use crate::coder::Point;
use crate::coder::{sc_serialize,sc_deserialize};
#[test]
fn coders_works() {
let pnt = Point{x:1,y:1};
let serialized_pnt = sc_serialize(&pnt);
let deserialized_pnt:Point = sc_deserialize(&serialized_pnt);
assert_eq!(deserialized_pnt,pnt);
}
}
测试用例具体写法我想大家一看就懂。
- 需要注意就是
running 1 test
test coder::tests::coders_works ... ok
实现hash函数
在写 hash 函数同样我们也需要用到一个外部库rust-crypto
,所以在依赖添加rust-crypto
[dependencies]
serde = {version = "1.0.106",features = ["derive"]}
bincode = "1.2.1"
rust-crypto = "0.2"
在依赖添加完 crypto 后我们还需要在 lib.rs 头文件引入
use bincode;
use serde::{Deserialize,Serialize};
use crypto::digest::Digest;
use crypto::sha3::Sha3;
pub fn get_hash(value:&[u8]) -> String{
let mut hasher = Sha3::sha3_256();
hasher.input(value);
hasher.result_str()
}
- 在定义区块结构时 hash 类型 String 所以这里返回类型为 String
- 输入
value:&[u8]
为一个字节数组的引用,
这里引用说明我们只是使用值而没有修改其值,在写底层语言我们需要把引用和借用搞清楚,我们平时写代码时就需要考虑什么时候使用引用什么时候使用借用
创建一个 hasher 对象let mut hasher = Sha3::sha3_256();
,然后将值输入到 hasher 对象通过返回值来实现一个求 hash 的函数。
网友评论