☰
超级账本Fabric智能合同区块链:从链码开发到避坑实践
2026/10/3 2:56:39 网站建设 项目流程

简介:面向高校毕业设计、期末大作业及区块链课程实践,这份基于Hyperledger Fabric打造的智能合同区块链项目,提供了完整可运行的高分源码与全部配套资料,可帮助解决智能合同场景下链码开发、网络搭建与项目选型等常见难点。压缩包共1040个文件,以Go语言源码为主(828个),覆盖核心链码与业务逻辑;另有Markdown文档33个,说明架构设计与部署步骤;YAML、Shell、JSON、Dockerfile等配置文件,便于完成Fabric网络初始化与容器化部署。整体约3.79MB,目录结构清晰,便于按模块检索学习。已有109人学习下载,参考热度良好。项目曾获98分毕业设计高分,代码经测试运行成功、功能完整,适合具备一定区块链基础的学生参考其Fabric多组织网络配置、智能合约编排及项目文档撰写方法,或直接复用部分模块以加速自身课设、毕设进度,是理论与实践结合的完整范本。

1. 智能合同区块链:Fabric 不是拿来跑“币”的,是拿来跑“合同状态”的

拿到“基于 Hyperledger-Fabric 打造的智能合同区块链”这个毕业设计标题,第一反应很可能是“这不就是写智能合约吗”?真做起来你会发现,Fabric 世界里没有 Solidity 合约,只有装在 Peer 节点上的链码(Chaincode),而且链码通常用 Go 或 Node.js 写。所谓“智能合同”,把它落在 Fabric 上,最靠谱的翻译是:从合同起草、审批、签署、履行到归档的全生命周期状态流转,加上防篡改存证。这套方案的价值也正在这里——它不是为了发币,而是为了让你能向答辩老师证明“我在联盟链里做了一个多角色协作、权限可校验、账本可审计的合同管理系统”。

这篇笔记会从 Fabric 网络结构讲起,一路走到链码状态机、私有数据集合、CouchDB 富查询,最后给你整理五条实打实的踩坑记录。适合两类人:一类是拿这个题目做毕业设计、需要本地跑通并讲清楚原理的同学;另一类是第一次接触联盟链、不想碰 PoW 挖矿想直接看业务落地的开发者。看完你至少能回答三个问题:它是什么,怎么做,坑在哪里。

2. Fabric 网络里谁在做什么:把账本、节点、链码和“智能合同”对上号

2.1 节点角色与通道:一张表看清 Fabric 的权限隔离

Hyperledger Fabric 和公链最大的区别是“身份先行”。在以太坊里,谁都能部署合约,合约面前人人平等;而 Fabric 里的每一个操作都对应着一个已注册的数字身份,身份由 CA(证书颁发机构)签发,组织(Org)再把这些身份聚合成 MSP(成员服务提供者)。实操里最常见的误区,是把 Fabric 的 Peer 和区块链网络混为一谈,其实 Peer 只是一个“节点工人”。

组件职责在智能合同场景里的对应物
Peer 节点接收交易提案、执行链码、维护世界状态与账本合同业务的受理窗口
Orderer 节点对交易排序并打包出块,不执行业务逻辑公证处
CA 节点给组织、管理员、Peer 签发证书身份注册中心
Channel 通道逻辑上的隔离网络,不同通道账本互相不可见不同的业务线(如采购合同、销售合同)
Chaincode 链码安装在 Peer 上的业务逻辑代码智能合同的状态机
世界状态当前所有合同数据的最新取值合同台账

我在本地搭这个项目时,用的拓扑是“两组织一通道”:Org1 做合同发起方,Org2 做审批方,两个 Peer 各自装着同一个链码,Orderer 用 Raft 模式排序。为什么要两个组织而不是一个?因为智能合同的核心卖点是“多方对账”,只有一个组织的网络跑得再好,答辩时一问“多方怎么互信”就会露馅。

2.2 排序服务与状态数据库:为什么智能合同场景默认配 CouchDB

Fabric 的 Orderer 在 2.x 之前有一个经典选项叫 Solo,单节点排序,代码里几行就能跑通,但生产环境不能用。2.x 以后官方主推的是 Raft(etcdraft),Currenly fabric-samples 里的 test-network 默认使用的就是 Raft 排序。毕设场景我建议直接用 Raft,理由不是“生产级”,而是 solo 模式在网络重启、节点掉线后太容易给你表演“区块高度不增长”的玄学问题。

状态数据库这边有两个选择:LevelDB 和 CouchDB。LevelDB 是键值存储,读快、内存占用低,但只支持按键查询;CouchDB 支持 JSON 富查询,能写范围查询和组合条件。智能合同场景里最常见的需求是什么?“查我名下的合同”“查金额大于 10 万的合同”“查已签署但未履行的合同”,这些用 LevelDB 做会疼到你怀疑人生。我一般默认给这个项目配 CouchDB,并且在链码包里放好索引文件,否则 Fabric 的富查询会因为没建索引直接报错。

2.3 链码与智能合约的名词之争:源码到底写在哪个目录

标题里写的是“智能合同”,网上一搜“智能合约源码”出来一堆 Solidity 文件,但 Hyperledger-Fabric 的链路是:你写 Go 或 Node.js 源码,打包成 .tar.gz,通过 peer lifecycle chaincode install 装到 Peer 容器里,再由链码容器执行。这个“源码”和以太坊那种部署到 EVM 的字节码完全是两回事。

写代码的位置也有讲究。fabric-samples 目录下通常会有一个链码目录,例如 fabric-samples/contract/,你在自己的项目里可以建一个 chaincode/ 目录,里面放 go.mod、智能合同业务代码和辅助工具。注意 Fabric 2.x 的链码不再靠 GOPATH 找源码路径,install 时用的是“打包好的 tar.gz”,所以源码放哪不重要,重要的是 package 命令里的 label 和 .tar.gz 路径要对。把源码放在一个独立于 docker-compose 文件的目录里,方便打包,也方便后续链码升级时重新 build。

3. 把网络从零拉起来:cryptogen、configtxgen 到 peer lifecycle 的整条命令链

3.1 证书与创世区块:cryptogen 和 configtxgen 的最小用法

本地开发我不用 Fabric CA,而是用 cryptogen 快速生成一套测试证书。crypto-config.yaml 是这个环节的输入,它定义了组织和节点数量。最小配置往往长这样:

OrdererOrgs: - Name: Orderer Domain: example.com Specs: - Hostname: orderer PeerOrgs: - Name: Org1 Domain: org1.example.com EnableNodeOUs: true Specs: - Hostname: peer0 Users: Count: 1 - Name: Org2 Domain: org2.example.com EnableNodeOUs: true Specs: - Hostname: peer0 Users: Count: 1

跑完cryptogen generate --config=./crypto-config.yaml后,会生成 crypto-config/ 目录,里面各组织的 MSP 证书都齐了。这里的EnableNodeOUs: true很关键,它让 Fabric 能区分节点身份和管理员身份,不开启会导致后面 peer channel join 时报权限问题,属于新手最容易翻车的地方之一。

接着是 configtxgen 生成创世区块和通道交易文件。configtx.yaml 里要定义两个 profile,一个是给 orderer 用的系统通道创世块,另一个是给应用通道用的通道配置:

export FABRIC_CFG_PATH=$PWD # 生成区块文件 configtxgen -profile TwoOrgsOrdererGenesis -channelID system-channel -outputBlock ./channel-artifacts/genesis.block # 生成创建通道所需的交易文件 configtxgen -profile TwoOrgsChannel -channelID contractchannel -outputCreateChannelTx ./channel-artifacts/channel.tx # 生成锚节点更新文件(可选,但多组织网络建议生成) configtxgen -profile TwoOrgsChannel -channelID contractchannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -asOrg Org1MSP

这段命令里最容易看错的是-channelID contractchannel,它决定了通道名字。后面 peer channel create 必须用同一个 ID,不然会报"failed to create channel: got unexpected status: BAD_REQUEST",我感觉这一条至少能拦住一半初次上手的人。

3.2 用 docker-compose 拉起 Peer 与 Orderer:四个必调参数

证书和区块就绪后,网络本体靠 docker-compose 起。项目根目录的 docker-compose.yaml 里四个核心容器:orderer.example.com、peer0.org1.example.com、peer0.org2.example.com,外加 stateDatabase 的 couchdb 容器。

services: peer0.org1.example.com: image: hyperledger/fabric-peer:2.5 container_name: peer0.org1.example.com environment: - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock - CORE_PEER_ID=peer0.org1.example.com - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 - CORE_PEER_LOCALMSPID=Org1MSP - CORE_PEER_TLS_ENABLED=true - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt - CORE_LEDGER_STATE_STATEDATABASE=CouchDB - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.org1.example.com:5984 - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw volumes: - ../crypto-config:/etc/hyperledger/fabric/msp - ../channel-artifacts:/etc/hyperledger/channel-artifacts depends_on: - couchdb.org1.example.com

四个参数我会重点看:CORE_VM_ENDPOINT是 Peer 用来拉起链码容器用的 docker 接口,必须指向宿主机的 docker.sock,少了它链码永远起不来;CORE_PEER_LOCALMSPID要和 organization 名字一致,否则连 channel 都加入失败;CORE_LEDGER_STATE_STATEDATABASE=CouchDB是富查询的前提;TLS 相关三个路径如果证书挂载错了,日志里会报 “unable to find valid certification path”。CouchDB 的账号密码默认是 admin/adminpw,生产里必须改掉,本地毕设可以先留着。

3.3 创建通道、安装与提交链码:五个 peer 命令串完生命周期

网络起来以后,进入 cli 容器操作。这个 cli 容器不是必须的,但胜在方便,把 peer 命令和证书路径都准备好了。按顺序执行以下命令:

docker exec -it cli bash export CORE_PEER_LOCALMSPID=Org1MSP export CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt export CORE_PEER_ADDRESS=peer0.org1.example.com:7051 export CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp/users/Admin@org1.example.com/msp # 创建通道 peer channel create -c contractchannel -f /etc/hyperledger/channel-artifacts/channel.tx \ -o orderer.example.com:7050 \ --ordererTLSHostnameOverride orderer.example.com \ --tls --cafile /etc/hyperledger/fabric/tls/orderer-ca.crt # 加入通道 peer channel join -b contractchannel.block # 打包链码 peer lifecycle chaincode package contractcc.tar.gz \ --path /opt/gopath/src/github.com/contractcc --lang golang --label contractcc_1.0

这里我要解释一下 Fabric 2.x 和 1.4 时代的最大区别:2.x 里链码不再 deploy 一次就算完,而是走install→approveformyorg→commit三步。打包时--label contractcc_1.0是给链码一个可读名字,同一个链码不同版本的 label 不能重复。peer lifecycle chaincode queryinstalled会返回一个 package-id,后续 approve 时要用它,这是最容易漏的一步。紧接着:

# 查询 package-id peer lifecycle chaincode queryinstalled # Org1 批准 peer lifecycle chaincode approveformyorg \ -o orderer.example.com:7050 \ --channelID contractchannel \ --name contractcc --version 1.0 --sequence 1 \ --package-id <上一步查到的id> \ --tls --cafile /etc/hyperledger/fabric/tls/orderer-ca.crt \ --waitForEvent # Org2 也要用同样的 package-id 批准一次 # 提交链码定义 peer lifecycle chaincode commit \ -o orderer.example.com:7050 \ --channelID contractchannel \ --name contractcc --version 1.0 --sequence 1 \ --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles ... \ --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles ...

approve 里的--sequence 1是链码定义的版本序号,每次升级要加 1。commit 时要把每个组织的 peer 地址都列出来,否则背书策略要求的组织没参与提交就会失败。到这一步,链码才真正“上线”。

4. 智能合同的核心:把合同生命周期写成链码状态机

4.1 合同数据模型:字段设计决定了后面查询的难易

很多人在链码里写合约,第一反应是“把合同文件整个上链”,这是性能上的灾难。Fabric 的账本适合存结构化数据和哈希,不适合存 PDF 原文件。我的习惯是把合同拆成两个域:元数据域上链,正文文档放到链下存储(IPFS、对象存储或自己服务器的目录),链上只保留文档哈希。

用 Go 写链码,合同主数据结构可以这样定义:

type Contract struct { ContractID string `json:"contractId"` Title string `json:"title"` PartyA string `json:"partyA"` PartyB string `json:"partyB"` Amount float64 `json:"amount"` Currency string `json:"currency"` Status string `json:"status"` DocHash string `json:"docHash"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` }

字段设计的核心是“够用、可查、可演”。ContractID 是整个合同的业务主键,后面所有操作都用它做幂等判断;DocHash 是合同 PDF 的 SHA-256 值,录入了明文哈希,才能证明“链上状态对应对的是哪一份文件”。CreatedAt 和 UpdatedAt 一定要存 ISO8601 字符串而不是 Unix 时间戳,因为 CouchDB 富查询对时间范围过滤时,ISO8601 的字符串排序天然就是时间排序。

4.2 状态机流转:草稿、审批、签署、履约的合法迁移路径

智能合同里的“智能”二字体现在状态机。合同不能用一条UpdateStatus命令乱改,而是要约束合法迁移路径。我常用的状态集合是:

当前状态允许的操作下一个状态
DRAFT 草稿SubmitContractREVIEWING
REVIEWING 审批中ApproveContract / RejectContractAPPROVED / REJECTED
APPROVED 已审批SignContractSIGNED
SIGNED 已签署StartExecutionEXECUTING
EXECUTING 履行中CompleteContractCOMPLETED
任意非终态CancelContractCANCELLED

链码里的每次状态迁移都要做校验,直接用一张 map 描述邻接关系更清晰:

var validTransitions = map[string][]string{ "DRAFT": {"REVIEWING", "CANCELLED"}, "REVIEWING": {"APPROVED", "REJECTED", "CANCELLED"}, "APPROVED": {"SIGNED", "CANCELLED"}, "SIGNED": {"EXECUTING", "CANCELLED"}, "EXECUTING": {"COMPLETED", "CANCELLED"}, } func (s *ContractContract) submitForReview(ctx contractapi.TransactionContextInterface, contractID string) error { contract, err := s.getByID(ctx, contractID) if err != nil { return err } if !contains(validTransitions[contract.Status], "REVIEWING") { return fmt.Errorf("不能从状态 %s 迁移到 REVIEWING", contract.Status) } contract.Status = "REVIEWING" contract.UpdatedAt = time.Now().Format(time.RFC3339) return s.putState(ctx, contract) }

这里要特别注意:GetState 之后必须先确认对象存在,否则空指针直接让链码 panick 崩溃,Fabrc 里链码崩溃的表现是“endorsement failure,status 500”,查日志才知道是 panic。状态机做好了,答辩时你就能画一张图,告诉老师“合同的每一步迁移都有合法性校验,非法路径直接被链码拒绝”。

4.3 私有数据集合:合同正文能不能上链

合同正文能不能直接上链?技术上能,但一是不需要,二是太贵。Fabric 账本数据会同步到所有通道成员,合同正文里往往有甲乙方名称、金额、银行账号这些敏感信息,直接写进公开账本等于把商业机密广播给了所有组织管理员。正确做法是用 Private Data Collection。

在链码目录里加一个 collections_config.json:

[ { "name": "contractDetail", "policy": "OR('Org1MSP.member', 'Org2MSP.member')", "requiredPeerCount": 1, "maxPeerCount": 2, "blockToLive": 0 } ]

链码里写私有数据用ctx.GetStub().PutPrivateData("contractDetail", key, value)。这个集合的数据会通过 gossip 协议只发给 policy 指定的组织,别的组织就算同在一个通道也看不到。毕设里如果你能把“为什么不用公开账本存正文”讲明白,老师一般会认可你有工程意识,而不只是会调接口。

4.4 身份与权限:MSPID 校验和幂等检查一个都不能少

Fabric 链码里拿调用者身份很简单:ctx.GetClientIdentity().GetMSPID()。但很多初版代码只校验了笔头字段,没校验调用者身份,结果任意组织都能审批合同,多组织网络形同虚设。我一般在 Approve 方法里强校验:

func (s *ContractContract) approve(ctx contractapi.TransactionContextInterface, contractID string) error { mspID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { return fmt.Errorf("获取调用者身份失败: %v", err) } if mspID != "Org2MSP" { return fmt.Errorf("只有审批方 Org2 可以执行审批,当前调用者来自 %s", mspID) } // ... 后续状态迁移逻辑 }

幂等检查通常放在写入前:先GetState(contractID),如果已经存在,直接返回 error 而不是静默覆盖。Fabric 的背书、排序、提交是异步链路,同一个 invoke 被重放的情况在自动化测试里很容易触发,幂等检查是给你自己的后悔药。绑定客户端身份还有一个高级玩法:把调用者证书的 CN 字段一起写进合同历史,这样“谁在什么时间批了哪一步”就变成了一条不可抵赖的链上审计日志。

5. 避坑实录:我从这个项目里踩过的五个坑(现象、原因、解决办法)

5.1 Orderer 还没就绪就 createChannel:Connection refused

现象:peer channel create报Error: error getting channel configuration from orderer: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp ... connection refused"。

原因:docker-compose 里 orderer 和 peer 是并行启动的,orderer 初始化需要时间,cli 容器一上来就去创建通道,此时 orderer 的 gRPC 服务还没监听 7050 端口。这不是玄学,是典型的容器启动顺序问题。

解决:等 orderer 日志稳定后再执行创建命令。检查方法:

docker logs orderer.example.com 2>&1 | tail -20 docker exec cli bash -c 'nc -z orderer.example.com 7050 && echo ready'

看到日志里出现Starting ...且端口探测成功,再跑 create。更保险的做法是在 docker-compose 里给 cli 加 restart: on-failure,或者干脆写一个等待脚本。

5.2 链码 install 成功但背书失败:先看 peer 容器日志再改代码

现象:peer chaincode invoke报Error: endorsement failure during invoke. response: status:500 message: chaincode error,但日志里看不到具体异常。

原因:Fabric 的链码是跑在独立容器里的,业务 panic、数据库连接失败、索引缺失都会伪装成 status 500。很多人这时候开始翻 invoke 命令的参数,纯属浪费时间。

解决:先看链码容器和 peer 容器日志:

docker logs peer0.org1.example.com 2>&1 | grep -A 20 "error\|Error\|panic"

大多数情况你会看到Error: could not assemble transaction或者具体到业务代码的报错信息。我还遇到过链码容器镜像没更新、跑的还是旧代码的情况,这时候要查docker ps里链码容器的镜像 ID 是否和最新打包版本一致。排错顺序应该是:业务日志 → 背书错误 → invoke 参数,而不是反过来。

5.3 CouchDB 启动翻车:本地卷权限和索引文件缺一不可

现象:peer 容器日志不断报CouchDB connection error: cannot connect to couchdb... EOF,或者 CouchDB 容器起来又退出。

原因:常见的两个。第一,macOS/Linux 上把 CouchDB 数据目录用 volume 挂载到宿主机,宿主机目录权限是 root,CouchDB 容器内运行用户 uid 不匹配,导致无法写入。第二,Fabric 富查询要求链码包里必须有 META-INF/statedb/couchdb/indexes/*.json 索引文件,没有索引时 Fabric 会报No index exists。

解决:本地毕设我建议数据目录使用命名卷而不是 bind mount,让 docker 自己管理权限。索引文件放在链码目录的 META-INF/statedb/couchdb/indexes/ 下,例如:

{ "index": { "fields": ["status", "amount"] }, "ddoc": "indexStatusAmountDoc", "name": "indexStatusAmount", "type": "json" }

这条索引解决“按状态查合同、按金额范围过滤”的组合查询,是智能合同仪表盘最常用的查询。没有它,Fabric 会直接拒绝你的富查询请求。

5.4 链码升级后旧数据查不到:sequence、package-id、版本号三件套

现象:链码升级到 1.1 后,新方法能调用,但查询旧合同返回空,甚至GetHistoryForKey里丢失了早期记录。

原因:你重新打包链码时用了新 label,但没有重新 install 到所有组织;或者 approve 的--sequence没加 1;或者你在第一个版本里用的 collection 名称和第二个版本不一致。Fabric 2.x 的链码升级严格依赖 sequence,每次升级必须install新包、查询新 package-id、重新approveformyorg(sequence 加 1)、最后commit,四个动作缺一不可。

解决:升级前建议把链码代码放到独立版本分支,打包命令里的 label 统一带上版本后缀(例如 contractcc_1.1),然后执行:

peer lifecycle chaincode install contractcc_1.1.tar.gz peer lifecycle chaincode queryinstalled peer lifecycle chaincode approveformyorg -C contractchannel -n contractcc -v 1.1 --sequence 2 ...

5.5 多机部署的 TLS 证书坑:系统时间与 MSP 目录

现象:两台机器都起了 Docker,peer 之间连接报TLS handshake error,或背书调用时报certificate has expired or is not yet valid。

原因:最常被忽略的是系统时间不同步。Fabric 的证书有效期很短,测试证书默认一年,如果某台机器时区错乱或时间偏差超过几十秒,TLS 握手直接失败。另一个原因是不同机器的 MSP 目录用了不一样的 crypto-config 拷贝,证书和私钥对不上。

解决:部署前先统一ntpdate或使用容器内挂载宿主机的/etc/localtime;然后再比对 crypto-config 的 md5,确保两边证书一致。我在本机调试多容器时也遇到过类似问题——时间同步是根因,跟签发的 CA 没关系,别去怀疑 cryptogen 生成器。

6. 答辩前我会做的三件事:验证、讲法和一个加分技巧

6.1 用 GetHistoryForKey 证明“链上不可篡改”

智能合同项目最怕老师问一句“它跟中心化数据库有什么区别”。与其背概念,不如现场演示一条历史轨迹。Fabric 链码里GetHistoryForKey能返回指定 key 的完整变更史:

peer chaincode query -C contractchannel -n contractcc \ -c '{"function":"GetContractHistory","Args":["CONTRACT001"]}'

输出里你会看到 DRAFT → REVIEWING → APPROVED → SIGNED 的每一步,每条记录都带着交易 ID 和时间戳。这段输出就是“不可篡改”的直观证据——普通数据库里,更新一条 UPDATE 之后旧值就没了,而链上每一步都留痕。这条命令建议提前跑好,把结果保存成文件,答辩时候直接掏出来。

6.2 答辩怎么讲:从“实现了什么”切换到“为什么这样实现”

评分标准通常看两件事:完整性(有没有跑通)和深度(有没有想过为什么)。讲框架的时候别只说“用了 Hyperledger-Fabric”,要讲清楚三个选择:为什么用联盟链而不是公链(因为合同参与方是确定的、需要准入控制);为什么用 Raft 而不是 Solo(因为 Solo 是单点,无法体现共识机制,而且新版本官方已不建议在生产用);为什么用 CouchDB(因为智能合同的检索需求是“按状态和金额过滤”,LevelDB 做不到)。这三个为什么每一句都是踩过坑之后沉淀出来的,比背论文目录有用得多。

6.3 加分项:把合同哈希接到可信时间戳上

想再多拿一分,可以做一个不限平台的小扩展:把链上合同哈希(DocHash)提交给外部时间戳服务,把时间戳证书的序列号和返回时间一起写入链码。这样合同就从“我方链上可验证”升级为“第三方可信时间证明”。

我在交付前还有最后一个习惯:删掉所有容器和数据卷,按文档从 cryptogen 一步步重跑一遍,确保没有任何隐藏在容器残留里的“假跑通”。因为见过太多项目在答辩前靠着旧数据撑场面,一清环境就翻车的例子。这一步很笨,但它是整套源码、文档、资料里最值钱的部分。希望这篇笔记能帮你把 Hyperledger-Fabric 的智能合同项目跑通,少走几条弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询