Hyperledger Fabric2中文文档-开发链码

翻译官方文档https://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html

什么是链码

链码是程序,用Go,Node.js,Java其中一种语言编程。链码运行在Peer的独立进程中,负责初始化账本,管理账本状态。

链码通常用来处理网络成员同意的逻辑事务,所以它也被称为“智能合约”。可以调用链码更新或者查询交易。如果有合适的权限,两码可以调用另一个链码,无论是否在一个channel中,获取账本状态。注意如果被调用的链码和链码处于不同的channel中,只有读权限。也就是说被调用链码只有读功能,不参与后续事务的验证和检查。

接下来的部分,我们会从应用开发者的角度来探索链码。我们以一个例子来介绍链码API中的方法。如果你是部署链码的运维人员,可以查看https://hyperledger-fabric.readthedocs.io/en/latest/deploy_chaincode.htmlhttps://hyperledger-fabric.readthedocs.io/en/latest/chaincode_lifecycle.html

本文档介绍的是链码的基础API。你也可以使用高级API——Fabric Contract API,相关文档https://hyperledger-fabric.readthedocs.io/en/latest/developapps/smartcontract.html

链码API

每个链码程序必须实现链码接口,用来响应接收的事务处理,不同语言的接口文档在下方

Go   https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim#Chaincode

Node.js  https://hyperledger.github.io/fabric-chaincode-node/master/api/fabric-shim.ChaincodeInterface.html

Java  https://hyperledger.github.io/fabric-chaincode-java/master/api/org/hyperledger/fabric/shim/Chaincode.html

每种语言都会用Invoke方法来提交事务。这个方法可以让你读写账本数据。

链码中也需要定义一个Init函数,实现初始化。这个函数是链码需要的,并不需要app调用。你可以使用lifecycle进程来设定在调用链码之前是否需要Init函数。这方面资料查看https://hyperledger-fabric.readthedocs.io/en/latest/chaincode_lifecycle.html#step-three-approve-a-chaincode-definition-for-your-organization

另一个链码API是ChaincodeStubInterface

Go https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim#ChaincodeStubInterface

Node.js https://hyperledger.github.io/fabric-chaincode-node/master/api/fabric-shim.ChaincodeStub.html

Java https://hyperledger.github.io/fabric-chaincode-java/master/api/org/hyperledger/fabric/shim/ChaincodeStub.html

用来访问和修改账本,且在链码之间调用。

本文采用Go语言链码,来演示一个简单的资产管理的例子。

资产链码

本例是在账本中创建一个简单的资产管理(key-value形式)

选择一个目录

如果你没有使用过Go编程,首选需要安装Go并配置环境。我们假定你选用的版本支持这些模块。

首先创建一个存放链码的目录

为了简单起见,我们执行下面的命令

mkdir sacc && cd sacc

现在来创建一个模块和脚本

go mod init sacc
touch sacc.go

初始整理

首先,我们做一些整理。和所有链码一样,需要实现Init和Invoke函数。我们先引入依赖包。我们需要引入shim和peer。然后再定义一个结构体来接收链码的shim函数。

package main

import (
    "fmt"

    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

初始化链码

接下来实现Init函数

// Init is called during chaincode instantiation to initialize any data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

}

注意:链码升级也会调用Init函数,当我们升级现有链码时,务必要确保Init是否需要修改。可以提供一个空的Init函数如果没有需要迁移的数据或者初始化的部分。

 下一步,我们使用ChaincodeStubInterface.GetStringArgs来找到参数并检验。在这里我们需要一个键值对

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
  // Get the args from the transaction proposal
  args := stub.GetStringArgs()
  if len(args) != 2 {
    return shim.Error("Incorrect arguments. Expecting a key and a value")
  }
}

我们已经验证了参数,接下来将数据保存到账本中。key-value的形式向 ChaincodeStubInterface.PutState 中传值。如果进展顺利,返回一个peer.Response 对象来表明初始化成功

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
  // Get the args from the transaction proposal
  args := stub.GetStringArgs()
  if len(args) != 2 {
    return shim.Error("Incorrect arguments. Expecting a key and a value")
  }

  // Set up any variables or assets here by calling stub.PutState()

  // We store the key and the value on the ledger
  err := stub.PutState(args[0], []byte(args[1]))
  if err != nil {
    return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
  }
  return shim.Success(nil)
}

调用链码

首先,增加Invoke函数

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The 'set'
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

}

在Init中,我们需要从ChaincodeStubInterface获取准确的参数。Invoke的参数会成为链码中函数的名字。这里我们只定义两个内部的函数set和get,用来设置资产和查询资产。我们使用ChaincodeStubInterface.GetFunctionAndParameters来获取函数名和函数的参数。

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

}

接下来我们需要验证函数是set或get,并调用这些内部函数,返回一个合适的值(shim.Success 或shim.Error,会被序列化成gRPC数据

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else {
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

实现链码内的函数

如前文所述,我们需要定义两个被链码调用的函数。注意我们之前提到的,管理账本装态需要ChaincodeStubInterface.PutStateChaincodeStubInterface.GetState 

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

完整代码

最后需要增加main函数,用来调用 shim.Start函数。完整代码展示

package main

import (
    "fmt"

    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // Get the args from the transaction proposal
    args := stub.GetStringArgs()
    if len(args) != 2 {
            return shim.Error("Incorrect arguments. Expecting a key and a value")
    }

    // Set up any variables or assets here by calling stub.PutState()

    // We store the key and the value on the ledger
    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
    }
    return shim.Success(nil)
}

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else { // assume 'get' even if fn is nil
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

// main function starts up the chaincode in the container during instantiate
func main() {
    if err := shim.Start(new(SimpleAsset)); err != nil {
            fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
    }
}

链码访问控制

链码可以通过调用GetCreator()函数来获取证书做访问控制。另外Go shim提供了扩展API,这些API通过提取提交者的证书中的客户端标识,来做访问控制。可以是客户端自身身份,组织身份,或者是客户端属性。

例如标识资产的键值对中包含客户端的身份(如JSON属性中指定资产拥有者),并且只有这个客户端才能升级键值对的数据。客户身份标识的拓展API可以用来在链码中提取提交者的信息和做访问控制。

详情https://github.com/hyperledger/fabric-chaincode-go/blob/master/pkg/cid/README.md

 

管理依赖包

链码需要的GO包不是标准库,需要将依赖包安装至链码包中。如果将链码构建为一个module,最简单的方式是在打包之前将包安装至vendor中

go mod tidy
go mod vendor

这会将依赖包放入到本地的vendor目录中.

如果依赖包已经被放入vendor中,peer chaincode package和peer chaincode install操作会将关联的依赖包都放入链码中

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值