主题
cbdc-biz/app/endorser
背书节点 + 审计代理。本仓库 submodule cbdc-chain/,本页所有路径相对该 submodule 根。
基本事实
| 项 | 值 | 出处 |
|---|---|---|
| 路径 | cbdc-biz/app/endorser/ | — |
| 独立 go.mod | 无(共用 cbdc-biz/go.mod) | — |
| Go 文件数 | 10 个 .go(含生成的 wire_gen.go)+ 1 个 conf.pb.go | — |
_test.go | 无 | — |
目录:
cbdc-biz/app/endorser/
├── cmd/endorser/{main.go,wire.go,wire_gen.go}
└── internal/
├── biz/ biz.go + endorser.go
├── conf/ conf.proto + conf.pb.go
├── data/ data.go(空 Data struct)
├── server/ server.go + grpc.go
└── service/ service.go + endorser.go入口启动(main.go)
来源:cbdc-biz/app/endorser/cmd/endorser/main.go。
-conf flag 默认 ../../configs(main.go:39-41)。
Wire DI
来源:cmd/endorser/wire.go、wire_gen.go:26-33。
wireApp(*conf.Server, log.Logger, *node.Node)
├─ NewEndorserBiz(node)
├─ NewEndorserService(biz)
├─ NewGRPCServer(*conf.Server, service, logger)
└─ newApp(logger, gs)ProviderSet:server.ProviderSet(internal/server/server.go:8)+ biz.ProviderSet(internal/biz/biz.go:6)+ service.ProviderSet(internal/service/service.go:6)+ 顶层 newApp(main.go:43-53)。
internal/biz
来源:cbdc-biz/app/endorser/internal/biz/endorser.go。
go
// :28
type EndorserBiz struct { node *node.Node }| 方法 | file:line | 职责 |
|---|---|---|
NewEndorserBiz | endorser.go:32 | DI 工厂 |
Init(ctx) | endorser.go:41-77 | 部署 token 公开参数到 FabricX namespace:读各 TMS 配置 → 从 publicParameters.path 读 PP 文件 → 比对链上 setup/hash(shouldDeployPublicParams)→ 仅在缺失或不一致时调 dep.DeployTMSWithPP(tmsID, ppRaw) |
关键说明
背书核心逻辑不在 biz 层。EndorserBiz.Init 只负责 token 公开参数部署。真正的"背书 + 审计代理"由 FSC View 框架(pkg/views/audit.go 与 pkg/views/endorsement.go)+ Fabric 链码背书器实现。
internal/data
internal/data/data.go 提供 Data(空字段 struct)+ NewData + ProviderSet(data.go:9-21),未被 Wire 注入。Endorser 自身无业务表;FSC 状态存 PostgreSQL(cbdc_endorser1 库,由 core.yaml 配置)。
internal/service
来源:cbdc-biz/app/endorser/internal/service/endorser.go:20-28。
| RPC | 入参 → 出参 | 实现 |
|---|---|---|
Init | InitRequest → InitResponse | 调 s.biz.Init(ctx),成功返回 Message:"ok" |
仅此 1 个 RPC。对应 proto:api/cbdc-biz/v1/endorser.proto:7-9:
proto
service EndorserService {
rpc Init(InitRequest) returns (InitResponse) {}
}internal/conf
来源:cbdc-biz/app/endorser/internal/conf/conf.proto:1-41。
proto
message Bootstrap { Biz biz = 1; }
message Biz {
Server server = 1;
Observability observability = 2;
}
message Server {
string network = 1;
string addr = 2; // gRPC 监听
google.protobuf.Duration timeout = 3;
}
message Observability {
Metrics metrics = 1; // addr
Tracing tracing = 2; // endpoint / sample_rate / environment
Logging logging = 3; // level
}configs
来源:cbdc-biz/conf/endorser1/core.yaml(顶层 conf/ 集中目录,不在 app 内)。
关键字段:
| 字段 | 值 | 说明 |
|---|---|---|
biz.server.addr | 0.0.0.0:9300 | gRPC 监听 |
biz.server.timeout | 60s | RPC 超时 |
fsc.id | endorser1 | FSC 节点 ID |
fsc.p2p.listenAddress | /ip4/0.0.0.0/tcp/9301 | libp2p 监听 |
fsc.persistences.default.type | postgres | FSC 状态存储 |
fsc.persistences.default.opts.dataSource | host=cbdc_local_db port=5432 ... dbname=cbdc_endorser1 | 连接串(库名内嵌于此) |
fabric.default.channels[0].name | arma | Fabric 通道 |
token.tms.mytms.driver | zkatdlog | 隐私 token 驱动 |
token.tms.mytms.services.network.fabric.fsc_endorsement.endorsers | [endorser1] | 背书者列表 |
引用的环境变量(${VAR} 占位):
OTLP_ENDPOINT / BIZHUB_ADDR / FSC_AUDITOR_ADDR / FSC_ISSUER_ADDR / FSC_ENDORSER1_ADDR / FSC_INSTITUTION_001_ADDR / KMS_USER_PIN / TLS_ENABLED / TLS_CLIENT_AUTH_REQUIRED / COMMITTER_NOTIFICATION_ADDR / COMMITTER_QUERY_ADDR。
biz.bizhub_addr: ${BIZHUB_ADDR}(core.yaml:14)在 yaml 中存在,但 endorser 的 conf.proto Biz 消息仅有 server/observability 两字段,故该值不进入 Go 配置结构。
端口
| 端口 | 协议 | 来源 |
|---|---|---|
9300 | gRPC(EndorserService.Init) | cbdc-biz/conf/endorser1/core.yaml:7 |
9301 | libp2p(FSC P2P) | cbdc-biz/conf/endorser1/core.yaml:24(fsc.p2p.listenAddress) |
双角色:审计代理 + Token SDK 真签名方
重要纠正
仅看 cmd/endorser/main.go 的显式 RegisterResponder 调用,会以为 endorser 只跑 AuditResponderView(纯转发)。实际 Token SDK 在 FSC node init 阶段会根据配置自动注册第二个 responder(RequestApprovalResponderView),那才是真签名入口。
| 角色 | 入口 | 注册方 | 行为 |
|---|---|---|---|
| 审计代理(不签) | AuditResponderView | cbdc-biz 显式(main.go:105-110) | 收 Bank 的 tx → 转发 Auditor → 把 σ 透传回 Bank |
| Token SDK FSC endorser(真签) | RequestApprovalResponderView | Token SDK 隐式(fsc_endorsement.endorser: true 触发) | 跑校验流水线 → 用 FSC node 身份签 proposal endorsement |
触发隐式注册的关键配置(cbdc-biz/conf/endorser1/core.yaml:139-149):
yaml
services:
network:
fabric:
fsc_endorsement:
endorser: true # 本机是 endorser
id: endorser
policy:
type: all
endorsers:
- endorser1Token SDK 读到 AmIAnEndorserKey(即 services.network.fabric.fsc_endorsement.endorser,service.go:26)为 true 时(fabric-token-sdk/token/services/network/fabric/endorsement/fsc/service.go:56-73)自动调 viewRegistry.RegisterResponder(NewRequestApprovalResponderView(...), &RequestApprovalView{})。
签名前的校验流水线
来源:fabric-token-sdk/token/services/network/fabric/endorsement/fsc/responder.go 的 RequestApprovalResponderView.Call(responder.go:72-106)。Call 顺序执行 4 个方法,各自映射一类错误(errors.go:13-24):
| # | 步骤 | 检查内容 | 失败错误 |
|---|---|---|---|
| 1 | receive | 接收 TX;transient 字段须为 2 或 3 个(tmsID + token_request + 可选 approval_metadata,responder.go:121);解析并校验 TMSID;RWS 须为空 + chaincode 名/版本/函数校验(responder.go:108-200) | ErrReceivedProposal / ErrInvalidTransient / ErrInvalidProposal |
| 2 | validateProposal | creator / proposalBytes / signature / header / payload 均非空(:213-233)→ mspManager.IsValid(creator)(:242)→ acl.CheckACL(:250)→ verifier.Verify(proposalBytes, signature)(:260) | ErrValidateProposal |
| 3 | validate | validator.UnmarshallAndVerifyWithMetadata(...) 业务校验(:303)+ db.AppendValidationRecord 落本地 ttxdb(:317) | ErrValidateProposal |
| 4 | endorse | translate 写 action 到 RWS(:269-291),随后真正签名 | ErrEndorseProposal |
endorse(responder.go:332-355)取 endorserID = r.endorserService.EndorserID(request.TMSID)(:335),全程通过后调真正的签名(:348):
go
endorsementResult, err := r.endorserService.Endorse(ctx, request.Tx, endorserID)endorserID —— endorser 节点自己的 FSC node 身份。
客户端"搜索 endorser"
客户端节点(institution / issuer / auditor)core.yaml 内有镜像结构:
yaml
fsc_endorsement:
endorser: false # 自己不是 endorser
policy:
type: all # 当前用 all(要求列出的全部签)
endorsers:
- endorser1文件位置:cbdc-biz/conf/institution-001/core.yaml:151-157、cbdc-biz/conf/issuer/core.yaml:159-165、cbdc-biz/conf/auditor/core.yaml:209-215。
policy.type(常量 service.go:30-31,分流逻辑 service.go:109-118):
all—— 要求列表里全部 endorser 签(cbdc-biz 当前用此;为空时默认即all,service.go:78-81)1outn——rand.Intn随机选 1 个签
完整时序
来源:综合 pkg/views/endorsement.go:34-98(cbdc-biz AuditAndEndorseView)、pkg/views/audit.go:88-128(cbdc-biz AuditResponderView)、fabric-token-sdk/token/services/ttx/collectendorsements.go:95-166(SDK CollectEndorsementsView.Call 编排)、fabric-token-sdk/.../endorsement/fsc/responder.go(真签 responder)。
三层签名缺一不可
来源:fabric-x-committer · 02 架构。
| 协议层 | 签名方 | 进入哪个集合 |
|---|---|---|
| Token owner 同意支出 | wallet 私钥 | TokenRequest.Signatures |
| Auditor 审计批准 | auditor 私钥 | TokenRequest.AuditorSignatures |
| 链上 endorsement | endorser FSC node 私钥 | proposal endorsement 字段 |
Committer Verifier 端按 namespace 评估策略,三层任一缺失或不满足 → ABORTED_SIGNATURE_INVALID,不落账。
扩容含义(修正版)
| 项 | 旧描述 | 修正 |
|---|---|---|
| endorser 是否纯代理 | "纯转发" | 错 —— 同时是审计代理 + 真签名方 |
| 多副本本地存储 | "TMS 注册冲突" | validate 阶段 db.AppendValidationRecord 写本地 ttxdb(responder.go:317),各副本有独立 ttxdb |
| 每副本独立证书原因 | 一般 mTLS | proposal endorsement 由各副本 FSC node 身份签(responder.go:335,348) |
当前单点故障
cbdc-biz/conf/endorser1/core.yaml:147-148 中 endorsers 仅列 endorser1(endorser2 行被注释)。endorser1 宕机 = all 策略下背书无法完成。配置内已预留 endorser2 注释行(core.yaml:54-59,149)。
与其他 app 的关系
调用者(→ Endorser)
| 调用方 | 触发 | 链路 |
|---|---|---|
| Bank 侧 View 调用(institution / issuer) | pkg/views/endorsement.go:25-98 的 AuditAndEndorseView(替代 SDK 原生 ttx.NewCollectEndorsementsView(tx)) | Bank → Endorser(view.Identity("endorser1"),pkg/views/endorsement.go:44) |
| 运维(一次性) | grpcurl -plaintext localhost:9300 cbdc_biz.v1.EndorserService/Init | README.md:122 |
Endorser → 其他
| 目标 | 用途 | 出处 |
|---|---|---|
| Auditor | RequestAuditView → 拿审计签名(sigma) | pkg/views/audit.go:51-81 |
| Fabric Network(链码) | 通过 FSC + Fabric 接口完成 Token 背书 | core.yaml token.tms.mytms |
代理审计的代码实现已落地在 pkg/views/audit.go 与 pkg/views/endorsement.go(见下表)。cbdc-chain/docs/ 中有同名设计文档但已与代码脱节,本 wiki 不作引用依据。
与 cbdc-biz/pkg/
| pkg | 用途 | 出处 |
|---|---|---|
pkg/common/nodesdk | StartFSC(flagconf, datadir) | main.go:100 |
pkg/signet | Init / Shutdown / Middlewares | main.go:93, 96、server/grpc.go:18 |
pkg/views | AuditResponderView + AuditAndEndorseView 注册 | main.go:108-109 |
pkg/runtime | LogStartup + StartPprof | main.go:67-68 |
pkg/views/ 关键 view:
| View | 文件 | 角色 |
|---|---|---|
AuditResponderView | pkg/views/audit.go:86-128 | Endorser 端:接收 Bank tx → 转发 Auditor → 返回 sigma |
RequestAuditView | pkg/views/audit.go:51-81 | Endorser → Auditor:发起审计请求 |
AuditAndEndorseView | pkg/views/endorsement.go:25-98 | Bank 端:透明替代原生 ttx.NewCollectEndorsementsView |
FSC / Token SDK 集成
启动:nodesdk.StartFSC(flagconf, path.Join(flagconf, "data"))(main.go:100),内部装载 SDK + fsc.Start()。
View 注册:viewregistry.GetRegistry(node).RegisterResponder(&views.AuditResponderView{}, &views.AuditAndEndorseView{})(main.go:106-109)。
Token SDK:EndorserBiz.Init 取 tms.GetTMSDeployerService(node)(internal/biz/endorser.go:49),按需调 dep.DeployTMSWithPP(tmsID, ppRaw)(:71)部署公开参数。
端到端入口:Init RPC
来源:README.md:122。
sh
grpcurl -plaintext localhost:9300 cbdc_biz.v1.EndorserService/Init执行栈:
相关文档
| 文件 | 内容 |
|---|---|
cbdc-biz/README.md | 本地初始化命令、目录位置、Wire 生成命令 |
cbdc-chain/docs/ 已与代码脱节
docs/plans/2026-03-23-endorser-audit-proxy-plan.md 等历史设计文档存在,但已不作为本 wiki 的事实依据。代理审计的当前实现请直接读 pkg/views/audit.go 与 pkg/views/endorsement.go。
测试
endorser app 目录内无 _test.go 文件。
跨 app 测试:tests/e2e/endorser_proxy_test.go(integration 构建标签,tests/ 模块下,见 cbdc-chain · 模块详解 · tests)经 issue / transfer / redeem 隐式覆盖代理审计链路。
看代码推荐路径
cmd/endorser/main.go:56-122→ 启动顺序。internal/service/endorser.go:20-28→ 仅一个 RPC:Init。internal/biz/endorser.go:41-77→ 公开参数部署。pkg/views/audit.go:51-128+pkg/views/endorsement.go:25-98→ 审计代理 View 三件套(核心逻辑)。tests/e2e/endorser_proxy_test.go→ 端到端验证。
待考
- BizHub 调用点(
core.yaml:14有bizhub_addr: ${BIZHUB_ADDR},但 endorser 的conf.proto不解析该字段)。 - 多 Endorser 节点(
endorser2)的实际启用配置(当前仅注释行core.yaml:54-59,149)。 - token_selector 与 endorser 的整合点(
README.md:207Token Selector 章节,endorser app 代码未见引用)。 - 背书失败的重试逻辑(在 Bank 侧 / FSC 框架层处理)。