news 2026/9/9 20:22:23

用Python手写一个最小区块链:从哈希到工作量证明实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python手写一个最小区块链:从哈希到工作量证明实战

1. 为什么用Python写区块链:先搞懂几个核心概念

1.1 区块链到底是个什么东西

以前跟朋友聊起区块链,大部分人的第一反应就是比特币、炒币、挖矿,后来变成NFT、Web3,好像这个东西离普通开发者特别远。其实剥掉那些金融外壳,区块链的内核就是一个非常朴素的数据结构:一条由哈希值串联起来的链表。

什么叫"由哈希串联"?你可以把区块链想象成一串珍珠项链,每颗珍珠是一个区块(Block),里面的数据是交易记录或者任意业务数据,而串起珍珠的那根线就是哈希值。每个区块里都存着前一个区块的哈希值,只要任何一个区块的数据被改动,它的哈希就会变,后面所有区块的"线"就都对不上了,整个链条立刻露出破绽。这个设计听起来简单,但正是这个"一环扣一环"的结构,让链上的数据一旦写入就很难篡改。

我在开头100字里说的"实战",就是想带着你用Python从零把这条链搭出来。不依赖任何区块链框架,不炒币,就是用纯Python实现一个可以跑起来的、结构完整的最小区块链系统。这个项目特别适合三类人:刚学完Python基础、想找个综合性练手项目的同学;工作中需要理解区块链原理但一直没时间深入的后端开发者;以及要在简历上放一个"够实在"的项目、又不想只会调库的求职者。

1.2 为什么选择Python来做这个实战

说实话,真要搞生产级的区块链系统,Go、Rust、C++这些语言更常见,因为它们对并发和性能的掌控力更强。但我们的目标是"理解原理",Python反而是最合适的教学语言,原因有三个。

第一,Python的语法足够直白。区块链核心逻辑其实就是"类 + 哈希函数 + 循环",这些概念在Python里表达起来几乎没有噪音。你不需要跟指针、内存管理搏斗,可以把全部注意力放在"链是怎么连起来的""工作量证明是怎么算出来的"这些本质问题上。

第二,Python的hashlib库内置了SHA-256等一整套哈希算法,标准库直接调用,不用额外装任何加密相关的第三方包。哈希是区块链的基石,Python把这一步做得极其简单,比如hashlib.sha256(data).hexdigest()一行就能拿到哈希值。

第三,Python的生态能让你快速把原型变成可演示的东西。我们后面会用一个轻量的Flask框架给区块链套上HTTP接口,跑起来之后用浏览器或者Postman就能直接调用,非常直观。如果换成C++,光搭HTTP服务就够折腾半天的。

2. 环境准备与项目结构设计

2.1 开发环境的选择与配置

写Python区块链的代码,其实对开发环境要求很低。我建议你满足这几个基本条件就够了:

  • Python 3.8 及以上版本(我用的是3.10,3.8以上的版本都可以)
  • 一个趁手的代码编辑器(VS Code、PyCharm、甚至直接用IDLE都行)
  • 如果要跑Web接口部分,需要装Flask:pip install flask

这里特别提醒一点,很多新手在装Python的时候容易踩坑:安装那一步一定要勾选"Add Python to PATH"这个选项,不然你在命令行里敲python会提示找不到命令。如果已经装完才发现没勾选,也别重新装,手动把Python的安装目录加到系统环境变量里就行。具体路径一般长这样:C:\Users\你的用户名\AppData\Local\Programs\Python\Python310\

另外强烈建议你新建一个独立的虚拟环境来放这个项目,别直接往全局Python里装包。虚拟环境的好处是隔离依赖,你以后在这个项目里装了什么包、什么版本都是可复现的,不会跟其他项目互相污染。创建方式很简单:

python -m venv blockchain_env

Windows下激活是blockchain_env\Scripts\activate,Linux和macOS下是source blockchain_env/bin/activate。激活之后命令行前面会多出(blockchain_env)前缀,说明你已经在这个虚拟环境里了。

2.2 项目结构怎么搭

虽然我们只是做一个教学项目,但代码结构还是要清晰一点,不然写着写着就成一坨了。我用的是最简单、也最容易扩展的划分方式:

blockchain_demo/ ├── block.py # 区块类 ├── blockchain.py # 区块链类(核心逻辑) ├── server.py # Flask Web接口 └── test_demo.py # 本地测试脚本(可选)

把区块类和区块链类分开,是因为职责不一样:区块类只负责"我是一个区块,我存什么数据,我怎么算自己的哈希";区块链类负责"我这条链怎么连接区块、怎么校验、怎么挖矿"。后面如果要做智能合约、要加P2P网络,这个结构也能比较平滑地扩展下去。

很多教程喜欢把全部代码塞进一个文件里,为了演示方便无可厚非,但我个人不建议你在自己的项目里这么干。真出了bug排查起来会很痛苦,因为你根本分不清是哪个环节出了问题。从第一个项目开始就养成模块化习惯,后面收益极大。

3. 核心代码实现:从区块到链

3.1 实现区块(Block)类

区块是整个链上的最小单元。一个标准的区块需要包含哪些字段?我梳理了一下,至少要有这五个:

字段含义
index区块在链上的序号,从0开始
timestamp区块生成的时间戳
transactions区块里承载的业务数据,这里就是交易记录
previous_hash前一个区块的哈希值,这是"链"的物理连接点
nonce随机数,工作量证明的核心变量,后面细说

把思路落到代码上,区块类长这样:

import hashlib import json import time class Block: def __init__(self, index, timestamp, transactions, previous_hash, nonce=0): self.index = index self.timestamp = timestamp self.transactions = transactions self.previous_hash = previous_hash self.nonce = nonce self.hash = self.compute_hash() def compute_hash(self): block_string = json.dumps({ "index": self.index, "timestamp": self.timestamp, "transactions": self.transactions, "previous_hash": self.previous_hash, "nonce": self.nonce }, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest()

这里有两个细节我要着重讲一下,因为它们是新手最容易踩坑的地方。

第一个细节是json.dumps()里的sort_keys=True。这个参数会对字典的键进行排序,保证序列化出来的字符串是确定性的。Python的字典在3.7之后虽然是有序的,但如果你不排序,不同环境下键的顺序可能出现差异,导致同样的区块内容算出不同的哈希。哈希函数要求"同样的输入必有同样的输出",所以这一步必须加上。

第二个细节是数据要先.encode()成字节串再传给sha256()hashlib.sha256()接收的是bytes类型,不是str类型。直接传字符串会报错,这是Python哈希操作最常见的坑之一。把字典变成JSON字符串、再转成bytes,这一步调用链看起来很啰嗦,但每一步都有它存在的理由。

3.2 实现区块链(Blockchain)类

有了区块类,接下来就是区块链类。区块链类要做的事情有这几件:创建创世区块(Genesis Block)、获取最后一个区块、记录待打包的交易、校验链的完整性。

先看代码:

class Blockchain: def __init__(self): self.chain = [] self.pending_transactions = [] self.difficulty = 4 self.create_genesis_block() def create_genesis_block(self): genesis_block = Block(0, time.time(), [], "0") genesis_block.hash = genesis_block.compute_hash() self.chain.append(genesis_block) @property def last_block(self): return self.chain[-1] def add_transaction(self, sender, receiver, amount): self.pending_transactions.append({ "sender": sender, "receiver": receiver, "amount": amount })

创世区块是整条链的第一个区块,它没有前一个区块,所以previous_hash用一个特殊值"0"来表示。从数据结构的角度看,创世区块就像链表里的头节点,是整个链的锚点。它的index是0,timestamp是创建时刻,transactions一般是空的(有些实现会塞一条创始交易进去,但教学项目没必要)。

pending_transactions是一个待处理交易池。你可能会问:为什么不直接写进区块?这就是区块链的关键设计思路了——交易先进入一个"等待区",等到矿工(也就是我们的挖矿程序)计算出满足条件的工作量证明,才把等待区里的所有交易打包进一个新区块,然后挂到链上。这个过程跟现实世界很相似:你提交的转账请求不是立刻生效的,而是要等下一个区块被挖出来,你的交易才会被确认。

3.3 哈希计算与链的完整性校验

哈希是整个区块链的信任基石,所以我把它的原理单独拿出来讲透。

SHA-256是一个单向哈希函数,它有一个非常重要的特性:输入的微小变化会导致输出的巨大变化(雪崩效应)。举个直观的例子,"Hello World"和"Hello World!"这两个字符串只差一个感叹号,但它们的SHA-256哈希值完全不同。这意味着什么?意味着只要有人改了区块里的任何一个字段,哪怕只是交易金额里的小数点后移了一位,这个区块的哈希就会面目全非。

那区块链是怎么利用这个特性来防篡改的?再看一遍区块类的previous_hash字段——每个区块都引用了前一个区块的哈希。链上第N个区块的哈希变了,第N+1个区块里存储的previous_hash就跟它对不上了,校验的时候就会报错。而且这种"不匹配"会一路向后传导,篡改越是往链头方向,需要重算的区块就越多。这就是为什么说"链越长,越安全"。

校验整条链完整性的方法:

def is_chain_valid(self, chain): for i in range(1, len(chain)): current = chain[i] previous = chain[i - 1] if current.previous_hash != previous.hash: print(f"区块 {current.index} 的前置哈希不匹配") return False if current.hash != current.compute_hash(): print(f"区块 {current.index} 的哈希被篡改") return False return True

这段代码做了两件事:首先检查当前区块记录的previous_hash是否真的等于前一个区块的哈希;然后重新计算当前区块的哈希,看它是否等于当前区块自己记录的哈希。两步都通过,才说明从这个区块开始往前都是可信的。

实际跑测试的时候你会发现,计算机校验一条几百个区块的链只需要几毫秒,这就是哈希算法的效率优势。但如果真的有人想把整条链改成对自己有利的样子,他需要把改动点之后的所有区块全部重新计算一遍,在难度足够高的情况下,这个计算量是天文数字。这就是区块链"计算上不可篡改"的真正含义。

4. 工作量证明(PoW)机制的实现

4.1 什么是工作量证明

如果说哈希校验是区块链的"防盗门",那工作量证明(Proof of Work,PoW)就是区块链的"造币厂"和"安全护栏"。它解决的问题是:谁来打包新区块?打包的人凭什么不能作弊?

PoW的思路很朴素:你想往链上添加一个新的区块,可以,但你必须先做一道数学题——找到一个特定的数字,让整个区块的哈希值满足某种条件。这个数字没有巧算方法,只能靠暴力穷举去试,试一次的成本虽然不高,但试几万次、几百万次,就有实实在在的计算成本了。这个过程就是大家常说的"挖矿"。

在我们这个小项目里,"找到特定数字"的条件我设置为:区块的哈希值必须以前N个0开头。N就是难度(difficulty)。N=4意味着哈希值前面要有4个0,比如0000a1f2...这样。如果仅仅是随便写个数据就能挂上链,那任何人都能以零成本制造垃圾区块,整个链就废了。PoW的存在让每个新区块都"来之不易",恶意者想搞乱这条链,必须付出巨大的算力代价。

4.2 一个简单但完整的PoW实现

代码实现其实就围绕着一个变量展开:nonce(随机数)。挖矿的过程就是不断改变nonce的值,重新计算哈希,直到找到满足条件的nonce。

import time class Blockchain: # 接上面3.2的代码... def proof_of_work(self, block): block.nonce = 0 computed_hash = block.compute_hash() while not computed_hash.startswith('0' * self.difficulty): block.nonce += 1 computed_hash = block.compute_hash() return computed_hash def add_block(self, block, proof): previous_hash = self.last_block.hash if previous_hash != block.previous_hash: return False if not self.is_valid_proof(block, proof): return False block.hash = proof self.chain.append(block) return True def is_valid_proof(self, block, block_hash): return (block_hash.startswith('0' * self.difficulty) and block_hash == block.compute_hash())

这段代码逻辑很直观:初始化nonce为0,算出哈希,看是否以4个0开头,不是就nonce加1再来一次,直到满足条件为止。

我实测了一下,在difficulty=4的情况下,找一个有效nonce大概需要几万次计算,耗时通常在不到1秒,体感上"刷"一下就出来了。如果把难度调到5,耗时会直接翻好几倍,可能就需要几秒了。这里就引出了PoW的一个核心思想:难度决定了出块速度。难度越高,找到有效nonce的平均计算次数越大,出块时间越长。

把挖矿和打包整合起来的完整流程:

def mine(self): if not self.pending_transactions: return None last_block = self.last_block new_block = Block( index=last_block.index + 1, timestamp=time.time(), transactions=self.pending_transactions, previous_hash=last_block.hash ) proof = self.proof_of_work(new_block) self.add_block(new_block, proof) self.pending_transactions = [] return new_block

这里有一个很容易被忽略的业务细节:挖矿成功后,pending_transactions要被清空。如果不清空,下一轮挖矿的时候这批交易会被重复打包,整个账本数据就乱了。这个清理动作一定要放在区块成功入链之后再执行,顺序不能反。如果你在挖矿失败时就把交易池清空了,那些没进链的交易就会凭空消失,这也是个隐蔽的bug。

5. 让区块链可交互:交易记录与Web接口

5.1 交易的数据结构设计

到目前为止,我们的区块链还只是一串结构好看但没人用的数据。为了让这个项目"活"起来,我给它加两层能力:第一层是交易(Transaction),第二层是HTTP接口。

交易的设计可以很简单,在区块链领域,一笔交易通常至少包含三个要素:付款方、收款方、金额。我们的add_transaction方法已经支持了:

blockchain.add_transaction("Alice", "Bob", 50) blockchain.add_transaction("Bob", "Charlie", 25)

这种简易交易在真实系统中远远不够。真实的加密货币交易还会包含数字签名(证明你确实拥有这笔钱)、输入输出(UTXO模型)或者账户余额与nonce(账户模型)、手续费等大量字段。但在教学项目里,我刻意把交易简化成"字典"结构,为的就是让你先看到主干,再补细节。

你可能会问:交易里要不要加个ID和时间戳?我建议加上,因为真实系统里的交易是不可变的,需要一个唯一标识来索引和查询。可以用uuid.uuid4().hex生成一个UUID作为交易ID,也可以再加个timestamp字段记录交易发起时间。这些都是低成本的小改动,但对项目的完整度提升很大。

5.2 用Flask给区块链套上HTTP接口

光在命令行里跑python脚本虽然能说明白原理,但不够性感。我习惯给这种教学项目加上一个HTTP层,让整个系统可以通过接口来操作和查看,这也是它从"数据结构demo"向"系统"迈进的关键一步。

我们选择Flask来实现,因为它是Python社区最轻量的Web框架之一,三五行代码就能起一个服务:

from flask import Flask, jsonify, request from blockchain import Blockchain app = Flask(__name__) blockchain = Blockchain() @app.route('/chain', methods=['GET']) def get_chain(): chain_data = [ { "index": block.index, "timestamp": block.timestamp, "transactions": block.transactions, "previous_hash": block.previous_hash, "nonce": block.nonce, "hash": block.hash } for block in blockchain.chain ] return jsonify({"length": len(chain_data), "chain": chain_data}) @app.route('/transaction', methods=['POST']) def add_transaction(): data = request.get_json() required = ["sender", "receiver", "amount"] if not all(key in data for key in required): return "交易信息不完整", 400 blockchain.add_transaction(data["sender"], data["receiver"], data["amount"]) return "交易已加入待处理池", 201 @app.route('/mine', methods=['GET']) def mine_block(): block = blockchain.mine() if block is None: return "没有待打包的交易", 400 return jsonify({ "message": "新区块挖矿成功", "index": block.index, "hash": block.hash, "transactions": block.transactions }), 200 if __name__ == '__main__': app.run(host='127.0.0.1', port=5000)

启动服务之后,你就可以用浏览器或者Postman完整地体验一遍"区块链运转"的流程了:

  1. 先用GET /chain查看当前链,这时候链上只有一个创世区块。
  2. POST /transaction提交几笔交易,比如Alice转给Bob 50,Bob转给Charlie 25。
  3. GET /mine触发挖矿,系统会把待处理交易打包进一个新区块。
  4. GET /chain,链上多了一个区块,里面装着刚才提交的交易。

整个流程走一遍,你就对"交易进池→挖矿打包→上链"有了身体记忆,这比看一百遍概念图都管用。

有一个细节我想提醒你:上面这段接口代码里,/mine用的是GET方法,这在语义上其实不太严谨。GET请求应该是无副作用的,而挖矿会改变区块链的状态,严格来说应该用POST。我在这里用GET纯粹是为了方便你用浏览器直接访问测试。真正写项目的时候,建议把挖矿改成POST,同时加上简单的身份认证。

6. 常见问题与排查技巧实录

6.1 两个最常见的"哈希对不上"问题

我在网上答疑的时候,这个项目被问得最多的就是两个问题。第一个是"改完区块数据后重新计算哈希,结果跟前一个区块的previous_hash还是对不上"。这个问题的根源通常是:你在修改第N个区块的数据后,只更新了它自己的hash,但第N+1个区块里存的previous_hash还是旧值。记住,篡改检测是连锁反应,必须顺着链一路往后把所有依赖它的区块都重新计算。这也是大多数教学项目里is_chain_valid方法存在的意义。

第二个是"同样的代码,为什么我算出来的哈希跟教程里不一样"。这个大概率是json.dumps()里的sort_keys没加,或者字典的键顺序在不同运行时不一样导致的。我前面也强调过,哈希函数对输入是敏感的,输入只要差一个空格,输出就天差地别。所以序列化区块数据时,务必保证字段顺序是确定性的。

6.2 难度与性能的平衡

很多同学在改难度的时候会有个困惑:难度设了多少,挖矿时间怎么预估?

这里有个粗略的计算方法。SHA-256哈希值的每一位都是十六进制字符,范围是0到f共16种可能。要哈希以1个0开头,概率是1/16;以4个0开头,概率就是(1/16)^4,也就是1/65536。也就是说,平均而言你需要尝试65536次哈希运算才能找到一个有效的nonce。加上哈希里有大量字符"恰好落在0到f之间"的分布是均匀的,这个估算在工程上是比较准的。

我实测了不同难度的表现:

难度平均尝试次数完成时间(我的笔记本)
3约4096次瞬间完成
4约65536次不到1秒
5约104万次几秒
6约1677万次几十秒到几分钟

所以在教学环境里,难度建议设在3到4之间,既能体现"挖矿有成本",又不至于等太久导致失去耐心。要是你的电脑比较老旧,难度4也可能等得心焦,可以降到3。

6.3 数据一致性与并发隐患

这个项目写到后面,如果你再深入一点,还会遇到一个真实系统必须面对的问题:并发和数据一致性。

比如Flask默认的多线程模式下,如果两个请求同时到达,一个在挖矿、另一个在提交交易,有可能出现pending_transactions被并发修改的情况,导致这次挖到的区块内容不完整,或者交易被重复打包。虽然Flask内置的开发服务器并发能力很弱,这类问题在小并发下不一定复现,但意识一定要有。

解决思路也很经典:加锁。Python标准库的threading.Lock就可以,在mine()add_transaction()这两个方法内部、修改共享状态的关键代码段上锁,保证同一时刻只有一个线程在操作链和交易池。真实区块链系统用的是更复杂的共识机制来处理这个问题(比如最长链原则、分叉选择等),但在我们这个小项目里,一把锁就足够了。把这个改进做进去,你的项目在面试官那里会很加分,因为它说明你不仅写得出功能,还思考过真实系统的问题。

6.4 几个容易被忽略的调试技巧

最后分享几个调试技巧,都是我踩过坑换来的经验。

第一,把区块的完整哈希输入到在线SHA-256计算器里验证一下,确认你的代码没有算错。如果计算结果对不上,优先检查序列化的格式和自己是否漏了字段。

第二,测试链的完整性时,故意篡改一个区块的数据再重新校验,看能不能成功检测出来。如果检测不出来,问题多半出在is_chain_valid里只比对了部分字段,或者哈希根本没有包含被篡改的那个字段。

第三,加日志。我在proof_of_work循环里每隔10000次打印一下当前的nonce和哈希值,用来观察挖矿进度。虽然正式项目里不会这么做(太消耗IO),但调试阶段能帮你确认代码是不是真的卡死了。

第四,善用Python的printrepr。哈希字符串很容易出现不可见字符的格式问题(虽然hexdigest不会),遇到"看起来一样但实际不相等"的情况,直接打印两者的repr结果对比,一眼就能看出差别。

我在实际写这个项目的过程中最大的体会就是:区块链远看是一堆高大上的术语,走近了才发现核心就是"哈希+链表+共识"这三板斧。这三板斧拎清楚了,后面再去看智能合约、分布式网络、加密签名这些概念,都能很快找到它们在系统里的位置。把上面这些代码完整敲一遍、跑通一遍,你对区块链的认知深度会远超那些只刷概念的人。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 20:22:11

GWO、DBO、DOA算法在光伏参数辨识中的对比与Matlab实现

光伏参数辨识模型对比:GWO、DBO、DOA三种算法的Matlab实现与实战复盘光伏组件标称参数和实际运行参数对不上,这个问题做光伏系统仿真的人应该都深有体会。厂家数据手册里给的I-V曲线是在标准测试条件(STC)下测的,温度和…

作者头像 李华
网站建设 2026/9/9 20:20:01

SR-IOV虚拟化网络性能优化:从原理到实战的完整指南

如果你搞过云计算、NFV,或者只是在自己的服务器上折腾过KVM虚拟化,大概率见过这个词——SR-IOV。网上讲它的文章不少,但多数要么浮在概念层,只告诉你“这玩意儿能让虚拟机的网络性能逼近物理机”,要么直接甩一篇驱动编…

作者头像 李华
网站建设 2026/9/9 20:19:01

SpringBoot+Vue智慧养老中心管理系统实战开发指南

前阵子有个做养老信息化项目的朋友跟我聊起他们公司的系统架构,我脑子里第一时间冒出来的,就是SpringBootVue这套组合。说实话,近几年我接触到的智慧园区、智慧社区、医院信息管理类项目,十有八九都是这个技术栈,智慧养…

作者头像 李华
网站建设 2026/9/9 20:18:56

Node.js+Vue3健身房会员卡拼团系统完整设计与实战

我做过不少管理系统类的项目,但这套“健身房会员卡 拼团”组合的 nodejsvue3 项目,算是近一年里投入精力比较多的一个。不是因为它技术难度有多高,而是业务逻辑比想象中琐碎:会员卡要管购卡、续费、过期、冻结、转卡,…

作者头像 李华
网站建设 2026/9/9 20:17:27

【计算机JAVA毕业设计案例】依托轻量化SpringBoot框架与Vue技术的职工健康监测信息化系统搭建 企业职工健康监护数字化管理系统的设计与实现(程序+文档+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华