目录
前言
XuperChain是百度公司开发的一个开源区块链底层架构,其拥有链内并行技术、可插拔共识机制、一体化智能合约等多项国际领先技术,具备全球化部署能力,可以满足开发者各类业务场景需求,让区块链应用搭建更灵活、更安全、更高效。本文主要介绍利用XuperChain开发的一个“蝴蝶乒乓球拍认证与追溯系统”过程,及开发过程中的感受。
一、概述
在乒乓球拍底板领域,名气与实力最强的应该是日本产的蝴蝶乒乓球底板。目前是世界第一品牌,价格也相对贵一些。它的整体性能很高,能攻能守。
目前,每一个蝴蝶乒乓球拍都含有防伪码或者唯一标识,通过防伪码或者唯一标识可以鉴别乒乓球拍是否是当事人所有或者是否是仿制的,从而可以防止乒乓球拍丢失后被低价售出、违法厂商仿制等问题。
乒乓球拍认证与追溯应用就是利用区块链的特性(可追溯、透明安全等)确保蝴蝶乒乓球拍的真假与追溯,即正规厂商每出售一个商品给区域代理商都会进行上链(产品的唯一标识或防伪码、时间、厂商信息、代理商信息等),区域代理商售卖给买家会继续将这一过程上链(产品的唯一标识或防伪码、时间、代理商信息、买家信息等),以及买家之间的交易过程继续上链(产品的唯一标识或防伪码、时间、卖家信息、买家信息等),这一过程可能重复多次。因此通过一系列的上链过程,不但可以确保蝴蝶乒乓球拍是否正规合法(通过链上查询蝴蝶乒乓球拍信息),同时也可以追溯蝶乒乓球拍的交易过程,信息完全透明化。
本章内容分为5大部分,分别是环境准备、设计概览、底层设计与部署、顶层调用与实现、总结与扩展。
设计概览部分主要是关于如何设计实现该应用的结构,包括模块结构、数据结构等。底层设计与部署部分主要是关于如何设计智能合约与智能合约的部署实现过程,为上层调用提供SDK接口。顶层调用与实现部分主要是关于如何利用API实现接口调用完成应用的主要业务逻辑,为用户提供可视化结果。最后总结与扩展部分主要是关于实现该应用的总结,以及应用扩展相关的介绍。
二、环境配置
乒乓球拍认证与追溯应用涉及几个模块的设计与实现,每个模块涉及不同的技术,因此需要准备对应的技术语言与运行环境。
语言:C++、go(需要安装go语言环境依赖)
运行环境:Linux(可以通过虚拟机安装)
对应的安装包与环境已给出,如图1所示

其中go1.14.4.linux-amd64.tar.gz是go语言程序运行所需要的环境依赖包,需要在Linux环境下解压并配置环境。
ubuntu-18.04.1-desktop-amd64.iso是桌面Linux系统镜像文件,可以根据这个镜像文件在虚拟机中安装Linux系统。
VMware-workstation-full-16.1.2-17966106.exe是虚拟机安装包,直接运行安装即可。
xuper-sdk-go-1.0.0.zip是官方Go SDK的压缩文件,调用智能合约需要用到的环境,需要解压并配置相关文件。
1.安装虚拟机
安装建议:通过在本机Windows系统下先安装虚拟机,再在安装的虚拟机下安装Linux系统。下过介绍安装虚拟机的过程。
第1步:双击运行文件VMware-workstation-full-16.1.2-17966106.exe,并在弹出的安装提示中单击“是”,如果没有就直接忽略即可,安装界面结果如图2所示,单击“下一步”

第二步:勾选“我接受许可协议中的条款”,然后单击“下一步”,如图3所示。

第3步:选择软件安装路径,默认安装在C盘驱动器中,单击“更改”修改安装路径(如:D:/VMWare16/目录下),然后单击“下一步”,如图4所示。注意:安装路径文件夹名称不能包含汉字!

第4步:取消“检查时启动...”和“加入VMware ...”检查,然后单击“下一步”,如图5所示。

第5步:单击“下一步”,如图6所示。

第6步:单击“安装”,如图7所示。

第7步:在安装中,此过程需要几分钟,请耐心等待......,如图8所示。

第8步:点击“完成”,如果之后有重启系统的提示,请点击“是”,完成重启系统,如图9所示。

第9点:重启系统后,在桌面找到安装好的虚拟机图标,右击选择“以管理员身份”运行打开,如图10所示。注意:如果同学有对应的许可证,输入对应的许可证即可;如果没有选择试用30天即可。

2.安装Linux环境
完成虚拟机的安装后,接下来需要在虚拟机中安装Linux系统,步骤如下。
第1步:在桌面找到安装好的虚拟机图标,右击选择“以管理员身份”运行打开,并单击“创建新的虚拟机”,如图11所示。

第2步:在弹出的新建虚拟机向导中,首先选择“自定义”,再单击“下一步”,如图12所示。

第3步:在弹出的虚拟机硬件兼容性中,继续单击“下一步”,如图13所示。

第4步:选择安装程序光盘映像文件,点击“浏览”,找到镜像文件ubuntu-18.04.1-desktop-amd64.iso位置,单击“下一步”,如图14所示。

第5步:设置账户信息,系统名、登录账户、登录密码,然后单击“下一步”,如图15所示。注意:命名是英文,不要出现中文。

第6步:虚拟机的命名(自定义)以及选择安装位置,单击“下一步”,如图16所示。注意:路径不要出现中文以及特殊字符。

第7步:处理器配置,默认设置都是2即可,点击“下一步”,如图17所示。

第8步:设置虚拟机内存,设置4096MB或者推荐内存设置都可,单击“下一步”,如图18所示。

第9步:选择网络类型为:使用网络地址转换(NAT),单击“下一步”,如图19所示。

第10步:选择I/O控制器类型,推荐选择即可,单击“下一步”,如图20所示。

第11步:选择磁盘类型,推荐选择即可,单击“下一步”,如图21所示。

第12步:磁盘选择:创建新虚拟磁盘,单击“下一步”,如图22所示。

第13步:磁盘容量,建议选择20GB,单击“下一步”,如图23所示。

第14步:磁盘文件地址我这里选择默认,单击“下一步”,如图24所示。

第15步:完成创建,简易安装开始,等待安装完成即可,单击“完成”,如图25,26所示。


第16步:安装完成界面,点击界面后显示输入账户密码界面,使用之前设置的账户密码进行登录,如图27、28、29所示。



三、系统需求分析与设计
本小节介绍系统的需求分析与简单设计思想。
1.概述
通常应用设计分为3个层次,分别是业务应用层、服务接口层、底层实现层,下面分别介绍。
1.业务应用层
业务应用层最直接目的是给用户提供可视化或其他形式的展示,用户可以通过一定的操作得到自己想要的结果。如在该应用中,已经注册过的用户可以通过查询功能得到自己乒乓球拍的详细信息。
一般来说,该层可以通过Web端(浏览器访问)、移动App端(手机App访问)等方式实现,主要是结果的可视化展示。虽然实现方式不一样,不过其调用原理是一致的,都是通过调用服务接口层的API实现的,并不关心结果是如何生成的。
2.服务接口层
服务接口层主要是为业务应用层提供服务的,通常提供增加数据信息、删除数据信息、修改数据信息、查询数据信息这四种服务API接口。
3.底层实现层
底层实现层主要是提供数据、数据结构、网络访问、数据安全等内容,这些内容已经被完整的实现,不需要关心实现细节,只需要按照规定直接使用即可。
该层主要是为服务接口层提供支持的,对服务接口层实现增加数据信息、删除数据信息、修改数据信息、查询数据信息四种服务,需要在底层进行数据的存储、修改等。
注意以上描述只是简单介绍了每一层的目的或者功能,如图31所示,真正的具体实现是更为复杂的,因为需要考虑很多因素,本次应用快速迭代开发实现只是为了帮助更好地掌握区块链相关知识以及区块链的应用场景。

以上描述可能有些抽象,举个例子,公司老板给财务部的员工分配一个人任务(如完成去年的财务报表),财务部的员工就要去资料室收集相关数据,并根据自己所学的技能,将数据进行分析、筛选、统计、总结甚至形成图表的形式展示给老板。这个过程,老板或者老板的命令属于业务应用层,只关心结果即财务报表,并不关心报表是怎么生成的;财务部员工的工作属于服务接口层,访问并处理数据,对数据进行加工;而资料室或者档案室就属于底层实现层,只提供最原始的数据、数据存储形式以及对数据进行安全访问,即不是每个员工都可以进入资料室的。
2.需求分析
需求分析的目标是把用户对待开发软件提出的“要求”或“需要”进行分析与整理,确认后形成描述完整、清晰与规范的文档,确定软件需要实现哪些功能,完成哪些工作。此外,软件的一些非功能性需求(如软件性能、可靠性、响应时间、可扩展性等),软件设计的约束条件,运行时与其他软件的关系等也是软件需求分析的目标。
为了快速开发出该应用,需要进行简化,最终确定该应用要完成的功能即可,其他就暂时不用管,有兴趣的同学可以自己研究一下。
具体来说,该应用需要完成的功能有用户信息添加功能、用户——乒乓球拍信息添加功能、乒乓球拍信息追溯功能、乒乓球拍鉴伪功能,下面分别描述这四个主要功能。
1.用户信息添加功能
该功能是当用户(可能是代理商,也可能是个人购买者)购买蝴蝶乒乓球拍时,如果该用户信息没有在区块链上进行存储,需要将该用户信息上链(相当于用户注册功能),便于以后的信息追溯与乒乓球拍的绑定。
2.用户——乒乓球拍信息添加功能
该功能主要是将用户与购买的乒乓球拍进行绑定,以便确认某个乒乓球拍真正属于某个用户,而不是通过非法手段获取到的或者仿制的。
3.乒乓球拍信息追溯功能
该功能可以帮助用户查询乒乓球拍从生产到个人购买的整个流程追溯信息,更好的了解乒乓球拍信息以及对应的价值。
4.乒乓球拍鉴伪功能
该功能可以帮助用户通过用户信息与乒乓球拍的信息确认乒乓球拍的真伪,防止上当受骗。
3.模块设计
根据XuperChain的结构设计与应用开发流程,乒乓球拍认证与追溯应用的模块设计如图32所示。该模块分为两部分:业务实现模块、开放网络模块。

业务实现模块可通过Web端、移动App端以及Restful API等方式实现,主要是实现用户端功能的可视化显示,即将用户操作的结果显示给用户。
使用XuperChain有两种形式:开放网络与自建链的形式:开放网络地址为:https://xuper.baidu.com/ 开放网络不需要用户手动创建链,使用成本较低,用户可以在开放网络编写、部署、调用智能合约等;自建链方式需要用户手动创建区块链网络,可搭建一个节点或者多个节点,此方式门槛较高。为了快速搭建本应用,选择了第一种方式,使用百度区块链提供的集成开放网络进行开发实现。
开放网络模块提供了区块链的底层实现(账本/区块)、运行环境(XVM虚拟机,智能合约运行的环境)。只需要按照业务逻辑设计编写对应的智能合约并将智能合约部署到开放网络中即可。
其中开放网络SDK也是XuperChain已经做好的统一接口API,该SDK是为编写智能合约时需要使用的SDK,在写智能合约时,会需要存储、更改或者查询一些数据,同时也会在合约运行时查询链相关信息。此SDK在用户编写智能合约时使用,在合约运行时,合约代码会调用此SDK。因此不管智能合约用C++、go、还是其他语言,最终都会统一成对应的SDK,这样在业务实现模块进行合约代码调用时,不再局限于对应的语言,也就是说C++底层实现智能合约可以使用go语言编写的合约调用代码进行调用。
4.数据结构设计
目前XuperChain的C++合约支持Key-Value存储和Table存储模型,简单起见,选用Key-Value形式存储数据。因此需要对应的数据组织形式方便存储与查询,根据之前需求分析的结果设计出如图33所示的数据结构。

四、底层设计与部署
超级链开放网络是基于百度完全自主研发的开源技术搭建的区块链基础服务网络,由分布在全国的超级联盟节点组成,符合中国标准,为用户提供区块链应用快速部署和运行的环境,以及计算和存储等资源的弹性付费能力,直接降低用户部署和运维成本,让信任链接更加便利。
为了使用开放网络环境提供的智能合约创建、部署等相关功能,根据官方相关文档提示,需要进行如下操作:(1)创建合约账户(2)创建智能合约(3)部署智能合约(4)调用智能合约。 其中前三个步骤是需要在开放网络上完成的,下面将详细介绍创建合约账户、创建智能合约、部署智能合约的过程。
1.开放网络操作指南
注册与认证的步骤如下:
第1步:登录超级链官网https://xuper.baidu.com/n/xuperChain,点击“工作台”,如图34所示。

第2步:第一次使用百度超级链会先让登录百度账户,输入百度账户,单击“登录”即可,如图35所示。

第3步:进入创建新的超级链账户界面,勾选“我已阅读并接受超级链平台《用户使用条款》”,并点击“立即创建”,如图36所示。

第4步:进入设置账户安全码页,安全码作为交易密码,请务必牢记。平台无法提供安全码找回功能。设置完成后,点击“下一步”,如图37所示。

第5步:进入记录超级链账户页,请务必按照页面指引,下载账户私密钥(private.key文件)、记下助记词,一旦遗失,会导致无法找回账户。平台无法提供找回私密钥、助记词功能。点击“进入工作台”,进入工作台页后,即注册平台账号成功!如图38所示。注意:不需要进行密钥托管。

第6步:进入工作台页面,如图39所示。

第7步:使用平台功能前,需完成实名认证。在工作台相关实名认证弹框内,点击“立即认证”,可跳到认证信息填写页,进入认证页面,按要求完成认证流程,认证立即生效,如图40所示。

下面开始创建新的合约账户。创建账户后,可以创建、安装、调用、查看当前账户下的智能合约。
第1步:在工作台,选择“开放网络”—> “合约管理”,点击“创建合约账户”,如图41所示。

第2步:进入创建合约账户页,如图42所示,输入安全码(注册与认证部分的安全码)后点击“确认创建”,系统自动生成账户名称后,即创建完毕,结果如图43所示。


第3步:如果创建过程中出现余额不足,可在工作台选择“开放网络”—>“概览”,点击“立即充值”,如图44所示,在充值页面充值2元即可,满足该应用的开发所需。

如图45所示为充值2元的界面。

下面开始讲述创建合约的过程。
第1步:在工作台,选择“开放网络”—>“合约管理”,点击“创建智能合约”,如图46所示。

第2步:进入创建合约页,按要求填写合约基本信息(合约名称:pingpongtrace)、选择合约模板,点击创建合约,如图47所示。即创建成功,结果如图48所示。注意:使用空白合约。


第3步:创建成功后可根据页面指引(如点击“立即编辑”),进入在线IDE进行合约开发。或在合约列表页点击编辑按钮。在IDE内,选择左侧合约文件main.cc,对合约进行编辑、编译、安装,如图49所示。

第4步:进入安装流程,用户需按合约代码完成预执行操作。点击“开始验证”,执行通过会进入安装确认页,如图50所示。

第5步:进入确认安装页,页面显示安装合约预计消耗的余额。点击“安装合约”将合约上链,上链过程需要等待20S左右,如图51所示。

第6步:返回首页时,可看到合约状态变更为“安装成功”,即该合约已完成安装,如图52所示。若未看到合约状态变更,请刷新当前页面。

2.智能合约设计
在创建与部署智能合约部分,涉及到了智能合约的编辑,如何设计乒乓球拍认证与追溯应用的智能合约,下面简单介绍一些接口设计与描述,如图53所示。

乒乓球拍认证与追溯应用的智能合约源码如下。注意:可直接复制粘贴到开放网络中进行编译、安装上链,以便调用。
#include <iomanip>
#include <sstream>
#include "xchain/xchain.h"
// 乒乓球拍认证与追溯应用上链API规范
// 参数由Context提供
class PingPongTraceBack{
public:
// 初始化写入权限
// 参数: owner - 具有写入权限的address
virtual void initialize() = 0;
// 用户信息添加
virtual void addUserInfo() = 0;
// 通过userid查询用户当前拥有的球拍
virtual void queryPingPongByUser() = 0;
// 用户——乒乓球拍信息绑定
virtual void bindUserAndPingPong() = 0;
// 乒乓球拍信息追溯
virtual void trackBackPingPong() = 0;
// 乒乓球拍鉴伪
virtual void forgeryPingPong() = 0;
// 查询具有写权限的账户
virtual void queryOwner() = 0;
};
struct PingPongTraceBackDemo : public PingPongTraceBack, public xchain::Contract {
private:
// 定义主键前缀
const std::string OWNER_KEY = "Owner";
const std::string USER = "U_";
const std::string PINGPONG = "P_";
const std::string USER_PINGPONG = "R1_";
const std::string PINGPONG_USER = "R2_";
// 检测调用者是否是合约的拥有者
bool isOwner(xchain::Context* ctx, const std::string& caller) {
std::string owner;
if (!ctx->get_object(OWNER_KEY, &owner)) {
return false;
}
return (owner == caller);
}
public:
void initialize() {
// 获取合约上下文对象
xchain::Context* ctx = this->context();
// 从合约上下文中获取合约参数, 由合约部署者指定具有写入权限的address
const std::string owner = ctx->arg("owner");
if (owner.empty()) {
ctx->error("missing owner address");
return;
}
// 将具有写入权限的owner地址记录在区块链账本中
ctx->put_object(OWNER_KEY, owner);
ctx->ok("success");
}
void addUserInfo() {
// 获取合约上下文对象
xchain::Context* ctx = this->context();
// 从参数中获取用户主键id,必填参数,没有则返回错误
const std::string& userid = ctx->arg("userid");
if (userid.empty()) {
ctx->error("missing 'userid'");
return;
}
// 从参数中获取用户名username,必填参数,没有则返回错误
const std::string& username = ctx->arg("username");
if (username.empty()) {
ctx->error("missing 'username'");
return;
}
std::string user_key = USER + userid;
std::string user_value = username;
// 保存用户信息失败直接返回
if (!ctx->put_object(user_key, user_value)) {
ctx->error("failed to save user info");
return;
}
std::string result = "{ userid: " + userid + ", username: " + username + " } save successfully!";
// 保存成功
ctx->ok(result);
}
void queryPingPongByUser() {
// 获取合约上下文对象
xchain::Context* ctx = this->context();
std::string result;
// 从参数中获取user主键id,必填参数,没有则返回错误
const std::string& userid = ctx->arg("userid");
if (userid.empty()) {
ctx->error("missing 'userid'");
return;
}
// 如果userid不存在返回错误
std::string user_key = USER + userid;
std::string user_value;
ctx->get_object(user_key, &user_value);
if (user_value.empty()) {
result = "user:" + userid + " is not existing!";
ctx->error(result);
return;
}
// 从账本中将所有的用户——球拍记录查询出来并判断当前球拍是否归当前用户所有
std::string r1_key = USER_PINGPONG + userid + "%";
std::unique_ptr<xchain::Iterator> iter =
ctx->new_iterator(r1_key, r1_key + "~");
std::string r1_data;
while (iter->next()) {
std::pair<std::string, std::string> res;
iter->get(&res);
if (res.first.length() > r1_key.length()) {
std::string content = res.second;
if (content == "1") {
std::string pingpongid = res.first.substr(r1_key.length(), res.first.length());
// std::string pingpongid = res.first;
r1_data += (pingpongid + " ");
}
}
}
result = "userid: " + userid + " pingpongid: " + r1_data;
// 执行成功,返回status code 200
ctx->ok(result);
}
void bindUserAndPingPong() {
// 获取合约上下文对象
xchain::Context* ctx = this->context();
std::string result;
// 从参数中获取user主键id,必填参数,没有则返回错误
const std::string& userid = ctx->arg("userid");
if (userid.empty()) {
ctx->error("missing 'userid'");
return;
}
// 从参数中获取pingpong主键id,必填参数,没有则返回错误
const std::string& pingpongid = ctx->arg("pingpongid");
if (pingpongid.empty()) {
ctx->error("missing 'pingpongid'");
return;
}
// 如果userid不存在返回错误
std::string user_key = USER + userid;
std::string user_value;
ctx->get_object(user_key, &user_value);
if (user_value.empty()) {
result = "user:" + userid + " is not existing!";
ctx->error(result);
return;
}
// 查询pingpongid目前的拥有者
std::string last_userid;
std::string pingpong_key = PINGPONG + pingpongid;
std::string r1_key = USER_PINGPONG + userid + "%" + pingpongid;
std::string r2_key = PINGPONG_USER + pingpongid + "_" + userid;
ctx->get_object(pingpong_key, &last_userid);
if (last_userid.empty()) {
// 如果查询结果为空,则说明乒乓球拍还未交易,不属于任何人,直接添加相关信息即可
// 1、添加乒乓球拍信息
if (!ctx->put_object(pingpong_key, userid)) {
return;
}
// 2、添加用户——球拍记录信息
if (!ctx->put_object(r1_key, "1")) {
return;
}
// 3、添加球拍——用户记录信息
if (!ctx->put_object(r2_key, "init")) {
return;
}
// 返回结果
result = userid + " produces " + pingpongid + " successfully!";
ctx->ok(result);
} else {
// 如果查询结果不为空,则说明乒乓球属于厂商或者用户,需要添加记录以及修改相关信息
// 1、修改当前的乒乓球拍拥有者信息
if (!ctx->put_object(pingpong_key, userid)) {
return;
}
// 2、将目前拥有者改为未拥有
std::string last_userid_key = USER_PINGPONG + last_userid + "_" + pingpongid;
if (!ctx->put_object(last_userid_key, "0")) {
return;
}
// 3、添加用户——球拍记录信息
if (!ctx->put_object(r1_key, "1")) {
return;
}
// 4、添加球拍——用户记录信息
if (!ctx->put_object(r2_key, last_userid)) {
return;
}
// 返回结果
result = userid + " buys " + pingpongid + " from " + last_userid + " successfully!";
ctx->ok(result);
}
}
void trackBackPingPong(){
// 获取合约上下文对象
xchain::Context* ctx = this->context();
std::string result = "\n";
// 从参数中获取pingpong主键id,必填参数,没有则返回错误
const std::string& pingpongid = ctx->arg("pingpongid");
if (pingpongid.empty()) {
ctx->error("missing 'pingpongid'");
return;
}
// 根据pingpongid查询球拍的信息
std::string pingpong_key = PINGPONG + pingpongid;
std::string pingpong_value;
ctx->get_object(pingpong_key, &pingpong_value);
if (pingpong_value.empty()) {
result = pingpongid + " is not existing!";
ctx->error(result);
return;
}
std::string cur_userid = pingpong_value;
std::string last_userid = pingpong_value;
std::string r2_key;
while (last_userid != "init") {
r2_key = PINGPONG_USER + pingpongid + "_" + cur_userid;
ctx->get_object(r2_key, &last_userid);
std::string res = cur_userid + " buys " + pingpongid + " from " + last_userid + "\n";
result += res;
cur_userid = last_userid;
}
ctx->ok(result);
}
void forgeryPingPong(){
// 获取合约上下文对象
xchain::Context* ctx = this->context();
std::string result;
// 从参数中获取user主键id,必填参数,没有则返回错误
const std::string& userid = ctx->arg("userid");
if (userid.empty()) {
ctx->error("missing 'userid'");
return;
}
// 从参数中获取pingpong主键id,必填参数,没有则返回错误
const std::string& pingpongid = ctx->arg("pingpongid");
if (pingpongid.empty()) {
ctx->error("missing 'pingpongid'");
return;
}
// 如果userid不存在返回错误
std::string user_key = USER + userid;
std::string user_value;
ctx->get_object(user_key, &user_value);
if (user_value.empty()) {
result = "user:" + userid + " is not existing!";
ctx->error(result);
return;
}
// 确认当前乒乓球拍的拥有者是否是userid
std::string pingpong_key = PINGPONG + pingpongid;
std::string pingpong_value;
ctx->get_object(pingpong_key, &pingpong_value);
// 没有该球拍记录,则是仿制的
if (pingpong_value.empty()) {
result = pingpongid + " is not existing and it is forged!";
ctx->error(result);
return;
}
// 球拍拥有者不是当前交易人,请谨慎交易
if (pingpong_value != userid) {
result = pingpongid + " does not belong to " + userid;
ctx->error(result);
return;
}
// 球拍是真的,且属于交易人
result = pingpongid + " is real and it belongs to" + userid;
ctx->ok(result);
}
void queryOwner() {
// 获取合约上下文对象
xchain::Context* ctx = this->context();
std::string owner;
if (!ctx->get_object(OWNER_KEY, &owner)) {
// 没查到owner信息,返回错误
ctx->error("get owner failed");
return;
}
// 执行成功,返回owner address
ctx->ok(owner);
}
};
DEFINE_METHOD(PingPongTraceBackDemo, initialize) { self.initialize(); }
DEFINE_METHOD(PingPongTraceBackDemo, addUserInfo) { self.addUserInfo(); }
DEFINE_METHOD(PingPongTraceBackDemo, queryPingPongByUser) { self.queryPingPongByUser(); }
DEFINE_METHOD(PingPongTraceBackDemo, bindUserAndPingPong) { self.bindUserAndPingPong(); }
DEFINE_METHOD(PingPongTraceBackDemo, trackBackPingPong) { self.trackBackPingPong(); }
DEFINE_METHOD(PingPongTraceBackDemo, forgeryPingPong) { self.forgeryPingPong(); }
DEFINE_METHOD(PingPongTraceBackDemo, queryOwner) { self.queryOwner(); }
五、顶层调用与实现
目前,百度区块链开放网络对外提供的SDK为Go SDK,需要采用Go SDK进行C++合约调用操作,即开放网络将编辑的C++智能合约编译运行上链后以Go SDK的形式提供给使用者调用。
因此需要配置Go SDK环境,同时也要配置相关的环境,如版本控制工具git、go语言运行环境、gcc编译工具、make工具等。
1.环境搭建
第1步:找到桌面的虚拟机图标,双击打开,点击“MyLinux”——>“开启虚拟机”打开之前安装的Linux系统,等待一会时间即可,如图54所示

第2步:输入登录密码,登录Linux系统,如图55所示。

第3步:在Linux系统主页,将鼠标定位到主页,并右击打开选择列表,点击“Open Terminal”,如图56所示,打开的Linux终端如图57所示。


第4步:输入以下命令安装gcc环境,其中#符号后面的是注释,解释说明用的,不用输入到终端,如图58所示。
# sudo是root命令,会有提示输入账户密码,直接输入按enter回车即可
# 更新apt版本
sudo apt update
# 安装gcc(中间会有选择,直接输入y按enter回车即可)
sudo apt install gcc

第5步:输入以下命令安装git工具,其中#符号后面的是注释,解释说明用的,不用输入到终端,如图59所示。
# sudo是root命令,会有提示输入账户密码,直接输入按enter回车即可
# 安装git(中间会有选择,直接输入y按enter回车即可)
sudo apt install git

第6步:为了安装Go语言环境,先将直接拖到虚拟机中,如图60所示。

第7步:通过以下命令将go安装包复制到当前目录下的go文件夹下,如图61所示。
# ls命令是查看当前目录的所有文件
ls
# mkdir 创建新目录
mkdir go
# 复制某个文件到某个目录下
cp ~/Desktop/go1.14.4.windows-amd64.msi ./go/
# cd命令是切换目录
cd go
# 查看go目录下的所有文件
ls

第8步:输入以下命令解压go安装包并添加PATH环境变量,结果如图62所示。
# ls查看当前目录下的所有文件
ls
# 解压go安装包到/usr/local目录下
sudo tar -C /usr/local -xzf go1.14.4.linux-amd64.tar.gz
# 将 /usr/local/go/bin 目录添加至 PATH 环境变量
export PATH=$PATH:/usr/local/go/bin
# 使用go命令查看go的版本
go version

第9步:输入以下命令安装make工具,结果如图63所示。
# 使用管理员命令安装make
sudo apt install make

下面讲述安装Go SDK的过程。
第1步:为了安装go语言环境,先将直接拖到虚拟机中,如图64所示。

第2步:输入以下命令复制Go SDK压缩包到当前目录XuperChain目录下并解压,结果如图65所示。
# 查看当前目录所有文件
ls
# 创建新的文件夹XuperChain
mkdir XuperChain
ls
# 切换到XuperChain目录
cd XuperChain/
# 复制xuper-sdk-go-1.0.0.zip文件到当前目录下
cp ~/Desktop/xuper-sdk-go-1.0.0.zip ./
ls
# 使用unzip命令解压xuper-sdk-go-1.0.0.zip压缩包
unzip xuper-sdk-go-1.0.0.zip

下面讲述配置开放网络环境。
第1步:在工作台界面下载私钥,如图66所示。如果之前已经下载好,忽略此步骤。

第2步:将私钥文件拖到虚拟机中,如图67所示。

第3步:使用以下命令将私钥文件复制到Go SDK目录的keys文件夹下,如图68所示。
# 在XuperChain目录下查看所有文件
ls
# 切换到 sdk xuper-sdk-go-1.0.0目录
cd xuper-sdk-go-1.0.0/
# 查看sdk目录下的所有文件
ls
# 创建新的目录keys
mkdir keys
# 切换到keys目录下
cd keys/
# 复制私钥文件private.key到keys文件夹下
cp ~/Desktop/private.key ./

第4步:使用以下命令编辑文件sdk.yaml并改写内容,然后Ctrl+s或者点击右上角的Save,最后点“x”关闭文件,完成初始化SDK配置网络开放环境,如图69所示。
# 查看sdk目录下的所有文件
ls
# 切换到conf目录
cd conf
# 查看conf目录下的所有文件
ls
# 修改文件sdk.yaml
sudo gedit sdk.yaml
# 要改写的内容
# endorseService Info
# testNet addrs
endorseServiceHost: "39.156.69.83:37100"
complianceCheck:
isNeedComplianceCheck: true
isNeedComplianceCheckFee: true
complianceCheckEndorseServiceFee: 400
complianceCheckEndorseServiceFeeAddr: aB2hpHnTBDxko3UoP2BpBZRujwhdcAFoT
complianceCheckEndorseServiceAddr: jknGxa6eyum1JrATWvSJKW3thJ9GKHA9n
minNewChainAmout: "100"
crypto: "xchain"

2.合约调用设计
完成智能合约上链与合约调用的环境搭建后,如何设计业务应用层的调用逻辑,下面简单介绍一些方法设计与描述,如图70所示。

利用go语言编写的合约调用方法接口文件sample.go实现代码如下:
package main
// 导入依赖包
import (
"fmt"
"os"
"github.com/xuperchain/xuper-sdk-go/account"
"github.com/xuperchain/xuper-sdk-go/transfer"
"github.com/xuperchain/xuper-sdk-go/contract"
)
// 定义区块链节点与名字
var (
node = "39.156.69.83:37100"
bcname = "xuper"
)
// 根据私钥文件与账户密码,获取当前账户
func getAccount() (*account.Account, error) {
// 第二个参数换成个人在工作台中设置的6位密码
acc, err := account.GetAccountFromFile("keys/", "xxxxxx")
if err != nil {
fmt.Errorf("GetAccountFromFile error: %v\n", err)
}
fmt.Println("address=", acc.Address)
return acc, nil
}
// 获取当前账户的余额
func getBalance(acc *account.Account) {
// 初始化客户端操作事务
trans := transfer.InitTrans(acc, node, bcname)
// 得到当前账户的余额
balance, err := trans.GetBalance()
fmt.Printf("balance %v, err %v\n", balance, err)
return
}
// 添加用户信息(生产商、代理商、购买者1、购买者2)
func addUserInfo(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "pingpongtrace"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 添加生产商信息
methodName := "addUserInfo"
args := map[string]string{
"userid": "producer01",
"username": "producer",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("producer txid: %v\n\n", txid)
// 添加代理商信息
methodName = "addUserInfo"
args = map[string]string{
"userid": "agent001",
"username": "agent",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("agent txid: %v\n\n", txid)
// 添加购买者1信息
methodName = "addUserInfo"
// 设置生产商调用参数
args = map[string]string{
"userid": "buyer1001",
"username": "buyer1",
}
// 调用合约
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("buyer1 txid: %v\n\n", txid)
// 添加购买者2信息
methodName = "addUserInfo"
args = map[string]string{
"userid": "buyer1002",
"username": "buyer2",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("buyer2 txid: %v\n\n", txid)
}
/*
描述:模拟球拍从生产到交易到购买者buyer2手中的过程
主要过程:
1、生产商生产乒乓球拍(数量3个,编号分别是:pingpong101、pingpong102、pingpong103),绑定生产商与球拍信息
2、查询生产商拥有的球拍信息、球拍的追溯信息
3、代理上从生产商购买三个球拍,绑定代理商与球拍信息
4、查询代理商拥有的球拍信息、球拍的追溯信息
5、购买者1从代理商购买pingpong101球拍,绑定购买者1与pingpong101球拍信息
6、查询购买者1拥有的球拍信息,pingpong101球拍的追溯信息
7、购买者2从购买者1购买pingpong101,绑定购买者2与1000球拍信息
8、查询购买者2拥有的球拍信息,pingpong101球拍的追溯信息
9、对球拍pingpong101(真)、pingpong104(假)进行鉴伪
*/
// 1、生产商生产乒乓球拍,绑定生产商与球拍信息
func bindProducerAndPingPong(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 绑定生产商与球拍pingpong101信息
methodName := "bindUserAndPingPong"
args := map[string]string{
"userid": "producer01",
"pingpongid": "pingpong101",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong101(producer) txid: %v\n\n", txid)
// 绑定生产商与球拍pingpong102信息
methodName = "bindUserAndPingPong"
args = map[string]string{
"userid": "producer01",
"pingpongid": "pingpong102",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong102(producer) txid: %v\n\n", txid)
// 绑定生产商与球拍pingpong103信息
methodName = "bindUserAndPingPong"
args = map[string]string{
"userid": "producer01",
"pingpongid": "pingpong103",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong103(producer) txid: %v\n\n", txid)
}
// 2、查询生产商拥有的球拍信息、球拍的追溯信息
func queryPingPongInfoByProducer(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 查询生产商拥有的球拍信息
methodName := "queryPingPongByUser"
args := map[string]string{
"userid": "producer01",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("producer:producer01 txid: %v\n\n", txid)
// 追溯pingpong101球拍信息
methodName = "trackBackPingPong"
args = map[string]string{
"pingpongid": "pingpong101",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("trackBackPingPong:pingpong101(producer) txid: %v\n\n", txid)
}
// 3、代理上从生产商购买三个球拍,绑定代理商与球拍信息
func bindAgentAndPingPong(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 绑定代理商与球拍pingpong101信息
methodName := "bindUserAndPingPong"
args := map[string]string{
"userid": "agent001",
"pingpongid": "pingpong101",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong101(agent) txid: %v\n\n", txid)
// 绑定生产商与球拍pingpong102信息
methodName = "bindUserAndPingPong"
args = map[string]string{
"userid": "agent001",
"pingpongid": "pingpong102",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong102(agent) txid: %v\n\n", txid)
// 绑定生产商与球拍pingpong103信息
methodName = "bindUserAndPingPong"
args = map[string]string{
"userid": "agent001",
"pingpongid": "pingpong103",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong103(agent) txid: %v\n\n", txid)
}
// 4、查询代理商拥有的球拍信息、球拍的追溯信息
func queryPingPongInfoByAgent(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 查询生产商拥有的球拍信息
methodName := "queryPingPongByUser"
args := map[string]string{
"userid": "agent001",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("agent:agent001 txid: %v\n\n", txid)
// 追溯pingpong101球拍信息
methodName = "trackBackPingPong"
args = map[string]string{
"pingpongid": "pingpong101",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("trackBackPingPong:pingpong101(agent) txid: %v\n\n", txid)
}
// 5、购买者1从代理商购买pingpong101球拍,绑定购买者1与pingpong101球拍信息
func bindBuyer1AndPingPong(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 绑定代理商与球拍pingpong101信息
methodName := "bindUserAndPingPong"
args := map[string]string{
"userid": "buyer1001",
"pingpongid": "pingpong101",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong101(buyer1) txid: %v\n\n", txid)
}
// 6、查询购买者1拥有的球拍信息,pingpong101球拍的追溯信息
func queryPingPongInfoByBuyer1(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 查询购买者1拥有的球拍信息
methodName := "queryPingPongByUser"
args := map[string]string{
"userid": "buyer1001",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("buyer1:buyer1001 txid: %v\n\n", txid)
// 追溯pingpong101球拍信息
methodName = "trackBackPingPong"
args = map[string]string{
"pingpongid": "pingpong101",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("trackBackPingPong:pingpong101(buyer1) txid: %v\n\n", txid)
}
// 7、购买者2从购买者1购买pingpong101,绑定购买者2与1000球拍信息
func bindBuyer2AndPingPong(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 绑定代理商与球拍pingpong101信息
methodName := "bindUserAndPingPong"
args:=map[string]string{
"userid": "buyer1002",
"pingpongid": "pingpong101",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("pingpongid:pingpong101(buyer2) txid: %v\n\n", txid)
}
// 8、查询购买者2拥有的球拍信息,pingpong101球拍的追溯信息
func queryPingPongInfoByBuyer2(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 查询购买者1拥有的球拍信息
methodName := "queryPingPongByUser"
args := map[string]string{
"userid": "buyer1002",
}
txid, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("buyer2:buyer1002 txid: %v\n\n", txid)
// 追溯pingpong101球拍信息
methodName = "trackBackPingPong"
args = map[string]string{
"pingpongid": "pingpong101",
}
txid, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("trackBackPingPong:pingpong101(buyer2) txid: %v\n\n", txid)
}
// 9、对球拍pingpong101(真)、pingpong104(假)进行鉴伪
func forgeryPingPong(acc *account.Account){
// 合约账户(设置成个人的合约账户名称)
contractAccount := "XC7015555951801205@xuper"
// 合约名字
contractName := "demo4"
// 初始化 wasm 合约
wasmContract := contract.InitWasmContract(acc, node, bcname, contractName, contractAccount)
// 设置调用函数、参数、调用合约
// 对球拍pingpong101(真)进行鉴伪
methodName := "forgeryPingPong"
args := map[string]string{
"userid": "buyer1002",
"pingpongid": "pingpong101",
}
_, err := wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
os.Exit(-1)
}
fmt.Printf("\n\n")
// 对球拍pingpong104(假)进行鉴伪
methodName = "forgeryPingPong"
args = map[string]string{
"userid": "buyer1002",
"pingpongid": "pingpong104",
}
_, err = wasmContract.InvokeWasmContract(methodName, args)
if err != nil {
fmt.Printf("InvokeWasmContract err: %v\n\n", err)
fmt.Println("=========forgeryPingPong completely!=========\n")
os.Exit(-1)
}
fmt.Printf("\n\n")
}
// 最终调用函数(可能是组合几个函数一起的),将相关流程组合起来(统一用户的信息绑定与查询)
// 1、调用函数:添加用户信息
func addUserInfoFunc(acc *account.Account){
fmt.Println("=========start add user info: producer(producer01)、agent(agent001)、buyer1(buyer1001)、buyer2(buyer1002)=========\n")
addUserInfo(acc)
fmt.Println("=========add user info completely!=========\n")
}
// 2、调用函数:生产商相关
func producerFunc(acc *account.Account){
fmt.Println("=========producer(id:producer01) starts producing three pingpong: pingpong101、pingpong102、pingpong103=========\n")
bindProducerAndPingPong(acc)
fmt.Println("=========producer(id:producer01) produces pingpong completely!=========\n")
fmt.Println("=========query infos related to producer(id:producer01)=========\n")
queryPingPongInfoByProducer(acc)
fmt.Println("=========query completely!=========\n")
}
// 3、调用函数:代理商相关
func agentFunc(acc *account.Account){
fmt.Println("=========agent(id:agent001) buys pingpong(id:pingpong101) from producer(id:producer01)=========\n")
bindAgentAndPingPong(acc)
fmt.Println("=========agent(id:agent001) buys pingpong completely!=========\n")
fmt.Println("=========query infos related to agent(id:agent001)=========\n")
queryPingPongInfoByAgent(acc)
fmt.Println("=========query completely!=========\n")
}
// 4、调用函数:购买者1相关
func buyer1Func(acc *account.Account){
fmt.Println("=========buyer1(id:buyer1001) buys pingpong(id:pingpong101) from agent(id:agent001)=========\n")
bindBuyer1AndPingPong(acc)
fmt.Println("=========buyer1(id:buyer1001) buys pingpong completely!=========\n")
fmt.Println("=========query infos related to buyer1(id:buyer1001)=========\n")
queryPingPongInfoByBuyer1(acc)
fmt.Println("=========query completely!=========\n")
}
// 5、调用函数:购买者2相关
func buyer2Func(acc *account.Account){
fmt.Println("=========buyer2(id:buyer1002) buys pingpong(id:pingpong101) from buyer1(id:buyer1001)=========\n")
bindBuyer2AndPingPong(acc)
fmt.Println("=========buyer2(id:buyer1002) produces pingpong completely!=========\n")
fmt.Println("=========query infos related to buyer2(id:buyer1002)=========\n")
queryPingPongInfoByBuyer2(acc)
fmt.Println("=========query completely!=========\n")
}
// 6、调用函数:对球拍pingpong101(真)、pingpong104(假)进行鉴伪
func forgeryPingPongFunc(acc *account.Account){
fmt.Println("=========forgeryPingPong: pingpong(pingpong101 is real) and pingpong(pingpong104 is not real)=========\n")
forgeryPingPong(acc)
fmt.Println("=========forgeryPingPong completely!=========\n")
}
// 主调用函数
func main() {
acc, err := getAccount()
if err != nil {
os.Exit(-1)
}
// 1、获取当前账户余额
getBalance(acc)
// 2、以下过程每次只能执行一个并且按顺序执行,执行其中一个的时候,其他函数全部注释,即在函数前面加 // 即可
// 顺序执行:addUserInfoFunc()、producerFunc()、agentFunc()、buyer1Func()、buyer2Func()
addUserInfoFunc(acc)
//producerFunc(acc)
//agentFunc(acc)
//buyer1Func(acc)
//buyer2Func(acc)
//forgeryPingPongFunc(acc)
return
}
3.合约调用过程实例
1、演示描述与过程
描述:模拟球拍从生产到交易到购买者buyer2手中的过程
主要过程:
(1)添加用户信息(生产商、代理商、购买者1、购买者2)。
(2)生产商生产乒乓球拍(数量3个,编号分别是:pingpong101、pingpong102、pingpong103),绑定生产商与球拍信息;查询生产商拥有的球拍信息、球拍的追溯信息。
(3)代理商从生产商购买三个球拍,绑定代理商与球拍信息;查询代理商拥有的球拍信息、球拍的追溯信息。
(4)购买者1从代理商购买pingpong101球拍,绑定购买者1与pingpong101球拍信息;查询购买者1拥有的球拍信息,pingpong101球拍的追溯信息。
(5)购买者2从购买者1购买pingpong101,绑定购买者2与1000球拍信息;查询购买者2拥有的球拍信息,pingpong101球拍的追溯信息。
(6)对球拍pingpong101(真)、pingpong104(假)进行鉴伪。
2、操作步骤
(1)预处理:修改代码部分内容,将合约账户设置成个人的合约账户名称、替换个人6位安全码、如果合约名字不是pingpongtrace,也要替换;注意以上替换需要全部替换,不仅仅是图71中所提示的地方(提示:可以将代码复制到记事本,然后利用里面的替换操作进行全部替换,避免一个一个复制粘贴带来的遗漏)。

(2)将sample.go文件拖到虚拟机中,并根据以下命令复制到Go SDK目录的example文件下,如图72所示。
#切换到Go SDK目录下,如果已经在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
#复制sample.go文件到example文件下
cp ~/Desktop/sample.go ./example/

(3)添加用户操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图73所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(4)添加用户操作:执行以下命令编译sample.go文件并执行,如图74所示,执行结果如图75所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample


(5)添加用户操作:根据步骤(4)执行结果的txid(交易id)在工作台的区块链浏览器进行查询,如图76所示,查询结果如图77所示。之后的每个操作都会伴随一个或者多个交易,有兴趣可以自己都查询一下,之后不再重复演示。


(6)生产商生成球拍操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图78所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(7)生产商生成球拍操作:执行以下命令编译sample.go文件并执行,执行结果如图79所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample

(8)代理商从生产商购买球拍等操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图80所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(9)代理商从生产商购买球拍等操作:执行以下命令编译sample.go文件并执行,执行结果如图81所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample

(10)购买者1从代理商购买球拍等操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图82所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(11)购买者1从代理商购买球拍等操作:执行以下命令编译sample.go文件并执行,执行结果如图83所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample

(12)购买者2从购买者1购买球拍等操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图84所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(13)生产商生成球拍操作:执行以下命令编译sample.go文件并执行,执行结果如图85所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample

(14)球拍鉴伪等操作:通过以下命令打开example目录下的sample.go文件,改写文件最下方的main函数内容如图86所示,完成后保存关闭即可。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 打开sample.go文件
Gedit ./example/sample.go

(15)生产商生成球拍操作:执行以下命令编译sample.go文件并执行,执行结果如图87所示。
# 切换到Go SDK目录下,如果已在Go SDK目录则不用切换
cd XuperChain/xuper-sdk-go-1.0.0/
# 编译sample.go文件
make
# 编译成功后会在当前目录生成一个sample可执行文件,直接执行即可
./sample

总结
基于百度超级链XuperChain完成了底层合约的设计、开发、编译、部署、测试等过程,对其所实现的模块功能如增加记录、查询记录等进行验证,基本完成相应的功能要求,不过相应模块设计以及实现比较粗糙,可以根据自己的需求或者想法进一步完善,如提供客户端支持、Web端支持。
通过以上流程基本上可以快速完成一个区块链应用搭建,可以帮助更好的理解区块链技术、知识等。区块链技术不仅仅可以作为支持数字货币交易的底层技术,还能脱离数字货币,应用于金融、贸易、征信、物联网、共享经济等诸多领域。区块链凭借其安全性,可以帮助私人公司或者政府部门建立更加值得信赖的网络,可以让用户更加放心分享信息和价值。目前,区块链的应用已延伸到物联网、智能制造、供应 链管理、数字资产交易等多个领域。
由于区块链的特点:1.去中心化 2.开放性 3.不可篡改性 4.匿名性 5.可追溯性,很适合乒乓球拍认证与追溯应用的设计与实现。 百度超级链XuperChain开源技术是百度自主研发创新的产物,拥有链内并 行技术、可插拔共识机制、一体化智能合约等业内领先技术支撑,让区块链应用 搭建更灵活、性能更高效、安全性更强,全面赋能区块链开发者。建议自己动手实现一个其他相关的应用,更加百度超级链XuperChain开源技术的熟悉程度。
本文档内容较多,如有什么不足或不对的地方,欢迎读者批评指正并提出改善意见!