如何写回交易哈希?

解读

国内面试官问“如何写回交易哈希”,并不是想听区块链的挖矿逻辑,而是考察候选人能否把 CouchDB 当成“可信追加账本”来用:

  1. 业务端产生一条交易记录,需要生成不可逆、可审计的哈希指纹;
  2. 该哈希必须原子地写回同一份文档,保证后续任何字段修改都会让哈希失效,从而防篡改
  3. 整个过程要适配 CouchDB 的多主复制离线优先特性,避免“先写库再回写”造成的双写冲突
    一句话:在 CouchDB 的 JSON 文档里,怎样安全、原子、可追溯地把“交易哈希”字段落盘,并保证后续任何篡改都能被发现。

知识点

  1. _rev 机制:CouchDB 用 MVCC,每次更新产生新 _rev,旧版本物理保留,天然适合“追加式账本”。
  2. update 函数:CouchDB 提供服务器端钩子,可原子改写文档并插入计算字段,避免“读-改-写”竞争。
  3. validate_doc_update:同一钩子链上的校验函数,可强制要求哈希字段必须存在且符合算法长度,拒绝非法提交
  4. Etag 与 412 Precondition Failed:国内金融项目常把 _rev 作为 Etag,先比对再回写,防止离线合并时覆盖他人哈希。
  5. 附件(_attachments):若哈希值需要与原始 PDF、票据二进制绑定,可一起塞进附件,用 content_digest 做二次校验。
  6. 区块链侧链思路:部分央企项目把 CouchDB 当“链下账本”,哈希写回后定时批量上链,只存根哈希,节省 Gas。
  7. 国密合规:国内银行要求 SM3 而非 SHA-256,需在 Erlang 层或 Node 层预置国密算法库,并在 update 函数里调用。

答案

分四步落地,全部在 CouchDB 原生能力内完成,无需外部锁:

第一步:生成交易哈希
业务服务在应用层按固定规则序列化(字段 key 升序、UTF-8 无 BOM、浮点转字符串),使用国密 SM3计算得 64 位 hex,记为 txHash

第二步:原子写回
不直接 PUT /db/docid,而是POST /db/_design/ledger/_update/addHash/docid,触发如下 update 函数:

function(doc, req) { if (!doc) return [null, {code: 404, body: 'Doc not found'}]; var sm3 = require('views/lib/sm3'); // 预置国密 var payload = {}; Object.keys(doc).sort().forEach(function(k){ if (k !== 'txHash') payload[k] = doc[k]; }); var calc = sm3(JSON.stringify(payload)); if (calc !== req.body) return [null, {code: 400, body: 'Hash mismatch'}]; doc.txHash = calc; return [doc, {body: JSON.stringify({_id: doc._id, _rev: doc._rev, txHash: calc})}]; }

该函数在同一 Erlang 进程内完成校验+写回,保证原子性;返回的新 _rev 即刻成为后续版本的父版本。

第三步:强制校验
在同一 design 文档里再加 validate_doc_update

function(newDoc, oldDoc, userCtx) { if (newDoc.type === 'trade' && !newDoc.txHash) throw({forbidden: '交易文档必须含 txHash'}); if (oldDoc && oldDoc.txHash && newDoc.txHash !== oldDoc.txHash) throw({forbidden: '交易哈希一旦写入不可修改'}); }

由此拒绝任何绕过 update 函数的直接写库,包括某些后台脚本。

第四步:复制与审计
下游数据中心通过_filtered replication只同步含 txHash 的文档;
审计端调用 GET /db/docid?revs=true&open_revs=all遍历所有历史 _rev,配合 txHash 字段即可秒级发现哪一版被篡改,满足国内等保 2.0 对数据完整性的要求。

拓展思考

  1. 如果交易哈希需要多机构会签,可在 CouchDB 里再建一个“会签子文档”,利用双向复制让各机构本地追加签名哈希,最后由 update 函数聚合出聚合哈希写回父文档,实现“链下多签”。
  2. 当数据量达到千亿级,可对历史版本做分层存储:热数据留在 CouchDB,冷版本按 _rev 范围导出到对象存储 OSS,只在 CouchDB 保留 txHash + _rev 索引,实现哈希锚定冷数据,兼顾合规与成本。
  3. 国内央行数字货币试点项目,要求秒级对账,可在 update 函数里同步写一条哈希通知到 Kafka,供监管节点实时拉取;CouchDB 本身不丢数据,Kafka 只负责流式对账,两者通过 txHash 做幂等,保证最终一致