Fabric智能合约API学习

这篇文档通过学习之前写的chaincode来查看Fabric的智能合约相关的源码。并学习这些源码的功能。

智能合约里面的方法如何定义的

智能合约中的每个导出的方法都必须有contractapi.TransactionContextInterface类型的参数,并且这个方法是被定义在SmartContract上的,如下面一个获取资产出价价格的方法。

1
2
3
4
// GetAssetBidPrice returns the bid price
func (s *SmartContract) GetAssetBidPrice(ctx contractapi.TransactionContextInterface, assetID string) (string, error) {
return getAssetPrice(ctx, assetID, typeAssetBid)
}

contractapi.TransactionContextInterface的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// TransactionContextInterface defines the interface which TransactionContext
// meets. This can be taken by transacton functions on a contract which has not set
// a custom transaction context to allow transaction functions to take an interface
// to simplify unit testing.
// 交易上下文接口,为了方便测试
type TransactionContextInterface interface {
// GetStub should provide a way to access the stub set by Init/Invoke
// 获取由Init/Invoke设置的存根
GetStub() shim.ChaincodeStubInterface
// GetClientIdentity should provide a way to access the client identity set by Init/Invoke
// 获取由Init/Invoke设置的客户端身份
GetClientIdentity() cid.ClientIdentity
}

mock方式测试shim.ChaincodeStubInterface中的方法

接着看shim.ChaincodeStubInterface有哪些功能,源码在这里就不贴了,直接看测试用例,下面的测试用例的入口是TestStart,不包含尚未实现mock的方法。

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
package chaincode_test

import (
"encoding/json"
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"log"
"testing"
"unsafe"

"github.com/guozhe001/learn-contractapi-go/chaincode"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-chaincode-go/shimtest"
"github.com/stretchr/testify/require"
)

func mockInitLedger(t *testing.T, stub *shimtest.MockStub) {
assets := []chaincode.Asset{
{ID: AssetId, Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300},
{ID: "asset2", Color: "red", Size: 5, Owner: "Brad", AppraisedValue: 400},
{ID: "asset3", Color: "green", Size: 10, Owner: "Jin Soo", AppraisedValue: 500},
{ID: "asset4", Color: "yellow", Size: 10, Owner: "Max", AppraisedValue: 600},
{ID: "asset5", Color: "black", Size: 15, Owner: "Adriana", AppraisedValue: 700},
{ID: "asset6", Color: "white", Size: 15, Owner: "Michel", AppraisedValue: 800},
}
stub.MockTransactionStart("test")
putState(t, stub, assets...)
id := stub.GetTxID()
timestamp, err := stub.GetTxTimestamp()
channelID := stub.GetChannelID()
require.NoError(t, err)
require.NotNil(t, timestamp)
log.Printf("GetTxID()=%s, GetTxTimestamp()=%s, GetChannelID()=%s", id, timestamp, channelID)
stub.MockTransactionEnd("test")
}

func marshal(asset chaincode.Asset, t *testing.T) []byte {
assetJSON, err := json.Marshal(asset)
require.NoError(t, err)
return assetJSON
}

// ChaincodeStubInterface#PutState()
func putState(t *testing.T, stub *shimtest.MockStub, assets ...chaincode.Asset) {
for _, asset := range assets {
log.Printf("putState=%v", asset)
require.NoError(t, stub.PutState(asset.ID, marshal(asset, t)))
}
}

// ChaincodeStubInterface#GetState()
// ChaincodeStubInterface#PutState()
// ChaincodeStubInterface#DelState()
func getState(assetId string, t *testing.T, stub *shimtest.MockStub) {
// 获取指定key的资产的世界状态
state, err := stub.GetState(assetId)
require.NoError(t, err)
printAsset(t, state)
newAssetID := "temp001"
newAsset := chaincode.Asset{ID: newAssetID, Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}
// put一个新的资产
putState(t, stub, newAsset)
// 查询新的资产
newState, err := stub.GetState(newAssetID)
require.NoError(t, err)
require.NotNil(t, newState)
printAsset(t, newState)
// 指定资产ID删除资产
require.NoError(t, stub.DelState(newAssetID))
// 删除之后重新查询新的资产
newStateAgain, err := stub.GetState(newAssetID)
require.NoError(t, err)
require.Nil(t, newStateAgain)
}

func getHistoryForKey(assetId string, t *testing.T, stub *shimtest.MockStub) {
// 获取key的历史数据,目前mock还未实现
history, err := stub.GetHistoryForKey(assetId)
require.NoError(t, err)
if history != nil {
if history.HasNext() {
next, err := history.Next()
require.NoError(t, err)
marshal, err := json.Marshal(next)
require.NoError(t, err)
log.Printf("asset=%s history=%s", assetId, marshal)
}
history.Close()
}
}

func printAsset(t *testing.T, state []byte) {
var a chaincode.Asset
require.NoError(t, json.Unmarshal(state, &a))
marshal, err := json.Marshal(a)
require.NoError(t, err)
log.Printf("result state json value = %s", marshal)
}

// ChaincodeStubInterface#GetArgs()
// ChaincodeStubInterface#GetStringArgs()
func getArgs(t *testing.T, stub *shimtest.MockStub) {
args := stub.GetArgs()
for _, arg := range args {
log.Print(string(arg))
}

stringArgs := stub.GetStringArgs()
for _, argString := range stringArgs {
log.Print(argString)
}
}

// ChaincodeStubInterface#GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
// ChaincodeStubInterface#GetStateByRangeWithPagination(startKey, endKey string, pageSize int32, bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)
func getStateByRange(t *testing.T, stub *shimtest.MockStub) {
// GetStateByRange不指定startKey和endKey,会返回全部的资产;谨慎使用
states, err := stub.GetStateByRange("", "")
require.NoError(t, err)
printStateQueryIteratorInterface(t, states)
// GetStateByRangeWithPagination 因为mockStub直接返回三个nil,所以无法在mock环境测试
pagination, metadata, err := stub.GetStateByRangeWithPagination("", "", 5, "")
require.NoError(t, err)
log.Print("==========================================================================================")
log.Printf("GetStateByRangeWithPagination metadata=%v", metadata)
printStateQueryIteratorInterface(t, pagination)
}

func printStateQueryIteratorInterface(t *testing.T, states shim.StateQueryIteratorInterface) {
if states != nil {
for states.HasNext() {
next, err := states.Next()
require.NoError(t, err)
log.Print(next)
}
states.Close()
}
}

// ChaincodeStubInterface#CreateCompositeKey(objectType string, attributes []string) (string, error)
// ChaincodeStubInterface#SplitCompositeKey(compositeKey string) (string, []string, error)
func createCompositeKey(t *testing.T, stub *shimtest.MockStub) {
objectType := "test"
attributes := []string{"param1", "param2", "param3", "end"}
// 创建组合key,拼接了一下
key, err := stub.CreateCompositeKey(objectType, attributes)
require.NoError(t, err)
log.Printf("key=%s", key)
// 分割组合key,CreateCompositeKey的逆运算
compositeKey, strings, err := stub.SplitCompositeKey(key)
require.Equal(t, objectType, compositeKey)
require.Equal(t, attributes, strings)
newAsset := chaincode.Asset{ID: key, Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}
putState(t, stub, newAsset)
empty := []string{}
// 根据创建组合key的参数查询,后面的参数可以是空,这样会全部匹配出来
states, err := stub.GetStateByPartialCompositeKey(objectType, empty)
require.NoError(t, err)
require.NotNil(t, states)
printStateQueryIteratorInterface(t, states)
}

const (
AssetId string = "asset1"
TestMSP string = "TestMSP"
TestCollection string = "private_TestMSP"
Blank string = ""
)

// ChaincodeStubInterface#SetStateValidationParameter(key string, ep []byte) error
// ChaincodeStubInterface#GetStateValidationParameter(key string) ([]byte, error)
func setStateValidationParameter(t *testing.T, stub *shimtest.MockStub) {
// 新建一个基于状态的背书策略
endorsementPolicy, err := statebased.NewStateEP(nil)
require.NoError(t, err)
// 向背书策略添加需要背书的公司
require.NoError(t, endorsementPolicy.AddOrgs(statebased.RoleTypeMember, TestMSP))
policy, err := endorsementPolicy.Policy()
require.NoError(t, err)
// SetStateValidationParameter设置基于状态的背书策略
require.NoError(t, stub.SetStateValidationParameter(AssetId, policy))
// GetStateValidationParameter获取基于状态的背书策略
parameter, err := stub.GetStateValidationParameter(AssetId)
require.NoError(t, err)
str := byteToString(parameter)
// 打印出来的StateValidationParameter有特殊字符,所以使用包含传入的字符的方式断言
log.Printf("ID=%s, StateValidationParameter=%s", AssetId, str)
require.Contains(t, str, TestMSP)
}

// ChaincodeStubInterface#GetPrivateData(collection, key string) ([]byte, error)
// ChaincodeStubInterface#GetPrivateDataHash(collection, key string) ([]byte, error) 获取私有数据的hash值,这个方法就算不是私有数据的所有者也可以调用,mock版本没有实现;
// ChaincodeStubInterface#DelPrivateData(collection, key string) error 删除私有数据,mock版本没有实现;
// ChaincodeStubInterface#SetPrivateDataValidationParameter(collection, key string, ep []byte) error 设置私有数据的
// ChaincodeStubInterface#GetPrivateDataValidationParameter(collection, key string) ([]byte, error)
// ChaincodeStubInterface#GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) 根据范围查询私有数据
// ChaincodeStubInterface#GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error)
func getPrivateData(t *testing.T, stub *shimtest.MockStub) {
key := "private001"
privateAsset := chaincode.Asset{ID: key, Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}
bytes, err := json.Marshal(privateAsset)
require.NoError(t, err)
// 添加私有数据
require.NoError(t, stub.PutPrivateData(TestCollection, key, bytes))
// 获取私有资产
data, err := stub.GetPrivateData(TestCollection, key)
require.NoError(t, err)
require.NotNil(t, data)
printAsset(t, data)
// 使用不存在的其他的collection获取私有资产,不会返回error,会返回nil数据
data, err = stub.GetPrivateData("test_collections", key)
require.NoError(t, err)
require.Nil(t, data)
// 使用其他的key获取不存在私有资产
data, err = stub.GetPrivateData(TestCollection, AssetId)
require.NoError(t, err)
require.Nil(t, data)
// 查询公共资产数据,断言没有这个资产
state, err := stub.GetState(key)
require.NoError(t, err)
require.Nil(t, state)

endorsementPolicy, err := statebased.NewStateEP(nil)
require.NoError(t, err)
require.NoError(t, endorsementPolicy.AddOrgs(statebased.RoleTypeMember, TestMSP))
policy, err := endorsementPolicy.Policy()
require.NoError(t, err)
require.NoError(t, stub.SetPrivateDataValidationParameter(TestCollection, key, policy))
parameter, err := stub.GetPrivateDataValidationParameter(TestCollection, key)
require.NoError(t, err)
str := byteToString(parameter)
// 打印出来的StateValidationParameter有特殊字符,所以使用包含传入的字符的方式断言
log.Printf("ID=%s, StateValidationParameter=%s", AssetId, str)
require.Contains(t, str, TestMSP)
// GetPrivateDataHash(collection, key string) ([]byte, error) 获取私有数据的hash值,这个方法就算不是私有数据的所有者也可以调用,mock版本没有实现;
// DelPrivateData(collection, key string) error 删除私有数据,mock版本没有实现;
//require.NoError(t, stub.DelPrivateData(TestCollection, key))
//// 删除之后再次查询,断言已经没有此资产
//data, err = stub.GetPrivateData(TestCollection, key)
//require.NoError(t, err)
//require.Nil(t, state)
// GetPrivateDataByRange没有实现
//byRange, err := stub.GetPrivateDataByRange(TestCollection, Blank, Blank)
//require.NoError(t, err)
//require.NotNil(t, byRange)
//for byRange.HasNext() {
// next, err := byRange.Next()
// require.NotNil(t, err)
// log.Print(next)
//}
}

func byteToString(data []byte) string {
str := (*string)(unsafe.Pointer(&data))
return *str
}

// ChaincodeStubInterface#ChaincodeStubInterface#GetCreator() ([]byte, error) 获取签约交易提议的人,签约提议的人也是这个交易的创建者; mockstub未实现
// ChaincodeStubInterface#GetTransient() (map[string][]byte, error) 获取临时数据,这个方法只有设置了临时数据的peer才能查到数据,主要是为了做隐私保护的,详情参考隐秘的交易资产
// ChaincodeStubInterface#GetBinding() ([]byte, error) TODO 待理解
// ChaincodeStubInterface#GetDecorations() ([]byte, error) TODO 待理解,目前看是为了传递更多关于提议的的额外数据
// ChaincodeStubInterface#GetSignedProposal() ([]byte, error) 获取提议
// ChaincodeStubInterface#SetEvent(name string, payload []byte) error 允许链码在提议的response设置一个事件。无论交易的有效性如何,事件都将在已提交的块中的交易内可用。一个交易只能包含一个事件,并且如果是链码调用另一个链码的情况,事件只能在最外层。
func stubOthers(t *testing.T, stub *shimtest.MockStub) {
m := make(map[string][]byte)
tempAsset := chaincode.Asset{ID: "temp001", Color: "blue", Size: 5, Owner: "Tomoko", AppraisedValue: 300}
m["temp_asset"], _ = json.Marshal(tempAsset)
require.NoError(t, stub.SetTransient(m))
transient, err := stub.GetTransient()
require.NoError(t, err)
require.NotNil(t, transient)
for k, v := range transient {
log.Printf("k=%s, v=%s", k, v)
}
decorations := stub.GetDecorations()
for k, v := range decorations {
log.Printf("k=%s, v=%s", k, v)
}
}

// 测试shim.ChaincodeStubInterface接口
func stubTest(t *testing.T, stub *shimtest.MockStub) {
assetId := AssetId
mockInitLedger(t, stub)
stub.MockTransactionStart("test1")
getState(assetId, t, stub)
//getHistoryForKey(assetId, t, stub)
getArgs(t, stub)
stub.MockTransactionStart("test1")
getStateByRange(t, stub)
createCompositeKey(t, stub)
setStateValidationParameter(t, stub)
getPrivateData(t, stub)
stubOthers(t, stub)
}

// 测试contractapi.Contract的方法
func contractTest(t *testing.T, ccc *contractapi.ContractChaincode, stub *shimtest.MockStub) {
log.Printf("DefaultContract=%s", ccc.DefaultContract)
info := ccc.Info
log.Printf("info=%v", info)
stub.MockTransactionStart("contract_test")
// 如果调用一个不存在的方法,如果实现了GetUnknownTransaction接口,则会执行此接口返回的方法;否则不执行,并且也不会报错,但是如果有before方法是会执行的
response := stub.MockInvoke("uuid_002", [][]byte{[]byte("Unknow")})
log.Printf("response=%#v, response.Status=%d, response.Payload=%s", response, response.Status, byteToString(response.Payload))
// 调用一个被忽略的方法, 虽然IgnoredMe方法在智能合约中存在,但是因为合约满足IgnoreContractInterface接口然后把这个方法加入到了忽略列表中,所以最后还是调用的默认方法
response = stub.MockInvoke("uuid_002", [][]byte{[]byte("IgnoredMe")})
log.Printf("response=%#v, response.Status=%d, response.Payload=%s", response, response.Status, byteToString(response.Payload))
// 指定某个指定合约,调用一个不存在的方法,冒号前面的部分是智能合约名称,后面是方法名称
response = stub.MockInvoke("uuid_002", [][]byte{[]byte("TestSmartContract:Unknow")})
log.Printf("response=%#v, response.Status=%d, response.Payload=%s", response, response.Status, byteToString(response.Payload))
//invoke := ccc.Invoke(stub)
//log.Printf("response=%v", invoke)
stub.MockTransactionEnd("uuid_001")
transactionSerializer := ccc.TransactionSerializer
log.Printf("transactionSerializer=%v", transactionSerializer)
}

// 测试入口
func TestStart(t *testing.T) {
// 一个链码包中可以有多个智能合约
assetChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{}, &TestSmartContract{})
require.NoError(t, err)
// NewMockStub
stub := shimtest.NewMockStub("mockSub", assetChaincode)
stubTest(t, stub)
contractTest(t, assetChaincode, stub)
}

type TestSmartContract struct {
contractapi.Contract
}

// GetUnknownTransaction returns the current set unknownTransaction, may be nil
func (t *TestSmartContract) GetUnknownTransaction() interface{} {
return t.UnknownTransaction
}

// Default 如果不指定方法名称时指定的默认方法
func (t *TestSmartContract) UnknownTransaction(ctx contractapi.TransactionContextInterface) string {
log.Printf("hello, i'm Default func in TestSmartContract!")
return "i'm TestSmartContract, Bye!"
}

在智能合约中添加了如下的一些方法:

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

// GetUnknownTransaction returns the current set unknownTransaction, may be nil
func (s *SmartContract) GetUnknownTransaction() interface{} {
return s.UnknownTransaction
}

// Default 如果不指定方法名称时指定的默认方法
func (s *SmartContract) UnknownTransaction(ctx contractapi.TransactionContextInterface) string {
log.Printf("hello, i'm Default func!")
return "Bye!"
}

// GetBeforeTransaction returns the current set beforeTransaction, may be nil
func (s *SmartContract) GetBeforeTransaction() interface{} {
return s.BeforeTransaction
}

func (s *SmartContract) BeforeTransaction(ctx contractapi.TransactionContextInterface) {
log.Printf("i'm BeforeTransaction")
}

// GetAfterTransaction returns the current set afterTransaction, may be nil
func (s *SmartContract) GetAfterTransaction() interface{} {
return s.AfterTransaction
}

func (s *SmartContract) AfterTransaction(ctx contractapi.TransactionContextInterface) {
log.Printf("i'm AfterTransaction")
}

func (s *SmartContract) IgnoredMe(ctx contractapi.TransactionContextInterface) {
log.Printf("Ignored Me!")
}

func (s *SmartContract) GetIgnoredFunctions() []string {
return []string{"IgnoredMe"}
}

测试无法mock测试的shim.ChaincodeStubInterface方法

一些其他的无法使用shimtests做mock测试的shim.ChaincodeStubInterface方法:

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

// SomeStubMethod stub其他的无法通过mock方式测试的方法练习
func (s *SmartContract) SomeStubMethod(ctx contractapi.TransactionContextInterface, assetID string) error {
stub := ctx.GetStub()
// stub.GetArgs()和stub.GetStringArgs()都是获取调用链码时的入参,第一个参数时方法名,后面的参数是这个方法的参数的信息,如下:
// 2021/01/25 08:06:32 stub.GetArgs(),i=0, arg=Practice_SmartContract:SomeStubMethod
//2021/01/25 08:06:32 stub.GetArgs(),i=1, arg=asset1
for i, arg := range stub.GetArgs() {
log.Printf("stub.GetArgs(),i=%d, arg=%s", i, byteToString(arg))
}
for i, arg := range stub.GetStringArgs() {
log.Printf("stub.GetStringArgs(),i=%d, arg=%s", i, arg)
}
binding, err := stub.GetBinding()
if err != nil {
return err
}
log.Printf("stub.GetBinding()=%s", byteToString(binding))
for k, v := range stub.GetDecorations() {
log.Printf("stub.GetDecorations(), k=%s, v=%s", k, byteToString(v))
}
// stub.GetCreator()返回的是证书,如过是组织s2.supply.com的管理员发起的交易,则此处获得的是:Admin@s2.supply.com-cert.pem
creator, err := stub.GetCreator()
if err != nil {
return err
}
log.Printf("stub.GetCreator()=%s", byteToString(creator))
// 已经签名的提议,包含以下内容:
// 1.通道名称
// 2.链码名称
// 3.发起交易的组织名称
// 4.发起交易的人的证书
// 5.调用链码时的入参:方法名,参数等
// stub.GetSignedProposal().GetProposalBytes()的信息如下:
//2021/01/25 08:06:32 stub.GetSignedProposal().GetProposalBytes()=
//�
//v ��������"alljoinchannel*@252b6bbd22eeaf2193cdbc86fe7bd9fa257e33a6209a5da7d81dcc41b8bb1b9d:secured_supply�
//�
// GylSOrg2MSP�-----BEGIN CERTIFICATE-----
//MIICETCCAbegAwIBAgIRAJw2YUKkmyKusGHm33D7LhkwCgYIKoZIzj0EAwIwbTEL
//MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
//cmFuY2lzY28xFjAUBgNVBAoTDXMyLnN1cHBseS5jb20xGTAXBgNVBAMTEGNhLnMy
//LnN1cHBseS5jb20wHhcNMjEwMTA3MDgzMTAwWhcNMzEwMTA1MDgzMTAwWjBYMQsw
//CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
//YW5jaXNjbzEcMBoGA1UEAwwTQWRtaW5AczIuc3VwcGx5LmNvbTBZMBMGByqGSM49
//AgEGCCqGSM49AwEHA0IABJ6An5vHmug1YBIUXKuD50ZJ79TiwDkW5uEr2ZkXU5Em
//XwVlxwCOKpfqKOr1Xdk0DWMlAQPQIxeXktdVBJxFc4KjTTBLMA4GA1UdDwEB/wQE
//AwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIGO9q5qcp089i7bDqwyxRYdg
//aX65Bvs4X5wCsXWbxj37MAoGCCqGSM49BAMCA0gAMEUCIQCRBC/uF8ooaLQzSDo6
//e5+4UbBqjSi5MUy3IYfVrM5tHQIgaGHKXcKZY7q0Txs6LsbtayW6kWPOAee6Z1W8
//top2VDc=
//-----END CERTIFICATE-----
//�w�}dȧC>�v�@�El�S����I
//G
//Esecured_supply/
//%Practice_SmartContract:SomeStubMethod
//asset1
proposal, err := stub.GetSignedProposal()
if err != nil {
return err
}
log.Printf("stub.GetSignedProposal()=%#v", proposal)
bytes := proposal.GetProposalBytes()
log.Printf("stub.GetSignedProposal().GetProposalBytes()=%s", byteToString(bytes))
p := &peer.Proposal{}
err = proto.Unmarshal(bytes, p)
if err != nil {
return err
}
log.Printf("stub.GetSignedProposal().GetProposalBytes(),proto.Unmarshal=%#v", p)
//headerBytes:= p.GetHeader()
//header := &peer.ChaincodeHeaderExtension{}
//err = proto.Unmarshal(headerBytes, header)
//if err != nil {
// return err
//}
//log.Printf("stub.GetSignedProposal().GetProposalBytes()-Proposal-GetHeader()=%#v", header)
//payloadBytes := p.GetPayload()
//payload := &peer.ChaincodeProposalPayload{}
//err = proto.Unmarshal(payloadBytes, payload)
//if err != nil {
// return err
//}
//log.Printf("stub.GetSignedProposal().GetProposalBytes()-Proposal-GetPayload()=%#v", payload)
log.Printf("stub.GetSignedProposal().GetSignature()=%s", byteToString(proposal.GetSignature()))

// 设置一个Event
if err := stub.SetEvent("hello event", []byte("hello")); err != nil {
return err
}
//2021/01/25 10:22:57 stub.GetHistoryForKey(asset1), next=&queryresult.KeyModification{
//TxId:"f251ce5352e294cd628fc0b5d09271ebe8253b41d66069c164195fe2783c3adc",
//Value:[]uint8{0x7b, 0x22, 0x49, 0x44, 0x22, 0x3a, 0x22, 0x61, 0x73, 0x73, 0x65, 0x74, 0x31, 0x22, 0x2c
//, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x22, 0x3a, 0x22, 0x62, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x22, 0x73, 0x69, 0x7a, 0x65, 0x22, 0x3a, 0x35, 0x2c, 0x22, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x22, 0x3a, 0x22, 0x54, 0x6f, 0x6d, 0x6f, 0x6b, 0x6f, 0x22, 0x2c, 0x22, 0x61, 0x70, 0x70, 0x72, 0x61, 0x69, 0x73, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a, 0x33, 0x30, 0x30, 0x7d},
//Timestamp:(*timestamp.Timestamp)(0xc00043d1a0),
//IsDelete:false, XXX_NoUnkeyedLiteral:struct {}{},
//XXX_unrecognized:[]uint8(nil),
//XXX_sizecache:0}
assetHistory, err := stub.GetHistoryForKey(assetID)
if err != nil {
return err
}
defer assetHistory.Close()
for assetHistory.HasNext() {
next, err := assetHistory.Next()
if err != nil {
return err
}
log.Printf("stub.GetHistoryForKey(%s), next=%#v", assetID, next)
}

return nil
}

func byteToString(data []byte) string {
str := (*string)(unsafe.Pointer(&data))
return *str
}

shim包下面也有一个GetMSPID方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13

// GetMSPID returns the local mspid of the peer by checking the CORE_PEER_LOCALMSPID
// env var and returns an error if the env var is not set
// 通过检查环境变量CORE_PEER_LOCALMSPID返回peer节点本地的mspid,如果没有设置这个环境变量则返回一个error
func GetMSPID() (string, error) {
mspid := os.Getenv("CORE_PEER_LOCALMSPID")

if mspid == "" {
return "", errors.New("'CORE_PEER_LOCALMSPID' is not set")
}

return mspid, nil
}

TODO 待整合到一起

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
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
/*
SPDX-License-Identifier: Apache-2.0
*/

package main

import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"log"
"time"

"github.com/golang/protobuf/ptypes"
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)

const (
typeAssetForSale = "S"
typeAssetBid = "B"
typeAssetSaleReceipt = "SR"
typeAssetBuyReceipt = "BR"
statusEnable = "enable"
statusDelete = "delete"
)

type SmartContract struct {
contractapi.Contract
}

// Asset struct and properties must be exported (start with capitals) to work with contract api metadata
type Asset struct {
ObjectType string `json:"objectType"` // ObjectType is used to distinguish different object types in the same chaincode namespace
ID string `json:"assetID"`
OwnerOrg string `json:"ownerOrg"`
PublicDescription string `json:"publicDescription"`
Status string `json:"status"`
ParentID string `json:"parentID"`
}

type receipt struct {
price int
timestamp time.Time
}

// AssetProperties 资产属性
type AssetProperties struct {
ObjectType string `json:"objectType"` // ObjectType is used to distinguish different object types in the same chaincode namespace
ID string `json:"assetID"`
Issuer string `json:"issuer"`
Amount int `json:"amount"`
CreateDate time.Time `json:"createDate"`
EndDate time.Time `json:"endDate"`
Salt string `json:"salt"`
}

// CreateAsset creates an asset and sets it as owned by the client's org
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, assetID, publicDescription string) error {
// 获取临时数据库的数据,返回一个map[string][]byte
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}

// Asset properties must be retrieved from the transient field as they are private
immutablePropertiesJSON, ok := transientMap["asset_properties"]
fmt.Println("immutablePropertiesJSON:", immutablePropertiesJSON)
if !ok {
return fmt.Errorf("asset_properties key not found in the transient map")
}

return createAsset(ctx, immutablePropertiesJSON, assetID, publicDescription, "")
}

// CreateAsset creates an asset and sets it as owned by the client's org
func createAsset(ctx contractapi.TransactionContextInterface, immutablePropertiesJSON []byte, assetID, publicDescription string,
parentID string) error {
// Get client org id and verify it matches peer org id.
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
fmt.Println("clientOrgID:", clientOrgID)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %v", err)
}

asset := Asset{
ObjectType: "asset",
ID: assetID,
OwnerOrg: clientOrgID,
PublicDescription: publicDescription,
Status: statusEnable,
ParentID: parentID,
}
fmt.Println("asset:", asset)
assetBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to create asset JSON: %v", err)
}

err = ctx.GetStub().PutState(asset.ID, assetBytes)
if err != nil {
return fmt.Errorf("failed to put asset in public data: %v", err)
}

// Set the endorsement policy such that an owner org peer is required to endorse future updates
err = setAssetStateBasedEndorsement(ctx, asset.ID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for owner: %v", err)
}

// Persist private immutable asset properties to owner's private data collection
collection := buildCollectionName(clientOrgID)
fmt.Println("collection:", collection)
err = ctx.GetStub().PutPrivateData(collection, asset.ID, immutablePropertiesJSON)
if err != nil {
return fmt.Errorf("failed to put Asset private details: %v", err)
}
return nil
}

// // verifyAssetProperties 验证资产属性的信息
// func verifyAssetProperties(immutablePropertiesJSON []byte, asset Asset) error {
// assetProperties, err := getAssetProperties(immutablePropertiesJSON)
// if err != nil {
// return err
// }
// // 资产的属性ID和资产ID相同
// if asset.ID != assetProperties.ID {
// return fmt.Errorf("资产ID和资产属性ID必须相同")
// }
// // 资产的发行者就是资产的创建者,所有人都可以发行,但是别人认不认可这个组织发行的资产是另一回事
// if asset.OwnerOrg != assetProperties.Issuer {
// return fmt.Errorf("资产的发行方必须是当前创建资产的组织")
// }
// // 理论上这里应该还有更多的校验,比如说创建时间和失效时间的验证
// return nil
// }
// ChangePublicDescription updates the assets public description. Only the current owner can update the public description
func (s *SmartContract) ChangePublicDescription(ctx contractapi.TransactionContextInterface, assetID string, newDescription string) error {
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}
return changeOriginAssetInfo(ctx, *asset, "", newDescription)
}

// AgreeToSell adds seller's asking price to seller's implicit private data collection
func (s *SmartContract) AgreeToSell(ctx contractapi.TransactionContextInterface, assetID string) error {
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return err
}

clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %v", err)
}

// Verify that this clientOrgId actually owns the asset.
if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot sell an asset owned by %s", clientOrgID, asset.OwnerOrg)
}

return agreeToPrice(ctx, assetID, typeAssetForSale)
}

// AgreeToBuy adds buyer's bid price to buyer's implicit private data collection
func (s *SmartContract) AgreeToBuy(ctx contractapi.TransactionContextInterface, assetID string) error {
return agreeToPrice(ctx, assetID, typeAssetBid)
}

// agreeToPrice adds a bid or ask price to caller's implicit private data collection
func agreeToPrice(ctx contractapi.TransactionContextInterface, assetID string, priceType string) error {
// In this scenario, client is only authorized to read/write private data from its own peer.
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %v", err)
}

transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}

// Asset price must be retrieved from the transient field as they are private
price, ok := transMap["asset_price"]
if !ok {
return fmt.Errorf("asset_price key not found in the transient map")
}

collection := buildCollectionName(clientOrgID)

// Persist the agreed to price in a collection sub-namespace based on priceType key prefix,
// to avoid collisions between private asset properties, sell price, and buy price
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(priceType, []string{assetID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}

// The Price hash will be verified later, therefore always pass and persist price bytes as is,
// so that there is no risk of nondeterministic marshaling.
err = ctx.GetStub().PutPrivateData(collection, assetPriceKey, price)
if err != nil {
return fmt.Errorf("failed to put asset bid: %v", err)
}

return nil
}

// VerifyAssetProperties Allows a buyer to validate the properties of
// an asset against the owner's implicit private data collection
func (s *SmartContract) VerifyAssetProperties(ctx contractapi.TransactionContextInterface, assetID string) (bool, error) {
transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return false, fmt.Errorf("error getting transient: %v", err)
}

/// Asset properties must be retrieved from the transient field as they are private
immutablePropertiesJSON, ok := transMap["asset_properties"]
if !ok {
return false, fmt.Errorf("asset_properties key not found in the transient map")
}

asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return false, fmt.Errorf("failed to get asset: %v", err)
}

// 添加资产状态的验证
if (*asset).Status != statusEnable {
return false, fmt.Errorf("资产不可以,不允许交易: %v", err)
}

collectionOwner := buildCollectionName(asset.OwnerOrg)
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionOwner, assetID)
if err != nil {
return false, fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
}
if immutablePropertiesOnChainHash == nil {
return false, fmt.Errorf("asset private properties hash does not exist: %s", assetID)
}

hash := sha256.New()
hash.Write(immutablePropertiesJSON)
calculatedPropertiesHash := hash.Sum(nil)

// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
return false, fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x",
calculatedPropertiesHash,
immutablePropertiesJSON,
immutablePropertiesOnChainHash,
)
}

return true, nil
}

// TransferAsset checks transfer conditions and then transfers asset state to buyer.
// TransferAsset can only be called by current owner
func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, assetID string, buyerOrgID string) error {
clientOrgID, err := getClientOrgID(ctx, false)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %v", err)
}

transMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient data: %v", err)
}

immutablePropertiesJSON, ok := transMap["asset_properties"]
if !ok {
return fmt.Errorf("asset_properties key not found in the transient map")
}

priceJSON, ok := transMap["asset_price"]
if !ok {
return fmt.Errorf("asset_price key not found in the transient map")
}

var agreement Agreement
err = json.Unmarshal(priceJSON, &agreement)
if err != nil {
return fmt.Errorf("failed to unmarshal price JSON: %v", err)
}

asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
}

// 添加资产状态的验证
if (*asset).Status != statusEnable {
return fmt.Errorf("资产不可以,不允许交易")
}

err = verifyTransferConditions(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, priceJSON)
if err != nil {
return fmt.Errorf("failed transfer verification: %v", err)
}

err = transferAssetState(ctx, asset, immutablePropertiesJSON, clientOrgID, buyerOrgID, agreement.Price)
if err != nil {
return fmt.Errorf("failed asset transfer: %v", err)
}

return nil

}

// SplitAsset 拆分资产为两个资产,传入的amount是拆分后的其中一个资产的金额
func (s *SmartContract) SplitAsset(ctx contractapi.TransactionContextInterface, assetID string, amount int) error {
asset, err := s.ReadAsset(ctx, assetID)
if err != nil {
return err
}
immutableProperties, err := getAssetPrivateProperties(ctx, assetID)
if err != nil {
return err
}
assetProperties, err := getAssetProperties(immutableProperties)
if err != nil {
return err
}
if assetProperties.Amount <= amount {
return fmt.Errorf("资产ID的金额为%d小于想要拆分的金额为%d,不允许拆分", assetProperties.Amount, amount)
}
if err := splitAsset(ctx, assetProperties, assetID+"1", amount, *asset); err != nil {
return err
}
if err := splitAsset(ctx, assetProperties, assetID+"2", assetProperties.Amount-amount, *asset); err != nil {
return err
}
// 拆分之后删除旧资产
collection := buildCollectionName((*asset).OwnerOrg)
err = ctx.GetStub().DelPrivateData(collection, asset.ID)
if err != nil {
return fmt.Errorf("failed to delete Asset private details from org: %v", err)
}
// 修改公共资产信息
changeOriginAssetInfo(ctx, *asset, statusDelete, "已拆分")
return nil
}

// 根据transient获取的assetProperties的字节数组获取AssetProperties
func getAssetProperties(immutablePropertiesJSON []byte) (AssetProperties, error) {
var assetProperties AssetProperties
if err := json.Unmarshal(immutablePropertiesJSON, &assetProperties); err != nil {
return assetProperties, fmt.Errorf("failed to unmarshal price JSON: %v", err)
}
return assetProperties, nil
}

// ChangePublicDescription updates the assets public description. Only the current owner can update the public description
func changeOriginAssetInfo(ctx contractapi.TransactionContextInterface, asset Asset, status string, newDescription string) error {
// No need to check client org id matches peer org id, rely on the asset ownership check instead.
clientOrgID, err := getClientOrgID(ctx, false)
if err != nil {
return fmt.Errorf("failed to get verified OrgID: %v", err)
}

// Auth check to ensure that client's org actually owns the asset
if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot update the description of a asset owned by %s", clientOrgID, asset.OwnerOrg)
}

// 添加资产状态的验证
if asset.Status != statusEnable {
return fmt.Errorf("资产不可用,不允许修改")
}
if status != "" {
asset.Status = status
}
if newDescription != "" {
asset.PublicDescription = newDescription
}
updatedAssetJSON, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset: %v", err)
}

return ctx.GetStub().PutState(asset.ID, updatedAssetJSON)
}

// splitAsset 从原始资产属性拆分成指定ID和金额的资产
func splitAsset(ctx contractapi.TransactionContextInterface, originAssetProperties AssetProperties, newAssetID string, newAmount int,
asset Asset) error {
originAssetProperties.Amount = newAmount
originAssetProperties.ID = newAssetID
immutablePropertiesJSON, err := json.Marshal(originAssetProperties)
if err != nil {
return err
}
return createAsset(ctx, immutablePropertiesJSON, newAssetID, asset.PublicDescription, asset.ID)
}

// verifyTransferConditions checks that client org currently owns asset and that both parties have agreed on price
func verifyTransferConditions(ctx contractapi.TransactionContextInterface,
asset *Asset,
immutablePropertiesJSON []byte,
clientOrgID string,
buyerOrgID string,
priceJSON []byte) error {

// CHECK1: Auth check to ensure that client's org actually owns the asset

if clientOrgID != asset.OwnerOrg {
return fmt.Errorf("a client from %s cannot transfer a asset owned by %s", clientOrgID, asset.OwnerOrg)
}

// CHECK2: Verify that the hash of the passed immutable properties matches the on-chain hash

collectionSeller := buildCollectionName(clientOrgID)
immutablePropertiesOnChainHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, asset.ID)
if err != nil {
return fmt.Errorf("failed to read asset private properties hash from seller's collection: %v", err)
}
if immutablePropertiesOnChainHash == nil {
return fmt.Errorf("asset private properties hash does not exist: %s", asset.ID)
}

hash := sha256.New()
hash.Write(immutablePropertiesJSON)
calculatedPropertiesHash := hash.Sum(nil)

// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(immutablePropertiesOnChainHash, calculatedPropertiesHash) {
return fmt.Errorf("hash %x for passed immutable properties %s does not match on-chain hash %x",
calculatedPropertiesHash,
immutablePropertiesJSON,
immutablePropertiesOnChainHash,
)
}

// CHECK3: Verify that seller and buyer agreed on the same price

// Get sellers asking price
assetForSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
sellerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionSeller, assetForSaleKey)
if err != nil {
return fmt.Errorf("failed to get seller price hash: %v", err)
}
if sellerPriceHash == nil {
return fmt.Errorf("seller price for %s does not exist", asset.ID)
}

// Get buyers bid price
collectionBuyer := buildCollectionName(buyerOrgID)
assetBidKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// TODO 疑问:这个方法是由资产拥有者调用的,那么资产拥有者怎么可以获取资产买方的出价信息呢?如果是从公共状态获取购买方的出价hash是没问题的,但是从购买方的私有数据集中获取出价hash很让人费解。
buyerPriceHash, err := ctx.GetStub().GetPrivateDataHash(collectionBuyer, assetBidKey)
if err != nil {
return fmt.Errorf("failed to get buyer price hash: %v", err)
}
if buyerPriceHash == nil {
return fmt.Errorf("buyer price for %s does not exist", asset.ID)
}

hash = sha256.New()
hash.Write(priceJSON)
calculatedPriceHash := hash.Sum(nil)

// Verify that the hash of the passed price matches the on-chain sellers price hash
if !bytes.Equal(calculatedPriceHash, sellerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, seller hasn't agreed to the passed trade id and price",
calculatedPriceHash,
priceJSON,
sellerPriceHash,
)
}

// Verify that the hash of the passed price matches the on-chain buyer price hash
if !bytes.Equal(calculatedPriceHash, buyerPriceHash) {
return fmt.Errorf("hash %x for passed price JSON %s does not match on-chain hash %x, buyer hasn't agreed to the passed trade id and price",
calculatedPriceHash,
priceJSON,
buyerPriceHash,
)
}

return nil
}

// transferAssetState performs the public and private state updates for the transferred asset
func transferAssetState(ctx contractapi.TransactionContextInterface, asset *Asset, immutablePropertiesJSON []byte, clientOrgID string, buyerOrgID string, price int) error {
asset.OwnerOrg = buyerOrgID
updatedAsset, err := json.Marshal(asset)
if err != nil {
return err
}

err = ctx.GetStub().PutState(asset.ID, updatedAsset)
if err != nil {
return fmt.Errorf("failed to write asset for buyer: %v", err)
}

// Change the endorsement policy to the new owner
err = setAssetStateBasedEndorsement(ctx, asset.ID, buyerOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new owner: %v", err)
}

// Transfer the private properties (delete from seller collection, create in buyer collection)
collectionSeller := buildCollectionName(clientOrgID)
err = ctx.GetStub().DelPrivateData(collectionSeller, asset.ID)
if err != nil {
return fmt.Errorf("failed to delete Asset private details from seller: %v", err)
}

collectionBuyer := buildCollectionName(buyerOrgID)
err = ctx.GetStub().PutPrivateData(collectionBuyer, asset.ID, immutablePropertiesJSON)
if err != nil {
return fmt.Errorf("failed to put Asset private properties for buyer: %v", err)
}

// Delete the price records for seller
assetPriceKey, err := ctx.GetStub().CreateCompositeKey(typeAssetForSale, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for seller: %v", err)
}

err = ctx.GetStub().DelPrivateData(collectionSeller, assetPriceKey)
if err != nil {
return fmt.Errorf("failed to delete asset price from implicit private data collection for seller: %v", err)
}

// Delete the price records for buyer
assetPriceKey, err = ctx.GetStub().CreateCompositeKey(typeAssetBid, []string{asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for buyer: %v", err)
}

err = ctx.GetStub().DelPrivateData(collectionBuyer, assetPriceKey)
if err != nil {
return fmt.Errorf("failed to delete asset price from implicit private data collection for buyer: %v", err)
}

// Keep record for a 'receipt' in both buyers and sellers private data collection to record the sale price and date.
// Persist the agreed to price in a collection sub-namespace based on receipt key prefix.
receiptBuyKey, err := ctx.GetStub().CreateCompositeKey(typeAssetBuyReceipt, []string{asset.ID, ctx.GetStub().GetTxID()})
if err != nil {
return fmt.Errorf("failed to create composite key for receipt: %v", err)
}

txTimestamp, err := ctx.GetStub().GetTxTimestamp()
if err != nil {
return fmt.Errorf("failed to create timestamp for receipt: %v", err)
}

timestamp, err := ptypes.Timestamp(txTimestamp)
if err != nil {
return err
}
assetReceipt := receipt{
price: price,
timestamp: timestamp,
}
receipt, err := json.Marshal(assetReceipt)
if err != nil {
return fmt.Errorf("failed to marshal receipt: %v", err)
}

err = ctx.GetStub().PutPrivateData(collectionBuyer, receiptBuyKey, receipt)
if err != nil {
return fmt.Errorf("failed to put private asset receipt for buyer: %v", err)
}

receiptSaleKey, err := ctx.GetStub().CreateCompositeKey(typeAssetSaleReceipt, []string{ctx.GetStub().GetTxID(), asset.ID})
if err != nil {
return fmt.Errorf("failed to create composite key for receipt: %v", err)
}

err = ctx.GetStub().PutPrivateData(collectionSeller, receiptSaleKey, receipt)
if err != nil {
return fmt.Errorf("failed to put private asset receipt for seller: %v", err)
}

return nil
}

// getClientOrgID gets the client org ID.
// The client org ID can optionally be verified against the peer org ID, to ensure that a client
// from another org doesn't attempt to read or write private data from this peer.
// The only exception in this scenario is for TransferAsset, since the current owner
// needs to get an endorsement from the buyer's peer.
func getClientOrgID(ctx contractapi.TransactionContextInterface, verifyOrg bool) (string, error) {
// GetClientIdentity()获取客户端的身份,返回ClientIdentity接口,这个接口有如下方法:

// GetID returns the ID associated with the invoking identity. This ID
// is guaranteed to be unique within the MSP.
// * GetID() (string, error) 获取

clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return "", fmt.Errorf("failed getting client's orgID: %v", err)
}

if verifyOrg {
err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return "", err
}
}

return clientOrgID, nil
}

// verifyClientOrgMatchesPeerOrg checks the client org id matches the peer org id.
func verifyClientOrgMatchesPeerOrg(clientOrgID string) error {
peerOrgID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting peer's orgID: %v", err)
}

if clientOrgID != peerOrgID {
return fmt.Errorf("client from org %s is not authorized to read or write private data from an org %s peer",
clientOrgID,
peerOrgID,
)
}

return nil
}

// setAssetStateBasedEndorsement adds an endorsement policy to a asset so that only a peer from an owning org
// can update or transfer the asset.
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, assetID string, orgToEndorse string) error {

endorsementPolicy, err := statebased.NewStateEP(nil)
if err != nil {
return err
}
err = endorsementPolicy.AddOrgs(statebased.RoleTypeMember, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
// fmt.Printf("assetID=%s, orgToEndorse=%s, policy=%s, len(policy)=%d \n", assetID, orgToEndorse, policy, len(policy))
// fmt.Printf("assetID=%s, policy=%s, endorsementPolicy.ListOrgs=%s\n", assetID, string(policy[:]), endorsementPolicy.ListOrgs())
return ctx.GetStub().SetStateValidationParameter(assetID, policy)
}

func buildCollectionName(clientOrgID string) string {
return fmt.Sprintf("_implicit_org_%s", clientOrgID)
}

func getClientImplicitCollectionName(ctx contractapi.TransactionContextInterface) (string, error) {
clientOrgID, err := getClientOrgID(ctx, true)
if err != nil {
return "", fmt.Errorf("failed to get verified OrgID: %v", err)
}

err = verifyClientOrgMatchesPeerOrg(clientOrgID)
if err != nil {
return "", err
}

return buildCollectionName(clientOrgID), nil
}

func main() {
chaincode, err := contractapi.NewChaincode(new(SmartContract))
if err != nil {
log.Panicf("Error create transfer asset chaincode: %v", err)
}

if err := chaincode.Start(); err != nil {
log.Panicf("Error starting asset chaincode: %v", err)
}
}