作者用 Rust 重写其玩具区块链项目
来源标题:Rewriting my toy blockchain in Rust (2022)
作者在 Recurse Center 的 mini batch 期间,将原本用 Python 写的玩具区块链移植到了 Rust。项目最初包含工作量证明、Merkle 树和椭圆曲线加密,原计划一周完成,但最终改为编译到 WebAssembly,以便在浏览器中进行挖矿。
以下正文由 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 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。