Hyperledger Fabric是由Linux基金会主导的一个开源企业级分布式账本框架,旨在为各行业提供模块化、可扩展、安全的解决方案,相较于公有链,Fabric的许可制特性、可插拔组件以及支持智能合约(链码)的多种编程语言,使其成为企业和开发者构建区块链应用的热门选择,本教程将带你一步步走进Hyperledger Fabric的开发世界,从环境搭建到应用部署,助你打下坚实基础。
Hyperledger Fabric 核心概念解析
在动手开发之前,理解Fabric的核心概念至关重要:
- 成员服务提供商 (MSP, Member Service Provider):负责定义和管理身份,包括用户、节点和组织,它确保了网络中参与者身份的真实性和合法性。
- 通道 (Channel):账本数据的隔离机制,每个通道是一个独立的账本,仅包含特定参与者(组织)的交易信息,实现了数据的隐私和保密。
- 排序服务 (Ordering Service):负责对来自不同通道的交易进行排序和打包,确保所有节点对交易顺序达成一致。
- 对等节点 (Peer):网络的基本组成部分,负责维护账本、执行链码、验证交易和响应查询,每个组织通常运行多个Peer节点。
- 链码 (Chaincode):运行在Peer节点上的智能合约,用于定义和操作账本上的资产(业务逻辑),可以是Go、Java或TypeScript编写。
- 账本 (Ledger):由两个部分组成:
- 区块链 (Blockchain):按时间顺序记录交易数据的数据结构,一旦写入不可篡改。
- 状态数据库 (State Database):存储当前资产状态的数据库,方便快速查询。
- 应用 (Application):与区块链网络交互的客户端应用,通过SDK调用链码功能,发起交易或查询账本。
开发环境准备
Fabric开发环境的搭建相对复杂,推荐使用官方提供的fabric-samples和Fabric Docker镜像来简化流程。
-
基础软件安装:
- 操作系统:推荐Ubuntu 20.04 LTS (或CentOS 7+),也可在macOS和Windows上通过WSL2进行。
- Docker:容器化运行环境,用于启动Fabric网络组件,请确保安装Docker和Docker Compose。
- Go:链码主要开发语言之一,建议Go 1.18+。
- Node.js & npm:用于开发基于Node.js的链码和应用程序,建议Node 14+ LTS。
- Python (可选):如果使用Python链码。
- Git:用于克隆代码仓库。
-
Hyperledger Fabric Samples, Binaries and Docker Images:
- 克隆官方示例仓库:
git clone https://github.com/hyperledger/fabric-samples.git cd fabric-samples
- 下载二进制文件和Docker镜像(脚本会自动处理):
cd first-network ./byfn.sh generate # 或者使用脚本下载最新版本 curl -sSL https://bit.ly/2ysbOFE | bash -s -- 2.4.1 1.5.0
- 将下载的二进制文件(
peer,osnadmin,configtxgen,configtxlator等)添加到系统PATH环境变量中。
- 克隆官方示例仓库:
构建你的第一个Fabric网络:First Network示例
fabric-samples/first-network提供了一个“一键启动”的示例网络,包含两个组织(Org1和Org2)各两个Peer节点,一个排序服务,以及一个通道。
-
生成网络配置与证书:
- 进入
first-network目录:cd fabric-samples/first-network
- 运行
generate.sh脚本生成必要的配置文件(crypto-config)和创世区块:./byfn.sh generate
- 进入
-
启动网络:
- 运行
up.sh脚本启动网络并创建通道:./byfn.sh up
- 脚本会自动执行通道创建、Peer加入、锚点Peer更新以及链码安装、实例化和调用等操作,成功后,你会看到交易测试成功的输出。
- 运行
-
关闭网络:
- 测试完成后,可运行
down.sh脚本停止并删除容器、网络:./byfn.sh down
- 测试完成后,可运行
开发自己的链码 (Chaincode)
链码是Fabric应用的核心逻辑,下面以Go语言为例,编写一个简单的“资产转移”链码。
-
链码结构:
- 通常包含
Init(初始化)和Invoke(调用)两个主要函数。 Init用于初始化资产状态,Invoke用于执行具体的业务逻辑(如查询、创建、转移资产)。
- 通常包含
-
示例链码 (asset-transfer-basic):
- 在
fabric-samples/chaincode/asset-transfer-basic/chaincode-go/目录下已有示例代码。 - 我们可以基于此修改或创建新的链码文件,例如
my_chaincode.go:
package main import ( "fmt" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) // SmartContract provides functions for managing an Asset type SmartContract struct { contractapi.Contract } // Asset describes basic details of what makes up a simple asset type Asset struct { ID string `json:"ID"` Owner string `json:"owner"` Value int `json:"value"` } // InitLedger adds a base set of assets to the ledger func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error { assets := []Asset{ {ID: "asset1", Owner: "Alice", Value: 100}, {ID: "asset2", Owner: "Bob", Value: 200}, } for _, asset := range assets { assetJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(asset.ID, assetJSON) if err != nil { return fmt.Errorf("failed to put to world state. %v", err) } } return nil } // CreateAsset issues a new asset to the world state with given details func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, owner string, value int) error { exists, err := s.AssetExists(ctx, id) if err != nil { return err } if exists { return fmt.Errorf("the asset %s already exists", id) } asset := Asset{ ID: id, Owner: owner, Value: value, } assetJSON, err := json.Marshal(asset) if err != nil { return err } return ctx.GetStub().PutState(id, assetJSON) } // ReadAsset returns the asset stored in the world state with given id func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) { assetJSON, err := ctx.GetStub().GetState(id) if err != nil { return nil, fmt.Errorf("failed to read from world state: %v", err) } if assetJSON == nil { return nil, fmt.Errorf("the asset %s does not exist", id) } var asset Asset err = json.Unmarshal(assetJSON, &asset) if err != nil { return nil, err } return &asset, nil } // AssetExists returns true when asset with given ID exists in world state func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { assetJSON, err := ctx.GetStub().GetState(id) if err != nil { return false, fmt.Errorf("failed to read from world state: %v", err) } return assetJSON != nil, nil } func main() { assetChaincode, err := contractapi.NewChaincode(&SmartContract{}) if err != nil { fmt.Printf("Error creating asset chaincode: %v", err) return } if err := assetChaincode.Start(); err != nil { fmt.Printf("Error starting asset chaincode: %v", err) } }
- 在
-
链码打包与安装:
- 打包:将链码代码打包为tar.gz文件。
peer chaincode package mycc.tar.gz --path ./chaincode/asset-transfer-basic/chaincode-go/ --lang golang --label mycc_1
- 打包:将链码代码打包为tar.gz文件。