从零搭建最小区块链:Python实现哈希、工作量证明与链校验
发布时间:2026/10/17 11:33:50 锦皓数字建站

我至今记得第一次亲手搭出一个能跑的区块链时的心情那感觉有点像看着自己拼的乐高终于按图纸转了起来。别误会我不是让你去造一台矿机也不是让你去研究什么交易策略而是想建议每个想学区块链的新手都能用半天时间从零构建一个最小可运行的区块链原型。当你亲手把「区块」「哈希」「工作量证明」「链校验」这几个名词写进代码后很多网上说得神乎其神的概念都会变得具体起来。这篇文章就是我自己的思路、代码和踩坑记录适合完全没写过区块链、但有最基础编程经验的读者。1. 动手写代码前先想清楚这条链到底要做什么很多人一上来就搜《比特币白皮书》《以太坊黄皮书》结果在梅克尔树、椭圆曲线签名、虚拟机这些术语里越陷越深。我的建议是反过来先别追求完整先想清楚一个最小区块链要解决什么核心问题然后用最简单的代码把它们体现出来。1.1 区块链解决的三个原始问题第一个问题是数据不可篡改。传统数据库里管理员可以改记录或者拿到权限的人可以直接UPDATE一行数据。区块链用“哈希链接”让每个区块都指着前一个区块想改中间任何一个历史块后面所有块都要跟着改而且全网节点都能发现异常。第二个问题是记账权怎么分配。既然大家都能记一笔凭什么你记的账别人要认最早期的方案就是工作量证明你要先花成本算出符合条件的哈希才有资格把新区块广播出去。这个成本是用算力和电力换的不是嘴上说说。第三个问题是账本怎么保持一致。一个去中心系统里每个人手里都有一份账本副本难免出现分叉或不同步。于是大家约定只认最长的链。这条准则看起来简单实际上就是所有共识协议的地基。1.2 最小原型的边界先做一个诚实的单机节点我在第一版里故意没有碰P2P网络也没有做数字签名甚至没有做真正意义上的钱包地址。为什么因为对一个新手来说单机版已经足够理解“区块哈希工作量证明”这条主线了。P2P是让这条链“跑”起来的东西但如果你连链本身都没有搭好谈网络同步就是空中楼阁。所以我建议你第一步只做一个诚实节点的本地账本。所谓诚实就是默认我们写的代码不会故意作恶只是按照规则生成区块、验证区块。等单机版本跑通了再考虑多节点、广播、冲突解决路会顺很多。1.3 技术选型为什么用Python选Python并不是因为它性能最好。恰恰相反它性能一般但对新手来说可读性极高而且标准库就带了hashlib和json不需要装任何第三方依赖。用Python写出来的代码每一行都接近自然语言你能一眼看出“这个字段是前一个区块的哈希”“这个循环是在找符合条件的nonce”。我记得某技术社区有人用C写了一个迷你区块链代码里全是指针和内存管理虽然性能好但新手很容易被底层细节带偏。等你用Python把概念完全理解透了再去看C或Go的工程实现是加分项。2. 区块的数据结构一个“自带防篡改标记”的容器区块链里的“区块”本质上就是一个数据结构。你可以把它想成一个带封条的快递盒盒子里装着交易数据封条上的编号由盒子内容计算出来。如果有人拆开盒子改了内容封条编号必然对不上。2.1 一个最小区块需要哪些字段我在第一版里只用了五个字段index区块在链上的序号一般创世区块是0timestamp区块生成时间用Unix时间戳transactions交易列表先随便放字符串后面再结构化previous_hash前一个区块的完整哈希这是形成链条的关键nonce工作量证明用的随机数后面细说还有一个字段叫hash它是由前面所有字段联合计算出来的当前区块哈希。这里有一个很重要的设计决策hash不能由用户随便赋值它必须永远等于“用当前字段重新计算出的值”否则整个验证逻辑就崩了。2.2 用Python实现Block类import hashlib import json import time class Block: def __init__(self, index, transactions, previous_hash, nonce0): self.index index self.transactions transactions self.timestamp time.time() self.previous_hash previous_hash self.nonce nonce self.hash self.compute_hash() def compute_hash(self): block_string json.dumps({ index: self.index, transactions: self.transactions, timestamp: self.timestamp, previous_hash: self.previous_hash, nonce: self.nonce }, sort_keysTrue).encode() return hashlib.sha256(block_string).hexdigest()注意json.dumps里的sort_keysTrue这非常关键。如果字典的键顺序不稳定同一份数据可能生成不同的字符串哈希也就对不上。我在第一次调试时没加这个参数Python 3.6之前字典还是无序的同一个区块竟然算出了两个哈希排查了半天才发现是序列化顺序问题。2.3 哈希字段为什么是“自我引用”很多新手会问self.hash计算的时候并没有包含self.hash那这个哈希到底是不是区块的一部分答案是哈希是“结果”不是“输入”。它就像一个人的身份证号是由出生日期、姓名、地点等信息算出来的标识而不是这些信息的一部分。这样就会形成一个有趣的约束区块里任何字段发生变化compute_hash()的结果就会立刻不同。所以当我说“一个区块是自校验的”意思就是“它的完整性由它自己的内容决定不需要依赖外部数据库”。3. 哈希链接验证整条链完整性的核心逻辑区块本身有了自校验能力但一条链光靠单个区块还不够。你必须把后一个区块的previous_hash指向前一个区块的hash这样所有区块才形成一个“一荣俱荣、一损俱损”的链条。3.1 哈希链接的生活类比想象一条拉链每个齿都咬合上一个齿。如果有人想改第三块布的颜色那他必须同时改第三块布上的齿形状还要改第四块布上的咬合口再改第五块、第六块……改到最后整条拉链都要重新做。哈希链接比这个更强。因为previous_hash是“前一个区块的全部内容”的指纹所以哪怕你只把第二个区块里的一个字符串从“hello”改成“hello!”第三个区块里记录的previous_hash就会失效。这就像你改了昨天账本里的数字今天账本封面的指纹立刻对不上一样。3.2 链的校验算法有了区块类和哈希链接我们还需要一个方法来从头到尾验证整条链是否有效。我写了一个很朴素的版本class Blockchain: def __init__(self, difficulty4): self.chain [] self.difficulty difficulty self.pending_transactions [] self.create_genesis_block() def create_genesis_block(self): genesis_block Block(0, [], 0) genesis_block self.mine_block(genesis_block) self.chain.append(genesis_block) def mine_block(self, block): target 0 * self.difficulty while not block.hash.startswith(target): block.nonce 1 block.hash block.compute_hash() return block def is_chain_valid(self): for i in range(1, len(self.chain)): current self.chain[i] previous self.chain[i - 1] if current.previous_hash ! previous.hash: print(f区块 {i} 的 previous_hash 与前一区块哈希不一致) return False if current.hash ! current.compute_hash(): print(f区块 {i} 的内容被修改哈希不匹配) return False target 0 * self.difficulty if not current.hash.startswith(target): print(f区块 {i} 没有满足工作量证明要求) return False return True这里最需要理解的环节是循环里的第一个判断current.previous_hash ! previous.hash。它把“链条是否连续”这个问题简化成了“前后两个字符串是否相等”的问题。3.3 改一个区块会发生什么你可以自己做个实验在测试代码里先把第二个区块的交易列表改掉然后调用完整校验。这时你会得到两个错误被改区块自身的哈希和重新计算的结果对不上它后面所有区块的previous_hash也都对不上也就是说篡改不能“只改一个地方”。每改一个区块你就必须重新计算从那个位置到链尾的所有区块而且每个新区块都要再次满足工作量证明条件。这可比改一行的数据库记录难多了。4. 工作量证明挖矿不是碰运气是可控的成本很多新手第一次听到“挖矿”时第一反应是“靠运气撞大运”。其实工作量证明的数学逻辑非常朴素找一个字符串让它的SHA-256哈希值以若干个0开头。这个计算过程没有捷径只能逐个试但验证结果却非常快。4.1 为什么区块不是想加就能加如果没有工作量证明任何人都可以毫秒级生成大量区块把账本撑爆或者故意构造一条超长链来覆盖其他人的记录。所以必须给“生成区块”这件事加上成本。成本越高攻击者越难通过伪造大量区块来操纵系统。在比特币场景里这个成本就是算力在学术和工程模拟里这个成本就是CPU时间。不管哪种目的都是一样的让“记账权”变得稀缺让诚实节点有时间对账。4.2 难度与目标字符串难度是一个整数一般用difficulty表示。如果difficulty 4目标就是让哈希的前4个字符全是0。由于SHA-256哈希值是十六进制字符串每一位有16种可能所以单次尝试满足条件的概率是 1/16^4也就是大约 1/65536。这意味着平均要试六万多次才能找到一个符合条件的nonce。难度每增加1找到合法哈希的期望尝试次数就变成原来的16倍成本也是16倍。这是一个指数增长的过程也是“工作量”的真实含义。4.3 proof_of_work实现与演示我上一节代码里已经有mine_block方法了核心逻辑就是循环递增 nonce 并重新计算哈希def mine_block(self, block): target 0 * self.difficulty while not block.hash.startswith(target): block.nonce 1 block.hash block.compute_hash() return block有个细节新手很容易忽略block.nonce 1改变了区块内容所以哈希必须重新调用compute_hash()否则循环永远跳不出去。很多第一次实现的人会卡在这里敲了半天发现 nonce 一直在涨哈希却纹丝不动。我在测试difficulty4时挖一个空区块大概是几万次计算在我电脑上要卡一两秒。如果你用的是老旧笔记本调到5可能就要等几十秒这恰恰能让你直观感受到难度增长的恐怖。4.4 关于难度调整的一点补充真实世界的区块链并不是固定难度而是每隔一段时间根据全网算力动态调整。目标很简单让出块时间保持在一个稳定区间。比如如果你期望每10分钟出一个块但实测平均只用了5分钟那就说明难度太低要把难度调高。项目原型阶段不需要做这么复杂的调参。但你要明白难度不是随意设置的它应该和“你的实验环境能承受的计算量”匹配。调到太大你写测试时每次都会卡住调到太小又看不到计算的成本感。新手阶段我建议从4开始。5. 交易与余额把最小闭环补完整到这一步我们已经有了一个能挖矿、能校验的单机链。但如果你让它处理真实业务会发现它只支持“随便塞字符串”这连最小账本都算不上。接下来要做的是让区块内部长出自洽的交易结构。5.1 交易结构从字符串到字段最简单的交易至少要有发送方、接收方、金额三个字段。我经常用四个字段from付款方地址to收款方地址amount转账金额signature签名先占位真实系统里地址是公钥的哈希签名由私钥生成。但新手项目如果逼着自己实现非对称加密代码复杂度会瞬间上来。我更推荐你第一版先用普通字符串“alice”“bob”当地址签名留空等主链路跑通之后再补RS256或者椭圆曲线签名。def add_transaction(self, from_addr, to_addr, amount): # 第一版不做签名验证只做余额检查 if self.get_balance(from_addr) amount: print(余额不足交易被拒绝) return False self.pending_transactions.append({ from: from_addr, to: to_addr, amount: amount, }) return True5.2 余额检查与UTXO的伏笔余额怎么算最直接的想法是扫描整条链上所有和from_addr相关的交易把收到的加总把发出的减掉。如果你默认系统初始给每个人发了一笔钱那还得在创世区块里写好初始分配。def get_balance(self, address): balance 0 for block in self.chain: for tx in block.transactions: if tx[from] address: balance - tx[amount] if tx[to] address: balance tx[amount] return balance这种“根据历史交易汇总余额”的方式非常直观但真实区块链里为了效率往往采用UTXO模型。简单理解UTXO不存“我账户有多少钱”而是存“我还拥有哪些未花费的产出”。你付钱时不是“余额减一”而是“把我手里的几个未花费产出标记为已花并生成新的产出”。新手第一次能跑通“余额遍历”就够了。UTXO是性能优化也是防止双花的技术细节放在单机项目里学有点超前。5.3 新交易如何打包进新区块有了pending_transactions之后挖矿方法就不能只做一个空区块。正确的步骤是把你本地缓存的所有待处理交易复制出来创建一个新区块transactions字段填这些交易新区块的previous_hash指向当前链最后一个区块的哈希执行工作量证明校验通过后把区块追加到链尾清空待处理交易这样每一笔交易都要经过“打包进区块 挖矿 链校验”才算真正被账本确认。如果你只是把交易放在内存里没有写进区块那它随时可能丢这在项目里是很严重的隐患。6. 从单机链到多节点网络共识才是区块链的灵魂单机版跑通后你可能会产生一个错觉这不就是一个带哈希校验的List吗别急着下结论真正让区块链从“特殊数据结构”变成“区块链”的是多节点之间的共识机制。6.1 为什么只有一条链不算区块链人的记忆是靠对比纠错的。如果全世界只有一个账本物理机一坏账本原地蒸发。更重要的是如果只有一个记账者他完全可以偷偷修改旧记录而没有旁观者发现。所以分布式是必要条件。每个节点都保存一份完整的账本副本任何节点对账本的修改都必须通过广播让其他节点知道。你说的“链”只有在“多个无关的机器都认可同一条链”这个前提下才配叫区块链。6.2 P2P同步与最长链规则最简单的多节点同步可以这样做每个节点启动一个HTTP接口允许其他节点拉取自己的链当一个节点挖出新区块就把区块广播给所有已知节点收到区块的节点先验证再决定要不要替换本地链。这里有个经典规则也是最容易被新手误解的出现分叉时各节点不会比对谁“更像官方”而是直接选总工作量最大的链。深度上就体现为最长链。为什么最长链就一定可信因为它经过了最多的哈希计算作恶者要追上这条链必须付出高于全网的支持算力这在大型网络里几乎不可能。6.3 共识不是投票是矿工遵守的协议新手经常把共识理解成“大家投票表决”。实际上大多数公链的共识并没有“投票”这么有仪式感它更像“大家默认遵守同一套出块和校验规则”。区块是否合法由规则决定而不是由社区表态决定。具体到代码层面共识可以精简成三条约定所有节点使用同样的区块结构所有节点用同样的方式验证哈希和难度所有节点在出现冲突时选择最长链只要这三条一致哪怕节点彼此讨厌也能保持账本最终一致。这就是共识的奇妙之处它不要求大家“互相信任”只要求大家“遵守同一套校验规则”。7. 新手最常见的四个坑以及我的三个调试习惯最后分享一些我自己踩过的坑。这些东西在教科书和源码里不容易看到但对新手来说非常关键。7.1 坑一误以为区块链就是普通链表链表强调的是“地址顺序”区块链强调的则是“哈希引用”。普通链表里你把某个节点的指针改了后面的节点还能找到区块链里只要你改了一个区块的内容后面所有区块的校验立即失败。这不是一个可选的加固而是不可分割的设计。我见过有人把区块类写成了类似“自定义Node”的结构里面没有previous_hash只靠一个next指针连接。这种结构就算哈希算得再漂亮也只是一个封装过的链表不是区块链。7.2 坑二把哈希当成加密哈希和加密的本质区别在于加密是可逆的哈希是不可逆的。SHA-256就像指纹采集器输入任意数据都能得到一个固定长度的摘要但你几乎不可能从摘要反推出原始数据。这个区别很重要因为很多新手会以为“哈希越复杂就是加密越安全”。实际上密码学里加密的目标是保密性哈希的目标是完整性。你在区块链里需要的是“别人改了内容你立刻知道”而不是“把内容藏起来不让人看到”所以核心用的是哈希而不是加密。7.3 坑三创世区块没有做特殊处理创世区块是整条链的祖先它的previous_hash没有真实的前一区块。我见过两个处理方式直接设置previous_hash 0最简单我推荐设置成一个随机字符串比如genesis也行最怕的是有人把创世区块的previous_hash设置成空字符串然后在compute_hash()里序列化时出现了两个不同的结果。规范做法就是固定写一个非空字符串并且把它写进生成逻辑里不要靠输入参数去猜。7.4 坑四难度设置太高或太低我在测试时一开始把难度设为6结果每次挖矿要等一分钟测试节奏全被打乱后来又改成0所有区块都瞬间生成完全感受不到工作量证明的作用。新区块建议从4开始如果你的电脑比较弱可以降到3。核心原则是既要让你感受到“挖矿需要等待”又不要让每次测试都卡到让人想放弃。等你确认整条链路逻辑无误后再临时调高标准去测试难度对出块时间的影响。7.5 三个调试习惯如果你也卡在某个报错上不妨试试我的三个习惯它们帮我节省了大量时间。第一个习惯是打印区块哈希和nonce变化。挖矿循环跑很久没结束时看一眼nonce是不是在涨、哈希的前缀有没有变化能立刻判断出是难度太大还是代码死循环。第二个习惯是单独写一个verify_hash()函数只比较“当前字段重新计算出来的哈希”和“区块上保存的哈希”。这个函数越简单越好因为它是所有校验的基础。万一整条链验证失败你先把验证函数单独测一遍能排除掉逻辑帮凶。第三个习惯是每次改完数据结构都重新生成创世区块和整条链而不是复用旧的持久化数据。区块结构一变旧哈希自然全旧失效没有最糟糕的痛苦是面对一堆过期哈希还以为是新代码出了bug。我已经用这套方法带着几个完全没接触过区块链的朋友跑通过最小原型。他们最后都会感叹原来一直觉得“好高级、好神秘”的东西拆开之后也就是“哈希加链表加成本控制”的组合。接下来你想往哪个方向深入就要看你的应用场景了。如果你对智能合约或者权限控制感兴趣我后面可以再单独写一篇从共识层往上搭建的完整思路。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。