ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python实现最小区块链:数据结构、哈希与共识

Python实现最小区块链:数据结构、哈希与共识 简介区块链作为近年来备受关注的技术常让人感觉入门门槛高。这份PPT以“区块链入门知识了解”为线索面向零基础初学者用讲故事的方式把复杂概念讲明白。内容从2008年中本聪发表比特币白皮书、2009年创世区块诞生讲起系统介绍区块链的定义、技术层面与应用视角再通过菠萝村记账、区块打包、结转余额、竞聘记账先生等生动比喻逐步拆解区块、链、分布式账本、去中心化、共识机制等概念。资源共1个文件为PPTX演示文稿大小约4.43MB版式清晰、章节递进既适合个人自学也适合作为团队内部区块链科普分享的辅助材料。当前已有282人浏览学习打开即可按页阅读便于快速建立对区块链技术脉络和核心特点的整体认知。1. 为什么通俗易懂区块链难在把账本讲成一个可验证的动作有次我给团队做内部技术分享屏幕刚打出时间戳服务器这几个字底下做支付的同学直接反问这和带 WAL 的 MySQL 日志有什么区别这个问题点到要害上了。区别不在密码学而在谁有权往日志末尾追加。数据库是中心化的写入区块链是把记一页账的权利交给一群互不信任的节点去按规则争夺然后再用哈希把一页一页账硬性钉成一条链。这个差别一句话能说完但如果不用代码和实测去把它垫实听众很容易滑进去中心化不可篡改这类大词里听完依旧说不清账到底是怎么记的。这篇文章合并起来就讲三件事先用数据结构把区块链的链看穿再用不到 90 行 Python 跑通一条最小区块链最后把共识、交易、权限这几个绕不开的点讲到位。目标是让你能自己从 0 复现一遍然后把它装进一套 20 分钟讲得完的演示结构里。2. 先看穿区块链的数据结构区块、哈希与前一个区块的指纹一个区块链项目无论采用什么共识、用哪种底层存储地基都是区块这个数据结构。一个区块要能当账本的一页至少得装三类东西业务数据、自己在这一页的位置、以及指向前一页的连接。真实系统里还会有默克尔根、版本号、难度值、燃料限额等一大堆字段但先理解最小集就够了。2.1 区块的字段划分哪些参与哈希哪些只当展示最小集里一个区块只有六个字段。我习惯用一张表把这六项摆出来每次讲都要把是否参与哈希这一列顺着过一遍。字段类型参与哈希作用indexint是区块在链中的高度从 0 开始timestampfloat是出块时间使用节点本地时钟transactionslist是业务数据演示中用字符串数组即可previous_hashstr是前一个区块的哈希链式引用的核心nonceint是工作量证明的计数器挖矿时逐步递增hashstr否前五项序列化后计算出的 SHA-256 摘要存下来供下一个区块引用有人会问timestamp 为什么也要参与哈希如果节点篡改时间哈希就会变化后面所有区块的引用关系全部要重算同时两个候选块也就不用依赖网络时钟判断谁先出只需要看谁先满足难度条件、谁先被后续区块引用。注意真正的链顺序判定不靠 timestamp靠 previous_hash 的逐级传导不要被时间戳服务器这个说法带偏。hash 字段不参与自身的哈希计算否则形成自指循环。这是刚接触区块链时最容易绕晕的依赖关系。把这六项字段讲清楚再进入哈希的计算听众跟上就会轻松很多。2.2 修改一个字符指纹完全变样哈希的雪崩效应SHA-256 输出 64 个十六进制字符。输入里任意一个字符被改掉输出每一位都有接近 50% 的概率翻转结果看起来就是完全不同的字符串。这是篡改必留痕迹的数学来源也是演示里最容易制造冲击感的部分。import hashlib import json def block_hash(index, timestamp, txs, previous_hash, nonce): 把区块的关键字段编码成 JSON再取 SHA-256 指纹。 payload { index: index, timestamp: timestamp, txs: txs, previous_hash: previous_hash, nonce: nonce, } raw json.dumps(payload, sort_keysTrue).encode(utf-8) return hashlib.sha256(raw).hexdigest() txs_original [alice - bob: 10] txs_tampered [alice - bob: 999] h1 block_hash(1, 1700000000, txs_original, 0 * 64, 0) h2 block_hash(1, 1700000000, txs_tampered, 0 * 64, 0) print(h1) print(h2) print(same?:, h1 h2) # False逻辑说明我把区块字段先转成 dict 再做 JSON 序列化而不是直接拼字符串是为了解决跨节点复现哈希时字段顺序不确定的问题。sort_keysTrue 保证任何语言、任何进程对同一段内容都能算出同一个 JSON 文本。nonce 现在传 0它参与哈希但暂时不影响结果真正让它发挥作用要等第 3 章的挖矿过程。把前两个 print 的完整输出放进 PPT让观众看到两串完全不同的 64 位字符。这一页的核心价值在于给不可篡改提供可验证的语义不是有人承诺不改而是任何人重算一遍立刻发现不一样。2.3 为什么用哈希引用而非链表指针传统链表用内存地址作为前驱指针地址只在单进程内有效。区块链网络中每个节点各自保存一份全量副本内存地址没有任何可比性哈希是内容寻址previous_hash 在任何机器上重算结果都一致。所以它成了跨节点的全局引用也让分叉成为可能不同节点可以各自在自己的高度上挖出合法块任何合法块都会被其他节点验证最终由共识规则决定保留哪一支。还要纠正一个常见偏差区块链的不可篡改不是改不掉而是修改后能被任何全节点迅速发现且修复成本指数上升。比如你改了第 100 块里的一笔交易这个块的哈希就变了后面 100 个块的 previous_hash 全部失效它们的 nonce 全都要重新挖。链越长、难度越高恢复合法链的成本越高。生产环境里还要乘上全网投入的算力所以业务上常说六个确认后才安全演示环境里没有全网算力这个概念表格或代码里能直接体会的是改一个字符串校验立刻失败。实际代码里previous_hash 不需要等于本区块的 index 减一只要内容是前一个合法块的哈希即可。这种设计天然对分叉开放两条不同高度的合法候选链只要各自哈希链完整都能通过校验。真正决定保留哪支的规则在共识层不在数据结构层。把链层和共识层分开想后面理解 PoW 和最长链规则就不容易混。3. 从 0 开始搭最小链用不到 90 行 Python 跑通一条区块链最小可运行的区块链不需要网络、不需要数字签名、也不需要持久化存储只需要三部分区块结构、工作量证明、链完整性的校验。下面这段代码把三者写完存成 mini_chain.py直接python mini_chain.py就能跑。3.1 完整代码Block、Blockchain 与 is_valid 校验import hashlib import json import time DIFFICULTY 4 # 要求哈希前导 4 个十六进制零 def sha256_of(block_payload) - str: JSON 稳定编码后计算 SHA-256返回 64 位 hex 字符串。 text json.dumps(block_payload, sort_keysTrue, separators(,, :)) return hashlib.sha256(text.encode(utf-8)).hexdigest() class Block: def __init__(self, index, transactions, previous_hash, difficultyDIFFICULTY): self.index index self.timestamp time.time() self.transactions transactions self.previous_hash previous_hash self.nonce 0 self.hash self.mine(difficulty) def mine(self, difficulty): 循环增大 nonce直到哈希前导 zero 数量满足难度。 prefix 0 * difficulty while True: digest sha256_of({ index: self.index, timestamp: self.timestamp, txs: self.transactions, previous_hash: self.previous_hash, nonce: self.nonce, }) if digest.startswith(prefix): return digest self.nonce 1 class MinimalChain: def __init__(self, difficultyDIFFICULTY): self.chain [] self.difficulty difficulty genesis Block(0, [genesis], 0 * 64, difficulty) self.chain.append(genesis) property def last_block(self): return self.chain[-1] def add_block(self, transactions): block Block( len(self.chain), transactions, self.last_block.hash, self.difficulty, ) self.chain.append(block) return block def is_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: return False recomputed sha256_of({ index: current.index, timestamp: current.timestamp, txs: current.transactions, previous_hash: current.previous_hash, nonce: current.nonce, }) if recomputed ! current.hash: return False return True逻辑说明mine 是核心循环每次 nonce 加 1 都会产生一组完全不同的哈希一旦找到前缀满足条件的 digest 就停并把它存进 self.hash。创世块没有前驱用 64 个字符的 0 做占位哈希具体值全网统一即可。is_valid 从第 1 个区块开始对每一块做两件事验证 previous_hash 是否指向前一块以及用当前字段重算哈希是否与存储值一致。任一条件失败整条链都视为无效。参数说明difficulty 决定前导零个数数值越大合法哈希越难找。演示环境建议 4平均要尝试 16 的四次方也就是 65536 次普通笔记本上大概 0.1 到 0.5 秒既有等待感又不拖沓调试阶段可以改成 1 或 2 提速想让观众直观感受算力成本就临时调到 5。3.2 跑一遍看输出合法链、篡改链、难度变化给 mini_chain.py 补一段入口再运行if __name__ __main__: chain MinimalChain(difficulty4) chain.add_block([alice - bob: 10, bob - carol: 5]) chain.add_block([carol - dave: 2]) for b in chain.chain: print(f#{b.index} hash{b.hash[:16]} nonce{b.nonce}) print(valid before tamper:, chain.is_valid()) # 篡改第一个业务区块里的交易金额 chain.chain[1].transactions [alice - bob: 999] print(valid after tamper:, chain.is_valid())输出结果是下面这种结构哈希和 nonce 每次运行会因为时间戳和随机搜索而变化但前导零个数和 True/False 顺序不变#0 hash0000f8a1c3e9... nonce18321 #1 hash0000b4de7f1a... nonce942 #2 hash0000aa91e4c2... nonce510 valid before tamper: True valid after tamper: False逻辑说明篡改前为 True篡改后为 False原因是第 1 个业务块的 transactions 变化导致重算哈希与存储的 self.hash 不一致。这里只改了一个数字就破坏了整条链的合法性。演示时可以马上追问听众哪个字段对不上答出 previous_hash 或 hash 都说明听进去了。想观察链式引用失效可以把篡改对象换成第 0 块修改chain.chain[0].transactions后跑is_valid()这次是从第 1 块起就返回 False因为第 0 块哈希已经变化而第 1 块仍指着旧值。两处对比能直观区分自身哈希被改和前向引用断掉这两种失效模式。3.3 参数语义nonce 的作用区间与回溯成本工作量证明的含义是要生成一个合法块必须暴力搜索满足前缀条件的 nonce。每多一个前导零平均搜索空间扩大 16 倍。难度前导零个数平均尝试次数本地演示体感2256瞬时34096毫秒级4655360.1~0.5 秒51048576约 2~5 秒61677721620~60 秒不建议现场做回溯成本则更好理解。假设攻击者要修改第 k 块除了重新搜索第 k 块的 nonce还会因为它的 previous_hash 变化导致 k1、k2 直到链尾所有块全部得重新去挖。如果目标块后面已经跟了 6 个区块篡改这一个块等于连续挖 7 次满足难度的合法哈希。所以不可篡改在生产里更准确的表述是篡改成本随难度和链长指数上升。提示演示前把难度抽成环境变量在文件头部补一行import os然后把初始化写成chain MinimalChain(difficultyint(os.getenv(DIFF, 4)))。现场想拖一下时长就设 DIFF5想让流程快一点就设回 4不用改动业务代码。4. 把谁来记账变成共识规则共识、交易与权限到第 3 章为止你只是在单机进程里操作一条链。它告诉你账怎么记但没告诉你听谁的。真实环境里一群互不信任的节点共同维护同一条账本首先要回答谁有资格往末尾追加新区块追加之后大家凭什么接受共识算法就是这套规则。4.1 四类共识的取舍以及演示里为什么优先选 PoW把主流共识粗分成四类一张表就能把各自瓶颈说清楚。共识模型一句话解释主要瓶颈常见适用场景PoW工作量证明先算出符合难度哈希的节点获得记账权算力和能源开销无需许可的公链、技术演示PoS权益证明质押资产越大的节点获得记账权概率越高资金沉淀成本与可能的马太效应追求节能的公开链PBFT/HotStuff 类节点相互通信达到 2/3 多数后才记账消息量随节点数平方增长联盟链、私有链Raft 类选出一个 Leader其余节点跟随复制Leader 单点与选举期间短暂不可用企业内网的区块链实验我一般推荐演示场景用 PoW因为它的成本度量最简单就是 CPU 循环次数。观众能从 nonce 的递增直观理解记账节点在做一件可验证的苦力活。PBFT 和 Raft 有复杂的通信协议细节20 分钟演示里很难讲透。你给全栈团队做内部培训时把 PoW 讲清楚后再用这张表点一下另外三种模型即可。4.2 交易从提出到上链mempool、打包、验证与回滚一条交易要进入链典型路径分四步客户端把签名后的交易广播到 P2P 网络每个节点先放进自己的内存池mempool。获得记账权的节点从内存池挑交易打成一个候选区块执行 PoW 寻找合法 nonce。候选块广播出去其他节点逐笔验证签名、余额、双花通过后才链接到本地链。如果同一高度出现两个合法块按分支规则选一条例如累计难度更大的支链获胜落选支链里的交易退回内存池。代码可以简化成一段模型演示暂存区到区块的流转class Mempool: def __init__(self): self.pool [] def submit(self, tx): # 生产环境这里还应该有签名验证、余额检查和交易去重 self.pool.append(tx) def take(self, size5): batch self.pool[:size] self.pool self.pool[size:] return batch mempool Mempool() mempool.submit({from: alice, to: bob, amount: 10}) mempool.submit({from: carol, to: dave, amount: 3}) txs mempool.take(5) print(txs) # 取走的交易进入候选区块逻辑说明这段代码把内存池抽象成一个先进先出列表submit 只做入队take 决定一次打包几笔。真实系统里内存池要按手续费排序、处理孤儿交易以及拒绝同一笔资产被重复使用。演示时用它讲一个概念就够了节点只对已经进链的交易负责仍在内存池里的交易随时可能被替换或丢弃。4.3 五个高频翻车点演示现场最容易暴露的问题用 index 当顺序依据index 只是节点本地自增序号。分叉出现时同一个高度会有多个候选块真正顺序由 previous_hash 传导和分支规则决定index 只说明发生在同一高度。优先用 timestamp 排序节点时钟各不相同两个块的本地时间差几秒很正常。要判断谁先被网络接纳只能看后续块对它的引用链timestamp 更适合做展示和调试不适合参与正确性判断。把不可篡改理解成无法删除旧块不能被静默改写但可以通过新块把旧状态标记作废。链上保留历史痕迹这恰恰是审计价值的来源。双花检测不是链的职责链本身只记录交易顺序双花是否成立由账户模型判断。UTXO 模式要求每个输入只能被消费一次账户余额模式要求余额充足演示用字符串数组不涉及这个检查要跟听众说明这个缺失。手续费既是激励也是反垃圾机制高手续费的交易会被优先打包零手续费的演示链没有防护灌几万条垃圾交易就能把内存池塞满。在做平台化方案时这笔账必须重新设计。5. .pptx 演示向把信任讲成业务方能现场验证的几分钟最后这部分落到通俗易懂的最终载体也就是标题里的 .pptx。你要维护的不只是一条代码链还要设计一次信息传达。我的做法是只做三张关键页外加一次现场破坏性演示总时长控制在 20 分钟以内。5.1 中心化日志 vs 区块链账本一张对照表把差异说清对比维度中心化日志区块链账本谁能写服务端或管理员满足共识条件的记账节点如何定序服务端自增日志序号previous_hash 链式引用加工作量证明篡改发现依赖审计和备份不内建重算任意区块哈希即可发现故障影响单点停机整体不可写多数节点在线即继续进块性能可轻松上万 TPS受共识机制限制通常远低于日志写入讲法上只强调前两行一个中心服务器说了算一个按规则争夺记账权。后三行留给问答环节不要一口气读完。业务团队对性能数字很敏感提前讲容易把讨论带偏到那价格能不能用 Kafka 替代。5.2 现场破坏把交易改成 999让校验当场变 False这一步是固定要放的视觉锤。先把代码完整跑一次is_valid 输出 True然后现场修改 transactions 里的金额改成 999再运行 is_valid 输出 False。切忌提前把结果截图放进 PPT观众看到是准备好的脚本信任感立刻减半。具体操作建议把 mini_chain.py 复制一份到演示目录不要动原始文件。打开终端先跑一次确认 valid before tamper: True。用编辑器去掉chain.chain[1].transactions [alice - bob: 999]这一行的注释。再跑一次让 False 出现后停三秒别急着解释先问听众哪个字段对不上。这一步几乎不用讲代码观众已经在之前 True之后 False的对比里感受到 previous_hash 与 hash 的绑定关系。如果现场有网可以临时把 DIFFICULTY 调成 5让出块明显变慢再切回 4省得费口舌解释难度参数。5.3 三个边界演示结束前必须说清的限制这套最小链没有 P2P 广播只有一个进程区块链的分布式并没有真正发生。没有签名和账户模型交易里的 from/to 只是字符串不代表任何真实资产的转移。PoW 只解决谁来记账、如何认同的问题不解决这笔交易是否合法双花检查必须由应用层完成。最后可以留 5 分钟练习让听众拿着第 3 章代码保持 DIFFICULTY4先后做三次改动——改一笔交易的金额、把两个相邻区块调换顺序、删掉创世块之后的所有块。三组实验做完能准确说出是 previous_hash 断了的人才算真正理解了哈希引用这条链的底线。本文还有配套的精品资源点击获取
返回列表