Tendermint拜占庭容错引擎
这是一个演示示例项目。这里用的是单节点tendermint实际上应该是最少4节点。另外ABCI没有持久化存储每次启动跟tendermint节点进行区块数据同步的时候也是返回的默认数据所以每次启动tendermint节点的时候都需要清空一下tendermint-home目录。但是这些不影响对整体功能的理解。组成部分这个小系统有两部分组成一个单节点tendermint core去官网下载好之后先执行初始化然后启动把进程拉起来即可。启动的时候设置一下告诉它ABCI进程的地址和端口26658。第二个部分是ABCI程序本例中是个Java进程本质是个gRPC服务端监听26658端口服务绑定一个继承了ABCIApplicationImplBase类的Service去实现它的抽象方法来自tendermint进程的rpc调用都是由这些抽象方法中实现的业务逻辑进行处理。ABCI程序中的基础代码可以使用tendermint提供的proto文件和maven插件来自动生成开发者只需要自己实现上面说的Service中的方法即可。演示流程用curl向tendermint节点26657端口发送转账和查询两个请求tendermint会通过gRPC调用ABCI程序来进行交易验签、账务处理、账务查询等操作。ABCI程序启动mvn compile mvn package -DskipTests java -jar .\target\tendermint-abci-1.0-SNAPSHOT.jartendermint节点初始化.\tendermint.exe init --home .\tendermint-home\tendermint节点启动.\tendermint.exe node --home .\tendermint-home\ --abci grpc --proxy_app tcp://127.0.0.1:26658密钥对 alice PublicKey: MCowBQYDK2VwAyEAqUGJETBs0UM8N/2dt2jxws7/W16XZG7Z7fmnvakwgY PrivateKey: MC4CAQAwBQYDK2VwBCIEIN20QL5UtLYUtankHQ1uaTk8mPmOzc47HykL7HbahZ7z bob PublicKey: MCowBQYDK2VwAyEAafLrCRx28XE/oUMjTTUe3WNyJoRVHOmSVOvYOq33SBo PrivateKey: MC4CAQAwBQYDK2VwBCIEIEsX3Hb77evB6hONZHw0K7kyf3auC3rpI8lGDmUaUTU carol PublicKey: MCowBQYDK2VwAyEAUVarvjgIFfokE2v1wn8xetKmW0rcSfsW9p3CZvfshXM PrivateKey: MC4CAQAwBQYDK2VwBCIEIK7yfAc6zj5CsFwRrh1toNglZr5xJIvZlfNxYQIyt8模拟客户端构造的客户端请求带签名{amount:100,fromId:alice,signContent:alice:bob:100,signature:K5UFXaUAtMAom9Km2zLYEOVLC1O1MDr2XP2BY4N2nOBK50QfEty8JLugxboZNx3EMeh2m5rBKVA278KKI9TCQ,toId:bob}模拟客户端请求powershell执行转账$txBytes [System.Text.Encoding]::UTF8.GetBytes({amount:100,fromId:alice,signContent:alice:bob:100,signature:K5UFXaUAtMAom9Km2zLYEOVLC1O1MDr2XP2BY4N2nOBK50QfEty8JLugxboZNx3EMeh2m5rBKVA278KKI9TCQ,toId:bob}) $txHex ($txBytes | ForEach-Object { $_.ToString(x2) }) -join curl http://localhost:26657/broadcast_tx_commit?tx0x$txHex查询$queryBytes [System.Text.Encoding]::UTF8.GetBytes(alice) $queryHex ($queryBytes | ForEach-Object { $_.ToString(x2) }) -join curl http://localhost:26657/abci_query?data0x$queryHex执行结果StatusCode : 200 StatusDescription : OK Content : {jsonrpc:2.0,id:-1,result:{response:{code:0,log:balance: 900,info:,index:0, key:null,value:OTAw,proofOps:null,height:0,codespace:}}} RawContent : HTTP/1.1 200 OK X-Server-Time: 1774939938 Content-Length: 171 Content-Type: application/json Date: Tue, 31 Mar 2026 06:52:18 GMT {jsonrpc:2.0,id:-1,result:{response:{code:0,log:b... Forms : {} Headers : {[X-Server-Time, 1774939938], [Content-Length, 171], [Content-Type, application/json], [Date, Tue, 31 Mar 2026 06:52:18 GMT]} Images : {} InputFields : {} Links : {} ParsedHtml : System.__ComObject RawContentLength : 171——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————QA一、整体架构为什么分轻节点和全节点轻节点不存储全量区块数据通过验证 Merkle 证明来确认数据真实性给普通用户用。全节点存储完整数据参与共识是网络的骨干。全节点三个组成部分的职责Tendermint Core共识引擎负责节点间 P2P 通信、区块提议、投票ABCI 应用业务逻辑处理交易验证、执行、查询KMS签名验签机保管节点验证者私钥对共识投票消息Prevote/Precommit签名注意不是对用户交易签名用户交易的签名由谁验证用户用自己的私钥签名交易发给全节点后Tendermint 转发给 ABCI 应用的 CheckTx 方法由业务代码验签。KMS 不介入用户交易。二、端口和协议三个端口各自的协议端口通信双方协议26656Tendermint ↔ Tendermint自定义 P2P 协议TCP26657外部客户端 → TendermintHTTP JSON看起来像RPC底层是普通HTTP26658Tendermint → ABCI 应用gRPCHTTP/2 Protobuf 二进制为什么 26657 用 curl 能直接调RPC 是编程模型概念不是具体协议。Tendermint 选择用 HTTP JSON 来实现 RPC所以 curl、浏览器、HttpClient 都能直接调SDK 只是把这些 HTTP 调用封装成看起来像本地方法调用的形式。三、gRPC 和 Protobuf两者关系Protobuf 是数据序列化格式和 JSON 是同一类东西但是二进制的。gRPC 是远程调用框架选择用 Protobuf 作为数据格式底层传输用 HTTP/2。类比Protobuf 之于 gRPC就像 JSON 之于 REST。proto 文件是什么既描述数据结构长什么样也描述接口有哪些方法。protoc 编译器读这个文件自动生成对应语言的代码。Tendermint 用 GoABCI 应用用 Java两边都从同一份 proto 文件生成代码数据格式天然一致。Protobuf 为什么比 JSON 小序列化后不传字段名只传字段编号类型值。比如 {code:0,log:ok} 是 21 字节Protobuf 编码只有 6 字节。gRPC 方法名放在哪里传输方法名走 HTTP/2 的 path 头不在 body 里body 里只有 Protobuf 序列化后的参数数据。四、ABCI 回调顺序握手阶段启动时Echo → Info → InitChainEcho是握手探测。Tendermint 启动时用 Echo 来确认 ABCI 服务已经就绪、网络连通确认之后才进行后续握手。心跳一般是周期性的Echo 只在启动连接时做一次。Info是 ABCI 向 Tendermint 汇报自己的状态Tendermint 根据汇报结果决定怎么处理。Tendermint 问你现在到第几块了appHash 是什么ABCI 答我在第 N 块appHash 是 xxxTendermint 判断→ 如果 N Tendermint 自己的高度直接继续→ 如果 N Tendermint 高度重放 N1 到最新的区块给 ABCI→ 如果 appHash 对不上报错退出InitChain ⚠️ 有一个关键条件InitChain 不是每次启动都调用只有在创世时调用一次也就是区块高度为 0 的时候。具体触发条件Info 返回 lastBlockHeight 0Tendermint 判断这是一条全新的链才会调用 InitChain。用途是做创世初始化比如从 genesis.json 里读取初始验证者集合设置初始参数区块大小限制、gas 上限等初始化创世账户余额正式做法是从 genesis.json 里读初始账户而不是硬编码在构造方法里Info 返回 height0 → Tendermint 判断是新链 → 调用 InitChain传入 genesis.json 的内容 → ABCI 从这里初始化账户等创世数据 → 之后开始正常出块 Info 返回 heightNN0 → Tendermint 判断是重启 → 不调用 InitChain → 直接重放或继续我们现在把账户硬编码在构造方法里是一个简化做法。生产环境正确做法是把初始账户写在 genesis.json 的 app_state 字段里在 InitChain 回调时读取并初始化。每个区块的生命周期客户端提交交易→ CheckTx预检不改状态→ 进 mempool 等待共识达成出块→ BeginBlock开启事务→ DeliverTx × N执行每笔交易改状态→ EndBlock收尾变更验证者→ Commit持久化返回 appHash→ Tendermint 把 appHash 存入区块头广播完整流程Tendermint 和 ABCI 各自的工作客户端提交交易Tendermint收到交易转发给 ABCI 做 CheckTxABCI验签、余额预检返回 codeTendermintcode0 放入 mempool非0 拒绝返回客户端共识阶段出块前纯 Tendermint 的工作不涉及 ABCI轮到某个验证者做 Proposer从 mempool 打包交易构造候选区块广播 Proposal 给其他节点各节点 Prevote → Precommit 两轮投票收到 2/3 的 Precommit 后共识达成确定提交这个区块BeginBlockTendermint把区块头信息、作恶证据传给 ABCI自己开始准备执行区块ABCI开启数据库事务记录当前区块高度处理 Slash 惩罚逻辑DeliverTx × NTendermint按顺序把区块里每笔交易传给 ABCI收集每笔交易的执行结果code、log、events记录哪些交易有效哪些无效ABCI执行转账修改账户余额返回执行结果EndBlockTendermint收到 ABCI 返回的验证者变更列表更新自己维护的验证者集合为下一轮共识准备ABCI返回验证者变更增减节点、修改投票权重简单实现空返回即可CommitTendermint收到 ABCI 返回的 appHash写入区块头把完整区块持久化到 blockstore.db更新 state.db然后把区块广播给其他节点同步ABCI持久化账户数据计算新 appHash更新 lastBlockHeight返回 appHash一句话总结各自职责Tendermint 负责共识投票、区块打包、数据广播、区块持久化、验证者管理ABCI 负责交易验签、业务状态变更、业务数据持久化、appHash计算两者之间没有重叠Tendermint 不碰账户余额ABCI 不碰投票和网络。CheckTx 和 DeliverTx 的区别CheckTx 是轻量预检不修改状态交易进 mempool 前调用可以并发。DeliverTx 才是真正执行修改账户余额在区块被共识确认后调用。两个都要验签因为 CheckTx 之后到 DeliverTx 之间可能有其他交易先执行导致状态变化。五、Unix Socket vs TCP 安全与性能区别Unix Socket 只能同机通信地址是文件路径而不是 IP端口完全绕过网络协议栈直接在内核里传数据性能更好也更安全。TCP 支持跨机器通信但有完整网络协议栈开销。什么时候用哪个Tendermint 和 ABCI 在同一台机器/同一个容器用 Unix Socket性能和安全最优跨机器部署只能用 TCP必须加 mTLS 双向认证当前代码用 TCP 明文安全吗本机开发调试完全没问题127.0.0.1 的流量不出网卡外部无法访问。生产环境必须处理。六、验签与密钥算法Ed25519交易验签和节点身份验签Tendermint 默认都用 Ed25519为什么公钥私钥用不同格式X509EncodedKeySpec 对应的是 X.509 格式专门用于公钥。