跳转至

长安链 SDK-Go:从跑通到用对

业务应用 不直接 和链打交道:交易构造与签名、gRPC 长连接、TLS、结果确认、订阅流——都由 SDK 收敛成 ChainClient 上的方法调用。跑通一个程序只是起点,用对身份、分清响应、处理持续数据流,才是业务接入的完整路径。

阅读基线

原始素材为 60 分钟、演示驱动 的技术分享。代码基线:sdk-go@v2.4.1_qc;教程仓库:sdk-go-demo。

文中代码均摘自真实可运行文件,保留原稿标注的路径与行号;带 ... 的片段表示省略,完整程序见对应文件。本文完整保留原稿章节、演示、配置及接口索引,仅调整展示形式。

1. SDK 是什么

业务应用 不直接 和链打交道:交易构造与签名、gRPC 长连接、TLS、结果确认、订阅流——都由 SDK 收敛成 ChainClient 上的方法调用。

flowchart LR
    subgraph APP["业务应用层(开发者侧)"]
        direction TB
        A1["存证 / 溯源业务"]
        A2["链上索引器 / 数据管道"]
        A3["实时监控 / 告警 / 风控"]
    end

    subgraph SDK["chainmaker-sdk-go —— Go 业务 API 入口"]
        direction TB
        S0["ChainClient"]
        S1["连接池 + 多节点择优 + 重试"]
        S2["交易构造 / 私钥签名 / TLS"]
        S3["合约调用与查询"]
        S4["订阅服务(block / tx / event)"]
        S5["链配置 · 证书 · 多签 · Gas · 归档 · 隐私计算 ..."]
    end

    subgraph CHAIN["长安链 ChainMaker"]
        direction TB
        N1["节点1 RPC :12301"]
        N2["节点2 RPC :12302"]
        N3["节点N RPC"]
        LEDGER["账本 + 合约虚拟机<br/>WASM / EVM / 系统合约"]
    end

    A1 --> S0
    A2 --> S0
    A3 --> S0
    S0 --> S1
    S0 --> S2
    S0 --> S3
    S0 --> S4
    S0 --> S5
    S1 -- "gRPC(TLS)" --> N1
    S1 -- "gRPC(TLS)" --> N2
    S1 -- "gRPC(TLS)" --> N3
    N1 --> LEDGER
    N2 --> LEDGER
    N3 --> LEDGER
    S3 -. "同步调用/查询" .-> N1
    S4 -. "gRPC 流式推送" .-> N2

一句话: chainmaker-sdk-go 是 Go 业务接入长安链的 API 封装与协议适配层。

2. Quickstart:5 分钟跑通第一个程序

要准备什么

要准备 从哪来 怎么放
Go 工程 go.mod go mod init + go get chainmaker.org/chainmaker/sdk-go/v2 项目根目录
SDK 配置 sdk_config.yml 抄 sdk-go-demo/testdata/configs/(6 套模板) testdata/configs/
证书/密钥 从链部署目录 crypto-config/ 复制 配置里写相对路径, 以运行目录为基准
合约文件 官方标准合约(存证 wasm / EVM bin+abi) testdata/contracts/

证书目录结构:每个目录是什么

testdata/configs/ 下两个证书根目录,对应两种身份体系:

testdata/configs/:身份与密码套件
testdata/configs/
├── crypto-config/        # cert 模式(证书身份)——本演示使用
│   ├── std/              # 标准密码套件(SHA256/ECDSA-RSA)
│   └── gm/               # 国密套件(SM2/SM3,GMTLS 双证书)
├── crypto-config-pk/     # pk 模式(公钥身份)
│   ├── std/              # 标准套件
│   ├── gm/               # 国密套件
│   └── k1/               # secp256k1 套件(以太坊式曲线,配 EVM 地址)
└── sdk_config*.yml       # 6 套配置 = 3 种认证模式 × TLS 开/关

cert 模式按“组织”分目录(crypto-config/std/ 下,以 wx-org1 为例,全部真实文件):

crypto-config/std/wx-org1.chainmaker.org/
crypto-config/std/wx-org1.chainmaker.org/
├── ca/                        # 组织 CA —— SDK 配置的 trust_root_paths 指向这里
│   ├── ca.crt                     # 校验节点 TLS 证书用
│   └── ca.key                     # (签发用,正常不随应用分发)
├── node/                      # 节点身份(部署链用;客户端一般只读它的 CA)
│   ├── common1/                   # 同步/普通节点:sign.* 签名 + tls.* 通信 + nodeid
│   └── consensus1/                # 共识节点:同构
└── user/                      # 用户身份 —— SDK 客户端在这里取自己的证书
    ├── admin1/                    # 管理员:治理类交易背书用(部署合约收的就是 admin1 的签名)
    ├── client1/                   # 普通客户端:tls.*(连接) + sign.*(交易签名) + addr(账户地址)
    │                               #   以及 sign.key.enc(加密私钥样例,配 *_pwd 使用)
    └── light1/                    # 轻节点用户(06-cert-alias 演示“别名换绑证书”时用到它)

pk 模式按“节点”分目录(crypto-config-pk/std/,没有身份签名证书,身份 = 公钥):

crypto-config-pk/std/node1/
crypto-config-pk/std/node1/
├── node1.key / node1.pem          # 节点公私钥(.pem 是公钥,无 .crt)
├── node1.tls.crt / node1.tls.key  # TLS 通信证书(只管连接,不代表身份)
├── admin/admin1..admin16/         # 每个管理员一组 .key + .pem
├── user/                          # 普通用户公私钥
├── client-tls/client1/            # TLS 客户端证书
└── ca/wx-org1..16/                # 多组织 CA(TLS 校验用)

三种模式的区别与演示选择

cert(permissionedWithCert) pk(public) pwk(permissionedWithKey)
身份载体 证书(sign.crt) 公钥(.pem),无证书 公钥 + 链上权限管理
目录组织 按 组织(wx-orgN) 按 节点(nodeN) 复用 cert 布局
签名材料 sign.key + sign.crt .key + .pem .key
目录来源 链部署 crypto-config/ 部署工具生成的公钥集 同 pk
对应配置 sdk_config(_tls).yml sdk_config_pk(_tls).yml sdk_config_pwk(_tls).yml

本演示的身份与密码套件

本演示全部使用 crypto-config/std/(cert 模式 + 标准密码套件):sdk_config.yml 引用 wx-org1.../user/client1/ 做业务调用,部署合约时由 pkg/util.AdminEndorsers 收集四个组织的 admin1 背书。

国密链把路径里的 std 换成 gm 即可,另需启用 GMTLS 双证书(user_enc_* 配置)。

已核实:sdk_config_pwk.yml 引用的 wx-org1.../admin/admin.key 在当前 testdata 中 不存在(该组织下是 user/admin1/),pwk 模板无法直接跑通——演示时只讲差异,不现场运行 pwk。

真实演示环境:sdk-go-demo 仓库

原分享使用 sdk-go-demo 作为递进式教程仓库;订阅优化演示使用 SDK 仓库的 examples/subscribe/optimize。教程仓库的结构如下,准备好对应链环境与配置后运行:

sdk-go-demo/:17 个递进教程
sdk-go-demo/
├── 01-quickstart .. 17-evm/    # 17 个递进教程(本讲用 01/06/07/08/15)
├── 99-probe/                    # 连不上链时的只读诊断探针
├── pkg/util/util.go             # AdminEndorsers / MustSuccess 等公共辅助
├── testdata/
│   ├── configs/                 # 6 套 SDK 配置 + 上面两棵证书树
│   └── contracts/               # 标准合约,开箱即用
│       ├── wasm/evidence/       #   存证(Rust WASM,含源码 src/fact.rs)
│       ├── evm/erc20|erc721/    #   EVM(.bin + .abi + .sol 源码)
│       ├── wasm/bulletproofs/   #   零知识范围证明
│       └── wasm/paillier/       #   同态加密
└── Makefile                     # make run-01 ~ run-17 一键运行

一键命令(Makefile 已封装):

在 sdk-go-demo/ 下运行
make run-01          # 运行 01-quickstart(01~17 序号均可)
make run DEMO=07-subscribe
make check           # build + vet + fmt 一键检查(贡献 demo 前跑)

连不上链先跑探针(99-probe,只读不改链):打印链的 auth_type/crypto.hash、各组织 trust_root 证书指纹、共识组织、CONTRACT_MANAGE 背书策略——逐项与本地 sdk_config.yml 对比,CA 不匹配 / auth_type 不一致一眼可见。

原分享演示与教程对照:

演示 目录 讲到
演示 1 01-quickstart §2 Quickstart
演示 2 07-subscribe §6 订阅
演示 3 examples/subscribe/optimize(sdk-go 仓库) §7 订阅优化
演示 4 08-archive §8 归档
引用 06-cert-alias / 03-block-query / 15-transaction §3 基本功能
演示 1:Quickstart
运行第一个程序
cd /path/to/sdk-go-demo/01-quickstart && go run main.go

预期输出:

部署、写入与查询结果
contract deployed: blockHeight=..., txId=...
invoke contract success: txId=..., fileHash=...
query contract success: result={"file_hash":"...","file_name":"...","time":"..."}

第一个程序长什么样

sdk-go-demo/01-quickstart/main.go · 原稿行号 16–30
client, err := sdk.NewChainClient(
    sdk.WithConfPath("../testdata/configs/sdk_config.yml"),
)
if err != nil {
    log.Fatalf("create client failed: %v", err)
}
defer func() { _ = client.Stop() }()

记住三件事:

  1. 配置文件一把梭:链、节点、身份、TLS 全在 sdk_config.yml。
  2. client 复用,Stop 收尾。
  3. 连接谁、以谁的身份 → 配置决定;接下来只关心“调用哪个合约、传什么参数”。

最容易踩的四个坑

连接前先核对这四项

  1. 相对路径以 运行目录 为基准:cd 01-quickstart 里跑就用 ../testdata/...。
  2. trust_root_paths 指向 节点 CA:连别人的链必须换对方 CA。
  3. tls_host_name 与节点证书 SAN 一致(如 chainmaker.org)。
  4. auth_type 必须与目标链一致:public=pk,permissionedWithKey=pwk。

全部配置项速查 → 见文末 附录 A。

3. 基本功能:一步到位的方法

核心认知:大多数场景不需要手动构造 Payload。

两类返回

第一类:返回 TxResponse(写入类及部分查询接口,要检查状态码)。

sdk-go-demo/01-quickstart/main.go · 调用与查询
// 01-quickstart/main.go:69 —— 同步等执行结果
invokeResp, err := client.InvokeContract(claimContract, "save", "", kvs, -1, true)
// 01-quickstart/main.go:85 —— 只读查询
queryResp, err := client.QueryContract(claimContract, "find_by_file_hash", queryKvs, -1)
sdk-go-demo/06-cert-alias/main.go · 原稿行号 97–104
resp, err := client.AddAlias()   // 添加当前客户端证书的别名——连参数都不用传
if err != nil {
    log.Fatalf("add alias failed: %v", err)
}
if resp.Code != common.TxStatusCode_SUCCESS {
    log.Fatalf("add alias failed: code=%d, msg=%s", resp.Code, resp.Message)
}
fmt.Printf("add alias success: alias=%s\n", certAlias)

第二类:直接返回解包好的结构体(查询类,拿到就能用)。

sdk-go-demo/03-block-query/main.go · 区块与链信息
// 03-block-query/main.go:22
height, err := client.GetCurrentBlockHeight()                // uint64
// 03-block-query/main.go:31
blockInfo, err := client.GetBlockByHeight(height-1, false)   // *common.BlockInfo
// 03-block-query/main.go:67
chainInfo, err := client.GetChainInfo()                      // *discovery.ChainInfo,含节点列表

第二类连两层状态码都替你处理了,失败直接体现在 err;第一类要自己检查。

真实例子:存证写入 + 查询

sdk-go-demo/01-quickstart/main.go · 原稿行号 63–89(节选)
kvs := []*common.KeyValuePair{
    {Key: "time", Value: []byte(curTime)},
    {Key: "file_hash", Value: []byte(fileHash)},
    {Key: "file_name", Value: []byte(fmt.Sprintf("file_%s", curTime))},
}

invokeResp, err := client.InvokeContract(claimContract, "save", "", kvs, -1, true)
...
queryResp, err := client.QueryContract(claimContract, "find_by_file_hash", queryKvs, -1)
fmt.Printf("query contract success: result=%s\n", string(queryResp.ContractResult.Result))
  • 参数是 []*common.KeyValuePair:string 键 + []byte 值, 名字和编码按合约约定。
  • 检查顺序:err → resp.Code == SUCCESS → ContractResult.Code == 0 → 解析 Result。
  • ContractResult.Result 是 []byte:本例 string(...) 打印;JSON 就 json.Unmarshal,协议由合约说了算。

什么时候够用

判断标准:这个操作只需你一个人的签名。 业务调用、只读查询、证书别名、Gas 查询——都够用。

证书别名:不要重复 AddAlias

一个真实的坑(06-cert-alias/main.go:85-89 注释):配置了 alias 时 NewChainClient 自动把别名上链,此后 sender 变为 ALIAS 类型, 再手动调 AddAlias() 会被链端拒绝。

4. 复杂场景:多方签名

触发条件:链的权限策略要求多个身份对同一操作签名——部署/升级/冻结合约、改链配置。

三步模式(真实部署代码)

sdk-go-demo/01-quickstart/main.go · 原稿行号 37–55
payload, err := client.CreateContractCreatePayload(
    claimContract, "2.0.0",
    "../testdata/contracts/wasm/evidence/rust-fact-2.0.0.wasm",
    common.RuntimeType_WASMER, nil,
)
if err != nil {
    log.Fatalf("create contract payload failed: %v", err)
}

endorsers, err := util.AdminEndorsers(payload)
if err != nil {
    log.Fatalf("make endorsers failed: %v", err)
}
resp, err := client.SendContractManageRequest(payload, endorsers, 5, true)
if err != nil {
    log.Fatalf("send contract manage request failed: %v", err)
}
util.MustSuccess(resp, "deploy contract")
fmt.Printf("contract deployed: blockHeight=%d, txId=%s\n\n", resp.TxBlockHeight, resp.TxId)

一句话分工:

  1. CreateContractCreatePayload 描述“要做什么”,不发送。
  2. AdminEndorsers 各背书方 对同一份 Payload 分别签名(改一个字节全部重签)。
  3. SendContractManageRequest 补上发起者签名并发送。

AdminEndorsers 不是 SDK 接口,是 demo 的辅助函数(sdk-go-demo/pkg/util/util.go:31)——演示环境把 4 个组织 admin 私钥都放本地的便利写法。生产环境私钥分散,应各自签名后收集,或走多签系统合约。

查表速判

场景 模式 代表接口
业务调用、查询、别名 一步到位 InvokeContract / QueryContract / AddAlias
部署/升级/冻结合约 Payload + 背书 + Send CreateContractCreatePayload + SendContractManageRequest
链配置更新 Payload + 背书 + Send CreateChainConfig*Payload + SendChainConfigUpdateRequest
自己控制请求结构 显式两步 GetTxRequest(...) → SendTxRequest(...)

5. 底层:一次请求的完整流程

前面两种模式,底层共用同一条流水线。

flowchart LR
    subgraph APP["调用方"]
        direction TB
        K["1. 准备参数<br/>合约 · 方法<br/>kvs 参数列表"]
        C["5. 检查返回<br/>err / resp<br/>两层状态码"]
        D["6. 解析结果<br/>按协议解码<br/>校验业务字段"]
        C --> D
    end

    subgraph SDK["chainmaker-sdk-go"]
        direction TB
        P["2. 构造 Payload<br/>链ID · 请求类型<br/>合约 · 方法 · 参数"]
        R["3. 组装并签名<br/>TxRequest<br/>载荷 + 发起者签名"]
        S["4. 发送请求<br/>SendTxRequest<br/>timeout · withSyncResult"]
        T["返回 TxResponse<br/>外层状态 · TxId<br/>ContractResult"]
        P -- "GenerateTxRequest" --> R
        R -- "已签名请求" --> S
        S -. "发送调用返回" .-> T
    end

    subgraph REMOTE["RPC 调用边界"]
        direction TB
        RPC["RPC 接口<br/>请求发出<br/>响应返回"]
    end

    K -- "传入参数" --> P
    S -- "gRPC 发送 TxRequest" --> RPC
    RPC -. "gRPC 返回 TxResponse" .-> T
    T -. "返回 resp / err" .-> C

    classDef box fill:#ece8ff,stroke:#a78bfa,color:#333333
    class K,C,D,P,R,S,T,RPC box
    linkStyle 1,2,4,5 stroke:#dc2626
    linkStyle 3,6,7 stroke:#2563eb

读图:红实线向右 = 请求发出;蓝虚线向左 = 响应返回。InvokeContract 内部就是 2→3→4。

请求怎么组装

层 结构 关键字段
参数 KeyValuePair 列表 Key: string / Value: bytes,放入 Payload.Parameters
载荷 Payload ChainId / TxType / ContractName / Method / TxId / Timestamp
请求 TxRequest Payload + Sender(发起者签名)+ 按需 Endorsers / Payer

边界:签名后改 Payload 必须重新签名;构造完成 ≠ 已发送。

响应怎么检查(四步,不跳步)

层级 字段 说明
① Go 返回值 err 构造/签名/通信/等待阶段的错误
② 外层 resp.Code / Message 交易状态码,对照 TxStatusCode_SUCCESS
② 外层 TxId / TxBlockHeight 跟踪标识与元数据; 有 TxId ≠ 已上链
③ 合约层 ContractResult.Code 0 才是合约执行成功,与外层不是一回事
④ 数据 ContractResult.Result 原始 []byte,按协议解码

异步提交成功不等于写入成功

异步提交的坑:withSyncResult=false 时拿到的是提交响应,Code=SUCCESS 不能 作为写入成功的依据——保留 TxId,用 GetTxByTxId 或订阅确认最终结果。

6. 订阅:从一次响应到持续数据流

轮询要反复调 GetBlockByHeight 并自己维护游标;订阅由节点在数据产生时 主动推送。

演示 2:区块、交易与合约事件三路订阅
运行 07-subscribe
cd /path/to/sdk-go-demo/07-subscribe && go run main.go

三路订阅同时滚动输出(跑 30 秒自动退出):

订阅输出
recv block header: height=..., hash=...
recv tx: txId=...
recv contract event: height=..., contractName=claim001

订阅接口与返回类型

接口 通道元素类型 关键参数
SubscribeBlock(ctx, start, end, withRWSet, onlyHeader) *BlockHeader 或 *BlockInfo onlyHeader=true 时忽略 withRWSet
SubscribeTx(ctx, start, end, contractName, txIds) *Transaction 空合约名=全部
SubscribeContractEvent(ctx, start, end, name, topic) *ContractEventInfo 单 topic
SubscribeContractEvents(ctx, start, end, name, topics []string) *ContractEventInfo 多 topic(v2.4.1 新增)

高度语义速查

start / end 含义
-1, -1 实时订阅最新
0, -1 从头补历史,再持续实时
0, 10 补完区间即结束(channel 关闭)
起始 > 当前高度 / 起始 > 终止 / < -1 报错

生命周期:ctx 取消或 client.Stop() → 退出收流;channel 关闭后 ok=false 必须处理,否则死循环读零值。

7. 订阅优化:多节点自动择优 + 断点续订

解决三个真实痛点

  1. 连到落后节点:连接池里多个节点,可能正好连着慢的。
  2. 断流丢数据:连接抖动 / EOF 后要 从已收到的位置续订,不是从头再来。
  3. 消费端太慢:通道缓冲 256,堆积有风险。

机制

flowchart LR
    START["SubscribeBlockWithOptimize<br/>SubscribeContractEvent(s)WithOptimize"] --> CHK{"config.optimizeDetection > 0<br/>且 nodeList > 1 ?"}
    CHK -- 否 --> PLAIN["降级为普通订阅<br/>(事件订阅直接报错返回)"]
    CHK -- 是 --> SUB["actualSubscribe()<br/>建立 gRPC 流 + 收流协程"]
    SUB --> TICK["定时器 optimizeDetection 秒"]
    TICK --> IDLE{"距上次收到数据<br/>超过 switchTimeDiff 秒 ?"}
    IDLE -- 否 --> TICK
    IDLE -- 是 --> PROBE["getLastBlockHeight()<br/>用当前最优连接探高度"]
    PROBE --> DIFF{"最优高度 - 已推送高度 > 0 ?"}
    DIFF -- 否 --> TICK
    DIFF -- 是 --> NEWREQ["switchHandler.NewSubscribeTxReq()<br/>以 pushedBlockHeight 为起点重建请求"]
    NEWREQ --> NIL{"返回 nil ?<br/>(已到 endBlock)"}
    NIL -- 是 --> EXIT["正常退出,关闭 channel"]
    NIL -- 否 --> CANCEL["curCancel() 取消旧订阅"]
    CANCEL --> SUB
    SUB -. "收到 error 连续 >= 10 次" .-> EXIT

关键结论:会不会自动升级?分类型

区块订阅会,事件订阅不会:

sdk-go/sdk_subscribe.go · 原稿行号 56–66
func (cc *ChainClient) SubscribeBlock(ctx context.Context, startBlock, endBlock int64, withRWSet,
    onlyHeader bool) (<-chan interface{}, error) {
    // 增加使用乐观订阅处理的机制
    if !cc.config.disableSubscribeOptimize && len(cc.config.nodeList) > 1 && cc.config.optimizeDetection > 0 {
        return cc.SubscribeBlockWithOptimize(ctx, startBlock, endBlock, withRWSet, onlyHeader, -1, -1)
    }

    payload := cc.CreateSubscribeBlockPayload(startBlock, endBlock, withRWSet, onlyHeader)

    return cc.Subscribe(ctx, payload)
}

SubscribeContractEvents 的注释明确写着“ 不会自动启用优化模式 ”(sdk_subscribe.go:203)——事件订阅必须显式调 SubscribeContractEvent(s)WithOptimize。

参数与配置

参数 / 配置 语义 默认
optimizeDetection(接口参数) 检测周期(秒) <=0 取 15s
switchTimeDiff(接口参数) 静默多久才探测切换(秒) <=0 取 10s
optimize_detection(配置) 连接择优周期; 仅多节点生效 60s;关闭设 -1
disable_subscribe_optimize(配置) 关闭区块订阅自动升级 false

版本要求:优化订阅自 v2.3.7;合约事件优化要求底链 ≥ v2.3.6。

演示 3:多节点环境下的优化订阅
运行 SDK 的 optimize 示例
cd /path/to/sdk-go/examples/subscribe/optimize && go run main.go

观察:30s 超时自动退出;事件返回 ContractEventInfoList;停掉一个节点可看到日志:

Subscriber switching connection conditions are met → restart subscription success

两个必讲的坑

返回类型与降级策略都不一样

  • 普通事件订阅返回 *ContractEventInfo( 单个,SDK 拆开逐个推);优化版返回 *ContractEventInfoList——断言类型不同,套错会 panic;且 ContractEvents 可能为空,直接 [0] 越界。
  • 单节点或未配 optimize_detection 时,事件订阅优化 直接报错不降级;区块订阅则降级为普通订阅。

8. 归档:链上瘦身与链外查询

一句话:归档把旧区块搬到链外存储,节点存储有界;归档数据仍可查、可恢复。

数据去哪由配置决定(archive: 段):mysql(自建库)或 archivecenter(归档中心,配合 archive_center_query_first 让查询优先走链外)。

四个操作(摘自 08-archive/main.go,完整程序可运行)

sdk-go-demo/08-archive/main.go · 状态、归档与恢复
// 08-archive/main.go:111 —— 查节点归档状态
status, err := client.GetArchiveStatus()
// 08-archive/main.go:130 —— 当前可归档高度
height, err := client.GetArchivedBlockHeight()
// 08-archive/main.go:139 —— 归档到该高度,回调逐块上报进度
err = client.ArchiveBlocks(height, "quick", func(msg sdk.ProcessMessage) error {
    fmt.Printf("archive progress: %+v\n", msg)
    return nil
})
// 08-archive/main.go:151 —— 恢复归档区块回节点
err = client.RestoreBlocks(1, "quick", func(msg sdk.ProcessMessage) error { ... })

ArchiveBlocks 内部自动做:查状态 → 算起止高度 → 归档服务未注册时用创世块注册 → 逐块搬运。

第二个参数 mode 是 保留参数,当前未使用——传什么都没语义。

归档后查询 + 三个坑

归档后的区块 不能 再用 GetBlockByHeight,改用 GetArchivedBlockByTxId / GetArchivedBlockByHash / GetArchivedBlockByHeight / GetArchivedTxByTxId。

归档任务不要并发跑

  1. 高度截断:超过 MaxAllowArchiveHeight 的部分直接截掉。
  2. 不允许跳块:节点与归档服务高度不连续报 not match。
  3. 串行调度:任一方 in process 时再次发起直接报错——归档任务别并发跑。
演示 4:归档与恢复(需 MySQL / 归档中心)
运行 08-archive
cd /path/to/sdk-go-demo/08-archive && go run main.go

9. 版本时间线:2.3.7 → 2.4.1 用户可感知变更

v2.3.7:订阅体验升级

  • 优化订阅区块:SubscribeBlockWithOptimize,多节点自动择优 + 断点续订。
  • 优化订阅合约事件:SubscribeContractEventWithOptimize(底链需 ≥ v2.3.6)。
  • 新增配置项:optimize_detection(连接择优周期)、node_only_async(老链兼容)。

v2.3.8:多签升级

  • 多签 V2 方法族:MultiSignContractReq/Vote/Trig V2 系列 + Payer / GasLimit 变体。
  • 区块高度查询优化:GetCurrentBlockHeight 走新接口,低版本底链自动回退。

v2.3.12:运维信息增强

  • 查节点上所有链:GetChainMakerServerMessage。
  • 查服务端完整响应:GetChainMakerServerInfo。

v2.4.1:治理与可观测

  • 合约 Owner 转移:CreateContractTransferOwnerPayload(SDK 侧入参预校验)。
  • 查询合约字节码:GetContractBytecode。
  • 资源白名单:增/删/改/查 4 个接口。
  • 账户序列号:GetAccountSequence / GetCurrentAccountSequence。
  • 多 topic 事件订阅:SubscribeContractEvents(+WithOptimize),单 topic 版保留为兼容壳。

明细与证据表见原分享内部大纲 §8;正式发布口径另需确认1。

10. 总结

  1. 五分钟搭好工程(配置 + 证书 + 合约四件套)就能跑通第一个程序。
  2. 日常调用用一步到位的方法;多方签名才升级到 Payload + 背书 + 发送。
  3. 底层流程 KV → Payload → 签名 Request → 发送 → 状态检查 → 业务解码,解释了便捷方法做了什么。
  4. 订阅 把一次响应扩展成持续数据流; 优化订阅 再加节点探测与切换续订。
  5. 归档 让链上存储有界:先查状态再搬运、串行调度、归档后改用 GetArchived*。

学习路径

从入门到专题的递进路径
入门:01-quickstart(5m) → 02-contract-lifecycle(15m) → 03-block-query(10m)
核心:04-chain-config → 05-cert → 06-cert-alias → 07-subscribe(10m)
进阶:08-archive → 09-multisign → 10-gas → 11-payer → 12-pubkey → 13-tx-pool → 14-consensus-sync → 15-transaction
专题:16-privacy → 17-evm

两套代码资产:

  • sdk-go-demo:01→17 递进式学习路径。
  • sdk-go/examples:按接口分类的 API 参考,本文演示 3 出自这里,其余演示出自教程仓库。

附录 A:SDK 配置项速查

依据 sdk-go/utils/config.go 配置模型与 testdata/configs/ 6 套真实模板核对。

配置文件全览(cert + TLS 模板骨架)

完整 sdk_config.yml 骨架:点击可折叠
sdk_config.yml · cert + TLS 模板骨架
chain_client:
  # ───────── 基础身份 ─────────
  chain_id: "chain1"                # 链 ID,与目标链创世配置一致
  org_id: "wx-org1.chainmaker.org"  # 组织 ID(cert 模式)
  # auth_type: permissionedWithCert # 认证模式;pk 为 public,pwk 为 permissionedWithKey
  # alias: my_cert_alias            # 证书别名:自动上链,此后交易用别名替代全量证书
  enable_normal_key: false          # TxId 生成方式:默认 TimestampKey
  # crypto: { hash: SHA256 }        # 签名哈希算法(pk 模板必配,与节点一致)

  # ───────── 证书与密钥 ─────────
  user_key_file_path: ".../client1.tls.key"        # TLS 私钥(连接)
  user_crt_file_path: ".../client1.tls.crt"        # TLS 证书(连接)
  # user_key_pwd: "123"                            # 私钥密码(加密 PEM 时)
  user_sign_key_file_path: ".../client1.sign.key"  # 交易签名私钥(业务身份,不允许用 TLS 私钥)
  user_sign_crt_file_path: ".../client1.sign.crt"  # 交易签名证书(不允许用 TLS 证书)
  # user_sign_key_pwd: "123"
  # user_enc_key_file_path: ".../client1.tls.enc.key"  # 国密 GMTLS 双证书体系才需要
  # user_enc_crt_file_path: ".../client1.tls.enc.crt"

  # ───────── 同步重试 / 订阅优化 ─────────
  node_only_async: false            # 仅老版本底链(<v2.3.3)需 true
  retry_limit: 20                   # 轮询交易结果最大次数;删除或 <=0 走纯同步
  retry_interval: 500               # 轮询间隔(ms)
  optimize_detection: 60            # 连接择优周期(s),仅多节点生效;关闭设 -1
  disable_subscribe_optimize: false # true 则关闭区块订阅自动升级
  # proxy: "http://user:pass@host:8080"

  # ───────── 节点连接(可多条) ─────────
  nodes:
    - node_addr: "127.0.0.1:12301"      # 节点 RPC 地址
      conn_cnt: 10                       # 连接数
      enable_tls: true                  # 与节点 rpc.tls 一致
      trust_root_paths:                  # 节点 CA,连别人的链必须换
        - ".../wx-org1.chainmaker.org/ca"
      tls_host_name: "chainmaker.org"    # 与节点证书 SAN 一致
      chain_tls_host_name: ""            # nginx 按 X-Server-Name 分流时使用

  # ───────── 归档 ─────────
  archive:
    type: "mysql"                   # archivecenter(归档中心)/ mysql
    dest: "root:pwd:host:3306"      # user:pwd:host:port
    secret_key: xxx                  # 归档数据加密密钥
  archive_center_query_first: false  # true 且配置归档中心时,查询优先走归档中心
  # archive_center_config: { ... }  # 归档中心 HTTP/RPC 地址、TLS、消息大小

  # ───────── RPC 客户端 ─────────
  rpc_client:
    max_receive_message_size: 100   # 单条 gRPC 消息上限(MB)
    max_send_message_size: 100
    send_tx_timeout: 60              # 发送交易超时(s)
    get_tx_timeout: 60               # 查询交易超时(s)

  # ───────── 安全扩展(默认关闭) ─────────
  pkcs11:
    enabled: false                  # 硬件加密机;私钥不落盘场景
  kms:
    enabled: false                  # 云密钥管理服务

pk 模式差异:无 org_id、无签名证书,显式 auth_type: public 与 crypto.hash;证书路径从 crypto-config-pk/ 取。 pwk 同理,auth_type: permissionedWithKey。

一句话分清两组证书:

TLS 证书
管“连得上”。
签名证书
管“交易算谁发的”。

附录 B:SDKInterface 接口索引

依据 sdk-go/sdk_interface.go:63-111:SDKInterface 采用 接口组合设计——按需依赖特定功能、Mock 测试时可只实现所需接口。ChainClient 实现全部 16 个子接口,方法都以 client.Xxx(...) 直接调用。

子接口 职责 代表方法(节选) 定义位置 对应教程
ChainClientBasic 生命周期与版本 Stop、GetChainMakerServerVersion、GetChainMakerServerMessage/Info :303 01 · 99-probe
ContractManager 合约全生命周期 CreateContractCreate/Upgrade/Freeze/Unfreeze/RevokePayload、TransferOwnerPayload、InvokeContract、QueryContract、GetTxRequest、SendTxRequest :565 01 · 02 · 17
BlockQuery 区块/交易查询 GetTxByTxId、GetBlockByHeight/ByHash/ByTxId、GetLastBlock、GetChainInfo :871 01 · 03
ChainConfigManager 链配置 GetChainConfig、GetChainConfigSequence、CreateChainConfig*Payload、SendChainConfigUpdateRequest :392 04
CertManager 证书管理 AddCert、DeleteCert、QueryCert、GetCertHash、SendCertManageRequest :338 05
CertAliasManager 证书别名 AddAlias、QueryCertsAlias、UpdateCertByAlias、DeleteCertsAlias :213 06
SubscribeService 订阅 SubscribeBlock/Tx/ContractEvent(s)、ByPreAlias/ByPreTxId/ByPreOrgId、*WithOptimize、SubscribeWithTxReq :966 07(§6–§7)
ArchiveManager 数据归档 GetArchiveStatus、ArchiveBlocks、RestoreBlocks、GetArchivedBlockBy*、GetArchivedTxByTxId :254 08(§8)
MultiSignManager 多签 MultiSignContractReq/Vote/Trig/Query(+V2、+Payer、+GasLimit 变体) :743 09
GasManager Gas 管理 GetGasAdmin、GetGasBalance、EstimateGas、AttachGasLimit、CreateRechargeGasPayload、FrozenGasAccountPayload :646 10
PayerManager Gas 代付 SetContractMethodPayer、UnsetContractMethodPayer、QueryContractMethodPayer、QueryTxPayer :821 11
PubkeyManager 公钥身份 CreatePubkeyAdd/Del/QueryPayload、SendPubkeyManageRequest :842 12
TxPoolManager 交易池 GetPoolStatus、GetTxIdsByTypeAndStage、GetTxsInPoolByTxIds :1025 13
ConsensusSyncManager 共识与同步 GetConsensusValidators、GetConsensusHeight、GetSyncState、同步规则系列 :525 14
TransactionManager 交易治理 CreateTxBlacklistAdd/DeletePayload、GetTxBlacklist、SendTransactionManagerRequest :1044 15
HibeService 层级属性加密 CreateHibeTxPayloadParamsWithHibeParams、QueryHibeParamsWithOrgId、DecryptHibeTxByTxId :707 examples/hibe

找接口的路径:SDKInterface 组合(sdk_interface.go:63)→ 子接口定义与完整方法签名 → sdk-go/examples/<模块>/main.go 抄可运行片段。EVM 无独立接口:通过 RuntimeType_EVM + GetEVMAddressFrom* 工具函数体现。


最后更新:2026-10-09


  1. 原稿基线说明:v2.4.1 无 tag 仅分支,正式发布口径以 Release owner 确认为准。此处记录的是原分享基线,不代表后续发布状态。 ↩

评论