区块链技术开发教程

区块链技术指南,区块链技术的工作原理讲解,区块链技术入门教程

超级账本实践——链码开发

2018-11-29

区块链技术教程,区块链开发,超级账本。本文分享如何写链码,以及部署到超级账本网络上并讲解如何调用。

点击区块链技术培训课程获取更多区块链技术学习资料。


前言

上一篇分享了如何搭建超级账本联盟链,本文分享如何写链码,以及部署到超级账本网络上并讲解如何调用。

目录

智能合约与链码的概念

网络节点的配置

链码sdk接口

部署和调试

一、智能合约与链码的概念

1.1 简介

智能合约指的是能自动执行的计算机程序,好比是“合同” + “法院”的形式,放在网络上的电子合同即是智能合约,而且交易内容以代码的形式展现,合同规则透明不可篡改,并且强制执行。即在这套系统中“代码即法律”。
Fabric的智能合约称为链码(chaincode),分为系统链码和用户链码。

系统链码: 包括系统的配置
用户链码: 用户链码用于实现用户的功能,真正实现了逻辑与数据的分离。

WechatIMG24.png


1.2 链码特性

部署在Fabric区块链网络结点上

运行在隔离的安全容器中(如docker)

由背书节点管理和维护

通过背书节点开放的接口执行

与Fabric区块链交互的唯一渠道

⽣成Transaction的唯一来源 Transactions > Block > Ledger

一笔完整的交易流程如图所示,背书节点与链码容器通信,然后返回链码执行结果,经过一系列传输最终才会生成区块记录在账本里。

WechatIMG25.png

1.3 系统链码

系统内置的链码,负责Fabric节点自身的处理逻辑,包括系统配置、背书、校验等工作。

系统链码仅支持Go语言,在Peer节点启动时会自动完成注册和部署。

cscc:负责管理记账节点上的配置信息,加入通道等

escc:对读写集转换和签名背书(msp管理)

lscc:负责管理链码的生命周期,如安装、实例化、升级、查询

vscc:负责签名验证/策略验证

qscc:负责ledger查询,如区块、交易数据、区块链信息等

mvcc: 比对读写集的版本


1.4 用户链码

实现业务逻辑的链码,由应用开发人员使用基于区块链分布式账本的状态及处理逻辑,运行在链码容器中,通过Fabric提供的接口与账本平台进行交互。
开发语言:go、java、python、node.js
目前fabric对go语言的链码支持的最好
SDK:$GOPATH/src/github.com/hyperledger/fabric/core/chaincode/shim注:按照Fabric的设计,shim包是供Chaincode开发的SDK。以上的路径是在docker容器中的存放路径。

1.5 什么时候会执行系统链码?

下图分别表示背书节点执行用户链码之后会执行ESCC,以及写入账本之前需要运行VSCC。
• ESCC决定如何对交提案进行背书
• VSCC决定交易的有效性(包括背书的正确性)

WechatIMG23.png


1.6 链码的生命周期

链码的生命周期比较完善包括以下几种:
打包(package)
安装(install)
实例化(instance)
升级(upgrade)
启动(start)
停止(stop)

注:
1. 链码的安装是单次单节点的。
2. 链码的存储是可以多个链共享的,实例化的时候才会记录到不同链的账本数据里,不同链的数据是独立隔离的。

1.7 如何开始编写链码?

在编写之前需要两个概念:
• Transaction:⼀次Chaincode函数的运⾏。
Transaction存储chaincode执⾏的相关信息,⽐如chaincodeID、函数名称、参数等,并
不包含操作的数据。


World State:Fabric区块链系统中所有变量的值的集合。
Transaction实际操作的是数据,每个chaincode都有⾃⼰的数据。
Fabric使⽤Rocksdb存储数据,⼀个key-value数据库。Key -> 变量,value -> 值。
Fabric将每⼀对key-value叫做⼀个state,⽽所有的chaincode的state的合集就是World State。

2. 使用SDK编写链码

链码是通过sdk与背书节点交互的

必须要实现的接口(go语言实现) 编写链码就是使用sdk提供的接口(比如获取交易号shim.GetTxID)完善以下代码的过程:


import (
    "fmt"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)// 定义合约
type SmartContract struct {}//初始化函数 只会执行一次func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response //查询或更新world state, 可多次被调用func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response //Query transaction中被调⽤,查询world state,可多次被调⽤func (s *SmartContract) Query(APIstub shim.ChaincodeStubInterface) sc.Response// 主函数.func main() {}

  

链码示例 下面是一个简单的包含读写功能的链码:


//链块学院 liankuai.tech//张俊 github.com/yuminvvv111 956532364@qq.com
package main
/*
 * 导入依赖的模块
 */import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"
 
    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)
// 定义结构体
type SmartContract struct {
}
// 定义人员的结构.  这个结构依赖上边注入的模块encoding/json
type People struct {
    Name   string `json:"name"`
    Gender  string `json:"gender"`
    Age string `json:"age"`
    Domicle  string `json:"domicle"`
}
/*
 * 初始化函数 只会执行一次
 */func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    return shim.Success(nil)
}
/*
 * 查询或更新world state, 可多次被调用
 */func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
 
    // 检索获取的参数
    function, args := APIstub.GetFunctionAndParameters()
    // 匹配获取的函数名字执行对应的函数
 switch function {
    case "initLedger":
        return s.initLedger(APIstub)
    case "queryAllPeoples":
        return s.queryAllPeoples(APIstub)
    case "changePeopleDomicle":
        return s.changePeopleDomicle(APIstub, args)
    }
 
    return shim.Error("错误的函数名")
}
 
func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
    peoples := []People{
        People{Name: "张三", Gender: "男", Age: "18", Domicle: "北京"},
        People{Name: "李四", Gender: "女", Age: "19", Domicle: "上海"},
        People{Name: "王五", Gender: "男", Age: "20", Domicle: "广州"},
    }
 
    i := 0
    for i < len(peoples) {
        fmt.Println("i is ", i)
        peopleAsBytes, _ := json.Marshal(peoples[i])
        APIstub.PutState("PEOPLE"+strconv.Itoa(i), peopleAsBytes)
        fmt.Println("Added", peoples[i])
        i = i + 1
    }
 
    return shim.Success(nil)
}
func (s *SmartContract) queryAllPeoples(APIstub shim.ChaincodeStubInterface) sc.Response {
 
    startKey := "PEOPLE0"
    endKey := "PEOPLE999"
 
    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
    if err != nil {
        return shim.Error(err.Error())
    }
    defer resultsIterator.Close()
 
    // 这个缓冲就是返回查询到的json数据
    var buffer bytes.Buffer
    buffer.WriteString("[")
 
    bArrayMemberAlreadyWritten := false
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        if err != nil {
            return shim.Error(err.Error())
        }
        // 为每个元素中间添加间隔的逗号
        if bArrayMemberAlreadyWritten == true {
            buffer.WriteString(",")
        }
        buffer.WriteString("{\"Key\":")
        buffer.WriteString("\"")
        buffer.WriteString(queryResponse.Key)
        buffer.WriteString("\"")
 
        buffer.WriteString(", \"Record\":")
        // Record是一个json字符串
        buffer.WriteString(string(queryResponse.Value))
        buffer.WriteString("}")
        bArrayMemberAlreadyWritten = true
    }
    buffer.WriteString("]")
 
    fmt.Printf("- queryAllPeoples:\n%s\n", buffer.String())
 
  return shim.Success(buffer.Bytes())
}
func (s *SmartContract) changePeopleDomicle(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
 
    if len(args) != 2 {
        return shim.Error("参数不正确")
    }
 
    peopleAsBytes, _ := APIstub.GetState(args[0])
    people := People{}
 
    json.Unmarshal(peopleAsBytes, &people)
    people.Domicle = args[1]
 
    peopleAsBytes, _ = json.Marshal(people)
    APIstub.PutState(args[0], peopleAsBytes)
 
    return shim.Success(nil)
}
// 主函数.func main() {
 
    // 创建一个新的合约
    err := shim.Start(new(SmartContract))
    if err != nil {
        fmt.Printf("创建合约错误: %s", err)
    }
}


2.1 SDK常用接口

shim.ChaincodeStubInterface APIs 分为五类

• State 操作:

GetState(key string) ([]byte, error)
PutState(key string, value []byte) error
DelState(key string) error
RangeQueryState(startKey, endKey string) (StateRangeQueryIteratorInterface, error)


• Chaincode相互调⽤:

InvokeChaincode(chaincodeName string, args [][]byte) ([]byte, error)QueryChaincode(chaincodeName string, args [][]byte) ([]byte, error)


• Table 操作:

CreateTable(name string, columnDefiniGons []*ColumnDefiniGon) error GetTable(tableName string) (*Table, error)DeleteTable(tableName string) errorInsertRow(tableName string, row Row) (bool, error) ReplaceRow(tableName string, row Row) (bool, error) GetRow(tableName string, key []Column) (Row, error) GetRows(tableName string, key []Column) (<-chan Row, error) DeleteRow(tableName string, key []Column) error


• TransacGon操作:

GetArgs() [][]byteGetStringArgs() []stringGetTxID() stringReadCertAttribute(attributeName string) ([]byte, error) GetCallerCerGficate() ([]byte, error) GetCallerMetadata() ([]byte, error)GetBinding() ([]byte, error)GetPayload() ([]byte, error)GetTxTimestamp() (*Gmestamp.Timestamp, error) VerifyAttribute(attributeName string, attributeValue []byte) (bool, error) VerifyAttributes(attrs ...*attt.Attribute) (bool, error) VerifySignature(cerGficate, signature, message []byte) (bool, error)


• Event操作:

    
SetEvent(name string, payload []byte) error


2.2 链码的部署和调用

2.2.1 如何部署链码

1. 编写yaml格式的网络配置文件

2. docker-compose启动网络 

3. 创建通道

4. 加入通道

5. 安装链码

6. 实例化链码

2.2.2 如何调用链码?

调用链码分两种情况:

2.2.2.1. 终端里输入命令调用

查询和更改world state操作

peer chaincode invoke -C myc -n mycc -c '{"function":"initLedger","Args":["hello world"]}'


只查询

peer chaincode query.....


删除

peer chaincode delete.....


2.2.2.2. 应用程序使用另外一种sdk提供的接口调用

通过fabric提供的另外一种sdk:HFC( HyperledgerFabricClient) 是提供给应用程序开发的SDK, API包含了交易处理、 安全的成员管理服务、 区块链查询和事件处理等。
包括两大模块:
* 1. fabric-client,比如使用NewChannel接口可以创建通道, sendTransactionProposal可以调用链码里的函数
* 2. fabric-ca,比如enroll用户登录,register新用户注册

2.2.3 SDK调用链码示例

var Fabric_Client = require('fabric-client');   //加载sdk模块var channel = fabric_client.newChannel('mychannel'); //创建通道var peer = fabric_client.newPeer('grpc://localhost:7051'); //连接peer节点
channel.addPeer(peer);var order = fabric_client.newOrderer('grpc://localhost:7050') //连接排序节点
channel.addOrderer(order);var request = {
        chaincodeId: 'myChaincode', //调用哪个链码
        fcn: 'testFunc', //调用链码里的哪个函数
        args: ['arg1', 'arg2', 'arg3], //参数
        chainId: 'mychannel', //哪个通道的链码
        txId: tx_id
    };
channel.sendTransactionProposal(request); //发送交易

二、小结

开发链码主要用到的语言是golang,因为fabric对它的支持目前最好。而应用层使用sdk的开发其它语言也可以,nodejs是比较通用和快速的选择。
部署和调用链码之前需要先创建通道、加入通道、更新锚节点,然后才能进行有关链码的操作,比如安装链码、实例化链码、invoke和query等。


-END-


001周末班+0628二维码.jpg

区块链应用案例手箭头.png

点击查看更多区块链应用成功案例 ,区块链技术开发教程 。



003学习路径+公众号.jpg


7*24客服电话

150-1118-1611

扫码添加助教微信

二维码
菜单 在线咨询 回到顶部
关闭