0%

获取人类可读的区块内容分2步:

  1. 从账本里把区块取出来,格式为protobuf
  2. 把protobuf格式的区块,转换为JSON格式

所以这篇文章3步走:

  1. 获取区块
  2. 解析区块
  3. 常见区块类型样例

获取区块

拉取应用通道区块

peer channel fetch能够拉去某个通道最新、最老、特定高度的区块,只不过得到的区块是protobuf格式的,人眼不可读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@eedf1a41eb00:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel fetch -h
Fetch a specified block, writing it to a file.

Usage:
peer channel fetch <newest|oldest|config|(number)> [outputfile] [flags]

Flags:
--bestEffort Whether fetch requests should ignore errors and return blocks on a best effort basis
-c, --channelID string In case of a newChain command, the channel ID to create. It must be all lower case, less than 250 characters long and match the regular expression: [a-z][a-z0-9.-]*
-h, --help help for fetch

Global Flags:
--cafile string Path to file containing PEM-encoded trusted certificate(s) for the ordering endpoint
--certfile string Path to file containing PEM-encoded X509 public key to use for mutual TLS communication with the orderer endpoint
--clientauth Use mutual TLS when communicating with the orderer endpoint
--connTimeout duration Timeout for client to connect (default 3s)
--keyfile string Path to file containing PEM-encoded private key to use for mutual TLS communication with the orderer endpoint
-o, --orderer string Ordering service endpoint
--ordererTLSHostnameOverride string The hostname override to use when validating the TLS connection to the orderer.
--tls Use TLS when communicating with the orderer endpoint

以下命令下载了指定channel的第0个区块,即此channel的创世块,区块保存为mychannel.block。

1
peer channel fetch 0 mychannel.block  -c mychannel 

如果不指定保存的区块名,会自动生成,格式:通道名_区块号,比如:

1
peer channel fetch 3 -c mychannel

操作:

1
2
3
4
5
6
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel fetch 3 -c mychannel
2019-08-01 02:20:45.378 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-08-01 02:20:45.382 UTC [cli.common] readBlock -> INFO 002 Received block: 3
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto log.txt mychannel.block mychannel_3.block scripts

获取通道配置去块

如果使用peer channel fetch config可以获取某个通道的最新配置所在的区块。

1
peer channel fetch config -c mychannel 

命令执行结果的最后一行会显示配置所在的区块号,区块文件保存为mychannel_config.block

拉取系统通道区块

拉取系统通道通常是为了获取系统配置,系统配置存在系统区块中,下面就是获取系统区块的方法。

系统通道区块是保存在orderer节点上的,需要在获取的时候指定orderer配置和证书,然后使用config而不是区块号,直接获取最新的配置,创世块使用区块0获取。

  1. 连接到orderer节点,查询系统通道名称

    1
    ls /var/hyperledger/production/orderer/chains/
  2. cli上设置orderer的设置,设置上面查询的通道名字,获取的区块与通道同名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    export CHANNEL_NAME=byfn-sys-channel
    export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
    export CORE_PEER_ADDRESS=orderer.example.com:7050
    export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
    export CORE_PEER_LOCALMSPID="OrdererMSP"
    export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp
    export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

    peer channel fetch config $CHANNEL_NAME.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

以上命令执行结果的最后一行,会显示当前配置所在的区块高度。

解析工具

configtxlator是一个fabric中protbuf和JSON格式之间的转换工具,fabric中任何的使用Protobuf定义的类型,都可使用该工具进行转换。

解析示例

比如创建通道交易:channel.tx也是protobuf格式的,可以利用此工具解析:

1
configtxlator proto_decode  --type common.Envelope --input channel.tx
  • --type xxx:阅读源码,找出该proto格式数据对应的数据类型
  • --input xxx:proto格式数据文件

结果:

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
{
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": {
"name": "SampleConsortium"
},
"version": "0"
}
},
"version": "0"
}
},
"signatures": []
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T06:30:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": null
}
},
"signature": null
}

解析区块

使用configtxlator可以把区块从protobuf解析成JSON格式,

1
configtxlator proto_decode  --type common.Block --input mychannel.block > mychannel.block.json

结果见样例

拓展查询

Fabric提供了使用peer channel fetch获取区块的功能,但没有提供查询交易、db等信息的接口,那如何办?

获取交易,有方法:GetTransactionByID

获取DB,有方法:GetStateLevelDBData

如果结果是非结构体格式的[]byte或者string,可以直接组装成JSON格式,比如GetStateLevelDBDataGetTransactionByID是返回protobuf定义的结构体,可以直接使用configtxlator调用的接口DeepMarshalJSON把结构体转换为JSON字符串。

DeepMarshalJSON是tools protolator提供的一个接口,protolator这个工具是完成protobuf数据和JSON数据之间转换的实际工具。利用这些工具和简单的Web框架,可以搭建出查询通道、区块、交易、数据(KV)的简易网站。

常见区块样例

fabric里包含了2大类区块:

  1. 配置区块
  2. 普通区块

区块0是配置区块,又被称为创世块,后续对配置的每一次改动都会生成1个配置块存入所修改配置的通道以及系统通道。

系统通道创世块

主要是包含了一下配置:

  1. 组织
  2. 通道
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
685
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Consortiums": {
"groups": {
"SampleConsortium": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "/Channel/Orderer/Admins",
"policies": {},
"values": {
"ChannelCreationPolicy": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Admins"
}
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "/Channel/Orderer/Admins",
"policies": {
"Admins": {
"mod_policy": "/Channel/Orderer/Admins",
"policy": {
"type": 1,
"value": {
"identities": [],
"rule": {
"n_out_of": {
"n": 0,
"rules": []
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {},
"version": "0"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "0"
},
"last_update": null
},
"header": {
"channel_header": {
"channel_id": "byfn-sys-channel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:40Z",
"tls_cert_hash": null,
"tx_id": "1c988034ab1d1658ebe508f89acd5f1e4e7bc2eeb6355e9d6084c36fba8ea562",
"type": 1,
"version": 1
},
"signature_header": {
"creator": null,
"nonce": "vPlG4HADPlXSQNWhjggubdDnUWXuH+wP"
}
}
},
"signature": null
}]
},
"header": {
"data_hash": "uzdDxPqI54ONV6pv/4m/l2OXNBFKuprirariF2kW278=",
"number": "0",
"previous_hash": null
},
"metadata": {
"metadata": [
"",
"",
"",
""
]
}
}

应用通道创世块

系通道的配置的信息主要是:

  1. 组织关系,权限、证书
  2. 配置更新策略

某个链码的配置包含在链码里,不在通道的配置里。

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
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"Consortium": {
"mod_policy": "Admins",
"value": {
"name": "SampleConsortium"
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "1"
},
"last_update": {
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": {
"name": "SampleConsortium"
},
"version": "0"
}
},
"version": "0"
}
},
"signatures": [{
"signature": "MEQCIEuHMvFeH442rJZ8BURKXjseQ3MMpGO0VprPj8WdW5nOAiB1aDbHtIKo5ThxqvWCCJEdsXiKtAAbzN8DkE5VV4hUrg==",
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "Org1MSP"
},
"nonce": "d3VBJn3yTRNFp7JKJCL/XFObhMe3pS3W"
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:45Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "Org1MSP"
},
"nonce": "tGLvhwDbf/Py2be3k/WLsZzKRPcry9po"
}
}
},
"signature": "MEUCIQDmxLlWl9H6CdVqUbQ6OPmeZ1JnJYRYDtD18KQ1b82WkwIgNxFDuaYCdBs3ZzZxvsF2CD7pAtrbER4ZDOq3SkKie7E="
}
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:45Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 1,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "OrdererMSP"
},
"nonce": "xfIU/l1eFdFbtagz4fuZaJzuPW/hroRx"
}
}
},
"signature": "MEUCIQDm/oh3UE8/ZQu0Cad7rJw/FaUsCu4SsXZfxY3VSaMsCQIgbEsRitpgRb9vy80u9C7z103nj1/kQtka1LRiGSLy/6I="
}]
},
"header": {
"data_hash": "tJk5enGTIZYB/+XlYPbOwpjRH9341YajXd5YaWv86/k=",
"number": "0",
"previous_hash": null
},
"metadata": {
"metadata": [
"",
"",
"AA==",
""
]
}
}

应用通道配置块

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
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org1.example.com",
"port": 7051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "1"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org2.example.com",
"port": 9051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"Consortium": {
"mod_policy": "Admins",
"value": {
"name": "SampleConsortium"
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "3"
},
"last_update": {
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {
"Admins": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Readers": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Writers": {
"mod_policy": "",
"policy": null,
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {},
"values": {},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Readers": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Writers": {
"mod_policy": "",
"policy": null,
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org2.example.com",
"port": 9051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "Admins",
"policies": {},
"values": {},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"signatures": [{
"signature": "MEQCIG3LXxjcsZ+8jSCZEZbUMqWOslJGqFAzNz1zz/JlqssmAiBaO8xvVXT1vusIAQaLsldAXXU9np280n5UwYyJLsuYBw==",
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "InlUsKBi6xaHgTtivM0cOO5Vabiw6oSS"
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:19:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "5U3mT268xjTWiMCBXcO2+wEJcikv2YJj"
}
}
},
"signature": "MEUCIQCpsp/8oaQEmtp5baG8ogkrXgP+unRtBIQzLgsVf9AlbQIgLrQ6mG1TyxMEdFtL1rjaLLj9iL3A/qzFSh3jTiUH7ME="
}
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:19:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 1,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "OrdererMSP"
},
"nonce": "6oiXowzUUpo6JFlSyB50x1Ma12ZoFUba"
}
}
},
"signature": "MEQCIBNyEeC+cWzOCLq9J9haktAwM3e+BBjNeEQc55xiB2yPAiAIF+KkWEh+RARJM/iA15JSfgvCP9zwgIrvSxO/Ki/KQQ=="
}]
},
"header": {
"data_hash": "35xdxe58OecLRLqrdJREGDaw+XEVSDFvN4Vk8v/GNPA=",
"number": "2",
"previous_hash": "6xJ0rP3gVYFZpwcBDW/i84IIkTi8Jj0cWBeX2GXsCUQ="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhig4rR9Ua6PViqus4vI1MI7T5OdGzlRTykSRjBEAiBI+JDzNh3q5fzUahojTbQkWxVBf+QksfhsRNdTtEVYfgIgJqbdOquXXkWQ8kB7xKjub/sfbX1Cnm2Vo2uHlSZAIG4=",
"CgIIAg==",
"AA==",
""
]
}
}

应用通道普通区块

普通区块指不修改配置的区块,里面包含2类交易:

  1. 实例化链码的交易
  2. 调用链码的交易

实例化实际Invoke的是LSCC,调用应用chaincode,也会先用到LSCC,再到应用chaincode,那么怎么区分当前到底是Invoke哪个chaincode?区块中的chaincode_proposal_payload,代表了当前操作的chaincode。

ns_rwset是操作过程的读写集,包含LSCC和应用chaincode的。

实例化chaincode

ns_rwset包含2个collection_hashed_rwset

  • 第1个:说明了当前操作的是lscc,读mycc没结果,然后把mycc放到写集,这是创建合约的典型过程。
  • 第2个:是进行chaincode的初始化,也是只写入,这里是设置a和b的值。
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
{
"data": {
"data": [{
"payload": {
"data": {
"actions": [{
"header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "CLJ2euIe3YSE7LhNiTuXdBCS2DnvabH2"
},
"payload": {
"action": {
"endorsements": [{
"endorser": "CgdPcmcyTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVHA5QTREM25oa2NJaVdXRk4xRUZQakFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVSRjBFV0NuQW5oMm8KcnFFazhmRTlLdEFCdmJIc1FYTHhMRnZZdlZYVlFPS1crVDBpVk44eWdQbTZlM0kxcG5FTS9hK3Vha2dtYWNmOApzVnVZVERMSythTk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnVTMwdllnVHpCYS84ZGFaZlpBYThzLzN3RFdZdlRpL1BEY3RuWFJBT043QUNJQmNvQzlXSgpYK28rdHFUUDZybS8vdWxpUU02Rk53cUtBSDlYQWxOcVdhbWsKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEUCIQCRu+F6XBVrfy24JtxuAiy9+3PNYy6tRmKDmgtmGx0OLAIgQeMPTKzPhh9fH39tmZEDjJCGet/ob2BzbRExZIYq2po="
}],
"proposal_response_payload": {
"extension": {
"chaincode_id": {
"name": "lscc",
"path": "",
"version": "1.4.2"
},
"events": null,
"response": {
"message": "",
"payload": "CgRteWNjEgMxLjAaBGVzY2MiBHZzY2MqLBIMEgoIAhICCAASAggBGg0SCwoHT3JnMU1TUBADGg0SCwoHT3JnMk1TUBADMkQKIJJH+4JOIdCHD/55PPBgJOZehvHg5Ytk6QxbGU9uNzQAEiAHHxQpHhUrsq6I5/355ad00xNha6MVSoT2oNRaF19jJjogR2/KGpSSdAAZcfHsKDbLCTIfC3Emizdi1okxyT8hgTRCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBAB",
"status": 200
},
"results": {
"data_model": "KV",
"ns_rwset": [{
"collection_hashed_rwset": [],
"namespace": "lscc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "mycc",
"version": null
}],
"writes": [{
"is_delete": false,
"key": "mycc",
"value": "CgRteWNjEgMxLjAaBGVzY2MiBHZzY2MqLBIMEgoIAhICCAASAggBGg0SCwoHT3JnMU1TUBADGg0SCwoHT3JnMk1TUBADMkQKIJJH+4JOIdCHD/55PPBgJOZehvHg5Ytk6QxbGU9uNzQAEiAHHxQpHhUrsq6I5/355ad00xNha6MVSoT2oNRaF19jJjogR2/KGpSSdAAZcfHsKDbLCTIfC3Emizdi1okxyT8hgTRCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBAB"
}]
}
},
{
"collection_hashed_rwset": [],
"namespace": "mycc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [],
"writes": [{
"is_delete": false,
"key": "a",
"value": "MTAw"
},
{
"is_delete": false,
"key": "b",
"value": "MjAw"
}
]
}
}
]
},
"token_expectation": null
},
"proposal_hash": "QFsKOSAIcsdRQ/3UYkYrUuK36Apj4iulCKW/LKmtAAs="
}
},
"chaincode_proposal_payload": {
"TransientMap": {},
"input": {
"chaincode_spec": {
"chaincode_id": {
"name": "lscc",
"path": "",
"version": ""
},
"input": {
"args": [
"ZGVwbG95",
"bXljaGFubmVs",
"CicIARILEgRteWNjGgMxLjAaFgoEaW5pdAoBYQoDMTAwCgFiCgMyMDA=",
"EgwSCggCEgIIABICCAEaDRILCgdPcmcxTVNQEAMaDRILCgdPcmcyTVNQEAM=",
"ZXNjYw==",
"dnNjYw=="
],
"decorations": {}
},
"timeout": 0,
"type": "GOLANG"
}
}
}
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": "EgYSBGxzY2M=",
"timestamp": "2019-08-01T02:19:04.887742126Z",
"tls_cert_hash": null,
"tx_id": "91c8e4c4e6c91db19eb8e40f95e2b29cea3d916b2a3249459b9fa83b44fc445a",
"type": 3,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "CLJ2euIe3YSE7LhNiTuXdBCS2DnvabH2"
}
}
},
"signature": "MEUCIQD0jJ2zDEbsKIi8ekG3E5xF//EddpMJdKL5nF0anvHp7QIgTNsEJk3N344hq4fklJn/c0fVojzeos4t4w17r7VOJSg="
}]
},
"header": {
"data_hash": "w8fXJmELmqvXJAWXPge6oCH0SxwoR4nube3o//fYz4A=",
"number": "3",
"previous_hash": "qpUWyIdR36SuQa/xqGEnwm1wjpaQxIeU8aaA8RFeN/8="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhjerftAXzK8sFOlQ17D0d7A0oP73xf29PkSRjBEAiAp7vqkuMOhC4XzOjwov/HlydZHpMgKuVfeBITm8T752AIgDl9dQHLeaLXEmjFXgv+38H1tFgv/ohHNxdraFexmlrg=",
"CgIIAg==",
"AA==",
""
]
}
}

调用chaincode

ns_rwset包含2个collection_hashed_rwset

  • 第1个:说明了当前操作的是lscc,从区块3里把mycc读取出来。
  • 第2个:说明操作的是mycc,先把a和b读取出来,然后又把a和b的结果写回mycc。
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
{
"data": {
"data": [{
"payload": {
"data": {
"actions": [{
"header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "6Z4RNNIV085Nv/pOYUc5W3EaWcBueAAs"
},
"payload": {
"action": {
"endorsements": [{
"endorser": "CgdPcmcxTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVTlaQzNZWEVySzEybHlSRmx2VW1DREFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN4CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVGdnVqVG5lMXYyRk4KVENxOTI4WlZrSlhheXRNbVlZcnJ2dndoMEV4b1VVZU1yRjRueU9hZjBRcUo4NjZKeTNibFNQL0xDZFJqOElzdwprdzZmNFBDdEE2Tk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnVkJwVFZxMVBIblR6b3lmdGh1OTN0a2tBNytWN1ZOeUgvaU5nOW8vRklCQXdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnSCtpTHF0THQ4WGYwSzNSVHNZUVJLNFIyY05hMXNSV3BVd05QYkxEdzFZd0NJRTg4c2kyTgprZkMvTzB6SkhDandhdFR1aDFVbEt4eFc4OU16aWx3aXJjWnEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEQCIEnnXFAiBoxqlqgTRs9aBW5PWv+uyjHEEi1XGWXbX0aaAiBEteVfQu7352r5zokdeKdG5+K5LF+IiAbR+QOomOjF1Q=="
},
{
"endorser": "CgdPcmcyTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVHA5QTREM25oa2NJaVdXRk4xRUZQakFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVSRjBFV0NuQW5oMm8KcnFFazhmRTlLdEFCdmJIc1FYTHhMRnZZdlZYVlFPS1crVDBpVk44eWdQbTZlM0kxcG5FTS9hK3Vha2dtYWNmOApzVnVZVERMSythTk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnVTMwdllnVHpCYS84ZGFaZlpBYThzLzN3RFdZdlRpL1BEY3RuWFJBT043QUNJQmNvQzlXSgpYK28rdHFUUDZybS8vdWxpUU02Rk53cUtBSDlYQWxOcVdhbWsKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEUCIQCKz1zIddnNjjoGnzSTuxvVy19TvQSlONS4C9Y3iaJhaAIgY+Zpl003I0X/FsH8+ekifXvugeHCPeZ3UhkO90MWg3U="
}
],
"proposal_response_payload": {
"extension": {
"chaincode_id": {
"name": "mycc",
"path": "",
"version": "1.0"
},
"events": null,
"response": {
"message": "",
"payload": null,
"status": 200
},
"results": {
"data_model": "KV",
"ns_rwset": [{
"collection_hashed_rwset": [],
"namespace": "lscc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "mycc",
"version": {
"block_num": "3",
"tx_num": "0"
}
}],
"writes": []
}
},
{
"collection_hashed_rwset": [],
"namespace": "mycc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "a",
"version": {
"block_num": "3",
"tx_num": "0"
}
},
{
"key": "b",
"version": {
"block_num": "3",
"tx_num": "0"
}
}
],
"writes": [{
"is_delete": false,
"key": "a",
"value": "OTA="
},
{
"is_delete": false,
"key": "b",
"value": "MjEw"
}
]
}
}
]
},
"token_expectation": null
},
"proposal_hash": "okKS/G4+W9VT9+t6VXJnAClnPvOhtUkiCdqjPmzkqjI="
}
},
"chaincode_proposal_payload": {
"TransientMap": {},
"input": {
"chaincode_spec": {
"chaincode_id": {
"name": "mycc",
"path": "",
"version": ""
},
"input": {
"args": [
"aW52b2tl",
"YQ==",
"Yg==",
"MTA="
],
"decorations": {}
},
"timeout": 0,
"type": "GOLANG"
}
}
}
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": "EgYSBG15Y2M=",
"timestamp": "2019-08-01T02:19:38.504172871Z",
"tls_cert_hash": null,
"tx_id": "2377b308f2ea91e58d1b21d71b09370dc3c39bab3b92d2c0bc40801843150d6b",
"type": 3,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "6Z4RNNIV085Nv/pOYUc5W3EaWcBueAAs"
}
}
},
"signature": "MEUCIQD5A7tvxqe4q/l+D1KOpDDaTZ5DdpsLs+xTA1CqsR+nBAIgSo+cFAzBBDfRJK8bcV6khmqWuhATKHm/7uiJiDkDjKI="
}]
},
"header": {
"data_hash": "9Q8SRPt+L2bvRGXAteiCXOkiutLu1nZBiCCaiwAlFdk=",
"number": "4",
"previous_hash": "vE7W/Qond3H6koHXpoWm2dfcZfZnmMYwF5lfNj6hDlY="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhhh6UhNerUcr/HbaA9SbvQ3xInOkYF9oKoSRjBEAiAwGjDnt+7TPrPz7fhm/63rUCFlJYzgRXdPautelXme3gIgUXb8ynZgf2rfIA8bL+y992HIgjtDCBppydWmPmXD9g4=",
"CgIIAg==",
"AA==",
""
]
}
}

这篇文章介绍了如何快速的搭建一个fabric网络,然后又把搭建过程分解,针对每一步都做详细解释,希望你能熟练今后到不看文档也能搭建出fabric网络。

本文是Building Your First Network的笔记和实践记录,基于Fabric 1.4,commit id:9dce73。

前提:

  1. 安装了Docker、Go等环境。
  2. 已经下载了fabric仓库,完成make all

下载fabric-samples和准备工作

有2种方式。

方式1:一键下载和编译。

1
curl -sSL http://bit.ly/2ysbOFE | bash -s

方式2:手动clone,放到GOPATH下,然后执行脚本,构建和拉去一些镜像,为搭建网络做准备。方式2只不过是把方式1的工作,手动做掉了。

1
2
3
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples
sh scripts/bootstrap.sh

参考资料:https://hyperledger-fabric.readthedocs.io/en/release-1.4/install.html。

快速启动你的第一个Fabric网络

这一节的目的是用几分钟的时间启动一个网络,并且了解启动一个网络的过程。

启动网络

fabric-samples下有多个示例,本次要使用的是first-network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  fabric-samples git:(release-1.4) ll | grep ^d
drwxr-xr-x 5 centos centos 193 7月 12 08:21 balance-transfer
drwxr-xr-x 4 centos centos 273 7月 12 08:21 basic-network
drwxrwxr-x 2 centos centos 175 1月 9 2019 bin
drwxr-xr-x 8 centos centos 113 7月 12 08:21 chaincode
drwxr-xr-x 3 centos centos 139 7月 12 08:21 chaincode-docker-devmode
drwxr-xr-x 3 centos centos 44 7月 12 06:47 commercial-paper
drwxrwxr-x 2 centos centos 64 1月 9 2019 config
drwxr-xr-x 2 centos centos 59 7月 12 08:21 docs
drwxr-xr-x 5 centos centos 110 7月 12 08:21 fabcar
drwxr-xr-x 7 centos centos 4.0K 7月 17 03:14 first-network
drwxr-xr-x 4 centos centos 55 7月 12 08:21 high-throughput
drwxr-xr-x 4 centos centos 55 7月 12 08:21 interest_rate_swaps
drwxr-xr-x 4 centos centos 67 7月 17 03:46 scripts

进入first-network然后执行./byfn.sh up,启动操作会持续两三分钟,byfn是Building Your First Network的缩写。

启动过程实际做了这些事:

第一阶段:生成配置文件

  1. 使用加密工具cryptogen生成证书
  2. 使用工具configtxgen生成orderer节点的创世块,即得到genesis.block
  3. 使用工具configtxgen生成配置应用通道channel的交易channel.tx,即得到mychannel.block
  4. 使用工具configtxgen生成Org1的MSP的anchor peer
  5. 使用工具configtxgen生成Org2的MSP的anchor peer

第二阶段:启动网络

这个阶段是启动容器,包含客户端(cli)、peer,每个org有2个peer,peer0和peer1,默认是solo共识算法,还会启动1个orderer。

第三阶段:创建和加入通道,部署和测试链码

  1. 创建应用通道mychannel
  2. peer加入mychannel
  3. 在mychannel上更新Org1和Org2 MSP的anchor peer
  4. 在ogr1好org2的peer0上安装chaincode
  5. 在mychannel中,在peer0.org2上实例化chaincode,1个通道上只需示例化1次chaincode
  6. 在mychannel中,Invoke刚实例化的chaincode
  7. 在peer1.org2上安装chaincode,并查询

byfn.sh中的networkUp()函数是./byfn.sh up的主要执行函数,它的主要功能就是组织上面3个阶段。

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
# Generate the needed certificates, the genesis block and start the network.
function networkUp() {
checkPrereqs
# 生成配置:证书、交易、私钥
# generate artifacts if they don't exist
if [ ! -d "crypto-config" ]; then
generateCerts
replacePrivateKey
generateChannelArtifacts
fi
# 启动网络/容器
COMPOSE_FILES="-f ${COMPOSE_FILE}"
if [ "${CERTIFICATE_AUTHORITIES}" == "true" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_CA}"
export BYFN_CA1_PRIVATE_KEY=$(cd crypto-config/peerOrganizations/org1.example.com/ca && ls *_sk)
export BYFN_CA2_PRIVATE_KEY=$(cd crypto-config/peerOrganizations/org2.example.com/ca && ls *_sk)
fi
if [ "${CONSENSUS_TYPE}" == "kafka" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_KAFKA}"
elif [ "${CONSENSUS_TYPE}" == "etcdraft" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_RAFT2}"
fi
if [ "${IF_COUCHDB}" == "couchdb" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}"
fi
IMAGE_TAG=$IMAGETAG docker-compose ${COMPOSE_FILES} up -d 2>&1
# 检查容器是否启动
docker ps -a
if [ $? -ne 0 ]; then
echo "ERROR !!!! Unable to start network"
exit 1
fi

if [ "$CONSENSUS_TYPE" == "kafka" ]; then
sleep 1
echo "Sleeping 10s to allow $CONSENSUS_TYPE cluster to complete booting"
sleep 9
fi

if [ "$CONSENSUS_TYPE" == "etcdraft" ]; then
sleep 1
echo "Sleeping 15s to allow $CONSENSUS_TYPE cluster to complete booting"
sleep 14
fi

# 执行端到端脚本:创建并加入应用通道,然后测试
# now run the end to end script
docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT $VERBOSE $NO_CHAINCODE
if [ $? -ne 0 ]; then
echo "ERROR !!!! Test failed"
exit 1
fi
}

启动日志,日志中标记了各阶段,建议详读一下:

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
$ cd first-network
➜ first-network git:(release-1.4) ./byfn.sh up
Starting for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds
Continue? [Y/n] y
proceeding ...
LOCAL_VERSION=1.4.0
DOCKER_IMAGE_VERSION=1.4.0
/home/centos/go/src/github.com/hyperledger/fabric-samples/bin/cryptogen

/**** 第1阶段:生成配置文件 ****/

##########################################################
##### Generate certificates using cryptogen tool #########
##########################################################
+ cryptogen generate --config=./crypto-config.yaml
org1.example.com
org2.example.com
+ res=0
+ set +x

/home/centos/go/src/github.com/hyperledger/fabric-samples/bin/configtxgen
##########################################################
######### Generating Orderer Genesis block ##############
##########################################################
CONSENSUS_TYPE=solo
+ '[' solo == solo ']'
+ configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-17 06:34:26.973 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.088 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: solo
2019-07-17 06:34:27.088 UTC [common.tools.configtxgen.localconfig] Load -> INFO 003 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.186 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 004 orderer type: solo
2019-07-17 06:34:27.186 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 005 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.188 UTC [common.tools.configtxgen] doOutputBlock -> INFO 006 Generating genesis block
2019-07-17 06:34:27.189 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Writing genesis block
+ res=0
+ set +x

#################################################################
### Generating channel configuration transaction 'channel.tx' ###
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
2019-07-17 06:34:27.228 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.315 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 005 Generating new channel configtx
2019-07-17 06:34:27.425 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 006 Writing new channel tx
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org1MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
2019-07-17 06:34:27.477 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.559 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org2MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
2019-07-17 06:34:27.689 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.773 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x



/**** 第2阶段:启动容器网络 ****/


Creating network "net_byfn" with the default driver
Creating volume "net_orderer.example.com" with default driver
Creating volume "net_peer0.org1.example.com" with default driver
Creating volume "net_peer1.org1.example.com" with default driver
Creating volume "net_peer0.org2.example.com" with default driver
Creating volume "net_peer1.org2.example.com" with default driver
Creating orderer.example.com ... done
Creating peer0.org2.example.com ... done
Creating peer1.org2.example.com ... done
Creating peer0.org1.example.com ... done
Creating peer1.org1.example.com ... done
Creating cli ... done
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8c2ccb5ee443 hyperledger/fabric-tools:latest "/bin/bash" Less than a second ago Up Less than a second cli
5af5a3fb3bb7 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:8051->8051/tcp peer1.org1.example.com
396b363bb6f5 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:7051->7051/tcp peer0.org1.example.com
94be2011d20f hyperledger/fabric-orderer:latest "orderer" 2 seconds ago Up Less than a second 0.0.0.0:7050->7050/tcp orderer.example.com
da8c17df215d hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:9051->9051/tcp peer0.org2.example.com
fcd30620e876 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:10051->10051/tcp peer1.org2.example.com
10510312db61 dc535406-4013-4141-be9e-e472c1cf24a1-simple-5e32b897538246406863e63956e4c561246725b6fbf114a1fedff16775cf782d "tail -f /dev/null" 22 hours ago Exited (137) 22 hours ago dc535406-4013-4141-be9e-e472c1cf24a1-simple
21a3b8dc137a hyperledger/fabric-buildenv:amd64-latest "/bin/bash" 22 hours ago Exited (0) 22 hours ago musing_swartz
48407948b7d7 hyperledger/fabric-buildenv "/bin/bash" 22 hours ago Exited (130) 22 hours ago affectionate_curie
358f0c0de3e1 92b20cd39f98 "/bin/bash" 23 hours ago Exited (130) 22 hours ago festive_clarke
de5938eccc11 92b20cd39f98 "./scripts/check_dep…" 23 hours ago Exited (127) 23 hours ago quizzical_einstein
324b27de3a34 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago amazing_booth
26a53801f203 92b20cd39f98 "./scripts/check_dep…" 23 hours ago Exited (127) 23 hours ago jolly_saha
94a33ddda70a 92b20cd39f98 "./scripts/golinter.…" 23 hours ago Exited (0) 23 hours ago peaceful_allen
6a0c7fded448 92b20cd39f98 "./scripts/golinter.…" 23 hours ago Created recursing_beaver
233496b4065c 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago wizardly_shockley
f0a255a96610 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago jolly_ganguly
664416bc4fee 965663acb7cf "/bin/sh -c 'apt-get…" 24 hours ago Exited (100) 24 hours ago nostalgic_chaplygin
51cf784ef4e1 ba82c6de-50fb-4ffd-989d-0dcf54e14e3b-simple-9961bcae6dad48592af2e9f1c1df3c96b568f9394ec82b2f351e79fa51a4f786 "tail -f /dev/null" 47 hours ago Exited (137) 47 hours ago ba82c6de-50fb-4ffd-989d-0dcf54e14e3b-simple
b7642b085ac1 14669948-7a23-4b3b-aa14-8b0622986e03-simple-f8a7b2e1352d04d884580725c2be9b642dd29df7e3e095a4a9403ac789dde2ac "tail -f /dev/null" 2 days ago Exited (137) 2 days ago 14669948-7a23-4b3b-aa14-8b0622986e03-simple

/**** 第3阶段:创建并加入应用通道,然后测试 ****/

____ _____ _ ____ _____
/ ___| |_ _| / \ | _ \ |_ _|
\___ \ | | / _ \ | |_) | | |
___) | | | / ___ \ | _ < | |
|____/ |_| /_/ \_\ |_| \_\ |_|

Build your first network (BYFN) end-to-end test

Channel name : mychannel
Creating channel...
+ peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:32.113 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:32.190 UTC [cli.common] readBlock -> INFO 002 Received block: 0
===================== Channel 'mychannel' created =====================

Having all peers join the channel...
+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:32.272 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:32.338 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org1 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:35.449 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:35.536 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org1 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:38.617 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:38.673 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org2 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:41.755 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:41.837 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org2 joined channel 'mychannel' =====================

Updating anchor peers for org1...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:44.930 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:44.951 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org1MSP' on channel 'mychannel' =====================

Updating anchor peers for org2...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:48.037 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:48.059 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org2MSP' on channel 'mychannel' =====================

Installing chaincode on peer0.org1...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:34:51.167 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.167 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:34:51.462 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer0.org1 =====================

Install chaincode on peer0.org2...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:34:51.542 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.542 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:34:51.817 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer0.org2 =====================

Instantiating chaincode on peer0.org2...
+ peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'
+ res=0
+ set +x
2019-07-17 06:34:51.910 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.910 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
===================== Chaincode is instantiated on peer0.org2 on channel 'mychannel' =====================

Querying chaincode on peer0.org1...
===================== Querying on peer0.org1 on channel 'mychannel'... =====================
+ peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
Attempting to Query peer0.org1 ...3 secs
+ res=0
+ set +x

100
===================== Query successful on peer0.org1 on channel 'mychannel' =====================
Sending invoke transaction on peer0.org1 peer0.org2...
+ peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
+ res=0
+ set +x
2019-07-17 06:35:27.719 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
===================== Invoke transaction successful on peer0.org1 peer0.org2 on channel 'mychannel' =====================

Installing chaincode on peer1.org2...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:35:27.809 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:35:27.809 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:35:28.060 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer1.org2 =====================

Querying chaincode on peer1.org2...
===================== Querying on peer1.org2 on channel 'mychannel'... =====================
+ peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
Attempting to Query peer1.org2 ...3 secs
+ res=0
+ set +x

90
===================== Query successful on peer1.org2 on channel 'mychannel' =====================

========= All GOOD, BYFN execution completed ===========


_____ _ _ ____
| ____| | \ | | | _ \
| _| | \| | | | | |
| |___ | |\ | | |_| |
|_____| |_| \_| |____/

使用docker查看起来的服务:

1
2
3
4
5
6
7
8
9
10
11
➜  first-network git:(release-1.4) docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fe690a4f3e9f dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer1.org2.example.com-mycc-1.0
03a5f82384a0 dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9 "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer0.org1.example.com-mycc-1.0
a737b47e9de6 dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer0.org2.example.com-mycc-1.0
8c2ccb5ee443 hyperledger/fabric-tools:latest "/bin/bash" 2 hours ago Up 2 hours cli
5af5a3fb3bb7 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:8051->8051/tcp peer1.org1.example.com
396b363bb6f5 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:7051->7051/tcp peer0.org1.example.com
94be2011d20f hyperledger/fabric-orderer:latest "orderer" 2 hours ago Up 2 hours 0.0.0.0:7050->7050/tcp orderer.example.com
da8c17df215d hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:9051->9051/tcp peer0.org2.example.com
fcd30620e876 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:10051->10051/tcp peer1.org2.example.com

上面有3个dev-peer*.org*.example.com-mycc-1.0容器,它们是链码容器,每一个安装过链码的peer都会创建一个属于自己的链码容器,在调用链码的时候,peer会通过gRPC和自己的链码容器通信。

关闭网络

使用./byfn.sh down命令,关闭first-network

  1. 依次停止channel、客户端、orderer、peer
  2. 删除cli、orderer、peer、netowrk
  3. 删除docker镜像

./byfn.sh up失败时,也需要使用此命令清理数据,以免后面启动网络时出问题。

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
➜  first-network git:(release-1.4) ./byfn.sh down
Stopping for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds
Continue? [Y/n] y
proceeding ...
WARNING: The BYFN_CA1_PRIVATE_KEY variable is not set. Defaulting to a blank string.
WARNING: The BYFN_CA2_PRIVATE_KEY variable is not set. Defaulting to a blank string.
Stopping cli ... done
Stopping orderer.example.com ... done
Stopping peer1.org1.example.com ... done
Stopping peer0.org1.example.com ... done
Stopping peer0.org2.example.com ... done
Stopping peer1.org2.example.com ... done
Removing cli ... done
Removing orderer.example.com ... done
Removing peer1.org1.example.com ... done
Removing peer0.org1.example.com ... done
Removing peer0.org2.example.com ... done
Removing peer1.org2.example.com ... done
Removing network net_byfn
Removing volume net_orderer.example.com
Removing volume net_peer0.org1.example.com
Removing volume net_peer1.org1.example.com
Removing volume net_peer0.org2.example.com
Removing volume net_peer1.org2.example.com
Removing volume net_orderer2.example.com
WARNING: Volume net_orderer2.example.com not found.
Removing volume net_orderer3.example.com
WARNING: Volume net_orderer3.example.com not found.
Removing volume net_orderer4.example.com
WARNING: Volume net_orderer4.example.com not found.
Removing volume net_orderer5.example.com
WARNING: Volume net_orderer5.example.com not found.
Removing volume net_peer0.org3.example.com
WARNING: Volume net_peer0.org3.example.com not found.
Removing volume net_peer1.org3.example.com
WARNING: Volume net_peer1.org3.example.com not found.
05c281ff186c
f3ccbe5e2b80
7f0144ca0eae
Untagged: dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab:latest
Deleted: sha256:9425c8298cafe082ed22c5968d431a6098d53ef2318fb5d286efb96b4bc44915
Deleted: sha256:9005a0d9f52947d9256aa4766d4c26a9bab98f229aab7f2598da05789fc977ef
Deleted: sha256:98602d24729b179952f685f8f83f1effaf3733e7f93354a9d31b15f711bc0fac
Deleted: sha256:fe2b67155487d7e001c8a0b2ef100bb710b1b816897bc9d2a80029f4c7bd0b54
Untagged: dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9:latest
Deleted: sha256:0759f367d73c68e71b6077ebd46611d43a8d9c1c9ebc398b838010268b175d65
Deleted: sha256:2d56a884d5514a4467471cf06b42c0cfa492a80a239d48f79fa48273982d81b7
Deleted: sha256:614c6a2a164cc8afbb7f348fdf6d048834dc0cb2a94a22638b8d4dcd72eaeb14
Deleted: sha256:9a39bc364e8d141bdab60a80946e4af10513cb070c34e4bda1b1cbbf88f9dca3
Untagged: dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b:latest
Deleted: sha256:63a4ecd2677f62197f547b1cef9041e3f3ad5c929b1dcd139610b106862a92b5
Deleted: sha256:6a30b775f40f687d0ed98fe9d5fdd2da60ce22753b066db599e4308303f16c13
Deleted: sha256:d94c0213bc68b7ee8dfa942c82c07cf8d8b3e7a4d68cf5ca79d372557e5f6567
Deleted: sha256:c720d5e3b8c3b5690da0b10588517592ca11d94b3427cf75e88be0e000352ec9

部署网络步骤详解

准备工作

  1. 以下用到的工具都在fabric-samples/bin目录下,手动执行记得把该目录添加到PATH。
  2. 这些目录下的工具必须是跟当前fabirc项目是版本匹配的,最好把fabric编译生成的工具fabric/build/bin/*,拷贝到fabric-samples/bin目录。
  3. 执行./byfn.sh down清理掉之前启动的数据,不然可能造成错误。

生成证书

byfn.sh中能找到生成证书的命令,手动执行命令可以生成证书,生成的证书在crypto-config目录下。

crypto-config.yaml是证书的配置文件。ordererOrganizations是系统通道组织,peerOrganizations是应用通道组织,包含2个组织:org1.example.comorg2.example.com

各组织的子目录是:证书、MSP、私钥、TLS证书、以及组织下的用户,用户目录会包含所有用户,以及用户的各种证书。

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
➜  first-network git:(release-1.4) cryptogen generate --config=./crypto-config.yaml
org1.example.com
org2.example.com
➜ first-network git:(release-1.4) tree crypto-config -L 3
crypto-config
├── ordererOrganizations
│   └── example.com
│   ├── ca
│   ├── msp
│   ├── orderers
│   ├── tlsca
│   └── users
└── peerOrganizations
├── org1.example.com
│   ├── ca
│   ├── msp
│   ├── peers
│   ├── tlsca
│   └── users
└── org2.example.com
├── ca
├── msp
├── peers
├── tlsca
└── users

20 directories, 0 files

生成创世块

系统通道保存的链是系统链,链上的区块都是配置信息,它的第一个区块,被称为创世块genesis.block,用来初始化系统链。

生成创世块的工具是configtxgen,会自动在执行目录下寻找configtx.yaml文件,该文件包含了网络的初始配置,使用-profile指定系统链的配置TwoOrgsOrdererGenesis,该变量定义在configtx.yaml中。

使用-outputBlock指定输出的创世块文件。

命令:

1
configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-29 07:17:02.140 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:17:02.229 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: solo
2019-07-29 07:17:02.229 UTC [common.tools.configtxgen.localconfig] Load -> INFO 003 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:17:02.311 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 004 orderer type: solo
2019-07-29 07:17:02.311 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 005 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:17:02.313 UTC [common.tools.configtxgen] doOutputBlock -> INFO 006 Generating genesis block
2019-07-29 07:17:02.313 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Writing genesis block
➜ first-network git:(release-1.4) ls channel-artifacts
genesis.block

以上命令是采用Solo共识算法创世块,如果使用Raft需要使用-profile SampleMultiNodeEtcdRaft选项:

命令:

1
configtxgen -profile SampleMultiNodeEtcdRaft -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block

记录:

1
2
3
4
5
6
7
8
9
➜  first-network git:(release-1.4) configtxgen -profile SampleMultiNodeEtcdRaft -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-29 08:44:18.348 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: etcdraft
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 Orderer.EtcdRaft.Options unset, setting to tick_interval:"500ms" election_tick:10 heartbeat_tick:1 max_inflight_blocks:5 snapshot_interval_size:20971520
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] Load -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 08:44:18.552 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 005 orderer type: solo
2019-07-29 08:44:18.553 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 006 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 08:44:18.558 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Generating genesis block
2019-07-29 08:44:18.559 UTC [common.tools.configtxgen] doOutputBlock -> INFO 008 Writing genesis block

生成创建应用通道的交易

网络启动后,只有1个系统通道,应用通道需要通过交易生成,这个交易(channel.tx)需要使用configtxgen工具创建,具体命令如下,-outputCreateChannelTx说明了是要生成创建应用通道的交易。

命令:

1
configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
2019-07-29 07:26:03.976 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:26:04.072 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 005 Generating new channel configtx
2019-07-29 07:26:04.172 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 006 Writing new channel tx
➜ first-network git:(release-1.4)
➜ first-network git:(release-1.4) ls channel-artifacts
channel.tx genesis.block

生成更新组织1的锚节点交易

组织节点加入到应用通道后,需要更新系统配置,把组织1的锚节点写入到配置块,这个也需要通过1笔交易完成。工具依然是configtxgen-outputAnchorPeersUpdate表明了这是生成更新组织锚节点配置交易的操作。

命令:

1
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
2019-07-29 07:30:26.456 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:30:26.557 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-29 07:30:26.641 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
➜ first-network git:(release-1.4)
➜ first-network git:(release-1.4) ls channel-artifacts
channel.tx genesis.block Org1MSPanchors.tx

生成更新组织2的锚节点交易

与上面类似,这是生成组织2锚节点配置的交易。

命令:

1
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP

记录:

1
2
3
4
5
6
7
8
9
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
2019-07-29 07:32:45.446 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:32:45.567 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
➜ first-network git:(release-1.4) ls ./channel-artifacts/
channel.tx genesis.block Org1MSPanchors.tx Org2MSPanchors.tx

启动网络

启动网络涉及到docker容器的创建与启动,这部分不挨个手动执行,使用byfn.sh完成,这样可以完成fabric网络的启动,系统通道的启动也在这个阶段完成。

byfn.sh利用scripts/script.sh完成的从创建应用通道到调研合约、查询合约的过程,这部分继续手动执行,需要注释掉byfn.sh中的下面这行:

1
docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT $VERBOSE $NO_CHAINCODE

然后执行:

1
./byfn.sh up

这样启动的是solo共识算法的网络,我启动的是raft共识的网络:

1
./byfn.sh up -o etcdraft

如果是solo,启动起来的容器与快速启动你的第一个Fabric网络中的类似,只不过缺少3个链码容器。如果是raft应当是下面这样:

  • 5个orderer节点,
  • 4个peer节点
  • 1个cli客户端
1
2
3
4
5
6
7
8
9
10
11
12
➜  first-network git:(r1.4-raft) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fc0891e02afd hyperledger/fabric-tools:latest "/bin/bash" 26 seconds ago Up 25 seconds cli
9363a51d3f68 hyperledger/fabric-orderer:latest "orderer" 29 seconds ago Up 25 seconds 0.0.0.0:10050->7050/tcp orderer4.example.com
7d9c13f964a5 hyperledger/fabric-orderer:latest "orderer" 29 seconds ago Up 25 seconds 0.0.0.0:7050->7050/tcp orderer.example.com
e16a90f3f3fc hyperledger/fabric-peer:latest "peer node start" 29 seconds ago Up 25 seconds 0.0.0.0:7051->7051/tcp peer0.org1.example.com
4c7776287dc7 hyperledger/fabric-peer:latest "peer node start" 29 seconds ago Up 27 seconds 0.0.0.0:8051->8051/tcp peer1.org1.example.com
aaeab5fdb418 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 27 seconds 0.0.0.0:11050->7050/tcp orderer5.example.com
817a3ec7dd9d hyperledger/fabric-peer:latest "peer node start" 30 seconds ago Up 27 seconds 0.0.0.0:10051->10051/tcp peer1.org2.example.com
26524f34f654 hyperledger/fabric-peer:latest "peer node start" 30 seconds ago Up 27 seconds 0.0.0.0:9051->9051/tcp peer0.org2.example.com
2485874c48d1 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 27 seconds 0.0.0.0:8050->7050/tcp orderer2.example.com
5a8142d00432 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 28 seconds 0.0.0.0:9050->7050/tcp orderer3.example.com

创建应用通道

连接cli发送创建mychannel的交易。

命令:

1
peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  first-network git:(r1.4-raft) ✗ docker exec -it cli  /bin/bash
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto scripts
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls channel-artifacts/
Org1MSPanchors.tx Org2MSPanchors.tx channel.tx genesis.block
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-29 11:25:57.987 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 11:25:58.026 UTC [cli.common] readBlock -> INFO 002 Got status: &{NOT_FOUND}
2019-07-29 11:25:58.029 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized
2019-07-29 11:25:58.231 UTC [cli.common] readBlock -> INFO 004 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.234 UTC [channelCmd] InitCmdFactory -> INFO 005 Endorser and orderer connections initialized
2019-07-29 11:25:58.436 UTC [cli.common] readBlock -> INFO 006 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.441 UTC [channelCmd] InitCmdFactory -> INFO 007 Endorser and orderer connections initialized
2019-07-29 11:25:58.643 UTC [cli.common] readBlock -> INFO 008 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.646 UTC [channelCmd] InitCmdFactory -> INFO 009 Endorser and orderer connections initialized
2019-07-29 11:25:58.848 UTC [cli.common] readBlock -> INFO 00a Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.852 UTC [channelCmd] InitCmdFactory -> INFO 00b Endorser and orderer connections initialized
2019-07-29 11:25:59.053 UTC [cli.common] readBlock -> INFO 00c Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:59.056 UTC [channelCmd] InitCmdFactory -> INFO 00d Endorser and orderer connections initialized
2019-07-29 11:25:59.260 UTC [cli.common] readBlock -> INFO 00e Received block: 0
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

确认应用通道创建成功

连接orderer查看mychannel是否创建,可以看到已经存在mychannel目录,证明mychannel已创建。

命令:

1
ls /var/hyperledger/production/orderer/chains/mychannel/

记录:

1
2
3
4
➜  ~ docker exec -it orderer.example.com bash
root@7d9c13f964a5:/opt/gopath/src/github.com/hyperledger/fabric# ls
root@7d9c13f964a5:/opt/gopath/src/github.com/hyperledger/fabric# ls /var/hyperledger/production/orderer/chains/mychannel/
blockfile_000000

加入应用通道

从cli上可以发起peer0.org1加入mychannel的交易请求,方法是通过让peer channel join读取环境变量信息,环境变量决定了当前为哪个peer进行处理。

CORE_PEER*相关的环境变量,代表了所有和peer相关的配置信息:

1
2
3
4
5
6
7
8
9
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# env | grep "CORE_PEER"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
CORE_PEER_LOCALMSPID=Org1MSP
CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
CORE_PEER_TLS_ENABLED=true
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ID=cli
CORE_PEER_ADDRESS=peer0.org1.example.com:7051

切换peer时,只需修改以下4个环境变量,它们代表当前是哪个peer,以及peer的配置,其他环境变量通用:

  • CORE_PEER_LOCALMSPID
  • CORE_PEER_TLS_ROOTCERT_FILE
  • CORE_PEER_MSPCONFIGPATH
  • CORE_PEER_ADDRESS,最易分辨当前是在为哪个peer操作,比如默认是peer0.org1

以下为加入mychannel和列出当前peer(peer0.org1)所加入的通道:

命令:

1
2
peer channel join -b mychannel.block
peer channel list

记录:

1
2
3
4
5
6
7
8
9
10
11
12
➜  first-network git:(r1.4-raft) ✗ docker exec -it cli  /bin/bash
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto mychannel.block scripts
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 11:38:30.638 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 11:38:30.719 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 11:45:29.692 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel

也可以登录peer0.org1去查看peer加入的通道:

1
2
3
4
5
➜  ~ docker exec -it peer0.org1.example.com bash
root@e16a90f3f3fc:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 11:59:51.415 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel

没有命令能够查看某个通道所有的peer。

peer0.org1重复加入会发起proposal失败:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 11:58:25.746 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Error: proposal failed (err: bad proposal response 500: cannot create ledger from genesis block: LedgerID already exists)

修改环境变量的规则见fabric-samples/first-network/scripts/utils.sh中的setGloabls函数。以下是peer1.org0加入mychanel:

1
2
3
4
5
6
7
8
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer1.org1.example.com:8051
# 执行加入通道
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 12:01:20.752 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 12:01:20.832 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel

确认peer加入的应用通道和数据

登录peer1.org1确认已经加入mychanel,以及同步到了mychannel上的链数据:

1
2
3
4
5
6
7
➜  ~ docker exec -it peer1.org1.example.com bash
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 12:02:48.662 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls /var/hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000
/var/hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000

更新锚节点配置

使用cli更新peer1.org1的锚节点配置:

因为上一步操作环境变量已经修改成peer1.org1的,所以,就直接让peer1.org1做锚节点好了。

命令:

1
peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

记录:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-29 12:05:12.585 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 12:05:12.609 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update

安装链码

使用cli为peer1.org1安装链码,安装链码是把链码源码打包和拷贝到peer节点的文件系统上,具体见快速入门Fabric核心概念和框架:安装链码

安装命令为peer channel isntall,包含了以下参数:

  • -n mycc:链码名称
  • -v 1.0:链码版本
  • -l golang:链码语言
  • -p github.com/chaincode/chaincode_example02/go/:本地链码代码所在路径,从$GOPATH/src下开始搜索。

更多选项见peer chaincode install -h

命令:

1
peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/

记录:

1
2
3
4
5
6
7
8
# 查看链码源码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls ../../../../github.com/chaincode/chaincode_example02/go/
chaincode_example02.go
# 安装链码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
2019-07-30 02:09:10.516 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 02:09:10.516 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-30 02:09:10.800 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >

查看安装的链码

2种方式。

1、在cli上执行查询:

命令:

1
peer chaincode list --installed

记录:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode list --installed
Get installed chaincodes on peer:
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Id: 476fca1a949274001971f1ec2836cb09321f0b71268b3762d68931c93f218134

2、登录到peer1.org1上查询链码文件:

命令:

1
ls /var/hyperledger/production/chaincodes

记录:

1
2
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls /var/hyperledger/production/chaincodes/mycc.1.0
/var/hyperledger/production/chaincodes/mycc.1.0

链码安装过程

也可以查看peer1.org1安装链码的日志:

1
2
3
4
5
6
7
8
9
➜  ~ docker logs peer1.org1.example.com
...省略老日志
2019-07-30 02:09:10.796 UTC [endorser] callChaincode -> INFO 04b [][876f7f14] Entry chaincode: name:"lscc"
2019-07-30 02:09:10.799 UTC [lscc] executeInstall -> INFO 04c Installed Chaincode [mycc] Version [1.0] to peer
2019-07-30 02:09:10.799 UTC [endorser] callChaincode -> INFO 04d [][876f7f14] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:09:10.799 UTC [comm.grpc.server] 1 -> INFO 04e unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35232 grpc.code=OK grpc.call_duration=3.276308ms
2019-07-30 02:10:51.409 UTC [endorser] callChaincode -> INFO 04f [][9db45293] Entry chaincode: name:"lscc"
2019-07-30 02:10:51.411 UTC [endorser] callChaincode -> INFO 050 [][9db45293] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:10:51.411 UTC [comm.grpc.server] 1 -> INFO 051 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35240 grpc.code=OK grpc.call_duration=2.667734ms

部署链码

实例化链码也成部署链码,鉴于少1个字,下文使用部署链码。

通过cli部署链码,由于之前设置的环境变量,部署等价于由peer1.org1触发。

在一个通道上,链码只需部署1次,所以无需每个peer都去部署链码。部署链码实际是一笔部署交易,该交易的结果就是部署链码容器。

部署命令是peer chaincode instantiate,需要使用以下标记:

  • -o orderer.example.com:7050:指定要连接的orderer节点
  • --tls true:开启TLS验证
  • --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem:使用的CA证书文件,注意使用的是orderer的证书
  • -C mychannel:在哪个通道上部署
  • -n mycc:链码名称
  • -l golang:链码语言
  • -v 1.0:链码版本
  • -c '{"Args":["init","a","100","b","200"]}':链码的构建过程消息,JSON格式,调用init函数,设置a和b的值
  • -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')':指定背书策略,必须由org1和org2同时背书

命令:

1
peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'

部署和快速查询如下:

1
2
3
4
5
6
7
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'
2019-07-30 02:18:57.119 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 02:18:57.119 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode list --instantiated -C "mychannel"
Get instantiated chaincodes on channel mychannel:
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Escc: escc, Vscc: vscc

查看部署的链码

链码部署后,会启动链码容器,可以查看:

1
2
3
➜  ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8bab025e153e dev-peer1.org1.example.com-mycc-1.0-cd123150154e6bf2df7ce682e0b1bcbea40499416f37a6da3aae14c4eb51b08d "chaincode -peer.add…" 11 minutes ago Up 11 minutes dev-peer1.org1.example.com-mycc-1.0

查看链码容器日志:

1
2
3
➜  ~ docker logs dev-peer1.org1.example.com-mycc-1.0
ex02 Init
Aval = 100, Bval = 200

部署链码的过程

可以通过peer1.org1的日志查看:

  1. 背书
  2. 生成Docker构建镜像:GenerateDockerBuild
  3. 接收到区块2(前面一步必然被orderer打包了)
  4. 部署链码:HandleStateUpdates
  5. 提交区块2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2019-07-30 02:17:46.522 UTC [endorser] callChaincode -> INFO 052 [mychannel][1c8a3567] Entry chaincode: name:"lscc"
2019-07-30 02:17:46.526 UTC [endorser] callChaincode -> INFO 053 [mychannel][1c8a3567] Exit chaincode: name:"lscc" (3ms)
2019-07-30 02:17:46.527 UTC [comm.grpc.server] 1 -> INFO 054 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35248 grpc.code=OK grpc.call_duration=6.246381ms
2019-07-30 02:18:57.122 UTC [endorser] callChaincode -> INFO 055 [mychannel][5059d46e] Entry chaincode: name:"lscc"
2019-07-30 02:18:57.141 UTC [chaincode.platform.golang] GenerateDockerBuild -> INFO 056 building chaincode with ldflagsOpt: '-ldflags "-linkmode external -extldflags '-static'"'
2019-07-30 02:19:12.363 UTC [endorser] callChaincode -> INFO 057 [mychannel][5059d46e] Exit chaincode: name:"lscc" (15240ms)
2019-07-30 02:19:12.363 UTC [comm.grpc.server] 1 -> INFO 058 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35252 grpc.code=OK grpc.call_duration=15.242059303s
2019-07-30 02:19:14.462 UTC [gossip.privdata] StoreBlock -> INFO 059 [mychannel] Received block [2] from buffer
2019-07-30 02:19:14.464 UTC [committer.txvalidator] Validate -> INFO 05a [mychannel] Validated block [2] in 2ms
2019-07-30 02:19:14.464 UTC [cceventmgmt] HandleStateUpdates -> INFO 05b Channel [mychannel]: Handling deploy or update of chaincode [mycc]
2019-07-30 02:19:14.503 UTC [kvledger] CommitWithPvtData -> INFO 05c [mychannel] Committed block [2] with 1 transaction(s) in 38ms (state_validation=0ms block_and_pvtdata_commit=16ms state_commit=6ms)
2019-07-30 02:20:41.569 UTC [endorser] callChaincode -> INFO 05d [mychannel][3d51a2d1] Entry chaincode: name:"lscc"
2019-07-30 02:20:41.571 UTC [endorser] callChaincode -> INFO 05e [mychannel][3d51a2d1] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:20:41.571 UTC [comm.grpc.server] 1 -> INFO 05f unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35260 grpc.code=OK grpc.call_duration=2.998184ms

查询链码

链码查询的命令是peer chaincode query,需要指定通道以及链码,最后是调用参数。

如果key存在可以查询到正确的值,如果key不存在,查询结果提示Error。

命令:

1
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'

记录:

1
2
3
4
5
6
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
100
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
200
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","c"]}'
Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Nil amount for c\"}"

1次查询操作,链码容器的日志会是这样,可以看到触发Invoke,然后是Query Response,并不是真正去Invoke:

1
2
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}

调用链码

调用链码是一笔调用交易,需要:

  • 指定orderer节点
  • 使用TLS、指定orderer的CA证书
  • 指定哪个通道的,哪个链码
  • 通过peerAddresses指定背书的peer,以及要使用的证书

只有当背书策略满足要求时,调用交易才会判断为有效,然后修改链码容器内的数据。

不满足背书策略的调用交易

以下为无效的调用交易,因为只指定了peer1.org1进行背书,而mycc的背书策略要求org1和org2的2个peer同时背书才行。

命令:

1
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --peerAddresses peer1.org1.example.com:8051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt  -c '{"Args":["invoke","a","b","10"]}'

-c '{"Args":["invoke","a","b","10"]}'指调用链码的invoke函数,参数为a, b和10,含义是对把a和b的值分别修改为a-10,b+10,具体操作可以见源码:
fabric-samples/chaincode/chaincode_example02/go/chaincode_example02.go。

链码容器日志如下,可以看到触发了Invoke,值进行了变更。

1
2
ex02 Invoke
Aval = 90, Bval = 210

通过查询命令可以发现链码数据并未改变,因为交易proposal不满足背书策略,cli不会发起交易给orderer,数据不会提交。

1
2
3
4
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
100
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
200

有效的调用交易

之前安装链码时,只在peer1.org1进行了安装,但背书要求org2的节点也要进行背书,背书节点需要已经安装链码,否则无法进行背书,会返回错误的背书响应:

1
Error: endorsement failure during invoke. response: status:500 message:"cannot retrieve package for chaincode mycc/1.0, error open /var/hyperledger/production/chaincodes/mycc.1.0: no such file or directory"

本文选择peer1.org2,先让它加入mychannel,然后安装链码,详细过程见加入应用通道安装链码

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
# 设置环境变量
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_LOCALMSPID="Org2MSP"
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_ADDRESS=peer1.org2.example.com:10051
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 加入mychannel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-30 03:45:39.637 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-30 03:45:39.731 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-30 03:46:09.910 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 设置org2的锚节点
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-30 03:47:27.412 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-30 03:47:27.447 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 安装链码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
2019-07-30 03:48:18.903 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 03:48:18.903 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-30 03:48:19.180 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >

之前已经实例化过1链码,所以无需再次实例化。

接下来重新执行调用链码,并且制定2个背书节点,分别是peer1.org1和peer1.org2。背书启用了TLS,需要在--peerAddresses后面,使用--tlsRootCertFiles指定对应peer的证书文件,使用--cafile指定orderer的证书文件。

命令:

1
2
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --peerAddresses peer1.org1.example.com:8051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt --peerAddresses peer1.org2.example.com:10051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
2019-07-30 03:52:53.880 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

可以通过命令查询到,链码中的数据已经更新到最新,说明调用链码成功。

1
2
3
4
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
90
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
210

链码容器的日志可以看到Invoke的日志:

1
2
ex02 Invoke
Aval = 90, Bval = 210

链码FAQ

执行完上面的操作,你是否有这2个疑问:

  1. 有2个背书节点,不应该2个背书节点都Invoke吗,为什么链码容器日志只看到1次Invoke?
  2. 背书节点Invoke链码容器时,链码容器的数据并不会提交,链码容器里的数据是什么时候更新的?

疑问1解答

有2个背书节点,不应该2个背书节点都Invoke吗,为什么链码容器日志只看到1次Invoke?

每个组织进行背书,都必须部署自己的链码容器,背书时通过gRPC和自己组织的链码容器交互,所以上面查看链码容器dev-peer1.org1.example.com-mycc-1.0日志的时候,只看到1次Invoke,另外1次,在org2的链码容器dev-peer1.org2.example.com-mycc-1.0日志里。

有效的调用交易这一节,我们只安装了链码,因为在某个通道内,链码只需部署1次,并且之前peer1.org1已经发送了部署交易,所以无需再发送部署交易。

1
2
3
4
➜  ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0700ebc80246 dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab "chaincode -peer.add…" 3 hours ago Up 3 hours dev-peer1.org2.example.com-mycc-1.0
8bab025e153e dev-peer1.org1.example.com-mycc-1.0-cd123150154e6bf2df7ce682e0b1bcbea40499416f37a6da3aae14c4eb51b08d "chaincode -peer.add…" 4 hours ago Up 4 hours dev-peer1.org1.example.com-mycc-1.0

这里有一点需要注意,当org2的背书节点进行背书时,发现没有链码容器,会自动创建,而不是安装链码后,即可主动部署链码容器,可以从peer1.org2的日志确认以上流程。

1
2
3
4
5
6
7
8
9
10
11
# 安装链码
2019-07-30 03:48:19.177 UTC [endorser] callChaincode -> INFO 063 [][353ca5ab] Entry chaincode: name:"lscc"
2019-07-30 03:48:19.179 UTC [lscc] executeInstall -> INFO 064 Installed Chaincode [mycc] Version [1.0] to peer
2019-07-30 03:48:19.179 UTC [endorser] callChaincode -> INFO 065 [][353ca5ab] Exit chaincode: name:"lscc" (2ms)
2019-07-30 03:48:19.179 UTC [comm.grpc.server] 1 -> INFO 066 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:38546 grpc.code=OK grpc.call_duration=2.758068ms

# 12s后才部署链码容器
2019-07-30 03:52:37.932 UTC [endorser] callChaincode -> INFO 067 [mychannel][6b87148b] Entry chaincode: name:"mycc"
2019-07-30 03:52:37.948 UTC [chaincode.platform.golang] GenerateDockerBuild -> INFO 068 building chaincode with ldflagsOpt: '-ldflags "-linkmode external -extldflags '-static'"'
2019-07-30 03:52:53.875 UTC [endorser] callChaincode -> INFO 069 [mychannel][6b87148b] Exit chaincode: name:"mycc" (15943ms)
2019-07-30 03:52:53.876 UTC [comm.grpc.server] 1 -> INFO 06a unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:38984 grpc.code=OK grpc.call_duration=15.945466965s

疑问2解答

背书节点Invoke链码容器时,链码容器的数据并不会提交,链码容器里的数据是什么时候更新的?

链码容器只是负责执行调用,不负责存储数据,所以不存在链码容器数据何时更新的问题。

下面是调用链码的流程图,可以看到链码执行时,是通过Shim从fabric账本读取数据的,然后把执行结果返回。如果交易被peer节点验证有效,调用交易的结果会写入当fabric账本,如果无效,不会改变fabric账本,所以你现在是否理解不满足背书策略的调用交易不会改变数据,而有效的调用交易会改变数据。

启动自定义网络

利用byfn启动自定义网络

./byfn.sh不止updown2个命令,还有其他命令,以及更多的参数,比如restart, generate和upgrade。

在上一节,使用了完全默认的参数,启动了网络,这是完全傻瓜式的。基于对fabric的掌握,还可以设置更多的参数,比如使用参数可以指定channel名称,而不是使用默认的mychannel,可以设置超时时间,使用指定docker编排文件创建各容器,指定chaincode的语言是Go还是Java等,还有更多的参数自己探索吧,设置一些参数重新启动网络。

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
➜  first-network git:(release-1.4) ./byfn.sh help
Usage:
byfn.sh <mode> [-c <channel name>] [-t <timeout>] [-d <delay>] [-f <docker-compose-file>] [-s <dbtype>] [-l <language>] [-o <consensus-type>] [-i <imagetag>] [-a] [-n] [-v]
<mode> - one of 'up', 'down', 'restart', 'generate' or 'upgrade'
- 'up' - bring up the network with docker-compose up
- 'down' - clear the network with docker-compose down
- 'restart' - restart the network
- 'generate' - generate required certificates and genesis block
- 'upgrade' - upgrade the network from version 1.3.x to 1.4.0
-c <channel name> - channel name to use (defaults to "mychannel")
-t <timeout> - CLI timeout duration in seconds (defaults to 10)
-d <delay> - delay duration in seconds (defaults to 3)
-f <docker-compose-file> - specify which docker-compose file use (defaults to docker-compose-cli.yaml)
-s <dbtype> - the database backend to use: goleveldb (default) or couchdb
-l <language> - the chaincode language: golang (default) or node
-o <consensus-type> - the consensus-type of the ordering service: solo (default), kafka, or etcdraft
-i <imagetag> - the tag to be used to launch the network (defaults to "latest")
-a - launch certificate authorities (no certificate authorities are launched by default)
-n - do not deploy chaincode (abstore chaincode is deployed by default)
-v - verbose mode
byfn.sh -h (print this message)

Typically, one would first generate the required certificates and
genesis block, then bring up the network. e.g.:

byfn.sh generate -c mychannel
byfn.sh up -c mychannel -s couchdb
byfn.sh up -c mychannel -s couchdb -i 1.4.0
byfn.sh up -l node
byfn.sh down -c mychannel
byfn.sh upgrade -c mychannel

Taking all defaults:
byfn.sh generate
byfn.sh up
byfn.sh down

总结

通过这篇文章能够掌握部署一个Fabric网络需哪些步骤,以及各步骤需要做哪些工作。但这篇文章,缺少了关于docker配置各容器的部分,后面会单独一篇文章介绍。

声明

这是一篇信息整合的文章,80%的内容来自Fabric官方文档和网络文章,在此基础上整理和修改,剩下20%为操作记录。

官方文档资料链接

剩下的官方文档链接都加入到了下面的笔记中

网络文章

天山老妖S:HyperLeger Fabric SDK开发系列文章

陶辉:区块链开源实现hyperledger fabric架构详解

Hyperledger Fabric 开发系列文章

笔记摘录

架构

HyperLeger Fabric架构„

Fabric网络是通过组织(organization)来划分的,每个组织内都包含承担不同功能的Peer 节点,每个Peer节点又可以担任多种角色。所有的组织共用一个统一的Orderer排序服务集群。基于Hyperledger Fabric区块链网络的设计时需要考虑组织之间的业务关系以及内部每个模块之间的联系,统一进行规划。

每个组织通常拥有自己的客户端、Peer节点和CA节点,并且可以根据需要创建一个或多个不同的类型节点。Orderer节点不属于某个组织的实体,属于组织共同维护的节点。

排序节点

orderer负责排序和打包区块。排序服务节点只是决定交易处理的顺序,并不对交易的合法性进行校验,也无需去管之前的交易是否合法,也不负责维护账本信息,只有记账节点才有账本写入权限。peer验证交易后会给交易打上交易是否合法。

排序服务节点接收包含背书签名的交易,对未打包的交易进行排序生成区块,广播给Peer节点中的主节点。排序服务提供的是原子广播,保证同一个链上的节点接收到相同的消息,并且有相同的逻辑顺序。排序服务独立于Peer进程存在并且以先来先服务的方式对Fabric网络上的所有通道进行排序交易。

Tx排序的依据是什么?根据每个通道按时间顺序调用,创建每个通道的交易区块。

出块的依据是什么?交易数、时间?都可以,具体见orderer排序交易

peer节点角色

peer文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/peers/peers.html

peer节点负责背书和验证交易,以及Org之间的通信。

每个Org可以有多个Peer,每个Peer节点都是记账节点,并且可担任多种角色:

  • Endorser Peer(背书节点)
  • Leading Peer(主节点)
  • Committer Peer(记账节点)
  • Anchor Peer(锚节点)

背书节点(Endorser Peer)

部分Peer节点会执行交易并对结果进行签名背书。背书节点是动态的角色,是与具体链码绑定的,由链码的背书策略指定。

只有在应用程序向节点发起交易背书请求时才成为背书节点,其它时候是普通的记账节点,只负责验证交易并记账。

主节点(Leading Peer)

主节点负责和Orderer排序服务节点通信,从排序服务节点处获取最新的区块,并把区块分发到本channel内同组织的其他节点。可以使用配置文件强制设置,也可以选举产生。

注意:主节点不是指Raft中的leader,是指org中的主节点(leading peer)。

记账节点(Committer Peer)

负责验证区块里的交易,然后将区块提交(写入/追加)到其通道账本的副本。记账节点还将每个块中的每个交易标记为有效或无效,通过验证的为有效,否则为无效。

锚节点(Anchor Peer)

在一个通道上可以被所有其它Peer节点发现的Peer节点,通道上的每个组织都有一个或多个锚节点,多个锚节点可以用来防止单点故障,通过锚节点实现不同组织的Peer节点发现通道上的所有组织的锚节点。

举个例子,peer0.org1是org1的锚节点,它连接peer0.org2时,如果peer0.org2知道org3的锚节点peer0.org3,那么会告诉peer0.org1,org3的锚节点是peer0.org3,以后peer0.org1就可以直接和peer0.org3通信了。

客户端

2种:1. CLI,2. SDK.

SDK

Fabric提供了三种语言版本的SDK,分别如下:

  1. Fabric Nodejs SDK
  2. Fabric Java SDK
  3. Fabric Go SDK

Fabric区块链应用可以通过SDK访问Fabric区块链网络中的多种资源,包括账本、交易、链码、事件、权限管理等。应用程序代表用户与Fabric区块链网络进行交互,Fabric SDK API提供了如下功能:

  1. 创建通道
  2. 将peer节点加入通道
  3. 在peer节点安装链码
  4. 在通道实例化链码
  5. 通过链码调用交易
  6. 查询交易或区块的账本

链码

链码即智能合约,链码分系统链码和用户链码,在没有特殊强调的时候,链码就是指用户链码。

链码操作包含3个基本操作:安装(install)、实例化(instantiation)和调用(Invoke),以及其他操作比如打包、签名。

用户链码被编译成一个独立的应用程序,运行于隔离的Docker容器中,在链码部署的时候会自动生成链码的Docker镜像,链码容器通过gRPC协议与相应的Peer节点进行交互,以操作分布式账本中的数据。

一个channel内链码只需实例化1次,所有运行链码的节点都需要安装该链码,如果只需要验证交易,并不需要安装链码,因为实例化后,可以通过gRPC通信与链码容器交互验证链码。

背书策略(Endorser Policy)

背书策略是背书节点如何决策交易是否合法的条件。链码实例化时可指定背书策略,当记账节点接收到交易时,会获知相关链码信息,然后检查链码的背书策略,判断交易是否满足背书策略,若满足则标注交易为合法。

背书策略可分为主体Principal(P)和阈值Threshold(T)两部分,具体如下:

  • Principal指定由哪些成员进行背书。
  • Threshold接受两个输入,分别为阈值t和若干个P的集合n,只要交易中包含了n中t个成员的背书则认为交易合法。

背书策略可以指定某几个组织内的任意成员身份进行背书,或者要求至少有一个管理员身份进行背书等等。

  • T(1, ‘A’, ‘B’) 则需要A,B中任意成员背书。
  • T(1, ‘A’, T(2, ‘B’, ‘C’))则需要A成员背书或B,C成员同时背书。

目前客户端已经实现对背书策略的支持,可以通过-P来指定背书策略,结合AND、OR来组合成员,完成成员身份(admin、member)的集合。

-P OR ( 'Org1.admin' , AND ('Org2.member' , 'Org3.member') )

可以把OR、AND理解为函数,函数内为参数。

上述背书策略指定要么Org1的admin进行背书,或者Org2和Org3的成员同时进行背书,才满足背书策略。

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#endorsement-policy

系统链码

系统链码与用户链码有相同的编程模型,但系统链码运行在Peer节点,用户链码则在隔离的容器中运行。因此,系统链码内置为Peer节点的可执行文件中,不遵循用户链码的生命周期,安装、实例化、升级不适用于系统链码

系统链码用于减少Peer节点与用户链码进行gRPC通信的开销,同时权衡管理的灵活性。系统链码只能通过Peer节点的二进制文件升级,必须通过一组固定的参数进行注册,但不具有背书策略

Hyperledger Fabric系统链码实现了一系列系统功能,以便系统集成人员能够根据需求对其进行修改与替换。

常见系统链码如下:

  • 生命周期系统链码(LSCC ):负责对用户链码的生命周期进行管理。
  • 配置系统链码(CSCC):处理在Peer节点上的通道配置。
  • 查询系统链码(QSCC):提供账本的查询API,例如获取区块以及交易。
  • 背书系统链码(ESCC):背书过程的管理和配置。
  • 验证系统链码(VSCC):处理交易验证,包括检查背书策略以及多进程并发控制。

这样容易记:一共5个SCC,前2个与配置相关,后3个与操作相关,配置有生命周期和配置,操作有背书、验证和查询

在修改或者替换系统链码(LSCC、ESCC、VSCC)时必须注意,因为系统链码在主交易执行的路径中。VSCC在将区块提交至账本前,所有在通道的Peer节点会计算相同的验证以避免账本分歧(不确定性)。如果VSCC被改变或者替换,需要特别小心。

生命周期

通过install安装链码,通过instantiate实例化链码,然后可以通过invoke、query调用链码和查询链码。
如果需要升级链码,则需要先install安装新版本的链码,通过upgrade升级链码。在install安装链码前,可以通过package打包并签名生成打包文件,然后再通过install安装。

链码管理

peer chaincode -h可以列出链码的几种操作:

1
2
3
4
5
6
7
8
install     Package the specified chaincode into a deployment spec and save it on the peer's path.
instantiate Deploy the specified chaincode to the network.
invoke Invoke the specified chaincode.
list Get the instantiated chaincodes on a channel or installed chaincodes on a peer.
package Package the specified chaincode into a deployment spec.
query Query using the specified chaincode.
signpackage Sign the specified chaincode package
upgrade Upgrade chaincode.
  • 安装:把chaincode打包成部署规格,并且保存到peer的某个路径。

  • 实例化:部署chaincode到网络。

  • 调用:调用chaincode。

  • list:列出某个通道上已经实例化的chaincode或已安装到peer上的chaincode。

  • 打包:把chaincode打包成部署规格。

  • 查询:使用chaincode查询,即查询该查询该chaincode上的信息。

  • signpackage:对打包的chaincode签名。

  • 升级:升级已实例化的chaincode。

可以看到安装chaincode实际已经包含了打包的过程。

打包链码

链码包由三个部分组成:

  1. 由ChaincodeDeploymentSpec(CDS)格式定义的链码。CDS根据代码及其它属性(如名称与版本)定义链码包;
  2. 一个可选的实例化策略,能够被用作背书的策略进行描述
  3. 拥有链码的实体的一组签名。

其中,链码的签名主要目的如下:

  1. 建立链码的所有权;
  2. 允许验证链码包中的内容;
  3. 允许检测链码包是否被篡改。

通道上的链码的实例化交易的创建者能够被链码的实例化策略验证。

链码打包的方法由两种,一种是打包被多个所有者所拥有的链码,需要初始化创建一个被签名的链码包(SignedCDS),然后将其按顺序的传递给其它所有者进行签名;一种是打包单个所有者持有的链码。

创建一个被签名的链码包的命令如下:

1
peer chaincode package -n sacc -p chaincodedev/chaincode/sacc -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out
  • -s选项创建一个能被多个所有者签名的链码包,而不是简单的创建一个原始的CDS。一旦-s被指定,如果其它所有者想要签名CDS,则-S选项必须被指定。否则,将会创建一个SignedCDS,除CDS外仅仅包括实例化策略。
  • -S选项使用被在core.yaml文件中定义的localMspid属性的值标识的MSP对链码包进行签名。
  • -S选项是可选的。如果创建了一个没有签名的链码包,不能被其它所有者使用signpackage命令进行签名。
  • -i选项是可选的,允许为链码指定实例化策略。实例化策略与背书策略有相同的格式,指定哪些身份能够实例化链码。本例中仅OrgA的admin能够实例化链码。如果没有实例化策略被指定,将会使用默认的策略,仅允许拥有Peer的MSP的管理员身份实例化链码。

签名链码

在创建阶段就被签名的链码包能够交给其它所有者进行检查与签名,支持带外对链码进行签名。
ChaincodeDeploymentSpec可以选择由所有者集合进行签名,从而创建一个SignedChaincodeDeploymentSpec(SignedCDS)。SignedCDS包括3个部分:

  1. CDS包括源代码,链码的名称与版本号;

  2. 链码的实例化策略,表示为背书策略;

  3. 链码所有者的列表,由背书策略定义。

当在某些通道上实例化链码时,背书策略是在带外确定的,用于提供合适的MSP主体。如果没指定实例化策略,则默认的策略就是通道的任何MSP管理员。

每一个链码的所有者通过将SignedCDS与链码所有者的身份(例如证书)组合并签署组合结果来背书ChaincodeDeploymentSpec。

一个链码的所有者能够对自己所创建的签名过的链码包进行签名,需要使用如下命令:

1
peer chaincode signpackage ccpack.out signedccpack.out

ccpack.out、signedccpack.out分别是输入与输出包。signedccpack.out包括一个对链码包附加的签名(通过local msp进行的签名)。

安装链码

将链码的源代码打包成ChaincodeDeploymentSpec(CDS)的规定的格式,然后安装到通道中的背书节点上

当安装的链码包只包含一个ChaincodeDeploymentSpec时,将使用默认初始化策略并包括一个空的所有者列表。
链码应该仅仅被安装在链码所有者成员的背书节点上,用于实现链码对于网络中其它成员在逻辑上是隔离的。

安装链码会发送一个SignedProposal到生命周期系统链码(LSCC) ,也就是说会发送调用系统链码的交易。

使用CLI安装sacc链码的命令如下:

1
peer chaincode install -n sacc -v 1.0 -p sacc
  • -n选项指定链码实例名称
  • -v选项指定链码的版本
  • -p选项指定链码所在路径,必须在GOPATH路径下

CLI内部创建sacc的SignedChaincodeDeploymentSpec,然后将其发送给本地Peer节点,Peer节点会调用LSCC上的安装方法。为了在Peer节点上安装链码,SignedProposal的签名必须来自于Peer节点的本地MSP管理员之一

未安装 chaincode 的节点不能执行 chaincode,但仍可以验证交易并提交到账本中。所以背书节点必须按照链码,记账节点并非必须安装链码。

实例化链码

链码和通道是低耦合的:实例化调用生命周期系统链码(LSCC)用于创建及初始化通道上的链码。链码能够被绑定到任意数量的通道,以及在每个通道上单独的操作。无论链码安装及实例化到多少个通道上,每个通道的状态都是隔离的。

实例化的创建者必须满足包含在SignedCDS内链码的实例化策略,而且还必须是通道的写入器(作为通道创建的一部分被配置)。可以防止部署链码的流氓实体或者欺骗者在未被绑定的通道上执行链码。

默认的实例化策略是任意的通道MSP的管理员,因此链码实例化交易的创建者必须是通道管理员之一。当交易提案到达背书节点后,背书节点会根据实例化策略验证创建者的签名。在提交实例化交易到账本前,在交易验证时再一次完成该操作。

实例化交易同样设置了通道上的链码的背书策略 。背书策略描述了交易被通道上成员接受的认证要求。
使用CLI去实例化sacc的链码并初始化状态为user1与0,命令如下:

1
peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["user1","0"]}' -P "OR ('Org1.member','Org2.member')"
  • -n选项指定链码实例名称
  • -v选项指定链码的版本
  • -c 选项指定链码的调用参数
  • -P选项指定链码的背书策略

链码的背书策略表示,org1.member或者org2.member必须对调用使用sacc(这名字起的让我以为是某种SCC)的交易进行签名,以保障交易是有效的。在成功实例化后,通道的链码进入激活状态,可以处理任意的交易提案。交易到达背书节点时,会同时被处理。

实例化选项:

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
root@5af5a3fb3bb7:/var/hyperledger/production# peer chaincode instantiate -h
Deploy the specified chaincode to the network.

Usage:
peer chaincode instantiate [flags]

Flags:
-C, --channelID string The channel on which this command should be executed
--collections-config string The fully qualified path to the collection JSON file including the file name
--connectionProfile string Connection profile that provides the necessary connection information for the network. Note: currently only supported for providing peer connection information
-c, --ctor string Constructor message for the chaincode in JSON format (default "{}")
-E, --escc string The name of the endorsement system chaincode to be used for this chaincode
-h, --help help for instantiate
-l, --lang string Language the chaincode is written in (default "golang")
-n, --name string Name of the chaincode
--peerAddresses stringArray The addresses of the peers to connect to
-P, --policy string The endorsement policy associated to this chaincode
--tlsRootCertFiles stringArray If TLS is enabled, the paths to the TLS root cert files of the peers to connect to. The order and number of certs specified should match the --peerAddresses flag
-v, --version string Version of the chaincode specified in install/instantiate/upgrade commands
-V, --vscc string The name of the verification system chaincode to be used for this chaincode

Global Flags:
--cafile string Path to file containing PEM-encoded trusted certificate(s) for the ordering endpoint
--certfile string Path to file containing PEM-encoded X509 public key to use for mutual TLS communication with the orderer endpoint
--clientauth Use mutual TLS when communicating with the orderer endpoint
--connTimeout duration Timeout for client to connect (default 3s)
--keyfile string Path to file containing PEM-encoded private key to use for mutual TLS communication with the orderer endpoint
-o, --orderer string Ordering service endpoint
--ordererTLSHostnameOverride string The hostname override to use when validating the TLS connection to the orderer.
--tls Use TLS when communicating with the orderer endpoint
--transient string Transient map of arguments in JSON encoding

调用链码

调用链码:

1
peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C mychannel -n sacc -c '{"Args":["invoke","user1","user2","10"]}'

查询链码

1
peer chaincode query -C mychannel -n sacc -c '{"Args":["query","user1"]}'

调用链码的前提是链码已经实例化。

链码实例化之后存在了运行了一个容器,这个容器一直在运行,所以调用1个链码,实际是peer容器和链码容器交互的过程。

链码与Peer节点的交互过程如下:

  1. 链码通过gRPC与Peer节点交互,当Peer节点收到客户端的交易提案请求后,会发送一个链码消息对象(包含交易提案信息、调用者信息)给对应的链码。
  2. 链码调用Invoke方法,通过发送获取数据(GetState)和写入数据(PutState)消息,向Peer节点获取账本状态信息和发送预提交状态。
  3. 链码发送模拟执行结果给Peer节点,Peer节点对交易提案和模拟执行结果进行背书签名。

升级链码

链码的升级通过改变其版本号(作为SignedCDS的一部分)。SignedCDS另外的部分,如所有者及实例化策略都是可选的。然而,链码的名称必须是一致的,否则会被当做另外一个新的链码
在升级前,必须将新版本的链码安装到有需求的背书节点上。升级也是一种交易,会把新版本的链码绑定到通道中。升级只能在一个时间点对一个通道产生影响,其它通道仍然运行旧版本的链码
由于可能存在多个版本的链码同时存在,升级过程不会自动删除老版本链码,用户必须手动操作删除过程
升级与实例化transaction有一点不同的是:通过现有的chaincode实例化策略检查升级transaction,而不是用新的策略检查。这是为了确保只有当前实例化策略中指定的成员能够升级chaincode。
在升级期间,链码的Init函数也会被调用,执行有关升级的数据或者使用数据重新进行初始化,在升级链码的期间避免对状态进行重置。
安装新版本的链码
peer chaincode install -n sacc -v 1 -p path/to/my/chaincode/v1
upgrade升级链码
peer chaincode upgrade -n sacc -v 1 -c '{"Args":["d", "e", "f"]}' -C mychannel

都是交易

安装、实例化、调用、升级链码,都是创建一笔交易,通过交易实现。

链码开发

github.com/hyperledger/fabric/core/chaincode/shim包是开发Go语言链码的API。shim 包提供了链码与账本交互的中间层。链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态。

链码启动必须通过调用 shim 包中的 Start 函数,而 Start 函数被调用时需要传递一个类型为 Chaincode 的参数,这个参数 Chaincode 是一个接口类型,该接口中有两个重要的函数 Init 与 Invoke 。

Chaincode 接口定义如下:

1
2
3
4
type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}

Init 与 Invoke 方法

编写链码,关键是实现 Init 与 Invoke 两个方法,必须由所有链码实现。Fabric 通过调用指定的函数来运行事务。

  • Init:在链码实例化或升级时被调用, 完成初始化数据的工作。
  • invoke:更新或查询提案事务中的分类帐本数据状态时,Invoke 方法被调用, 因此响应调用或查询的业务实现逻辑都需要在此方法中编写实现。

示例:https://blog.51cto.com/9291927/2318364

通道

通道由排序服务管理,排序服务节点还负责对通道中的交易进行排序。。

目前通道分为系统通道(System Channel)和应用通道(Application Channel)。排序服务通过系统通道来管理应用通道,用户的交易信息通过应用通道传递。

每个组织可以有多个节点加入同一个通道,组织内的节点中可以指定一个锚节点或多个锚节点(增强系统可靠性,避免单点故障)。另外,同一组织的节点会选举或指定主导节点(leading peer),主导节点负责接收从排序服务发来的区块,然后转发给本组织的其它节点。主导节点可以通过特定的算法选出,可以保证在节点数量不断变动的情况下仍能维持整个网络的稳定性。

在Fabric网络中,可能同时存在多条彼此隔离的通道,每条通道包含一条私有的区块链和一个私有账本,通道中可以实例化一个或多个链码,以操作区块链上的数据

通道是共识服务提供的一种通讯机制,基于发布-订阅关系,将Peer节点和排序节点根据某个Topic连接在一起,形成一个具有保密性的通讯链路(虚拟),实现业务隔离的要求。

排序服务提供了供Peer节点订阅的主题(如发布-订阅消息队列),每个主题是一个通道。Peer节点可以订阅多个通道,并且只能访问自己所订阅通道上的交易,因此一个Peer节点可以通过接入多个通道参与到多条链中。

排序服务支持多通道,提供了通向客户端和Peer节点的共享通信通道,提供了包含交易的消息广播服务(broadcast和deliver)。客户端可以通过通道向连接到通道的所有节点广播(broadcast)消息,向连接到通道的所有节点投递(deliver)消息。多通道使得Peer节点可以基于应用访问控制策略来订阅任意数量的通道,应用程序根据业务逻辑决定将交易发送到1个或多个通道。

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/channels.html

创建

在创建通道的时候,需要定义通道的成员和组织、锚节点(anchor peer)和排序服务节点,一条与通道对应的区块链会同时生成,用于记录账本的交易,通道的初始配置信息记录在区块链的创世区块中,可以通过增加一个新的配置区块来更改通道的配置信息。

创建通道的时候定义了成员,只有通过成员MSP验证的实体,才能够加入到通道并访问通道的数据。在通道中一般包含有若干成员(组织),若两个网络实体的×××书能够追溯到同一个根CA,则认为这两个实体属于同一组织。

配置

通道的配置信息都被打包到一个区块中,并存放在通道的共享账本中,成为通道的配置区块,配置区块除了配置信息外不包含其它交易信息。通道可以使用配置区块来更新配置,因此在账本中每新添加一个配置区块,通道就按照最新配置区块的定义来修改配置。通道账本的首个区块一定是配置区块,也称为创世区块(Genesis Block)。

交易

交易的过程,实际是共识的3步:背书、排序和校验。

类型

Fabric区块链的交易分两种,部署交易和调用交易

部署交易把链码部署到Peer节点上并准备好被调用,当一个部署交易成功执行时,链码就被部署到各个背书节点上。(部署指实例化)

调用交易是客户端应用程序通过Fabric提供的API调用先前已部署好的某个链码的某个函数执行交易,并相应地读取和写入KV数据库,返回是否成功或者失败。

流程

详读这篇文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/txflow.html

背书节点校验客户端的签名,然后执行智能合约代码模拟交易。交易处理完成后,对交易信息签名,返回给客户端。客户端收到签名后的交易信息后,发给排序服务节点排序。排序服务节点将交易信息排序打包成区块后,广播发给确认节点,写入区块链中。

客户端提案

客户端应用程序利用SDK(Node.js,Java,Python)构造交易提案(Proposal),提案有2种:

  1. 实例化chaincode。
  2. 调用chaincode。

客户端把交易提案(Proposal)根据背书策略发送给一个或多个背书节点,交易提案中包含本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等。
SDK将交易提案打包为可识别的格式(如gRPC上的protobuf),并使用用户的加密凭证为该交易提案生成唯一的签名。

背书策略定义需要哪些节点背书交易才有效,例如需要5个成员的背书节点中至少3个同意;或者某个特殊身份的成员支持等。客户端只有在收集足够多的背书节点的交易提案签名,交易才能被视为有效。

背书节点为提案背书

背书节点(endorser)收到交易提案后,验证签名并确定提交者是否有权执行操作。背书节点将交易提案的参数作为输入,在当前状态KV数据库上执行交易,生成响应返回给客户端,响应包含执行返回值、读写集的交易结果(此时不会更新账本),交易结果集、背书节点的签名和背书结果(YES/NO)作为提案的结果,客户端SDK解析信息判断是否应用于后续的交易。

在所有合法性校验通过后,背书节点按照交易提案调用链码模拟执行交易。链码执行时,读取的数据(键值对)是背书节点中本地的状态数据库,链码读取过的数据回被归总到读集(Read Set);链码对状态数据库的写操作并不会对账本做改变,所有的写操作将归总到一个写入集(Write Set)中记录下来。读集和写集将在确认节点中用于确定交易是否最终写入账本。

资料:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#endorsement

客户端收集提案背书

客户端验证背书节点签名,并比较各节点返回的提案结果,判断提案结果是否一致以及是否参照指定的背书策略执行。

当背书结果都通过验证,并且满足背书策略时,客户端生成一笔交易发送给排序节点。交易包含交易签名、proposal、读写集、背书结果和通道ID。

orderer排序交易

排序服务节点对接收到的交易进行共识排序,然后按照区块生成策略,将一批交易打包到一起,生成新的区块,调用deliver API投递消息,发送给确认节点。

区块的广播有两种触发条件,一种是当通道的交易数量达到某个预设的阈值,另一种是在交易数量没有超过阈值但距离上次广播的时间超过某个特定阈值,也可触发广播数据块。两种方式相结合,使得经过排序的交易及时生成区块并广播给通道的Leader节点(记账节点),Leader节点验证后,再发送给同channel同组织的其他记账节点。

peer验证区块

peer需要对区块内的所有交易进行验证,验证交易是否按背书策略执行以及根据读写集把交易打上有效或者无效的标签。最后把区块追加到本地的区块链,修改世界状态。

记账节点收到排序服务节点发来的区块后,逐笔检查区块中的交易:

  1. 先检查交易的合法性以及该交易是否曾经出现过。
  2. 然后调用校验系统链码(VSCC,Validation System Chaincode)检验交易的签名背书是否合法,
  3. 以及背书的数量是否满足背书策略的要求。
  4. 记账节点对交易进行多版本并发控制(MVCC)检查,即校验交易的读集(Read Set)是否和当前账本中的版本一致(即没有变化)。如果没有改变,说明交易写集(Write Set)中对数据的修改有效,把该交易标注为有效,交易的写集更新到状态数据库中。如果当前账本的数据和读集版本不一致,则该交易被标注为无效,不更新状态数据库。

交易流程中,采用MVCC的乐观锁模型,提高了系统的并发能力。但MVCC也带来了一些局限性。例如,在同一个区块中若有两个交易先后对某个数据项做更新,顺序在后的交易将失败,因为后序交易的读集版本和当前数据项版本已经不一致。

区块写入账本

每个peer都把区块追加到对应channel的账本上,每个有效交易的write set会被提交到状态数据库。

客户端获取交易结果

客户端可以通过事件订阅交易的结果:是否添加到数据库,是否有效。

结构

账本

账本由区块链和状态数据库两部分组成

  1. 区块链是一组不可更改的有序的区块(数据块),记录着全部交易的日志。
  2. 状态数据库记录了账本中所有键值对的当前值(世界状态),相当于对当前账本的交易日志做了索引。链码执行交易的时候需要读取账本的当前状态,从状态数据库可以迅速获取键值的最新状态。

数据库

Fabric区块链网络中,每个通道都有其账本,每个Peer节点都保存着其所加入通道的账本,Peer节点的账本包含如下数据:

  1. 账本编号,用于快速查询存在哪些账本
  2. 账本数据,用于区块数据存储
  3. 区块索引,用于快速查询区块/交易
  4. 状态数据,用于最新的世界状态数据
  5. 历史数据,用于跟踪键的历史

Fabric的Peer节点账本中有四种数据库,idStore(ledgerID数据库)、blkstorage(block文件存储)、statedb(状态数据库)、historydb(历史数据库)。

账本数据库基于文件系统,将区块存储在文件块中,然后在区块索引LevelDB中存储区块交易对应的文件块及其偏移,即将区块索引LevelDB作为账本数据库的索引。目前支持的区块索引有:区块编号、区块哈希、交易ID、区块交易编号

状态数据库存储的是所有曾经在交易中出现的键值对的最新值(世界状态)。调用链码执行交易可以改变状态数据,为了高效的执行链码调用,所有数据的最新值都被存放在状态数据库中;状态数据库是有序交易日志的快照,任何时候都可以根据交易日志重新生成状态数据库;状态数据库会在Peer节点启动的时候自动恢复或重构,未完备前,本Peer节点不会接受新的交易;状态数据库可以使用LevelDB或者CouchDB,CouchDB能够存储任意的二进制数据,支持富文本查询。链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#world-state

历史状态数据库用于查询某个key的历史修改记录,历史状态数据库并不存储key具体的值,而只记录在某个区块的某个交易里,某key变动了一次。后续需要查询的时候,根据变动历史去查询实际变动的值。

ledgerID数据库存储chainID,用于快速查询节点存在哪些账本。

ledgersData是Peer节点账本的根目录,Peer节点的账本存储在Peer节点容器的/var/hyperledger/production/ledgersData目录下,通过命令行可以进入Peer节点容器进行查看,命令如下:

1
docker exec -it peer0.org1.example.com bash
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
root@5af5a3fb3bb7:/var/hyperledger/production# ls -R chaincodes
chaincodes:
root@5af5a3fb3bb7:/var/hyperledger/production# ls -R transientStore/
transientStore/:
000001.log CURRENT LOCK LOG MANIFEST-000000




root@5af5a3fb3bb7:/var/hyperledger/production# ls -R ledgersData/
ledgersData/:
bookkeeper chains configHistory historyLeveldb ledgerProvider pvtdataStore stateLeveldb

ledgersData/bookkeeper:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/chains:
chains index

ledgersData/chains/chains:
mychannel

ledgersData/chains/chains/mychannel:
blockfile_000000

ledgersData/chains/index:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/configHistory:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/historyLeveldb:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/ledgerProvider:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/pvtdataStore:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/stateLeveldb:
000001.log CURRENT LOCK LOG MANIFEST-000000
  • chains/chains目录下的mychannel目录channel的名称,Fabric支持多通道的机制,而通道之间的账本是隔离的,每个通道都有自己的账本空间。
  • chains/index目录包含levelDB数据库文件,存储区块索引数据库,使用leveldb实现。
  • historyLeveldb目录存储智能合约中写入的key的历史记录的索引地址,使用leveldb实现。
  • ledgerProvider目录存储当前节点所包含channel的信息(已经创建的channel id 和正在创建中的channel id),使用leveldb实现。
  • stateLeveldb目录存储智能合约写入的数据,可选择使用leveldb或couchDB,即状态数据库。

索引

区块索引用于快速定位区块。索引键可以是区块高度、区块哈希、交易哈希。索引值为区块文件编号+文件内偏移量+区块数据长度。

Hyperledger Fabric提供了多种区块索引的方式,以便能快速找到区块。索引的内容是文件位置指针(File Location Pointer)。文件位置指针由三个部分组成:所在文件的编号(fileSuffixNum)、文件内的偏移量(offset)、区块占用的字节数(bytesLength)。

整个网络有一条系统链,保存有网络的配置信息,比如MSP、各种策略、各种配置项,系统链在排序服务中(即,peer上没有?),保存在ordering节点的系统channel中,任何改变整个网络配置的,都会产生一个新的配置块添加到系统链上去。

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#system-chain

共识/排序服务

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/orderer/ordering_service.html

kafka

共识集群由多个排序服务节点(OSN)和一个Kafka集群组成。排序节点之间不直接通信,仅仅与Kafka集群通信。

在排序节点的实现里,通道(Channel)在Kafka中是以主题topic的形式隔离。

每个排序节点内部,针对每个通道都会建立与Kafka集群对应topic的生产者及消费者。生产者将排序节点收到的交易发送到Kafka集群进行排序,在生产的同时,消费者也同步消费排序后的交易。

Raft

fabric的raft基于etcd的raft,raft是CFT,是leader-follower模型。

需要注意的一点是,所有channel共用排序集群,但每个channel都有各自的Raft实例,所以每个channel可以选举自己的leader。

虽然所有的 Raft 节点都必须包含在系统通道中,但他们并不需要都包含在应用通道中。通道创建者(和通道管理员)拥有选择可用排序节点子集的能力并且可以根据需要增加或移除排序节点(只要每次只增加会移除一个节点)。

where a leader node is elected (per channel) and its decisions are replicated by the followers.

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#raft

通信

Gossip

gossip是数据扩散协议,有3个功能:

  1. 管理节点发现和channel的成员关系
  2. 同channel上的所有peer扩散账本数据
  3. 同channel上的所有peer间同步账本状态

更多信息:https://hyperledger-fabric.readthedocs.io/en/release-1.4/gossip.html

Gossip 协议最终的目的是将数据分发到网络中的每一个节点,Gossip数据分发协议实现了两种数据传输方式。

推送数据
  1. 网络中的某个节点随机选择N个节点作为数据接收对象,N配置在配置文件中
  2. 该节点向其选中的N个节点传输相应的信息
  3. 接收到信息的节点处理它接收到的数据
  4. 接收到数据的节点再从第一步开始重复执行

拉去数据
  1. 某个节点周期性地选择随机N个节点询问有没有最新的信息
  2. 收到请求的节点回复请求节点其最近未收到的信息

数据同步

节点之间使用数据同步保证账本数据和状态数据的即使更新,数据同步主要有2类:

  1. 主动广播,比如广播新打包的区块,排序节点把区块发送给主节点,它基于Gossip的推送数据
  2. 主动请求,比如新加入的节点,向已存在的其他组织锚节点请求数据,锚节点给他响应,它基于Gossip的拉去数据

应用开发

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/developing_applications.html

Fabric网络

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/network/network.html

文档里介绍了以上各种概念在网络中的位置。

由于你知我知的网络原因,开发者遇到了以下问题:

  1. brew/apt-get/yum等安装软件慢、更新慢
  2. docker下载镜像慢
  3. go get某些package无法访问、超时

怎么解决?

  1. 挂代理,实现科学上网
  2. 换镜像,曲线救国

镜像都在国内,所以镜像效果比代理好。

换代理请看让终端科学上网

接下来看几个常用的镜像。

Linux发行版镜像

阿里镜像首页列出了所有发行版的镜像状态,以及【帮助】,展示了如何更换源。

这里不仅包含了发行版的镜像,还有homebrew、docker,但我认为这2个阿里的镜像不太好用,但列出来了。

Brew镜像

你需要让Homebrew飞

Docker镜像

使用加速器的原理是,docker deamon会先去加速器寻找镜像,如果找不到才从docker官方仓库拉镜像。如果指定拉某个镜像仓库的镜像,镜像加速器是用不上的。

看如何配置Docker镜像加速器

推荐使用阿里云、七牛、DaoCloud的镜像。

Go modules代理

现在国内已经有第三方的Go modules代理服务了,比如:

  1. goproxy.io,是盛奥飞小哥捐给了七牛搭建的Go modules代理服务。
  2. aliyun goproxy,阿里云昨天(大概2019年07月15日)刚开放了Go modules代理服务。

fabric使用vendor,下载各种东西的时候需要翻墙,即便是可以翻墙,也是有缺点的:

  1. 慢。
  2. 翻墙有流量限制。

Homebrew源

homebrew默认使用的是Github,虽然已经科学上网了,速度依然是KB级别的,相当的慢。使用国内的源,速度有质的提升,推荐2个国内的:

腾讯:

清华大学:

建议ping一下以上2个源,选延时小的。

下载和修改安装脚本

下载官方安装脚本:

1
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install >> brew_install

修改官方脚本,把Github源替换为腾讯源:

1
2
3
4
5
6
7
8
9
#!/usr/bin/ruby
# This script installs to /usr/local only. To install elsewhere (which is
# unsupported) you can untar https://github.com/Homebrew/brew/tarball/master
# anywhere you like.
HOMEBREW_PREFIX = "/usr/local".freeze
HOMEBREW_REPOSITORY = "/usr/local/Homebrew".freeze
HOMEBREW_CORE_TAP = "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core".freeze
HOMEBREW_CACHE = "#{ENV["HOME"]}/Library/Caches/Homebrew".freeze
BREW_REPO = "https://github.com/Homebrew/brew.git".freeze

BREW_REPO = "https://github.com/Homebrew/brew.git".freeze替换为BREW_REPO = "https://mirrors.cloud.tencent.com/homebrew/brew.git".freeze

运行脚本安装

1
/usr/bin/ruby ~/brew_install

这个版本的安装脚本已经没有CORE_TAP_REPO了,所以下载homebrew core的时候依然去Github下载,非常慢,可以在brew.git下载完,control-c结束掉。

把仓库https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git克隆到/usr/local/Homebrew/Library/Taps/homebrew/目录,然后再执行上面的安装脚本。

更换brew源

如果brew已经安装了,直接修改源就行了。

1、替换brew.git和homebrew-core.git的源:

1
2
3
4
5
6
cd "$(brew --repo)"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/brew.git


cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git

2、更新 bottles源

对于bash用户:

1
2
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile

对于zsh用户

1
2
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc

代码Review的时候,遇到过一些log滥用的情况,今天聊一聊滥用(过渡使用)日志。

好的log能够帮助开发人员快速定位bug,而差的log各有各的不同。

你滥用日志了吗?

是什么导致了滥用log?是不是存在这些误解:

1. 害怕出了问题,现有的log无法定位,要多加一些log,恨不得每段都有一个log,log数简直越多越好,看日志有一种,每一步都非常清晰的错觉。

2. 不知道log多了,定位效率更低,试问你有没有经历过几分钟刷出了G级别日志文件?在这种日志文件里定位bug,简直是大海捞针,这让log的价值非常低。

3. 不知道log多了会影响性能,log自身涉及格式化和文件读写,虽然现在各log库都已经比较高效了,但是,这也扛不住“海量”的log啊,积少成多,势必影响程序性能。

4. 对log级别错误的认知:日志级别设置为Info,Debug、Trace级别的日志不会打印,Debug、Trace级别日志多没关系。虽然日志不会输出,并不代表相关代码没执行啊。

第4点重点解释一下:

debug-demo

这是一个打印Debug级别的日志,它还有1项日志信息,是来自func()的结果,请问:

  1. 日志级别设置为Info,log.Debug会执行吗?func()还会执行吗?
  2. 如果这行日志频繁被执行,是不是浪费了CPU做无用功?

如果你认为不会执行,看下面的Demo,log使用zap。

log-test

结果:

log-ret

事实证明无论限制的日志级别是什么,log.***一定会被调用,它入参中的函数也一定会被调用,只不过是日记级别不满足打印时,不会打印而已。被调函数的结果只被这条log.***使用,结果这个日志根本不打印,这就浪费了CPU。

日志级别都设置为Info了,Debug级别的日志为何还会打印?

如果你有这个问题,你可能没有理解2个地方。

日志级别设置为Info,不代表log.Debug函数不执行。log.Debug函数一定会执行,看下图,log.Info,Error等接口会调用相同的真实实现函数log.loglog.log的入参包含了log.Info等接口的入参,以及当前的log_level,比如以下2种是等价的:

log-log

所以,无论设置的是什么日志级别控制,log.Debug一定会被执行,至于当前日志是否会打印,会在log.log里决定。

image-20190712072742045

日志为Warn级别,Debug日志不会打印,func()会不会执行?

日志打印本质是函数调用,会先计算入参,再调用函数。比如:

log-debug

所以func()一定会被调用。

总结

针对滥用日志的情况给几点建议:

  1. 1条日志描述清when、where、what,提供有效信息,这就对定位很有帮助了。
  2. 只在“可能”出问题的地方打印日志,一些能根据上下文日志推断的地方,就无需再增加日志。
  3. 日志打印不要调用函数。
  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/07/12/do-not-abuse-of-log/
关注公众号,获取最新Golang文章

初次接触fabric会遇到各种构建问题,坑很多,网上有各种规避办法,但规避不是解决办法,所以决定把fabric的Makefile扫一遍。

fabric的Makefile包含了fabric所有的构建信息,掌握了这个Makefile,遇到任何构建问题,我相信你都能找到问题的根源,并从根上解决问题。而不是遇到问题,就网上找资料,结果做了很多无用功,也无法解决问题。

Makefile文件就在fabric的根目录下,该文件还引入了另外2个Makefile文件:

  1. docker-env.mk,这个文件描述了Docker构建先关的信息
  2. gotools.mk,这个文件描述了go tools相关的构建信息

我们先介绍Makefile文件,然后介绍另外2个文件,如果想在Makefile遇到这2个文件的时候查看,可随时使用目录跳转去查看即可。

即使掌握了Makefile,仍然会遇到一些问题,所以最后会给出一些建议,让你少踩一些坑。

本文基于fabric 1.4,commit id:9dce73,不同版本可能有细微差别,但不影响掌握构建过程。

Makefile详细解读

Makefile看起来有点长,磨刀不误砍柴工,花2个小时,是非常有益处的,建议多看几遍,吃透编译流程。

fabric的Makefile

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
# Copyright IBM Corp All Rights Reserved.
# Copyright London Stock Exchange Group All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
# -------------------------------------------------------------
# This makefile defines the following targets
#
# make项列表
#
# 构建所有
# - all (default) - builds all targets and runs all non-integration tests/checks
# 运行所有测试和检查
# - checks - runs all non-integration tests/checks
# 运行linter和verify来检查改动的文件
# - desk-check - runs linters and verify to test changed packages
# 构建configtxgen,主要用来创建创世块、创建通道时的配置交易、更新通道的锚点交易
# - configtxgen - builds a native configtxgen binary
# 构建configtxlator,configtxgen生成的配置是二进制,使用configtxlator转换为json
# - configtxlator - builds a native configtxlator binary
# 构建cryptogen,提供加解密的程序
# - cryptogen - builds a native cryptogen binary
# 构建idemixgen,用来创建身份(id)混合器创建配置文件
# - idemixgen - builds a native idemixgen binary
# peer节点
# - peer - builds a native fabric peer binary
# 排序节点
# - orderer - builds a native fabric orderer binary
# 发布当前平台的包
# - release - builds release packages for the host platform
# 发布所有平台的包
# - release-all - builds release packages for all target platforms
# 跑单元测试
# - unit-test - runs the go-test based unit tests
# 对更改过的文件跑单元测试
# - verify - runs unit tests for only the changed package tree
# 以coverprofile模式对所有pkg跑单元测试
# - profile - runs unit tests for all packages in coverprofile mode (slow)
# - test-cmd - generates a "go test" string suitable for manual customization
# 安装go tools,TODO 安装到哪,镜像还是外部GOPATH下?
# - gotools - installs go tools like golint
# 对所有代码运行lint
# - linter - runs all code checks
# 检查dep依赖
# - check-deps - check for vendored dependencies that are no longer used
# 检查所有代码Apache license
# - license - checks go source files for Apache license header
# 构建所有的native程序,包含peer,orderer等
# - native - ensures all native binaries are available
# 构建所有的docker镜像,docker-clean为清除镜像
# - docker[-clean] - ensures all docker images are available[/cleaned]
# 列出所有相关的docker镜像
# - docker-list - generates a list of docker images that 'make docker' produces
# 构建peer-docker镜像
# - peer-docker[-clean] - ensures the peer container is available[/cleaned]
# 构建orderer-docker镜像
# - orderer-docker[-clean] - ensures the orderer container is available[/cleaned]
# 构建tools-docker镜像
# - tools-docker[-clean] - ensures the tools container is available[/cleaned]
# 基于.proto文件生成所有的protobuf文件
# - protos - generate all protobuf artifacts based on .proto files
# 清理所有构建数据
# - clean - cleans the build area
# 比clean更牛,还会清理掉持久状态数据
# - clean-all - superset of 'clean' that also removes persistent state
# 清理发布的包
# - dist-clean - clean release packages for all target platforms
# 清理单元测试状态数据
# - unit-test-clean - cleans unit test state (particularly from docker)
# 执行基本的检查,比如license,拼写,lint等
# - basic-checks - performs basic checks like license, spelling, trailing spaces and linter
# CI使用的选项
# - enable_ci_only_tests - triggers unit-tests in downstream jobs. Applicable only for CI not to
# use in the local machine.
# 拉去第三方docker镜像
# - docker-thirdparty - pulls thirdparty images (kafka,zookeeper,couchdb)
# 把所有make docker所产生的镜像,打上latest tag
# - docker-tag-latest - re-tags the images made by 'make docker' with the :latest tag
# 把所有make docker所产生的镜像,打上stable tag
# - docker-tag-stable - re-tags the images made by 'make docker' with the :stable tag
# 生成命令参考文档
# - help-docs - generate the command reference docs

# 基础版本
BASE_VERSION = 1.4.2
# 前一个版本
PREV_VERSION = 1.4.1
# chaintool版本
CHAINTOOL_RELEASE=1.1.3
# 基础镜像版本
BASEIMAGE_RELEASE=0.4.15

# 设置项目名称,如果没有设置,则使用hyperledger
# Allow to build as a submodule setting the main project to
# the PROJECT_NAME env variable, for example,
# export PROJECT_NAME=hyperledger/fabric-test
ifeq ($(PROJECT_NAME),true)
PROJECT_NAME = $(PROJECT_NAME)/fabric
else
PROJECT_NAME = hyperledger/fabric
endif

# 构建路径
# ?=指当没有指定BUILD_DIR时,才使用默认的`.build`作为构建目录
BUILD_DIR ?= .build
# 未知,全文未使用
NEXUS_REPO = nexus3.hyperledger.org:10001/hyperledger

# 额外版本:git commit号
EXTRA_VERSION ?= $(shell git rev-parse --short HEAD)
# 项目版本由基础版本和额外版本组成
PROJECT_VERSION=$(BASE_VERSION)-snapshot-$(EXTRA_VERSION)

# Go编译信息
# 设置包名
PKGNAME = github.com/$(PROJECT_NAME)
# CGO编译选项
CGO_FLAGS = CGO_CFLAGS=" "
# 当前CPU架构
ARCH=$(shell go env GOARCH)
# OS和CPU架构
MARCH=$(shell go env GOOS)-$(shell go env GOARCH)

# Go编译时传入的版本信息,主要是docker相关信息,比如
## var Version string = "latest"
## var CommitSHA string = "development build"
## var BaseVersion string = "0.4.15"
## var BaseDockerLabel string = "org.hyperledger.fabric"
## var DockerNamespace string = "hyperledger"
## var BaseDockerNamespace string = "hyperledger"

# defined in common/metadata/metadata.go
METADATA_VAR = Version=$(BASE_VERSION)
METADATA_VAR += CommitSHA=$(EXTRA_VERSION)
METADATA_VAR += BaseVersion=$(BASEIMAGE_RELEASE)
METADATA_VAR += BaseDockerLabel=$(BASE_DOCKER_LABEL)
METADATA_VAR += DockerNamespace=$(DOCKER_NS)
METADATA_VAR += BaseDockerNamespace=$(BASE_DOCKER_NS)

# 使用GO_LDFLAGS设置go的ldflag信息,传入METADATA_VAR
# patsubst指替换通配符
GO_LDFLAGS = $(patsubst %,-X $(PKGNAME)/common/metadata.%,$(METADATA_VAR))

GO_TAGS ?=

# chaintool下载链接
CHAINTOOL_URL ?= https://nexus.hyperledger.org/content/repositories/releases/org/hyperledger/fabric/hyperledger-fabric/chaintool-$(CHAINTOOL_RELEASE)/hyperledger-fabric-chaintool-$(CHAINTOOL_RELEASE).jar

export GO_LDFLAGS GO_TAGS

# 检查go、docker、git、curl这几个程序是否存在
EXECUTABLES ?= go docker git curl
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH: Check dependencies")))

# Go shim的依赖项,shim是chaincode的一个模块,可以先不去理解
GOSHIM_DEPS = $(shell ./scripts/goListFiles.sh $(PKGNAME)/core/chaincode/shim)

# protobuf相关的文件
PROTOS = $(shell git ls-files *.proto | grep -Ev 'vendor/|testdata/')

# 项目文件,不包含git、样例、图片、vendor等文件
# No sense rebuilding when non production code is changed
PROJECT_FILES = $(shell git ls-files | grep -v ^test | grep -v ^unit-test | \
grep -v ^.git | grep -v ^examples | grep -v ^devenv | grep -v .png$ | \
grep -v ^LICENSE | grep -v ^vendor )
# docker镜像发布模板
RELEASE_TEMPLATES = $(shell git ls-files | grep "release/templates")
# 镜像列表
IMAGES = peer orderer ccenv buildenv tools
# 发布平台
RELEASE_PLATFORMS = windows-amd64 darwin-amd64 linux-amd64 linux-s390x linux-ppc64le
# 发布的package
RELEASE_PKGS = configtxgen cryptogen idemixgen discover configtxlator peer orderer

# 要发的pkg和它们的路径
pkgmap.cryptogen := $(PKGNAME)/common/tools/cryptogen
pkgmap.idemixgen := $(PKGNAME)/common/tools/idemixgen
pkgmap.configtxgen := $(PKGNAME)/common/tools/configtxgen
pkgmap.configtxlator := $(PKGNAME)/common/tools/configtxlator
pkgmap.peer := $(PKGNAME)/peer
pkgmap.orderer := $(PKGNAME)/orderer
pkgmap.block-listener := $(PKGNAME)/examples/events/block-listener
pkgmap.discover := $(PKGNAME)/cmd/discover

# 把docker-env.mk包含进来,主要是docker构建相关的选项
include docker-env.mk

# all包含/依赖了编译程序、编译镜像和进行检查
# all会进行检查,本地编译和发布docker镜像
all: native docker checks

# 检查包含/依赖了基本检查、单元测试和集成测试
checks: basic-checks unit-test integration-test

# 基本检查指许可证、拼写和格式
basic-checks: license spelling trailing-spaces linter check-metrics-doc

# 包含/依赖检查和验证
desk-check: checks verify

help-docs: native
@scripts/generateHelpDocs.sh

# 拉取第三方镜像,并打上tag,BASE_DOCKER_TAG定义在docker-env.mk
# 都是fabric定制的couchdb、zookeeper、kafka镜像
# Pull thirdparty docker images based on the latest baseimage release version
.PHONY: docker-thirdparty
docker-thirdparty:
docker pull $(BASE_DOCKER_NS)/fabric-couchdb:$(BASE_DOCKER_TAG)
docker tag $(BASE_DOCKER_NS)/fabric-couchdb:$(BASE_DOCKER_TAG) $(DOCKER_NS)/fabric-couchdb
docker pull $(BASE_DOCKER_NS)/fabric-zookeeper:$(BASE_DOCKER_TAG)
docker tag $(BASE_DOCKER_NS)/fabric-zookeeper:$(BASE_DOCKER_TAG) $(DOCKER_NS)/fabric-zookeeper
docker pull $(BASE_DOCKER_NS)/fabric-kafka:$(BASE_DOCKER_TAG)
docker tag $(BASE_DOCKER_NS)/fabric-kafka:$(BASE_DOCKER_TAG) $(DOCKER_NS)/fabric-kafka

# 调用脚本执行拼写检查
.PHONY: spelling
spelling:
@scripts/check_spelling.sh

# 调用脚本执行许可证检查
.PHONY: license
license:
@scripts/check_license.sh

# 调用脚本执行末尾空格检查
.PHONY: trailing-spaces
trailing-spaces:
@scripts/check_trailingspaces.sh

# 包含gotools.mk,这个文件主要用来安装一些gotools,可以使用单个命令来装某个gotools,比如安装dep
# `make gotool.dep`,具体见该文件
include gotools.mk

# 实际调用gotools-install安装相关的gotools
.PHONY: gotools
gotools: gotools-install

# 以下这段设置是各程序的依赖
# 编译peer,依赖./build/bin/peer
# 编译peer-docker,依赖./build/image/peer/$(DUMMY),DUMMY指DOCKER-TAG,定义在docker-env.mk
.PHONY: peer
peer: $(BUILD_DIR)/bin/peer
peer-docker: $(BUILD_DIR)/image/peer/$(DUMMY)

# orderer和镜像的依赖
.PHONY: orderer
orderer: $(BUILD_DIR)/bin/orderer
orderer-docker: $(BUILD_DIR)/image/orderer/$(DUMMY)

# 编译configtxgen的依赖
.PHONY: configtxgen
configtxgen: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.CommitSHA=$(EXTRA_VERSION)
configtxgen: $(BUILD_DIR)/bin/configtxgen

# 编译configtxlator的依赖
configtxlator: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.CommitSHA=$(EXTRA_VERSION)
configtxlator: $(BUILD_DIR)/bin/configtxlator

# 编译cryptogen的依赖
cryptogen: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.CommitSHA=$(EXTRA_VERSION)
cryptogen: $(BUILD_DIR)/bin/cryptogen

# 编译idemixgen的依赖
idemixgen: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.CommitSHA=$(EXTRA_VERSION)
idemixgen: $(BUILD_DIR)/bin/idemixgen

# 编译discover的依赖
discover: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.Version=$(PROJECT_VERSION)
discover: $(BUILD_DIR)/bin/discover

# 编译tools相关的docker
tools-docker: $(BUILD_DIR)/image/tools/$(DUMMY)

# 生成构建环境(buildenv)镜像
buildenv: $(BUILD_DIR)/image/buildenv/$(DUMMY)

# 未知
ccenv: $(BUILD_DIR)/image/ccenv/$(DUMMY)

# 进行集成测试
.PHONY: integration-test
integration-test: gotool.ginkgo ccenv docker-thirdparty
./scripts/run-integration-tests.sh

# 进行单元测试
unit-test: unit-test-clean peer-docker docker-thirdparty ccenv
unit-test/run.sh

# 进行单元测试
unit-tests: unit-test

# CI选项
enable_ci_only_tests: unit-test

# 运行verify,就像注释说的,依然是单元测试
verify: export JOB_TYPE=VERIFY
verify: unit-test

# 运行带有profile的单元测试
profile: export JOB_TYPE=PROFILE
profile: unit-test

# Generates a string to the terminal suitable for manual augmentation / re-issue, useful for running tests by hand
test-cmd:
@echo "go test -tags \"$(GO_TAGS)\""

# 编译所有docker镜像,依赖都是.build/image下
docker: $(patsubst %,$(BUILD_DIR)/image/%/$(DUMMY), $(IMAGES))

# 编译所有native程序,native指所有fabric本身的程序,依赖如下
native: peer orderer configtxgen cryptogen idemixgen configtxlator discover

# 运行linter
linter: check-deps buildenv
@echo "LINT: Running code checks.."
@$(DRUN) $(DOCKER_NS)/fabric-buildenv:$(DOCKER_TAG) ./scripts/golinter.sh

# 运行check-deps
check-deps: buildenv
@echo "DEP: Checking for dependency issues.."
@$(DRUN) $(DOCKER_NS)/fabric-buildenv:$(DOCKER_TAG) ./scripts/check_deps.sh

# 运行check-metrics-doc
check-metrics-doc: buildenv
@echo "METRICS: Checking for outdated reference documentation.."
@$(DRUN) $(DOCKER_NS)/fabric-buildenv:$(DOCKER_TAG) ./scripts/metrics_doc.sh check

# 运行generate-metrics-doc
generate-metrics-doc: buildenv
@echo "Generating metrics reference documentation..."
@$(DRUN) $(DOCKER_NS)/fabric-buildenv:$(DOCKER_TAG) ./scripts/metrics_doc.sh generate

# 安装chain tool
$(BUILD_DIR)/%/chaintool: Makefile
@echo "Installing chaintool"
@mkdir -p $(@D)
curl -fL $(CHAINTOOL_URL) > $@
chmod +x $@

# We (re)build a package within a docker context but persist the $GOPATH/pkg
# directory so that subsequent builds are faster
# 构建所有镜像和pkg
# DRUN是`docker run`和参数的简写
# 本地创建docker里要用到的gopath目录,然后挂载到docker里
# 然后在docker里按个编译pkgmap里面的程序,比如peer、orderer、cryptogen等等
$(BUILD_DIR)/docker/bin/%: $(PROJECT_FILES)
$(eval TARGET = ${patsubst $(BUILD_DIR)/docker/bin/%,%,${@}})
@echo "Building $@"
@mkdir -p $(BUILD_DIR)/docker/bin $(BUILD_DIR)/docker/$(TARGET)/pkg
@$(DRUN) \
-v $(abspath $(BUILD_DIR)/docker/bin):/opt/gopath/bin \
-v $(abspath $(BUILD_DIR)/docker/$(TARGET)/pkg):/opt/gopath/pkg \
$(BASE_DOCKER_NS)/fabric-baseimage:$(BASE_DOCKER_TAG) \
go install -tags "$(GO_TAGS)" -ldflags "$(DOCKER_GO_LDFLAGS)" $(pkgmap.$(@F))
@touch $@

# 创建本地bin目录
$(BUILD_DIR)/bin:
mkdir -p $@

# 运行changelog
changelog:
./scripts/changelog.sh v$(PREV_VERSION) v$(BASE_VERSION)

# protoc-gen-go依赖.build/docker/gotools
$(BUILD_DIR)/docker/gotools/bin/protoc-gen-go: $(BUILD_DIR)/docker/gotools

# 构建go tools的docker镜像,给payload使用
# 创建本地目录(.build/docker/gotools)并挂载到(/opt/gotools),依赖基础镜像,然后在docker中执行gotools.mk
# 最后调用gotools.mk生成程序,设置了GOTOOLS_BINDIR,生成的二进制会放在这个目录,因为这个目录映射了出来,
# 所以bin就在主机的`.build/docker/gotools/bin/`目录
# So, 如果构建成功,不需要像其他文章说的那样,需要手动拷贝protoc-gen-go到`.build/docker/gotools/bin/`目录
# 但是,如果翻墙失败,可以考虑手动复制protoc-gen-go的方式
$(BUILD_DIR)/docker/gotools: gotools.mk
@echo "Building dockerized gotools"
@mkdir -p $@/bin $@/obj
@$(DRUN) \
-v $(abspath $@):/opt/gotools \
-w /opt/gopath/src/$(PKGNAME) \
$(BASE_DOCKER_NS)/fabric-baseimage:$(BASE_DOCKER_TAG) \
make -f gotools.mk GOTOOLS_BINDIR=/opt/gotools/bin GOTOOLS_GOPATH=/opt/gotools/obj

# 构建本地的运行文件,依赖设置都在上面了,这是进行构建,与Docker类似
# 程序即pkgmap中的程序
$(BUILD_DIR)/bin/%: $(PROJECT_FILES)
@mkdir -p $(@D)
@echo "$@"
$(CGO_FLAGS) GOBIN=$(abspath $(@D)) go install -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))
@echo "Binary available as $@"
@touch $@

# 设置各镜像各自的payload文件
# 比如ccenv的payload拷贝会翻译成:cp .build/docker/gotools/bin/protoc-gen-go .build/bin/chaintool .build/goshim.tar.bz2 .build/image/ccenv/payload
# payload definitions'
$(BUILD_DIR)/image/ccenv/payload: $(BUILD_DIR)/docker/gotools/bin/protoc-gen-go \
$(BUILD_DIR)/bin/chaintool \
$(BUILD_DIR)/goshim.tar.bz2
$(BUILD_DIR)/image/peer/payload: $(BUILD_DIR)/docker/bin/peer \
$(BUILD_DIR)/sampleconfig.tar.bz2
$(BUILD_DIR)/image/orderer/payload: $(BUILD_DIR)/docker/bin/orderer \
$(BUILD_DIR)/sampleconfig.tar.bz2
$(BUILD_DIR)/image/buildenv/payload: $(BUILD_DIR)/gotools.tar.bz2 \
$(BUILD_DIR)/docker/gotools/bin/protoc-gen-go

# 各镜像payload的实际拷贝
$(BUILD_DIR)/image/%/payload:
mkdir -p $@
cp $^ $@

.PRECIOUS: $(BUILD_DIR)/image/%/Dockerfile

# 根据image下的各目录中的Dockerfile.in生成对应的Dockerfile
$(BUILD_DIR)/image/%/Dockerfile: images/%/Dockerfile.in
mkdir -p $(@D)
@cat $< \
| sed -e 's|_BASE_NS_|$(BASE_DOCKER_NS)|g' \
| sed -e 's|_NS_|$(DOCKER_NS)|g' \
| sed -e 's|_BASE_TAG_|$(BASE_DOCKER_TAG)|g' \
| sed -e 's|_TAG_|$(DOCKER_TAG)|g' \
> $@
@echo LABEL $(BASE_DOCKER_LABEL).version=$(BASE_VERSION) \\>>$@
@echo " " $(BASE_DOCKER_LABEL).base.version=$(BASEIMAGE_RELEASE)>>$@

# 根据Dockerfile生成tools-image,并打上2个tag,分别是当前版本tag和latest tag
$(BUILD_DIR)/image/tools/$(DUMMY): $(BUILD_DIR)/image/tools/Dockerfile
$(eval TARGET = ${patsubst $(BUILD_DIR)/image/%/$(DUMMY),%,${@}})
@echo "Building docker $(TARGET)-image"
$(DBUILD) -t $(DOCKER_NS)/fabric-$(TARGET) -f $(@D)/Dockerfile .
docker tag $(DOCKER_NS)/fabric-$(TARGET) $(DOCKER_NS)/fabric-$(TARGET):$(DOCKER_TAG)
docker tag $(DOCKER_NS)/fabric-$(TARGET) $(DOCKER_NS)/fabric-$(TARGET):$(ARCH)-latest
@touch $@

# 根据Dockerfile、payload生成image下的所有镜像,比如orderer,然后打上tag
$(BUILD_DIR)/image/%/$(DUMMY): Makefile $(BUILD_DIR)/image/%/payload $(BUILD_DIR)/image/%/Dockerfile
$(eval TARGET = ${patsubst $(BUILD_DIR)/image/%/$(DUMMY),%,${@}})
@echo "Building docker $(TARGET)-image"
$(DBUILD) -t $(DOCKER_NS)/fabric-$(TARGET) $(@D)
docker tag $(DOCKER_NS)/fabric-$(TARGET) $(DOCKER_NS)/fabric-$(TARGET):$(DOCKER_TAG)
docker tag $(DOCKER_NS)/fabric-$(TARGET) $(DOCKER_NS)/fabric-$(TARGET):$(ARCH)-latest
@touch $@

# 打包gotools
$(BUILD_DIR)/gotools.tar.bz2: $(BUILD_DIR)/docker/gotools
(cd $</bin && tar -jc *) > $@

# 打包goshim
$(BUILD_DIR)/goshim.tar.bz2: $(GOSHIM_DEPS)
@echo "Creating $@"
@tar -jhc -C $(GOPATH)/src $(patsubst $(GOPATH)/src/%,%,$(GOSHIM_DEPS)) > $@

# 打包sampleconfig
$(BUILD_DIR)/sampleconfig.tar.bz2: $(shell find sampleconfig -type f)
(cd sampleconfig && tar -jc *) > $@

# 打包protos
$(BUILD_DIR)/protos.tar.bz2: $(PROTOS)

$(BUILD_DIR)/%.tar.bz2:
@echo "Creating $@"
@tar -jc $^ > $@

# 发布当前平台的relase包
# builds release packages for the host platform
release: $(patsubst %,release/%, $(MARCH))

# builds release packages for all target platforms
release-all: $(patsubst %,release/%, $(RELEASE_PLATFORMS))

release/%: GO_LDFLAGS=-X $(pkgmap.$(@F))/metadata.CommitSHA=$(EXTRA_VERSION)

release/windows-amd64: GOOS=windows
release/windows-amd64: $(patsubst %,release/windows-amd64/bin/%, $(RELEASE_PKGS))

release/darwin-amd64: GOOS=darwin
release/darwin-amd64: $(patsubst %,release/darwin-amd64/bin/%, $(RELEASE_PKGS))

release/linux-amd64: GOOS=linux
release/linux-amd64: $(patsubst %,release/linux-amd64/bin/%, $(RELEASE_PKGS))

release/%-amd64: GOARCH=amd64
release/linux-%: GOOS=linux

release/linux-s390x: GOARCH=s390x
release/linux-s390x: $(patsubst %,release/linux-s390x/bin/%, $(RELEASE_PKGS))

release/linux-ppc64le: GOARCH=ppc64le
release/linux-ppc64le: $(patsubst %,release/linux-ppc64le/bin/%, $(RELEASE_PKGS))

release/%/bin/configtxlator: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/configtxgen: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/cryptogen: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/idemixgen: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/discover: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/orderer: GO_LDFLAGS = $(patsubst %,-X $(PKGNAME)/common/metadata.%,$(METADATA_VAR))

release/%/bin/orderer: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

release/%/bin/peer: GO_LDFLAGS = $(patsubst %,-X $(PKGNAME)/common/metadata.%,$(METADATA_VAR))

release/%/bin/peer: $(PROJECT_FILES)
@echo "Building $@ for $(GOOS)-$(GOARCH)"
mkdir -p $(@D)
$(CGO_FLAGS) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(abspath $@) -tags "$(GO_TAGS)" -ldflags "$(GO_LDFLAGS)" $(pkgmap.$(@F))

.PHONY: dist
dist: dist-clean dist/$(MARCH)

dist-all: dist-clean $(patsubst %,dist/%, $(RELEASE_PLATFORMS))

dist/%: release/%
mkdir -p release/$(@F)/config
cp -r sampleconfig/*.yaml release/$(@F)/config
cd release/$(@F) && tar -czvf hyperledger-fabric-$(@F).$(PROJECT_VERSION).tar.gz *

# 在docker中生成protobuf文件
.PHONY: protos
protos: buildenv
@$(DRUN) $(DOCKER_NS)/fabric-buildenv:$(DOCKER_TAG) ./scripts/compile_protos.sh

%-docker-list:
$(eval TARGET = ${patsubst %-docker-list,%,${@}})
@echo $(DOCKER_NS)/fabric-$(TARGET):$(DOCKER_TAG)

# 列出当前所有镜像
docker-list: $(patsubst %,%-docker-list, $(IMAGES))

%-docker-clean:
$(eval TARGET = ${patsubst %-docker-clean,%,${@}})
-docker images --quiet --filter=reference='$(DOCKER_NS)/fabric-$(TARGET):$(ARCH)-$(BASE_VERSION)$(if $(EXTRA_VERSION),-snapshot-*,)' | xargs docker rmi -f
-@rm -rf $(BUILD_DIR)/image/$(TARGET) ||:

# 清理所有镜像
docker-clean: $(patsubst %,%-docker-clean, $(IMAGES))

docker-tag-latest: $(IMAGES:%=%-docker-tag-latest)

%-docker-tag-latest:
$(eval TARGET = ${patsubst %-docker-tag-latest,%,${@}})
docker tag $(DOCKER_NS)/fabric-$(TARGET):$(DOCKER_TAG) $(DOCKER_NS)/fabric-$(TARGET):latest

docker-tag-stable: $(IMAGES:%=%-docker-tag-stable)

%-docker-tag-stable:
$(eval TARGET = ${patsubst %-docker-tag-stable,%,${@}})
docker tag $(DOCKER_NS)/fabric-$(TARGET):$(DOCKER_TAG) $(DOCKER_NS)/fabric-$(TARGET):stable

.PHONY: clean
clean: docker-clean unit-test-clean release-clean
-@rm -rf $(BUILD_DIR)

# 清理所有状态数据,依赖tools清理,发包清理
.PHONY: clean-all
clean-all: clean gotools-clean dist-clean
-@rm -rf /var/hyperledger/*
-@rm -rf docs/build/

# 发布版本清理
.PHONY: dist-clean
dist-clean:
-@rm -rf release/windows-amd64/hyperledger-fabric-windows-amd64.$(PROJECT_VERSION).tar.gz
-@rm -rf release/darwin-amd64/hyperledger-fabric-darwin-amd64.$(PROJECT_VERSION).tar.gz
-@rm -rf release/linux-amd64/hyperledger-fabric-linux-amd64.$(PROJECT_VERSION).tar.gz
-@rm -rf release/linux-s390x/hyperledger-fabric-linux-s390x.$(PROJECT_VERSION).tar.gz
-@rm -rf release/linux-ppc64le/hyperledger-fabric-linux-ppc64le.$(PROJECT_VERSION).tar.gz

%-release-clean:
$(eval TARGET = ${patsubst %-release-clean,%,${@}})
-@rm -rf release/$(TARGET)

# 发包清理
release-clean: $(patsubst %,%-release-clean, $(RELEASE_PLATFORMS))

# 单元测试清理
.PHONY: unit-test-clean
unit-test-clean:
cd unit-test && docker-compose down

docker env的Makefile

docker-env.mk主要是Docker镜像构建相关的设置。

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
// docker-env.mk
# Copyright London Stock Exchange Group All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Mac上设置--user选项
ifneq ($(shell uname),Darwin)
DOCKER_RUN_FLAGS=--user=$(shell id -u)
endif

# 架构是s390x,uid不是0(root账号)的时候,-v选项
ifeq ($(shell uname -m),s390x)
ifneq ($(shell id -u),0)
DOCKER_RUN_FLAGS+=-v /etc/passwd:/etc/passwd:ro
endif
endif

# 以下是http和https的代理设置
ifneq ($(http_proxy),)
DOCKER_BUILD_FLAGS+=--build-arg 'http_proxy=$(http_proxy)'
DOCKER_RUN_FLAGS+=-e 'http_proxy=$(http_proxy)'
endif
ifneq ($(https_proxy),)
DOCKER_BUILD_FLAGS+=--build-arg 'https_proxy=$(https_proxy)'
DOCKER_RUN_FLAGS+=-e 'https_proxy=$(https_proxy)'
endif
ifneq ($(HTTP_PROXY),)
DOCKER_BUILD_FLAGS+=--build-arg 'HTTP_PROXY=$(HTTP_PROXY)'
DOCKER_RUN_FLAGS+=-e 'HTTP_PROXY=$(HTTP_PROXY)'
endif
ifneq ($(HTTPS_PROXY),)
DOCKER_BUILD_FLAGS+=--build-arg 'HTTPS_PROXY=$(HTTPS_PROXY)'
DOCKER_RUN_FLAGS+=-e 'HTTPS_PROXY=$(HTTPS_PROXY)'
endif
ifneq ($(no_proxy),)
DOCKER_BUILD_FLAGS+=--build-arg 'no_proxy=$(no_proxy)'
DOCKER_RUN_FLAGS+=-e 'no_proxy=$(no_proxy)'
endif
ifneq ($(NO_PROXY),)
DOCKER_BUILD_FLAGS+=--build-arg 'NO_PROXY=$(NO_PROXY)'
DOCKER_RUN_FLAGS+=-e 'NO_PROXY=$(NO_PROXY)'
endif

# DRUN代表docker run,并伴随以下参数,把当前路径映射到容器的gopath对应路径下
# 并且设置容器内的工作目录
DRUN = docker run -i --rm $(DOCKER_RUN_FLAGS) \
-v $(abspath .):/opt/gopath/src/$(PKGNAME) \
-w /opt/gopath/src/$(PKGNAME)

# docker build
DBUILD = docker build $(DOCKER_BUILD_FLAGS)

# 基础docker namespace设置时,使用hyperledger
BASE_DOCKER_NS ?= hyperledger
# 基础docker tag,由arch和release组成docker tag
BASE_DOCKER_TAG=$(ARCH)-$(BASEIMAGE_RELEASE)

# 与上面类似
DOCKER_NS ?= hyperledger
DOCKER_TAG=$(ARCH)-$(PROJECT_VERSION)
PREV_TAG=$(ARCH)-$(PREV_VERSION)

# 基础镜像标签
BASE_DOCKER_LABEL=org.hyperledger.fabric

# 动态连接信息
DOCKER_DYNAMIC_LINK ?= false
# Docker内的ldfalgs信息,继承makefile的
DOCKER_GO_LDFLAGS += $(GO_LDFLAGS)

ifeq ($(DOCKER_DYNAMIC_LINK),false)
DOCKER_GO_LDFLAGS += -linkmode external -extldflags '-static -lpthread'
endif

#
# What is a .dummy file?
#
# Make is designed to work with files. It uses the presence (or lack thereof)
# and timestamps of files when deciding if a given target needs to be rebuilt.
# Docker containers throw a wrench into the works because the output of docker
# builds do not translate into standard files that makefile rules can evaluate.
# Therefore, we have to fake it. We do this by constructioning our rules such
# as
# my-docker-target/.dummy:
# docker build ...
# touch $@
#
# If the docker-build succeeds, the touch operation creates/updates the .dummy
# file. If it fails, the touch command never runs. This means the .dummy
# file follows relatively 1:1 with the underlying container.
#
# This isn't perfect, however. For instance, someone could delete a docker
# container using docker-rmi outside of the build, and make would be fooled
# into thinking the dependency is statisfied when it really isn't. This is
# our closest approximation we can come up with.
#
# As an aside, also note that we incorporate the version number in the .dummy
# file to differentiate different tags to fix FAB-1145
#
DUMMY = .dummy-$(DOCKER_TAG)

# Make是跟文件打交道的,它使用文件位置(?)和时间戳觉得是否要重新构建文件。
# 但Docker容器产生的文件是make无法识别的。所以做了适配,docker build运行
# 成功时,touch回去创建或更新dummy文件,如果失败则touch不执行。
# 这样保证了dummy文件和容器保持一对一的关系。

go tools的Makefile

gotools指的一些列go语言的工具,并不是golang/tools仓库,具体是哪些tools,请看Makefile文件解析。

这些tools的安装有2种方式:

  1. 少数几个支持从vendor目录直接安装
  2. 默认方式是使用go get方式安装,所以请翻墙

另外,gotools.mk实际是在docker中运行的,也就是生成的程序都在docker镜像中,在当前host并没有运行,具体看gotools.mk调用的地方。调用处把GOBIN设置为了.build/docker/gotools/bin,并映射到了docker,构建后可以查看生成的程序:

1
2
➜  fabric git:(r1.4) ls .build/docker/gotools/bin
counterfeiter dep ginkgo gocov gocov-xml goimports golint manifest-tool misspell mockery protoc-gen-go

Makefile注释:

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
// gotools.mk
# Copyright IBM Corp All Rights Reserved.
# Copyright London Stock Exchange Group All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0

# 所有相关的tools,之所以叫go tools,因为是Go语言的,并不是指golang/tools仓库
GOTOOLS = counterfeiter dep golint goimports protoc-gen-go ginkgo gocov gocov-xml misspell mockery manifest-tool
# 构建目录与Makefile保持一致
BUILD_DIR ?= .build
# 构建gotools是的GOPATH,也就源码所在目录,当未设置时,使用默认路径
GOTOOLS_GOPATH ?= $(BUILD_DIR)/gotools
# gotools的生成二进制位置,当未设置时,使用GOPATH/bin
GOTOOLS_BINDIR ?= $(GOPATH)/bin

# 每个tool的目录映射
# go tool->path mapping
go.fqp.counterfeiter := github.com/maxbrunsfeld/counterfeiter
go.fqp.gocov := github.com/axw/gocov/gocov
go.fqp.gocov-xml := github.com/AlekSi/gocov-xml
go.fqp.goimports := golang.org/x/tools/cmd/goimports
go.fqp.golint := golang.org/x/lint/golint
go.fqp.manifest-tool := github.com/estesp/manifest-tool
go.fqp.misspell := github.com/client9/misspell/cmd/misspell
go.fqp.mockery := github.com/vektra/mockery/cmd/mockery

# 安装所有tools
.PHONY: gotools-install
gotools-install: $(patsubst %,$(GOTOOLS_BINDIR)/%, $(GOTOOLS))

# 清理tools
.PHONY: gotools-clean
gotools-clean:
-@rm -rf $(BUILD_DIR)/gotools

# 可以使用vendor中的版本构建部分tools,比如protoc-gen-go,ginkgo,goimports,golint
# Special override for protoc-gen-go since we want to use the version vendored with the project
gotool.protoc-gen-go:
@echo "Building github.com/golang/protobuf/protoc-gen-go -> protoc-gen-go"
GOBIN=$(abspath $(GOTOOLS_BINDIR)) go install ./vendor/github.com/golang/protobuf/protoc-gen-go

# Special override for ginkgo since we want to use the version vendored with the project
gotool.ginkgo:
@echo "Building github.com/onsi/ginkgo/ginkgo -> ginkgo"
GOBIN=$(abspath $(GOTOOLS_BINDIR)) go install ./vendor/github.com/onsi/ginkgo/ginkgo

# Special override for goimports since we want to use the version vendored with the project
gotool.goimports:
@echo "Building golang.org/x/tools/cmd/goimports -> goimports"
GOBIN=$(abspath $(GOTOOLS_BINDIR)) go install ./vendor/golang.org/x/tools/cmd/goimports

# Special override for golint since we want to use the version vendored with the project
gotool.golint:
@echo "Building golang.org/x/lint/golint -> golint"
GOBIN=$(abspath $(GOTOOLS_BINDIR)) go install ./vendor/golang.org/x/lint/golint

# go dep的构建,使用特定版本
# Lock to a versioned dep
gotool.dep: DEP_VERSION ?= "v0.5.1"
gotool.dep:
@GOPATH=$(abspath $(GOTOOLS_GOPATH)) go get -d -u github.com/golang/dep
@git -C $(abspath $(GOTOOLS_GOPATH))/src/github.com/golang/dep checkout -q $(DEP_VERSION)
@echo "Building github.com/golang/dep $(DEP_VERSION) -> dep"
@GOPATH=$(abspath $(GOTOOLS_GOPATH)) GOBIN=$(abspath $(GOTOOLS_BINDIR)) go install -ldflags="-X main.version=$(DEP_VERSION) -X main.buildDate=$$(date '+%Y-%m-%d')" github.com/golang/dep/cmd/dep
@git -C $(abspath $(GOTOOLS_GOPATH))/src/github.com/golang/dep checkout -q master

# 所有tools构建时的默认安装方式,会使用go get从网络拉去
# Default rule for gotools uses the name->path map for a generic 'go get' style build
gotool.%:
$(eval TOOL = ${subst gotool.,,${@}})
@echo "Building ${go.fqp.${TOOL}} -> $(TOOL)"
@GOPATH=$(abspath $(GOTOOLS_GOPATH)) GOBIN=$(abspath $(GOTOOLS_BINDIR)) go get ${go.fqp.${TOOL}}

$(GOTOOLS_BINDIR)/%:
$(eval TOOL = ${subst $(GOTOOLS_BINDIR)/,,${@}})
@$(MAKE) -f gotools.mk gotool.$(TOOL)

构建建议

列出几条构建建议,建议在make前先做好,会提高构建效率,并且少采坑。

翻墙

设置好翻墙,包括http和https代理,以便能下载Github,golang.org的包,参考让终端科学上网

发文时fabric还使用的vendor,如果限制fabric已经使用go mod了,建议配置国内go modules代理,这样就无需翻墙了,参考本文结束语

Linux系统包管理设置为国内的源,Mac上brew设置为腾讯源

参考让镜像飞,加速你的开发

docker设置为国内的源

参考Docker镜像加速

检查GOPATH和PATH,以及http代理

确保配置正确:

1
2
3
4
echo $http_proxy
echo $https_proxy
echo $GOPATH
echo $PATH

安装docker-compose

centos7下请参考:

1
2
3
4
5
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

sudo chmod +x /usr/local/bin/docker-compose

docker-compose --version

Mac上安装Gnu-tar

如果未安装,可能遇到下面的错误:

1
2
3
Step 3/5 : ADD payload/goshim.tar.bz2 $GOPATH/src/
failed to copy files: Error processing tar file(bzip2 data invalid: bad magic value in continuation file):
make: [build/image/ccenv/.dummy-x86_64-1.0.7-snapshot-ac3fabd] Error 1

需要安装gnu-tar,用gnu-tar替换mac默认的bsdtar,可以用brew list gnu-tar找到gnu-tar的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ brew install gnu-tar
➜ fabric git:(yx-release-1.4) ✗ brew install gnu-tar
==> Reinstalling gnu-tar
==> Downloading https://mirrors.cloud.tencent.com/homebrew-bottles/bottles/gnu-tar-1.32.mojave.bottle.tar.gz
######################################################################## 100.0%
==> Pouring gnu-tar-1.32.mojave.bottle.tar.gz
==> Caveats
GNU "tar" has been installed as "gtar".
If you need to use it as "tar", you can add a "gnubin" directory
to your PATH from your bashrc like:

PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH"
==> Summary
🍺 /usr/local/Cellar/gnu-tar/1.32: 15 files, 1.7MB
$ which tar
/usr/local/opt/gnu-tar/libexec/gnubin/tar

按提示设置PATH才可以使用gnu tar。export PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH"加入到.zshrc,不必每次构建都设置PATH,但这样会让Mac默认使用GNU tar。

需要make clean然后重新make,不然依然会遇到bad magic的问题。

Git升级到2.22以上版本

如果未升级可能遇到上文提到的dep不存在的问题。

通过Makefile定位编译问题

这类问题是类似的,要找到报错的位置,是做哪项构建时报的错,以及报错位置的前提条件是什么,这里列举2个。

dep不存在的问题

在执行make all的时候,遇到了dep不存在的问题:

1
2
DEP: Checking for dependency issues..
./scripts/check_deps.sh: line 7: dep: command not found

通过Makefile知道,dep属于gotools,单独执行make gotools查看问题。

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
➜  fabric git:(r1.4) ✗ make gotools
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/maxbrunsfeld/counterfeiter -> counterfeiter
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building golang.org/x/lint/golint -> golint
GOBIN=/home/centos/go/bin go install ./vendor/golang.org/x/lint/golint
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building golang.org/x/tools/cmd/goimports -> goimports
GOBIN=/home/centos/go/bin go install ./vendor/golang.org/x/tools/cmd/goimports
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/onsi/ginkgo/ginkgo -> ginkgo
GOBIN=/home/centos/go/bin go install ./vendor/github.com/onsi/ginkgo/ginkgo
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/axw/gocov/gocov -> gocov
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/AlekSi/gocov-xml -> gocov-xml
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/vektra/mockery/cmd/mockery -> mockery
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”
make[1]: 进入目录“/home/centos/go/src/github.com/hyperledger/fabric”
Building github.com/estesp/manifest-tool -> manifest-tool
make[1]: 离开目录“/home/centos/go/src/github.com/hyperledger/fabric”

其中,果然没有安装dep,单独编译dep:

1
2
3
4
5
6
7
8
➜  fabric git:(r1.4) ✗ make gotool.dep
Unknown option: -C
usage: git [--version] [--help] [-c name=value]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
make: *** [gotool.dep] 错误 129

报错误,git没有-C选项,怀疑centos系统自带git太老,git version查看果然只有1.9

按照Git的INSTALL文件指导安装git,详细见:https://github.com/git/git/blob/master/INSTALL ,下面是简要安装步骤。

通过wget下载最新的git release,然后使用以下命令安装git到/usr/bin目录。

1
2
3
4
wget https://github.com/git/git/archive/v2.22.0.tar.gz
# 省略:解压然后进入该目录
make
make prefix=/usr install

验证git版本,和make dep。

1
2
3
4
5
➜  fabric git:(r1.4) ✗ git version
git version 2.22.0

➜ fabric git:(r1.4) ✗ make gotool.dep
Building github.com/golang/dep v0.5.1 -> dep

通过fabric Makefile可知check_deps.shmake check-deps的一部分,执行make check-deps可以看到检查dep通过了。

1
2
3
4
5
6
7
8
9
10
11
12
➜  fabric git:(r1.4) make check-deps
DEP: Checking for dependency issues..
dep:
version : v0.5.1
build date : 2019-07-15
git hash :
go version : go1.11.5
go compiler : gc
platform : linux/amd64
features : ImportDuringSolve=false
# out of sync, but ignored, due to noverify in Gopkg.toml:
github.com/grpc-ecosystem/go-grpc-middleware: hash of vendored tree not equal to digest in Gopkg.lock

protoc-gen-go 不存在的问题

1
2
3
4
5
$ make all
mkdir -p .build/image/ccenv/payload
cp .build/docker/gotools/bin/protoc-gen-go .build/bin/chaintool .build/goshim.tar.bz2 .build/image/ccenv/payload
cp: .build/docker/gotools/bin/protoc-gen-go: No such file or directory
make: *** [.build/image/ccenv/payload] Error 1

查看构建日志是否有以下日志:

1
Building dockerized gotools

应当是不存在,不过构建存在错误,所以才没有构建出docker要使用的gotools。

解决办法是:

1
2
$ make clean
$ make .build/docker/gotools # 直接编译docker的gotools

如果报网络连接导致的错误,参考终端科学上网,然后重新执行,或者下面这种办法:

1
2
$ make gotools # 本地安装gotools
$ cp `which protoc-gen-go` .build/docker/gotools/bin/ # 拷贝到docker gotools目录

然后重新make allmake docker

构建日志

构建日志比较长,放到了附录中,对构建日志加了注释,可根据构建日志进一步掌握构建过程。

镜像解读

通过make allmake docker可以生成fabric的所有镜像,这些镜像可以通过make docker-list查看,如果使用docker images查看,会看到更多的镜像,并且发现下面这5个镜像还有另外一个”lastest”的标签,看Makefile可以知道,其实是1个镜像2个标签而已。

1
2
3
4
5
6
➜  fabric git:(r1.4) make docker-list
hyperledger/fabric-peer:amd64-1.4.2-snapshot-9dce7357b
hyperledger/fabric-orderer:amd64-1.4.2-snapshot-9dce7357b
hyperledger/fabric-ccenv:amd64-1.4.2-snapshot-9dce7357b
hyperledger/fabric-buildenv:amd64-1.4.2-snapshot-9dce7357b
hyperledger/fabric-tools:amd64-1.4.2-snapshot-9dce7357b
  • fabric-baseos: peer、orderer、链码容器的镜像所依赖的基础镜像。
  • fabric-baseimage:是fabric-buildenv所依赖的镜像。
  • fabric-peer:可以使用该镜像启动一个peer节点。
  • fabric-orderer:可以使用该镜像启动一个排序节点。
  • fabric-ccenv:peer进程使用ccenv来构建链码容器镜像,注意ccenv不是链码容器镜像的运行环境,链码容器镜像是基于baseos的。
  • fabric-buildenv:实际包含的是go tools.tar.bz2和protoc-gen-go的镜像,Make时一些工具会使用该镜像。
  • fabric-tools:是fabric自身tools集合的镜像。

这几个镜像的Dockerfile文件在:images目录下,各镜像具体内容见各自的Dockerfile。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
➜  fabric git:(r1.4) tree images
images
├── buildenv
│   └── Dockerfile.in
├── ccenv
│   └── Dockerfile.in
├── orderer
│   └── Dockerfile.in
├── peer
│   └── Dockerfile.in
├── testenv
│   ├── Dockerfile.alpine
│   └── softhsm
│   └── APKBUILD
└── tools
└── Dockerfile.in

7 directories, 7 files

结束语

现在国内已经有第三方的Go modules代理服务了,比如:

  1. goproxy.io,是即将毕业的盛奥飞小哥捐给了七牛搭建的Go modules代理服务。
  2. aliyun goproxy,现在阿里云开放了Go modules代理服务。

fabric使用vendor,下载各种东西的时候需要翻墙,即便是可以翻墙,也是有缺点的:

  1. 慢。
  2. 翻墙有流量限制。

fabric赶紧支持go mod吧,这样再也不用翻墙了。

参考资料

  1. fabric工程项目构建Makefile翻译及解析

附录

一份构建日志:

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
➜  fabric git:(r1.4) ✗ make all
// 构建native那些程序,等价make native
// peer
.build/bin/peer
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/metadata.Version=1.4.2 -X github.com/hyperledger/fabric/common/metadata.CommitSHA=9dce735 -X github.com/hyperledger/fabric/common/metadata.BaseVersion=0.4.15 -X github.com/hyperledger/fabric/common/metadata.BaseDockerLabel=org.hyperledger.fabric -X github.com/hyperledger/fabric/common/metadata.DockerNamespace=hyperledger -X github.com/hyperledger/fabric/common/metadata.BaseDockerNamespace=hyperledger" github.com/hyperledger/fabric/peer
Binary available as .build/bin/peer
// orderer
.build/bin/orderer
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/metadata.Version=1.4.2 -X github.com/hyperledger/fabric/common/metadata.CommitSHA=9dce735 -X github.com/hyperledger/fabric/common/metadata.BaseVersion=0.4.15 -X github.com/hyperledger/fabric/common/metadata.BaseDockerLabel=org.hyperledger.fabric -X github.com/hyperledger/fabric/common/metadata.DockerNamespace=hyperledger -X github.com/hyperledger/fabric/common/metadata.BaseDockerNamespace=hyperledger" github.com/hyperledger/fabric/orderer
Binary available as .build/bin/orderer
// configtxgen
.build/bin/configtxgen
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/configtxgen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/configtxgen
Binary available as .build/bin/configtxgen
// cryptogen
.build/bin/cryptogen
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/cryptogen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/cryptogen
Binary available as .build/bin/cryptogen
// idemixgen
.build/bin/idemixgen
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/idemixgen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/idemixgen
Binary available as .build/bin/idemixgen
// configtxlator
.build/bin/configtxlator
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/configtxlator/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/configtxlator
Binary available as .build/bin/configtxlator
// discover
.build/bin/discover
CGO_CFLAGS=" " GOBIN=/home/centos/go/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/cmd/discover/metadata.Version=1.4.2-snapshot-9dce735" github.com/hyperledger/fabric/cmd/discover
Binary available as .build/bin/discover

// 以下这部分等价make docker
// 构建peer镜像
Building .build/docker/bin/peer
# github.com/hyperledger/fabric/peer
/tmp/go-link-829040977/000006.o: In function `pluginOpen':
/workdir/go/src/plugin/plugin_dlopen.go:19: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000021.o: In function `mygetgrouplist':
/workdir/go/src/os/user/getgrouplist_unix.go:16: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000020.o: In function `mygetgrgid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:38: warning: Using 'getgrgid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000020.o: In function `mygetgrnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:43: warning: Using 'getgrnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000020.o: In function `mygetpwnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:33: warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000020.o: In function `mygetpwuid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:28: warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-829040977/000004.o: In function `_cgo_18049202ccd9_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:49: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
// 构建需要的压缩包
(cd sampleconfig && tar -jc *) > .build/sampleconfig.tar.bz2
// 复制peer需要的payload
mkdir -p .build/image/peer/payload
cp .build/docker/bin/peer .build/sampleconfig.tar.bz2 .build/image/peer/payload
// 打包peer镜像
mkdir -p .build/image/peer
Building docker peer-image
docker build --build-arg 'http_proxy=http://192.168.102.143:1087' --build-arg 'https_proxy=http://192.168.102.143:1087' -t hyperledger/fabric-peer .build/image/peer
Sending build context to Docker daemon 33.56MB
Step 1/7 : FROM hyperledger/fabric-baseos:amd64-0.4.15
---> 9d6ec11c60ff
Step 2/7 : ENV FABRIC_CFG_PATH /etc/hyperledger/fabric
---> Running in 3bea4b2a628b
Removing intermediate container 3bea4b2a628b
---> 8892a2046872
Step 3/7 : RUN mkdir -p /var/hyperledger/production $FABRIC_CFG_PATH
---> Running in 06437fde2305
Removing intermediate container 06437fde2305
---> 98fc3c6b0fae
Step 4/7 : COPY payload/peer /usr/local/bin
---> 635a5f0e02c4
Step 5/7 : ADD payload/sampleconfig.tar.bz2 $FABRIC_CFG_PATH
---> d2e3f4b80946
Step 6/7 : CMD ["peer","node","start"]
---> Running in 47e57005f4f8
Removing intermediate container 47e57005f4f8
---> 59a7e54bfe1a
Step 7/7 : LABEL org.hyperledger.fabric.version=1.4.2 org.hyperledger.fabric.base.version=0.4.15
---> Running in aaacacec80e8
Removing intermediate container aaacacec80e8
---> e97b7fd4ff49
Successfully built e97b7fd4ff49
// 构建peer镜像完成,为镜像打包
Successfully tagged hyperledger/fabric-peer:latest
docker tag hyperledger/fabric-peer hyperledger/fabric-peer:amd64-1.4.2-snapshot-9dce735
docker tag hyperledger/fabric-peer hyperledger/fabric-peer:amd64-latest
// 以下为构建orderer镜像,与peer镜像过程类似
Building .build/docker/bin/orderer
# github.com/hyperledger/fabric/orderer
/tmp/go-link-846385019/000018.o: In function `pluginOpen':
/workdir/go/src/plugin/plugin_dlopen.go:19: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000021.o: In function `mygetgrouplist':
/workdir/go/src/os/user/getgrouplist_unix.go:16: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000020.o: In function `mygetgrgid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:38: warning: Using 'getgrgid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000020.o: In function `mygetgrnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:43: warning: Using 'getgrnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000020.o: In function `mygetpwnam_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:33: warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000020.o: In function `mygetpwuid_r':
/workdir/go/src/os/user/cgo_lookup_unix.go:28: warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-846385019/000004.o: In function `_cgo_18049202ccd9_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:49: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
mkdir -p .build/image/orderer/payload
cp .build/docker/bin/orderer .build/sampleconfig.tar.bz2 .build/image/orderer/payload
mkdir -p .build/image/orderer
Building docker orderer-image
docker build --build-arg 'http_proxy=http://192.168.102.143:1087' --build-arg 'https_proxy=http://192.168.102.143:1087' -t hyperledger/fabric-orderer .build/image/orderer
Sending build context to Docker daemon 28.09MB
Step 1/8 : FROM hyperledger/fabric-baseos:amd64-0.4.15
---> 9d6ec11c60ff
Step 2/8 : ENV FABRIC_CFG_PATH /etc/hyperledger/fabric
---> Using cache
---> 8892a2046872
Step 3/8 : RUN mkdir -p /var/hyperledger/production $FABRIC_CFG_PATH
---> Using cache
---> 98fc3c6b0fae
Step 4/8 : COPY payload/orderer /usr/local/bin
---> 50854bee0fa6
Step 5/8 : ADD payload/sampleconfig.tar.bz2 $FABRIC_CFG_PATH/
---> bab56963bf0f
Step 6/8 : EXPOSE 7050
---> Running in bda05dbbf18a
Removing intermediate container bda05dbbf18a
---> 7b335f36f7d2
Step 7/8 : CMD ["orderer"]
---> Running in 210013bf0e3e
Removing intermediate container 210013bf0e3e
---> b543c69c8caf
Step 8/8 : LABEL org.hyperledger.fabric.version=1.4.2 org.hyperledger.fabric.base.version=0.4.15
---> Running in c762fc3e0590
Removing intermediate container c762fc3e0590
---> aa8604c99f23
Successfully built aa8604c99f23
Successfully tagged hyperledger/fabric-orderer:latest
docker tag hyperledger/fabric-orderer hyperledger/fabric-orderer:amd64-1.4.2-snapshot-9dce735
docker tag hyperledger/fabric-orderer hyperledger/fabric-orderer:amd64-latest
// 以下开始构gotools镜像
Building dockerized gotools
// 以下实际在docker中运行
// 默认go get下载,然后默认安装到$GOPATH/bin
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/maxbrunsfeld/counterfeiter -> counterfeiter
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/golang/dep v0.5.1 -> dep
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building golang.org/x/lint/golint -> golint
// 这几个指定了安装目录/opt/gotools/bin,实际映射到.build/docker/gotools/bin/
GOBIN=/opt/gotools/bin go install ./vendor/golang.org/x/lint/golint
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building golang.org/x/tools/cmd/goimports -> goimports
GOBIN=/opt/gotools/bin go install ./vendor/golang.org/x/tools/cmd/goimports
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/golang/protobuf/protoc-gen-go -> protoc-gen-go
GOBIN=/opt/gotools/bin go install ./vendor/github.com/golang/protobuf/protoc-gen-go
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/onsi/ginkgo/ginkgo -> ginkgo
GOBIN=/opt/gotools/bin go install ./vendor/github.com/onsi/ginkgo/ginkgo
// 以下安装到$GOPATH/bin
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/axw/gocov/gocov -> gocov
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/AlekSi/gocov-xml -> gocov-xml
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/client9/misspell/cmd/misspell -> misspell
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/vektra/mockery/cmd/mockery -> mockery
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
make[1]: Entering directory '/opt/gopath/src/github.com/hyperledger/fabric'
Building github.com/estesp/manifest-tool -> manifest-tool
make[1]: Leaving directory '/opt/gopath/src/github.com/hyperledger/fabric'
// 安装chaintool,gotools镜像需要
Installing chaintool
curl -fL https://nexus.hyperledger.org/content/repositories/releases/org/hyperledger/fabric/hyperledger-fabric/chaintool-1.1.3/hyperledger-fabric-chaintool-1.1.3.jar > .build/bin/chaintool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 16.4M 100 16.4M 0 0 1142k 0 0:00:14 0:00:14 --:--:-- 2544k
chmod +x .build/bin/chaintool
Creating .build/goshim.tar.bz2
// 设置ccenv的payload
mkdir -p .build/image/ccenv/payload
cp .build/docker/gotools/bin/protoc-gen-go .build/bin/chaintool .build/goshim.tar.bz2 .build/image/ccenv/payload
mkdir -p .build/image/ccenv
// 构建ccenv镜像,是chain code的环境镜像,所以简写为ccenv
Building docker ccenv-image
docker build --build-arg 'http_proxy=http://192.168.102.143:1087' --build-arg 'https_proxy=http://192.168.102.143:1087' -t hyperledger/fabric-ccenv .build/image/ccenv
Sending build context to Docker daemon 25.12MB
Step 1/5 : FROM hyperledger/fabric-baseimage:amd64-0.4.15
---> c4c532c23a50
Step 2/5 : COPY payload/chaintool payload/protoc-gen-go /usr/local/bin/
---> 44e06a863d08
Step 3/5 : ADD payload/goshim.tar.bz2 $GOPATH/src/
---> 233605b067d5
Step 4/5 : RUN mkdir -p /chaincode/input /chaincode/output
---> Running in be1909a39e06
Removing intermediate container be1909a39e06
---> 605b1c70e97f
Step 5/5 : LABEL org.hyperledger.fabric.version=1.4.2 org.hyperledger.fabric.base.version=0.4.15
---> Running in dc470f15e125
Removing intermediate container dc470f15e125
---> 7cb803c8b124
Successfully built 7cb803c8b124
Successfully tagged hyperledger/fabric-ccenv:latest
docker tag hyperledger/fabric-ccenv hyperledger/fabric-ccenv:amd64-1.4.2-snapshot-9dce735
docker tag hyperledger/fabric-ccenv hyperledger/fabric-ccenv:amd64-latest
// 构建buildenv镜像
// gotools放进压缩包
(cd .build/docker/gotools/bin && tar -jc *) > .build/gotools.tar.bz2
mkdir -p .build/image/buildenv/payload
// gotools和protoc-gen-go是buildenv的payload
cp .build/gotools.tar.bz2 .build/docker/gotools/bin/protoc-gen-go .build/image/buildenv/payload
mkdir -p .build/image/buildenv
Building docker buildenv-image
docker build --build-arg 'http_proxy=http://192.168.102.143:1087' --build-arg 'https_proxy=http://192.168.102.143:1087' -t hyperledger/fabric-buildenv .build/image/buildenv
Sending build context to Docker daemon 47.17MB
Step 1/5 : FROM hyperledger/fabric-baseimage:amd64-0.4.15
---> c4c532c23a50
Step 2/5 : COPY payload/protoc-gen-go /usr/local/bin/
---> 90f62f1410b4
Step 3/5 : ADD payload/gotools.tar.bz2 /usr/local/bin/
---> e27228cd3fb8
Step 4/5 : ENV GOCACHE "/tmp"
---> Running in 780e38380727
Removing intermediate container 780e38380727
---> b610d861e6ce
Step 5/5 : LABEL org.hyperledger.fabric.version=1.4.2 org.hyperledger.fabric.base.version=0.4.15
---> Running in 226095fc14b5
Removing intermediate container 226095fc14b5
---> 6ba655852ec7
Successfully built 6ba655852ec7
Successfully tagged hyperledger/fabric-buildenv:latest
// gotools实际打包在了buildenv镜像中
docker tag hyperledger/fabric-buildenv hyperledger/fabric-buildenv:amd64-1.4.2-snapshot-9dce735
docker tag hyperledger/fabric-buildenv hyperledger/fabric-buildenv:amd64-latest
// 打包tools镜像,它的dockerfile文件:.build/image/tools/Dockerfile
// 从这里可以看到镜像里实际包含的是configtxgen configtxlator cryptogen peer discover idemixgen,这几个工具
// 并对系统进行了更新
// 所以tools镜像指的是fabric tools的镜像,而不是go tools
mkdir -p .build/image/tools
Building docker tools-image
docker build --build-arg 'http_proxy=http://192.168.102.143:1087' --build-arg 'https_proxy=http://192.168.102.143:1087' -t hyperledger/fabric-tools -f .build/image/tools/Dockerfile .
Sending build context to Docker daemon 179.5MB
Step 1/14 : FROM hyperledger/fabric-baseimage:amd64-0.4.15 as builder
---> c4c532c23a50
Step 2/14 : WORKDIR /opt/gopath
---> Running in bc4dd206cdcd
Removing intermediate container bc4dd206cdcd
---> c156c64ba0c0
Step 3/14 : RUN mkdir src && mkdir pkg && mkdir bin
---> Running in 752a63efe3be
Removing intermediate container 752a63efe3be
---> 001cb4d1136f
Step 4/14 : ADD . src/github.com/hyperledger/fabric
---> 5ba1e6fe79df
Step 5/14 : WORKDIR /opt/gopath/src/github.com/hyperledger/fabric
---> Running in 9b03a753a124
Removing intermediate container 9b03a753a124
---> e0eb57e0b44b
Step 6/14 : ENV EXECUTABLES go git curl
---> Running in 6b5978688143
Removing intermediate container 6b5978688143
---> 2a28ae07b3da
Step 7/14 : RUN make configtxgen configtxlator cryptogen peer discover idemixgen
---> Running in 27e814a9a148
// 在镜像里安装native中的各种工具,所以gotools镜像,包含的并不是gotools那几个工具
.build/bin/configtxgen
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/configtxgen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/configtxgen
Binary available as .build/bin/configtxgen
.build/bin/configtxlator
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/configtxlator/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/configtxlator
Binary available as .build/bin/configtxlator
.build/bin/cryptogen
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/cryptogen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/cryptogen
Binary available as .build/bin/cryptogen
.build/bin/peer
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/metadata.Version=1.4.2 -X github.com/hyperledger/fabric/common/metadata.CommitSHA=9dce735 -X github.com/hyperledger/fabric/common/metadata.BaseVersion=0.4.15 -X github.com/hyperledger/fabric/common/metadata.BaseDockerLabel=org.hyperledger.fabric -X github.com/hyperledger/fabric/common/metadata.DockerNamespace=hyperledger -X github.com/hyperledger/fabric/common/metadata.BaseDockerNamespace=hyperledger" github.com/hyperledger/fabric/peer
Binary available as .build/bin/peer
.build/bin/discover
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/cmd/discover/metadata.Version=1.4.2-snapshot-9dce735" github.com/hyperledger/fabric/cmd/discover
Binary available as .build/bin/discover
.build/bin/idemixgen
CGO_CFLAGS=" " GOBIN=/opt/gopath/src/github.com/hyperledger/fabric/.build/bin go install -tags "" -ldflags "-X github.com/hyperledger/fabric/common/tools/idemixgen/metadata.CommitSHA=9dce735" github.com/hyperledger/fabric/common/tools/idemixgen
Binary available as .build/bin/idemixgen
Removing intermediate container 27e814a9a148
---> 019fcc98aafe
Step 8/14 : FROM hyperledger/fabric-baseimage:amd64-0.4.15
---> c4c532c23a50
Step 9/14 : ENV FABRIC_CFG_PATH /etc/hyperledger/fabric
---> Running in 971f1e778c1b
Removing intermediate container 971f1e778c1b
---> 3abe7ab3eda7
Step 10/14 : RUN apt-get update && apt-get install -y jq
---> Running in 0c6bc2dab637
Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [109 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]
Get:3 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]
Get:4 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [896 kB]
Get:5 http://archive.ubuntu.com/ubuntu xenial-backports InRelease [107 kB]
Get:6 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1558 kB]
Get:7 http://security.ubuntu.com/ubuntu xenial-security/restricted amd64 Packages [12.7 kB]
Get:8 http://security.ubuntu.com/ubuntu xenial-security/universe amd64 Packages [569 kB]
Get:9 http://archive.ubuntu.com/ubuntu xenial/restricted amd64 Packages [14.1 kB]
Get:10 http://archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [9827 kB]
Get:11 http://security.ubuntu.com/ubuntu xenial-security/multiverse amd64 Packages [6117 B]
Get:12 http://archive.ubuntu.com/ubuntu xenial/multiverse amd64 Packages [176 kB]
Get:13 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [1277 kB]
Get:14 http://archive.ubuntu.com/ubuntu xenial-updates/restricted amd64 Packages [13.1 kB]
Get:15 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [974 kB]
Get:16 http://archive.ubuntu.com/ubuntu xenial-updates/multiverse amd64 Packages [19.1 kB]
Get:17 http://archive.ubuntu.com/ubuntu xenial-backports/main amd64 Packages [7942 B]
Get:18 http://archive.ubuntu.com/ubuntu xenial-backports/universe amd64 Packages [8532 B]
Fetched 15.9 MB in 17s (896 kB/s)
Reading package lists...
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
libonig2
The following NEW packages will be installed:
jq libonig2
0 upgraded, 2 newly installed, 0 to remove and 55 not upgraded.
Need to get 231 kB of archives.
After this operation, 797 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 libonig2 amd64 5.9.6-1ubuntu0.1 [86.7 kB]
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 jq amd64 1.5+dfsg-1ubuntu0.1 [144 kB]
debconf: unable to initialize frontend: Dialog
debconf: (TERM is not set, so the dialog frontend is not usable.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
dpkg-preconfigure: unable to re-open stdin:
Fetched 231 kB in 2s (105 kB/s)
Selecting previously unselected package libonig2:amd64.
(Reading database ... 22655 files and directories currently installed.)
Preparing to unpack .../libonig2_5.9.6-1ubuntu0.1_amd64.deb ...
Unpacking libonig2:amd64 (5.9.6-1ubuntu0.1) ...
Selecting previously unselected package jq.
Preparing to unpack .../jq_1.5+dfsg-1ubuntu0.1_amd64.deb ...
Unpacking jq (1.5+dfsg-1ubuntu0.1) ...
Processing triggers for libc-bin (2.23-0ubuntu11) ...
Setting up libonig2:amd64 (5.9.6-1ubuntu0.1) ...
Setting up jq (1.5+dfsg-1ubuntu0.1) ...
Processing triggers for libc-bin (2.23-0ubuntu11) ...
Removing intermediate container 0c6bc2dab637
---> bc8cfafb544f
Step 11/14 : VOLUME /etc/hyperledger/fabric
---> Running in 260f4ec6bd3e
Removing intermediate container 260f4ec6bd3e
---> 9f682419f109
Step 12/14 : COPY --from=builder /opt/gopath/src/github.com/hyperledger/fabric/.build/bin /usr/local/bin
---> ff18ec787bf5
Step 13/14 : COPY --from=builder /opt/gopath/src/github.com/hyperledger/fabric/sampleconfig $FABRIC_CFG_PATH
---> 70163f0cac4f
Step 14/14 : LABEL org.hyperledger.fabric.version=1.4.2 org.hyperledger.fabric.base.version=0.4.15
---> Running in 2f70bb608ac2
Removing intermediate container 2f70bb608ac2
---> e395ec9d27e8
Successfully built e395ec9d27e8
Successfully tagged hyperledger/fabric-tools:latest
// gotools镜像打包完成,打上tag
docker tag hyperledger/fabric-tools hyperledger/fabric-tools:amd64-1.4.2-snapshot-9dce735
docker tag hyperledger/fabric-tools hyperledger/fabric-tools:amd64-latest

// 以下等价于make checks
// 许可证检查
All files have SPDX-License-Identifier headers
// 拼写检查
Checking changed go files for spelling errors ...
spell checker passed
// trailing spaces检查
Checking trailing spaces ...
// dep检查
DEP: Checking for dependency issues..
dep:
version : v0.5.1
build date : 2019-07-16
git hash :
go version : go1.11.5
go compiler : gc
platform : linux/amd64
features : ImportDuringSolve=false
# out of sync, but ignored, due to noverify in Gopkg.toml:
github.com/grpc-ecosystem/go-grpc-middleware: hash of vendored tree not equal to digest in Gopkg.lock
// 执行lint
LINT: Running code checks..
Checking with gofmt
Checking with goimports
Checking for golang.org/x/net/context
Checking with go vet
METRICS: Checking for outdated reference documentation..
cd unit-test && docker-compose down
WARNING: The TEST_PKGS variable is not set. Defaulting to a blank string.
WARNING: The JOB_TYPE variable is not set. Defaulting to a blank string.
docker pull hyperledger/fabric-couchdb:amd64-0.4.15
amd64-0.4.15: Pulling from hyperledger/fabric-couchdb
34667c7e4631: Already exists
d18d76a881a4: Already exists
119c7358fbfc: Already exists
2aaf13f3eff0: Already exists
3f89de4cf84b: Already exists
24194f819972: Already exists
78e4eabd31a5: Already exists
c7652b6bde40: Already exists
b4646dd65c45: Already exists
5e6defad8a30: Already exists
7695bf5d0b9d: Pull complete
6d9d46f66bc3: Pull complete
4912f1b4990a: Pull complete
f3b174a93eea: Pull complete
3763a939777a: Pull complete
f293593adbb6: Pull complete
1ae53ace804f: Pull complete
d4aa6d764b18: Pull complete
d747b2b30e48: Pull complete
52cbd2253fea: Pull complete
Digest: sha256:e9c528f90c84c50dd3a79c2d2c5f1ff87264a8009a1971b269ceecace4ef1fb9
Status: Downloaded newer image for hyperledger/fabric-couchdb:amd64-0.4.15
docker tag hyperledger/fabric-couchdb:amd64-0.4.15 hyperledger/fabric-couchdb
docker pull hyperledger/fabric-zookeeper:amd64-0.4.15
amd64-0.4.15: Pulling from hyperledger/fabric-zookeeper
34667c7e4631: Already exists
d18d76a881a4: Already exists
119c7358fbfc: Already exists
2aaf13f3eff0: Already exists
3f89de4cf84b: Already exists
24194f819972: Already exists
78e4eabd31a5: Already exists
c7652b6bde40: Already exists
b4646dd65c45: Already exists
5e6defad8a30: Already exists
0e045d9c2cdc: Pull complete
7ef4d8920518: Pull complete
dbeed81d9a45: Pull complete
aeea025ecc4e: Pull complete
Digest: sha256:4e4e8b8aaed7864f23d0c6c018cc8589e8e1d042413abc034dd7a6b3faacd2f0
Status: Downloaded newer image for hyperledger/fabric-zookeeper:amd64-0.4.15
docker tag hyperledger/fabric-zookeeper:amd64-0.4.15 hyperledger/fabric-zookeeper
docker pull hyperledger/fabric-kafka:amd64-0.4.15
amd64-0.4.15: Pulling from hyperledger/fabric-kafka
34667c7e4631: Already exists
d18d76a881a4: Already exists
119c7358fbfc: Already exists
2aaf13f3eff0: Already exists
3f89de4cf84b: Already exists
24194f819972: Already exists
78e4eabd31a5: Already exists
c7652b6bde40: Already exists
b4646dd65c45: Already exists
5e6defad8a30: Already exists
d0459116a54a: Pull complete
1bbcec7bfdef: Pull complete
5911218c5933: Pull complete
Digest: sha256:68398b1e1ee4165fd80b1a2f0e123625f489150673c7dc4816177816e43ace78
Status: Downloaded newer image for hyperledger/fabric-kafka:amd64-0.4.15
docker tag hyperledger/fabric-kafka:amd64-0.4.15 hyperledger/fabric-kafka
unit-test/run.sh

// 省略后面的单元测试
  1. 本文作者:大彬
  2. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/07/16/fabric-makefile/
关注公众号,获取最新Golang文章

新老朋友好久不见,我是大彬,这篇文章准备了很久,不是在拖延,而是中间做了一些其他事情,耽搁了一些。

这篇文章主要介绍Go内存分配和Go内存管理,会轻微涉及内存申请和释放,以及Go垃圾回收。

从非常宏观的角度看,Go的内存管理就是下图这个样子,我们今天主要关注其中标红的部分。

Go内存管理

友情提醒:

文章有点长,建议先收藏,后阅读,绝对是学习内存管理的好资料。

本文基于go1.11.2,不同版本Go的内存管理可能存在差别,比如1.9与1.11的mheap定义就是差别比较大的,后续看源码的时候,请注意你的go版本,但无论你用哪个go版本,这都是一个优秀的资料,因为内存管理的思想和框架始终未变。

Go这门语言抛弃了C/C++中的开发者管理内存的方式:主动申请与主动释放,增加了逃逸分析和GC,将开发者从内存管理中释放出来,让开发者有更多的精力去关注软件设计,而不是底层的内存问题。这是Go语言成为高生产力语言的原因之一。

我们不需要精通内存的管理,因为它确实很复杂,但掌握内存的管理,可以让你写出更高质量的代码,另外,还能助你定位Bug。

这篇文章采用层层递进的方式,依次会介绍关于存储的基本知识,Go内存管理的“前辈”TCMalloc,然后是Go的内存管理和分配,最后是总结。这么做的目的是,希望各位能通过全局的认识和思考,拥有更好的编码思维和架构思维。

最后,这不是一篇源码分析文章,因为Go源码分析的文章已经有很多了,这些源码文章能够帮助你去学习具体的工程实践和奇淫巧计了,文章的末尾会推荐一些优秀文章,如果你对内存感兴趣,建议每一篇都去看一下,挑出自己喜欢的,多花时间研究下。

1. 存储基础知识回顾

这部分我们简单回顾一下计算机存储体系、虚拟内存、栈和堆,以及堆内存的管理,这部分内容对理解和掌握Go内存管理比较重要,建议忘记或不熟悉的朋友不要跳过。

存储金字塔

img

这幅图表达了计算机的存储体系,从上至下依次是:

  • CPU寄存器
  • Cache
  • 内存
  • 硬盘等辅助存储设备
  • 鼠标等外接设备

从上至下,访问速度越来越慢,访问时间越来越长。

你有没有思考过下面2个简单的问题,如果没有不妨想想:

  1. 如果CPU直接访问硬盘,CPU能充分利用吗?
  2. 如果CPU直接访问内存,CPU能充分利用吗?

CPU速度很快,但硬盘等持久存储很慢,如果CPU直接访问磁盘,磁盘可以拉低CPU的速度,机器整体性能就会低下,为了弥补这2个硬件之间的速率差异,所以在CPU和磁盘之间增加了比磁盘快很多的内存。

CPU和内存速率差异

然而,CPU跟内存的速率也不是相同的,从上图可以看到,CPU的速率提高的很快(摩尔定律),然而内存速率增长的很慢,虽然CPU的速率现在增加的很慢了,但是内存的速率也没增加多少,速率差距很大,从1980年开始CPU和内存速率差距在不断拉大,为了弥补这2个硬件之间的速率差异,所以在CPU跟内存之间增加了比内存更快的Cache,Cache是内存数据的缓存,可以降低CPU访问内存的时间。

不要以为有了Cache就万事大吉了,CPU的速率还在不断增大,Cache也在不断改变,从最初的1级,到后来的2级,到当代的3级Cache,*(有兴趣看cache历史)*。

MBP的CPU和Cache信息

三级Cache分别是L1、L2、L3,它们的速率是三个不同的层级,L1速率最快,与CPU速率最接近,是RAM速率的100倍,L2速率就降到了RAM的25倍,L3的速率更靠近RAM的速率。

看到这了,你有没有Get到整个存储体系的分层设计自顶向下,速率越来越低,访问时间越来越长,从磁盘到CPU寄存器,上一层都可以看做是下一层的缓存。

看了分层设计,我们看一下内存,毕竟我们是介绍内存管理的文章。

虚拟内存

虚拟内存是当代操作系统必备的一项重要功能了,它向进程屏蔽了底层了RAM和磁盘,并向进程提供了远超物理内存大小的内存空间。我们看一下虚拟内存的分层设计

虚拟内存原理

上图展示了某进程访问数据,当Cache没有命中的时候,访问虚拟内存获取数据的过程。

访问内存,实际访问的是虚拟内存,虚拟内存通过页表查看,当前要访问的虚拟内存地址,是否已经加载到了物理内存,如果已经在物理内存,则取物理内存数据,如果没有对应的物理内存,则从磁盘加载数据到物理内存,并把物理内存地址和虚拟内存地址更新到页表。

有没有Get到:物理内存就是磁盘存储缓存层

另外,在没有虚拟内存的时代,物理内存对所有进程是共享的,多进程同时访问同一个物理内存存在并发访问问题。引入虚拟内存后,每个进程都要各自的虚拟内存,内存的并发访问问题的粒度从多进程级别,可以降低到多线程级别

栈和堆

我们现在从虚拟内存,再进一层,看虚拟内存中的栈和堆,也就是进程对内存的管理。

虚拟内存布局

上图展示了一个进程的虚拟内存划分,代码中使用的内存地址都是虚拟内存地址,而不是实际的物理内存地址。栈和堆只是虚拟内存上2块不同功能的内存区域:

  • 栈在高地址,从高地址向低地址增长。

  • 堆在低地址,从低地址向高地址增长。

栈和堆相比有这么几个好处

  1. 栈的内存管理简单,分配比堆上快。
  2. 栈的内存不需要回收,而堆需要,无论是主动free,还是被动的垃圾回收,这都需要花费额外的CPU。
  3. 栈上的内存有更好的局部性,堆上内存访问就不那么友好了,CPU访问的2块数据可能在不同的页上,CPU访问数据的时间可能就上去了。

堆内存管理

内存管理

我们再进一层,当我们说内存管理的时候,主要是指堆内存的管理,因为栈的内存管理不需要程序去操心。这小节看下堆内存管理干的是啥,如上图所示主要是3部分:分配内存块,回收内存块和组织内存块

在一个最简单的内存管理中,堆内存最初会是一个完整的大块,即未分配内存,当来申请的时候,就会从未分配内存,分割出一个小内存块(block),然后用链表把所有内存块连接起来。需要一些信息描述每个内存块的基本信息,比如大小(size)、是否使用中(used)和下一个内存块的地址(next),内存块实际数据存储在data中。

内存块链表

一个内存块包含了3类信息,如下图所示,元数据、用户数据和对齐字段,内存对齐是为了提高访问效率。下图申请5Byte内存的时候,就需要进行内存对齐。

内存块和对齐

释放内存实质是把使用的内存块从链表中取出来,然后标记为未使用,当分配内存块的时候,可以从未使用内存块中有先查找大小相近的内存块,如果找不到,再从未分配的内存中分配内存。

上面这个简单的设计中还没考虑内存碎片的问题,因为随着内存不断的申请和释放,内存上会存在大量的碎片,降低内存的使用率。为了解决内存碎片,可以将2个连续的未使用的内存块合并,减少碎片。

以上就是内存管理的基本思路,关于基本的内存管理,想了解更多,可以阅读这篇文章《Writing a Memory Allocator》,本节的3张图片也是来自这片文章。

2. TCMalloc

TCMalloc是Thread Cache Malloc的简称,是Go内存管理的起源,Go的内存管理是借鉴了TCMalloc,随着Go的迭代,Go的内存管理与TCMalloc不一致地方在不断扩大,但其主要思想、原理和概念都是和TCMalloc一致的,如果跳过TCMalloc直接去看Go的内存管理,也许你会似懂非懂。

掌握TCMalloc的理念,无需去关注过多的源码细节,就可以为掌握Go的内存管理打好基础,基础打好了,后面知识才扎实。

在Linux里,其实有不少的内存管理库,比如glibc的ptmalloc,FreeBSD的jemalloc,Google的tcmalloc等等,为何会出现这么多的内存管理库?本质都是在多线程编程下,追求更高内存管理效率:更快的分配是主要目的。

那如何更快的分配内存?

我们前面提到:

引入虚拟内存后,让内存的并发访问问题的粒度从多进程级别,降低到多线程级别。

这是更快分配内存的第一个层次

同一进程的所有线程共享相同的内存空间,他们申请内存时需要加锁,如果不加锁就存在同一块内存被2个线程同时访问的问题。

TCMalloc的做法是什么呢?为每个线程预分配一块缓存,线程申请小内存时,可以从缓存分配内存,这样有2个好处:

  1. 为线程预分配缓存需要进行1次系统调用,后续线程申请小内存时,从缓存分配,都是在用户态执行,没有系统调用,缩短了内存总体的分配和释放时间,这是快速分配内存的第二个层次
  2. 多个线程同时申请小内存时,从各自的缓存分配,访问的是不同的地址空间,无需加锁,把内存并发访问的粒度进一步降低了,这是快速分配内存的第三个层次

基本原理

下面就简单介绍下TCMalloc,细致程度够我们理解Go的内存管理即可。

声明:我没有研究过TCMalloc,以下介绍根据TCMalloc官方资料和其他博主资料总结而来,错误之处请朋友告知我。

TCMalloc概要图

结合上图,介绍TCMalloc的几个重要概念:

  1. Page:操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系。《TCMalloc解密》里称x64下Page大小是8KB。
  2. Span:一组连续的Page被称为Span,比如可以有2个页大小的Span,也可以有16页大小的Span,Span比Page高一个层级,是为了方便管理一定大小的内存区域,Span是TCMalloc中内存管理的基本单位。
  3. ThreadCache:每个线程各自的Cache,一个Cache包含多个空闲内存块链表,每个链表连接的都是内存块,同一个链表上内存块的大小是相同的,也可以说按内存块大小,给内存块分了个类,这样可以根据申请的内存大小,快速从合适的链表选择空闲内存块。由于每个线程有自己的ThreadCache,所以ThreadCache访问是无锁的。
  4. CentralCache:是所有线程共享的缓存,也是保存的空闲内存块链表,链表的数量与ThreadCache中链表数量相同,当ThreadCache内存块不足时,可以从CentralCache取,当ThreadCache内存块多时,可以放回CentralCache。由于CentralCache是共享的,所以它的访问是要加锁的。
  5. PageHeap:PageHeap是堆内存的抽象,PageHeap存的也是若干链表,链表保存的是Span,当CentralCache没有内存的时,会从PageHeap取,把1个Span拆成若干内存块,添加到对应大小的链表中,当CentralCache内存多的时候,会放回PageHeap。如下图,分别是1页Page的Span链表,2页Page的Span链表等,最后是large span set,这个是用来保存中大对象的。毫无疑问,PageHeap也是要加锁的。

PageHeap

上文提到了小、中、大对象,Go内存管理中也有类似的概念,我们瞄一眼TCMalloc的定义:

  1. 小对象大小:0~256KB
  2. 中对象大小:257~1MB
  3. 大对象大小:>1MB

小对象的分配流程:ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache缓存都是足够的,不需要去访问CentralCache和HeapPage,无锁分配加无系统调用,分配效率是非常高的。

中对象分配流程:直接在PageHeap中选择适当的大小即可,128 Page的Span所保存的最大内存就是1MB。

大对象分配流程:从large span set选择合适数量的页面组成span,用来存储数据。

通过本节的介绍,你应当对TCMalloc主要思想有一定了解了,我建议再回顾一下上面的内容。

本节图片皆来自《TCMalloc解密》,图片版权归原作者所有。

精彩文章推荐

本文对于TCMalloc的介绍并不多,重要的是3个快速分配内存的层次,如果想了解更多,可阅读下面文章。

  1. TCMalloc

必读,通过这篇你能掌握TCMalloc的原理和性能,对掌握Go的内存管理有非常大的帮助,虽然如今Go的内存管理与TCMalloc已经相差很大,但是,这是Go内存管理的起源和“大道”,这篇文章顶看十几篇Go内存管理的文章。

  1. TCMalloc解密

可选异常详细,包含大量精美图片,看完得花小时级别,理解就需要更多时间了,看完这篇不需要看其他TCMalloc的文章了。

  1. TCMalloc介绍

可选,算是TCMalloc的文档的中文版,多数是从英文版翻译过来的,如果你英文不好,看看。

3. Go内存管理

前面铺垫了那么多,终于到了本文核心的地方。前面的铺垫不是不重要,相反它们很重要,Go语言内存管理源自前面的基础知识和内存管理思维,如果你跳过了前面的内容,建议你回头看一看,它可以帮助你更好的掌握Go内存管理。

前文提到Go内存管理源自TCMalloc,但它比TCMalloc还多了2件东西:逃逸分析和垃圾回收,这是2项提高生产力的绝佳武器。

这一大章节,我们先介绍Go内存管理和Go内存分配,最后涉及一点垃圾回收和内存释放。

Go内存管理的基本概念

前面计算机基础知识回顾,是一种自上而下,从宏观到微观的介绍方式,把目光引入到今天的主题。

Go内存管理的许多概念在TCMalloc中已经有了,含义是相同的,只是名字有一些变化。先给大家上一幅宏观的图,借助图一起来介绍。

Go内存管理

Page

与TCMalloc中的Page相同,x64下1个Page的大小是8KB。上图的最下方,1个浅蓝色的长方形代表1个Page。

Span

与TCMalloc中的Span相同,Span是内存管理的基本单位,代码中为mspan一组连续的Page组成1个Span,所以上图一组连续的浅蓝色长方形代表的是一组Page组成的1个Span,另外,1个淡紫色长方形为1个Span。

mcache

mcache与TCMalloc中的ThreadCache类似,mcache保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问

但mcache与ThreadCache也有不同点,TCMalloc中是每个线程1个ThreadCache,Go中是每个P拥有1个mcache,因为在Go程序中,当前最多有GOMAXPROCS个线程在用户态运行,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问,线程的运行又是与P绑定的,把mcache交给P刚刚好。

mcentral

mcentral与TCMalloc中的CentralCache类似,是所有线程共享的缓存,需要加锁访问,它按Span class对Span分类,串联成链表,当mcache的某个级别Span的内存被分配光时,它会向mcentral申请1个当前级别的Span。

但mcentral与CentralCache也有不同点,CentralCache是每个级别的Span有1个链表,mcache是每个级别的Span有2个链表,这和mcache申请内存有关,稍后我们再解释。

mheap

mheap与TCMalloc中的PageHeap类似,它是堆内存的抽象,把从OS申请出的内存页组织成Span,并保存起来。当mcentral的Span不够用时会向mheap申请,mheap的Span不够用时会向OS申请,向OS的内存申请是按页来的,然后把申请来的内存页生成Span组织起来,同样也是需要加锁访问的。

但mheap与PageHeap也有不同点:mheap把Span组织成了树结构,而不是链表,并且还是2棵树,然后把Span分配到heapArena进行管理,它包含地址映射和span是否包含指针等位图,这样做的主要原因是为了更高效的利用内存:分配、回收和再利用。

大小转换

除了以上内存块组织概念,还有几个重要的大小概念,一定要拿出来讲一下,不要忽视他们的重要性,他们是内存分配、组织和地址转换的基础。

Go内存大小转换

  1. object size:代码里简称size,指申请内存的对象大小。
  2. size class:代码里简称class,它是size的级别,相当于把size归类到一定大小的区间段,比如size[1,8]属于size class 1,size(8,16]属于size class 2。
  3. span class:指span的级别,但span class的大小与span的大小并没有正比关系。span class主要用来和size class做对应,1个size class对应2个span class,2个span class的span大小相同,只是功能不同,1个用来存放包含指针的对象,一个用来存放不包含指针的对象,不包含指针对象的Span就无需GC扫描了。
  4. num of page:代码里简称npage,代表Page的数量,其实就是Span包含的页数,用来分配内存。

在介绍这几个大小之间的换算前,我们得先看下图这个表,这个表决定了映射关系。

最上面2行是我手动加的,前3列分别是size class,object size和span size,根据这3列做size、size class和num of page之间的转换。

仔细看一遍这个表,再向下看转换是如何实现的。

Go内存分配表

在Go内存大小转换那幅图中已经标记各大小之间的转换,分别是数组:class_to_sizesize_to_class*class_to_allocnpages,这3个数组内容,就是跟上表的映射关系匹配的。比如class_to_size,从上表看class 1对应的保存对象大小为8,所以class_to_size[1]=8,span大小为8192Byte,即8KB,为1页,所以class_to_allocnpages[1]=1

Size转换

为何不使用函数计算各种转换,而是写成数组?

有1个很重要的原因:空间换时间。你如果仔细观察了,上表中的转换,并不能通过简单的公式进行转换,比如size和size class的关系,并不是正比的。这些数据是使用较复杂的公式计算出来的,公式在makesizeclass.go中,这其中存在指数运算与for循环,造成每次大小转换的时间复杂度为O(N*2^N)。另外,对一个程序而言,内存的申请和管理操作是很多的,如果不能快速完成,就是非常的低效。把以上大小转换写死到数组里,做到了把大小转换的时间复杂度直接降到O(1)。

其他转换表字段

第4列num of objects代表是当前size class级别的Span可以保存多少对象数量,第5列tail waste是span%obj计算的结果,因为span的大小并不一定是对象大小的整数倍。

最后一列max waste代表最大浪费的内存百分比,计算方法在printComment函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func printComment(w io.Writer, classes []class) {
fmt.Fprintf(w, "// %-5s %-9s %-10s %-7s %-10s %-9s\n", "class", "bytes/obj", "bytes/span", "objects", "tail waste", "max waste")
prevSize := 0
for i, c := range classes {
if i == 0 {
continue
}
spanSize := c.npages * pageSize
objects := spanSize / c.size
tailWaste := spanSize - c.size*(spanSize/c.size)
maxWaste := float64((c.size-prevSize-1)*objects+tailWaste) / float64(spanSize)
prevSize = c.size
fmt.Fprintf(w, "// %5d %9d %10d %7d %10d %8.2f%%\n", i, c.size, spanSize, objects, tailWaste, 100*maxWaste)
}
fmt.Fprintf(w, "\n")
}

Span最浪费内存的场景是:Span内的每一个对象空间保存的对象,实际占用内存是前一个class中对象的大小加1,这样无法占用低一级的Span。一个对象空间未被占用的内存就被浪费了,所以一个Span内对象空间所浪费的内存为:所有对象空间浪费的内存之和+tail waste。

((c.size - (preSize+1)) * objects + tailWaste) / spanSize

Span内object分布情况

感谢foobar的提醒max waste的计算。

Go内存分配

涉及的概念已经讲完了,我们看下Go内存分配原理。

Go中的内存分类并不像TCMalloc那样分成小、中、大对象,但是它的小对象里又细分了一个Tiny对象,Tiny对象指大小在1Byte到16Byte之间并且不包含指针的对象。小对象和大对象只用大小划定,无其他区分。

Go内存对象分类

小对象是在mcache中分配的,而大对象是直接从mheap分配的,从小对象的内存分配看起。

小对象分配

Go内存管理

大小转换这一小节,我们介绍了转换表,size class从1到66共66个,代码中_NumSizeClasses=67代表了实际使用的size class数量,即67个,从0到66,size class 0实际并未使用到。

上文提到1个size class对应2个span class:

1
numSpanClasses = _NumSizeClasses * 2

numSpanClasses为span class的数量为134个,所以span class的下标是从0到133,所以上图中mcache标注了的span class是,span class 0span class 133。每1个span class都指向1个span,也就是mcache最多有134个span。

为对象寻找span

寻找span的流程如下:

  1. 计算对象所需内存大小size
  2. 根据size到size class映射,计算出所需的size class
  3. 根据size class和对象是否包含指针计算出span class
  4. 获取该span class指向的span。

以分配一个不包含指针的,大小为24Byte的对象为例。

根据映射表:

1
2
3
4
5
// class  bytes/obj  bytes/span  objects  tail waste  max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 32 8192 256 0 46.88%
// 4 48 8192 170 32 31.52%

size class 3,它的对象大小范围是(16,32]Byte,24Byte刚好在此区间,所以此对象的size class为3。

Size class到span class的计算如下:

1
2
3
4
// noscan为true代表对象不包含指针
func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}

所以,对应的span class为:

1
span class = 3 << 1 | 1 = 7

所以该对象需要的是span class 7指向的span。

从span分配对象空间

Span可以按对象大小切成很多份,这些都可以从映射表上计算出来,以size class 3对应的span为例,span大小是8KB,每个对象实际所占空间为32Byte,这个span就被分成了256块,可以根据span的起始地址计算出每个对象块的内存地址。

Span内对象

随着内存的分配,span中的对象内存块,有些被占用,有些未被占用,比如上图,整体代表1个span,蓝色块代表已被占用内存,绿色块代表未被占用内存。

当分配内存时,只要快速找到第一个可用的绿色块,并计算出内存地址即可,如果需要还可以对内存块数据清零。

span没有空间怎么分配对象

span内的所有内存块都被占用时,没有剩余空间继续分配对象,mcache会向mcentral申请1个span,mcache拿到span后继续分配对象。

mcentral向mcache提供span

mcentral和mcache一样,都是0~133这134个span class级别,但每个级别都保存了2个span list,即2个span链表:

  1. nonempty:这个链表里的span,所有span都至少有1个空闲的对象空间。这些span是mcache释放span时加入到该链表的。
  2. empty:这个链表里的span,所有的span都不确定里面是否有空闲的对象空间。当一个span交给mcache的时候,就会加入到empty链表。

这2个东西名称一直有点绕,建议直接把empty理解为没有对象空间就好了。

mcentral

实际代码中每1个span class对应1个mcentral,图里把所有mcentral抽象成1个整体了。

mcache向mcentral要span时,mcentral会先从nonempty搜索满足条件的span,如果没有找到再从emtpy搜索满足条件的span,然后把找到的span交给mcache。

mheap的span管理

mheap里保存了2棵二叉排序树,按span的page数量进行排序:

  1. free:free中保存的span是空闲并且非垃圾回收的span。
  2. scav:scav中保存的是空闲并且已经垃圾回收的span。

如果是垃圾回收导致的span释放,span会被加入到scav,否则加入到free,比如刚从OS申请的的内存也组成的Span。

mheap

mheap中还有arenas,有一组heapArena组成,每一个heapArena都包含了连续的pagesPerArena个span,这个主要是为mheap管理span和垃圾回收服务。

mheap本身是一个全局变量,它其中的数据,也都是从OS直接申请来的内存,并不在mheap所管理的那部分内存内。

mcentral向mheap要span

mcentral向mcache提供span时,如果emtpy里也没有符合条件的span,mcentral会向mheap申请span。

mcentral需要向mheap提供需要的内存页数和span class级别,然后它优先从free中搜索可用的span,如果没有找到,会从scav中搜索可用的span,如果还没有找到,它会向OS申请内存,再重新搜索2棵树,必然能找到span。如果找到的span比需求的span大,则把span进行分割成2个span,其中1个刚好是需求大小,把剩下的span再加入到free中去,然后设置需求span的基本信息,然后交给mcentral。

mheap向OS申请内存

当mheap没有足够的内存时,mheap会向OS申请内存,把申请的内存页保存到span,然后把span插入到free树 。

在32位系统上,mheap还会预留一部分空间,当mheap没有空间时,先从预留空间申请,如果预留空间内存也没有了,才向OS申请。

大对象分配

大对象的分配比小对象省事多了,99%的流程与mcentral向mheap申请内存的相同,所以不重复介绍了,不同的一点在于mheap会记录一点大对象的统计信息,见mheap.alloc_m()

Go垃圾回收和内存释放

如果只申请和分配内存,内存终将枯竭,Go使用垃圾回收收集不再使用的span,调用mspan.scavenge()把span释放给OS(并非真释放,只是告诉OS这片内存的信息无用了,如果你需要的话,收回去好了),然后交给mheap,mheap对span进行span的合并,把合并后的span加入scav树中,等待再分配内存时,由mheap进行内存再分配,Go垃圾回收也是一个很强的主题,计划后面单独写一篇文章介绍。

现在我们关注一下,Go程序是怎么把内存释放给操作系统的?

释放内存的函数是sysUnused,它会被mspan.scavenge()调用:

1
2
3
4
5
6
// MAC下的实现
func sysUnused(v unsafe.Pointer, n uintptr) {
// MADV_FREE_REUSABLE is like MADV_FREE except it also propagates
// accounting information about the process to task_info.
madvise(v, n, _MADV_FREE_REUSABLE)
}

注释说_MADV_FREE_REUSABLEMADV_FREE的功能类似,它的功能是给内核提供一个建议:这个内存地址区间的内存已经不再使用,可以回收。但内核是否回收,以及什么时候回收,这就是内核的事情了。如果内核真把这片内存回收了,当Go程序再使用这个地址时,内核会重新进行虚拟地址到物理地址的映射。所以在内存充足的情况下,内核也没有必要立刻回收内存。

4. Go栈内存

最后提一下栈内存。从一个宏观的角度看,内存管理不应当只有堆,也应当有栈。

每个goroutine都有自己的栈,栈的初始大小是2KB,100万的goroutine会占用2G,但goroutine的栈会在2KB不够用时自动扩容,当扩容为4KB的时候,百万goroutine会占用4GB。

关于goroutine栈内存管理,有篇很好的文章,饿了么框架技术部的专栏文章:《聊一聊goroutine stack》,把里面的一段内容摘录下,你感受下:

可以看到在rpc调用(grpc invoke)时,栈会发生扩容(runtime.morestack),也就意味着在读写routine内的任何rpc调用都会导致栈扩容, 占用的内存空间会扩大为原来的两倍,4kB的栈会变为8kB,100w的连接的内存占用会从8G扩大为16G(全双工,不考虑其他开销),这简直是噩梦。

另外,再推荐一篇曹大翻译的一篇汇编入门文章,里面也介绍了扩栈:第一章: Go 汇编入门 ,顺便入门一下汇编。

5. 总结

内存分配原理就不再回顾了,强调2个重要的思想:

  1. 使用缓存提高效率。在存储的整个体系中到处可见缓存的思想,Go内存分配和管理也使用了缓存,利用缓存一是减少了系统调用的次数,二是降低了锁的粒度,减少加锁的次数,从这2点提高了内存管理效率。
  2. 以空间换时间,提高内存管理效率。空间换时间是一种常用的性能优化思想,这种思想其实非常普遍,比如Hash、Map、二叉排序树等数据结构的本质就是空间换时间,在数据库中也很常见,比如数据库索引、索引视图和数据缓存等,再如Redis等缓存数据库也是空间换时间的思想。

6. 参考资料

除了文章中已经推荐的文章,再推荐几篇值得读的文章:

  1. 全成的内存分配文章,有不少帮助:https://juejin.im/post/5c888a79e51d456ed11955a8#heading-5
  2. 异常详细的源码分析文章,看完这篇我就不想写源码分析的文章了:https://www.cnblogs.com/zkweb/p/7880099.html
  3. 从硬件讲起的一篇文章,也是有点意思:https://www.infoq.cn/article/IEhRLwmmIM7-11RYaLHR
  4. 这篇文章的总流程图很棒:http://media.newbmiao.com/blog/malloc.png

7. 彩蛋

在查阅资料时,多篇文章都提到了这本书《The Linux Programming Interface》,关于Thread Cache有兴趣去读一下本书第31章。


  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/07/06/go-memory-allocation/
关注公众号,获取最新Golang文章

合照

6月2日Go语言中文网在杭州举办了线下的MeetUp活动,这次活动办很成功,感谢站长polaris在杭州举办活动的提议,感谢Seekload的筹备与主持,感谢Aaron提供场地,感谢所有到场者的技术经验分享,没有你们就没有这次精彩的活动。

在活动上,我做了个主题分享,今天把分享整理成文章,分享给学习Go语言的各位朋友。

参加本次活动的朋友,大多是刚接触Go,少数几个朋友把玩Go 2~3年了,所以我把主题定位到能让所有人听懂的主题。另外,大家所处行业各有不同,这就要求专注介绍Go本身的特性,这才是大家通用的地方。

最后选题为First class function in Go,这次没有做中文翻译,避免翻译后有误解。这个特性浅显易懂,但掌握Go语言的思维,才能把它用好。

线下分享后,证明选题选对了,大家都能听懂,所以现在不了解First class function的朋友不用着急,后面我会层层推进的方式介绍,相信你一定能理解,那就进入正文吧。

First class function in Go

概念介绍

幻灯片04

某个编程语言拥有First class function特性指可以把函数作为变量对待。也就说,函数与变量没有差别,它们是一样的,变量出现的地方都可以替换成函数,并且编译也是可以通过的,没有任何语法问题。

在Go里,变量可以存在于哪些地方?

幻灯片05

变量可以被声明、定义,可以使用type创建变量的类型,可以作为函数的入参和返回值,可以存在slice, array, map等数据结构里,可以被动态的创建。

在Go中,函数也可以被声明、定义,可以使用type创建一个函数类型,可以作为其他函数的入参和返回值,可以保存在其他类型的数据结构里,最后,函数是可以被动态创建的。

简要归类一下就是下图的样子,除了上面提到的内容,还有匿名函数和闭包,将按下图顺序介绍每一个小特性。

幻灯片06

定义函数类型

幻灯片07

使用type定义一个函数类型,type后是类型名称,本例中是Operation,再后面是类型的定义,对于函数而言,被称为signature,即函数签名,这个函数签名表示:Operation类型的函数,它以2个int类型为入参,以1个int为返回值。所有满足该函数签名的函数,都是Operation类型的函数。

幻灯片08

函数AddSub都符合Operation的签名,所以AddSub都是Operation类型。

声明函数类型的变量和为变量赋值

幻灯片09

变量opOperation类型的,可以把Add作为值赋值给变量op,执行op等价于执行Add

高阶函数

高阶函数分为函数作为入参和函数作为返回值2部分。

函数作为其他函数入参

幻灯片10

定义一个Calculator结构体,它始终保持计算后的结果。

它有一个方法Do,入参为一个Operation类型的函数op和1个int类型的变量a,使用计算器的值c.va作为op的入参,进行指定运算,并把结果保存会c.v

main中,声明了一个变量calccalc.v初始值为0,然后运行了加1和减2的操作,加减法的完成使用的我们之前定义的函数AddSub。操作等价于:

1
2
calc.v = Add(calc.v, 1)
calc.v = Sub(calc.v, 2)

函数作为返回值+动态创建

幻灯片11

这次,改变Operation的定义,修改为接收1个int类型的入参,返回1个int类型的返回值。

同时修改函数AddSub,它们接收1个int类型的入参,返回1个Operation类型的函数,这个函数是动态创建出来的。

Add为例介绍,在Add里动态创建了一个函数,

1
2
3
func(a int) int {
return a + b
}

该函数实现了在变量a基础上加b的操作,并返回结果,我们把这个函数赋值给**变量addB**,把addB作为返回值返回。

所以本例实现了以函数作为返回值和动态创建函数。

幻灯片12

OperationAddSub修改后,Calculator也要同步修改,方法Do修改为只接收Operation类型的函数。

main函数里,注意Do的入参:Add(1),它实现的效果是,创建了1个函数,该函数接收1个值,然后把这个值+1返回,如果用数学表示就是这样:

1
2
// Add(1)
add1(x) = x + 1

同理,Sub(2)的数学表示如下:

1
2
// Sub(2)
sub2(x) = x - 2

所以2次Do操作等价于:

1
2
3
4
// calc.Do(Add(1))
calc.v = add1(calc.v)
// calc.Do(Sub(2))
calc.v = sub2(calc.v)

匿名函数

幻灯片13

上图左边是普通函数,func后为函数名,然后为函数签名。右边只有func和函数签名,缺少函数名,右边的情况为匿名函数。

幻灯片14

Add函数其中定义的函数为例:

1
2
3
func(a int) int {
return a + b
}

这就是1个匿名函数,它没有名字。**addB并不是函数的名字,只是1个变量名而已,只不过这个变量名的类型是没有显示定义出来的**。

Add通常简写为右边的形式。

闭包

幻灯片15

很多人搞不清什么是匿名函数,什么是闭包,所以这里分开介绍这2个概念。

闭包指有权访问另一个函数作用域中的变量的函数。大白话就是,可以创建1个函数,它可以访问其他函数遍历,但不需要传值。

仍然是Add函数为例,比如匿名函数里直接使用了变量b,该匿名函数也是闭包函数。

闭包的特性注定了,闭包函数要定义在一个函数里面,定义在一个函数里面又只能是匿名函数。

那,匿名函数和闭包是不是就等价了?

No,一个函数可以是匿名函数,但不是闭包函数,因为闭包有时是有副作用的。

幻灯片16

我们想并发的把sl中的值打印出来,结果为何会是右边这样?

因为并发的匿名函数,使用的是test1中的i,v,即这是闭包函数,所有的goroutine都共享这2个值,并且启动1个goroutine后,这2个值变为下一个位置的值。你运行的结果也许不是9 9 9….,因为这个goroutine的调度有关。

如何才能符合预期的打印?只使用匿名函数进行传值,不使用闭包。

幻灯片17

Demo

接下来以一个实际的场景,和3种实现版本看如何用Go的思维去解决问题。

场景介绍

幻灯片19

做Go语言工作,尤其是跟网络打交道的工作,连接管理是逃不开的。我做区块链相关的技术工作,区块链中也有网络管理,所以我就以区块链的网络管理为场景进行介绍,但不涉及具体的技术细节,大家莫慌,只需要理解2个概念就行。

区块链是构建在P2P网络之上,在P2P网络中:

  1. 一个节点即可以是服务器也可以是客户端,被称为Host
  2. 和本节点连接的所有节点都被称为Peer

具体的场景是:Host需要保存所有建立连接的Peer,并对这些Peer进行维护:增加和删除Peer,并且提供Peer的查询和向所有Peer广播消息的接口

针对这个问题场景,我写了3个版本的Demo,我们依次来介绍,再看的时候,可以思考其中的不同。

版本1

幻灯片20

先看Peer定义,Peer中保存了ID,我们可以通过ID来表示全网中所有的节点,Peer中还有其他字段,比如网络连接、地址、协议版本等信息,此处已经省略掉。

Peer有一个WriteMsg的方法,实现向该Peer发送消息的功能,例子中使用打印替代。

Peer的定义在3个版本中都不会发生变化,所以后面就不再展示

幻灯片21

Host通过peers保存了所有连接的Peer,可以通过Peer.ID对Peer进行索引。Peer的管理是并发场景,比如,我们可能同时接收到多个Peer的连接,又同时需要向所有Peer广播消息,需要对peers加锁保护。最后,我们省略了Host的其他字段。

NewHost()用来创建一个Host对象,用来代表当前节点。

友情提醒:Host在每一个版本都会不同。

幻灯片22

Host有4个方法,分别是:

  1. AddPeer: 增加1个Peer。
  2. RemovePeer: 删除1个Peer。
  3. GetPeer: 通过Peer.ID查询1个Peer。
  4. BroadcastMsg: 向所有Peer发送消息。

每一个方法都需要获取lock,然后访问peers,如果只读取peers则使用读锁。

第1个版本已经介绍完了,大家可以思考一下版本1的缺点。

幻灯片23

第1个版本跟其他语言实现其实没有本质区别,用C++、Java等也能写出上面逻辑的代码,只不过这个是Go语言实现的罢了。

这个版本是一个communicate by sharing memory的体现,具体来讲,每个goroutine都是1个实体,它们同时运行,调用Host的不同方法来访问peers,只有拿到当前lock的goroutine才能访问peers,仿佛当前goroutine在同其他goroutine讲:我现在有访问权,你们等一下。本质上就是,通过共享Host.lock这块内存,各goroutine进行交流(表明自己拥有访问权)。

版本2

幻灯片24

很多Go老手都听过这句话了,这是Go的“联合创始人”Rob Pike某个会议上说的。

在Go中,推荐使用CSP实现并发,而不是习惯性的使用Lock,使用channel传递数据,达到多goroutine间共享数据的目的,也就是share memory by communicating

所以,我们版本2,就使用channel的方式,来实现Peer的管理。

幻灯片25

在版本1中,peers是大家都想访问的,并且Host有4个方法,画到了上面的图中,我们看下怎么用CSP实现。

peers需要在单独的goroutine中,其他的4个方法在其他的goroutine中调用,它们之间进行通信。

我对使用CSP有一个好的实践,就是把数据流动画出来,并把要流动的数据标上,然后那些数据流动的线条,就是channel,线条上的数据就是channel要传递的数据,图中也把这些线条和数据标上了。具体的细节,可以识别图片中的二维码,看看这篇老文,还有就是并不是所有的并发场景都适合使用channel,有些用锁更好,这篇文章也有介绍。

幻灯片26

重新定义Host,增加了4个channel,从上到下分别用于增加Peer、广播消息、删除Peer和停止Host。

幻灯片27

Host增加了2个方法:

  1. Start()用于启动1个goroutine运行loop()loop保存所有的peers
  2. Stop()用于关闭Host,让loop退出。

幻灯片28

左边是loop()的实现,它从4个channel里接收数据,然后做不同的操作。

右边是AddPeerRemovePeer, BroadcastMsg的实现。

利用1分钟的事件,左右两边对照着看,理解增加1个Peer的全过程。

这就是版本2的全部实现了,思考一下版本2有什么问题,原因是啥?

幻灯片29

幻灯片30

问题就是我们没有实现GetPeer这个方法,聪明的你一定在Host的定义就发现了,只有增加、删除和广播消息的channel。

没能实现GetPeer的原因下图中进行了介绍,你有没有解决办法?

幻灯片31

幻灯片32

幻灯片33

可能会有很多goroutine调用GetPeer,我们需要向每一个goroutine发送结果,这就需要每一个goroutine都需要对应的1个接收结果的channel。

所以我们可以增加1个query channel,channel里传递Peer.ID和接收结果的channel。

还有没有其他办法?我们今天的主题First class function还有入场,你有办法用这个特性实现吗?

版本3

幻灯片34

First class function: 函数可以向变量一样使用。那channel里面是不是可以传递函数呢?当然可以。

幻灯片35

我们可以建立一个channel,用这个channel向loop传递操作peers的函数,所以函数的入参是peers map[string]*Peer,无需返回值,因为函数是在loop里面调用的,调用AddPeer等函数的goroutine是接收不到返回值的。我们把这个类型的函数定义为Operation

Host修改为只有2个channel,stop功能如版本2,opCh用来传递Operation类型的函数。

幻灯片36

loop函数可以简化为左边的形式了,右边是AddPeerRemovePeer,以AddPeer为例进行介绍,创建了一个匿名函数,向peers里增加p,然后把函数发送到opCh

幻灯片37

BroadcastMsgAddPeer类似。

幻灯片38

我们重点看一下GetPeer,创建了retCh用于接收查询的结果,创建了匿名函数进行查询,并把查询结果发送到retCh,然后启动1个goroutine把匿名函数写入到opCh,最后等待从retCh读取查询结果。

这样就实现了向每个调用GetPeer的goroutine发送查询结果。

总结

幻灯片40

总结都在上面了,不多说了。

友情提醒:这3种方式本身并无优劣之分,具体要用那种实现,要依赖自身的实际场景进行取舍。

源码

识别下图二维码。

幻灯片41

PPT下载

下载链接:https://lessisbetter.site/images/Go%E8%AF%AD%E8%A8%80%E6%80%9D%E7%BB%B4First-class-function.pdf

阅读原文下载。

云象介绍

广告时间,云象区块链持续招人,欢迎来撩。

幻灯片42

活动总结

最后奉上Seekload关于本次活动的总结:Gopher杭州线下面基第一期

本文源自同事分享,在此基础之上做简要修改而成。

Linux下有2traffic control(简写TC)和netem这2个工具。Netem 是 Linux 2.6 及以上内核版本提供的一个网络模拟功能模块,该功能模块可以用来在性能良好的局域网中,模拟出复杂的互联网传输性能,诸如低带宽、传输延迟、丢包等等情况。使用 Linux 2.6 (或以上) 版本内核的很多发行版 Linux 都开启了该内核功能,比如Fedora、Ubuntu、Redhat、OpenSuse、CentOS、Debian等等。TC可以用来控制 netem 的工作模式,可完成如下功能:(故障模拟) 模拟时延,丢包,重复包,乱序,控制带宽等。

本文介绍简单的使用方法,更详细的介绍及用法见:wiki:network emulation

TC实现原理

TC用于Linux内核的流量控制,主要是通过在输出端口处建立一个队列来实现流量控制。接收包从输入接口(Input Interface)进来后,经过流量限制(Ingress Policing)丢弃不符合规定的数据包,由输入多路分配器(Input De-Multiplexing)进行判断选择:如果接收包的目的是本主机,那么将该包送给上层处理;否则需要进行转发,将接收包交到转发块(Forwarding Block)处理。转发块同时也接收本主机上层(TCP、UDP等)产生的包。转发块通过查看路由表,决定所处理包的下一跳。然后,对包进行排列以便将它们传送到输出接口(Output Interface)。一般我们只能限制网卡发送的数据包,不能限制网卡接收的数据包,所以我们可以通过改变发送次序来控制传输速率。Linux流量控制主要是在输出接口排列时进行处理和实现的。

使用方法

以下模拟命令可配合使用,实现即延迟又丢包等情况。

模拟延迟传输

1
tc qdisc add dev eth0 root netem delay 100ms

该命令将 eth0 网卡的传输设置为延迟100毫秒发送。
更真实的情况下,延迟值不会这么精确,会有一定的波动,我们可以用下面的情况来模拟出带有波动性的延迟值:

1
tc qdisc add dev eth0 root netem delay 100ms 50ms

该命令将 eth0 网卡的传输设置为延迟 100ms ± 50ms (50 ~ 150 ms 之间的任意值)发送。
还可以更进一步加强这种波动的随机性:

1
tc qdisc add dev eth0 root netem delay 100ms 50ms 30%

该命令将 eth0 网卡的传输设置为 100ms ,同时,大约有 30% 的包会延迟 ± 50ms 发送。   

模拟网络丢包

1
tc qdisc add dev eth0 root netem loss 10%

该命令将 eth0 网卡的传输设置为随机丢掉 10% 的数据包。
也可以设置丢包的成功率:

1
tc qdisc add dev eth0 root netem loss 10% 30%

模拟包重复

1
tc qdisc add dev eth0 root netem duplicate 10%

该命令将 eth0 网卡的传输设置为随机产生 10% 的重复数据包 。

模拟包损坏

1
tc qdisc add dev eth0 root netem corrupt 1%

模拟包乱序

1
tc qdisc change dev eth0 root netem delay 10ms reorder 10% 50%

该命令将 eth0 网卡的传输设置为:有 10% 的数据包(50%相关)会被立即发送,其他的延迟 10 秒。

删除设备及显示设置

显示配置:

1
tc qdisc sh dev eth0

删除配置:

1
tc qd del dev eth0 root

参考资料

Linux网络流量控制工具—Netem

  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/05/18/linux-simulate-bad-network/
关注公众号,获取最新Golang文章