0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何使用Rust从零开发区块链

jf_wN0SrCdH 来源:Rust语言中文社区 2024-01-22 13:58 次阅读

在这一系列的文章中,我将向你展示如何使用 Rust 语言从零开始编写一个区块链。这些文章并不是为了想学习智能合约编程的人,而是为了理解区块链的底层知识 - 区块、链、键值数据库、网络、共识机制等的人而准备的。如果你想学习区块链的底层知识,这就是你要找的地方。

我已经在区块链领域工作了几年,从一开始就计划写这一系列的文章来学习区块链。但直到现在,我才有足够的时间来做这件事。我主要熟悉 Substrate 框架,从其文档站点学到了很多知识,从核心概念到如何使用它来开发应用。所以我建议你如果有时间的话,去阅读那些文档,他们官方还有关于它的课程,你可以在这里找到信息:Polkadot Academy。国内的话,OneBlock+社区在做免费的Substrate培训课程,你可以在这里找到最新一期(2023.12)报名链接。

Substrate 是一个功能齐全的区块链框架,它非常强大。但它也很复杂,难以深入理解,所以我开设这个系列,帮助那些想要深入了解区块链内部的开发者

Rust 无疑是编写区块链的首选,它也是我最喜欢的编程语言。如果你还没有接触过它,你应该去试试。

好的,让我们开始吧。

什么是区块?它是区块链的基本单位。在我们的例子中,它只是一个 Rust 结构体。我们可以这样定义它:

structBlock{
header:BlockHeader,
body:BlockBody,
}

区块由两部分组成:BlockHeader 和 BlockBody。每种类型的定义如下:

BlockHeader

structBlockHeader{
hash:String,
height:u64,
prev_hash:String,
timestamp:u64,
}

BlockBody

typeBlockBody=Vec;

区块的Body部分是一个普通的字符串向量,而头部看起来更有趣。在所有的字段中,prev_hash 是最有趣的,它存储了前一个区块的哈希字段值,我们将在这篇文章后面的链部分讨论它。

height 字段表示这个区块的序列号,新的区块被添加到区块链中时,高度会递增。

timestamp 字段表示创建这个区块时的 Unix 时间戳。所以它与你正在使用的本地机器有关。

而 hash 字段存储了这个区块的哈希值。我们会问:如何计算这个区块的哈希值?因为哈希字段是这个结构体的一部分,所以简单地序列化这个结构体是行不通的。我们需要在做计算时从其他字段中排除这个字段。所以算法看起来像这样:

fncalc_block_hash(height:u64,prev_hash:&str,timestamp:u64,body:&Vec)->String{

letconcated_str=vec![
height.to_string(),
prev_hash.to_string(),
timestamp.to_string(),
body.concat(),
]
.concat();

letmuthasher=Sha256::new();
hasher.update(concated_str.as_bytes());
hex::encode(hasher.finalize().as_slice())
}

我们不会教授如何编写 Rust 代码的细节,相反,我们主要会描述如何设计它的思路流程。

在这里,我们按照 height, prev_hash, timestamp, body 的顺序连接这个区块的元素。由于 body 是一个向量,我们应该首先连接它。一旦字符串连接完成,我们使用 Sha256 对其进行哈希计算。这一步创建了一个 32 字节的 u8 数组:[u8; 32]。然后我们使用 hex 将其编码为长度为 64 的字符串,这代表了这个区块的哈希。

你会注意到,我们将 prev_hash 值作为这个区块的哈希的来源之一。这非常重要,你可能会想知道我们为什么要这么做。

我们可以按照以下方式测试这个算法:

#[test]
fntest_block_hash(){
letblock1=Block::new(10,"aaabbbcccdddeeefff".to_string(),vec![]);
letblock2=Block::new(10,"aaabbbcccdddeeefff".to_string(),vec![]);
assert_eq!(block1.header.height,block2.header.height);
assert_eq!(block1.header.prev_hash,block2.header.prev_hash);
//XXX:havelittleprobabilitytofail
assert_eq!(block1.header.timestamp,block2.header.timestamp);
//XXX:havelittleprobabilitytofail
assert_eq!(block1.header.hash,block2.header.hash);

assert_eq!(block1.body,block2.body);
}

什么是链?你可以想象一条项链或者一条铁链。在我们的例子中,链是一个抽象的概念,每个区块都存储了前一个区块的哈希字段值。就这样,你看,没有复杂的地方。

在这个链中,每个区块只关心前一个区块,而不关心其他区块。所以它是一个相对简单的结构。

但我们即将遇到一个问题:第一个区块怎么办?它之前没有区块。

是的,对于这个边缘情况,我们需要为 hash 字段设置一个预定义的值。由于这个特殊情况,区块链的第一个区块通常被称为 创世(Genesis) 区块。

这就是整个区块链的样子:

b156688c-b8c3-11ee-8b88-92fbcf53809c.png

随着时间的推移,这个结构将无限扩展(或增长)。

区块链管理器

我们需要一个管理器来管理区块链。现在它非常简单,只包含一个区块的向量。

#[derive(Debug)]
structBlockChain{
blocks:Vec,
}

并在其上实现一些方法:

implBlockChain{
fnnew()->Self{
BlockChain{blocks:vec![]}
}

fngenesis()->Block{
lettxs=vec!["Thebigbrotheriswatchingyou.".to_string()];
Block::new(0,"1984,GeorgeOrwell".to_string(),txs)
}

fnadd_block(&mutself,block:Block){
self.blocks.push(block);
}
}

现在我们可以使用这个管理器来构建一个区块链:

fnmain(){

letmutblockchain=BlockChain::new();
letgenesis_block=BlockChain::genesis();
letprev_hash=genesis_block.header.hash.clone();
blockchain.add_block(genesis_block);

letb1=Block::new(1,prev_hash,vec![]);
letprev_hash=b1.header.hash.clone();
blockchain.add_block(b1);

letb2=Block::new(2,prev_hash,vec![]);
letprev_hash=b2.header.hash.clone();
blockchain.add_block(b2);

letb3=Block::new(3,prev_hash,vec![]);
letprev_hash=b3.header.hash.clone();
blockchain.add_block(b3);

letb4=Block::new(4,prev_hash,vec![]);
letprev_hash=b4.header.hash.clone();
blockchain.add_block(b4);

letb5=Block::new(5,prev_hash,vec![]);
//letprev_hash=b5.header.hash.clone();
blockchain.add_block(b5);

println!("{:#?}",blockchain);
}

它将打印出来类似下面的东西:

mike@alberta:~/works/blockchainworks/vintage$cargorun
Compilingvintagev0.1.0(/home/mike/works/blockchainworks/vintage)
Finisheddev[unoptimized+debuginfo]target(s)in0.22s
Running`target/debug/vintage`
BlockChain{
blocks:[
Block{
header:BlockHeader{
hash:"96cf34aa91e070ddf95eb9e0e8616b24e2f326c80d5fa9746e8dd8f0bec730d6",
height:0,
prev_hash:"1984,GeorgeOrwell",
timestamp:1705649594,
},
body:[
"Thebigbrotheriswatchingyou.",
],
},
Block{
header:BlockHeader{
hash:"0dd52ac54a9d621c47688f7920cd9eaee18ffe0cca3c83e124b8f78cef8999e5",
height:1,
prev_hash:"96cf34aa91e070ddf95eb9e0e8616b24e2f326c80d5fa9746e8dd8f0bec730d6",
timestamp:1705649594,
},
body:[],
},
Block{
header:BlockHeader{
hash:"61e95ab151cfa41c2a74cb076c33511ddf71f45dab0571f5f2db89df7ebc64cf",
height:2,
prev_hash:"0dd52ac54a9d621c47688f7920cd9eaee18ffe0cca3c83e124b8f78cef8999e5",
timestamp:1705649594,
},
body:[],
},
Block{
header:BlockHeader{
hash:"dde009c56c1b02d41fec8271e5f990e9b33c84a2cf044de6fc33e96605f90458",
height:3,
prev_hash:"61e95ab151cfa41c2a74cb076c33511ddf71f45dab0571f5f2db89df7ebc64cf",
timestamp:1705649594,
},
body:[],
},
Block{
header:BlockHeader{
hash:"f8cd3ab5f6ccc864515635878498e2e26b63b4fbf4dbc60ea3649e859b4a7d27",
height:4,
prev_hash:"dde009c56c1b02d41fec8271e5f990e9b33c84a2cf044de6fc33e96605f90458",
timestamp:1705649594,
},
body:[],
},
Block{
header:BlockHeader{
hash:"4415f4993729459f5ff07c7c963890f1d9210d5241f5203b9179c8d3db6e9dac",
height:5,
prev_hash:"f8cd3ab5f6ccc864515635878498e2e26b63b4fbf4dbc60ea3649e859b4a7d27",
timestamp:1705649594,
},
body:[],
},
],
}

我们做到了,它已经是一个区块链了。

如何持久化

到目前为止,我们只是将区块链保存在计算机的内存中,所以如果我们现在关闭计算机,区块链将一无所有。我们最好将整个链存储在我们的计算机上。

一般来说,人们会使用键值数据库(kv db)来存储区块链。为什么使用 kv db 而不是文件或 SQL db 呢?因为它简单且高效。

在我们的例子中,我们将使用 redb 作为我们的存储后端。根据其官方网站,Redb 是一个简单、便携、高性能、ACID、嵌入式键值存储,完全用 Rust 编写,并受到 lmdb 的启发。

接下来,我们需要设计一个存储模式。有以下几点:

使用两个表:blocks 表用于开发/生产模式,blocks_fortest 表用于测试模式;

每个区块都作为 redb 中的一个键值元素存储,其中键是区块的哈希字段值,值是区块的完全序列化字符串。

我们需要一个指针指向最后一个区块。'指向'实际上意味着持有区块的哈希值。我们可以使用这个指针从数据库中重构整个链(内存表示)。

我们还保持了 height 和区块的 hash 之间的映射关系。

然后我们需要将一个 db 实例注入到 BlockChain 管理器结构中。

#[derive(Debug)]
structBlockChain{
blocks:Vec,
db:Db,
}

基于这个管理器实例,我们可以按照以下方式实现一个持久化方法:

fnpersist_block_to_table(

&mutself,
table:TableDefinition<&str, &str>,
block:&Block,
)->Result<()>{
letheight=&block.header.height;
lethash=&block.header.hash;
letcontent=serde_json::to_string(&block)?;

//storehash->blockpair
self.db.write_block_table(table,&hash,&content)?;
//storeheight->hashpair
self.db
.write_block_table(table,&height.to_string(),&hash)?;
//storethelbp->hashpair(lastblockpointertohash)
self.db
.write_block_table(table,LAST_BLOCK_POINTER,&hash)?;

Ok(())
}

其中,我们使用 serde 框架并使用 serde_json 将整个 block 结构体序列化为字符串(json 格式)。如你所见,我们存储了 3 对键值对:

hash -> 序列化的区块字符串

height -> 区块哈希

lbp (最后一个区块的指针) -> 区块哈希

我们可以像这样从数据库中检索一个 Block:

fnretrieve_block_by_hash_from_table(

&self,
table:TableDefinition<&str, &str>,
hash:&str,
)->Result>{
letcontent=self.db.read_block_table(table,hash)?;
info!("{:?}",content);
ifletSome(content)=content{
letb:Block=serde_json::from_str(&content)?;
Ok(Some(b))
}else{
Ok(None)
}
}

我们使用 serde_json::from_str() 来反序列化原始字符串。

接下来,我们需要弄清楚如何从数据库中重新构建一个正确的区块链。我们可以使用一个迭代来做这个,首先获取最后一个区块,然后获取最后一个区块的前一个区块,依此类推。我们可以看看代码:

fnpopulate_from_db_table(&mutself,table:TableDefinition<&str, &str>)->Result<()>{

//findlastblockhashfromdb
letlast_block_hash=self.db.read_block_table(table,LAST_BLOCK_POINTER)?;
iflast_block_hash.is_none(){
returnOk(());
}
letlast_block_hash=last_block_hash.unwrap();

//retrievelastblock
letblock=self.retrieve_block_by_hash_from_table(table,&last_block_hash)?;
ifblock.is_none(){
returnOk(());
}
letblock=block.unwrap();
letmutprev_hash=block.header.prev_hash.clone();

letmutblocks:Vec=vec![block];
//iteratetooldblockesbyprev_hash
whileprev_hash!=GENESIS_PREV_HASH{
letblock=self.retrieve_block_by_hash_from_table(table,&prev_hash)?;
ifblock.is_none(){
returnOk(());
}
letblock=block.unwrap();
prev_hash=block.header.prev_hash.clone();

blocks.insert(0,block);
}

//contructaninstanceofblockchain
self.blocks=blocks;

Ok(())
}

我们可以像这样使用这个 API:

letmutblockchain=BlockChain::new();

blockchain
.populate_from_db()
.expect("errorwhenpopulatefromdb");

然后可以测试它:

#[test]
fntest_store_block_and_restore_block(){
letmutblockchain=BlockChain::new_to_table(TABLE_BLOCKS_FORTEST);

//initialization
letgenesis_block=BlockChain::genesis();
letprev_hash=genesis_block.header.hash.clone();
blockchain.add_block_to_table(TABLE_BLOCKS_FORTEST,genesis_block);

letb1=Block::new(1,prev_hash,vec![]);
letprev_hash=b1.header.hash.clone();
blockchain.add_block_to_table(TABLE_BLOCKS_FORTEST,b1);

letb2=Block::new(2,prev_hash,vec![]);
blockchain.add_block_to_table(TABLE_BLOCKS_FORTEST,b2);

letblock_vec=blockchain.blocks.clone();

blockchain
.populate_from_db_table(TABLE_BLOCKS_FORTEST)
.expect("errorwhenpopulatefromdb");

_=blockchain.db.drop_table(TABLE_BLOCKS_FORTEST);

for(i,block)inblock_vec.into_iter().enumerate(){
letblock_tmp=blockchain.blocks[i].clone();
assert_eq!(block,block_tmp);
}
}

在我们的代码中,使用 anyhow 来帮助管理各种错误,感谢这个漂亮的 crate,它使我们的生活更加轻松。

到目前为止,我们的区块链已经具有了持久化的能力,不再担心会丢失数据。我们已经到达了伟大征程的第一个里程碑。

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 源代码
    +关注

    关注

    94

    文章

    2927

    浏览量

    66063
  • 区块链
    +关注

    关注

    110

    文章

    15554

    浏览量

    104759
  • Rust
    +关注

    关注

    1

    文章

    223

    浏览量

    6387

原文标题:使用Rust从零开发区块链 01

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    社区看区块发展

    在其基础上创建社群与组织,构成了一个相对开放的的生态系统。 区块技术正是起源于这样的网络社区组织。2008年11月1日,一个自称中本聪(Satoshi Nakamoto)的人在一个隐秘的密码学讨论组上贴出
    发表于 01-04 13:32

    什么是区块 区块有什么用

    的分布式账本数据库,没有中心,数据存储的每个节点都会同步复制整个账本,信息透明难以篡改。  近几年,越来越多的机构开始重视并参与区块技术研发。最初的比特币、以太坊,到各种类型的区块
    发表于 03-26 11:31

    区块在商业方面的应用如何

    的步伐将提速,而随着应用范围金融向非金融领域加速扩展,区块将逐渐成为未来互联网的重要组成部分,为打造价值网络奠定重要基石。源中瑞平台主要针对的领域有产品购买、定制开发、需求发布这三
    发表于 07-14 11:31

    区块行业发展,金融领域应用方向?

    生产生活方式的改进,将成为驱动区块行业发展的动力源泉。区块技术发展至今已引起很多人关注,但区块
    发表于 08-06 17:34

    区块将改革供应

    团队携手研发出区块应用系统,它对于整个供应的权益有以下几点:1) 对于生产者:通过消费者对商品的购买数据以及售商的销售业绩来推动生产者的进步,通过生产者之间的竞争来推动发展,拓展
    发表于 08-08 11:11

    区块技术开发公司谈区块赚钱满足人哪些需求

    `  2018是区块技术发展的一年。各区块的企业和团队都建立了自己的区块项目。“天下熙熙,
    发表于 11-19 17:14

    区块软件开发公司谈区块在供应金融场景中的应用

    `<p>  区块技术开发公司:区块应用软件开发
    发表于 11-21 10:54

    区块软件开发公司谈未来区块的主要应用方向

    。在其他社交平台上总是会收到类似的广告窗口,因为区块应用程序的数据隐私是对垄断的大型数据平台的可耻销售。区块应用技术在社会领域的作用是将社会网络的控制
    发表于 11-22 16:54

    区块将如何优化产业

    )使用区块分布式记账的特点的运用开发,包括身份验证、证明、交易所、比特币、云存储等;  2)依据区块的去中心化系统
    发表于 12-13 15:19

    区块技术开发公司谈区块在酒业方面的应用

    假冒伪劣的行为,追查一切侵权行为的根源。利用区块技术,消费者不再能购买假酒,制造商可以以最小的成本解决假酒的问题。随着区块技术与实体经济的日益融合,我们可以共同
    发表于 12-14 11:41

    区块对我们的生活有什么影响

    今天的信息图表HIVE区块技术向我们展示,它让我们得以一窥区块技术在金融世界之外的潜力。区块
    发表于 07-10 04:20

    区块+全球50个案例看区块的应用与未来》高清pdf

    开发区块电商平台158第二节 区块农业存在的问题160第十章 区块医疗卫生163第一节 
    发表于 03-13 00:42

    区块钱包软件开发,区块钱包源码搭建

    区块钱包的钥匙谁也没法帮我们找回钱包。区块钱包软件开发,区块
    发表于 05-26 16:30

    什么是区块?比特币跟区块什么关系?

    什么是区块?比特币跟区块什么关系?有区块的实例吗?
    发表于 05-17 06:47

    什么是区块区块都有哪些应用?

    什么是区块区块未来的应用前景怎样?
    发表于 06-28 09:20