赞
踩
最近在搞Fabric,今天刚刚明白如何把自己的链码部署并能跑通
网上的中文教程完全不友好,上来直接开始写代码,我连新建什么文件夹都不知道啊!!
于是痛定思痛,爆肝了一周多的官方文档
准备自己写一个,以便帮助后来人
看懂这个教程需要两条要求:
1、会复制粘贴
2、看得懂中文
先声明,这个教程并不能帮助你搭建一个完整的包括应用程序和智能合约的项目,仅仅是教你如何部署属于自己的智能合约,这是最最最最基础的操作,这个可以让零基础的新手快速把Fabric用起来
现在开始!
目录
2.2 assetTransfer.go --> identityTransfer.go
12、使用对等生命周期 `chaincode checkcommitreadiness` 命令检查通道成员是否批准了相同的链码定义
我假定你们已经部署好了Fabric环境
且clone了Fabric-samples链码包(就是个文件夹)
这个具体位置每个人应该都不一样

这是我的
新手刚开始我建议直接从官方给的例子上改(就是改一下变量和参数而已),接下来我们去找官方给的例子
进入fabric-samples文件夹

我们后面新建自己的智能合约就从这个地方新建一个文件夹放自己的文件
比如第三行第四个(test-protocol-go)就是我自己建的
那一个文件夹应该包括啥呢?
下面用官方给的例子来给你讲一下咱应该怎么改写
看到突出显示的这一个了吧
让我们点进去看看它的内容

这是第一级
(注意,可能你这里面没有vendor,很正常,因为我这个是我起初测试的时候加里面的,不用管)
关键的是下面三个文件

先看第一个文件夹

点进去是这样的
mocks文件夹不需要我们修改,里面是官方给的文件,新建项目的话必须得有这个
咱只要把它按照这个文件夹的结构直接复制到自己的文件夹里就行
直接复制,完全不需要改!
至于smartcontract_test.go
这个是测试文件,是你写完链码之后用来测试有没有bug,没有bug再部署
所以如果你不需要测试的话也没必要有,可以不用管
这里的重点是smartcontract.go文件
这就是智能合约
我们一会就是在这个文件夹里修改,后面会细说,这个文件也要放到我们自己的项目的对应位置
现在返回上一级

这三个文件是需要copy到自己的项目里的
(你的这里可能go.sum是没有的,我记不太清这个是本来就有还是后面运行一次才生成的了,不过问题不大,有的话就一块copy,没有就可以只copy前两个)
但是红框里前两个文件是肯定有的
这几个文件都要copy到自己的项目里
至于我这里的vendor文件夹是跑了一次才生成的,所以不用管

这个test-protocol-go就是我自己新建的,名字你可以随便起,但是你记得后面对应的语句需要改成你自己的名字(需要改的时候我会提醒你)
让我们看看应该有什么文件

你如果看了前面,并按照操作来搞的话,现在应该有这四个文件(go.sum如果本来在示例没有的话那就是除了这个之外的三个)
identityTransfer.go对应的就是assetTransfer.go,我只是改成了我自己项目对应的名字
至于怎么改内容,后面会说!

chaincode里的文件就是直接复制过来就行,如上图
这样自己的智能合约文件夹就建好了
后面我们开始改代码!
我们需要改两个文件
文件名不用改,这样就行
下面的内容是官方示例的代码和我改了之后的代码的对比,可以先看
最后我会给一个总的
我加了注释,你不需要每条语句都明白,只需要知道大致每一块都是干啥的就可以
- package chaincode
-
- import (
- "encoding/json" // 包含用于 JSON 编解码的功能
- "fmt" // 提供格式化输入输出的功能
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi" // 引入 Hyperledger Fabric 的链码 API 包
- )
这个我们直接复制就能用
- // SmartContract 提供了管理资产的函数
- type SmartContract struct {
- contractapi.Contract
- }
-
- // Asset 描述了简单资产的基本细节
- // 按照字母顺序插入结构字段 => 以实现跨语言的确定性
- // Go语言在转换为JSON时会保持顺序,但不会自动排序
- type Asset struct {
- AppraisedValue int `json:"AppraisedValue"` // 资产评估价值
- Color string `json:"Color"` // 资产颜色
- ID string `json:"ID"` // 资产ID
- Owner string `json:"Owner"` // 资产所有者
- Size int `json:"Size"` // 资产尺寸
- }
这里就是定义了账本中存储的数据类型,官方示例里存的是名为Asset的资产
这个结构体描述了一个简单资产的基本细节,包括了资产的评估价值(`AppraisedValue`)、颜色(`Color`)、ID(`ID`)、所有者(`Owner`)和尺寸(`Size`)。结构体中的每个字段都使用了 `json` 标签,指定了在JSON编码/解码时字段的名称。注释中提到了按字母顺序排列字段,这有助于在不同语言之间保持字段的顺序一致性,因为Go语言会保持字段顺序进行JSON编码,但不会自动对字段进行排序。
在自己的项目里应该改成自己需要的数据类型
上面的SmartContract是提供一个类似API的功能来对账本进行操作,这个我们直接复制就行
- // SmartContract 提供了管理身份的函数
- type SmartContract struct {
- contractapi.Contract
- }
-
- // Identity 描述了简单身份的基本细节
- // 按照字母顺序插入结构字段 => 以实现跨语言的确定性
- // Go语言在转换为JSON时会保持顺序,但不会自动排序
- type Identity struct {
- ID string `json:"ID"` // 真实身份
- PID string `json:"PID"` // 假名身份
- SK string `json:"SK"` // V的私钥
- VPK string `json:"VPK"` // V的公钥
- }
- // InitLedger 向账本添加一组基本资产
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- // 创建一组资产
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- // 将每个资产存储到账本中
- for _, asset := range assets {
- // 将资产转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将JSON格式的资产存储到账本的世界状态中
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
- return fmt.Errorf("向世界状态存储失败:%v", err)
- }
- }
-
- return nil
- }
通过 `InitLedger` 函数,链码可以在启动时初始化账本,添加预设的资产信息,以便后续的交易和操作能够基于这些资产展开。每个资产都会被转换为`JSON`格式,并使用其`ID`作为键存储在账本的世界状态中。
-
- // InitLedger 向账本添加一组基本身份
- // ctx 是一个类型为 contractapi.TransactionContextInterface 的参数,可以在方法内部使用这个参数进行操作。
- // (s *SmartContract): 这部分是方法接收器(receiver),它表明这是一个属于 SmartContract 结构体的方法。
- // s *SmartContract 意味着这个方法与 SmartContract 结构体的实例相关联。
- // s 是一个指向 SmartContract 类型的指针,允许在方法内部修改结构体的值。
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- // 创建一组身份
- identities := []Identity{
- {ID: "identity1", PID: "PID1", SK: "SK1", VPK: "VPK1"},
- {ID: "identity2", PID: "PID2", SK: "SK2", VPK: "VPK2"},
- {ID: "identity3", PID: "PID3", SK: "SK3", VPK: "VPK3"},
- {ID: "identity4", PID: "PID4", SK: "SK4", VPK: "VPK4"},
- {ID: "identity5", PID: "PID5", SK: "SK5", VPK: "VPK5"},
- }
-
- // 将每个身份存储到账本中
- // _ 表示我们在这个循环中不需要使用索引
- // 如果不使用 _,就需要声明一个变量来存储迭代的索引或元素值,否则会报错。
- for _, identity := range identities {
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- // json.Marshal() 函数是 Go 语言中的一个函数,用于将数据转换为 JSON 格式的字节切片([]byte)。它接受一个数据结构作为参数,并尝试将其转换为 JSON 格式。
- // 返回值包括两个部分:
- // JSON 格式的字节切片([]byte):这个字节切片包含了传入数据结构的 JSON 表示形式。JSON 格式是一种轻量级的数据交换格式,通常用于在不同系统之间传递和存储数据。
- // 错误(error):如果转换过程中出现问题,函数会返回一个非空的错误对象。这个错误对象描述了在转换过程中发生的具体问题,例如数据结构中的字段无法被转换为 JSON 格式的情况。
-
- // nil 是一个预定义的标识符,表示指针、切片、映射、通道、函数和接口类型的零值。
- // 如果输出的错误不是零值,就要输出这个err
- if err != nil {
- return err
- }
-
- // 将JSON格式的身份存储到账本的世界状态中
- err = ctx.GetStub().PutState(identity.ID, identityJSON)
- if err != nil {
- return fmt.Errorf("向世界状态存储失败:%v", err)
- }
- }
- return nil
- }
改的内容其实很少,一个是需要根据自己的数据类型改一下格式
二是根据自己的数据的名称替换掉原来的
比如原来的是asset,我的是identity
直接替换就行
但是要注意大小写,identity、Identity、identities这三个别替换错了
如果代码有问题,大概率是这里和对应的asset没对应好
仔细对比一下我改了之后的和原来的,务必仔细
- // InitLedger 向账本添加一组基本资产
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- // 创建一组资产
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- // 将每个资产存储到账本中
- for _, asset := range assets {
- // 将资产转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将JSON格式的资产存储到账本的世界状态中
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
- return fmt.Errorf("向世界状态存储失败:%v", err)
- }
- }
-
- return nil
- }
这段代码是用于在智能合约中创建新资产的函数。
它接收资产的各种属性作为参数,并将新资产的详细信息存储到账本的世界状态中。
在创建新资产之前,函数会检查该资产是否已经存在于账本中,如果已存在则会返回错误。
如果资产不存在,则会根据传入的参数创建一个新的资产对象,并将其转换为JSON格式,最后将这个JSON格式的资产存储到账本的世界状态中。
- // CreateIdentity 向世界状态添加具有给定详细信息的新身份
- func (s *SmartContract) CreateIdentity(ctx contractapi.TransactionContextInterface, id string, pid string, sk string, vpk string) error {
- // 检查身份是否已存在
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份已存在,则返回错误
- if exists {
- return fmt.Errorf("身份 %s 已存在", id)
- }
-
- // 创建新的身份对象
- identity := Identity{
- ID: id,
- PID: pid,
- SK: sk,
- VPK: vpk,
- }
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- if err != nil {
- return err
- }
-
- // 将JSON格式的身份存储到世界状态中
- return ctx.GetStub().PutState(id, identityJSON)
- }
- // ReadAsset 返回世界状态中具有给定ID的资产
- func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
- // 从世界状态获取指定ID的资产信息
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return nil, fmt.Errorf("从世界状态读取失败:%v", err)
- }
- // 如果资产信息为空,则返回错误
- if assetJSON == nil {
- return nil, fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 将JSON格式的资产信息解析为资产对象
- var asset Asset
- err = json.Unmarshal(assetJSON, &asset)
- if err != nil {
- return nil, err
- }
-
- return &asset, nil
- }
这段代码是用于在智能合约中读取特定ID的资产信息的函数。
它从账本的世界状态中获取指定ID的资产信息,然后将其解析为 `Asset` 结构体。
如果指定ID的资产不存在,函数会返回相应的错误。
如果成功获取并解析了资产信息,函数将返回这个资产的指针和一个空错误。
这里因为我的项目很简单,我没写对应的方法
如果你要写的话,按照前面几个方法的思路改就行
如果只是想先走一遍流程
就没必要写,不影响(后面不用这个方法就是了哈哈哈哈)
- // UpdateAsset 根据提供的参数更新世界状态中的现有资产
- func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- // 检查资产是否存在于世界状态中
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果资产不存在,则返回错误
- if !exists {
- return fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 创建新的资产对象,用提供的参数覆盖原始资产信息
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- // 将新的资产信息转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将更新后的资产信息存储到世界状态中,覆盖原始资产信息
- return ctx.GetStub().PutState(id, assetJSON)
- }
这段代码是用于在智能合约中更新现有资产信息的函数。
它首先检查要更新的资产是否存在于世界状态中。
如果资产不存在,则函数返回相应的错误。
如果资产存在,则根据提供的参数创建一个新的资产对象,然后将新资产的信息以JSON格式存储到账本的世界状态中,从而覆盖原始资产的信息。
- // UpdateIdentity 根据提供的参数更新世界状态中的现有身份
- func (s *SmartContract) UpdateIdentity(ctx contractapi.TransactionContextInterface, id string, pid string, sk string, vpk string) error {
- // 检查身份是否存在于世界状态中
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份不存在,则返回错误
- if !exists {
- return fmt.Errorf("身份 %s 不存在", id)
- }
-
- // 创建新的身份对象,用提供的参数覆盖原始身份信息
- identity := Identity{
- ID: id,
- PID: pid,
- SK: sk,
- VPK: vpk,
- }
-
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- if err != nil {
- return err
- }
-
- // 将更新后的身份信息存储到世界状态中,覆盖原始身份信息
- return ctx.GetStub().PutState(id, identityJSON)
- }
- // DeleteAsset 从世界状态中删除指定的资产
- func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
- // 检查要删除的资产是否存在于世界状态中
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果资产不存在,则返回错误
- if !exists {
- return fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 从世界状态中删除指定ID的资产信息
- return ctx.GetStub().DelState(id)
- }
这段代码是智能合约中用于删除特定资产的函数。
函数首先检查要删除的资产是否存在于世界状态中,如果不存在,则返回相应的错误。
如果资产存在,则通过调用 `DelState` 方法从账本的世界状态中删除指定ID的资产信息。
- // DeleteIdentity 从世界状态中删除指定的身份
- func (s *SmartContract) DeleteIdentity(ctx contractapi.TransactionContextInterface, id string) error {
- // 检查要删除的身份是否存在于世界状态中
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份不存在,则返回错误
- if !exists {
- return fmt.Errorf("身份 %s 不存在", id)
- }
-
- // 从世界状态中删除指定ID的身份信息
- return ctx.GetStub().DelState(id)
- }
- // AssetExists 当世界状态中存在具有指定ID的资产时返回 true
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- // 从世界状态获取指定ID的资产信息
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("从世界状态读取失败:%v", err)
- }
-
- // 检查资产信息是否为空,若不为空则资产存在,返回 true;否则返回 false
- return assetJSON != nil, nil
- }
这段代码是智能合约中用于检查指定ID的资产是否存在于世界状态中的函数。
函数首先通过 `GetState` 方法从世界状态获取指定ID的资产信息。
如果成功获取到资产信息,则表示该资产存在于世界状态中,函数返回 true。
如果获取资产信息过程中出现错误或者资产信息为空,则表示该资产不存在于世界状态中,函数返回 false。
- // IdentityExists 当世界状态中存在具有指定ID的身份时返回 true
- func (s *SmartContract) IdentityExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- // 从世界状态获取指定ID的身份信息
- identityJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("从世界状态读取失败:%v", err)
- }
-
- // 检查身份信息是否为空,若不为空则身份存在,返回 true;否则返回 false
- return identityJSON != nil, nil
- }
- // TransferAsset 更新世界状态中具有给定ID的资产的所有者字段,并返回旧的所有者
- func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
- // 读取具有指定ID的资产信息
- asset, err := s.ReadAsset(ctx, id)
- if err != nil {
- return "", err
- }
-
- // 保存旧的所有者信息
- oldOwner := asset.Owner
- // 更新资产的所有者为新的所有者
- asset.Owner = newOwner
-
- // 将更新后的资产信息转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return "", err
- }
-
- // 将更新后的资产信息存储到世界状态中,更新资产的所有者信息
- err = ctx.GetStub().PutState(id, assetJSON)
- if err != nil {
- return "", err
- }
-
- // 返回旧的所有者信息
- return oldOwner, nil
- }
这段代码是用于在智能合约中转移资产所有权的函数。
函数首先通过调用 `ReadAsset` 方法读取具有指定ID的资产信息。
然后,它将资产的所有者字段更新为新的所有者,并将更新后的资产信息以JSON格式存储到世界状态中。
最后,函数返回资产转移之前的旧所有者信息。
- // GetAllAssets 返回世界状态中找到的所有资产
- func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
- // 使用空字符串作为startKey和endKey进行范围查询,
- // 可以查询链码命名空间中的所有资产。
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
- if err != nil {
- return nil, err
- }
- defer resultsIterator.Close()
-
- var assets []*Asset
- // 遍历查询结果
- for resultsIterator.HasNext() {
- // 获取下一个资产的信息
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- // 解析资产信息为Asset结构体
- var asset Asset
- err = json.Unmarshal(queryResponse.Value, &asset)
- if err != nil {
- return nil, err
- }
- // 将资产信息添加到资产列表中
- assets = append(assets, &asset)
- }
-
- return assets, nil
- }
这段代码用于获取世界状态中所有的资产信息。
函数通过调用 `GetStateByRange` 方法进行范围查询,使用空字符串作为起始键和结束键,以检索链码命名空间中的所有资产。
然后,通过迭代查询结果,将每个资产的JSON数据解码为 `Asset` 结构体,并将其添加到资产列表中,最终返回所有资产的列表。
- // GetAllIdentities 返回世界状态中找到的所有身份
- func (s *SmartContract) GetAllIdentities(ctx contractapi.TransactionContextInterface) ([]*Identity, error) {
- // 使用空字符串作为startKey和endKey进行范围查询,
- // 可以查询链码命名空间中的所有身份。
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
- if err != nil {
- return nil, err
- }
- defer resultsIterator.Close()
-
- var identities []*Identity
- // 遍历查询结果
- for resultsIterator.HasNext() {
- // 获取下一个身份的信息
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- // 解析身份信息为Identity结构体
- var identity Identity
- err = json.Unmarshal(queryResponse.Value, &identity)
- if err != nil {
- return nil, err
- }
- // 将身份信息添加到身份列表中
- identities = append(identities, &identity)
- }
-
- return identities, nil
- }
目前为止,我们就改完一个文件了
- package chaincode
-
- import (
- "encoding/json" // 包含用于 JSON 编解码的功能
- "fmt" // 提供格式化输入输出的功能
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi" // 引入 Hyperledger Fabric 的链码 API 包
- )
-
- // SmartContract 提供了管理资产的函数
- type SmartContract struct {
- contractapi.Contract
- }
-
- // Asset 描述了简单资产的基本细节
- // 按照字母顺序插入结构字段 => 以实现跨语言的确定性
- // Go语言在转换为JSON时会保持顺序,但不会自动排序
- type Asset struct {
- AppraisedValue int `json:"AppraisedValue"` // 资产评估价值
- Color string `json:"Color"` // 资产颜色
- ID string `json:"ID"` // 资产ID
- Owner string `json:"Owner"` // 资产所有者
- Size int `json:"Size"` // 资产尺寸
- }
-
- // InitLedger 向账本添加一组基本资产
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- // 创建一组资产
- assets := []Asset{
- {ID: "asset1", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
- {ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
- {ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
- {ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
- {ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
- {ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
- }
-
- // 将每个资产存储到账本中
- for _, asset := range assets {
- // 将资产转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将JSON格式的资产存储到账本的世界状态中
- err = ctx.GetStub().PutState(asset.ID, assetJSON)
- if err != nil {
- return fmt.Errorf("向世界状态存储失败:%v", err)
- }
- }
-
- return nil
- }
-
- // CreateAsset 向世界状态添加具有给定详细信息的新资产
- func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- // 检查资产是否已存在
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果资产已存在,则返回错误
- if exists {
- return fmt.Errorf("资产 %s 已存在", id)
- }
-
- // 创建新的资产对象
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- // 将资产转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将JSON格式的资产存储到世界状态中
- return ctx.GetStub().PutState(id, assetJSON)
- }
-
- // ReadAsset 返回世界状态中具有给定ID的资产
- func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) {
- // 从世界状态获取指定ID的资产信息
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return nil, fmt.Errorf("从世界状态读取失败:%v", err)
- }
- // 如果资产信息为空,则返回错误
- if assetJSON == nil {
- return nil, fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 将JSON格式的资产信息解析为资产对象
- var asset Asset
- err = json.Unmarshal(assetJSON, &asset)
- if err != nil {
- return nil, err
- }
-
- return &asset, nil
- }
-
- // UpdateAsset 根据提供的参数更新世界状态中的现有资产
- func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, owner string, appraisedValue int) error {
- // 检查资产是否存在于世界状态中
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果资产不存在,则返回错误
- if !exists {
- return fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 创建新的资产对象,用提供的参数覆盖原始资产信息
- asset := Asset{
- ID: id,
- Color: color,
- Size: size,
- Owner: owner,
- AppraisedValue: appraisedValue,
- }
- // 将新的资产信息转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return err
- }
-
- // 将更新后的资产信息存储到世界状态中,覆盖原始资产信息
- return ctx.GetStub().PutState(id, assetJSON)
- }
-
- // DeleteAsset 从世界状态中删除指定的资产
- func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error {
- // 检查要删除的资产是否存在于世界状态中
- exists, err := s.AssetExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果资产不存在,则返回错误
- if !exists {
- return fmt.Errorf("资产 %s 不存在", id)
- }
-
- // 从世界状态中删除指定ID的资产信息
- return ctx.GetStub().DelState(id)
- }
- // AssetExists 当世界状态中存在具有指定ID的资产时返回 true
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- // 从世界状态获取指定ID的资产信息
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("从世界状态读取失败:%v", err)
- }
-
- // 检查资产信息是否为空,若不为空则资产存在,返回 true;否则返回 false
- return assetJSON != nil, nil
- }
-
- // AssetExists 当世界状态中存在具有指定ID的资产时返回 true
- func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- // 从世界状态获取指定ID的资产信息
- assetJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("从世界状态读取失败:%v", err)
- }
-
- // 检查资产信息是否为空,若不为空则资产存在,返回 true;否则返回 false
- return assetJSON != nil, nil
- }
-
- // TransferAsset 更新世界状态中具有给定ID的资产的所有者字段,并返回旧的所有者
- func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) (string, error) {
- // 读取具有指定ID的资产信息
- asset, err := s.ReadAsset(ctx, id)
- if err != nil {
- return "", err
- }
-
- // 保存旧的所有者信息
- oldOwner := asset.Owner
- // 更新资产的所有者为新的所有者
- asset.Owner = newOwner
-
- // 将更新后的资产信息转换为JSON格式
- assetJSON, err := json.Marshal(asset)
- if err != nil {
- return "", err
- }
-
- // 将更新后的资产信息存储到世界状态中,更新资产的所有者信息
- err = ctx.GetStub().PutState(id, assetJSON)
- if err != nil {
- return "", err
- }
-
- // 返回旧的所有者信息
- return oldOwner, nil
- }
-
- // GetAllAssets 返回世界状态中找到的所有资产
- func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) {
- // 使用空字符串作为startKey和endKey进行范围查询,
- // 可以查询链码命名空间中的所有资产。
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
- if err != nil {
- return nil, err
- }
- defer resultsIterator.Close()
-
- var assets []*Asset
- // 遍历查询结果
- for resultsIterator.HasNext() {
- // 获取下一个资产的信息
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- // 解析资产信息为Asset结构体
- var asset Asset
- err = json.Unmarshal(queryResponse.Value, &asset)
- if err != nil {
- return nil, err
- }
- // 将资产信息添加到资产列表中
- assets = append(assets, &asset)
- }
-
- return assets, nil
- }
- package chaincode
-
- import (
- "encoding/json" // 包含用于 JSON 编解码的功能
- "fmt" // 提供格式化输入输出的功能
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi" // 引入 Hyperledger Fabric 的链码 API 包
- )
-
- // SmartContract 提供了管理身份的函数
- type SmartContract struct {
- contractapi.Contract
- }
-
- // Identity 描述了简单身份的基本细节
- // 按照字母顺序插入结构字段 => 以实现跨语言的确定性
- // Go语言在转换为JSON时会保持顺序,但不会自动排序
- type Identity struct {
- ID string `json:"ID"` // 真实身份
- PID string `json:"PID"` // 假名身份
- SK string `json:"SK"` // V的私钥
- VPK string `json:"VPK"` // V的公钥
- }
-
-
- // InitLedger 向账本添加一组基本身份
- // ctx 是一个类型为 contractapi.TransactionContextInterface 的参数,可以在方法内部使用这个参数进行操作。
- // (s *SmartContract): 这部分是方法接收器(receiver),它表明这是一个属于 SmartContract 结构体的方法。
- // s *SmartContract 意味着这个方法与 SmartContract 结构体的实例相关联。
- // s 是一个指向 SmartContract 类型的指针,允许在方法内部修改结构体的值。
- func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
- // 创建一组身份
- identities := []Identity{
- {ID: "identity1", PID: "PID1", SK: "SK1", VPK: "VPK1"},
- {ID: "identity2", PID: "PID2", SK: "SK2", VPK: "VPK2"},
- {ID: "identity3", PID: "PID3", SK: "SK3", VPK: "VPK3"},
- {ID: "identity4", PID: "PID4", SK: "SK4", VPK: "VPK4"},
- {ID: "identity5", PID: "PID5", SK: "SK5", VPK: "VPK5"},
- }
-
- // 将每个身份存储到账本中
- // _ 表示我们在这个循环中不需要使用索引
- // 如果不使用 _,就需要声明一个变量来存储迭代的索引或元素值,否则会报错。
- for _, identity := range identities {
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- // json.Marshal() 函数是 Go 语言中的一个函数,用于将数据转换为 JSON 格式的字节切片([]byte)。它接受一个数据结构作为参数,并尝试将其转换为 JSON 格式。
- // 返回值包括两个部分:
- // JSON 格式的字节切片([]byte):这个字节切片包含了传入数据结构的 JSON 表示形式。JSON 格式是一种轻量级的数据交换格式,通常用于在不同系统之间传递和存储数据。
- // 错误(error):如果转换过程中出现问题,函数会返回一个非空的错误对象。这个错误对象描述了在转换过程中发生的具体问题,例如数据结构中的字段无法被转换为 JSON 格式的情况。
-
- // nil 是一个预定义的标识符,表示指针、切片、映射、通道、函数和接口类型的零值。
- // 如果输出的错误不是零值,就要输出这个err
- if err != nil {
- return err
- }
-
- // 将JSON格式的身份存储到账本的世界状态中
- err = ctx.GetStub().PutState(identity.ID, identityJSON)
- if err != nil {
- return fmt.Errorf("向世界状态存储失败:%v", err)
- }
- }
- return nil
- }
-
-
- // CreateIdentity 向世界状态添加具有给定详细信息的新身份
- func (s *SmartContract) CreateIdentity(ctx contractapi.TransactionContextInterface, id string, pid string, sk string, vpk string) error {
- // 检查身份是否已存在
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份已存在,则返回错误
- if exists {
- return fmt.Errorf("身份 %s 已存在", id)
- }
-
- // 创建新的身份对象
- identity := Identity{
- ID: id,
- PID: pid,
- SK: sk,
- VPK: vpk,
- }
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- if err != nil {
- return err
- }
-
- // 将JSON格式的身份存储到世界状态中
- return ctx.GetStub().PutState(id, identityJSON)
- }
-
- // UpdateIdentity 根据提供的参数更新世界状态中的现有身份
- func (s *SmartContract) UpdateIdentity(ctx contractapi.TransactionContextInterface, id string, pid string, sk string, vpk string) error {
- // 检查身份是否存在于世界状态中
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份不存在,则返回错误
- if !exists {
- return fmt.Errorf("身份 %s 不存在", id)
- }
-
- // 创建新的身份对象,用提供的参数覆盖原始身份信息
- identity := Identity{
- ID: id,
- PID: pid,
- SK: sk,
- VPK: vpk,
- }
-
- // 将身份转换为JSON格式
- identityJSON, err := json.Marshal(identity)
- if err != nil {
- return err
- }
-
- // 将更新后的身份信息存储到世界状态中,覆盖原始身份信息
- return ctx.GetStub().PutState(id, identityJSON)
- }
-
- // DeleteIdentity 从世界状态中删除指定的身份
- func (s *SmartContract) DeleteIdentity(ctx contractapi.TransactionContextInterface, id string) error {
- // 检查要删除的身份是否存在于世界状态中
- exists, err := s.IdentityExists(ctx, id)
- if err != nil {
- return err
- }
- // 如果身份不存在,则返回错误
- if !exists {
- return fmt.Errorf("身份 %s 不存在", id)
- }
-
- // 从世界状态中删除指定ID的身份信息
- return ctx.GetStub().DelState(id)
- }
-
- // IdentityExists 当世界状态中存在具有指定ID的身份时返回 true
- func (s *SmartContract) IdentityExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) {
- // 从世界状态获取指定ID的身份信息
- identityJSON, err := ctx.GetStub().GetState(id)
- if err != nil {
- return false, fmt.Errorf("从世界状态读取失败:%v", err)
- }
-
- // 检查身份信息是否为空,若不为空则身份存在,返回 true;否则返回 false
- return identityJSON != nil, nil
- }
-
- // GetAllIdentities 返回世界状态中找到的所有身份
- func (s *SmartContract) GetAllIdentities(ctx contractapi.TransactionContextInterface) ([]*Identity, error) {
- // 使用空字符串作为startKey和endKey进行范围查询,
- // 可以查询链码命名空间中的所有身份。
- resultsIterator, err := ctx.GetStub().GetStateByRange("", "")
- if err != nil {
- return nil, err
- }
- defer resultsIterator.Close()
-
- var identities []*Identity
- // 遍历查询结果
- for resultsIterator.HasNext() {
- // 获取下一个身份的信息
- queryResponse, err := resultsIterator.Next()
- if err != nil {
- return nil, err
- }
-
- // 解析身份信息为Identity结构体
- var identity Identity
- err = json.Unmarshal(queryResponse.Value, &identity)
- if err != nil {
- return nil, err
- }
- // 将身份信息添加到身份列表中
- identities = append(identities, &identity)
- }
-
- return identities, nil
- }
下面开始第二个文件
这个非常简单,就几行
- package main
-
- import (
- "log"
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi"
- "github.com/hyperledger/fabric-samples/asset-transfer-basic/chaincode-go/chaincode"
- )
-
- func main() {
- assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
- if err != nil {
- log.Panicf("Error creating asset-transfer-basic chaincode: %v", err)
- }
-
- if err := assetChaincode.Start(); err != nil {
- log.Panicf("Error starting asset-transfer-basic chaincode: %v", err)
- }
- }
我们要修改的就是下面几个地方

- package main
-
- import (
- "log"
-
- "github.com/hyperledger/fabric-contract-api-go/contractapi"
- "github.com/hyperledger/fabric-samples/asset-transfer-basic/test-protocol-go/chaincode"
- )
-
- func main() {
- identityChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{})
- if err != nil {
- log.Panicf("Error creating identity-transfer-basic chaincode: %v", err)
- }
-
- if err := identityChaincode.Start(); err != nil {
- log.Panicf("Error starting identity-transfer-basic chaincode: %v", err)
- }
- }
改完这两个文件,我们的智能合约已经写好了
务必记住自己的项目的名字和存储路径,后面经常用
后面开始部署
后面就没多少图了,不过我会尽可以用语言描述细一些,每一步我都会分开
编号方式我也做了改变,方便后面哪一步出问题可以评论区求助其他大神
(因为我也是菜鸡,大概率解决不了....)
cd fabric-samples/test-network
这个语句可能你会执行失败,因为它取决于你当前所处的路径
总之你自己切到这里来
我的绝对路径是这样的,仅供参考
cd ./go/src/github.com/hyperledger/fabric/scripts/fabric-samples/test-network
如果你之前启动过测试网络
需要先终止并删除之前的工件,用下面的语句
如果没启动过,就直接跳过这步(直接粘贴,不用改)
./network.sh down
./network.sh up createChannel
这一步顺带给你创建了个通道
如果运行成功,会有下面的输出
========= Channel successfully joined ===========
这一步不是必须的,但是能让你方便纠错
首先,你要打开一个新的终端,新的终端窗口 !!!别用刚刚的
还是先到这个目录
cd fabric-samples/test-network
输入(直接粘贴,不用改)
ll
找一下你这里有没有monitordocker.sh文件
如果没有,需要从别处复制声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。