区块链(05):同一笔支付在 UTXO 与账户账本里的两种状态迁移
买家拥有面额为 100 的一份合成资产,要向卖家支付 70,手续费 1,剩余 29 归买家。UTXO 模型要解释“旧输出被消费,新输出怎样生成”;账户模型要解释“余额和 nonce 如何变化”。两种表示都要保证没有凭空产生价值,但拒绝重复支出的检查点不同。先修 区块链(01):两份签名都正确,账本仍会冲突 的双签冲突,以及 区块链(03):签名证明授权了哪条消息 的授权边界。 先固定不变量,再看两种状态 合成单位都是整数,不讨论小数位。模型假定输入已被正确授权,但没有验证签名、脚本、gas 或协议费用规则。对 UTXO,初始状态只有 ("synthetic-coin", 0) -> (buyer, 100);交易引用该输出,生成 (seller, 70) 与 (buyer, 29),旧输出从未花费集合删除,手续费为 100 - 70 - 29 = 1。找零是一个新的可花费输出,不是将原输出的余额改成 29。 flowchart LR U[未消费输出 buyer:100] -->|消费同一 outpoint| T[交易] T --> S...
区块链(04):Merkle 包含证明需要可信根
收到一段订单交付凭证的“包含路径”,校验器可以不用下载全部订单就复算树根。但如果根本身来自攻击者,校验成功只是说“攻击者的树确实放了这条凭证”。这与“订单有效、付款最终确认、货物真实交付”是三个不同问题。 先修:区块链(02):哈希之前,先说清究竟签了哪些字节 的长度编码与域分离;对上一层哈希如何绑定两段字节有基本概念。模型固定三个合成叶 order-0、order-1、order-2;叶哈希用 leaf 标签,内部节点用 node 标签,奇数层末叶复制,最终根再绑定叶数量。这些是教学规则,并非 Bitcoin 的 double-SHA256 Merkle 规则。 从叶子走到根 flowchart BT L0[H leaf order-0] --> A[H node L0 L1] L1[H leaf order-1] --> A L2[H leaf order-2] --> B[H node L2 L2] B --> R[H tree 叶数3 与 H node A B] A --> R 证明最后一个叶的包含,需要提供它在每层的兄弟...
区块链(03):签名证明授权了哪条消息
02 篇解决了“哪组字段变成哪串字节”。有了字节,签名才能有确定对象;有了签名,仍不能推出线下身份、输入未花费或业务已交付。01 篇的两笔冲突意图正好检验这条边界:同一私钥可以对两个收款方分别签字。 先修:区块链(02):哈希之前,先说清究竟签了哪些字节 中的确定性字节编码;懂得摘要不是身份。系统模型是隔离的本机 OpenSSL 3.0.13,私钥由工具生成在自动清除的临时目录,公钥从私钥导出。这里没有钱包、链、RPC 或可绑定现实用户的身份证明。 验证的是签名字节,不是交易结果 sequenceDiagram participant A as 签名方(临时私钥) participant V as 校验方(公钥) A->>A: encode(教学域,订单,收款方,输入,金额) A->>V: 消息与 ECDSA-SHA256 签名 V->>V: 针对这一条消息验签 V-->>A: Verified OK / 失败 Note over V: 尚未查询账本、订单或交付凭证 实验先生成 secp256k1 密钥和...
区块链(02):哈希之前,先说清究竟签了哪些字节
把订单标识与收款人直接拼接再哈希,看起来已经“承诺”了一笔支付:b"ab" + b"c" == b"a" + b"bc"。左右两组输入完全不同,却产生相同的字节串;再强的哈希也无法替调用者找回字段边界。00 篇的重试指纹和 01 篇的签名意图都依赖这一层编码。 先修:区块链(00):从中心化订单基线开始 的字节自测,以及 区块链(01):两份签名都正确,账本仍会冲突 的签名意图。这里使用 Python 标准库 SHA-256 与自行定义的教学编码;不宣称编码兼容 Bitcoin 或 Ethereum。 把类型、边界与用途放进预映像 有限模型只接受 bytes,每个字段先写 4 字节大端长度,再写字段本身。于是: 12encode(ab, c) = 00000002 6162 00000001 63encode(a, bc) = 00000001 61 00000002 6263 固定消息的向量:SHA256(b"abc") = ba7816bf8f01cfea414140de5...
区块链(01):两份签名都正确,账本仍会冲突
00 篇里的数据库负责所有订单的顺序。去掉这个唯一权威,买家用同一可花费输入签发两笔意图:第一笔向卖家支付 70,第二笔向攻击者支付 70。两条消息在本地 secp256k1 密钥下分别得到合法的 ECDSA 签名。验签成功不意味着两笔都能入同一本账。 先修:区块链(00):从中心化订单基线开始 的事务/重试,理解“相同输入”指代同一份可消费状态。此处消息是人为编码的 教学域 + 订单号 + 收款方 + 输入标识 + 金额,不是 Bitcoin 原始交易或其签名预映像。 四个问题,四个检查点 flowchart LR I[两笔冲突意图] --> A[授权: 各自验签] A --> V[有效性: 输入尚未消费] V --> O[排序: 先应用哪一笔] O --> H[共同历史: 重组和确认] S[可任意造账号] -->|不能按账号简单计票| O 授权回答“是否由这把私钥签署了这些字节”,并不回答“签署者只签过一笔”。状态检查回答“针对当前状态,这个输入可否使用”;两笔分别面对同一个旧状态时都可能通过,但顺序执行后只能有一笔消费输入。...
区块链(00):从中心化订单基线开始
一笔订单的“已完成”究竟由谁宣布?先不建链。把合成订单 synthetic-001(面额 70)保存在单台数据库,支付、交付凭证和结算分开记。假设卖家已提交交付凭证;服务收到结算请求,把订单从 DELIVERED 变成 SETTLED,同时给卖家记 70。若数据库提交成功而响应在网络中丢失,客户端会重试。结算次数与收到响应的次数并不相同。 这一篇的先修是字节、哈希表、SQL 事务和 HTTP 重试。不熟悉字节编码可先做下面的自测,不必先掌握密码学。旧文思考区块链和学习区块链的基础资料保留为历史问题入口;这里的目标是把“需要链吗”变成可以检验的工程问题,而不是替旧文重写结论。 模型:谁说了算 单台 SQLite 是唯一排序者和记账权威。服务、数据库与客户端在同一管理域;客户端可超时和重复请求,不允许服务信任客户端宣称“上次一定失败”。安全目标是一个订单只结算一次,活性目标是在数据库可用且请求持续重试时返回持久化后的结果。数据库损坏、管理员篡改、跨机构不共享权威、现实交付真伪不属于这段测试的保证。 sequenceDiagram participant C as 客户端 pa...
Java EE 企业应用 11:服务端审批页面怎样处理提交与渲染
页面显示“Approved”,申请就已经批准了吗 采购审批页展示单据 ID 与租户,操作员按下批准按钮。页面可以呈现成功文字,也可以在校验失败时展示消息;但页面字符串不是数据库提交记录。更危险的是,浏览器可修改隐藏字段、重放视图状态,甚至伪造租户值。在第 24–26 篇落实身份与对象授权之前,审批页只能在本机隔离实验中调用已有业务用例,不能对公网开放。当前工程确有最小 approval.xhtml 与 LabApprovalPage.java,且现有 scenarios/11-faces.sh 已在 Liberty、GlassFish 上提交顺序审批与重复审批,独立 SQL 读回 APPROVED|2;证据见 verification/20261005T-glassfish-scenarios/。这不覆盖正式身份及全部校验分支。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 本章使用 Jakarta Faces 4.1、Servlet 6.1、Jakarta EE 11 与既有 Pos...
Java EE 企业应用 23:业务写入与通知任务怎样避免双写缺口
订单提交成功,通知任务还没发出去 采购订单落库后,系统要通知采购员和供应商。先提交数据库再发送消息,进程可能恰在两步之间崩溃:订单存在,没有待发送任务。先发消息再提交订单也不对:数据库回滚了,消费者却可能处理了不存在的订单。单库事务不能包住外部网络副作用。要消除的是“业务已经提交,但没有任何可恢复的通知意图”这个双写缺口,而不是声称数据库与消息系统拥有一个神奇的共同提交点。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 累计工程的 ProcurementUseCases.order 在一个受管事务中写订单、将申请转为 ORDERED 并通过 JdbcRequestStore.enqueueOrderCreated 写 notification_outbox;PostgreSQL 16.15 的建表脚本是 examples/javaee-enterprise/db/migrations/002-notification-outbox.sql。这里全部是 JDBC SQL;若换成 JPA ...
Java EE 企业应用 22:请求重试怎样避免重复创建采购订单
超时不告诉客户端订单有没有落库 审批后的采购申请发起下单;服务器提交了订单,却在 HTTP 响应到达采购员之前断线。客户端只知道“超时”,不知道订单是否存在。如果直接重试无身份标识的创建动作,可能产生两张订单。如果客户端把超时一律视为失败,订单已经存在却又催促人工补建。网络确认和数据库提交不可能靠一个 HTTP 状态合成原子动作,必须让两次请求指向同一业务操作,在数据库中可查到它的结果。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 现有工程 purchase_order 的 request_id 是 UNIQUE;JdbcRequestStore.insertOrder 采用 INSERT ... ON CONFLICT(request_id) DO NOTHING RETURNING id 并回查相同申请和租户,ProcurementUseCases.order 发现申请已 ORDERED 时返回已有订单 id。这是已实现的“对同一申请再次下单”局部保护,不是通用的客户端幂等键:如...
Java EE 企业应用 21:并发审批怎样避免覆盖和死锁
两人都看到待审批,只能有一个决策生效 同一张申请进入 SUBMITTED 后,审批员甲点击批准,审批员乙点击拒绝。两人的页面都曾读到待审批,并不代表两次写都应成功。若使用 UPDATE purchase_request SET status='APPROVED' WHERE id=? 和另一条拒绝语句,后来的写可能覆盖前一个决定。读到“合法状态”与“成功提交状态迁移”之间必须有一个原子检查点。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 累计工程已在 JdbcRequestStore.transition 中使用 SQL 条件更新:WHERE id=? AND tenant_id=? AND status=? AND version=?,写入 status 并把 version 加 1,更新行数不为 1 则抛 RequestConflictException;用例还调用领域状态机,禁止 DRAFT 直接批准。webapp 将冲突映射到 HTTP 409。这里 version 是显式 ...









