Hyperledger fabric 链码篇GO(四)

fabric中的链码也就是我们区块链所认知的智能合约,fabric中可由nodejs,java,go编写,本篇只针对GO语言编写链码。将详细介绍链码编写所必须的函数以及相关功能函数。

1、常识

  • 链码的包名指定

    // xxx.go
    package main
  • 必须要引入的包

    import(
    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
    //pb为别名的意思
    )
  • 链码的书写要求

    //自定义结构体,类,基于这个类实现接口函数
    type Test struct{
    //空着即可
    }
  • 链码API查询

    //go语言的链码包shim
    https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim

注意事项

要注意put写入的数据状态不会立刻得到获取,因为put只是执行链码的模拟交易,并不会真正将状态保存在账本中,必须经过orderer达成共识之后,将数据状态保存在区块中,然后保存在各peer节点的账本中

2、函数及链码基本结构

  • success

    // Success ... status:200
    //链码执行后返回成功信息
    func Success(payload []byte) pb.Response {
    return pb.Response{
    Status: OK,
    Payload: payload,
    }
    }
  • error

    // Error ...status:500
    //链码执行后返回错误信息
    func Error(msg string) pb.Response {
    return pb.Response{
    Status: ERROR,
    Message: msg,
    }
    }

2.1、基本结构

下面是编写一个链码的基本框架,由空结构体,Init函数和Invoke函数,主函数组成

package main
//引入必要的包
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
//别名pb
pb "github.com/hyperledger/fabric/protos/peer"
)
// 声明一个结构体,必备,为空
type SimpleChaincode struct {
}
//添加Init函数
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
//实现链码初始化或升级时的处理逻辑
//编写时可灵活使用stub中的API
}
//添加Invoke函数
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
//实现链码运行中被调用或查询时的处理逻辑
//编写时可灵活使用stub中的API
}
//主函数,需要调用shim.Start()方法启动链码
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}

2.2、Init函数

Init方法是系统初始化方法,当执行命令peer chaincode instantiate实例化chaincode时会调用该方法,同时命令中-c选项后面内容为会作为参数传入Init方法中。

#shell 命令
$ peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100", "b", "100"]}'

Args中共有5个参数,第一个为固定值,后面4个为参数,如果传入数据太多,可以采用json等数据格式

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
_ ,args := stub.GetFunctionAndParameters()
return shim.Success ([]byte("success init!!!"))
}

2.3、Invoke函数

Invoke方法的主要作用为写入数据,比如发起交易等,在执行命令 peer chaincode invoke 时系统会调用该方法,并把-c后的参数传入invoke方法中。

参数原理和init类似

3、账本操作API

ChaincodeStubInterface

3.1、参数解析API

含义:调用链码时需要给被调用的目标函数/方法传递参数,与参数解析相关的API 提供了获取这些参数(包括被调用的目标函数/方法名称)的方法

GetFunctionAndParameters

  • 获取客户端传入的参数GetFunctionAndParameters()
//源码
func (s *ChaincodeStub) GetFunctionAndParameters() (function string, params []string) {
allargs := s.GetStringArgs()
function = ""
params = []string{}
if len(allargs) >= 1 {
//返回时第一个为函数名,后面为其他参数
function = allargs[0]
params = allargs[1:]
}
return
}
//执行命令
peer chaincode invoke -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["invoke","a", "b", "10"]}'
//示例代码
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
_ ,args := stub.GetFunctionAndParameters()
//获取执行命令中的参数a,b,10
var a_parm = args[0]
var b_parm = args[1]
var c_parm = args[2]
return shim.Success ([]byte("success invoke!!!"))
}

3.2、账本数据状态操作API

含义:该类型API提供了对账本数据状态进行操作的方法,包括对状态数据的查询以及事务处理

GetState

  • GetState(key string) ([]byte, error) :根据指定的key查询相应的数据状态

    func (s *ChaincodeStub) GetState(key string) ([]byte, error) {
    // Access public data by setting the collection to empty string
    collection := ""
    return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID)
    }
    //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    keyvalue, err := stub.GetState("user1")
    return shim.Success(keyvalue)
    }

PutState

  • PutState(key string, value []byte) error:根据指定的key,将相应的value保存在分类账本中

    func (s *ChaincodeStub) PutState(key string, value []byte) error {
    if key == "" {
    return errors.New("key must not be an empty string")
    }
    // Access public data by setting the collection to empty string
    collection := ""
    return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID)
    }
    //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    stub.PutState("user1",[]byte("putvalue"))
    return shim.Success([]byte("sucess invoke putstate"))
    }

DelState

  • DelState(key string) error:根据指定的key将对应的数据状态删除

    func (s *ChaincodeStub) DelState(key string) error {
    // Access public data by setting the collection to empty string
    collection := ""
    return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID)
    }
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    err :=stub.DelState("user1")
    return shim.Success("delete success")
    }

GetStateByRange

  • GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error):根据指定的开始key和结束key,查询范围内的所有数据状态,注意,结束key对应的数据状态不包含在返回的结果集中

    func (s *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
    if startKey == "" {
    startKey = emptyKeySubstitute
    }
    if err := validateSimpleKeys(startKey, endKey); err != nil {
    return nil, err
    }
    collection := "" // ignore QueryResponseMetadata as it is not applicable for a range query without pagination
    iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
    return iterator, err
    }
    //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    startKey = "startKey"
    endKey = "endKey"
    //根据范围查询,得到StateQueryIteratorInterface迭代器接口
    keyIter , err := stub.getStateByRange(startKey,endKey)
    //关闭迭代器接口
    defer keyIter.close()
    var keys []string
    for keyIter.HasNext(){ //如果有下一个节点
    //得到下一个键值对
    response, iterErr := keysIter.Next()
    if iterErr != nil{
    return shim.Error(fmt.Sprintf("find an error %s",iterErr))
    }
    keys = append(keys,response.Key) //存储键值到数组中
    }
    //遍历key数组
    for key, value := range keys{
    fmt.Printf("key %d contains %s",key ,value)
    }
    //编码keys数组为json格式
    jsonKeys ,err = json.Marshal(keys)
    if err != nil{
    return shim.Error(fmt.Sprintf("data marshal json error: %s",err))
    }
    //将编码之后的json字符串传递给客户端
    return shim.Success(jsonKeys)
    }

GetHistoryForKey

  • GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error):根据指定的key查询所有的历史记录信息

    func (s *ChaincodeStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) {
    response, err := s.handler.handleGetHistoryForKey(key, s.ChannelID, s.TxID)
    if err != nil {
    return nil, err
    }
    return &HistoryQueryIterator{CommonIterator: &CommonIterator{s.handler, s.ChannelID, s.TxID, response, 0}}, nil
    } //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { keyIter , err := stub.GetHistoryForKey("user1")
    if err != nil{
    return shim.Error(fmt.Sprintf("GetHistoryForKey error: %s",err))
    }
    //关闭迭代器接口
    defer keyIter.close()
    var keys []string
    for keyIter.HasNext(){ //如果有下一个节点
    //得到下一个键值对
    response, iterErr := keysIter.Next()
    if iterErr != nil{
    return shim.Error(fmt.Sprintf("find an error %s",iterErr))
    }
    //交易编号
    txid := response.TxId
    //交易的值
    txvalue = response.Value
    //当前交易的状态
    txstatus = response.IsDelete
    //交易发生的时间戳
    txtimestamp = response.Timestamp
    keys = append(keys,txid) //存储键值到数组中
    }
    //编码keys数组为json格式
    jsonKeys ,err = json.Marshal(keys)
    if err != nil{
    return shim.Error(fmt.Sprintf("data marshal json error: %s",err))
    }
    //将编码之后的json字符串传递给客户端
    return shim.Success(jsonKeys)
    }

CreateCompositeKey

  • CreateCompositeKey(objectType string, attributes []string) (string, error):创建一个复合键

    func (s *ChaincodeStub) CreateCompositeKey(objectType string, attributes []string) (string, error) {
    return CreateCompositeKey(objectType, attributes)
    } func CreateCompositeKey(objectType string, attributes []string) (string, error) {
    if err := validateCompositeKeyAttribute(objectType); err != nil {
    return "", err
    }
    ck := compositeKeyNamespace + objectType + string(minUnicodeRuneValue)
    for _, att := range attributes {
    if err := validateCompositeKeyAttribute(att); err != nil {
    return "", err
    }
    ck += att + string(minUnicodeRuneValue)
    }
    return ck, nil
    }

SplitCompositeKey

  • SplitCompositeKey(compositeKey string) (string, []string, error):对指定的复合键进行分割

    func (s *ChaincodeStub) SplitCompositeKey(compositeKey string) (string, []string, error) {
    return splitCompositeKey(compositeKey)
    } func splitCompositeKey(compositeKey string) (string, []string, error) {
    componentIndex := 1
    components := []string{}
    for i := 1; i < len(compositeKey); i++ {
    if compositeKey[i] == minUnicodeRuneValue {
    components = append(components, compositeKey[componentIndex:i])
    componentIndex = i + 1
    }
    }
    return components[0], components[1:], nil
    }

GetQueryResult

  • GetQueryResult(query string) (StateQueryIteratorInterface, error) :对状态数据库进行富查询,目前支持富查询的只有CouchDB

    func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {
    // Access public data by setting the collection to empty string
    collection := ""
    // ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
    iterator, _, err := s.handleGetQueryResult(collection, query, nil) return iterator, err
    }

InvokeChaincode

  • InvokeChaincode(chaincodeName string, args [][]byte, channel string):调用其他链码

    //链码名,传递参数,通道名
    func (s *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response {
    // Internally we handle chaincode name as a composite name
    if channel != "" {
    chaincodeName = chaincodeName + "/" + channel
    }
    return s.handler.handleInvokeChaincode(chaincodeName, args, s.ChannelID, s.TxID)
    }
    //示例代码
    func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    //设置参数,a向b转账11
    trans := [][]byte{[]byte("invoke"),[]byte("a"),[]byte("b"),[]byte("11")}
    //调用chaincode
    response := stub.InvokeChaincode("mycc",trans,"mychannel")
    //判断是否操作成功
    if response.Status != shim.OK {
    errStr := fmt.Sprintf("Invoke failed ,error : %s ", response.Payload)
    return shim.Error(errStr)
    }
    return shim.Success([]byte"转账成功")
    }

3.3、交易信息API

含义:获取提交的交易信息的相关API

GetTxID

  • GetTxID() string :返回交易提案中指定的交易ID

    func (s *ChaincodeStub) GetTxID() string {
    return s.TxID
    }

GetChannelID

  • GetChannelID() string:返回交易提案中指定通道的ID

    func (s *ChaincodeStub) GetChannelID() string {
    return s.ChannelID
    }

GetTxTimestamp

  • GetTxTimestamp() (*timestamp.Timestamp, error):返回交易创建的时间戳,这个时间戳时peer接收到交易的具体时间

    func (s *ChaincodeStub) GetTxTimestamp() (*timestamp.Timestamp, error) {
    hdr := &common.Header{}
    if err := proto.Unmarshal(s.proposal.Header, hdr); err != nil {
    return nil, fmt.Errorf("error unmarshaling Header: %s", err)
    } chdr := &common.ChannelHeader{}
    if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
    return nil, fmt.Errorf("error unmarshaling ChannelHeader: %s", err)
    } return chdr.GetTimestamp(), nil
    }

GetBinding

  • GetBinding() ([]byte, error):返回交易的绑定信息,如一些临时性信息,以避免重复性攻击

    func (s *ChaincodeStub) GetBinding() ([]byte, error) {
    return s.binding, nil
    }

GetSignedProposal

  • GetSignedProposal() (*pb.SignedProposal, error):返回与交易提案相关的签名身份信息

    func (s *ChaincodeStub) GetSignedProposal() (*pb.SignedProposal, error) {
    return s.signedProposal, nil
    }

    GetCreator

  • GetCreator() ([]byte, error) :返回该交易提交者的身份信息

    func (s *ChaincodeStub) GetCreator() ([]byte, error) {
    return s.creator, nil
    }

GetTransient

  • GetTransient() (map[string][]byte, error):返回交易中不会被写至账本中的一些临时性信息

    func (s *ChaincodeStub) GetTransient() (map[string][]byte, error) {
    return s.transient, nil
    }

3.4、事件处理API

含义:与事件处理相关的API

SetEvent

  • SetEvent(name string, payload []byte) error:设置事件,包括事件名称和内容

    // SetEvent documentation can be found in interfaces.go
    func (s *ChaincodeStub) SetEvent(name string, payload []byte) error {
    if name == "" {
    return errors.New("event name can not be empty string")
    }
    s.chaincodeEvent = &pb.ChaincodeEvent{EventName: name, Payload: payload}
    return nil
    }

3.5、对PrivateData操作的API

含义:hyperledger fabric在1.2.0版本中新增的对私有数据操作的相关API

//根据指定key,从指定的私有数据集中查询对应的私有数据
func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error)
//根据给定的部分组合键的集合,查询给定的私有状态
func (s *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error)
//根据指定的开始key和结束key查询范围内的私有数据(不包括结束key)
func (s *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error)
//根据指定的查询字符串执行富查询
func (s *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
//将指定的key与value保存到私有数据集中
func (s *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error
//根据key删除相应数据
func (s *ChaincodeStub) DelPrivateData(collection string, key string) error

4、背书策略

背书的过程就是一笔交易被确认的过程,而背书策略就是用来指示相关的参与方如何对交易进行确认。背书策略的设置是通过部署链码实例化时instantiate命令中的-P参数来设置的

peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.member','Org2MSP.member')"

此命令表示需要组织1和组织2中任意一个用户共同来参与交易的确认并且同意,这样的交易才能生效并被记录到去区块链中

#也可以指定背书节点,使用peerAddresses来指定背书节点
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
  • 背书规则示例1

    #需要组织中任意用户共同参与背书
    "AND ('Org1MSP.member', 'Org2MSP.member')"
  • 背书规则示例2

    #只要组织中任意一个用户验证即可
    "OR ('Org1MSP.member', 'Org2MSP.member')"
  • 背书规则示例3

    #两种方法让交易生效
    # 1、组织1中有成员验证成功
    # 2、组织2和组织3中有成员共同参与验证成功
    "OR ('Org1MSP.member', AND ('Org2MSP.member', 'Org3MSP.member'))"

注意:背书规则只针对写入数据的操作进行校验,对于查询类的背书不操作

5、应用示例

设计了一个链码,实现了资产管理,转账交易的功能,主要实现了以下几个函数

  • init函数

    初始化链码,设定账户名称及资产额度

  • invoke函数

    实现函数的识别,并转到不同的函数功能

  • del函数

    删除账户

  • find函数

    查看账户额度

  • set函数

    根据账户名称设定要存入的金额

  • get函数

    根据账户名称设定要取出的金额

package main

import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
"strconv"
) type AssetChaincode struct {
//empty
} //Init function
func (t *AssetChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
_, args := stub.GetFunctionAndParameters()
if len(args) != 4 {
return shim.Error("Must four initialization parameters, representing name and money ,respectively")
}
var a = args[0]
var acount = args[1]
var b = args[2]
var bcount = args[3]
if len(a) < 2 {
return shim.Error("the length of name must not less than 2")
}
if len(b) < 2 {
return shim.Error("the length of name must not less than 2")
}
_, err := strconv.Atoi(acount)
if err != nil {
return shim.Error(a + " account is false")
}
_, err = strconv.Atoi(bcount)
if err != nil {
return shim.Error(b + " account is false")
} err = stub.PutState(a, []byte(acount))
if err != nil {
return shim.Error(a + " Occuring error when saving the data")
}
err = stub.PutState(b, []byte(bcount))
if err != nil {
return shim.Error(b + " Occuring error when saving the data")
}
return shim.Success([]byte("Init success"))
} //Invoke function
func (t *AssetChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fun, args := stub.GetFunctionAndParameters()
if fun == "set" {
return set(stub, args)
} else if fun == "get" {
return get(stub, args)
} else if fun == "payment" {
return payment(stub, args)
} else if fun == "del" {
return del(stub, args)
} else if fun == "find" {
return find(stub, args)
}
return shim.Error("not have this ability")
} //find function
func find(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("the number of parameters must one")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
return shim.Success(result)
} //payment function
func payment(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 3 {
return shim.Error("payment format error")
} var source, target string
source = args[0]
target = args[1]
asset, err := strconv.Atoi(args[2])
if err != nil {
return shim.Error("transfer amount atoi failed")
}
sourceS, err := stub.GetState(source)
if err != nil {
return shim.Error("query source asset failed")
}
targetS, err := stub.GetState(target)
if err != nil {
return shim.Error("query target asset failed")
}
sourceasset, err := strconv.Atoi(string(sourceS))
if err != nil {
return shim.Error("source asset transform int failed")
}
targetasset, err := strconv.Atoi(string(targetS))
if err != nil {
return shim.Error("target asset transform int failed")
}
if sourceasset < asset {
return shim.Error("source asset don't have enough balance")
}
sourceasset -= asset
targetasset += asset
err = stub.PutState(source, []byte(strconv.Itoa(sourceasset)))
if err != nil {
return shim.Error("after save payment soure asset failed")
}
err = stub.PutState(target, []byte(strconv.Itoa(targetasset)))
if err != nil {
return shim.Error("after save payment target asset failed")
}
return shim.Success([]byte("payment success"))
} //delete function
func del(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args)!=1{
return shim.Error("elete account format error")
}
err := stub.DelState(args[0])
if err!= nil{
return shim.Error("delete account error")
}
return shim.Success([]byte("delete account success"+args[0]))
} //set function
func set(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("set account asset format error")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
asset,err := strconv.Atoi(string(result))
if err!= nil{
return shim.Error("transfrom account balance error")
}
val,err := strconv.Atoi(string(args[1]))
if err!= nil{
return shim.Error("transfrom set balance error")
}
val += asset
err = stub.PutState(args[0],[]byte(strconv.Itoa(val)))
if err != nil {
return shim.Error("save balance error")
}
return shim.Success([]byte("set asset success!"))
} //get function
func get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("t account asset format error")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
asset,err := strconv.Atoi(string(result))
if err!= nil{
return shim.Error("transfrom account balance error")
}
val,err := strconv.Atoi(string(args[1]))
if err!= nil{
return shim.Error("transfrom set balance error")
}
if asset<val{
return shim.Error("not have enough asset")
}
asset -= val
err = stub.PutState(args[0],[]byte(strconv.Itoa(asset)))
if err != nil {
return shim.Error("save balance error")
}
return shim.Success([]byte("get asset success!"))
} //main function
func main() {
err := shim.Start(new(AssetChaincode))
if err != nil {
fmt.Printf("start assetchaincode error!,the message is :%s", err)
}
}

Hyperledger fabric 链码篇GO(四)的更多相关文章

  1. Hyperledger Fabric链码之一

    什么是链码(Chaincode)? 我们知道区块链有3个发展阶段:区块链1.0,区块链2.0,区块链3.0.其中区块链2.0就是各种区块链平台百花齐放的阶段,区块链2.0最大的特点就是智能合约,我们接 ...

  2. Hyperledger Fabric链码之三

    在<Hyperledger Fabric链码之一>和<Hyperledger Fabric链码之二>中我们介绍了链码的定义,并通过dev网络测试了测试了自己编写的链码程序. 本 ...

  3. Hyperledger Fabric链码之二

    上篇文章中我们介绍了链码的概念,本文中我们将介绍Fabric下链码的编写和测试.我们会通过一个简单例子的方式来阐述链码API的使用. 链码API     每一个链码程序都必须实现一个接口Chainco ...

  4. Hyperledger Fabric——balance transfer(四)安装和实例化chaincode

    详细解析blance transfer示例的安装(install)和实例化(Instantiate)链码(chaincode)的过程.安装chaincode会根据本地的链码文件生成chaincode镜 ...

  5. Hyperledger Fabric(4)链码ChainCode

    智能合约,是一个抽象的概念,智能合约的历史可以追溯到 1990s 年代.它是由尼克萨博(Nick Szabo)提出的理念,几乎与互联网同龄. 我们这里所说的智能合约只狭义的指区块链中.它能够部署和运行 ...

  6. 搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

    前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动fabric网络之前我们要准备好写好的chaincode.chaincode的开发一般是使用GO或者JAVA,而我选择的是GO语言.先分析一下官 ...

  7. Hyperledger Fabric 1.0 学习搭建 (四)--- 创建Fabric多节点集群

    4.1.配置说明 首先可以根据官方Fabric自带的e2e_cli列子中的集群方案来生成我们自己的集群,与案例不同的是我们需要把容器都分配到不同的服务器上,彼此之间通过网络来进行通信,网络构建完成后则 ...

  8. HyperLedger Fabric部署与链码解读

    1.Fabric简介 Fabric是超级账本中的一个项目,用以推进区块链技术.和其他区块链类似,它也有一个账本,使用智能合约,且是一个参与者可以分别管理自身交易的系统.它是一个联盟链.Fabric与其 ...

  9. Hyperledger Fabric:最简单的方式测试你的链码

    一直以来,写完链码进行测试都要先搭建一个Fabric环境,然后安装链码进行测试,实际上Fabric提供了最为简单的方式可以允许我们对编写的应用链码进行功能测试,不需要搭建一个完整的Fabeic环境.而 ...

随机推荐

  1. P6823 「EZEC-4」zrmpaul Loves Array

    发现进行一次排序后先前的操作都无效了,所以只需做最后一次排序后的操作.翻转操作打个翻转标记,互换操作根据翻转标记即可. 时间复杂度 \(O\left(n+m\right)\). code: #incl ...

  2. Java基础教程——UDP编程

    UDP:User Datagram Protocol,用户数据报协议 服务端: import java.net.*; import java.io.*; public class UdpServer ...

  3. 浅谈AsyncLocal,我们应该知道的那些事儿

    前言 最近查看有关框架源码,发现AsyncLocal这玩意水还挺深,于是花了一点功夫去研究,同时对比ThreadLocal说明二者区别以及在何时场景下使用AsyncLocal或ThreadLocal. ...

  4. 交换机三种端口模式Access、Hybrid和Trunk

    以太网端口有 3种链路类型:access.trunk.hybird 什么是链路类型? vlan的链路类型可以分为接入链路和干道链路. 1.接入链路(access link)指的交换机到用户设备的链路, ...

  5. 16.java设计模式之迭代器模式

    基本需求: 展示一个学校的结构,比如一个学校下面有多个学院,学院下面有多个系,对其节点主要是遍历,与组合模式略有不同 传统方案: 学校<-学院<-系 依次继承 这种方式,在一个页面中展示出 ...

  6. Verilog之阻塞赋值非阻塞赋值

    verilog设计进阶 时间:2014年5月6日星期二 主要收获: 1. 阻塞赋值与非阻塞赋值: 2. 代码测试: 3. 组合逻辑电路和时序逻辑电路. 阻塞赋值与非阻塞赋值: 1. 阻塞赋值" ...

  7. Prometheus 使用之 node exporter

    本文使用的 Prometheus 版本为 2.22.0,node exporter 版本为 1.0.1:部署在 Linux 服务器Prometheus 是开源的监控报警系统和时序列数据库 (TSDB) ...

  8. charles 常用功能(八)重定向

    1.点击鼠标右键 点击保存就保存到桌面上了 效果图 在123.txt中修改 然后另存为 点击红圈处 然后再次发送请求

  9. Visual Studio 2012 Ultimate旗舰版下载地址与序列号

    (为了方便个人使用转的的别的帖子的内容,原文链接http://wenku.baidu.com/link?url=acL08J8bTNQ4S5Sd3n3oLN5KJTtrfe8hHuP8aUrNscKN ...

  10. PyQt(Python+Qt)学习随笔:QDockWidget停靠部件floating和features属性

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 1.floating属性 floating属性表示QDockWidge ...