Skip to content

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 默认 ../../configsmain.go:39-41)。

Wire DI

来源:cmd/endorser/wire.gowire_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.ProviderSetinternal/server/server.go:8)+ biz.ProviderSetinternal/biz/biz.go:6)+ service.ProviderSetinternal/service/service.go:6)+ 顶层 newAppmain.go:43-53)。

internal/biz

来源:cbdc-biz/app/endorser/internal/biz/endorser.go

go
// :28
type EndorserBiz struct { node *node.Node }
方法file:line职责
NewEndorserBizendorser.go:32DI 工厂
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.gopkg/views/endorsement.go)+ Fabric 链码背书器实现。

internal/data

internal/data/data.go 提供 Data(空字段 struct)+ NewData + ProviderSetdata.go:9-21),未被 Wire 注入。Endorser 自身无业务表;FSC 状态存 PostgreSQL(cbdc_endorser1 库,由 core.yaml 配置)。

internal/service

来源:cbdc-biz/app/endorser/internal/service/endorser.go:20-28

RPC入参 → 出参实现
InitInitRequestInitResponses.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.addr0.0.0.0:9300gRPC 监听
biz.server.timeout60sRPC 超时
fsc.idendorser1FSC 节点 ID
fsc.p2p.listenAddress/ip4/0.0.0.0/tcp/9301libp2p 监听
fsc.persistences.default.typepostgresFSC 状态存储
fsc.persistences.default.opts.dataSourcehost=cbdc_local_db port=5432 ... dbname=cbdc_endorser1连接串(库名内嵌于此)
fabric.default.channels[0].namearmaFabric 通道
token.tms.mytms.driverzkatdlog隐私 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 配置结构。

端口

端口协议来源
9300gRPC(EndorserService.Initcbdc-biz/conf/endorser1/core.yaml:7
9301libp2p(FSC P2P)cbdc-biz/conf/endorser1/core.yaml:24fsc.p2p.listenAddress

双角色:审计代理 + Token SDK 真签名方

重要纠正

仅看 cmd/endorser/main.go 的显式 RegisterResponder 调用,会以为 endorser 只跑 AuditResponderView(纯转发)。实际 Token SDK 在 FSC node init 阶段会根据配置自动注册第二个 responderRequestApprovalResponderView),那才是真签名入口。

角色入口注册方行为
审计代理(不签)AuditResponderViewcbdc-biz 显式(main.go:105-110收 Bank 的 tx → 转发 Auditor → 把 σ 透传回 Bank
Token SDK FSC endorser(真签)RequestApprovalResponderViewToken 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:
          - endorser1

Token SDK 读到 AmIAnEndorserKey(即 services.network.fabric.fsc_endorsement.endorserservice.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.goRequestApprovalResponderView.Callresponder.go:72-106)。Call 顺序执行 4 个方法,各自映射一类错误(errors.go:13-24):

#步骤检查内容失败错误
1receive接收 TX;transient 字段须为 2 或 3 个(tmsID + token_request + 可选 approval_metadataresponder.go:121);解析并校验 TMSID;RWS 须为空 + chaincode 名/版本/函数校验(responder.go:108-200ErrReceivedProposal / ErrInvalidTransient / ErrInvalidProposal
2validateProposalcreator / proposalBytes / signature / header / payload 均非空(:213-233)→ mspManager.IsValid(creator):242)→ acl.CheckACL:250)→ verifier.Verify(proposalBytes, signature):260ErrValidateProposal
3validatevalidator.UnmarshallAndVerifyWithMetadata(...) 业务校验(:303)+ db.AppendValidationRecord 落本地 ttxdb(:317ErrValidateProposal
4endorsetranslate 写 action 到 RWS(:269-291),随后真正签名ErrEndorseProposal

endorseresponder.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-157cbdc-biz/conf/issuer/core.yaml:159-165cbdc-biz/conf/auditor/core.yaml:209-215

policy.type(常量 service.go:30-31,分流逻辑 service.go:109-118):

  • all —— 要求列表里全部 endorser 签(cbdc-biz 当前用此;为空时默认即 allservice.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
链上 endorsementendorser FSC node 私钥proposal endorsement 字段

Committer Verifier 端按 namespace 评估策略,三层任一缺失或不满足 → ABORTED_SIGNATURE_INVALID,不落账。

扩容含义(修正版)

旧描述修正
endorser 是否纯代理"纯转发" —— 同时是审计代理 + 真签名方
多副本本地存储"TMS 注册冲突"validate 阶段 db.AppendValidationRecord 写本地 ttxdb(responder.go:317),各副本有独立 ttxdb
每副本独立证书原因一般 mTLSproposal endorsement 由各副本 FSC node 身份签(responder.go:335,348

当前单点故障

cbdc-biz/conf/endorser1/core.yaml:147-148endorsers 仅列 endorser1endorser2 行被注释)。endorser1 宕机 = all 策略下背书无法完成。配置内已预留 endorser2 注释行(core.yaml:54-59,149)。

与其他 app 的关系

调用者(→ Endorser)

调用方触发链路
Bank 侧 View 调用(institution / issuer)pkg/views/endorsement.go:25-98AuditAndEndorseView(替代 SDK 原生 ttx.NewCollectEndorsementsView(tx)Bank → Endorser(view.Identity("endorser1")pkg/views/endorsement.go:44
运维(一次性)grpcurl -plaintext localhost:9300 cbdc_biz.v1.EndorserService/InitREADME.md:122

Endorser → 其他

目标用途出处
AuditorRequestAuditView → 拿审计签名(sigma)pkg/views/audit.go:51-81
Fabric Network(链码)通过 FSC + Fabric 接口完成 Token 背书core.yaml token.tms.mytms

代理审计的代码实现已落地在 pkg/views/audit.gopkg/views/endorsement.go(见下表)。cbdc-chain/docs/ 中有同名设计文档但已与代码脱节,本 wiki 不作引用依据。

cbdc-biz/pkg/

pkg用途出处
pkg/common/nodesdkStartFSC(flagconf, datadir)main.go:100
pkg/signetInit / Shutdown / Middlewaresmain.go:93, 96server/grpc.go:18
pkg/viewsAuditResponderView + AuditAndEndorseView 注册main.go:108-109
pkg/runtimeLogStartup + StartPprofmain.go:67-68

pkg/views/ 关键 view:

View文件角色
AuditResponderViewpkg/views/audit.go:86-128Endorser 端:接收 Bank tx → 转发 Auditor → 返回 sigma
RequestAuditViewpkg/views/audit.go:51-81Endorser → Auditor:发起审计请求
AuditAndEndorseViewpkg/views/endorsement.go:25-98Bank 端:透明替代原生 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.Inittms.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.gopkg/views/endorsement.go

测试

endorser app 目录内无 _test.go 文件。

跨 app 测试:tests/e2e/endorser_proxy_test.gointegration 构建标签,tests/ 模块下,见 cbdc-chain · 模块详解 · tests)经 issue / transfer / redeem 隐式覆盖代理审计链路。

看代码推荐路径

  1. cmd/endorser/main.go:56-122 → 启动顺序。
  2. internal/service/endorser.go:20-28 → 仅一个 RPC:Init
  3. internal/biz/endorser.go:41-77 → 公开参数部署。
  4. pkg/views/audit.go:51-128 + pkg/views/endorsement.go:25-98 → 审计代理 View 三件套(核心逻辑)。
  5. tests/e2e/endorser_proxy_test.go → 端到端验证。

待考

  • BizHub 调用点(core.yaml:14bizhub_addr: ${BIZHUB_ADDR},但 endorser 的 conf.proto 不解析该字段)。
  • 多 Endorser 节点(endorser2)的实际启用配置(当前仅注释行 core.yaml:54-59,149)。
  • token_selector 与 endorser 的整合点(README.md:207 Token Selector 章节,endorser app 代码未见引用)。
  • 背书失败的重试逻辑(在 Bank 侧 / FSC 框架层处理)。