Hyperledger Hackathon

周末参加了万达和IBM组织的Hyperledger Hackathon,确实是一场区块链的盛宴,三十四个参赛队,过程紧张而热烈。我们也在24小时内完成产品设计,前后端的开发,联调和部署,PPT的制作,虽未得奖,但收获颇丰。相关代码和文档地址:HyperledgerHackathon


1. 区块链概述

先说说什么是区块链,区块链属于一种去中心化的记录技术。参与到系统上的节点,可能不属于同一组织、彼此无需信任;区块链数据由所有节点共同维护,每个参与维护节点都能复制获得一份完整记录的拷贝。具有以下特点:

  • 维护一条不断增长的链,只可能添加记录,而发生过的记录都不可篡改;
  • 去中心化,或者说多中心化,无需集中的控制而能达成共识,实现上尽量分布式;
  • 通过密码学的机制来确保交易无法抵赖和破坏,并尽量保护用户信息和记录的隐私性。

更多区块链相关可参考:区块链技术指南


2. 积分链

我们的场景是积分链,基于区块链的垂直电商积分共享平台。

2.1 现状

  • 消费者:消费者拥有积分种类繁多,但积分价值低,很多积分成为”鸡肋“
  • 商户:商户发行的积分流动性差,对消费者吸引力有限,小商户甚至无力发行积分。
  • 现有积分平台:现有积分平台结算复杂,安全性存在隐患,导流效果不明显。

2.2 积分链

  • 去中心化:保持各垂直电商独立性
  • 积分共享:消费者可使用任意商户的任意积分
  • 快速结算:结算机制高效可靠
  • 交易透明:交易记录清晰易查
  • 安全信任:数据安全,防止篡改,不可撤销

2.3 业务模式

2.3.1 商户加盟

  • 约定积分价值、积分发行量,缴纳保证金。
  • 创建独立节点。
  • 生成智能合约,包含积分价值、积分发行量、发行方等。

2.3.2 积分产生

  • 用户在商户产生积分时,调用平台接口,平台增加该用户在该商户积分。
  • 商户积分总量达到发行量时,通知商户补充保证金,发行新积分。

2.3.3 积分使用

  • 用户可选择任意商户积分使用。
  • 使用后平台产生交易记录,包含用户/使用方/发行方/数量/订单号等。

2.3.4 结算

平台根据交易记录与积分价值约定,以周期或实时方式与各商户进行结算。

2.3.5 交易查询

商户可查询所有交易记录。

2.4 Chaincode

现场使用的是IBM bluemix,我们项目chaincode如下:

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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package main

//WARNING - this chaincode's ID is hard-coded in chaincode_example04 to illustrate one way of
//calling chaincode from a chaincode. If this example is modified, chaincode_example04.go has
//to be modified as well with the new ID of chaincode_example02.
//chaincode_example05 show's how chaincode ID can be passed in as a parameter instead of
//hard-coding.

import (
"errors"
"fmt"
"strconv"

"encoding/json"

"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
var username, shopA, shopB string // Entities
var err error

if len(args) != 3 {
return nil, errors.New("Incorrect number of arguments. Expecting 3: username, shopA, shopB")
}

// Initialize the chaincode
username = args[0]
shopA = args[1]
shopB = args[2]

fmt.Printf("username = %s, shopA= %s, shopB = %s\n", username, shopA, shopB)

var user_A, user_B, user_A_B, user_B_A string
user_A = username + "_" + shopA
user_B = username + "_" + shopB
user_A_B = username + "_" + shopA + "_" + shopB
user_B_A = username + "_" + shopB + "_" + shopA

// Write the state to the ledger
err = stub.PutState("user", []byte(username))
if err != nil {
return nil, err
}

shops := []string{shopA, shopB}
shopsBytes, _ := json.Marshal(shops)

err = stub.PutState(username, shopsBytes)
if err != nil {
return nil, err
}

err = stub.PutState(user_A, []byte(strconv.Itoa(0)))
if err != nil {
return nil, err
}
err = stub.PutState(user_B, []byte(strconv.Itoa(0)))
if err != nil {
return nil, err
}
err = stub.PutState(user_A_B, []byte(strconv.Itoa(0)))
if err != nil {
return nil, err
}

err = stub.PutState(user_B_A, []byte(strconv.Itoa(0)))
if err != nil {
return nil, err
}

return nil, nil
}

// Transaction
// 1. add user, shop, points: add, username, shopName, xx points
// 2. user1 spent shopA's points in shopB by xx points: consume, username, shopA, shopB, xx points
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {

fmt.Println("Invoke running. Function: " + function)

if function == "add" {
return t.add(stub, args)
} else if function == "consume" || function == "spend" {
return t.spend(stub, args)
}

return nil, errors.New("Received unknown function invocation: " + function)


}

func (t *SimpleChaincode) add(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
var username, shopname, user_shop string
var points, cur_points int // accumulated points
var err error

if len(args) != 3 {
return nil, errors.New("Incorrect number of arguments. Expecting 3: username, shopname, points")
}
username = args[0]
shopname = args[1]
points, _ = strconv.Atoi(args[2])

fmt.Println("add: got param: " + username + "," + shopname + "," + args[2])

user_shop = username + "_" + shopname

pointsBytes, err := stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
cur_points, _ = strconv.Atoi(string(pointsBytes))

cur_points = cur_points + points
fmt.Printf("After add points:%d, cur_points:%d\n", points, cur_points)

// Write the state back to the ledger
err = stub.PutState(user_shop, []byte(strconv.Itoa(cur_points)))
if err != nil {
return nil, err
}

return nil, nil

}

func (t *SimpleChaincode) spend(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
var username, spend_shopname, spent_shopname, user_spent_shop, user_spend_spent_shop string
var points, cur_points int // accumulated points
var err error

if len(args) != 4 {
return nil, errors.New("Incorrect number of arguments. Expecting 4: username, spend_shop, spent_shop, points")
}
username = args[0]
spend_shopname = args[1]
spent_shopname = args[2]
points, err = strconv.Atoi(args[3])
fmt.Println("spend: got param: " + username + "," + spend_shopname + "," + spent_shopname + ", " + args[3])

//user_spend_shop = username + "_" + spend_shopname
user_spent_shop = username + "_" + spent_shopname
user_spend_spent_shop = username + "_" + spend_shopname + "_" + spent_shopname

// Subtract spent shop's points and record user_spend_spent_shop value. This is the points that is how many points shopA spend shopB
var user_shop string
user_shop = user_spent_shop
pointsBytes, err := stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
cur_points, _ = strconv.Atoi(string(pointsBytes))

cur_points = cur_points - points
fmt.Printf("After spend points, user-shop's ledger:%s, points: %d, cur_points:%d\n", user_shop, points, cur_points)

// Write the state back to the ledger
err = stub.PutState(user_shop, []byte(strconv.Itoa(cur_points)))
if err != nil {
return nil, err
}

user_shop = user_spend_spent_shop
pointsBytes, err = stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
cur_points, _ = strconv.Atoi(string(pointsBytes))

cur_points = cur_points + points
fmt.Printf("After spend points, user-shops' ledger:%s, points:%d, cur_points %s\n", user_shop, points, cur_points)

// Write the state back to the ledger
err = stub.PutState(user_shop, []byte(strconv.Itoa(cur_points)))
if err != nil {
return nil, err
}


return nil, nil

}


// Query callback representing the query of a chaincode
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
fmt.Println("Query running. Function: " + function)

if function == "query_user" || function == "query" {
return t.query_user(stub, args)
} else if function == "query_shop" {
return t.query_shop(stub, args)
}
return []byte("No such function"), nil
}

func (t *SimpleChaincode) query_user(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
var username, shopA, shopB, user_A, user_B string
var shops []string
var shopA_points, shopB_points int // accumulated points
var err error

if len(args) != 1 {
return nil, errors.New("Incorrect number of arguments. Expecting 1: username")
}
username = args[0]
fmt.Println("query_user: got param: " + username )


shopsBytes, err := stub.GetState(username)
err = json.Unmarshal(shopsBytes, &shops)
if err != nil {
fmt.Println("Error unmarshalling user's shops: " + username + "\n--->: " + err.Error())
return nil, errors.New("Error unmarshalling user's shops " + username)
}
shopA = shops[0]
shopB = shops[1]

user_A = username + "_" + shopA
user_B = username + "_" + shopB

user_shop := user_A
pointsBytes, err := stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
shopA_points, _ = strconv.Atoi(string(pointsBytes))


user_shop = user_B
pointsBytes, err = stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
shopB_points, _ = strconv.Atoi(string(pointsBytes))

resp:= map[string]int{
shopA: shopA_points,
shopB: shopB_points,
}

jsonResp, err := json.Marshal(resp)
if err != nil {
return nil, errors.New("resp marshal fail" )
}
fmt.Printf("Query Response:%s\n", jsonResp)
return []byte(jsonResp), nil
}

//
// Settle shops' points
//
func (t *SimpleChaincode) query_shop(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
var username, shopA, shopB, user_A, user_B string
var shops []string
var shopA_points, shopB_points int // accumulated points
var err error

if len(args) != 1 {
return nil, errors.New("Incorrect number of arguments 2. Expecting: shopA, shopB")
}
username = "phyllis"
shopA = args[0]
shopB = args[1]
fmt.Println("query_shop: got param: " + shopA + "," + shopB)

//var user_A_B, user_B_A string
user_A = username + "_" + shopA
user_B = username + "_" + shopB
//user_A_B = username + "_" + shopA + "_" + shopB
//user_B_A = username + "_" + shopB + "_" + shopA


shopsBytes, err := stub.GetState(username)
err = json.Unmarshal(shopsBytes, &shops)
if err != nil {
fmt.Println("Error unmarshalling user's shops: " + username + "\n--->: " + err.Error())
return nil, errors.New("Error unmarshalling user's shops " + username)
}
shopA = shops[0]
shopB = shops[1]

user_A = username + "_" + shopA
user_B = username + "_" + shopB

user_shop := user_A
pointsBytes, err := stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
shopA_points, _ = strconv.Atoi(string(pointsBytes))


user_shop = user_B
pointsBytes, err = stub.GetState(user_shop)

if err != nil {
return nil, errors.New("Failed to get state: " + user_shop)
}
if pointsBytes == nil {
return nil, errors.New("Entity not found: " + user_shop)
}
shopB_points, _ = strconv.Atoi(string(pointsBytes))

resp:= map[string]int{
shopA: shopA_points,
shopB: shopB_points,
}

jsonResp, err := json.Marshal(resp)
if err != nil {
return nil, errors.New("resp marshal fail" )
}
fmt.Printf("Query Response:%s\n", jsonResp)
return []byte(jsonResp), nil

}
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}