基于百度超级链的商品溯源防伪系统开发
摘要
现如今由于假冒伪劣商品层出不穷,使得品牌方的品牌形象容易遭受负面影响,损害品牌方以及消费者的正当利益。为了维护品牌形象以及帮助消费者鉴别产品真伪,品牌方需要更好的溯源防伪方案。本文设计了一种基于百度超级链的商品溯源防伪方案。利用区块链不可篡改、数据可完整追溯以及时间戳功能,可有效解决物品的溯源防伪问题。
作业概述
背景分析
商品溯源
现如今由于假冒伪劣商品层出不穷,使得品牌方的品牌形象容易遭受负面影响,损害品牌方以及消费者的正当利益。为了维护品牌形象以及帮助消费者鉴别产品真伪,品牌方需要更好的溯源防伪方案。
产业链溯源通过记录商品在流通过程的状态信息链,提高消费者对产品质量的信任度,提高伪造商品的难度,约束流通过程所涉及到的第三方,从而提高生产销售伪劣产品的难度和成本,保护正品在市场上的正常流通以及消费者和商家的合法权益。
但传统的溯源方案存在溯源信息可能被篡改的风险,用户对这种溯源方案的信任度不高。
引入区块链技术
区块链技术的分布式账本逻辑配合全电子化的信息采集过程可以保证溯源信息的时效性和不可篡改性。通过引入区块链技术可以进一步提高防伪溯源系统的可靠性。
缺陷
但是,即使是加入了应用了区块链技术的防伪溯源方案,仍然无法解决溯源中一些尴尬的痛点问题,例如,如果对数据进行造假,并将数据记录区块链中,区块链并无法保证数据本身的真实性,只能保证数据不可篡改。因此本人认为。防伪溯源使用公链意义不大,因为用户无法检验数据本身的真实性,无论是使用公链还是联盟链。防伪溯源引入区块链,关键还是在于可以使商家通过区块链的技术去约束相关的第三方,使得品牌方能够更加准确的把控商品的各个环节中的状态,以此来提高产品的质量。如果消费者不信任品牌方维护自身产品,打击伪劣产品的动力,那么无论品牌方付出多少努力都是无济于事的。
百度超级链
当前,国内外的区块链系统多如过江之卿。对于区块链开发者而言,如何选择一个区块链系统持续地深耕是一个难题。百度超级链目前已经通过工信部电子一所的标准测试,在功能、安全、性能三方面都远超过同类产品。超级链也建立了活跃的开发者微信群,越来越多的开发者正在学习超级链,用超级链落地自己的项目。
相关工作
本文研究了百度超级链的工具集如何使用,并基于百度超级链编写了智能合约,以及编写了用于各方企业进行使用的网站,形成了一套商品防伪溯源方案。
作业设计与实现
百度超级链的工具集
百度超级链的工具集开发不完善,例如一个极其重要的功能缺失:提供用户查询交易相关的合约及其合约代码的API没有开发完成。笔者为此花费了许多时间查阅其相关文档,阅读其代码中的API文件,试用了其区块链浏览器xuperscan都没有得到答案,最后在百度开发者中心进行提问得到了答案——无此功能!这将直接导致用户无法验证所有链上交易是否与开发者白皮书所声明的那样去运作,区块链的最为核心的功能实际上竟然仍未实现。
百度超级链XuperChain环境搭建
详见官方文档
-
安装go语言编译环境,推荐使用的版本为1.14或1.15;
-
安装git;
-
使用git下载源码到本地;
-
执行make命令编译,可能出现拉取失败的情况,可以配置GOPROXY解决此问题;
-
在output目录得到bin,conf, data 三个文件夹以及一个 control.sh 脚本
-
使用controll.sh即可启动单节点 single 共识的链。
命令行工具的基本使用
-
创建address(后文记作user2), 生成的address,公钥,私钥在–output 指定位置; 此处可以看出命令行工具的功能缺失:没有助记词,无法设置安全码
1
bin/xchain-cli account newkeys --output ~/pingpong/keys/user2
-
查看user2的address:
1
cat ~/pingpong/keys/user2/address
得到
dqWwvzspTRwM8d6kycJzjxSZbVyteTFdN
-
为user2创建合约账号:
-
编写desc配置文件:
1 2 3 4 5 6 7 8 9
{ "module_name": "xkernel", "method_name": "NewAccount", "contract_name": "$acl", "args" : { "account_name": "1234123400002000", "acl": "{\"pm\": {\"rule\": 1,\"acceptValue\": 1},\"aksWeight\": {\"dqWwvzspTRwM8d6kycJzjxSZbVyteTFdN\": 1}}" } }
-
执行命令
1
bin/xchain-cli account new --desc ~/pingpong/data/new.json --fee 2000
注意此处如果不使用desc参数是无法创建address的合约账号的,创建的是默认地址的合约账号,不过后面有另一种更直接的方案。
-
- 为user2转账:
1
bin/xchain-cli transfer --to dqWwvzspTRwM8d6kycJzjxSZbVyteTFdN --amount 10000000000
- 为合约账号转账:
1
bin/xchain-cli transfer --to XC1234123400002000@xuper --amount 1000000000
- 转账后可以使用user2的秘钥创建合约账号
1
bin/xchain-cli account new --account 1234123400002001 --keys ~/pingpong/keys/user2 --fee 2000
GO SDK使用
由于GO SDK的部分操作相比xchain-cli更为方便,以下列出常用的方法。
-
环境配置参考官方文档
- 连接节点:
1
xchainClient, err := xuper.New(node)
- 创建账户:
1 2 3 4 5 6
//- `strength`:1弱(12个助记词),2中(18个助记词),3强(24个助记词)。 //- `language`:1中文,2英文。 acc, err := account.CreateAccount(2, 1) // 创建账户并存储到文件中 acc, err = account.CreateAndSaveAccountToFile("./keys", "123", 1, 1)
-
获取账户:
- 通过助记词:
1
acc, err := account.RetrieveAccount(mnemonic, 1)
- 通过私钥和安全码:
1 2
// key_path必须以/结尾,此函数不会自己加上/ acc, err := account.GetAccountFromFile(key_path, key_password)
-
通过命令行工具生成的私钥:
1
acc, err := account.GetAccountFromPlainFile(key_path)
- 通过助记词:
- 查询账户余额
1
bal, _ := xuperClient.QueryBalance(acc.Address)
- 查询合约账户余额
1
fmt.Println(xclient.QueryBalance(contractAccount)
-
设置合约账户
1
acc.SetContractAccount(contractAccount)
- 设置合约账户ACL
1 2 3 4
acc1.SetContractAccount(contractAccount) acl := xuper.NewACL(1, 1) acl.AddAK(acc1.Address, 1) tx, err := xuperClient.SetAccountACL(acc1, acl)
- 设置合约方法ACL
1
tx, err := xuperClient.SetMethodACL(acc1, contractName, "authorize", acl)
- 部署合约
1 2 3 4
codePath := "data/output.wasm" code, err := ioutil.ReadFile(codePath) acc.SetContractAccount(contractAccount) tx, err := xuperClient.DeployWasmContract(acc, contractName, code, args)
- 调用合约方法
1 2
tx, err := xuperClient.InvokeWasmContract(acc, contractName, "authorize", args) tx, err = xuperClient.QueryWasmContract(acc, contractName, "query_records", args)
开放网络
超级链开放网络是基于百度完全自主研发的开源技术搭建的区块链基础服务网络,由分布在全国的超级联盟节点组成,符合中国标准,为用户提供区块链应用快速部署和运行的环境,以及计算和存储等资源的弹性付费能力,直接降低用户部署和运维成本,让信任链接更加便利。
配置XuperIDE
- 配置并启动Docker Desktop,下载所需镜像;
- 设置节点为开放网络;
- 导入address,只支持助记词导入
参考百度超级链合约模板
以百度超级链提供的溯源模板为基础进行修改,修改了模板的几处低级错误。其中最离谱的错误如下,查询商品id记录超过指定id:添加了商品100000000和10000,查询商品10000,返回的数据会包含100000000的记录。
关键在于此句,查询区间的起始点应为goodsRecordsKey + "_"
合约设计
主要功能:
- 权限管理:
- 通过ACL设置authorize,create_good的权限;
- authorize:通过此方法注册用户,记录用户信息,只有已注册用户才能调用authorize,create_good,update_good;
- initialize:初始化合约需要root参数,由合约账户调用authorize对root进行授权;
- user_record:查询账户信息,查看账户是否注册,查看描述信息,查看授权者;
- 商品管理:
- create_good:生产商品;
- update_good:更新商品信息;
- good_records:查询商品溯源信息。
合约代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#include "xchain/xchain.h"
#include <cctype>
// 商品溯源合约模板
// 参数由xchain::Contract中的context提供
class SourceTrace {
public:
/*
* func: 初始化商品溯源合约
* @param: root: 初始用户
*/
virtual void initialize() = 0;
/*
* func: 授权用户更新权限
* @param: address: 地址
* @param: desc: 账户描述
*/
virtual void authorize() = 0;
/*
* func: 创建一个新的商品
* @param: id: 商品id
* @param: desc: 商品描述
*/
virtual void create_good() = 0;
/*
* func: 更新商品信息
* 说明: 合约发起者需经过authorize授权才允许更新信息
* @param: id: 商品id
* @param: info: 信息
*/
virtual void update_good() = 0;
/*
* func: 查询商品变更记录
* @param: id: 商品id
*/
virtual void good_records() = 0;
/*
* func: 查询商品变更记录
* @param: address: 地址
*/
virtual void user_record() = 0;
};
struct SourceTraceDemo : public SourceTrace, public xchain::Contract {
const std::string USER = "USER_";
const std::string AUTHRECORD = "AUTHRECORD_";
const std::string GOOD = "GOOD_";
const std::string GOODRECORD = "GOODRECORD_";
const std::string GOODRECORDTOP = "GOODRECORDTOP_";
const std::string CREATE = "CREATE: ";
bool _exist(const std::string& key) {
auto* ctx = context();
std::string value;
return ctx->get_object(key, &value);
}
bool get(const std::string& key, std::string& value) {
auto* ctx = context();
if (!ctx->get_object(key, &value)) {
ctx->error(key + " does not exist.");
return false;
}
return true;
}
bool get_arg(const std::string& key, std::string& value) {
auto* ctx = context();
value = ctx->arg(key);
if (value.empty()) {
ctx->error("missing argument " + key);
return false;
}
return true;
}
bool get_caller(std::string& caller) {
auto* ctx = context();
caller = ctx->initiator();
if (caller.empty()) {
ctx->error("missing initiator");
return false;
}
return true;
}
bool authorize(const std::string& caller, const std::string& address, const std::string& desc) {
auto* ctx = context();
const std::string& userkey = USER + address;
if (_exist(userkey)) {
ctx->error("user already exists.");
return false;
} else {
ctx->put_object(userkey, desc);
ctx->put_object(AUTHRECORD + address, caller);
return true;
}
}
void initialize() {
auto* ctx = context();
std::string caller;
if (!get_caller(caller)) return;
std::string address;
if (!get_arg("root", address)) return;
if (!authorize(caller, address, "root")) return;
ctx->ok("initialize success, root=" + address);
}
void authorize() {
auto* ctx = context();
std::string caller;
if (!get_caller(caller)) return;
std::string user;
if (!get(USER + caller, user)) return;
std::string desc;
if (!get_arg("desc", desc)) return;
std::string address;
if (!get_arg("address", address)) return;
if (!authorize(caller, address, desc)) return;
ctx->ok(address + " authorized, desc=" + desc);
}
void create_good() {
auto* ctx = context();
std::string caller;
if (!get_caller(caller)) return;
std::string user;
if (!get(USER + caller, user)) return;
std::string id;
if (!get_arg("id", id)) return;
std::string desc;
if (!get_arg("desc", desc)) return;
std::string goodKey = GOOD + id;
if (_exist(goodKey)) {
ctx->error("the id is already exist, please check again");
return;
}
ctx->put_object(goodKey, desc);
std::string goodRecordKey = GOODRECORD + id + "_0";
std::string goodRecordTopKey = GOODRECORDTOP + id;
std::string record = "info=create " + goodKey + "; desc=" + desc + "; address=" + caller;
ctx->put_object(goodRecordKey, record);
ctx->put_object(goodRecordTopKey, "0");
ctx->ok(id);
}
void update_good() {
auto* ctx = context();
std::string caller;
if (!get_caller(caller)) return;
std::string user;
if (!get(USER + caller, user)) return;
std::string id;
if (!get_arg("id", id)) return;
std::string info;
if (!get_arg("info", info)) return;
std::string record = "info=" + info + "; address=" + caller;
std::string goodRecordTopKey = GOODRECORDTOP + id;
std::string topStr;
if (!get(goodRecordTopKey, topStr)) return;
int top = std::stoi(topStr) + 1;
topStr = std::to_string(top);
std::string goodRecordKey = GOODRECORD + id + "_" + topStr;
ctx->put_object(goodRecordKey, record);
ctx->put_object(goodRecordTopKey, topStr);
ctx->ok(topStr);
}
void good_records() {
auto* ctx = context();
std::string id;
if (!get_arg("id", id)) return;
std::string goodKey = GOOD + id;
std::string value;
if (!get(goodKey, value)) return;
std::string goodRecordTopKey = GOODRECORDTOP + id;
std::string topStr;
if (!get(goodRecordTopKey, topStr)) return;
int top = std::stoi(topStr);
std::string result = goodKey + "\n";
for (int i=0; i<=top; ++i) {
std::string iStr = std::to_string(i);
std::string goodRecordKey = GOODRECORD + id + "_" + iStr;
std::string record;
if (!get(goodRecordKey, record)) return;
result += "record#" + iStr + ": " + record + "\n";
}
ctx->ok(result);
}
void user_record() {
auto* ctx = context();
std::string addr;
if (!get_arg("address", addr)) return;
std::string user;
if (!get(USER + addr, user)) return;
std::string record;
if (!get(AUTHRECORD + addr, record)) return;
ctx->ok("address: " + addr + "\ndescription: " + user + "\nauthorized by: " + record);
}
};
DEFINE_METHOD(SourceTraceDemo, initialize) {
self.initialize();
}
DEFINE_METHOD(SourceTraceDemo, authorize) {
self.authorize();
}
DEFINE_METHOD(SourceTraceDemo, create_good) {
self.create_good();
}
DEFINE_METHOD(SourceTraceDemo, update_good) {
self.update_good();
}
DEFINE_METHOD(SourceTraceDemo, good_records) {
self.good_records();
}
DEFINE_METHOD(SourceTraceDemo, user_record) {
self.user_record();
}
部署合约
此处XuperIDE同样存在令人困惑的问题:合约名称只能使用小写字母
开放网络工作台无法查看代码,由此可以看出查看链上合约的功能缺失
为了防止使用开放平台出现更多的问题,后文测试在单节点 single共识的链上进行。
Open Containing Folder可以找到编译生成的.wasm文件,使用GO SDK部署到链上。
使用GO SDK编写简易的网站
由于界面与本文主题无关,因此前端仅实现最为简单的表单提交,返回字符串表示结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
var (
// mnemonic = "担 呼 彩 鸣 卿 质 鸟 蒸 扣 嘴 奔 规"
key_path = "./keys/user1/"
key_password = "246897"
// node = "39.156.69.83:37100"
node = "localhost:37101"
bcname = "xuper"
contractAccount = "XC2250833561066804@xuper"
// contractAccount = "XC1234123412340002@xuper"
// contractName = "pingpong_check"
contractName = "trace_v1.153"
)
func main() {
// testAccount()
// testContractAccount()
// deploy()
// setACL()
run_router()
}
使用gin模块配置网站
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//router.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/xuperchain/xuper-sdk-go/v2/account"
"github.com/xuperchain/xuper-sdk-go/v2/xuper"
)
func getContractMethod(
r *gin.Engine, path string,
methodName string, args []string,
auth bool) {
xClient, err := xuper.New(node)
if err != nil {
panic(err)
}
if auth {
args = append(args, "mnemonic")
}
r.GET(path, func(c *gin.Context) {
c.HTML(http.StatusOK, "form.tmpl", gin.H{
"title": methodName,
"args": args,
})
})
r.POST(path, func(c *gin.Context) {
kwargs := map[string]string{}
for _, arg := range args {
kwargs[arg] = c.PostForm(arg)
}
acc := getAccount()
if auth {
mnemonic := kwargs["mnemonic"]
acc, err = account.RetrieveAccount(mnemonic, 1)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
}
tx, err := xClient.InvokeWasmContract(acc, contractName, methodName, kwargs)
status := http.StatusOK
statusStr := "Success"
res := ""
if err != nil {
status = http.StatusBadRequest
statusStr = "Failed"
res = err.Error()
} else {
res = string(tx.ContractResponse.Body)
}
c.String(status,
"Invoke %s.%s %s! Response:%s\n",
contractName, methodName, statusStr, res)
})
}
func run_router() {
r := gin.Default()
r.LoadHTMLGlob("templates/*")
getContractMethod(r, "/user/authorize", "authorize", []string{"address", "desc"}, true)
getContractMethod(r, "/good/create", "create_good", []string{"id", "desc"}, true)
getContractMethod(r, "/good/update", "update_good", []string{"id", "info"}, true)
getContractMethod(r, "/good/records", "good_records", []string{"id"}, false)
getContractMethod(r, "/user/record", "user_record", []string{"address"}, false)
r.Run() // listen and serve on 0.0.0.0:8080
}
作业测试与分析
部署合约
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func deploy() {
acc := getAccount()
xuperClient, err := xuper.New(node)
if err != nil {
panic("new xuper Client error:")
}
codePath := "data/output.wasm"
code, err := ioutil.ReadFile(codePath)
if err != nil {
panic(err)
}
fmt.Println(contractName)
args := map[string]string{
"root": acc.Address,
}
acc.SetContractAccount(contractAccount)
tx, err := xuperClient.DeployWasmContract(acc, contractName, code, args)
if err != nil {
panic(err)
}
fmt.Printf("Deploy wasm Success!TxID:%x\n", tx.Tx.Txid)
}
设置ACL
默认仅允许root调用authorize和create_good,此处可进一步扩展。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func setACL() {
acc1 := getAccount()
acc1.SetContractAccount(contractAccount)
xuperClient, err := xuper.New(node)
acl := xuper.NewACL(1, 1)
acl.AddAK(acc1.Address, 1)
tx, err := xuperClient.SetMethodACL(acc1, contractName, "authorize", acl)
if err != nil {
panic(err)
}
println(tx)
tx, err = xuperClient.SetMethodACL(acc1, contractName, "create_good", acl)
if err != nil {
panic(err)
}
println(tx)
}
添加商品
为测试方便,id使用简单的数字
填写参数:
- id:商品id
- desc:商品描述
- mnemonic:账号的助记词,仅支持中文
返回id已存在
更换id为101,返回成功
更换一个未授权的账号创建id=102,返回ACL not enough
若助记词解析失败则返回not valid
注册用户
修改操作需要用户已通过注册
填写参数:
- address:待授权账号
- desc:账号描述
- mnemonic:调用授权的账号的助记词,仅支持中文
授权成功
若已经授权,返回already exists
若没有authorize权限,返回ACL not enough
更新商品信息
更新商品信息的账户需要先经过authorize注册
填写参数:
- id:商品编号
- info:更新信息
- mnemonic:调用账号的助记词,仅支持中文
未注册的账户则返回user does not exist
成功则返回record编号
查询商品溯源信息
填写参数:
- id:商品编号
查询结果包含每条信息的创造用户
若商品不存在
用户信息
填写参数:
- address:查询的账户
用户信息包括描述和进行授权的用户
初始化时授权的root用户由合约账户授权
作业分析
基本实现了计划的功能,若要进一步改进,有以下几个方面
- 合约代码可以考虑开发一些配套接口,简化存储读写,增加类型适配,此处虽进行了一些尝试,但仍存在诸多问题;
- 权限的管理和溯源描述可进一步细化;
- 考虑是否增加物品所有权控制,由于笔者认为在溯源中意义不大,并未实现此功能。
创新性说明
本作品实现了基于百度超级链的商品溯源防伪方案。利用区块链不可篡改、数据可完整追溯以及时间戳功能,有效解决物品的溯源防伪问题。
总结
本文研究了百度超级链的工具集如何使用,并基于百度超级链编写了智能合约,以及编写了用于各方企业进行使用的网站,形成了一套商品防伪溯源方案。
在使用中发现了百度超级链的工具集开发很不完善,一些重要功能仍然缺失,许多地方都能看出质量不高。
另外,即使是加入了应用了区块链技术的防伪溯源方案,仍然无法解决溯源中一些尴尬的痛点问题,需要继续探索新的解决方案。