通过例子学rust(迷你区块链)

  1. 1. 导入所需的包
  2. 2. 创建结构体(blockchain.rs)
    1. 2.1. 交易信息
    2. 2.2. 区块头
    3. 2.3. 区块体
    4. 2.4.
  3. 3. 为链创建方法(blockchain.rs)
    1. 3.1. 创建新链
    2. 3.2. 修改难度和奖励
    3. 3.3. 创建新的交易
    4. 3.4. 计算当前块的hash值
    5. 3.5. 创建新的区块
    6. 3.6. 计算默克尔值
    7. 3.7. 工作量证明
    8. 3.8. hash计算
  4. 4. 主函数调用(main.rs)
    1. 4.1. 启动初始化
    2. 4.2. 循环内容
  5. 5. 效果
  6. 6. 完整代码

导入所需的包

Cargo.toml

1
2
3
4
5
6
7
[dependencies]
rand = "0.8.4"
chrono ="0.4.19" # 时间戳
sha2 = "0.9.5" #生成hash
serde = "1.0.126" #序列化
serde_json = "1.0.64"
serde_derive = "1.0.126"

创建结构体(blockchain.rs)

交易信息

交易结构体需要包含发送者,接收者以及金额,同时需要实现序列化,Debug和Clone(后续创建新块需要)特性

1
2
3
4
5
6
7
#[derive(Debug, Serialize, Clone)]
struct Transaction {
sender: String,
receiver: String,
amount: f32,
}

区块头

包含时间戳,前一个区块的hash,难度,默克尔值和nonce。

其中默克尔值是一个hash值,它包含每一笔交易的hash值。

nonce是用于工作量证明的值,得到一个nonce使得区块头的hash前X位为0,其中X为难度

1
2
3
4
5
6
7
8
#[derive(Debug, Serialize)]
pub struct BlockHeader {
timestamp: i64,
nonce: u32,
pre_hash: String,
merkle: String,
difficulty: u32,
}

区块体

包含区块头,交易计数以及所有的交易信息

1
2
3
4
5
6
#[derive(Debug, Serialize)]
pub struct Block {
header: BlockHeader,
count: u32,
transactions: Vec<Transaction>,
}

包含区块列表当前交易,难度,矿工地址以及奖励

1
2
3
4
5
6
7
pub struct Chain {
chain: Vec<Block>,
curr_trans: Vec<Transaction>,
difficulty: u32,
miner_address: String,
reward: f32,
}

为链创建方法(blockchain.rs)

创建新链

要创建一个全新的区块链,我们需要得到第一笔交易信息,由Root发给第一位矿工。

首先新建一个链,接收矿工地址和难度两个参数,调用generate_new_block()方法生成第一个区块,最后返回一个chain实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub fn new(miner_address: String, difficulty: u32) -> Chain {
// 创建新的区块链,接受第一笔交易信息,包含矿工地址 难度
let mut chain = Chain {
chain: Vec::new(),
curr_trans: Vec::new(),
difficulty,
miner_address,
reward: 100.0,
};
//创建新的链
chain.generate_new_block();
//创世区块 -> 见下文
chain //返回新的区块链
}

修改难度和奖励

创建修改难度和奖励的方法

1
2
3
4
5
6
7
8
9
10
pub fn update_difficulty(&mut self, difficulty: u32) -> bool {
//修改难度
self.difficulty = difficulty;
true
}
pub fn update_reward(&mut self, reward: f32) -> bool {
//修改奖励
self.reward = reward;
true
}

创建新的交易

创建新交易很简单,只需要将发送者和接受者信息传入Transaction结构体,最后将结构体加入到链中的交易信息里即可,返回的bool用于判断是否创建成功。

1
2
3
4
5
6
7
8
pub fn new_transaction(&mut self, sender: String, receiver: String, amount: f32) -> bool {
self.curr_trans.push(Transaction {
sender,
receiver,
amount,
});
true
}

计算当前块的hash值

定义last_hash方法来计算当前块的hash值。

1
2
3
4
5
6
7
8
9
pub fn last_hash(&self) -> String {
let block = match self.chain.last() {
Some(block) => block,
None => return String::from_utf8(vec![48; 64]).unwrap()
//新建的链不存在最后一个hash,使用40个0填充
};
Chain::hash(&block.header)
// hash方法见下文,用于计算区块的hash值
}

创建新的区块

首先生成区块头,传入初始化信息。原视频使用time生成时间戳,这边使用了chrono。

然后产生一笔新的交易,从Root给miner。

创建一个新的区块,将此处的新交易添加到区块的交易中,并且将历史交易记录添加到此区块,此时transaction列表长度即为交易数。

通过get_merkle方法得到默克尔值,通过proof_of_work得到nonce,最后将此block打包到链中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pub fn generate_new_block(&mut self) -> bool {
//生成新区块
let header = BlockHeader {
timestamp: Utc::now().timestamp(),
nonce: 0, //初始nonce设置为0
merkle: String::new(),
pre_hash: self.last_hash(), //使用self的last_hash()方法得到上一个块的hash
difficulty: self.difficulty,
};
let reward_trans = Transaction {
sender: String::from("Root"), //新区块的发送者为root
receiver: self.miner_address.clone(), //接受奖励的矿工地址
amount: self.reward,
};
let mut block = Block {
header,
count: 0, //交易计数
transactions: vec![], //新的区块没有交易
};
block.transactions.push(reward_trans);
block.transactions.append(&mut self.curr_trans);
block.count = block.transactions.len() as u32;

block.header.merkle = Chain::get_merkle(block.transactions.clone());// 默克尔值通过新的函数计算

Chain::proof_of_work(&mut block.header); //工作量证明 -> 见下文

println!("{:#?}", &block);
self.chain.push(block); //将新的区块加入链中
true
}

计算默克尔值

传入当前交易的列表,取出每一笔交易 计算hash后加入merkle列表。

如果默克尔列表中有奇数个值,则复制最后一个值并将其加入默克尔列表。

从默克尔hash列表中 取出前两个交易的hash值,拼接后将调用hash()得到一个hash,重复此操作直到默克尔列表中的hash被合并为一个时pop并返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn get_merkle(curr_trans: Vec<Transaction>) -> String {
let mut merkle = Vec::new();
for t in &curr_trans {
let hash = Chain::hash(t);
merkle.push(hash);
}
if merkle.len() % 2 ==1 {
let last = merkle.last().cloned().unwrap();
merkle.push(last);
}
while merkle.len() > 1 {
let mut h1 = merkle.remove(0);
let mut h2 = merkle.remove(0);
h1.push_str(&mut h2);
let nh = Chain::hash(&h1);
merkle.push(nh);
}
merkle.pop().unwrap()
}

工作量证明

接收一个区块头并且计算hash,从hash中取出一定长度的切片并且将其解析,其中长度由difficulty决定。

parse::<u32>()会尝试将其解析为u32类型,这边得到的是一个Result类型,我们的目的是得到一个nonce使得最终区块头的hash值中,前X位都是0,所以在得到ERROR时,我们依然将nonce+1,知道得到所需的hash为止。

最后我们就得到了一个可以将区块头的hash的前X位变成0的nonce,用于证明工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pub fn proof_of_work(header: &mut BlockHeader) {
loop {
let hash = Chain::hash(header);
let slice = &hash[..header.difficulty as usize];
match slice.parse::<u32>() {
Ok(val) => {
if val != 0 {
header.nonce +=1;
//不等于0时将其+1
}else {
println!("block hash: {}",hash);
break
}
},
Err(_) => {
header.nonce += 1;
continue;
}
};
}
}

hash计算

hash接受一个可被serde序列化的结构体,将结构体变成字符串,我们调用sha2的方法输出得到一个&[u8]类型的列表,通过hex_to_string 将其转换为一个16进制字符串,这边使用std::fmt::Write实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pub fn hash<T: serde::Serialize>(item: &T) -> String {
let input = serde_json::to_string(&item).unwrap();
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
let res = hasher.finalize();
let vec_res = res.to_vec();

Chain::hex_to_string(vec_res.as_slice())
}
pub fn hex_to_string(vec_res: &[u8]) -> String {
let mut s = String::new();
for b in vec_res {
write!(&mut s,"{:x}",b).expect("unable to write");
}
s
}

主函数调用(main.rs)

启动初始化

启动此程序时,我们需要初始化数据,开始第一笔交易以创建区块链,

使用io::stdin().read_line读取用户输入,并且将参数传给blockchain::Chain::new创建全新的区块链

1
2
3
4
5
6
7
8
9
10
11
12
let mut miner_address = String::new();
let mut difficulity = String::new();
let mut choice = String::new();
print!("input a miner address: ");
io::stdout().flush();
io::stdin().read_line(&mut miner_address);
print!("Difficulty: ");
io::stdout().flush();
io::stdin().read_line(&mut difficulity);
let diff = difficulity.trim().parse::<u32>().expect("we need an integer");
println!("generating genesis block!");
let mut chain = blockchain::Chain::new(miner_address.trim().to_string(), diff);

循环内容

打印菜单,使用match匹配用户输入,对其做相应处理,通过对应函数返回的bool来判断执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
loop {
println!("Menu");
println!("1) New Transaction");
println!("2) Mine block");
println!("3) Change Difficulty");
println!("4) Change Reward");
println!("0) Exit");
print!("Enter your choice: ");
io::stdout().flush();
choice.clear();
io::stdin().read_line(&mut choice);
println!("");
match choice.trim().parse().unwrap() {
0 => {
println!("exiting!");
process::exit(0);
}
1 => {
let mut sender = String::new();
let mut receiver = String::new();
let mut amount = String::new();
print!("Enter sender address: ");
io::stdout().flush();
io::stdin().read_line(&mut sender);
print!("Enter receiver address: ");
io::stdout().flush();
io::stdin().read_line(&mut receiver);
print!("Enter amount: ");
io::stdout().flush();
io::stdin().read_line(&mut amount);

let res = chain.new_transaction(sender.trim().to_string(),
receiver.trim().to_string(),
amount.trim().parse().unwrap());

match res {
true => println!("transaction added"),
false => println!("transaction failed"),
}
}
2 => {
println!("Generating block");
let res = chain.generate_new_block();
match res {
true => println!("Block generated successfully"),
false => println!("Block generated failed"),
}
}
3 => {
let mut new_diff = String::new();
print!("Enter new difficulty: ");
io::stdout().flush();
io::stdin().read_line(&mut difficulity);
let res = chain.update_difficulty(new_diff.trim().parse().unwrap());
match res {
true => println!("Update difficulty successfully"),
false => println!("Update difficulty failed"),
}
}
4 => {
let mut new_reward = String::new();
print!("Enter new reward: ");
io::stdout().flush();
io::stdin().read_line(&mut new_reward);
let res = chain.update_difficulty(new_reward.trim().parse().unwrap());
match res {
true => println!("Update reward successfully"),
false => println!("Update reward failed"),
}
}
_ => println!("\tinvalid option please retry\t"),
}
}

效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

input a miner address: test
Difficulty: 2
generating genesis block!
block hash: 00bc8d8f20ea56e4fbeafc8f0afe848d688b4b067e18384a6d2c4e3737a
Block {
header: BlockHeader {
timestamp: 1624696709,
nonce: 32928,
pre_hash: "0000000000000000000000000000000000000000000000000000000000000000",
merkle: "7bb26fbe128ae86921b2152e15be46fb02cb2e7d6ac918e77d487cadfc7b",
difficulty: 2,
},
count: 1,
transactions: [
Transaction {
sender: "Root",
receiver: "test",
amount: 100.0,
},
],
}
Menu
1) New Transaction
2) Mine block
3) Change Difficulty
4) Change Reward
0) Exit
Enter your choice:

完整代码

点击这里

blockchain.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
extern crate serde;
extern crate serde_json;
extern crate sha2;


use sha2::{Sha256, Digest};
use std::fmt::Write;
use chrono::Utc;


#[derive(Debug, Serialize, Clone)]
struct Transaction {
sender: String,
receiver: String,
amount: f32,
}

#[derive(Debug, Serialize)]
pub struct BlockHeader {
timestamp: i64,
nonce: u32,
pre_hash: String,
merkle: String,
difficulty: u32,
}

#[derive(Debug, Serialize)]
pub struct Block {
header: BlockHeader,
count: u32,
transactions: Vec<Transaction>,
}

pub struct Chain {
chain: Vec<Block>,
curr_trans: Vec<Transaction>,
difficulty: u32,
miner_address: String,
reward: f32,
}

impl Chain {
pub fn new(miner_address: String, difficulty: u32) -> Chain {
let mut chain = Chain {
chain: Vec::new(),
curr_trans: Vec::new(),
difficulty,
miner_address,
reward: 100.0,
};
chain.generate_new_block();
chain
}
pub fn new_transaction(&mut self, sender: String, receiver: String, amount: f32) -> bool {
self.curr_trans.push(Transaction {
sender,
receiver,
amount,
});
true
}
pub fn last_hash(&self) -> String {
let block = match self.chain.last() {
Some(block) => block,
None => return String::from_utf8(vec![48; 64]).unwrap()
};
Chain::hash(&block.header)
}

pub fn update_difficulty(&mut self, difficulty: u32) -> bool {
self.difficulty = difficulty;
true
}
pub fn update_reward(&mut self, reward: f32) -> bool {
self.reward = reward;
true
}

pub fn generate_new_block(&mut self) -> bool {
let header = BlockHeader {
timestamp: Utc::now().timestamp(),
nonce: 0,
merkle: String::new(),
pre_hash: self.last_hash(),
difficulty: self.difficulty,
};
let reward_trans = Transaction {
sender: String::from("Root"),
receiver: self.miner_address.clone(),
amount: self.reward,
};
let mut block = Block {
header,
count: 0,
transactions: vec![],
};
block.transactions.push(reward_trans);
block.transactions.append(&mut self.curr_trans);
block.count = block.transactions.len() as u32;

block.header.merkle = Chain::get_merkle(block.transactions.clone());

Chain::proof_of_work(&mut block.header);


println!("{:#?}", &block);
self.chain.push(block);
true
}
fn get_merkle(curr_trans: Vec<Transaction>) -> String {
let mut merkle = Vec::new();
for t in &curr_trans {
let hash = Chain::hash(t);
merkle.push(hash);
}
if merkle.len() % 2 ==1 {
let last = merkle.last().cloned().unwrap();
merkle.push(last);
}
while merkle.len() > 1 {
let mut h1 = merkle.remove(0);
let mut h2 = merkle.remove(0);
h1.push_str(&mut h2);
let nh = Chain::hash(&h1);
merkle.push(nh);
}
merkle.pop().unwrap()
}
pub fn proof_of_work(header: &mut BlockHeader) {
loop {
let hash = Chain::hash(header);
let slice = &hash[..header.difficulty as usize];
match slice.parse::<u32>() {
Ok(val) => {
if val != 0 {
header.nonce +=1;
}else {
println!("block hash: {}",hash);
break
}
},
Err(_) => {
header.nonce += 1;
continue;
}
};
}
}
pub fn hash<T: serde::Serialize>(item: &T) -> String {
let input = serde_json::to_string(&item).unwrap();
let mut hasher = Sha256::new();
hasher.update(input.as_bytes());
let res = hasher.finalize();
let vec_res = res.to_vec();
Chain::hex_to_string(vec_res.as_slice())
}
pub fn hex_to_string(vec_res: &[u8]) -> String {
let mut s = String::new();
for b in vec_res {
write!(&mut s,"{:x}",b).expect("unable to write");
}
s
}
}

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#[macro_use]
extern crate serde_derive;

use std::io;
use std::process;
use std::io::Write;

mod blockchain;

fn main() {
let mut miner_address = String::new();
let mut difficulity = String::new();
let mut choice = String::new();
print!("input a miner address: ");
io::stdout().flush();
io::stdin().read_line(&mut miner_address);
print!("Difficulty: ");
io::stdout().flush();
io::stdin().read_line(&mut difficulity);
let diff = difficulity.trim().parse::<u32>().expect("we need an integer");
println!("generating genesis block!");
let mut chain = blockchain::Chain::new(miner_address.trim().to_string(), diff);
loop {
println!("Menu");
println!("1) New Transaction");
println!("2) Mine block");
println!("3) Change Difficulty");
println!("4) Change Reward");
println!("0) Exit");
print!("Enter your choice: ");
io::stdout().flush();
choice.clear();
io::stdin().read_line(&mut choice);
println!("");
match choice.trim().parse().unwrap() {
0 => {
println!("exiting!");
process::exit(0);
}
1 => {
let mut sender = String::new();
let mut receiver = String::new();
let mut amount = String::new();
print!("Enter sender address: ");
io::stdout().flush();
io::stdin().read_line(&mut sender);
print!("Enter receiver address: ");
io::stdout().flush();
io::stdin().read_line(&mut receiver);
print!("Enter amount: ");
io::stdout().flush();
io::stdin().read_line(&mut amount);

let res = chain.new_transaction(sender.trim().to_string(),
receiver.trim().to_string(),
amount.trim().parse().unwrap());

match res {
true => println!("transaction added"),
false => println!("transaction failed"),
}
}
2 => {
println!("Generating block");
let res = chain.generate_new_block();
match res {
true => println!("Block generated successfully"),
false => println!("Block generated failed"),
}
}
3 => {
let mut new_diff = String::new();
print!("Enter new difficulty: ");
io::stdout().flush();
io::stdin().read_line(&mut difficulity);
let res = chain.update_difficulty(new_diff.trim().parse().unwrap());
match res {
true => println!("Update difficulty successfully"),
false => println!("Update difficulty failed"),
}
}
4 => {
let mut new_reward = String::new();
print!("Enter new reward: ");
io::stdout().flush();
io::stdin().read_line(&mut new_reward);
let res = chain.update_difficulty(new_reward.trim().parse().unwrap());
match res {
true => println!("Update reward successfully"),
false => println!("Update reward failed"),
}
}
_ => println!("\tinvalid option please retry\t"),
}
}
}