contract_chaincode.go源码学习

Chaincode如何被启用

定义智能合约

我们看下面的,名为SmartContract的结构,就是智能合约,你也可以起其他的名字,但是无论名字叫什么,智能合约都必须有一个内嵌的contractapi.Contract,我们看一下它的定义:

1
2
3
type SmartContract struct {
contractapi.Contract
}

启动智能合约的入口

在每个chaincode中都必须有一个main方法,这个方法创建一个新的链码并调用它的Start()方法:

1
2
3
4
5
6
7
8
9
10
11
func main() {
// 通过NewChaincode()方法创建链码
chaincode, err := contractapi.NewChaincode(new(SmartContract))
if err != nil {
log.Panicf("Error create transfer asset chaincode: %v", err)
}
// 调用链码的Start()方法来启动链码
if err := chaincode.Start(); err != nil {
log.Panicf("Error starting asset chaincode: %v", err)
}
}

智能合约启动的源码

让我们完整的看一下contractapi的源码,源码路径:github.com/hyperledger/fabric-contract-api-go/contractapi/contract_chaincode.go

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
// Copyright the Hyperledger Fabric contributors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package contractapi

import (
"encoding/json"
"fmt"
"reflect"
"sort"
"strings"
"unicode"

"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/internal"
"github.com/hyperledger/fabric-contract-api-go/internal/utils"
"github.com/hyperledger/fabric-contract-api-go/metadata"
"github.com/hyperledger/fabric-contract-api-go/serializer"
"github.com/hyperledger/fabric-protos-go/peer"
)

type contractChaincodeContract struct {
info metadata.InfoMetadata
functions map[string]*internal.ContractFunction
unknownTransaction *internal.TransactionHandler
beforeTransaction *internal.TransactionHandler
afterTransaction *internal.TransactionHandler
transactionContextHandler reflect.Type
}

// ContractChaincode a struct to meet the chaincode interface and provide routing of calls to contracts
type ContractChaincode struct {
DefaultContract string
contracts map[string]contractChaincodeContract
metadata metadata.ContractChaincodeMetadata
Info metadata.InfoMetadata
TransactionSerializer serializer.TransactionSerializer
}

// SystemContractName the name of the system smart contract
const SystemContractName = "org.hyperledger.fabric"

// NewChaincode creates a new chaincode using contracts passed. The function parses each
// of the passed functions and stores details about their make-up to be used by the chaincode.
// Public functions of the contracts are stored and are made callable in the chaincode. The function
// will error if contracts are invalid e.g. public functions take in illegal types. A system contract is added
// to the chaincode which provides functionality for getting the metadata of the chaincode. The generated
// metadata is a JSON formatted MetadataContractChaincode containing each contract as a name and details
// of the public functions and types they take in/return. It also outlines version details for contracts and the
// chaincode. If these are blank strings this is set to latest. The names for parameters do not match those used
// in the functions, instead they are recorded as param0, param1, ..., paramN. If there exists a file
// contract-metadata/metadata.json then this will overwrite the generated metadata. The contents of this file must
// validate against the schema. The transaction serializer for the contract is set to be the JSONSerializer by
// default. This can be updated using by changing the TransactionSerializer property
// 使用传入的`ContractInterface`列表创建一个新的链码(说明链码并不是智能合约,链码包含了很多智能合约)。
// 该函数解析每个被传递过来的`ContractInterface`的函数,并存储有关链码将使用的其组成的详细信息。
// contracts的公共方法被存储并且可以在链码中调用这些公共方法。如果合约定义的不合法(如公共方法使用非法的类型)这个方法会报错。
// 一个系统合约已添加到链码中,该合约提供了获取链码元数据的功能。
// 生成的元数据是JSON格式的MetadataContractChaincode,其中包含每个合约的名称以及公共函数以及它们参数和返回的类型的详细信息。
// 它还概述了合同和链码的版本详细信息。 如果这些是空白字符串,则将其设置为最新。
// 元数据对于公共方法的描述的参数名称与原始定义的函数中使用的名称不匹配,而是记录为param0,param1,...,paramN。
// 如果存在contract-metadata/metadata.json文件,那么它将覆盖生成的元数据。 该文件的内容必须根据架构进行验证。
// 默认情况下,合约的交易序列化器设置为JSONSerializer。可以通过更改TransactionSerializer属性来更改。
func NewChaincode(contracts ...ContractInterface) (*ContractChaincode, error) {
ciMethods := getCiMethods()

// new一个ContractChaincode
cc := new(ContractChaincode)
// 设置cc的contracts为一个空map
cc.contracts = make(map[string]contractChaincodeContract)

// 遍历传入的合约列表
for _, contract := range contracts {
additionalExcludes := []string{}
// 如果传入的合约类型是IgnoreContractInterface
if castContract, ok := contract.(IgnoreContractInterface); ok {
// 则把castContract中的需要忽略的方法赋值给additionalExcludes切片
additionalExcludes = castContract.GetIgnoredFunctions()
}
// 向cc中添加合约,下面再看addContract方法都干了什么
err := cc.addContract(contract, append(ciMethods, additionalExcludes...))

if err != nil {
return nil, err
}
}
// 创建系统合约,并把系统合约添加到链码中
sysC := new(SystemContract)
sysC.Name = SystemContractName

cc.addContract(sysC, ciMethods) // should never error as system contract is good
// 增加元数据
err := cc.augmentMetadata()

if err != nil {
return nil, err
}

metadataJSON, _ := json.Marshal(cc.metadata)
// 把元数据设置到系统合约中
sysC.setMetadata(string(metadataJSON))
// 设置链码的交易序列化器
cc.TransactionSerializer = new(serializer.JSONSerializer)

return cc, nil
}


// Start starts the chaincode in the fabric shim
// 在fabric shim中启动链码
// 至于shim是什么,先了解大概:shim包为链码提供API,这些API可以访问状态变量、交易上下文和调用其他的链码。
// shim包源码注释:
// Package shim provides APIs for the chaincode to access its state
// variables, transaction context and call other chaincodes.
func (cc *ContractChaincode) Start() error {
return shim.Start(cc)
}

// Init is called during Instantiate transaction after the chaincode container
// has been established for the first time, passes off details of the request to Invoke
// for handling the request if a function name is passed, otherwise returns shim.Success
// 首次建立链码容器后,在实例化交易之前调用Init
// 如果一个方法名被传递了过来,则把请求的详情传递给Invoke方法来处理,否则返回shim.Success
func (cc *ContractChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
nsFcn, _ := stub.GetFunctionAndParameters()
if nsFcn == "" {
return shim.Success([]byte("Default initiator successful."))
}

return cc.Invoke(stub)
}

// Invoke is called to update or query the ledger in a proposal transaction. Takes the
// args passed in the transaction and uses the first argument to identify the contract
// and function of that contract to be called. The remaining args are then used as
// parameters to that function. Args are converted from strings to the expected parameter
// types of the function before being passed using the set transaction serializer for the ContractChaincode.
// A transaction context is generated and is passed, if required, as the first parameter to the named function.
// Before and after functions are called before and after the named function passed if the contract defines such
// functions to exist. If the before function returns an error the named function is not called and its error
// is returned in shim.Error. If the after function returns an error then its value is returned
// to shim.Error otherwise the value returned from the named function is returned as shim.Success (formatted by
// the transaction serializer). If an unknown name is passed as part of the first arg a shim.Error is returned.
// If a valid name is passed but the function name is unknown then the contract with that name's
// unknown function is called and its value returned as success or error depending on its return. If no
// unknown function is defined for the contract then shim.Error is returned by Invoke. In the case of
// unknown function names being passed (and the unknown handler returns an error) or the named function
// returning an error then the after function if defined is not called. If the named function or unknown
// function handler returns a non-error type then then the after transaction is sent this value. The same
// transaction context is passed as a pointer to before, after, named and unknown functions on each Invoke.
// If no contract name is passed then the default contract is used.
func (cc *ContractChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 获取方法和入参
nsFcn, params := stub.GetFunctionAndParameters()
// 获取字符串":"在nsFcn中最后一次出现的下标
li := strings.LastIndex(nsFcn, 字符串":"在)

// 合约
var ns string
// 方法
var fn string

// 如果nsFcn中不存在":",则说明没有指定合约名称
if li == -1 {
ns = cc.DefaultContract
fn = nsFcn
} else {
ns = nsFcn[:li]
fn = nsFcn[li+1:]
}

// 如果合约名不在链码的合约里面,则报错
if _, ok := cc.contracts[ns]; !ok {
return shim.Error(fmt.Sprintf("Contract not found with name %s", ns))
}

// 如果方法为空,则报错
if fn == "" {
return shim.Error("Blank function name passed")
}

originalFn := fn
// 把fn强转成rune(int32类型的别名)
fnRune := []rune(fn)

// 如果传入的方法名的首字母是小写的,则转换成大写
if unicode.IsLower(fnRune[0]) {
fnRune[0] = unicode.ToUpper(fnRune[0])
fn = string(fnRune)
}
// 从链码的合约列表中获取合约
nsContract := cc.contracts[ns]
// 通过反射new一个transactionContextHandler类型;这几行有点看不懂了,暂停一下TODO
ctx := reflect.New(nsContract.transactionContextHandler)
ctxIface := ctx.Interface().(SettableTransactionContextInterface)
ctxIface.SetStub(stub)

ci, _ := cid.New(stub)
ctxIface.SetClientIdentity(ci)

beforeTransaction := nsContract.beforeTransaction

if beforeTransaction != nil {
_, _, errRes := beforeTransaction.Call(ctx, nil, nil)

if errRes != nil {
return shim.Error(errRes.Error())
}
}

var successReturn string
var successIFace interface{}
var errorReturn error

serializer := cc.TransactionSerializer

if _, ok := nsContract.functions[fn]; !ok {
unknownTransaction := nsContract.unknownTransaction
if unknownTransaction == nil {
return shim.Error(fmt.Sprintf("Function %s not found in contract %s", originalFn, ns))
}

successReturn, successIFace, errorReturn = unknownTransaction.Call(ctx, nil, serializer)
} else {
var transactionSchema *metadata.TransactionMetadata

for _, v := range cc.metadata.Contracts[ns].Transactions {
if v.Name == fn {
transactionSchema = &v
break
}
}

successReturn, successIFace, errorReturn = nsContract.functions[fn].Call(ctx, transactionSchema, &cc.metadata.Components, serializer, params...)
}

if errorReturn != nil {
return shim.Error(errorReturn.Error())
}

afterTransaction := nsContract.afterTransaction

if afterTransaction != nil {
_, _, errRes := afterTransaction.Call(ctx, successIFace, nil)

if errRes != nil {
return shim.Error(errRes.Error())
}
}

return shim.Success([]byte(successReturn))
}

func (cc *ContractChaincode) addContract(contract ContractInterface, excludeFuncs []string) error {
// 返回合同名称。当合同用于创建新的链码时,将调用此函数,然后使用返回的名称在调用Init/Invoke时在链码中标识合同。
// 此函数可以返回空白字符串,但这是未定义的行为。
ns := contract.GetName()

// 如果合约名称为空,则使用合约类型通过反射的方式获取合约的名称
if ns == "" {
ns = reflect.TypeOf(contract).Elem().Name()
}

// 如果链码中已经包含了相同的合约名称,则抛异常
if _, ok := cc.contracts[ns]; ok {
return fmt.Errorf("Multiple contracts being merged into chaincode with name %s", ns)
}

ccn := contractChaincodeContract{}
ccn.transactionContextHandler = reflect.ValueOf(contract.GetTransactionContextHandler()).Elem().Type()
transactionContextPtrHandler := reflect.ValueOf(contract.GetTransactionContextHandler()).Type()
ccn.functions = make(map[string]*internal.ContractFunction)
// GetInfo方法返回存储的合约的信息,这个信息会用于构建合约的元数据。如果此信息中的版本信息为空,则使用"latest"
// 如果信息中的title为空,则使用合约的GetName方法返回的名称,如果这个名称也为空,则使用合约的类型名
ccn.info = contract.GetInfo()

if ccn.info.Version == "" {
ccn.info.Version = "latest"
}

if ccn.info.Title == "" {
ccn.info.Title = ns
}

contractType := reflect.PtrTo(reflect.TypeOf(contract).Elem())
contractValue := reflect.ValueOf(contract).Elem().Addr()
// returns the unknown function to be used for a contract.
// When the contract is used in creating a new chaincode this function is called
// and the unknown transaction returned is stored. The unknown function is then
// called in cases where an unknown function name is passed for a call to the
// contract via Init/Invoke of the chaincode. If nil is returned the
// chaincode uses its default handling for unknown function names
// 返回要用于合约的未知函数,当合约被用于创建一个新的链码时此方法被调用并且返回存储的未知的交易。
// 在链码通过Init/Invoke调用合约时,如果一个未知的方法名称被传入了则调用此未知方法
// 如果此方法返回nil。则chaincode使用一个默认值来处理未知的方法名
// 个人理解就是不指定方法名调用合约时的默认处理逻辑。
ut := contract.GetUnknownTransaction()

if ut != nil {
var err error
ccn.unknownTransaction, err = internal.NewTransactionHandler(ut, transactionContextPtrHandler, internal.TransactionHandlerTypeUnknown)

if err != nil {
return err
}
}
// returns the before function to be used for a contract.
// When the contract is used in creating a new chaincode this function is called
// and the before transaction returned is stored. The before function is then
// called before the named function on each Init/Invoke of that contract via the
// chaincode. When called the before function is passed no extra args, only the
// the transaction context (if specified to take it). If nil is returned
// then no before function is called on Init/Invoke.
// 返回需要对此合约使用的前置函数,当合约被用于创建一个新的链码时此方法被调用并且返回存储的前置交易。
// 然后通过链码在该合约使用Init/Invoke调用指定函数之前调用前置函数。
// 当调用before函数时,不传递任何额外的参数,仅传递事务上下文(如果指定使用它)。
// 如果此方法返回nil,则在调用Init/Invoke之前没有需要执行的函数。
bt := contract.GetBeforeTransaction()

if bt != nil {
var err error
ccn.beforeTransaction, err = internal.NewTransactionHandler(bt, transactionContextPtrHandler, internal.TransactionHandlerTypeBefore)

if err != nil {
return err
}
}

// 获取后置函数,在调用合约的指定方法之后调用此函数
at := contract.GetAfterTransaction()

if at != nil {
var err error
ccn.afterTransaction, err = internal.NewTransactionHandler(at, transactionContextPtrHandler, internal.TransactionHandlerTypeAfter)

if err != nil {
return err
}
}

evaluateMethods := []string{}
if eci, ok := contract.(EvaluationContractInterface); ok {
// returns a list of function names that should be tagged in the
// metadata as "evaluate" to indicate to a user of the chaincode that they should query
// rather than invoke these functions
// 返回应该在元数据上打上"求值"标签的方法列表,来向链码的用户展示他们应该查询这个函数而不是调用它
// 个人理解:所以这些函数应该只是计算一些数据的,不会对账本产生影响。如果对账本产生影响又打上这个标签是不是很流氓?
evaluateMethods = eci.GetEvaluateTransactions()
}
// 遍历合约中的所有的方法
for i := 0; i < contractType.NumMethod(); i++ {
typeMethod := contractType.Method(i)
valueMethod := contractValue.Method(i)
// 如果此方法不再排除的列表中,则可以调用
if !utils.StringInSlice(typeMethod.Name, excludeFuncs) {
var err error
// 默认的调用方式是CallTypeSubmit
var callType internal.CallType = internal.CallTypeSubmit
// 如果在只用于计算的函数列表中,则调用方法修改为CallTypeEvaluate;
// 两次调用StringInSlice会遍历啊两个列表,是不是可以用map把这里优化一下呢?
if utils.StringInSlice(typeMethod.Name, evaluateMethods) {
callType = internal.CallTypeEvaluate
}
// 创建合约的方法
ccn.functions[typeMethod.Name], err = internal.NewContractFunctionFromReflect(typeMethod, valueMethod, callType, transactionContextPtrHandler)

if err != nil {
return err
}
}
}
// 如果合约方法的列表为空,则报错,一个合约中至少有一个公共方法
if len(ccn.functions) == 0 {
return fmt.Errorf("Contracts are required to have at least 1 (non-ignored) public method. Contract %s has none. Method names that have been ignored: %s", ns, utils.SliceAsCommaSentence(excludeFuncs))
}

cc.contracts[ns] = ccn

// 如果链码的默认的合约为空,则把当前的合约设置为默认的合约;所以传入的第一个合约就是默认的合约
if cc.DefaultContract == "" {
cc.DefaultContract = ns
}

return nil
}
// 反射的方式获取元数据
func (cc *ContractChaincode) reflectMetadata() metadata.ContractChaincodeMetadata {
reflectedMetadata := metadata.ContractChaincodeMetadata{}
reflectedMetadata.Contracts = make(map[string]metadata.ContractMetadata)
reflectedMetadata.Components.Schemas = make(map[string]metadata.ObjectMetadata)
reflectedMetadata.Info = &cc.Info

if cc.Info.Version == "" {
reflectedMetadata.Info.Version = "latest"
}

if cc.Info.Title == "" {
reflectedMetadata.Info.Title = "undefined"
}
// 遍历链码的合约
for key, contract := range cc.contracts {
// 创建合约的元数据
contractMetadata := metadata.ContractMetadata{}
contractMetadata.Name = key
infoCopy := contract.info
contractMetadata.Info = &infoCopy
// 如果这个合约是默认的合约,元数据的字段也设置为true
if cc.DefaultContract == key {
contractMetadata.Default = true
}
// 遍历合约的所有方法,并创建方法的元数据,最后把这些方法的元数据加入到合约的元数据
for key, fn := range contract.functions {
fnMetadata := fn.ReflectMetadata(key, &reflectedMetadata.Components)

contractMetadata.Transactions = append(contractMetadata.Transactions, fnMetadata)
}

sort.Slice(contractMetadata.Transactions, func(i, j int) bool {
return contractMetadata.Transactions[i].Name < contractMetadata.Transactions[j].Name
})

reflectedMetadata.Contracts[key] = contractMetadata
}

return reflectedMetadata
}

func (cc *ContractChaincode) augmentMetadata() error {
// 读取元数据的文件,就是上面说的contract-metadata/metadata.json文件
fileMetadata, err := metadata.ReadMetadataFile()
// 如果报错了并且报错信息不是因为文件不存在的错误,则把错误抛出去
// 优化建议:这里能不能使用不同的error类型来做这种判断呢,这么判断如果error信息更改了就必须两个地方同时修改
if err != nil && !strings.Contains(err.Error(), "Failed to read metadata from file") {
return err
}

reflectedMetadata := cc.reflectMetadata()

fileMetadata.Append(reflectedMetadata)
err = fileMetadata.CompileSchemas()

if err != nil {
return err
}
// 验证
err = metadata.ValidateAgainstSchema(fileMetadata)

if err != nil {
return err
}

cc.metadata = fileMetadata

return nil
}

// getCiMethods 获取合约接口的方法描述的切片
func getCiMethods() []string {
// 通过反射的方式获取这个类型
contractInterfaceType := reflect.TypeOf((*ContractInterface)(nil)).Elem()
ignoreContractInterfaceType := reflect.TypeOf((*IgnoreContractInterface)(nil)).Elem()
evaluateContractInterfaceType := reflect.TypeOf((*EvaluationContractInterface)(nil)).Elem()

interfaceTypes := []reflect.Type{contractInterfaceType, ignoreContractInterfaceType, evaluateContractInterfaceType}

// 遍历这些反射的类型,把他们的方法描述添加到ciMethods切片中
var ciMethods []string
for _, interfaceType := range interfaceTypes {
for i := 0; i < interfaceType.NumMethod(); i++ {
ciMethods = append(ciMethods, interfaceType.Method(i).Name)
}
}

return ciMethods
}