BSN智能合约开发培训-百度超级链Xuperchain(四)

  百度超级链XuperChain是一个支持多语言合约的区块链框架,有多种语言来供大家选择使用开发智能合约。目前超级链的智能合约可以使用C++ 或者 Go 语言来编写,理论上任何可以编译成Wasm字节码的语言都可以用来编写超级链的智能合约。C++ 合约相对Go合约性能会更好些,go合约在易用性上更好,开发者可以根据需要选择自己喜欢的语言来编写智能合约,这篇文章会通过一步步的指引来帮助大家使用C++ 或者Go来编写超级链的智能合约。下面的内容将按照步骤拆解,手把手教你如何顺利完成智能合约的编写,部署和测试,耐心读完本文相信你会对超级链智能合约建立完整认知,快速get新技能。

  1. 准备工作

  1.1环境要求

  目前超级链节点主要运行在linux和mac上,windows不能运行超级链节点。

  1.go >= 1.12.x && <= 1.13.x

  2.g++ >= 4.8.2 或者 clang++ >= 3.3

  3.Docker

  1.2下载编译XuperChain

  智能合约只有部署到链上才能运行,因此我们首先要编译并启动xuperchain节点。

  如果需要使用特定分支,使用git checkout来切换特定分支,如 git checkout v3.7

<code>$ cd $HOME $ git clone https://github.com/xuperchain/xuperchain.git xuperchain $ cd xuperchain && make  /<code>

  1.3设置环境变量

  这些环境变量有助于我们更方便的执行一些命令而不用指定命令的全路径。

<code>export PATH=$HOME/xuperchain/output:$PATH export XDEV_ROOT=$HOME/xuperchain/core/contractsdk/cpp/<code>

  1.4启动XuperChain

  --vm ixvm参数是选择ixvm合约虚拟机,开发合约过程中使用ixvm虚拟机能加快合约部署

<code>$ cd output ## 首先创建链 $ ./xchain-cli createChain ## 后台启动xuperchain节点 $ nohup ./xchain --vm ixvm &  /<code>

  1.5创建合约账号

  合约账号用来进行合约管理,比如合约的权限控制等,要部署合约必须创建合约账号,同时合约账号里面需要有充足的xuper来部署合约。

  创建合约账号 XC1111111111111111@xuper

<code>$ ./xchain-cli account new --account 1111111111111111 --fee 2000 contract response: { "pm": { "rule": 1, "acceptValue": 1.0 }, "aksWeight": { "dpzuVdosQrF2kmzumhVeFQZa1aYcdgFpN": 1.0 } } The gas you cousume is: 1000 The fee you pay is: 2000 Tx id: d62704970705a2682e2bd2c5b4f791065871fd45f64c87815b91d8a00039de35 account name: XC1111111111111111@xuper /<code>

  给合约账号转账

  $ ./xchain-cli transfer --to XC1111111111111111@xuper --amount 100000000

  cd26657006f6f75f07bd53ad0a7fe74d76985cd592542d8cc87dc3fcdde115f5

  1.6小结

  至此我们完成了所有的准备工作,包括编译XuperChain,创建链,启动节点,创建合约账号,后面我们开始体验怎么编译,部署和调用智能合约。

  2.快速体验

  在开始编写智能合约之前首先通过一个简单的例子来给大家演示合约是如何从代码到字节码,以及如何部署到链上,如何发起对智能合约的调用。我们使用一个c++合约为例来展示如何编译、部署、调用合约。

  2.1创建合约工程

  xdev工具是随XuperChain发布的一个合约编译和测试工具,在编译完xuperchain之后生成在output目录。

  xdev提供了一个默认的c++合约工程模板

<code>$ xdev init hello-cpp  /<code>

  这个命令创建了一个hello-cpp的合约工程

  2.2编译合约

  第一次编译的时间会长一点,因为xdev需要下载编译器镜像,以及编译超级链的标准库。

<code>$ xdev build -o hello.wasm CC main.cc LD wasm/<code>

  编译结果为hello.wasm,后面我们使用这个文件来部署合约

  2.3部署合约

<code>$ ./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname hello --fee 5200000 --runtime c ./hello-cpp/hello.wasm contract response: initialize succeed The gas you cousume is: 151875 The fee you pay is: 5200000 Tx id: 8c33a91c5cf564a28e7b62cad827ba91e19abf961702659dd8b70a3fb872bdf1  /<code>

  此命令看起来很长,但是其中很多参数都有默认值,我们先来看一下参数的含义:

  • wasm deploy :此为部署wasm合约的命令参数,不做过多解释

  •--account XC1111111111111111@xuper:此为部署wasm合约的账号(只有合约账号才能进行合约的部署)

  •--cname hello:这里的hello是指部署后在链上的合约名字,可以自行命名(但有规则,长度在4~16字符)

  •--runtime c 指明我们部署的是一个c++代码编译的合约,如果是go合约这里填go即可。

  •--fee 为我们部署这个合约所需要的xuper

  •最后的hello.wasm是合约编译好的文件

  2.4调用合约

<code>$ ./xchain-cli wasm invoke --method hello --fee 110000 hello contract response: hello world The gas you cousume is: 35 The fee you pay is: 110000 Tx id: d8989ad1bfd2d08bd233b7a09a544cb07976fdf3429144c42f6166d28e9ff695  /<code>

  参数解释如下:

  •wasm invoke 表示我们要调用一个合约

  •--method hello 表示我们要调用合约的hello方法

  •--fee 指明我们这次调用合约花费的xuper

  •最后的参数指明我们调用的合约名字hello

  2.5小结

  通过本节的学习,我们快速掌握了如果编译,部署和调用合约,在下面的章节里面我们学些如果使用C++或者Go语言来编写智能合约。

  3.合约编写详解

  XuperChain目前主要支持两种编译成wasm格式的合约语言,c++和go,合约框架的整体结构是一致的,在不同语言上的表现形式不太一样,但熟悉一种语言的SDK之后很容易迁移到其他语言。

  下面大概说明如何编写这两种类型的合约

  3.1 C++合约

  以counter合约为例来看如何编写一个C++合约。

  3.1.1合约样例

  代码在contractsdk/cpp/example/counter.cc

<code>#include "xchain/xchain.h" struct Counter : public xchain::Contract {}; DEFINE_METHOD(Counter, initialize) { xchain::Context* ctx = self.context(); const std::string& creator = ctx->arg("creator"); if (creator.empty()) { ctx->error("missing creator"); return; } ctx->put_object("creator", creator); ctx->ok("initialize succeed"); } DEFINE_METHOD(Counter, increase) { xchain::Context* ctx = self.context(); const std::string& key = ctx->arg("key"); std::string value; ctx->get_object(key, &value); int cnt = 0; cnt = atoi(value.c_str()); char buf[32]; snprintf(buf, 32, "%d", cnt + 1); ctx->put_object(key, buf); ctx->ok(buf); } DEFINE_METHOD(Counter, get) { xchain::Context* ctx = self.context(); const std::string& key = ctx->arg("key"); std::string value; if (ctx->get_object(key, &value)) { ctx->ok(value); } else { ctx->error("key not found"); } }  /<code>

  3.1.2代码解析

  下面我们逐行解析合约代码:

  •#include 为必须的,里面包含了编写合约所需要的库。

  • struct Counter : public xchain::Contract {}; 声明了我们的合约类,所有的合约类都要继承自xchain::Contract。

  • DEFINE_METHOD(Counter, initialize) 我们通过DEFINE_METHOD来为合约类定义合约方法,在这个例子里面我们为Counter类定义了一个叫initialize的合约方法。

  • xchain::Context* ctx = self.context();用来获取合约的上下文,每个合约都有一个对应的合约执行上下文,通过上下文我们可以获取合约参数,写入合约数据,context对象是我们经常要操作的一个对象。

  • const std::string& creator = ctx->arg("creator");,用于从合约上下文里面获取合约方法的参数,这里我们获取了名字叫creator的合约参数,合约的参数列表是一个map结构, key为合约参数的名字,value为参数对应的用户传递的值。

  • ctx->put_object("creator", creator); 通过合约上下文的put_object方法,我们可以向链上写入数据。

  • ctx->ok("initialize succeed"); 用于返回合约的执行结果,如果合约执行失败则调用ctx->error。

  通过上面的代码分析我们得到了如下知识

  •一个合约有多个方法组成,如counter合约的initialize,increase, get方法。

  • initialize是每个合约必须实现的方法,这个合约方法会在部署合约的时候自动执行。

  •每个合约方法有一个Context对象,通过这个对象我们能获取到很多有用的方法,如获取用户参数等。

  •通过Context对象的ok或者error方法我们能给调用方反馈合约的执行情况:成功或者失败。

  更多的c++语言合约例子在超级链项目的core/contractsdk/cpp/example里面寻找。

  3.2 Go合约

  以counter合约为例来看如何编写一个go合约。

  3.2.1合约样例

  代码在contractsdk/go/example/counter/counter.go

<code>package main import ( "strconv" "github.com/xuperchain/xuperunion/contractsdk/go/code" "github.com/xuperchain/xuperunion/contractsdk/go/driver" ) type counter struct{} func (c *counter) Initialize(ctx code.Context) code.Response { creator, ok := ctx.Args()["creator"] if !ok { return code.Errors("missing creator") } err := ctx.PutObject([]byte("creator"), creator) if err != nil { return code.Error(err) } return code.OK(nil) } func (c *counter) Increase(ctx code.Context) code.Response { key, ok := ctx.Args()["key"] if !ok { return code.Errors("missing key") } value, err := ctx.GetObject(key) cnt := 0 if err == nil { cnt, _ = strconv.Atoi(string(value)) } cntstr := strconv.Itoa(cnt + 1) err = ctx.PutObject(key, []byte(cntstr)) if err != nil { return code.Error(err) } return code.OK([]byte(cntstr)) } func (c *counter) Get(ctx code.Context) code.Response { key, ok := ctx.Args()["key"] if !ok { return code.Errors("missing key") } value, err := ctx.GetObject(key) if err != nil { return code.Error(err) } return code.OK(value) } func main() { driver.Serve(new(counter)) }  /<code>

  go合约的整体框架结构跟c++合约一样,在表现形式上稍微有点不一样:

  •c++合约使用DEFINE_METHOD来定义合约方法,go通过结构体方法来定义合约方法

  •c++通过ctx->ok来返回合约数据,go通过返回code.Response对象来返回合约数据

  • go合约需要在main函数里面调用driver.Serve来启动合约。

  更多的go语言合约例子在超级链项目的core/contractsdk/go/example里面寻找。

  3.2.2合约编译

  Go合约使用如下命令来编译合约

<code>GOOS=js GOARCH=wasm go build -o hello.wasm  /<code>

  3.2.3合约部署

  Go合约部署唯一跟c++合约不一样的地方在于--runtime参数,完整命令如下

<code>$ ./xchain-cli wasm deploy --account XC1111111111111111@xuper --cname hello --fee 5200000 --runtime go ./hello-go/hello.wasm/<code>

  Go合约的调用跟c++合约参数一致。

  3.3小结

  在这个章节里面我们学习了如何使用c++和go语言来编写合约,更多的合约例子可以在对应语言SDK的example目录里面寻找,在下一章节我们学习如果给合约编写单元测试。

  4.合约单测

  如果每次测试合约都需要部署到链上再发起调用会特别麻烦,xdev工具提供了单测能力,可以脱离链上环境运行合约。

  test目录下放着合约测试文件,文件以 .test.js结尾,可以有多个测试文件。以hello-cpp目录下的test/hello.test.js为例,文件内容如下:

<code>var assert = require("assert"); Test("hello", function (t) { var contract; t.Run("deploy", function (tt) { contract = xchain.Deploy({ name: "hello", code: "../hello.wasm", lang: "c", init_args: {} }) }); t.Run("invoke", function (tt) { resp = contract.Invoke("hello", {}); assert.equal(resp.Body, "hello world"); }) })  /<code>

  使用Test函数来定义测试case,hello为测试名字, 匿名js function作为测试的body。全局对象xchain是我们跟xchain环境打交道的入口,xchain.Deploy用来部署一个合约到xchain环境,返回的contract对象,调用contract.Invoke方法即可调用合约。Deploy和Invoke方法都是通过抛出异常的方式来处理错误,测试框架会自动捕获错误来结束测试case。t.Run可以定义子测试case。

  使用如下命令来启动测试

<code>$ cd hello-cpp $ xdev test # 测试test目录下的所有case === RUN hello === RUN hello/deploy === RUN hello/invoke --- PASS: hello (0.11s) --- PASS: hello/deploy (0.07s) --- PASS: hello/invoke (0.02s) PASS  /<code>

  5. VSCode编辑器集成

  配置编译和测试task

  为了方便在vscode里面编译和测试合约,在.vscode/tasks.json里面添加如下内容

<code>{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "xdev build", "type": "shell", "command": "xdev build -p", "options": { "cwd": "${workspaceFolder}" }, "group": { "kind": "build", "isDefault": true } }, { "label": "xdev test", "type": "shell", "command": "xdev test", "options": { "cwd": "${workspaceFolder}" } } ] } /<code>

  编译合约

  Run Build Task(⇧⌘B)来启动构建

  跑合约单测

  调用Run Task命令之后,选择xdev test来触发单元测试

代码补全

  为了让vscode帮我们自动补全代码,需要做如下配置,在项目的.vscode/settings.json文件里面加上这一个配置

<code>{ "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json" }/<code>

  之后就能用vscode的自动补全功能了.

  6.开放网络集成环境

  超级链开放网络是基于百度自研底层技术搭建的区块链基础服务网络,符合中国标准,超级节点遍布全国,区块链网络完全开放,为用户提供区块链快速部署和运行的环境,最低2元钱就用上的区块链服务,让信任链接更加便利。

  超级链开放网络为开发者提供了合约开发、编译、部署、管理的一站式可视化集成环境,下面介绍如何在开放网络上开发部署智能合约。

  6.1账户注册

  1. 在超级链官网https://xchain.baidu.com/使用百度账号登录,如果没有百度账号请先注册。

  2. 进入超级链开放网络控制台,第一次登录的用户,平台会为用户创建区块链账户,请按照创建账户指引文档完成安全码设置,并记录自己的助记词和私钥。

  3.

  6.2创建合约账户

  1.在工作台,选择「开放网络 —> 合约管理」,点击「创建合约账户」

  2.进入创建合约账户页,输入安全码后点击「确认创建」,系统自动生成账户名称后,即创建完毕

  3.

  6.3合约开发和部署

  1.在工作台,选择「开放网络 —> 合约管理」,点击「创建智能合约」

  2. 进入新页面,按要求填写基本信息、编辑合约代码,编译成功后点击「安装」,即可进入合约安装(部署)流程。合约代码编译有两种方式:

  •模板合约;选择模板后,只需在模板代码中填写相关参数即可(参考模板详情完成参数填写)

  •自定义合约;在编辑器内完成C++语言的合约编辑即可

  •

  3. 进入安装流程,用户需按合约代码完成预执行操作。点击「开始验证」,执行通过会进入安装确认页

  •模板合约;系统会提供模板的函数,只需填写参数即可(可参考模板详情)

  •自定义合约;根据页面操作说明,完成函数、参数填写

  •

  4. 进入确认安装页,页面显示安装合约预计消耗的余额。点击「安装合约」将合约上链,上链过程需要等待10S左右。安装完成后,在合约管理列表中可看到合约状态变更为‘安装成功’,即该合约已完成安装。

  6.4合约调用

  目前开放网络支持通过Go和Javascript两种SDK调用智能合约。

  • Go SDK:

  https://github.com/xuperchain/xuper-java-sdk

  • Javascript SDK:

  https://github.com/xuperchain/xuper-sdk-js

  7.结语

  以上就是关于如何编写超级链智能合约的锦囊秘籍,可以助你从小白秒变资深区块链开发者。百度超级链正在联合BSN举办区块链创新开发者大赛,现在基于百度超级链编写智能合约将有机会获得万元大奖,免费享受专业的区块链开发培训,点击阅读原文速去报名吧!