返回资讯中心
外部精选
软件工程
#开发者实时流

作者用 Rust 重写其玩具区块链项目

来源标题:Rewriting my toy blockchain in Rust (2022)

作者在 Recurse Center 的 mini batch 期间,将原本用 Python 写的玩具区块链移植到了 Rust。项目最初包含工作量证明、Merkle 树和椭圆曲线加密,原计划一周完成,但最终改为编译到 WebAssembly,以便在浏览器中进行挖矿。

Hacker News 最新kurinikku2026/11/15 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

作者注:Hacker News 评论见此处。

2022 年伊始,我参加了 Recurse Center 的 mini batch。2020 年秋天我参加过一个完整的 batch,和全职做自己 side project 的人待在一起,感觉是开启新一年的有趣方式。

我的计划是把我的玩具区块链从 Python 移植到 Rust(仓库见此处,文章见此处)。最初的项目经过多次迭代,从 proof-of-work 开始,后来加入了 Merkle trees 和椭圆曲线密码学——很适合作为一周重写的候选项目。

和很多 RC 项目一样(至少对我来说是这样),事情很少按计划发展。我曾在 Rust 编译器上困惑过一阵,之后转而考虑做速度优化,但最终却编译到 WebAssembly,让浏览器里也能挖矿。

我觉得一个不错的收尾,是能把 mini batch 的项目和我之前在 RC 做过的工作连接起来。

就我们的目的而言,区块链是一个分布式数据库,记录被划分成“区块”,区块组成“链”。

每个区块由 header(存放区块属性)和 body(存放记录)组成。

header 的哈希用于标识每个区块(通常称为区块哈希),并被包含在下一个区块的 header 中(因此成链)。

初始化 header 的 Python 函数相当直接——简单地把 int 转成 bytes,然后拼接。注意,除 nonce 之外的所有属性都是预先确定的。

VERSION: int = 0 def init_header(previous_hash: bytes, timestamp: int, nonce: int) -> bytes: """Initialize header.""" return ( VERSION.to_bytes(1, byteorder="big")

  • previous_hash
  • timestamp.to_bytes(4, byteorder="big")
  • nonce.to_bytes(32, byteorder="big") ) Python

nonce 的选取要使得区块哈希的前导字节为零。我们要求的零越多,找到 nonce 就越困难。在下面的例子中,我们要求前两个字节为零,也就是十六进制下的四个前导零。前十个整数还达不到要求。

previous_hash = (0).to_bytes(32, byteorder="big") timestamp = 1634700000 def sha256_2x(header: bytes) -> bytes: """Apply SHA-256 twice.""" return hashlib.sha256(hashlib.sha256(header).digest()).hexdigest() for nonce in range(10): header = init_header(previous_hash, timestamp, nonce) print(f"nonce: {nonce}, hash: {sha256_2x(header)}") Python

nonce: 0, hash: 489dbdf5fd580fdc0fc372546a83467e1001718409f00c962590727fba21cf2a nonce: 1, hash: 584b740dee7dd5b18d906338040e90a735ce2e4c89034e756960de16d8ad6635 nonce: 2, hash: 6501e86d4d5fb1fcd51e48e396c88ef0bf38c64d7981c5d9d05b048c588434b3 nonce: 3, hash: adf71f94f264f496b2b344c8c9afa9aa177b85261342130f1034cdc66165375b nonce: 4, hash: a2a302d40611fb0806fc4e623bbf744212ebefacefd5e6d54f7cfe27b7fc637c nonce: 5, hash: 65c80fd5c4d9f87ebbaab9da9a908615f73966019dc70eca7c3a7db6a21bf986 nonce: 6, hash: f03aba3135591c0ef0b042bfdfc095102e8008401dddcac122f227267739b1c9 nonce: 7, hash: cd4ebbe1ceceae965262f5e8037912fd100a10fe10bfea0465209df97a4a82fd nonce: 8, hash: 4d81346ceea40e57411c4d1dc5468a9ad675853ca87e666d0d81bb05bef5a88b nonce: 9, hash: e1b0f93e7d5c889a250b719840598847de66b85585e9b409b5683e96c7ac6765 nonce = 70822 header = init_header(previous_hash, timestamp, nonce) print(f"nonce: {nonce}, hash: {sha256_2x(header)}") Python

nonce: 70822, hash: 00001ff58495c3dc2a1aaa69ebca4e9b3e05e63e5a319fb73bcdccbcdbba1e72

把它写成 Python 函数,就是一个循环,逐步遍历可能的 nonce 值,当所需的前导字节为零时返回。

def run_proof_of_work(previous_hash: bytes, timestamp: int) -> Tuple[int, bytes]: """Find nonce that results in leading zero bytes in the block header.""" nonce = 0 while True: header = init_header(previous_hash, timestamp, nonce) guess = hashlib.sha256(hashlib.sha256(header).digest()) if guess.hexdigest()[:4] == "0000": return nonce, guess.digest() nonce += 1 Python

别逞英雄

遵循 Andrej Karpathy 关于使用最佳实践的建议,我在 Stack Overflow 上查了如何在 Rust 中拼接 bytes。我找到了下面这个帖子。

当我摆弄 .concat() 想看看 bytes 是被移动还是被复制时,遇到了取决于我拼接的是两个引用还是一个引用的反直觉行为。这样编译没问题(所有代码都在 main 中运行)。

let a: &mut [u8] = &mut [0xdeu8, 0xadu8]; let b: &[u8] = &[0xbeu8, 0xefu8]; let c: Vec<u8> = [a, b].concat(); println!("a before: {:x?}", a); println!("c before: {:x?}\n", c); a[0] = 0x00u8; println!("a after : {:x?}", a); println!("c after : {:x?}", c); Rust

a before: [de, ad] c before: [de, ad, be, ef] a after : [0, ad] c after : [de, ad, be, ef]

然而当我只有一个引用时,编译器就不高兴了。

let a: &mut [u8] = &mut [0xdeu8, 0xadu8]; let c: Vec<u8> = [a].concat(); println!("a before: {:x?}", a); println!("c before: {:x?}\n", c); a[0] = 0x00u8; println!("a after : {:x?}", a); println!("c after : {:x?}", c); Rust

error[E0382]: use of moved value: a --> src/main.rs:92:1 | 86 | let a: &mut [u8] = &mut [0xdeu8, 0xadu8]; | - move occurs because a has type &mut [u8], which does not implement the Copy trait 87 | let c: Vec<u8> = [a].concat(); | - value moved here ... 92 | a[0] = 0x00u8; | ^^^^ value used here after move For more information about this error, try rustc --explain E0382.

我查了文档,但找到的线索很少。RC 的酷之处在于你可以问 RC 的 Rust 专家。我在 RC Zulip 的 #rust 频道提了这个问题,得到了 Jesse Luehrs 和 Mikkel Paulson 非常有帮助的回复。

基本思想是,编译器不允许复制可变引用,因为同一份数据不能有多个可变引用。另一方面,复制不可变引用是没问题的。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →