forked from xilibi2003/learnblockchain
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
521 lines (305 loc) · 294 KB
/
atom.xml
File metadata and controls
521 lines (305 loc) · 294 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>深入浅出区块链</title>
<subtitle>系统学习区块链技术</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://learnblockchain.cn/"/>
<updated>2018-12-13T03:55:25.886Z</updated>
<id>https://learnblockchain.cn/</id>
<author>
<name>Tiny熊</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>区块链技术工坊 - 线下区块链技术分享</title>
<link href="https://learnblockchain.cn/2018/12/13/technical-workshop-4/"/>
<id>https://learnblockchain.cn/2018/12/13/technical-workshop-4/</id>
<published>2018-12-13T03:41:23.000Z</published>
<updated>2018-12-13T03:55:25.886Z</updated>
<content type="html"><![CDATA[<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br><a id="more"></a></p><h2 id="第四期-以太坊零手续费及其安全防御的实现"><a href="#第四期-以太坊零手续费及其安全防御的实现" class="headerlink" title="[第四期] 以太坊零手续费及其安全防御的实现"></a>[第四期] 以太坊零手续费及其安全防御的实现</h2><p>讲师:以太零CTO 钟瑞仙</p><p>主要内容:</p><pre><code>1. 以太坊⼿手续费简介2. 零⼿手续费的必要性 3. 零⼿手续费的实现4. 零⼿手续费带来的安全问题及其解决⽅方案5. 零⼿手续费的副作⽤</code></pre><p><a href="https://wiki.learnblockchain.cn/pdf/meetup_4.pdf" target="_blank" rel="noopener">点击下载PPT</a>及<a href="https://m.qlchat.com/wechat/page/channel-intro?channelId=2000002858537956" target="_blank" rel="noopener">完整课程视频</a></p>]]></content>
<summary type="html">
<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br>
</summary>
<category term="project" scheme="https://learnblockchain.cn/categories/project/"/>
<category term="project" scheme="https://learnblockchain.cn/tags/project/"/>
</entry>
<entry>
<title>区块链技术工坊 - 线下区块链技术分享</title>
<link href="https://learnblockchain.cn/2018/12/13/technical-workshop-2/"/>
<id>https://learnblockchain.cn/2018/12/13/technical-workshop-2/</id>
<published>2018-12-13T03:41:23.000Z</published>
<updated>2018-12-13T03:53:23.307Z</updated>
<content type="html"><![CDATA[<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br><a id="more"></a></p><h2 id="第二期-深度探索以太坊智能合约"><a href="#第二期-深度探索以太坊智能合约" class="headerlink" title="[第二期]深度探索以太坊智能合约"></a>[第二期]深度探索以太坊智能合约</h2><p>讲师:以太零CTO 钟瑞仙</p><p>主要内容包含:</p><pre><code>1. 以太坊账户介绍2. 交易数据⾥里data字段的编码规则3. 智能合约属性的索引和存储4. 预编译合约介绍及汇编调⽤</code></pre><p><a href="https://wiki.learnblockchain.cn/pdf/deep_smartcontract.pdf" target="_blank" rel="noopener">点击下载PPT</a>及<a href="https://m.qlchat.com/wechat/page/channel-intro?channelId=2000002631831830&sourceNo=link" target="_blank" rel="noopener">完整课程视频</a></p>]]></content>
<summary type="html">
<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br>
</summary>
<category term="project" scheme="https://learnblockchain.cn/categories/project/"/>
<category term="project" scheme="https://learnblockchain.cn/tags/project/"/>
</entry>
<entry>
<title>区块链技术工坊 - 线下区块链技术分享</title>
<link href="https://learnblockchain.cn/2018/12/13/technical-workshop-3/"/>
<id>https://learnblockchain.cn/2018/12/13/technical-workshop-3/</id>
<published>2018-12-13T03:41:23.000Z</published>
<updated>2018-12-13T03:53:22.131Z</updated>
<content type="html"><![CDATA[<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br><a id="more"></a></p><h2 id="第三期-高TPS与去中心化存储带来的机遇"><a href="#第三期-高TPS与去中心化存储带来的机遇" class="headerlink" title="[第三期]高TPS与去中心化存储带来的机遇"></a>[第三期]高TPS与去中心化存储带来的机遇</h2><p>讲师:星际区块(深圳)CEO 谢建怀</p><p>主要内容:</p><pre><code>1. 高TPS能让我们做更多有意思的东西2. 第三代区块链技术能落地的思考 3. 去中心化存储能在工程上带来哪些应用4. 区块链应用的项目探索(基于EOS、FIBOS和IPFS 应用)</code></pre><p><a href="https://wiki.learnblockchain.cn/pdf/meeting_3.pdf" target="_blank" rel="noopener">点击下载PPT</a>及<a href="https://m.qlchat.com/live/channel/channelPage/2000002746846802.htm" target="_blank" rel="noopener">完整课程视频</a></p>]]></content>
<summary type="html">
<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br>
</summary>
<category term="project" scheme="https://learnblockchain.cn/categories/project/"/>
<category term="project" scheme="https://learnblockchain.cn/tags/project/"/>
</entry>
<entry>
<title>区块链技术工坊 - 线下区块链技术分享</title>
<link href="https://learnblockchain.cn/2018/12/13/technical-workshop-1/"/>
<id>https://learnblockchain.cn/2018/12/13/technical-workshop-1/</id>
<published>2018-12-13T03:25:59.000Z</published>
<updated>2018-12-13T03:53:24.752Z</updated>
<content type="html"><![CDATA[<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br><a id="more"></a></p><h2 id="第一期-以太坊钱包开发"><a href="#第一期-以太坊钱包开发" class="headerlink" title="[第一期]以太坊钱包开发"></a>[第一期]以太坊钱包开发</h2><p>讲师:登链学院 熊丽兵</p><p>分享大纲:</p><pre><code>1. 私钥 地址 及账号2. 什么是HD钱包(分层确定性钱包)3. 助记词及私钥保存4. 如何测量gasLimit及设定gasPrice5. 如何发送签名交易及转移Token</code></pre><p><a href="https://wiki.learnblockchain.cn/pdf/eth_wallet.pdf" target="_blank" rel="noopener">点击下载PPT</a>及<a href="https://m.qlchat.com/wechat/page/channel-intro?channelId=2000002356009198" target="_blank" rel="noopener">完整课程视频</a></p>]]></content>
<summary type="html">
<p>区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。<br>欢迎大家关注微信:upchainedu 及时获取活动信息。<br>
</summary>
<category term="project" scheme="https://learnblockchain.cn/categories/project/"/>
<category term="project" scheme="https://learnblockchain.cn/tags/project/"/>
</entry>
<entry>
<title>Fabric1.0 交易流程</title>
<link href="https://learnblockchain.cn/2018/11/21/fabric_transaction_process/"/>
<id>https://learnblockchain.cn/2018/11/21/fabric_transaction_process/</id>
<published>2018-11-21T09:30:27.000Z</published>
<updated>2018-12-13T03:23:29.116Z</updated>
<content type="html"><![CDATA[<p>这篇文章详细介绍fabric的交易流程,以图片加文字的形式呈现。</p><a id="more"></a><h2 id="Fabric-1-0交易流程"><a href="#Fabric-1-0交易流程" class="headerlink" title="Fabric 1.0交易流程"></a>Fabric 1.0交易流程</h2><p>Fabric中的所有交易都是通过chaincode执行</p><p><img src="/images/transaction_process.png" alt=""></p><ol><li><p>应用程序客户端通过SDK调用证书服务(CA)服务,进行注册和登记,并获取身份证书。</p></li><li><p>应用程序客户端通过SDK创建好交易提案(Proposal),交易提案把带有本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等信息发送给背书(Endorser)节点。</p></li><li><p>背书(Endorser)节点收到交易提案(Proposal)后,开始进行验证,验证的内容如下:</p><ul><li>交易预案是完好的</li><li>该预案以前没有提交过(防止重放攻击)</li><li>携带的签名是合法的</li><li>交易发起者是否满足区块链写策略, 即ACL 权限检查</li></ul></li></ol><p>满足以上要求后,背书节点把’交易预案’作为输入参数,调用chaincode中的函数,chaincode根据当前的账本状态计算出一个’交易结果’,该结果包括返回值,读写集。此时,区块链账本并不会被更新。’交易结果’在被签名后与一个是/否的背书结果一同返回,称之为’预案回复’。</p><ol><li><p>应用程序客户端收到背书(Endorser)节点返回的信息后,判断提案结果是否一致,以及是否收到足够多的背书节点返回的结果(参照指定的背书策略执行),如果没有足够的背书,则中止处理,这个交易就会被舍弃。否则,将交易提案、模拟交易结果和背书信息打包组成一个交易并签名发给Orderer节点(一个排序服务)。</p></li><li><p>Orderer节点对来自客户端(SDK)的交易信息进行共识排序,分通道对’交易消息’按时间排序,并按通道将交易打包成块,发送给提交(Committer)节点。</p></li><li><p>提交(Committer)节点收到区块后,会对区块中的每笔交易进行校验,检查交易依赖的输入输出是否符合当前区块链的状态,验证背书策略是否满足,验证完成后将区块追加到本地的区块链,更新账本,并修改世界状态。具体过程如下:</p><ul><li>运行验证逻辑(VSCC检查背书策略)</li><li>在区块中指明哪些交易是有效和无效的。</li><li>在内存或文件系统上把区块加入区块链</li><li>将区块内的有效交易写入状态数据库。</li><li>发出Event消息,使得客户端通过SDK监听知道哪些交易是有效的或无效的。</li></ul></li></ol><p>本文的作者是lgy</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>这篇文章详细介绍fabric的交易流程,以图片加文字的形式呈现。</p>
</summary>
<category term="Fabric" scheme="https://learnblockchain.cn/categories/Fabric/"/>
<category term="联盟链" scheme="https://learnblockchain.cn/categories/Fabric/%E8%81%94%E7%9B%9F%E9%93%BE/"/>
<category term="Fabric" scheme="https://learnblockchain.cn/tags/Fabric/"/>
</entry>
<entry>
<title>Fabric 网络环境启动过程详解</title>
<link href="https://learnblockchain.cn/2018/11/21/fabric_startup%20_process/"/>
<id>https://learnblockchain.cn/2018/11/21/fabric_startup _process/</id>
<published>2018-11-21T09:30:27.000Z</published>
<updated>2018-12-12T11:13:03.332Z</updated>
<content type="html"><![CDATA[<p>这篇文章对Fabric的网络环境启动过程进行讲解,也就是我们<a href="https://learnblockchain.cn/2018/11/21/fabric_introduction/">上节</a>讲到的启动测试Fabric网络环境时运行network_setup.sh这个文件的执行流程</p><a id="more"></a><h2 id="Fabric网络环境启动过程详解"><a href="#Fabric网络环境启动过程详解" class="headerlink" title="Fabric网络环境启动过程详解"></a>Fabric网络环境启动过程详解</h2><p>上一节我们讲到 fabric网络环境的启动测试,主要是使用 <strong>./network_setup.sh up</strong> 这个命令,所以fabric网络环境启动的重点就在network_setup.sh这个文件中。接下来我们就分析一下network_setup.sh这个文件。<br>network_setup.sh其中包括两个部分,一个是利用generateArtifacts.sh脚本文件配置组织关系和颁发证书、公/私钥、通道证书等,另一个是docker-compose-cli.yaml用于根据配置启动集群并测试chaincode的示例代码。下面是具体的流程图介绍:</p><p><img src="/images/startup _process.png" alt=""></p><p><strong>首先看下generateArtifacts.sh脚本文件,它包含三个函数,分别是:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">1.generateCerts:</span><br><span class="line"> 该函数使用cryptogen工具根据crypto-config.yaml来生成公私钥和证书信息等。</span><br><span class="line"></span><br><span class="line">2.replacePrivateKey:</span><br><span class="line"> 将docker-compose-e2e-template.yaml文档中的ca私钥替换成具体的私钥。</span><br><span class="line"></span><br><span class="line">3.generateChannelArtifacts:</span><br><span class="line"> 使用configtxgen工具根据configtx.yaml文件来生成创世区块和通道相关信息,更新锚节点。</span><br></pre></td></tr></table></figure><p><strong>接着是docker-compose-cli.yaml文件</strong></p><p>docker-compose-cli.yaml文件根据组织关系启动docker集群,并在cli容器中执行command命令运行./scripts/script.sh脚本文件。 那./scripts/script.sh脚本具体做了什么呢?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">1. createChannel:创建channel。</span><br><span class="line">2. joinChannel:将每个peer节点加入channel。</span><br><span class="line">3. updateAnchorPeers:更新锚节点</span><br><span class="line">4. installChaincode:部署chaincode。</span><br><span class="line">5. instantiateChaincode:初始化chaincode。</span><br><span class="line">6. chaincodeQuery:chaincode查询</span><br></pre></td></tr></table></figure><p>另外docker-compose-cli.yaml这个文件还有一个配置项是需要注意的地方,那就是:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file: base/docker-compose-base.yaml</span><br></pre></td></tr></table></figure><p>这里的docker-compose-base.yaml其实就是Orderer和peer的基础配置文件,包括指定端口等。</p><h3 id="几个重要的配置文件"><a href="#几个重要的配置文件" class="headerlink" title="几个重要的配置文件"></a>几个重要的配置文件</h3><h4 id="1-crypto-config-yaml"><a href="#1-crypto-config-yaml" class="headerlink" title="1.crypto-config.yaml"></a>1.crypto-config.yaml</h4><p>基于crypto-config.yaml(此文件在../fabric/examples/e2e_cli中)<strong>生成公、私钥和证书信息,并保存在crypto-config文件夹中</strong>。另外crypto-config.yaml还定义了组织成员以及组织下的peer节点个数。</p><p><strong>crypto-config.yaml文件讲解:</strong></p><p>字段Name和Domain就是关于这个组织的名字和域名,这主要是用于生成证书的时候,证书内会包含该信息。而Template.Count=2是说我们要生成2套公私钥和证书,一套是peer0.org1的,还有一套是peer1.org1的(也就指定了org中存在peer0和peer1两个节点)。最后Users.Count=1是说每个Template下面会有几个普通User(注意,Admin是Admin,不包含在这个计数中),这里配置了1,也就是说我们只需要一个普通用户User1@org1.example.com 我们可以根据实际需要调整这个配置文件,增删Org Users等。文件内容如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Orderer</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> - Name: Orderer</span><br><span class="line"> Domain: example.com</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # "Specs" - See PeerOrgs below for complete description</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> Specs:</span><br><span class="line"> - Hostname: orderer</span><br><span class="line"># ---------------------------------------------------------------------------</span><br><span class="line"># "PeerOrgs" - Definition of organizations managing peer nodes</span><br><span class="line"># ---------------------------------------------------------------------------</span><br><span class="line">PeerOrgs:</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Org1</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> - Name: Org1</span><br><span class="line"> Domain: org1.example.com</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # "Specs"</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Uncomment this section to enable the explicit definition of hosts in your</span><br><span class="line"> # configuration. Most users will want to use Template, below</span><br><span class="line"> #</span><br><span class="line"> # Specs is an array of Spec entries. Each Spec entry consists of two fields:</span><br><span class="line"> # - Hostname: (Required) The desired hostname, sans the domain.</span><br><span class="line"> # - CommonName: (Optional) Specifies the template or explicit override for</span><br><span class="line"> # the CN. By default, this is the template:</span><br><span class="line"> #</span><br><span class="line"> # "{{.Hostname}}.{{.Domain}}"</span><br><span class="line"> #</span><br><span class="line"> # which obtains its values from the Spec.Hostname and</span><br><span class="line"> # Org.Domain, respectively.</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Specs:</span><br><span class="line"> # - Hostname: foo # implicitly "foo.org1.example.com"</span><br><span class="line"> # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above</span><br><span class="line"> # - Hostname: bar</span><br><span class="line"> # - Hostname: baz</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # "Template"</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Allows for the definition of 1 or more hosts that are created sequentially</span><br><span class="line"> # from a template. By default, this looks like "peer%d" from 0 to Count-1.</span><br><span class="line"> # You may override the number of nodes (Count), the starting index (Start)</span><br><span class="line"> # or the template used to construct the name (Hostname).</span><br><span class="line"> #</span><br><span class="line"> # Note: Template and Specs are not mutually exclusive. You may define both</span><br><span class="line"> # sections and the aggregate nodes will be created for you. Take care with</span><br><span class="line"> # name collisions</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> Template:</span><br><span class="line"> Count: 2</span><br><span class="line"> # Start: 5</span><br><span class="line"> # Hostname: {{.Prefix}}{{.Index}} # default</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # "Users"</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Count: The number of user accounts _in addition_ to Admin</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> Users:</span><br><span class="line"> Count: 1</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> # Org2: See "Org1" for full specification</span><br><span class="line"> # ---------------------------------------------------------------------------</span><br><span class="line"> - Name: Org2</span><br><span class="line"> Domain: org2.example.com</span><br><span class="line"> Template:</span><br><span class="line"> Count: 2</span><br><span class="line"> Users:</span><br><span class="line"> Count: 1</span><br></pre></td></tr></table></figure><p><strong>注:</strong><br>peer:<br>Fabric 网络中的节点,表现为一个运行着的docker容器。可以与网络中的其他peer进行通信,每个peer都在本地保留一份ledger的副本。它是org下的组织成员。<br>org:<br>一个组织,它可以由一个或多个peer组成。<br>Orderer :<br>联盟成员共享的中心化节点。用来对交易进行排序,是 Fabric 共识机制的重要组成部分。</p><h4 id="2-configtx-yaml"><a href="#2-configtx-yaml" class="headerlink" title="2.configtx.yaml"></a>2.configtx.yaml</h4><p>基于configtx.yaml(此文件在../fabric/examples/e2e_cli中)<strong>生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。还可以指定背书策略。</strong></p><p><strong>configtx.yaml文件讲解:</strong></p><ol><li>官方提供的examples/e2e_cli/configtx.yaml这个文件里面配置了由2个Org参与的Orderer共识配置TwoOrgsOrdererGenesis,以及由2个Org参与的Channel配置:TwoOrgsChannel。</li><li>另外我们可以在此文件的Orderer部分设置共识的算法是Solo还是Kafka,以及共识时区块大小,超时时间等,我们使用默认值即可,不用更改。而Peer节点的配置包含了MSP的配置,锚节点的配置。如果我们有更多的Org,或者有更多的Channel,那么就可以根据模板进行对应的修改。</li><li>Policies配置也要特别注意,该配置项定义了不同角色的权限,Reader,Writer以及Admin分别对应读,写,以及admin权限,读权限角色只能从别的peer节点同步账本而不能发起交易,只有writer定义项下的角色才拥有发起交易的也就是调用chaincode的invoke方法的权限(不一定都是invoke方案,只要涉及到chaincode中状态修改的方法,都只有拥有writer权限或admin权限的角色才能调用)。以该配置的Organizations配置下的Org1配置为例,”OR(‘Org1MSP.admin’, ‘Org1MSP.client’)”,表示org1的msp服务中的admin或者client角色拥有发起交易的权限。文件内容如下:</li></ol><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br></pre></td><td class="code"><pre><span class="line"># Copyright IBM Corp. All Rights Reserved.</span><br><span class="line">#</span><br><span class="line"># SPDX-License-Identifier: Apache-2.0</span><br><span class="line">#</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">################################################################################</span><br><span class="line">#</span><br><span class="line"># Profile</span><br><span class="line">#</span><br><span class="line"># - Different configuration profiles may be encoded here to be specified</span><br><span class="line"># as parameters to the configtxgen tool</span><br><span class="line">#</span><br><span class="line">################################################################################</span><br><span class="line">Profiles:</span><br><span class="line"></span><br><span class="line"> TwoOrgsOrdererGenesis:</span><br><span class="line"> Orderer:</span><br><span class="line"> <<: *OrdererDefaults</span><br><span class="line"> Organizations:</span><br><span class="line"> - *OrdererOrg</span><br><span class="line"> Consortiums:</span><br><span class="line"> SampleConsortium:</span><br><span class="line"> Organizations:</span><br><span class="line"> - *Org1</span><br><span class="line"> - *Org2</span><br><span class="line"> TwoOrgsChannel:</span><br><span class="line"> Consortium: SampleConsortium</span><br><span class="line"> Application:</span><br><span class="line"> <<: *ApplicationDefaults</span><br><span class="line"> Organizations:</span><br><span class="line"> - *Org1</span><br><span class="line"> - *Org2</span><br><span class="line"></span><br><span class="line">################################################################################</span><br><span class="line">#</span><br><span class="line"># Section: Organizations</span><br><span class="line">#</span><br><span class="line"># - This section defines the different organizational identities which will</span><br><span class="line"># be referenced later in the configuration.</span><br><span class="line">#</span><br><span class="line">################################################################################</span><br><span class="line">Organizations:</span><br><span class="line"></span><br><span class="line"> # SampleOrg defines an MSP using the sampleconfig. It should never be used</span><br><span class="line"> # in production but may be used as a template for other definitions</span><br><span class="line"> - &OrdererOrg</span><br><span class="line"> # DefaultOrg defines the organization which is used in the sampleconfig</span><br><span class="line"> # of the fabric.git development environment</span><br><span class="line"> Name: OrdererOrg</span><br><span class="line"></span><br><span class="line"> # ID to load the MSP definition as</span><br><span class="line"> ID: OrdererMSP</span><br><span class="line"></span><br><span class="line"> # MSPDir is the filesystem path which contains the MSP configuration</span><br><span class="line"> MSPDir: crypto-config/ordererOrganizations/example.com/msp</span><br><span class="line"></span><br><span class="line"> - &Org1</span><br><span class="line"> # DefaultOrg defines the organization which is used in the sampleconfig</span><br><span class="line"> # of the fabric.git development environment</span><br><span class="line"> Name: Org1MSP</span><br><span class="line"></span><br><span class="line"> # ID to load the MSP definition as</span><br><span class="line"> ID: Org1MSP</span><br><span class="line"></span><br><span class="line"> MSPDir: crypto-config/peerOrganizations/org1.example.com/msp</span><br><span class="line"></span><br><span class="line"> AnchorPeers:</span><br><span class="line"> # AnchorPeers defines the location of peers which can be used</span><br><span class="line"> # for cross org gossip communication. Note, this value is only</span><br><span class="line"> # encoded in the genesis block in the Application section context</span><br><span class="line"> - Host: peer0.org1.example.com</span><br><span class="line"> Port: 7051</span><br><span class="line"></span><br><span class="line"> - &Org2</span><br><span class="line"> # DefaultOrg defines the organization which is used in the sampleconfig</span><br><span class="line"> # of the fabric.git development environment</span><br><span class="line"> Name: Org2MSP</span><br><span class="line"></span><br><span class="line"> # ID to load the MSP definition as</span><br><span class="line"> ID: Org2MSP</span><br><span class="line"></span><br><span class="line"> MSPDir: crypto-config/peerOrganizations/org2.example.com/msp</span><br><span class="line"></span><br><span class="line"> AnchorPeers:</span><br><span class="line"> # AnchorPeers defines the location of peers which can be used</span><br><span class="line"> # for cross org gossip communication. Note, this value is only</span><br><span class="line"> # encoded in the genesis block in the Application section context</span><br><span class="line"> - Host: peer0.org2.example.com</span><br><span class="line"> Port: 7051</span><br><span class="line"></span><br><span class="line">################################################################################</span><br><span class="line">#</span><br><span class="line"># SECTION: Orderer</span><br><span class="line">#</span><br><span class="line"># - This section defines the values to encode into a config transaction or</span><br><span class="line"># genesis block for orderer related parameters</span><br><span class="line">#</span><br><span class="line">################################################################################</span><br><span class="line">Orderer: &OrdererDefaults</span><br><span class="line"></span><br><span class="line"> # Orderer Type: The orderer implementation to start</span><br><span class="line"> # Available types are "solo" and "kafka"</span><br><span class="line"> OrdererType: solo</span><br><span class="line"></span><br><span class="line"> Addresses:</span><br><span class="line"> - orderer.example.com:7050</span><br><span class="line"></span><br><span class="line"> # Batch Timeout: The amount of time to wait before creating a batch</span><br><span class="line"> BatchTimeout: 2s</span><br><span class="line"></span><br><span class="line"> # Batch Size: Controls the number of messages batched into a block</span><br><span class="line"> BatchSize:</span><br><span class="line"></span><br><span class="line"> # Max Message Count: The maximum number of messages to permit in a batch</span><br><span class="line"> MaxMessageCount: 10</span><br><span class="line"></span><br><span class="line"> # Absolute Max Bytes: The absolute maximum number of bytes allowed for</span><br><span class="line"> # the serialized messages in a batch.</span><br><span class="line"> AbsoluteMaxBytes: 98 MB</span><br><span class="line"></span><br><span class="line"> # Preferred Max Bytes: The preferred maximum number of bytes allowed for</span><br><span class="line"> # the serialized messages in a batch. A message larger than the preferred</span><br><span class="line"> # max bytes will result in a batch larger than preferred max bytes.</span><br><span class="line"> PreferredMaxBytes: 512 KB</span><br><span class="line"></span><br><span class="line"> Kafka:</span><br><span class="line"> # Brokers: A list of Kafka brokers to which the orderer connects</span><br><span class="line"> # NOTE: Use IP:port notation</span><br><span class="line"> Brokers:</span><br><span class="line"> - 127.0.0.1:9092</span><br><span class="line"></span><br><span class="line"> # Organizations is the list of orgs which are defined as participants on</span><br><span class="line"> # the orderer side of the network</span><br><span class="line"> Organizations:</span><br><span class="line"></span><br><span class="line">################################################################################</span><br><span class="line">#</span><br><span class="line"># SECTION: Application</span><br><span class="line">#</span><br><span class="line"># - This section defines the values to encode into a config transaction or</span><br><span class="line"># genesis block for application related parameters</span><br><span class="line">#</span><br><span class="line">################################################################################</span><br><span class="line">Application: &ApplicationDefaults</span><br><span class="line"></span><br><span class="line"> # Organizations is the list of orgs which are defined as participants on</span><br><span class="line"> # the application side of the network</span><br><span class="line"> Organizations:</span><br></pre></td></tr></table></figure><p>本文的作者是lgy</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>这篇文章对Fabric的网络环境启动过程进行讲解,也就是我们<a href="https://learnblockchain.cn/2018/11/21/fabric_introduction/">上节</a>讲到的启动测试Fabric网络环境时运行network_setup.sh这个文件的执行流程</p>
</summary>
<category term="Fabric" scheme="https://learnblockchain.cn/categories/Fabric/"/>
<category term="联盟链" scheme="https://learnblockchain.cn/categories/Fabric/%E8%81%94%E7%9B%9F%E9%93%BE/"/>
<category term="Fabric" scheme="https://learnblockchain.cn/tags/Fabric/"/>
</entry>
<entry>
<title>联盟链初识以及Fabric环境搭建流程</title>
<link href="https://learnblockchain.cn/2018/11/21/fabric_introduction/"/>
<id>https://learnblockchain.cn/2018/11/21/fabric_introduction/</id>
<published>2018-11-21T09:30:27.000Z</published>
<updated>2018-12-12T11:18:45.576Z</updated>
<content type="html"><![CDATA[<p>这篇文章首先简单介绍了联盟链是什么,再详细的介绍了Fabric环境搭建的整个流程。<br><a id="more"></a></p><h2 id="区块链分类"><a href="#区块链分类" class="headerlink" title="区块链分类"></a>区块链分类</h2><p>以参与方式分类,区块链可以分为:公有链、联盟链和私有链。</p><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h2><p>我们知道区块链就是一个分布式的,去中心化的公共数据库(或称公共账本)。而联盟链是区块链的一个分支,所以它本身也是一个分布式的,去中心化的公共数据库,跟其他链的区别就是它是针对特定群体的成员和有限的第三方,其内部指定多个预选节点为记账人,其共识过程受到预选节点控制的区块链</p><h2 id="本质"><a href="#本质" class="headerlink" title="本质"></a>本质</h2><p>联盟链本质仍然是一种私有链,只不过它要比单个小组织开发的私有链更大,但是却没有公有链这么大的规模,可以理解为它是介于公有链和私有链的一种区块链。</p><h2 id="联盟链的特点"><a href="#联盟链的特点" class="headerlink" title="联盟链的特点"></a>联盟链的特点</h2><ul><li>交易速度快<br> 我们知道对于公有链来说,要想达成共识,必须得由区块链中的所有节点来决定,本身公有链的节点数量就非常庞大,所以处理速度很慢。但对于联盟链来说,由于其节点不多的原因,而且只要当网络上2/3的节点达成共识,就可以完成交易,交易速度自然也就快很多。</li><li>数据默认不会公开<br> 不同于公有链,联盟链上的信息并不是所有有访问条件的人就可以访问的,联盟链的数据只限于联盟里的机构及其用户才有权限进行访问。</li><li>部分去中心化<br>与公有链不同,联盟链某种程度上只属于联盟内部的所有成员所有,且很容易达成共识,因为其节点数毕竟是有限的。</li></ul><h3 id="联盟链项目"><a href="#联盟链项目" class="headerlink" title="联盟链项目"></a>联盟链项目</h3><p>R3:由40多加银行参与的区块链联盟R3,包括世界著名的银行(如摩根大通、高盛、瑞信、伯克莱、汇丰银行等),IT巨头(如IBM、微软)。</p><p>超级账本(Hyperledger):由 Linux基金会在2015年12月主导发起该项目, 成员包括金融,银行,物联网,供应链,制造和科技行业的领头羊。</p><h2 id="Fabric介绍"><a href="#Fabric介绍" class="headerlink" title="Fabric介绍"></a>Fabric介绍</h2><p>我们知道智能合约比较成功的就是以太坊了。以太坊主要是公有链,其实对企业应用来说并不是特别合适,而且本身并没有权限控制功能,面向企业的,主要还是HyperLedger Fabric,当然还有R3的Corda。这里我们主要是讲Fabric。<br>Fabric是一个面向企业应用的区块链框架,基于Fabric的开发可以粗略分为几个层面:</p><p><strong>1.</strong> 参与Fabric的底层开发,这主要是fabric,fabric-ca和sdk等核心组件。<br><strong>2.</strong> 参与Fabric周边生态的开发,如支持如支持fabric的工具explorer, composer等。<br><strong>3.</strong> 利用fabric平台开发应用,这就是利用fabirc提供的各种sdk来为应用服务(应用开发)</p><p>大部分企业会参与2-3的内容,以3为主来服务应用场景,以2为辅。因为现在除了区块链核心功能尚未完善外,对区块链的管理,运维,监控,测试,优化,调试等工具非常匮乏。企业将不得不面对自己开发一些工作。</p><h3 id="Fabric环境依赖"><a href="#Fabric环境依赖" class="headerlink" title="Fabric环境依赖"></a>Fabric环境依赖</h3><p>Fabric官方推荐的开发环境是基于docker搭建的,使用docker搭建需要一下前置条件:</p><ul><li>docker一一Docker version 17.06.2-ce 或以上版本</li><li>Docker Compose一一1.14或以上版本</li><li>Go一一1.10或以上版本, Node.js一一8.9.x或以上版本</li><li>Python一一主要是python-pip</li></ul><h3 id="Fabric环境搭建具体步骤"><a href="#Fabric环境搭建具体步骤" class="headerlink" title="Fabric环境搭建具体步骤"></a>Fabric环境搭建具体步骤</h3><p>这里使用的是Ubuntu 16.04.4版本</p><h4 id="1-安装go及环境变量配置"><a href="#1-安装go及环境变量配置" class="headerlink" title="1.安装go及环境变量配置"></a><strong>1.安装go及环境变量配置</strong></h4><p>(1)下载最新版本的go二进制文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ wget https://dl.google.com/go/go1.9.2.linux-amd64.tar.gz</span><br></pre></td></tr></table></figure><p>(2)解压文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ sudo tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz</span><br></pre></td></tr></table></figure><p>(3)配置环境变量</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim ~/.profile</span><br></pre></td></tr></table></figure><p>添加以下内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">export PATH=$PATH:/usr/local/go/bin</span><br><span class="line">export GOROOT=/usr/local/go</span><br><span class="line">export GOPATH=$HOME/go</span><br><span class="line">export PATH=$PATH:$HOME/go/bin</span><br></pre></td></tr></table></figure><p>编辑保存并退出vi后,记得使这些环境变量生效</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source ~/.profile</span><br></pre></td></tr></table></figure><h4 id="2-安装Docker"><a href="#2-安装Docker" class="headerlink" title="2.安装Docker"></a><strong>2.安装Docker</strong></h4><p>Fabric的chaincode是运行在docker里的。</p><p><strong>(1)</strong> 由于apt官方库里的docker版本可能比较旧,所以先卸载可能存在的旧版本:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get remove docker docker-engine docker-ce docker.io</span><br></pre></td></tr></table></figure><p><strong>(2)</strong> 更新apt包索引:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br></pre></td></tr></table></figure><p><strong>(3)</strong> 安装以下包以使apt可以通过HTTPS使用存储库(repository):</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common</span><br></pre></td></tr></table></figure><p><strong>(4)</strong> 添加Docker官方的GPG密钥:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</span><br><span class="line">备注:可验证秘钥指纹 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88</span><br><span class="line">使用如下命令验证:</span><br><span class="line">sudo apt-key fingerprint 0EBFCD88</span><br></pre></td></tr></table></figure><p><strong>(5)</strong> 使用下面的命令来设置stable存储库:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"</span><br></pre></td></tr></table></figure><p><strong>(6)</strong> 再更新一下apt包索引:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br></pre></td></tr></table></figure><p><strong>(7)</strong> 安装最新版本的Docker CE:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install -y docker-ce</span><br><span class="line">注意:在生产系统上,可能会需要应该安装一个特定版本的Docker CE,而不是总是使用最新版本:</span><br><span class="line">列出可用的版本:apt-cache madison docker-ce</span><br><span class="line">选择要安装的特定版本,第二列是版本字符串,第三列是存储库名称,它指示包来自哪个存储库,以及扩展它的稳定性级别。要安装一个特定的版本,将版本字符串附加到包名中,并通过等号(=)分隔它们:</span><br><span class="line">sudo apt-get install docker-ce=<VERSION></span><br></pre></td></tr></table></figure><p><strong>(8)</strong> 测试是否安装成功</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker --version</span><br></pre></td></tr></table></figure><p><strong>(9)</strong> 使用阿里提供的镜像,否则后面下载Fabric镜像会非常慢<br>cd到/etc/docker目录下,创建文件daemon.json,输入下面的内容:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> "registry-mirrors": ["https://obou6wyb.mirror.aliyuncs.com"]</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>保存并退出,接着执行:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo systemctl daemon-reload</span><br><span class="line">sudo systemctl restart docker</span><br></pre></td></tr></table></figure><p><strong>(10)</strong> 查看docker服务是否启动:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">systemctl status docker</span><br></pre></td></tr></table></figure><p><strong>(11)</strong> 若未启动,则启动docker服务:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo service docker start或者sudo systemctl start docker</span><br></pre></td></tr></table></figure><h4 id="3-安装最新版本的Docker-compose"><a href="#3-安装最新版本的Docker-compose" class="headerlink" title="3.安装最新版本的Docker-compose"></a><strong>3.安装最新版本的Docker-compose</strong></h4><p><strong>(1)</strong> Docker-compose是支持通过模板脚本批量创建Docker容器的一个组件。在安装Docker-Compose之前,需要安装Python-pip,运行脚本:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get install python-pip</span><br></pre></td></tr></table></figure><p><strong>(2)</strong> 安装Docker-compose:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pip install docker-compose</span><br></pre></td></tr></table></figure><p><strong>(3)</strong> 验证是否成功:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo docker-compose --version</span><br></pre></td></tr></table></figure><p>安装Docker还可以参考<a href="https://blog.csdn.net/so5418418/article/details/78355868" target="_blank" rel="noopener">此篇文章</a></p><h4 id="4-Fabric源码下载"><a href="#4-Fabric源码下载" class="headerlink" title="4.Fabric源码下载"></a><strong>4.Fabric源码下载</strong></h4><p><strong>(1)</strong> 新建存放测试、部署代码的目录。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkdir -p ~/go/src/github.com/hyperledger/</span><br></pre></td></tr></table></figure><p><strong>(2)</strong> cd到刚创建的目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd ~/go/src/github.com/hyperledger</span><br></pre></td></tr></table></figure><p><strong>(3)</strong> 下载Fabric,这里使用使用git命令下载源码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/hyperledger/fabric.git</span><br></pre></td></tr></table></figure><p><strong>特别注意这里:</strong><br>直接使用上面的git clone下载会非常慢,因为github.global.ssl.fastly.Net域名被限制了。只要找到这个域名对应的ip地址,然后在hosts文件中加上ip–>域名的映射,刷新DNS缓存就可以了。<br>解决办法:<br>步骤【1】:查询域名<code>global-ssl.fastly.Net</code>和 <code>github.com</code> 公网地址<br>可以使用<a href="https://www.ipaddress.com/" target="_blank" rel="noopener">https://www.ipaddress.com/</a> 这个查。<br>分别查找下面这两个域名的ip地址:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">github.global.ssl.fastly.net</span><br><span class="line">github.com</span><br></pre></td></tr></table></figure><p>步骤【2】:将ip地址添加到hosts文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vim /etc/hosts</span><br></pre></td></tr></table></figure><p>在文件下方输入下面内容并保存,前面两个ip就是我们刚才上面查找到的ip:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">151.101.185.194 github.global.ssl.fastly.net</span><br><span class="line">192.30.253.113 github.com</span><br></pre></td></tr></table></figure><p>步骤【3】:修改完hosts还不会立即生效,你需要刷新DNS缓存,告诉电脑我的hosts文件已经修改了。<br>输入指令:<br>sudo /etc/init.d/networking restart 即可,如果不行也可以尝试重启一下电脑。<br>接下来再去git clone就快很多了。</p><p><strong>(4)</strong> 由于Fabric一直在更新,新版本的并不稳定,所有我们并不需要最新的源码,需要切换到v1.0.0版本的源码:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout v1.0.0</span><br></pre></td></tr></table></figure><h4 id="5-下载Fabric-Docker镜像"><a href="#5-下载Fabric-Docker镜像" class="headerlink" title="5.下载Fabric Docker镜像"></a><strong>5.下载Fabric Docker镜像</strong></h4><p><strong>(1)</strong> 前面步骤4下载完成后,我们可以看到当前工作目录(~/go/src/github.com/hyperledger/)下多了一个fabric的文件夹,<br>接下来我们cd到~/go/src/github.com/hyperledger/fabric/examples/e2e_cli目录下执行:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">source download-dockerimages.sh -c x86_64-1.0.0 -f x86_64-1.0.0</span><br></pre></td></tr></table></figure><p>(注:一定要下载完所有镜像并且镜像版本要和Fabric版本一致如何没有下载问继续执行source download-dockerimages.sh命令直到在完如图所有镜像),执行完所有会用到的Fabric docker镜像都会下载下来了。<br>运行以下命令检查下载的镜像列表:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker images</span><br></pre></td></tr></table></figure><p><strong>注意</strong>:如果下载时遇到权限问题,需要切换到root用户下:su root<br><strong>(2)</strong> 重启Docker</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">service docker restart</span><br></pre></td></tr></table></figure><h4 id="6-测试Fabric环境是否成功"><a href="#6-测试Fabric环境是否成功" class="headerlink" title="6.测试Fabric环境是否成功"></a><strong>6.测试Fabric环境是否成功</strong></h4><p>在~/go/src/github.com/hyperledger/fabric/examples/e2e_cli下执行如下命令启动测试</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./network_setup.sh up</span><br></pre></td></tr></table></figure><p>这个指令具体进行了如下操作:</p><ol><li>编译生成Fabric公私钥、证书的程序,程序在目录:fabric/release/linux-amd64/bin</li><li>基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。</li><li>基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。</li><li>基于docker-compose-cli.yaml启动1Orderer+4Peer+1CLI的Fabric容器。基于docker-compose-cli.yaml启动1Orderer+4Peer+1CLI的Fabric容器。<br>在CLI启动的时候,会运行scripts/script.sh文件,这个脚本文件包含了创建Channel,加入Channel,安装Example02,运行Example02等功能。</li></ol><p>运行完如果出现下图所示,说明整个Fabric网络已经通了。<br><img src="/images/fabric_test.png" alt="在这里插入图片描述"></p><h4 id="这里记录本人测试Fabric环境是否成功时遇到的问题"><a href="#这里记录本人测试Fabric环境是否成功时遇到的问题" class="headerlink" title="这里记录本人测试Fabric环境是否成功时遇到的问题"></a>这里记录本人测试Fabric环境是否成功时遇到的问题</h4><p><strong>1.</strong> 如果发现运行 <strong>./network_setup.sh up</strong>命令 后提示在…fabric/release/linux-amd64/bin文件夹下找不到指定文件<br><strong>解决办法</strong>:<br>可以在~/go/src/github.com/hyperledger/fabric/scripts文件下找到 <strong>bootstrap.1.0.0.sh</strong>文件,手动运行它 <strong>./bootstrap.1.0.0.sh</strong>, 此时可以在当前文件夹生成一个<strong>bin</strong>文件夹,bin里面的文件就是我们需要的,将它拷贝到前面的…fabric/release/linux-amd64/bin文件夹下</p><p><strong>2.</strong> 如果出现:Error on outputBlock: Error writing genesis block: open ./channel-artifacts/genesis.block: is a directory不能生成创世块的错误。<br><strong>解决办法:</strong><br>可以在~/go/src/github.com/hyperledger/fabric/examples/e2e_cli/channel-artifacts目录下,将genesis.block这个目录删除,<strong>rm -rf genesis.block/</strong></p><p><strong>3.</strong> 如果出现:.ERROR: for orderer.example.com Cannot start service orderer.example.com: b’OCI runtime create failed: container_linux.go:348: starting container process caused “process_linux.go:402: container init caused \“rootfs_linux.go:58:<br><strong>解决办法:</strong><br>执行./network_setup.sh down 清除网络后再启动即可</p><h3 id="测试Fabric网络"><a href="#测试Fabric网络" class="headerlink" title="测试Fabric网络"></a>测试Fabric网络</h3><p>接下来我们手动测试下Fabric网络,Fabric提供了SDK和CLI两种交互方式,这里我们使用的是CLI。<br>这里我们使用官方提供的小例子进行测试,在官方例子中,channel名字是mychannel,链码(智能合约)的名字是mycc。<br>首先要登录到CLI这个容器中,才能执行Fabric的CLI命令:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker exec -it cli bash</span><br></pre></td></tr></table></figure><p>这时用户名变为root@caa22f87a5bf,当前目录变为/opt/go/src/github.com/hyperledger/fabric/peer#,接着可执行peer命令,体验区块链的命令行使用方式。</p><p><strong>1.查看a账户的余额</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'</span><br></pre></td></tr></table></figure><p>此时我们可以看到控制台输出有:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Query Result: 90</span><br></pre></td></tr></table></figure><p>这里90就是a账户的余额</p><p><strong>2.调用链码,转账</strong></p><p>这里我们让b账户向a账户转账10:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/go/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 -c '{"Args":["invoke","b","a","10"]}'</span><br></pre></td></tr></table></figure><p>转账成功后,我们可以看到有输出如下提示:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DEBU 009 ESCC invoke result: version:1 response:<status:200 message:"OK"</span><br></pre></td></tr></table></figure><p>接下来我们使用前面的命令继续查看a账户的余额,输出结果如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Query Result: 100</span><br></pre></td></tr></table></figure><p>很明显我们已经转账成功了。</p><p><strong>退出cli容器:</strong></p><p>直接执行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exit</span><br></pre></td></tr></table></figure><p>最后如果我们要关闭Fabric网络,cd到<code>~/go/src/github.com/hyperledger/fabric/examples/e2e_cli</code>下(注意这里的路径按自己前面创建的,不一定要和我一样),执行:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./network_setup.sh down</span><br></pre></td></tr></table></figure><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://blog.csdn.net/github_34965845/article/details/80610060" target="_blank" rel="noopener">https://blog.csdn.net/github_34965845/article/details/80610060</a><br><a href="https://www.cnblogs.com/preminem/p/7729497.html" target="_blank" rel="noopener">https://www.cnblogs.com/preminem/p/7729497.html</a><br><a href="https://www.cnblogs.com/gao90/p/8692642.html" target="_blank" rel="noopener">https://www.cnblogs.com/gao90/p/8692642.html</a><br><a href="https://blog.csdn.net/so5418418/article/details/78355868" target="_blank" rel="noopener">https://blog.csdn.net/so5418418/article/details/78355868</a><br><a href="https://blog.csdn.net/iflow/article/details/77951610" target="_blank" rel="noopener">https://blog.csdn.net/iflow/article/details/77951610</a><br><a href="https://blog.csdn.net/vivian_ll/article/details/79966210" target="_blank" rel="noopener">https://blog.csdn.net/vivian_ll/article/details/79966210</a></p><p>本文的作者是lgy</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p><p><a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">深入浅出区块链知识星球</a>最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。</p>]]></content>
<summary type="html">
<p>这篇文章首先简单介绍了联盟链是什么,再详细的介绍了Fabric环境搭建的整个流程。<br>
</summary>
<category term="Fabric" scheme="https://learnblockchain.cn/categories/Fabric/"/>
<category term="联盟链" scheme="https://learnblockchain.cn/categories/Fabric/%E8%81%94%E7%9B%9F%E9%93%BE/"/>
<category term="Fabric" scheme="https://learnblockchain.cn/tags/Fabric/"/>
</entry>
<entry>
<title>深入理解Plasma(四)Plasma Cash</title>
<link href="https://learnblockchain.cn/2018/11/16/plasma-cash/"/>
<id>https://learnblockchain.cn/2018/11/16/plasma-cash/</id>
<published>2018-11-16T04:44:17.000Z</published>
<updated>2018-12-06T01:46:09.785Z</updated>
<content type="html"><![CDATA[<p>这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash。</p><a id="more"></a><p>在<a href="https://github.com/gitferry/mastering-ethereum/blob/master/Plasma-in-depth/plasma-mvp.md" target="_blank" rel="noopener">上一篇</a>文章中我们已经理解了 Plasma 的最小实现 Plasma MVP 如何使用 UTXO 模型实现 Plasma 链下扩容的核心思想。但由于 Plasma MVP 本身过于简单,并不能用于实际的生产环境中。2018 年 3 月,在巴黎举行的以太坊开发者大会上,Vitalik 发布了 Plasma Cash 模型<a href="https://ethresear.ch/t/plasma-cash-plasma-with-much-less-per-user-data-checking/1298" target="_blank" rel="noopener">[1]</a>,可以视为对 Plasma MVP 的改进。Plasma Cash 与 Plasma MVP 的主要区别是每次存款操作都会产生一个唯一的 coin ID 对应转移到侧链上的资产,并使用一种称为稀疏梅克尔树(Sparse Merkle Tree)的数据结构存储交易历史。由此带来的好处是用户不需要关注子链上的每个动态,只需要关注跟自己的 token 有关的动态。在下文中将介绍具体细节。</p><h3 id="存款(Deposits)"><a href="#存款(Deposits)" class="headerlink" title="存款(Deposits)"></a>存款(Deposits)</h3><p>Plasma Cash 中的每次存款操作都会对应产生一个 NFT(non-fungible token)<a href="https://en.wikipedia.org/wiki/Non-fungible_token" target="_blank" rel="noopener">[2]</a>。NFT 可以简单理解为“不可互换的 token”,即每个 token 都是独一无二的,由唯一的 ID 标记。以太坊官方为 NFT 提供了 ERC721 标准<a href="http://erc721.org/" target="_blank" rel="noopener">[3]</a>,在之前火爆到阻塞以太坊的 CryptoKitties 就是由 ERC721 合约实现的。</p><p>在 Plasma Cash 中,当用户向 Plasma 合约发送存款交易时,合约会生成一个与存款等值的 token,并给这个 token 分配一个唯一的 ID。如果一个用户分别执行两次存款操作,且每次存款都是 5 ETH,那么他将得到相等价值的两个完全不同的 token。和 Plasma MVP 一样,每次存款操作都会使得 Plasma 合约产生一个只包含这个存款交易的区块。</p><h3 id="Plasma-Cash-区块"><a href="#Plasma-Cash-区块" class="headerlink" title="Plasma Cash 区块"></a>Plasma Cash 区块</h3><p>Plasma Cash 中的每个 token 都被分配唯一的 ID,因此可以按 ID 的顺序存储每个 token 的交易历史。Plasma Cash 的区块按 token ID 的顺序给每个 token 分配了一个插槽(slot),每个插槽会记录这个 token 是否被交易的信息。例如在下图(来源<a href="https://github.com/ethsociety/learn-plasma" target="_blank" rel="noopener">[4]</a>)的区块中,包含 4 个 token,id 分别是 #1,#2,#3,#4。其中 #1,#2,#3 被标记为没有被花费,而 #4 由用户 A 发送给用户 B。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/pc-block.png" width="600" height="190" alt="Plasma Cash Block"></p><p>从上面这个例子中我们可以看到,每个插槽记录了其所对应的 token 在当前区块中的交易状态,所有存储了某个 token 的交易状态的区块按时间顺序连在一起就构成了这个 token 的全部交易历史。每当一个 token 被分配了一个 id,之后的所有交易状态都会被保存在每个区块相同的插槽中,也不会被其它 token 取代。因此,用户只需要关注每个区块中存储属于自己的 token 的状态,完全不用关心别的插槽存储的内容。</p><h3 id="交易与验证"><a href="#交易与验证" class="headerlink" title="交易与验证"></a>交易与验证</h3><p>由于 Plasma Cash 中的节点只追踪属于自己的 token 的交易历史,因此当有交易发生时,token 的发送者要向接收者提供关于这个 token 所有的交易历史(从存款交易开始)以便接收者验证。从下图(来源<a href="https://github.com/ethsociety/learn-plasma" target="_blank" rel="noopener">[4]</a>)的例子中可以看到 4 个区块中所记录的 4 个 token 的交易历史。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/pc-tx.png" width="400" height="500" alt="Plasma Cash TXs"></p><p>截止到区块 #4,可以看到token #1 和 token #3 始终没有被交易。token #2 在区块 #2 被 E 发送给了 F,在区块 #4 被 F 发送给了 G,在其它区块没有发生交易,token #2 的最终所有权归 G。token #4 在区块 #1 被 A 发送给了 B,在区块 #3 被 B 发送给了 C,在其它区块没有发生交易,token #4 的最终所有权归 C。F 为了向 G 证明 token #2 的合法性,需要向 G 提供 token #2 在前 4 个区块中的所有交易历史,也就是说不仅需要包括区块 #2 中 E => F 的交易证明、区块 #4中 F => G 的交易证明,还要包括在区块 #1 和 #3 中没有被交易的证明。到这里可能感觉有点奇怪,为什么还要包括没有被交易的证明?这是为了防止双花,因为 G 并不知道在区块 #1 和 #3 中 token #2 是否被交易给了其它人。假如 F 在区块 #3 中将 token #2 发送给了 H,并且对 G 隐瞒了这个交易,那么发生在区块 #4 中的 F => G 就是非法(双花)的。因此,在 Plasma Cash 中,完整且合法的交易历史是一个 token 被安全交易的前提。</p><h3 id="稀疏梅克尔树(Sparse-Merkle-Tree)"><a href="#稀疏梅克尔树(Sparse-Merkle-Tree)" class="headerlink" title="稀疏梅克尔树(Sparse Merkle Tree)"></a>稀疏梅克尔树(Sparse Merkle Tree)</h3><p>在上文中我们已经了解到一个交易的成功的前提是需要发送方提供关于一个 token 的完整交易历史。完整的交易历史既包括这个 token 在哪些区块被交易的信息,也包括这个 token 在哪些区块没有被交易的信息。我们都知道,在区块链中,使用梅克尔树(Merkle Tree,MT)构造梅克尔证明(Merkel Proof, MP)可以在 O(logN)的时间复杂度验证一个交易是否存在一个区块中。但想要证明一个交易没有存在一个区块中,使用标准的梅克尔树却没那么容易。因此,Plasma Cash 中使用了一种称为稀疏梅克尔树(Sparse Merkle Tree,SMT)的数据结构存储交易数据,能够在O(logN)的时间复杂度验证一个交易不存在。</p><p>SMT 实际上一点都不复杂,它的叶子节点是按数据集中的元素序号顺序排列的。如果某个叶子节点对应的元素为空,那么该叶子节点将存储一个特定的值(例如 0 的哈希值)。一个简单的 SMT 示例如下图(来源<a href="https://medium.com/@kelvinfichter/whats-a-sparse-merkle-tree-acda70aeb837" target="_blank" rel="noopener">[5]</a>)所示。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/SMT.png" width="400" height="400" alt="Sparse Merkle Tree"></p><p>扩展到 Plasma Cash 中,SMT 的叶子节点对应了区块中给每个 token 分配的插槽,按照每个 token 的 ID 排序。每个叶子节点存储对应的 token 的交易信息,如果 token 在这个区块中没有被交易,则相应的叶子节点存储的值为 <em>null</em>。</p><p>以上图为例,如果需要证明交易 A 存在,就像在标准的 MT 中一样,需要构造 MP:H(null) 和 H(H(null) + H(D))。如果需要证明 B 不存在,同样很简单,我们已经知道 B 的位置是第二个叶子节点,如果 B 不存在,那么该节点存储的值应该为 <em>null</em>。因此就像在标准的 MT 中证明存在的 MP 一样,只不过需要加上 H(null) 作为 MP 的一部分,即 MP:H(null)、H(A)和 H(H(null)+H(D))。</p><h3 id="取款-退出(Withdrawl-Exit)"><a href="#取款-退出(Withdrawl-Exit)" class="headerlink" title="取款/退出(Withdrawl/Exit)"></a>取款/退出(Withdrawl/Exit)</h3><p>Plasma Cash 中的取款操作在流程上跟 Plasma MVP 大体相同,都要从提交取款申请开始,经历争议期之后才能完成。由于 Plasma Cash 中采用的数据结构不同,在取款时需要提交的 token 所有权证明不同,因此当争议发生时需要提交的争议证明也不同。</p><h4 id="提交取款申请"><a href="#提交取款申请" class="headerlink" title="提交取款申请"></a>提交取款申请</h4><p>在向 Plasma 合约提交关于某个 token 的取款申请时,需要提供关于这个 token 最近的两次交易证明。例如,在上图中,假如 G 想要取走 token #2 到主链,那么他需要提交关于 F => G 以及 E => F 的 Merkle Proof。</p><h4 id="提交争议"><a href="#提交争议" class="headerlink" title="提交争议"></a>提交争议</h4><p>取款者在提交了取款申请之后同样需要支付一定的保证金,并等待一段时间的争议期。在这期间如果有其它节点提交了有效的争议证明,那么取款者不但无法完成取款操作,也会损失全部或部分的保证金。</p><p>目前 Plasma Cash 支持三种争议证明,分别应对三种不同的攻击场景(具体会在后文分析):</p><ol><li>已花费证明。如果能证明正在取款的 token 已经被花费,那么取款立即被取消;</li><li>双花证明。如果能证明取款申请中提供的两次交易证明中间还有别的交易,即发生了双花,那么取款立即被取消;</li><li>非法交易历史证明。用户还可以对正在取款的 token 的其它交易历史提出争议。这种争议不会立刻阻断取款,而是强制取款者提交其它交易证明来反驳争议,如果没有在规定时间内反驳,则取款被取消。</li></ol><h3 id="攻击场景"><a href="#攻击场景" class="headerlink" title="攻击场景"></a>攻击场景</h3><p>在这一节将讨论已有的 3 种攻击场景以及如何构造争议分别应对这些攻击<a href="https://karl.tech/plasma-cash-simple-spec/" target="_blank" rel="noopener">[6]</a>。在这里假设 Plasma Cash 中存在不可信的 operator 接收所有的交易并构造区块。</p><h4 id="发送交易后立即退出"><a href="#发送交易后立即退出" class="headerlink" title="发送交易后立即退出"></a>发送交易后立即退出</h4><p>如下图(来源<a href="https://github.com/loomnetwork/plasma-paper/blob/master/plasma_cash.pdf" target="_blank" rel="noopener">[7]</a>)所示,假设攻击者 Alice 向 Bob 发送了一个 token A,且 Bob 已经验证了 A 的交易历史没有问题,交易在区块 N+X 得到确认。在这之后,Alice 立即提交取款申请,企图将 token A 取回主链,并提交 A 在区块 N 以及之前的交易证明。为了应对这种情况,Bob 必须及时发现 Alice 的取款行为,并且在争议期结束前提交在区块 N+X 中 token A 被 Alice 发送给 Bob 的证明。这里需要注意的是,如果 Bob 在区块 N+Y 将 token A 发送给 Charlie 的交易是不能被当做争议证明的,只有最接近被争议的交易的下一个交易证明有效。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/attack1.png" width="700" height="220" alt="attack1"></p><h4 id="双花攻击"><a href="#双花攻击" class="headerlink" title="双花攻击"></a>双花攻击</h4><p>双花攻击需要 operator 配合,将含有已经被花费的 token 的交易打包入下一个区块中。如下图所示(来源<a href="https://github.com/loomnetwork/plasma-paper/blob/master/plasma_cash.pdf" target="_blank" rel="noopener">[7]</a>),攻击者 Alice 和 Charlie 是同谋,Alice 向 Bob 发送一个 token A 在区块 N+X 被确认,之后 Alice 又将 token A 发送给 Charlie,并在区块 N+Y 被确认。这时在主链看来,Bob 和 Charlie 都是 token A 的合法拥有者。接下来,Charlie 立即提交取款申请,企图取走 token A。Bob 为了防止自己的 token 被盗,可以在争议期内提交在区块 N+X 被确认的交易,表明自己在 Charlie 之前已经拥有了 token A。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/attack2.png" width="700" height="300" alt="attack2"></p><h4 id="取款包含非法交易历史"><a href="#取款包含非法交易历史" class="headerlink" title="取款包含非法交易历史"></a>取款包含非法交易历史</h4><p>这种攻击需要联合比较多的同谋者。如下图所示,Alice 在区块 N 拥有 token A。Bob 联合 operator、Charlie 以及 Dylan 企图盗走 Alice 的 token。首先,operator 伪造 Alice 将 token A 发送给 Bob 的交易,并在区块 N+X 得到确认,之后 Bob 将 token 发送给 Charlie,在区块 N+Y 确认。同样地,Charlie 接着将 token 发送给 Dylan,在区块 N+Z 确认。这是,Dylan 提出取款申请,企图取走 token A。Dylan 用于取款申请的两个交易证明 Charlie => Dylan 和 Bob => Charlie 都是合法的,但 token A 的交易历史中有一部分是伪造的。Alice 为了证明自己是 token A 的最新合法拥有者,可以提出争议,要求 Dylan 提供 Alice => Bob 的交易证明,同时 Alice 需要提交一部分保证金(否则任何人都可以随便提出争议)。Dylan 必须在一定的时间内提供合法的交易证明,否则取款失效。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/attack3.png" width="900" height="250" alt="attack3"></p><h3 id="相关项目"><a href="#相关项目" class="headerlink" title="相关项目"></a>相关项目</h3><blockquote><p>Talk is cheap, show me your code.</p></blockquote><p>目前已经有许多机构和公司已经实现了 Plasma Cash,但实现的语言和细节有所不同:</p><ul><li>Loom Network <a href="https://github.com/loomnetwork/plasma-cash" target="_blank" rel="noopener">[8]</a></li><li>Omisego <a href="https://github.com/omisego/plasma-cash" target="_blank" rel="noopener">[9]</a></li><li>Wolk <a href="https://github.com/wolkdb/deepblockchains/tree/master/Plasmacash" target="_blank" rel="noopener">[10]</a></li><li>Lucidity <a href="https://github.com/luciditytech/lucidity-plasma-cash" target="_blank" rel="noopener">[11]</a></li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>本篇介绍了 Plasma 框架下的基于 NFT 的项目 Plasma Cash。Plasma Cash 给每个新转移的 token 分配一个唯一的 token ID,并且用稀疏梅克尔树存储交易,使得用户可以只关注跟自己的 token 有关的动态,而不需要关注其它 token。Plasma Cash 可以被看作 Plasma 逐渐迈向成熟的一步,已经有很多公司使用 Plasma Cash 搭建自己的平台和应用,例如 Loomnetwork 公司搭建了自己的 Plasma Cash 子链并且编写了 SDK 支撑开发者在上面开发新的应用。然而 Plasma Cash 本身仍然存在较多的问题,例如 token 无法被分隔合并、需要提交的证明过长等。在接下来的文章中还会继续跟进 Plasma 最新的进展。</p><h3 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h3><ol><li><a href="https://ethresear.ch/t/plasma-cash-plasma-with-much-less-per-user-data-checking/1298" target="_blank" rel="noopener">https://ethresear.ch/t/plasma-cash-plasma-with-much-less-per-user-data-checking/1298</a></li><li><a href="https://en.wikipedia.org/wiki/Non-fungible_token" target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Non-fungible_token</a></li><li><a href="http://erc721.org/" target="_blank" rel="noopener">http://erc721.org/</a></li><li><a href="https://github.com/ethsociety/learn-plasma" target="_blank" rel="noopener">https://github.com/ethsociety/learn-plasma</a></li><li><a href="https://medium.com/@kelvinfichter/whats-a-sparse-merkle-tree-acda70aeb837" target="_blank" rel="noopener">https://medium.com/@kelvinfichter/whats-a-sparse-merkle-tree-acda70aeb837</a></li><li><a href="https://karl.tech/plasma-cash-simple-spec/" target="_blank" rel="noopener">https://karl.tech/plasma-cash-simple-spec/</a></li><li><a href="https://github.com/loomnetwork/plasma-paper/blob/master/plasma_cash.pdf" target="_blank" rel="noopener">https://github.com/loomnetwork/plasma-paper/blob/master/plasma_cash.pdf</a></li><li><a href="https://github.com/loomnetwork/plasma-cash" target="_blank" rel="noopener">https://github.com/loomnetwork/plasma-cash</a></li><li><a href="https://github.com/omisego/plasma-cash" target="_blank" rel="noopener">https://github.com/omisego/plasma-cash</a></li><li><a href="https://github.com/wolkdb/deepblockchains/tree/master/Plasmacash" target="_blank" rel="noopener">https://github.com/wolkdb/deepblockchains/tree/master/Plasmacash</a></li><li><a href="https://github.com/luciditytech/lucidity-plasma-cash" target="_blank" rel="noopener">https://github.com/luciditytech/lucidity-plasma-cash</a></li></ol><p>本文的作者是盖盖,他的微信公众号: chainlab</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/categories/ethereum/Plasma/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/tags/Plasma/"/>
<category term="扩容" scheme="https://learnblockchain.cn/tags/%E6%89%A9%E5%AE%B9/"/>
</entry>
<entry>
<title>深入理解Plasma(三)Plasma MVP</title>
<link href="https://learnblockchain.cn/2018/11/03/plasma-mvp/"/>
<id>https://learnblockchain.cn/2018/11/03/plasma-mvp/</id>
<published>2018-11-03T08:54:17.000Z</published>
<updated>2018-12-06T01:46:09.789Z</updated>
<content type="html"><![CDATA[<p>这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍 Plasma 的一个最小实现 Plasma MVP(Minima Viable Plasma)。</p><a id="more"></a><p>在<a href="https://github.com/gitferry/mastering-ethereum/blob/master/Plasma-in-depth/plasma-in-detail.md" target="_blank" rel="noopener">上一篇</a>文章中我们已经理解了 Plasma 中的一些关键操作,但是 Plasma 是一套框架,如果脱离了实际的应用,仍然很难彻底理解它。因此本篇将详细介绍 Plama 的第一个项目 Plasma MVP(Minimal Viable Plasma),即在 Plasma 框架下的最基础的实现。Plasma MVP 是 Vitalic 和他的团队在 2018 年初提出的基于 UTXO 模型实现的 Plasma 设计标准<a href="https://ethresear.ch/t/minimal-viable-plasma/426" target="_blank" rel="noopener">[1]</a>,它以最简单的方式实现了链下交易,但无法支持复杂的计算,例如脚本(Script)和智能合约。在阅读下面的内容之前,请确保已经理解了这个系列之前的文章。</p><p>整个 Plasma MVP 的生命周期可以通过下面这幅图表现出来:</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/plasma-mvp.jpg" width="360" height="400" alt="Blockchains of Blockchain"></p><h2 id="Plasma-合约"><a href="#Plasma-合约" class="headerlink" title="Plasma 合约"></a>Plasma 合约</h2><p>首先需要将 Plasma 合约部署到主链(以太坊)上作为主链和子链沟通的媒介。Plasma 合约会处理由子链提交的区块,并且将区块的哈希值存在主链上。除此之外,还会处理用户的存款(deposit)、取款(withdrawal/exit)以及争议(challenge)操作。</p><p>Plasma 合约中主要包括的数据结构有:</p><ul><li>Owner:合约的拥有者(即部署合约交易的发送者)的地址,即部署合约交易的发送者;</li><li>Plasma 区块列表:每个 Plasma 区块中存储了(1)区块的 Merkle root(2)区块提交的时间;</li><li>退出列表:即提交了退出申请的列表,每个退出申请存储了(1)申请者的地址(2)申请退出的 UTXO 的位置。</li></ul><p>Plasma 合约中主要包括的函数有:</p><ul><li>submitBlock(bytes32 root):向主链提交一个区块,仅仅提交区块中所有交易的 Merkle root;</li><li>deposit():生成一个只包含一个交易的区块,这个交易中包含与 msg.value 值相等的 UTXO;</li><li>startExit():执行给定 UTXO 的退出操作;</li><li>challengeExit():向某个正在执行的退出提出争议。</li></ul><h2 id="Operator"><a href="#Operator" class="headerlink" title="Operator"></a>Operator</h2><p>在前面的文章中我们已经知道 Plasma 子链是一个独立的区块链,那么也就有独立的共识机制。在 Plasma MVP 中采用的共识机制就是 PoA(Proof of Authority),即参与共识的只有唯一一个矿工,称为 Operator。Operator 负责处理所有子链上发生的交易,将其打包成区块存储在子链上,并且周期性地向 Plasma 合约提交区块,将子链上的状态(区块的哈希值)提交到主链共识。那么,既然 Operator 是唯一的矿工,这不就意味着 Plasma 违背了去中心化的初衷了吗?其实,这是去中心化向执行效率的妥协。在之前的文章中也提到过,Plasma 的安全基础依赖于底层的区块链,只要底层的区块链能够保证安全,那么在 Plasma 子链上发生的最差结果也只是迫使用户退出子链,而不会造成资产损失。</p><p>Operator 可以采用最简单的 REST API 方式实现,子链中的用户可以通过调用简单的 API 获取到子链中区块的数据。</p><h2 id="存款(deposit)"><a href="#存款(deposit)" class="headerlink" title="存款(deposit)"></a>存款(deposit)</h2><p>用户 Alice 通过存款(deposit)操作向 Plasma 合约发送带有一定数额的以太币或 ERC20 token 加入 Plasma Chain,这时 Plasma 合约会执行 deposit() 函数,生成一个只包含一个交易的区块,这个交易的 UTXO 记录了 Alice 从主链转移到子链的数额。当这个区块被主链确认后,Alice 就可以使用新生成的 UTXO 向其它用户发送交易了。</p><h2 id="交易(transaction)"><a href="#交易(transaction)" class="headerlink" title="交易(transaction)"></a>交易(transaction)</h2><p>在 Plasma MVP 中,所有用户发送的交易都是直接发送给 Operator,当积累了一定数量的交易后,由 Operator 将交易打包成区块。这里需要注意的是,由于 Plasma MVP 采用的是 UTXO 模型,所以即使交易的收款方不存在,交易也是成立的。</p><p>在子链上 Alice 向 Bob 发送一个交易的流程如下:</p><ol><li>Alice 首先需要得到 Bob 在子链上的地址;</li><li>Alice 将一个或多个 UTXO 作为输入构造交易发送到 Bob 的地址,并对交易签名;</li><li>等待该交易被打包到区块中;</li><li>Alice 向 Bob 发送确认消息,并且使用相同的私钥签名。</li></ol><h2 id="生成区块"><a href="#生成区块" class="headerlink" title="生成区块"></a>生成区块</h2><p>在 Plasma MVP 中,一个 Plasma 区块产生的情况只有两种:一种是 Operator 打包生成区块,另外一种是当用户执行 deposit 操作时,由 Plasma 合约直接生成一个只包含一个交易的区块。</p><h2 id="监视子链"><a href="#监视子链" class="headerlink" title="监视子链"></a>监视子链</h2><p>为了保证子链上资产的安全,用户需要周期性地检查子链上的数据,保证没有恶意交易产生。用户需要运行一种自动化的软件(例如钱包),每隔一段时间下载子链中的区块数据,检查每个区块中的交易,如果有恶意交易产生,立即退出子链。</p><h2 id="取款-退出(withdrawal-exit)"><a href="#取款-退出(withdrawal-exit)" class="headerlink" title="取款/退出(withdrawal/exit)"></a>取款/退出(withdrawal/exit)</h2><p>当 Alice 想要退出子链时,需要向 Plasma 合约发送一个 exit 交易,申请中需要包含(1)所要退出的 UTXO 的位置,包括区块号(blknum)、区块内交易号(txindex)以及交易内输出号(outindex)(2)包含该 UTXO 的交易(3)该交易的 Merkle proof(4)用于生成该 UTXO 所涉及的之前一系列交易的确认签名。除此之外,exit 交易中还要包含“退出押金(exit bond)”。如果这个 exit 被 challenge 成功,那么取款的操作将被取消,而且退出押金将被发送给提出 challenge 的用户。</p><p>之后这个申请会被放入一个优先队列中,通过这个公式计算优先级:</p><blockquote><p>Priority = blknum <em> 1000000000 + txindex </em> 10000 + oindex</p></blockquote><p>之所以采用这种优先队列的方式处理取款顺序的原因是保证旧的 UTXO 总能优先于新的 UTXO 被取出。也就是说,当有恶意交易(例如双花等)产生时,所有在恶意交易发生之前的交易都可以被优先取出。那么如何解决在恶意交易之后被确认的交易的取款问题呢?Plasma MVP 采用了“确认签名(Confirmation Signatures)”的机制,在下一小节我们将介绍这一机制是如何工作的。</p><h2 id="确认签名(Confirmation-Signatures)"><a href="#确认签名(Confirmation-Signatures)" class="headerlink" title="确认签名(Confirmation Signatures)"></a>确认签名(Confirmation Signatures)</h2><p>在 Plasma MVP 中,用户的退出顺序以所要退出的 UTXO 所在的交易的位置为准。假如 operator 作恶,在一个合法的交易之前插入一个非法的交易,那么当用户执行取款时,由于非法交易可以先被取出,因此当执行到该用户的交易时,可能 Plasma 合约中的资产已经被取空。为了解决这个问题,Plasma MVP 采用了“确认签名”机制,例如当 Alice 产生一个交易时,她首先会对交易签名。当该交易被打包入区块后,Alice 还需要对该交易进行一次签名,即“确认签名”。</p><p>引入确认签名机制后,当 Alice 发现在一个区块中自己的合法交易之前存在非法交易时,可以拒绝对自己的交易进行“确认签名”,同时申请取款。这样可以使得当前的交易失效,保证自己之前“确认签名”后的交易可以优先于非法交易之前取出。</p><p>这种确认签名机制极大地破坏了用户体验,用户每产生一个交易都要经历签名->等待确认->确认签名。而且由于确认签名也需要占据 Plasma 区块的空间,因此也降低了子链的可扩展性。为了解决这个问题,Plasma 的研究人员提出了扩展版本 More Viable Plasma 移除了确认签名的要求<a href="https://ethresear.ch/t/more-viable-plasma/2160" target="_blank" rel="noopener">[2]</a>。</p><h2 id="争议(Challenge)"><a href="#争议(Challenge)" class="headerlink" title="争议(Challenge)"></a>争议(Challenge)</h2><p>每个取款操作都会经历一个争议期。例如在 Alice 的某个 UTXO 退出子链的过程中,如果 Bob 在争议期内发现有恶意行为发生,他可以提出一个争议(challenge)。一个争议需要给出针对的 UTXO 的位置,以及该 UTXO 被花费的证明,即该 UTXO 已经存在于某个交易中,且这个交易已经被打包到区块。</p><p>合约通过调用 challengeExit() 函数执行一个争议,争议成功后会取消正在执行的取款操作,并将提交取款申请所冻结的押金发送给 Bob。</p><h2 id="攻击场景"><a href="#攻击场景" class="headerlink" title="攻击场景"></a>攻击场景</h2><p>在 Plasma 子链中主要存在两种攻击场景:</p><ol><li>Alice 试图忽视在子链中转移给 Bob 的资产,使用最初加入 Plasma 子链时的交易证明向主链提出取款申请。</li><li>Operator 生成一个恶意交易,占有其他用户的资产,并且尝试退出子链。</li></ol><p>下面对这两个攻击场景进行分析,观察 Plasma MVP 如何保证资产的安全性:</p><p>场景1</p><ol><li>Alice 使用最初加入子链时生成的交易作为证据向主链提出取款申请;</li><li>Bob(或者其他任意用户)拥有 Alice 申请退出的 UTXO 被花费的交易证明,并将此作为证据向主链提出一个争议;</li><li>争议生效,Alice 的退出申请被驳回,同时将 Alice 申请退出的押金发送给 Bob;</li><li>Alice 的攻击失效。</li></ol><p>场景2</p><ol><li>Operator 创建了一个非法交易,并且将其打包生成区块之后在主链得到确认;</li><li>Operator 提交取款申请,打算将 Alice 的资产取走;</li><li>在争议期内,Alice 发现了 Operator 的恶意行为,立即提出取款申请,退出子链;</li><li>由于 Alice 的申请优先级较高,因此会在 Operator 之前退出;</li><li>Operator 的攻击失效。</li></ol><h2 id="相关项目"><a href="#相关项目" class="headerlink" title="相关项目"></a>相关项目</h2><blockquote><p>Talk is cheap, show me your code.</p></blockquote><p>目前已经有许多机构和公司已经实现了 Plasma MVP,但实现的语言和细节有所不同:</p><ul><li>FourthState Lab<a href="https://github.com/fourthstate" target="_blank" rel="noopener">[3]</a></li><li>Omisego<a href="https://github.com/omisego/plasma-mvp" target="_blank" rel="noopener">[4]</a></li><li>Kyokan<a href="https://github.com/kyokan/plasma" target="_blank" rel="noopener">[5]</a></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文介绍了 Plasma 的最小实现版本 Plasma MVP,虽然采用最简单的 UTXO 模型,但已经足够体现出 Plasma 的核心思想。在 Plasma MVP 中,用户资产的安全主要依赖于用户及时发现恶意行为,并退出子链。接下来的文章将会介绍另外一个稍微复杂一点的项目,Plasma Cash。</p><h2 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h2><ol><li><a href="https://ethresear.ch/t/minimal-viable-plasma/426" target="_blank" rel="noopener">https://ethresear.ch/t/minimal-viable-plasma/426</a></li><li><a href="https://ethresear.ch/t/more-viable-plasma/2160" target="_blank" rel="noopener">https://ethresear.ch/t/more-viable-plasma/2160</a></li><li><a href="https://github.com/fourthstate" target="_blank" rel="noopener">https://github.com/fourthstate</a></li><li><a href="https://github.com/omisego/plasma-mvp" target="_blank" rel="noopener">https://github.com/omisego/plasma-mvp</a></li><li><a href="https://github.com/kyokan/plasma" target="_blank" rel="noopener">https://github.com/kyokan/plasma</a></li></ol><p>本文的作者是盖盖,他的微信公众号: chainlab</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍 Plasma 的一个最小实现 Plasma MVP(Minima Viable Plasma)。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/categories/ethereum/Plasma/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/tags/Plasma/"/>
<category term="扩容" scheme="https://learnblockchain.cn/tags/%E6%89%A9%E5%AE%B9/"/>
</entry>
<entry>
<title>以太坊钱包开发系列4 - 发送Token(代币)</title>
<link href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/"/>
<id>https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/</id>
<published>2018-10-26T09:34:44.000Z</published>
<updated>2018-12-12T11:18:41.457Z</updated>
<content type="html"><![CDATA[<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,本文是第四篇,Token(代币、通证)是以太坊的一大特色,既然开发钱包,则发送Token 功能必不可少。</p><a id="more"></a><h2 id="合约-ABI-信息"><a href="#合约-ABI-信息" class="headerlink" title="合约 ABI 信息"></a>合约 ABI 信息</h2><p>首先我们需要明白,进行Token转账的时候,其实是在调用合约的转账函数,而要调用一个合约的函数,需要知道合约的 ABI 信息。</p><p>其次 通常我们所说的Token, 其实指的是符合 ERC20 标准接口的合约, <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md" target="_blank" rel="noopener">ERC20</a> 接口定义如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">contract ERC20Interface {</span><br><span class="line"></span><br><span class="line"> string public constant name = <span class="string">"Token Name"</span>;</span><br><span class="line"> string public constant symbol = <span class="string">"SYM"</span>;</span><br><span class="line"> uint8 public constant decimals = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">totalSupply</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">constant</span> <span class="title">returns</span> (<span class="params">uint</span>);</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"> <span class="title">function</span> <span class="title">balanceOf</span>(<span class="params">address tokenOwner</span>) <span class="title">public</span> <span class="title">constant</span> <span class="title">returns</span> (<span class="params">uint balance</span>);</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"> <span class="title">function</span> <span class="title">allowance</span>(<span class="params">address tokenOwner, address spender</span>) <span class="title">public</span> <span class="title">constant</span> <span class="title">returns</span> (<span class="params">uint remaining</span>);</span></span><br><span class="line"><span class="function"> <span class="title">function</span> <span class="title">approve</span>(<span class="params">address spender, uint tokens</span>) <span class="title">public</span> <span class="title">returns</span> (<span class="params">bool success</span>);</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"> <span class="title">function</span> <span class="title">transfer</span>(<span class="params">address to, uint tokens</span>) <span class="title">public</span> <span class="title">returns</span> (<span class="params">bool success</span>);</span></span><br><span class="line"><span class="function"> <span class="title">function</span> <span class="title">transferFrom</span>(<span class="params">address from, address to, uint tokens</span>) <span class="title">public</span> <span class="title">returns</span> (<span class="params">bool success</span>);</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"> <span class="title">event</span> <span class="title">Transfer</span>(<span class="params">address indexed from, address indexed to, uint tokens</span>);</span></span><br><span class="line"><span class="function"> <span class="title">event</span> <span class="title">Approval</span>(<span class="params">address indexed tokenOwner, address indexed spender, uint tokens</span>);</span></span><br><span class="line"><span class="function">}</span></span><br></pre></td></tr></table></figure><p>ABI 全称是 Application Binary Interface,它就是合约接口的描述,因此有了合约的接口定义,就可以很容易通过编译拿到ABI 信息,比如像下图在Remix 的编译选项卡就可以直接复制ABI。</p><p><img src="https://learnblockchain.cn/media/15403775499051.jpg" alt=""></p><p>生成的 ABI 描述大概长这样:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">[</span><br><span class="line">...</span><br><span class="line">{</span><br><span class="line"><span class="string">"constant"</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="string">"inputs"</span>: [],</span><br><span class="line"><span class="string">"name"</span>: <span class="string">"totalSupply"</span>,</span><br><span class="line"><span class="string">"outputs"</span>: [</span><br><span class="line">{</span><br><span class="line"><span class="string">"name"</span>: <span class="string">""</span>,</span><br><span class="line"><span class="string">"type"</span>: <span class="string">"uint256"</span></span><br><span class="line">}</span><br><span class="line">],</span><br><span class="line"><span class="string">"payable"</span>: <span class="literal">false</span>,</span><br><span class="line"><span class="string">"stateMutability"</span>: <span class="string">"view"</span>,</span><br><span class="line"><span class="string">"type"</span>: <span class="string">"function"</span></span><br><span class="line">},</span><br><span class="line">{</span><br><span class="line"><span class="string">"constant"</span>: <span class="literal">true</span>,</span><br><span class="line"><span class="string">"inputs"</span>: [</span><br><span class="line">{</span><br><span class="line"><span class="string">"name"</span>: <span class="string">"tokenOwner"</span>,</span><br><span class="line"><span class="string">"type"</span>: <span class="string">"address"</span></span><br><span class="line">}</span><br><span class="line">],</span><br><span class="line"><span class="string">"name"</span>: <span class="string">"balanceOf"</span>,</span><br><span class="line"><span class="string">"outputs"</span>: [</span><br><span class="line">{</span><br><span class="line"><span class="string">"name"</span>: <span class="string">"balance"</span>,</span><br><span class="line"><span class="string">"type"</span>: <span class="string">"uint256"</span></span><br><span class="line">}</span><br><span class="line">],</span><br><span class="line"><span class="string">"payable"</span>: <span class="literal">false</span>,</span><br><span class="line"><span class="string">"stateMutability"</span>: <span class="string">"view"</span>,</span><br><span class="line"><span class="string">"type"</span>: <span class="string">"function"</span></span><br><span class="line">},</span><br><span class="line">...</span><br><span class="line">]</span><br></pre></td></tr></table></figure><p>它是一个JSON形式的数组,数组里每一个元素,都是对函数接口的描述,在外部调用合约的时候就需要遵从这个接口,以上面的接口为例,通常一个接口描述包含下述几个字段:</p><ul><li>name: 函数会事件的名称</li><li>type: 可取值有function,constructor,fallback,event</li><li>inputs: 函数的输入参数,每个参数对象包含下述属性:<ul><li>name: 参数名称</li><li>type: 参数的规范型(Canonical Type)。</li></ul></li><li>outputs: 一系列的类似inputs的对象,如果无返回值时,可以省略。</li><li>constant: true表示函数声明自己不会改变状态变量的值。</li><li>payable: true表示函数可以接收ether,否则表示不能。</li></ul><p>接下来在构造合约对象就需要是使用ABI。</p><h2 id="构造合约对象"><a href="#构造合约对象" class="headerlink" title="构造合约对象"></a>构造合约对象</h2><p>ethers.js 构造合约对象很简单,仅需要提供三个参数给ethers.Contract构造函数,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> abi = [...];</span><br><span class="line"><span class="keyword">var</span> addr = <span class="string">"0x..."</span>;</span><br><span class="line"><span class="keyword">var</span> contract = <span class="keyword">new</span> ethers.Contract(address, abi, provider);</span><br></pre></td></tr></table></figure><p>合约的地址在合约部署之后,可以获得,关于Token合约部署及ERC20相关的概念,这里不展开讲,不熟悉的同学,可以参考我另一篇文章<a href="https://learnblockchain.cn/2018/01/12/create_token/">创建自己的数字货币</a>。</p><p>只有就可以是使用 <code>contract</code> 对象来调用Token合约的函数。</p><h2 id="获取Token余额及转移Token"><a href="#获取Token余额及转移Token" class="headerlink" title="获取Token余额及转移Token"></a>获取Token余额及转移Token</h2><h3 id="获取Token余额"><a href="#获取Token余额" class="headerlink" title="获取Token余额"></a>获取Token余额</h3><p>结合UI来实现以下获取Token余额,UI如下:</p><p> <img src="https://learnblockchain.cn/media/15405195250777.jpg" alt=""></p><p>在HTML里,定义的标签如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>TT Token:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">readonly</span>=<span class="string">"readonly"</span> <span class="attr">class</span>=<span class="string">"readonly"</span> <span class="attr">id</span>=<span class="string">"wallet-token-balance"</span> <span class="attr">value</span>=<span class="string">"0.0"</span> /></span><span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"><span class="tag"></<span class="name">tr</span>></span></span><br></pre></td></tr></table></figure><p>对应的逻辑代码也很简单:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">var</span> tokenBalance = $(<span class="string">'#wallet-token-balance'</span>);</span><br><span class="line"> <span class="comment">// 直接调用合约方法</span></span><br><span class="line">contract.balanceOf(activeWallet.address).then(<span class="function"><span class="keyword">function</span>(<span class="params">balance</span>)</span>{</span><br><span class="line"> tokenBalance.val(balance);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h3 id="转移Token"><a href="#转移Token" class="headerlink" title="转移Token"></a>转移Token</h3><p>转移Token的UI效果如下:</p><p> <img src="https://learnblockchain.cn/media/15405195493994.jpg" alt=""></p><p>界面的HTML代码如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h3</span>></span>转移代币:<span class="tag"></<span class="name">h3</span>></span></span><br><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>发送至:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(target address)"</span> <span class="attr">id</span>=<span class="string">"wallet-token-send-target-address"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>金额:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(amount)"</span> <span class="attr">id</span>=<span class="string">"wallet-token-send-amount"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"wallet-token-submit-send"</span> <span class="attr">class</span>=<span class="string">"submit disable"</span>></span>发送<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>上面定义了两个文本输入框及一个“发送“按钮,在逻辑处理部分,转移Token代币尽管和获取余额类似,同样是调用合约的方法,不过转移代币需要发起一个交易,因此需要测量gas 消耗。<br>点击发送时运行一下(关键)代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> inputTargetAddress = $(<span class="string">'#wallet-token-send-target-address'</span>);</span><br><span class="line"><span class="keyword">var</span> inputAmount = $(<span class="string">'#wallet-token-send-amount'</span>);</span><br><span class="line"><span class="keyword">var</span> submit = $(<span class="string">'#wallet-token-submit-send'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> targetAddress = ethers.utils.getAddress(inputTargetAddress.val());</span><br><span class="line"><span class="keyword">var</span> amount = inputAmount.val();</span><br><span class="line"></span><br><span class="line">submit.click(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"><span class="comment">// 先计算transfer 需要的gas 消耗量,这一步有默认值,非必须。</span></span><br><span class="line"> contract.estimate.transfer(targetAddress, amount)</span><br><span class="line"> .then(<span class="function"><span class="keyword">function</span>(<span class="params">gas</span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 必须关联一个有过签名钱包对象</span></span><br><span class="line"> <span class="keyword">let</span> contractWithSigner = contract.connect(activeWallet);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 发起交易,前面2个参数是函数的参数,第3个是交易参数</span></span><br><span class="line"> contractWithSigner.transfer(targetAddress, amount, {</span><br><span class="line"> gasLimit: gas,</span><br><span class="line"> <span class="comment">// 偷懒,直接是用 2gwei</span></span><br><span class="line"> gasPrice: ethers.utils.parseUnits(<span class="string">"2"</span>, <span class="string">"gwei"</span>),</span><br><span class="line"> }).then(<span class="function"><span class="keyword">function</span>(<span class="params">tx</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(tx);</span><br><span class="line"> <span class="comment">// 介绍刷新上面的Token余额,重置输入框</span></span><br><span class="line"> }); </span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上述有一个地方都要注意一下,在合约调用 transfer 之前, 需要连接一个<strong>signer</strong>,因为发起交易的时候需要用它来进行签名,在ethers.js API里 Wallet 是 signer(抽象类)的实现类。</p><blockquote><p>所有会更改区块链数据的函数都需要关联签名器,如果是视图函数则只需要连接provider。</p></blockquote><p>ethers.js 的 Contract 提供了一个非常方便方法:<code>contract.estimate.functionName</code> 来计算预测交易的gasLimit。</p><p>在发起交易的时候,可以提供一个可选的<a href="https://docs.ethers.io/ethers.js/html/api-contract.html?highlight=estimate#overrides" target="_blank" rel="noopener">Overrides</a>参数,在这个参数里可以指定如交易的 gasLimit 、 gasPrice,如果我们不指定这个参数时,会默认使用 contract.estimate 获得的值作为 gasLimit,以及 provider.getGasPrice() 的值来指定 gasPrice。</p><p>哈哈,恭喜大家,到这里这里就完整的实现了一个基于以太坊去中心化网页钱包。</p><p>这是一条硬广,欢迎订阅<a href="https://xiaozhuanlan.com/blockchaincore" target="_blank" rel="noopener">深入浅出区块链技术小专栏</a>,目前仅需69元, 订阅就可以查看完整源码,还有其他惊喜哦~。<br>戳链接收看<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">详细的视频课程讲解</a>。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ol><li><a href="https://docs.ethers.io/ethers.js/html" target="_blank" rel="noopener">Ethers.js</a></li></ol><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p><p><a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">深入浅出区块链知识星球</a>最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。</p>]]></content>
<summary type="html">
<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,本文是第四篇,Token(代币、通证)是以太坊的一大特色,既然开发钱包,则发送Token 功能必不可少。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="比特币" scheme="https://learnblockchain.cn/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="钱包" scheme="https://learnblockchain.cn/tags/%E9%92%B1%E5%8C%85/"/>
</entry>
<entry>
<title>以太坊钱包开发系列3 - 展示钱包信息及发起签名交易</title>
<link href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/"/>
<id>https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/</id>
<published>2018-10-26T02:31:44.000Z</published>
<updated>2018-12-12T11:18:32.695Z</updated>
<content type="html"><![CDATA[<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第三篇介绍使用ethers.js的钱包对象获取相关信息及发起你离线交易。</p><a id="more"></a><h2 id="使用-Provider-连接以太坊网络"><a href="#使用-Provider-连接以太坊网络" class="headerlink" title="使用 Provider 连接以太坊网络"></a>使用 Provider 连接以太坊网络</h2><p>我们前面两篇文章介绍创建(或导入)钱包账号的过程都是是离线的,即不需要依赖以太坊网络即可创建钱包账号,但如果想获取钱包账号的相关信息,比如余额、交易记录,发起交易的话,就需要让钱包连上以太坊的网络。</p><p>不管是在 Web3 中,还是Ethers.js 都是使用 Provider 来进行网络连接的,Ethers.js 提供了集成多种 Provider 的方式:</p><ul><li><p>Web3Provider: 使用一个已有的web3 兼容的Provider,如有MetaMask 或 Mist提供。</p></li><li><p>EtherscanProvider 及 InfuraProvider: 如果没有自己的节点,可以使用Etherscan 及 Infura 的Provider,他们都是以太坊的基础设施服务提供商,Ethers.js 还提供了一种更简单的方式:使用一个默认的provider, 他会自动帮我们连接Etherscan 及 Infura。</p> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">let defaultProvider = ethers.getDefaultProvider('ropsten');</span><br></pre></td></tr></table></figure><p> 连接Provider, 通常有一个参数network网络名称,取值有: <code>homestead</code>, <code>rinkeby</code>, <code>ropsten</code>, <code>kovan</code>, 关于Provider的更多用法,可以参考<a href="https://docs.ethers.io/ethers.js/html/api-providers.html" target="_blank" rel="noopener">Ethers.js Provider</a>。</p></li><li><p>JsonRpcProvider 及 IpcProvider: 如果有自己的节点可以使用,可以连接主网,测试网络,私有网络或Ganache,这也是本系列文章使用的方式。</p></li></ul><p>使用钱包连接Provider的方法如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 连接本地的geth 节点,8545是geth 的端口</span></span><br><span class="line"><span class="keyword">var</span> provider = <span class="keyword">new</span> ethers.providers.JsonRpcProvider(<span class="string">"http://127.0.0.1:8545"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// wallet 为前两篇文章中生成的钱包对象, activeWallet就是后面可以用来请求余额发送交易的对象</span></span><br><span class="line"><span class="keyword">var</span> activeWallet = wallet.connect(App.provider);</span><br></pre></td></tr></table></figure><blockquote><p>启动geth的需要注意一下,需要使用 <code>--rpc --rpccorsdomain</code> 开启 RPC通信及跨域,</p></blockquote><h2 id="展示钱包详情:查询余额及Nonce"><a href="#展示钱包详情:查询余额及Nonce" class="headerlink" title="展示钱包详情:查询余额及Nonce"></a>展示钱包详情:查询余额及Nonce</h2><p>连接到以太坊网络之后,就可以向网络请求余额以及获取账号交易数量,使用一下API:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">activeWallet.getBalance().then(<span class="function"><span class="keyword">function</span>(<span class="params">balance</span>) </span>{</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">activeWallet.getTransactionCount().then(<span class="function"><span class="keyword">function</span>(<span class="params">transactionCount</span>) </span>{</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>activeWallet就是后面可以用来请求发送交易的对象</p><p><img src="https://learnblockchain.cn/media/15402868832290.jpg" alt=""></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><h3>钱包详情:<span class="xml"><span class="tag"></<span class="name">h3</span>></span></span></span><br><span class="line"><table></span><br><span class="line"> <tr><span class="xml"><span class="tag"><<span class="name">th</span>></span>地址:<span class="tag"></<span class="name">th</span>></span></span></span><br><span class="line"> <td></span><br><span class="line"> <input type=<span class="string">"text"</span> readonly=<span class="string">"readonly"</span> <span class="class"><span class="keyword">class</span></span>=<span class="string">"readonly"</span> id=<span class="string">"wallet-address"</span> value=<span class="string">""</span> /><span class="xml"><span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> <<span class="regexp">/td></span></span><br><span class="line"><span class="regexp"> </</span>tr></span><br><span class="line"> <tr><span class="xml"><span class="tag"><<span class="name">th</span>></span>余额:<span class="tag"></<span class="name">th</span>></span></span></span><br><span class="line"> <td></span><br><span class="line"> <input type=<span class="string">"text"</span> readonly=<span class="string">"readonly"</span> <span class="class"><span class="keyword">class</span></span>=<span class="string">"readonly"</span> id=<span class="string">"wallet-balance"</span> value=<span class="string">"0.0"</span> /><span class="xml"><span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> <<span class="regexp">/td></span></span><br><span class="line"><span class="regexp"> </</span>tr></span><br><span class="line"> <tr><span class="xml"><span class="tag"><<span class="name">th</span>></span>Nonce:<span class="tag"></<span class="name">th</span>></span></span></span><br><span class="line"> <td></span><br><span class="line"> <input type=<span class="string">"text"</span> readonly=<span class="string">"readonly"</span> <span class="class"><span class="keyword">class</span></span>=<span class="string">"readonly"</span> id=<span class="string">"wallet-transaction-count"</span> value=<span class="string">"0"</span> /><span class="xml"><span class="tag"></<span class="name">div</span>></span></span></span><br><span class="line"> <<span class="regexp">/td></span></span><br><span class="line"><span class="regexp"> </</span>tr></span><br><span class="line"> <tr><span class="xml"><span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span></span><br><span class="line"> <td></span><br><span class="line"> <div id=<span class="string">"wallet-submit-refresh"</span> <span class="class"><span class="keyword">class</span></span>=<span class="string">"submit"</span>>刷新<<span class="regexp">/div></span></span><br><span class="line"><span class="regexp"> </</span>td></span><br><span class="line"> <<span class="regexp">/tr></span></span><br><span class="line"><span class="regexp"></</span>table></span><br></pre></td></tr></table></figure><p>js处理的逻辑就是获取信息之后,填充相应的控件,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> inputBalance = $(<span class="string">'#wallet-balance'</span>);</span><br><span class="line"><span class="keyword">var</span> inputTransactionCount = $(<span class="string">'#wallet-transaction-count'</span>);</span><br><span class="line"></span><br><span class="line">$(<span class="string">"#wallet-submit-refresh"</span>).click(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取余额时, 包含当前正在打包的区块</span></span><br><span class="line"> activeWallet.getBalance(<span class="string">'pending'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">balance</span>) </span>{</span><br><span class="line"> <span class="comment">// 单位转换 wei -> ether</span></span><br><span class="line"> inputBalance.val(ethers.utils.formatEther(balance, { <span class="attr">commify</span>: <span class="literal">true</span> }));</span><br><span class="line"> }, <span class="function"><span class="keyword">function</span>(<span class="params">error</span>) </span>{</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> activeWallet.getTransactionCount(<span class="string">'pending'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">transactionCount</span>) </span>{</span><br><span class="line"> inputTransactionCount.val(transactionCount);</span><br><span class="line"> }, <span class="function"><span class="keyword">function</span>(<span class="params">error</span>) </span>{</span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line"><span class="comment">// 模拟一次点击获取数据</span></span><br><span class="line">$(<span class="string">"#wallet-submit-refresh"</span>).click();</span><br></pre></td></tr></table></figure><h2 id="发送签名交易"><a href="#发送签名交易" class="headerlink" title="发送签名交易"></a>发送签名交易</h2><p>之前我们有一篇文章:<a href="https://learnblockchain.cn//2018/09/12/web3-sendeth/">如何使用Web3.js API 在页面中进行转账</a>介绍过发起交易,不过当时的签名是利用MetaMask来完成的,现在我们要完成一个钱包,必须要发送一个签名交易,签名交易也称为离线交易(因为这个过程可以离线进行:在离线状态下对交易进行签名,然后把签名后的交易进行广播)。</p><p>尽管 Ethers.js 提供了非常简洁的API来发送签名交易,但是探究下简洁API背后的细节依然会对我们有帮助,这个过程大致可分为三步:</p><ol><li>构造交易</li><li>交易签名</li><li>发送(广播)交易</li></ol><h3 id="构造交易"><a href="#构造交易" class="headerlink" title="构造交易"></a>构造交易</h3><p>先来看看一个交易长什么样子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> txParams = {</span><br><span class="line"> nonce: <span class="string">'0x00'</span>,</span><br><span class="line"> gasPrice: <span class="string">'0x09184e72a000'</span>,</span><br><span class="line"> gasLimit: <span class="string">'0x2710'</span>,</span><br><span class="line"> to: <span class="string">'0x0000000000000000000000000000000000000000'</span>,</span><br><span class="line"> value: <span class="string">'0x00'</span>,</span><br><span class="line"> data: <span class="string">'0x7f7465737432000000000000000000000000000000000000000000000000000000600057'</span>,</span><br><span class="line"> <span class="comment">// EIP 155 chainId - mainnet: 1, ropsten: 3</span></span><br><span class="line"> chainId: <span class="number">3</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>发起交易的时候,就是需要填充每一个字段,构建这样一个交易结构。<br><code>to</code> 和 <code>value</code>: 很好理解,就是用户要转账的目标及金额。<br><code>data</code>: 是交易时附加的消息,如果是对合约地址发起交易,这会转化为对合约函数的执行,可参考:<a href="https://learnblockchain.cn/2018/08/09/understand-abi/">如何理解以太坊ABI</a><br><code>nonce</code>: 交易序列号<br><code>chainId</code>: 链id,用来去区分不同的链(分叉链)id可在<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md#specification" target="_blank" rel="noopener">EIP-55</a>查询。</p><blockquote><p><code>nonce</code> 和 <code>chainId</code> 有一个重要的作用就是防止重放攻击,如果没有nonce的活,收款人可能把这笔签名过的交易再次进行广播,没有chainId的话,以太坊上的交易可以拿到以太经典上再次进行广播。</p></blockquote><p><code>gasPrice</code>和<code>gasLimit</code>: Gas是以太坊的工作计费机制,是由交易发起者给矿工打包的费用。上面几个参数的设置比较固定,Gas的设置(尤其是gasPrice)则灵活的多。</p><p><code>gasLimit</code> 表示预计的指令和存储空间的工作量,如果工作量没有用完,会退回交易发起者,如果不够会发生<em>out-of-gas</em> 错误。<br><strong>一个普通转账的交易,工作量是固定的,gasLimit为21000</strong>,合约执行gasLimit则是变化的,也许有一些人会认为直接设置为高一点,反正会退回,但如果合约执行出错,就会吃掉所有的gas。幸运的是web3 和 ethers.js 都提供了测算Gas Limit的方法,下一遍<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4">发送代币</a></p><p><code>gasPrice</code>是交易发起者是愿意为工作量支付的<strong>单位</strong>费用,矿工在选择交易的时候,是按照gasPrice进行排序,先服务高出价者,因此如果出价过低会导致交易迟迟不能打包确认,出价过高对发起者又比较亏。</p><p>web3 和 ethers.js 提供一个方法 <strong>getGasPrice()</strong> 用来获取最近几个历史区块gas price的中位数,也有一些第三方提供预测gas price的接口,如:<a href="https://www.etherchain.org/tools/gasPriceOracle" target="_blank" rel="noopener">gasPriceOracle</a> 、 <a href="https://ethgasstation.info/json/ethgasAPI.json" target="_blank" rel="noopener">ethgasAPI</a>、 <a href="https://etherscan.io/gastracker" target="_blank" rel="noopener">etherscan gastracker</a>,这些服务通常还会参考当前交易池内交易数量及价格,可参考性更强,</p><p>常规的一个做法是利用这些接口给用户一个参考值,然后用户可以根据参考值进行微调。</p><h3 id="交易签名"><a href="#交易签名" class="headerlink" title="交易签名"></a>交易签名</h3><p>在构建交易之后,就是用私钥对其签名,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> tx = <span class="keyword">new</span> EthereumTx(txParams)</span><br><span class="line">tx.sign(privateKey)</span><br><span class="line"><span class="keyword">const</span> serializedTx = tx.serialize()</span><br></pre></td></tr></table></figure><p>代码参考<a href="https://github.com/ethereumjs/ethereumjs-tx" target="_blank" rel="noopener">ethereumjs-tx</a></p><h3 id="发送(广播)交易"><a href="#发送(广播)交易" class="headerlink" title="发送(广播)交易"></a>发送(广播)交易</h3><p>然后就是发送(广播)交易,代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">web3.eth.sendRawTransaction(serializedTx, <span class="function"><span class="keyword">function</span> (<span class="params">err, transactionHash</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(err);</span><br><span class="line"> <span class="built_in">console</span>.log(transactionHash);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><p>通过这三步就完成了发送签名交易的过程,ethers.js 里提供了一个简洁的接口,来完成所有这三步操作(强调一下,签名已经在接口里帮我们完成了),接口如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">activeWallet.sendTransaction({</span><br><span class="line"> to: targetAddress,</span><br><span class="line"> value: amountWei,</span><br><span class="line"> gasPrice: activeWallet.provider.getGasPrice(),</span><br><span class="line"> gasLimit: <span class="number">21000</span>,</span><br><span class="line"> }).then(<span class="function"><span class="keyword">function</span>(<span class="params">tx</span>) </span>{</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><h2 id="用ethers-js-实现发送交易"><a href="#用ethers-js-实现发送交易" class="headerlink" title="用ethers.js 实现发送交易"></a>用ethers.js 实现发送交易</h2><p>先来看看发送交易的UI界面:</p><p><img src="https://learnblockchain.cn/media/15403685871339.jpg" alt=""></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h3</span>></span>以太转账:<span class="tag"></<span class="name">h3</span>></span></span><br><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span> <span class="tag"><<span class="name">th</span>></span>发送至:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(target address)"</span> <span class="attr">id</span>=<span class="string">"wallet-send-target-address"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span> <span class="tag"><<span class="name">th</span>></span>金额:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(amount)"</span> <span class="attr">id</span>=<span class="string">"wallet-send-amount"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"wallet-submit-send"</span> <span class="attr">class</span>=<span class="string">"submit disable"</span>></span>发送<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>上面主要定义了两个文本输入框及一个“发送“按钮,在点击发送时运行一下(关键)代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> inputTargetAddress = $(<span class="string">'#wallet-send-target-address'</span>);</span><br><span class="line"><span class="keyword">var</span> inputAmount = $(<span class="string">'#wallet-send-amount'</span>);</span><br><span class="line"><span class="keyword">var</span> submit = $(<span class="string">'#wallet-submit-send'</span>);</span><br><span class="line"></span><br><span class="line">submit.click(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"><span class="comment">// 得到一个checksum 地址</span></span><br><span class="line"> <span class="keyword">var</span> targetAddress = ethers.utils.getAddress(inputTargetAddress.val());</span><br><span class="line"><span class="comment">// ether -> wei</span></span><br><span class="line"> <span class="keyword">var</span> amountWei = ethers.utils.parseEther(inputAmount.val());</span><br><span class="line"> activeWallet.sendTransaction({</span><br><span class="line"> to: targetAddress,</span><br><span class="line"> value: amountWei,</span><br><span class="line"> <span class="comment">// gasPrice: activeWallet.provider.getGasPrice(), (可用默认值)</span></span><br><span class="line"> <span class="comment">// gasLimit: 21000,</span></span><br><span class="line"> }).then(<span class="function"><span class="keyword">function</span>(<span class="params">tx</span>) </span>{</span><br><span class="line"> <span class="built_in">console</span>.log(tx);</span><br><span class="line"> });</span><br><span class="line">})</span><br></pre></td></tr></table></figure><p>哈哈哈~, 干活介绍到这里,现在夹带一点私货,有到了推广时间了,完整源码请订阅<a href="https://xiaozhuanlan.com/blockchaincore" target="_blank" rel="noopener">深入浅出区块链技术小专栏</a>查看,赶紧订阅吧,走过路过,不容错过。<br>戳链接收看<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">详细的视频课程讲解</a>。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><ol><li><a href="https://github.com/ethereumjs/ethereumjs-tx" target="_blank" rel="noopener">ethereum-tx</a></li><li><a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target="_blank" rel="noopener">EIP-55</a></li><li><a href="https://docs.ethers.io/ethers.js/html" target="_blank" rel="noopener">Ethers.js</a></li></ol><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p><p><a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">深入浅出区块链知识星球</a>最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。</p>]]></content>
<summary type="html">
<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第三篇介绍使用ethers.js的钱包对象获取相关信息及发起你离线交易。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="比特币" scheme="https://learnblockchain.cn/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="钱包" scheme="https://learnblockchain.cn/tags/%E9%92%B1%E5%8C%85/"/>
</entry>
<entry>
<title>以太坊钱包开发系列2 - 账号Keystore文件导入导出</title>
<link href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/"/>
<id>https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/</id>
<published>2018-10-25T12:34:44.000Z</published>
<updated>2018-12-12T11:18:27.626Z</updated>
<content type="html"><![CDATA[<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第二篇,主要介绍钱包账号导出与导入,将对Keystore文件的生成的原理进行介绍。</p><a id="more"></a><h2 id="如何导入Geth创建的账号?"><a href="#如何导入Geth创建的账号?" class="headerlink" title="如何导入Geth创建的账号?"></a>如何导入Geth创建的账号?</h2><p>在<a href="https://learnblockchain.cn/2018/10/20/eth-web-wallet_1/">上一篇文章</a>,介绍了如何使用私钥及助记词来创建账号,如果是使用已有的私钥及助记词,这其实也是账号导入的过程。</p><p>有一些同学会问,我的账号是Geth生成的,如何导入到钱包呢?使用Geth的同学,应该知道Geth在创建账号时会生成一个对应keystore JSON文件,Keystore文件存储加密后的私钥信息,因此我们需要做的就是导入这个Keystore文件,这个文件通常在同步区块数据的目录下的keystore文件夹(如: ~/.ethereum/keystore)里。</p><p>尽管在ethers.js 中,简单的使用一个函数就可以完成keystore文件的导入,不过理解Keystore 文件的作用及原理还是非常有必要的,当然如果你是在没有兴趣,可以直接跳到本文最后一节:使用ethers.js 实现账号导出导入。</p><h2 id="详细解读-Keystore-文件"><a href="#详细解读-Keystore-文件" class="headerlink" title="详细解读 Keystore 文件"></a>详细解读 Keystore 文件</h2><h3 id="为什么需要-Keystore-文件"><a href="#为什么需要-Keystore-文件" class="headerlink" title="为什么需要 Keystore 文件"></a>为什么需要 Keystore 文件</h3><p>通过这篇文章<a href="https://learnblockchain.cn/2018/09/28/hdwallet/">理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</a>,私钥其实就代表了一个账号,最简单的保管账号的方式就是直接把私钥保存起来,如果私钥文件被人盗取,我们的数字资产将洗劫一空。</p><p>Keystore 文件就是一种以加密的方式存储密钥的文件,这样的发起交易的时候,先从Keystore 文件是使用密码解密出私钥,然后进行签名交易。这样做之后就会安全的多,因为只有黑客同时盗取 keystore 文件和密码才能盗取我们的数字资产。</p><h3 id="Keystore-文件如何生成的"><a href="#Keystore-文件如何生成的" class="headerlink" title="Keystore 文件如何生成的"></a>Keystore 文件如何生成的</h3><p> 以太坊是使用对称加密算法来加密私钥生成Keystore文件,因此对称加密秘钥(注意它其实也是发起交易时需要的解密秘钥)的选择就非常关键,这个秘钥是使用KDF算法推导派生而出。因此在完整介绍Keystore 文件如何生成前,有必要先介绍一下KDF。</p><h4 id="使用-KDF-生成秘钥"><a href="#使用-KDF-生成秘钥" class="headerlink" title="使用 KDF 生成秘钥"></a>使用 KDF 生成秘钥</h4><p>密码学KDF(key derivation functions),其作用是通过一个密码派生出一个或多个秘钥,即从 password 生成加密用的 key。</p><p>其实在<a href="https://learnblockchain.cn/2018/09/28/hdwallet/">理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</a>中介绍助记词推导出种子的PBKDF2算法就是一种KDF函数,其原理是加盐以及增加哈希迭代次数。</p><p>而在Keystore中,是用的是<a href="https://tools.ietf.org/html/rfc7914" target="_blank" rel="noopener">Scrypt算法</a>,用一个公式来表示的话,派生的Key生成方程为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">DK = Scrypt(salt, dk_len, n, r, p)</span><br></pre></td></tr></table></figure><p>其中的 salt 是一段随机的盐,dk_len 是输出的哈希值的长度。n 是 CPU/Memory 开销值,越高的开销值,计算就越困难。r 表示块大小,p 表示并行度。</p><blockquote><p> Litecoin 就使用 scrypt 作为它的 POW 算法</p></blockquote><p>实际使用中,还会加上一个密码进行计算,用一张图来表示这个过程就是:</p><p><img src="https://learnblockchain.cn/media/15402189467872.png" alt=""></p><h4 id="对私钥进行对称加密"><a href="#对私钥进行对称加密" class="headerlink" title="对私钥进行对称加密"></a>对私钥进行对称加密</h4><p>上面已经用KDF算法生成了一个秘钥,这个秘钥就是接着进行对称加密的秘钥,这里使用的对称加密算法是 aes-128-ctr,aes-128-ctr 加密算法还需要用到一个参数初始化向量 iv。</p><h3 id="Keystore文件"><a href="#Keystore文件" class="headerlink" title="Keystore文件"></a>Keystore文件</h3><p>好了,我们现在结合具体 Keystore文件的内容,就很容易理解了Keystore 文件怎么产生的了。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">{ </span><br><span class="line"> <span class="attr">"address"</span>:<span class="string">"856e604698f79cef417aab..."</span>,</span><br><span class="line"> <span class="attr">"crypto"</span>:{ </span><br><span class="line"> <span class="attr">"cipher"</span>:<span class="string">"aes-128-ctr"</span>,</span><br><span class="line"> <span class="attr">"ciphertext"</span>:<span class="string">"13a3ad2135bef1ff228e399dfc8d7757eb4bb1a81d1b31...."</span>,</span><br><span class="line"> <span class="attr">"cipherparams"</span>:{ </span><br><span class="line"> <span class="attr">"iv"</span>:<span class="string">"92e7468e8625653f85322fb3c..."</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"kdf"</span>:<span class="string">"scrypt"</span>,</span><br><span class="line"> <span class="attr">"kdfparams"</span>:{ </span><br><span class="line"> <span class="attr">"dklen"</span>:<span class="number">32</span>,</span><br><span class="line"> <span class="attr">"n"</span>:<span class="number">262144</span>,</span><br><span class="line"> <span class="attr">"p"</span>:<span class="number">1</span>,</span><br><span class="line"> <span class="attr">"r"</span>:<span class="number">8</span>,</span><br><span class="line"> <span class="attr">"salt"</span>:<span class="string">"3ca198ce53513ce01bd651aee54b16b6a...."</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"mac"</span>:<span class="string">"10423d837830594c18a91097d09b7f2316..."</span></span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"id"</span>:<span class="string">"5346bac5-0a6f-4ac6-baba-e2f3ad464f3f"</span>,</span><br><span class="line"> <span class="attr">"version"</span>:<span class="number">3</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>来解读一下各个字段:</p><ul><li>address: 账号地址</li><li>version: Keystore文件的版本,目前为第3版,也称为V3 KeyStore。</li><li>id : uuid</li><li>crypto: 加密推倒的相关配置.<ul><li>cipher 是用于加密以太坊私钥的对称加密算法。用的是 aes-128-ctr 。</li><li>cipherparams 是 aes-128-ctr 加密算法需要的参数。在这里,用到的唯一的参数 iv。</li><li>ciphertext 是加密算法输出的密文,也是将来解密时的需要的输入。</li><li>kdf: 指定使用哪一个算法,这里使用的是 scrypt。</li><li>kdfparams: scrypt函数需要的参数</li><li>mac: 用来校验密码的正确性, mac= sha3(DK[16:32], ciphertext) 下面一个小节单独分析。</li></ul></li></ul><p>我们来完整梳理一下 Keystore 文件的产生:</p><ol><li>使用scrypt函数 (根据密码 和 相应的参数) 生成秘钥</li><li>使用上一步生成的秘钥 + 账号私钥 + 参数 进行对称加密。</li><li>把相关的参数 和 输出的密文 保存为以上格式的 JSON 文件</li></ol><h3 id="如何确保密码是对的?"><a href="#如何确保密码是对的?" class="headerlink" title="如何确保密码是对的?"></a>如何确保密码是对的?</h3><p>当我们在使用Keystore文件来还原私钥时,依然是使用kdf生成一个秘钥,然后用秘钥对ciphertext进行解密,其过程如下:</p><p><img src="https://learnblockchain.cn/media/15402194796574.png" alt=""></p><p>此时细心的同学会发现,无论使用说明密码,来进行这个操作,都会生成一个私钥,但是最终计算的以太坊私钥到底是不是正确的,却不得而知。</p><p>这就是 keystore 文件中 mac 值的作用。mac 值是 kdf输出 和 ciphertext 密文进行SHA3-256运算的结果,显然密码不同,计算的mac 值也不同,因此可以用来检验密码的正确性。检验过程用图表示如下:</p><p><img src="https://learnblockchain.cn/media/15402227441945.png" alt=""></p><p>现在我们以解密的角度完整的梳理下流程,就可以得到以下图:</p><p><img src="https://learnblockchain.cn/media/15402229547319.png" alt=""></p><h2 id="用ethers-js-实现账号导出导入"><a href="#用ethers-js-实现账号导出导入" class="headerlink" title="用ethers.js 实现账号导出导入"></a>用ethers.js 实现账号导出导入</h2><p>ethers.js 直接提供了加载keystore JSON来创建钱包对象以及加密生成keystore文件的方法,方法如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导入keystore Json</span></span><br><span class="line"> ethers.Wallet.fromEncryptedJson(json, password, [progressCallback]).then(<span class="function"><span class="keyword">function</span>(<span class="params">wallet</span>) </span>{</span><br><span class="line"> <span class="comment">// wallet</span></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用钱包对象 导出keystore Json</span></span><br><span class="line"> wallet.encrypt(pwd, [progressCallback].then(<span class="function"><span class="keyword">function</span>(<span class="params">json</span>) </span>{</span><br><span class="line"> <span class="comment">// 保存json</span></span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>现在结合界面来完整的实现账号导出及导入,先看看导出,UI图如下:</p><p><img src="https://learnblockchain.cn/media/15402637447655.jpg" alt=""></p><p>HTML 代码如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">h3</span>></span>KeyStore 导出:<span class="tag"></<span class="name">h3</span>></span></span><br><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>密码:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(password)"</span> <span class="attr">id</span>=<span class="string">"save-keystore-file-pwd"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"save-keystore"</span> <span class="attr">class</span>=<span class="string">"submit"</span>></span>导出<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>上面主要定义了一个密码输入框和一个导出按钮,点击“导出”后,处理逻辑代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// "导出" 按钮,执行exportKeystore函数</span></span><br><span class="line"> $(<span class="string">'#save-keystore'</span>).click(exportKeystore);</span><br><span class="line"></span><br><span class="line"> exportKeystore: <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 获取密码</span></span><br><span class="line"> <span class="keyword">var</span> pwd = $(<span class="string">'#save-keystore-file-pwd'</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// wallet 是上一篇文章中生成的钱包对象</span></span><br><span class="line"> wallet.encrypt(pwd.val()).then(<span class="function"><span class="keyword">function</span>(<span class="params">json</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> blob = <span class="keyword">new</span> Blob([json], {<span class="attr">type</span>: <span class="string">"text/plain;charset=utf-8"</span>});</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 使用了FileSaver.js 进行文件保存</span></span><br><span class="line"> saveAs(blob, <span class="string">"keystore.json"</span>);</span><br><span class="line"></span><br><span class="line"> });</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p><a href="https://github.com/eligrey/FileSaver.js" target="_blank" rel="noopener">FileSaver.js</a> 是可以用来在页面保存文件的一个库。</p><p>再来看看导入keystore 文件, UI图如下:</p><p><img src="https://learnblockchain.cn/media/15402647159310.jpg" alt=""></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="tag"><<span class="name">h2</span>></span>加载账号Keystore文件<span class="tag"></<span class="name">h2</span>></span></span><br><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>Keystore:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"select-wallet-drop"</span>></span>把Json文件拖动到这里<span class="tag"></<span class="name">div</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"file"</span> <span class="attr">id</span>=<span class="string">"select-wallet-file"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>密码:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"password"</span> <span class="attr">placeholder</span>=<span class="string">"(password)"</span> <span class="attr">id</span>=<span class="string">"select-wallet-password"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"select-submit-wallet"</span> <span class="attr">class</span>=<span class="string">"submit disable"</span>></span>解密<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>上面主要定义了一个文件输入框、一个密码输入框及一个“解密“按钮,因此处理逻辑包含两部分,一是读取文件,二是解析加载账号,关键代码如下:</p> <figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">// 使用FileReader读取文件,</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> fileReader = <span class="keyword">new</span> FileReader();</span><br><span class="line"> fileReader.onload = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> json = e.target.result;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从加载</span></span><br><span class="line"> ethers.Wallet.fromEncryptedJson(json, password).then(<span class="function"><span class="keyword">function</span>(<span class="params">wallet</span>) </span>{</span><br><span class="line"></span><br><span class="line"> }, <span class="function"><span class="keyword">function</span>(<span class="title">error</span>) </span>{</span><br><span class="line"></span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> };</span><br><span class="line">fileReader.readAsText(inputFile.files[<span class="number">0</span>]);</span><br></pre></td></tr></table></figure><p> 哈哈哈,有到了推广时间了,完整源码请订阅<a href="https://xiaozhuanlan.com/blockchaincore" target="_blank" rel="noopener">深入浅出区块链技术小专栏</a>查看,赶紧订阅吧,走过路过,不容错过。<br> 戳链接收看<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">详细的视频课程讲解</a>。</p><h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a href="https://medium.com/@julien.maffre/what-is-an-ethereum-keystore-file-86c8c5917b97" target="_blank" rel="noopener">what-is-an-ethereum-keystore-file</a></p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p><p><a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">深入浅出区块链知识星球</a>最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。</p>]]></content>
<summary type="html">
<p>以太坊去中心化网页钱包开发系列,点链接<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">观看视频课程</a>,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第二篇,主要介绍钱包账号导出与导入,将对Keystore文件的生成的原理进行介绍。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="比特币" scheme="https://learnblockchain.cn/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="钱包" scheme="https://learnblockchain.cn/tags/%E9%92%B1%E5%8C%85/"/>
</entry>
<entry>
<title>以太坊钱包开发系列1 - 创建钱包账号</title>
<link href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/"/>
<id>https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/</id>
<published>2018-10-25T10:34:44.000Z</published>
<updated>2018-12-12T11:18:21.033Z</updated>
<content type="html"><![CDATA[<p>以太坊去中心化网页钱包开发系列,详细的<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">视频课程讲解直接戳链接</a>,本系列将从零开始开发出一个可以实际使用的钱包,本系列是理论与实战相结合,文章一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第一篇,主要介绍钱包将实现哪些功能及怎么创建钱包账号,本钱包是基于<a href="https://docs.ethers.io/ethers.js/html" target="_blank" rel="noopener">ethers.js</a> 进行开发。</p><a id="more"></a><h2 id="去中心化网页钱包"><a href="#去中心化网页钱包" class="headerlink" title="去中心化网页钱包"></a>去中心化网页钱包</h2><p>先明确一下定义,什么是去中心化钱包,账号秘钥的管理,交易的签名,都是在客户端完成, 即私钥相关的信息都是在用户手中,钱包的开发者接触不到私钥信息。</p><blockquote><p>对应的中心化钱包则是私钥由中心服务器托管,如交易所的钱包就是这种。</p></blockquote><p>网页钱包,或者叫web钱包,是指钱包以网页的形式展现,去中心化网页钱包则交易的签名等操作是在浏览器里完成。<br>其他形式的钱包,如Android钱包或iOS钱包其开发思路和web钱包一样,因此文本对开发其他平台的钱包也有参考意义,不过本系列文章主要侧重在钱包功能的实现,并未过多考虑用户体验。</p><h2 id="钱包功能"><a href="#钱包功能" class="headerlink" title="钱包功能"></a>钱包功能</h2><p>一个钱包通常主要包含的功能有:</p><ul><li>账号管理(主要是私钥的管理):创建账号、账号导入导出</li><li>账号信息展示:如以太币余额、Token(代币)余额。</li><li>转账功能:发送以太币及发送Token(代币)</li></ul><p>这些功能将基于 ethers.js 进行开发, ethers.js 和web3.js 一样,也是一套和以太坊区块链进行交互的库,不仅如此,ethers.js 还对BIP 39等相关的提案进行了实现,可以在这个<a href="https://docs.ethers.io/ethers.js/html/" target="_blank" rel="noopener">链接</a>阅读其文档。</p><p>这些功能主要表现为钱包的两个界面,一个界面是:账号管理,一个界面是进行账号信息展示及转账。下面逐个进行介绍</p><h2 id="创建钱包账号"><a href="#创建钱包账号" class="headerlink" title="创建钱包账号"></a>创建钱包账号</h2><p>读过上一篇文章<a href="https://learnblockchain.cn/2018/09/28/hdwallet/">理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</a>的同学,会知道创建账号,可以有两种方式:</p><ul><li>直接生成32个字节的数当成私钥</li><li>通过助记词进行确定性推导出私钥</li></ul><h3 id="使用随机数作为私钥创建钱包账号"><a href="#使用随机数作为私钥创建钱包账号" class="headerlink" title="使用随机数作为私钥创建钱包账号"></a>使用随机数作为私钥创建钱包账号</h3><p>即方式一,可以使用ethers.utils.randomBytes生成一个随机数,然后使用这个随机数来创建钱包,如代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> privateKey = ethers.utils.randomBytes(<span class="number">32</span>);</span><br><span class="line"><span class="keyword">var</span> wallet = <span class="keyword">new</span> ethers.Wallet(privateKey);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"账号地址: "</span> + wallet.address);</span><br></pre></td></tr></table></figure><p>上面代码的 wallet 是 ethers 中的一个钱包对象,它除了有代码中出现的.address 属性之外,还有如 获取余额、发送交易等方法,在后面的文章会进行介绍。</p><p>注意ethers.utils.randomBytes 生成的是一个字节数组,如果想用十六进制数显示出来表示,需要转化为BigNumber代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> keyNumber = ethers.utils.bigNumberify(privateKey);</span><br><span class="line"><span class="built_in">console</span>.log(randomNumber._hex);</span><br></pre></td></tr></table></figure><p>现在我们结合界面,完整的实现创建账号,其效果图如下,加载私钥时创建账号。</p><p><img src="https://learnblockchain.cn/media/15401942330046.jpg" alt=""></p><p>界面代码(HTML)代码如下(主要是在表格中定义个一个输入框及一个按钮):</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>私钥:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(private key)"</span> <span class="attr">id</span>=<span class="string">"select-privatekey"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"select-submit-privatekey"</span> <span class="attr">class</span>=<span class="string">"submit"</span>></span>加载私钥<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>对应的逻辑代码(JavaScript)如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用JQuery获取两个UI标签</span></span><br><span class="line"> <span class="keyword">var</span> inputPrivatekey = $(<span class="string">'#select-privatekey'</span>);</span><br><span class="line"> <span class="keyword">var</span> submit = $(<span class="string">'#select-submit-privatekey'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成一个默认的私钥</span></span><br><span class="line"> <span class="keyword">let</span> randomNumber = ethers.utils.bigNumberify(ethers.utils.randomBytes(<span class="number">32</span>));</span><br><span class="line"> inputPrivatekey.val(randomNumber._hex);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 点击“加载私钥”时, 创建对应的钱包</span></span><br><span class="line"> submit.click(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> privateKey = inputPrivatekey.val();</span><br><span class="line"> <span class="keyword">if</span> (privateKey.substring(<span class="number">0</span>, <span class="number">2</span>) !== <span class="string">'0x'</span>) { privateKey = <span class="string">'0x'</span> + privateKey; }</span><br><span class="line"> <span class="keyword">var</span> wallet = <span class="keyword">new</span> ethers.Wallet(privateKey));</span><br><span class="line"></span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>如果用户提供一个已有账号的私钥,则会导入其原有账号。</p><h3 id="通过助记词方式创建钱包账号"><a href="#通过助记词方式创建钱包账号" class="headerlink" title="通过助记词方式创建钱包账号"></a>通过助记词方式创建钱包账号</h3><p>这是目前主流常见钱包的方式,关于助记词推导过程请阅读<a href="https://learnblockchain.cn/2018/09/28/hdwallet/">理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</a>。</p><p>我们需要先生成一个随机数,然后用随机数生成助记词,随后用助记词创建钱包账号,设计到的API有:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">var</span> rand = ethers.utils.randomBytes(<span class="number">16</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成助记词</span></span><br><span class="line"><span class="keyword">var</span> mnemonic = ethers.utils.HDNode.entropyToMnemonic(rand);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> path = <span class="string">"m/44'/60'/0'/0/0"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过助记词创建钱包</span></span><br><span class="line">ethers.Wallet.fromMnemonic(mnemonic, path);</span><br></pre></td></tr></table></figure><p>现在我们结合界面来实现一下通过助记词方式创建钱包账号,其效果图如下:</p><p><img src="https://learnblockchain.cn/media/15401977091699.jpg" alt=""></p><p>界面代码(HTML)代码如下(主要是在表格中定义个两个输入框及一个按钮):</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">table</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>助记词:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(mnemonic phrase)"</span> <span class="attr">id</span>=<span class="string">"select-mnemonic-phrase"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">th</span>></span>Path:<span class="tag"></<span class="name">th</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span><span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">placeholder</span>=<span class="string">"(path)"</span> <span class="attr">id</span>=<span class="string">"select-mnemonic-path"</span> <span class="attr">value</span>=<span class="string">"m/44'/60'/0'/0/0"</span> /></span><span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">tr</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">id</span>=<span class="string">"select-submit-mnemonic"</span> <span class="attr">class</span>=<span class="string">"submit"</span>></span>推倒<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">td</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">tr</span>></span></span><br><span class="line"><span class="tag"></<span class="name">table</span>></span></span><br></pre></td></tr></table></figure><p>对应的逻辑代码(JavaScript)如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">var</span> inputPhrase = $(<span class="string">'#select-mnemonic-phrase'</span>);</span><br><span class="line"> <span class="keyword">var</span> inputPath = $(<span class="string">'#select-mnemonic-path'</span>);</span><br><span class="line"> <span class="keyword">var</span> submit = $(<span class="string">'#select-submit-mnemonic'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 生成助记词</span></span><br><span class="line"> <span class="keyword">var</span> mnemonic = ethers.utils.HDNode.entropyToMnemonic(ethers.utils.randomBytes(<span class="number">16</span>));</span><br><span class="line"> inputPhrase.val(mnemonic);</span><br><span class="line"></span><br><span class="line"> submit.click(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="comment">// 检查助记词是否有效。</span></span><br><span class="line"> <span class="keyword">if</span> (!ethers.utils.HDNode.isValidMnemonic(inputPhrase.val())) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过助记词创建钱包对象</span></span><br><span class="line"> <span class="keyword">var</span> wallet = ethers.Wallet.fromMnemonic(inputPhrase.val(), inputPath.val());</span><br><span class="line"> });</span><br></pre></td></tr></table></figure><p>同样用户可以提供一个其保存的助记词来导入其钱包,有一些遗憾的是,ethers.js 暂时不支持通过添加密码作为Salt来保护种子(也可能是我没有找到,如果知道的同学,希望反馈下),如果需要此功能可以引入bip39 和 ethereumjs-wallet 库来实现,代码可参考<a href="https://learnblockchain.cn/2018/09/28/hdwallet/">理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</a>。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>其实 ethers 还提供了一个更简单的方法来创建钱包:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 直接创建一个随机钱包</span></span><br><span class="line">ethers.Wallet.createRandom();</span><br></pre></td></tr></table></figure><p>完整源码请订阅<a href="https://xiaozhuanlan.com/blockchaincore" target="_blank" rel="noopener">深入浅出区块链技术小专栏</a>查看, 哈哈,是不是有一点鸡贼,创作不易呀。<br>戳链接收看<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">详细的视频课程讲解</a>。</p><p>参考文档:<br><a href="https://docs.ethers.io/ethers.js/html" target="_blank" rel="noopener">ethers.js</a></p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p><p><a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">深入浅出区块链知识星球</a>最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。</p>]]></content>
<summary type="html">
<p>以太坊去中心化网页钱包开发系列,详细的<a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">视频课程讲解直接戳链接</a>,本系列将从零开始开发出一个可以实际使用的钱包,本系列是理论与实战相结合,文章一共有四篇:<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_1/">创建钱包账号</a>、<a href="https://learnblockchain.cn/2018/10/25/eth-web-wallet_2/">账号Keystore文件导入导出</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_3/">展示钱包信息及发起签名交易</a>、<a href="https://learnblockchain.cn/2018/10/26/eth-web-wallet_4/">发送Token(代币)</a>,这是第一篇,主要介绍钱包将实现哪些功能及怎么创建钱包账号,本钱包是基于<a href="https://docs.ethers.io/ethers.js/html" target="_blank" rel="noopener">ethers.js</a> 进行开发。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="比特币" scheme="https://learnblockchain.cn/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="钱包" scheme="https://learnblockchain.cn/tags/%E9%92%B1%E5%8C%85/"/>
</entry>
<entry>
<title>深入理解Plasma(二)Plasma 细节</title>
<link href="https://learnblockchain.cn/2018/10/24/plasma-in-detail/"/>
<id>https://learnblockchain.cn/2018/10/24/plasma-in-detail/</id>
<published>2018-10-24T12:54:17.000Z</published>
<updated>2018-12-06T01:46:09.788Z</updated>
<content type="html"><![CDATA[<p> 这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要对 Plasma 一些关键操作的细节进行剖析。</p><a id="more"></a><p>在<a href="https://learnblockchain.cn/2018/10/20/plasma-framework/">上一篇</a>文章中我们已经理解了什么是 Plasma 框架以及它是如何运行的,这一篇文章将对其运行过程中的一些关键部分,包括 Plasma 提交区块的过程,当有恶意行为发生时如何构建防伪证明以及如何退出 Plasma 子链等进行剖析。需要注意的是,由于 Plasma 是一套框架,因此本文只剖析 Plasma 项目的共性,每一部分的实现细则还是需要参考实际的项目,例如 Plasma MVP(Minimal-Viable-Plasma)和 Plasma Cash 等。</p><h2 id="存款(Deposit)"><a href="#存款(Deposit)" class="headerlink" title="存款(Deposit)"></a>存款(Deposit)</h2><p>Plasma 的主要思想就是将大部分计算过程都转移到链下进行,用户只有在进入和退出 Plasma Chain 的时候需要跟主链上的智能合约交互,这也是所有 Plasma 应用的标准流程。</p><p>用户在将主链的资产(如以太币或者其它 ERC20 合约发布的 token)转移到 Plasma Chain 的过程称为存款(Deposit),具体做法是直接向主链上的 Plasma 合约发送以太币或 token。Plasma 合约收到 Deposit 交易后会在子链上创建跟 Deposit 数额一致的交易,并将其打包进区块中,作为存款确认的证明。这个过程如下图所示(来源自<a href="https://plasma.io/" target="_blank" rel="noopener">[1]</a>)。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/Deposit.png" width="400" height="360" alt="Blockchains of Blockchain"></p><p>当用户看到子链上自己之前存款的交易被确认后,就可以在子链上使用这笔资产(给子链上的其他用户发送交易或者退出子链等)。</p><h2 id="状态确认(State-Commitment)"><a href="#状态确认(State-Commitment)" class="headerlink" title="状态确认(State Commitment)"></a>状态确认(State Commitment)</h2><p>当大部分都转移到链下进行时,需要某种机制确保链下状态的更新得到确认,这样才能保证当有恶意行为发生时,主链可以保证用户不会受到损失。这就是为什么需要状态确认(State Commitment),即子链周期性地将状态更新情况提交到主链进行共识。</p><p>然而,将子链中的所有交易都同步到主链显然违反了 Plasma 的初衷,在 Plasma 中,实际向主链提交的是 Merkle Tree 的根哈希。因此子链中的实际交易情况被隐藏,在主链上只能看到子链区块的哈希值。</p><p>当有恶意行为发生时,子链网络中的所有用户都可以向主链提交防伪证明,证明成立后,含有恶意交易的区块将被回滚。</p><h2 id="防伪证明(Fraud-Proof)"><a href="#防伪证明(Fraud-Proof)" class="headerlink" title="防伪证明(Fraud Proof)"></a>防伪证明(Fraud Proof)</h2><p>Plasma 的一个关键设计之一就是允许用户构造防伪证明(Fraud Proof)。防伪证明的意义在于只要发布区块的节点构造了含有恶意交易的区块,那么就要承担被惩罚的风险。每当新的区块被提交到主链上时,会留出一段时间给用户提交防伪证明,如果在这段时间内没有证明被提交,则认为新的区块被验证合法。如果有防伪证明检测到区块中存在恶意交易,则该区块将被舍弃,回滚到上一个被验证合法的区块。Plasma 中的防伪证明主要有以下(但不限于)几种:</p><ul><li>资产可花费证明</li><li>交易签名有效性证明</li><li>存取款证明</li></ul><p>至于每种防伪证明的具体形式,则依赖于实际 Plasma 应用的实现细则。</p><p>如下图所示(来源自<a href="https://plasma.io/" target="_blank" rel="noopener">[1]</a>),子链中每个节点都存有 1-4 个区块的数据。假设区块 1-3 已经被验证合法,而区块 4 中存在恶意交易,那么每个节点都可以使用 1-4 个区块中的数据构造防伪证明提交到主链,主链验证后将子链中的状态回滚到区块 1-3。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/fraud-proofs.png" width="700" height="200" alt="Blockchains of Blockchain"></p><p>防伪证明还可以使用零知识证明(zk-SNARKs 或者 STARKs)来构造,但由于目前通过零知识证明生成证明的时间和空间还有待优化,目前设计的 Plasma 并不依赖零知识证明。零知识证明在 Plasma 中的应用是一个很有前景的研究方向,感兴趣的读者可以参考以太坊研究团队关于这方面的研究<a href="https://ethresear.ch/t/plasma-is-plasma/2195" target="_blank" rel="noopener">[2]</a>)。</p><h2 id="取款(Withdrawal)"><a href="#取款(Withdrawal)" class="headerlink" title="取款(Withdrawal)"></a>取款(Withdrawal)</h2><p>取款(Withdrawal),顾名思义,就是从子链中的资产取回到主链上,因此取款也被称为退出(Exit)。取款操作可以说是 Plasma 框架中最重要的一步,因为它确保用户可以安全地将子链上的资产转移到主链上。之前的存款以及状态确认步骤已经产生了一些交易数据,并在主链上得到同步,用户可以利用这些数据构造资产证明,之后执行简单取款(Simple Withdrawal)操作退出子链。当有扣留(Withholding)攻击发生(即子链上的矿工恶意扣留区块,意图双花攻击等)时,用户可能无法成功获取数据构造资产证明,这时需要执行批量取款(Mass Withdrawal)操作退出子链。</p><p>需要注意的是,当子链中有取款操作发生时,跟这个取款操作相关的账号或者交易都将被禁止。</p><h3 id="简单取款(Simple-Withdrawal)"><a href="#简单取款(Simple-Withdrawal)" class="headerlink" title="简单取款(Simple Withdrawal)"></a>简单取款(Simple Withdrawal)</h3><p>执行简单取款的条件是所要取回的资产已经在主链和子链上确认。</p><p>一个简单取款的执行主要有以下几个步骤:</p><ul><li>向主链上的 Plasma 智能合约发送已签名的取款交易。取款的数额可以包含多个输出(UTXO模型),但所有输出必须在同一个子链当中,而且每个输出的余额必须全部取出,不能只取出一部分。取款数额的一部分还需要被当作押金,作为恶意行为的惩罚。</li><li>当取款请求发送后,需要等待一段“争议期(Challenge Period)”,这期间其它节点可以构造证据证明该取款中的部分或全部数额已经被花费。如果争议期内有节点提供证明成功,那么取款被取消,而且押金被扣除。</li><li>接下来可能还要等待一段时间,直到所有区块高度较低的取款操作都完成。这是为了保证所有取款操作按照“先来后到”的顺序执行。</li><li>当争议期结束后,如果没有争议被提出,则认为取款操作成立,取款者将子链资产成功取回到主链。</li></ul><h3 id="快速取款(Fast-Withdrawal)"><a href="#快速取款(Fast-Withdrawal)" class="headerlink" title="快速取款(Fast Withdrawal)"></a>快速取款(Fast Withdrawal)</h3><p>快速取款(Fast Withdrawal)跟简单取款相比的差别主要是引入一个中间人,白皮书上称为 Liquidity Provider,这里简称为 LP。如果一个用户不想等待很长的争议期(目前的实现至少要一周),那么它可以选择从 LP 这里直接取款,只需要消耗一个交易确认的时间,代价是需要支付给 LP 一定的费用。由于 Plasma 白皮书上关于快速取款的描述太过晦涩,这里主要参考 kfichter 提出的 Simple Fast Withdrawal<a href="https://ethresear.ch/t/simple-fast-withdrawals/2128" target="_blank" rel="noopener">[3]</a> 来介绍快速取款是如何实现的。</p><p>为了实现快速取款,取款方和 LP 可以利用一个流动合约(liquidity contract)。假设取款方是 Alice,她想要执行快速取款将 10 以太币从子链转移到主链。她首先向流动合约发送 10 以太币,注意这里的交易是在子链上进行的。当这个交易被子链打包成区块后,Alice 可以调用合约中的某个退出函数,这时 Alice 将获取一个代表她这笔资产的 token。Bob 作为 LP,他检查了子链上数据之后证明 Alice 的取款没有问题之后愿意以 9 以太币的价格购买这个 token。Alice 将 token 卖给 Bob,获得了 9 以太币,Bob 赚取了 1 以太币。</p><p>需要注意的是,实现快速取款的前提条件是没有拜占庭行为发生,即没有扣留区块攻击发生,因为 LP 需要验证取款方的交易历史。</p><h3 id="批量取款(Mass-Withdrawal)"><a href="#批量取款(Mass-Withdrawal)" class="headerlink" title="批量取款(Mass Withdrawal)"></a>批量取款(Mass Withdrawal)</h3><p>当子链中有拜占庭行为(例如,区块扣留攻击)发生时,将会影响以后生成防伪证明,因此网络中的每个用户都有责任快速退出子链。虽然批量取款(Mass Withdrawal)操作不是必要选择,但当大量用户执行取款时很可能会造成主链拥塞,也会消耗更多的 gas,因此批量取款是当子链受到攻击时更好的选择。</p><p>批量取款操作由于所采用的模型(UTXO 模型或者账户模型)不同会有较大的差别,而且目前关于批量取款的操作细节也正在研讨当中,因此这里只对批量取款做简单介绍,想要了解目前研究状态可以参考<a href="https://ethresear.ch/t/basic-mass-exits-for-plasma-mvp/3316" target="_blank" rel="noopener">[4]</a>。</p><p>当子链中有拜占庭行为发生时,用户之间可以共同协作执行批量取款。这时会有一个节点扮演取款处理人(Exit Processor)的角色,简称为 EP,负责当前某个批量操作(可以同时有多个批量取款操作发生,但同一个取款申请不能存在于多个批量取款),并且可以收取服务费作为报酬。EP 将构造一个位图(bitmap,即一串0/1)记录哪些资产要执行取款。之后 EP 将利用现有的区块数据检查每个取款是否合法,之后将构造一个批量退出初始化交易(Mass Exit Initiation Transaction,MEIT),并将其发送到主链上。在 MEIT 被主链确认之前,每个用户都可以对这个交易提出异议。当争议期结束,MEIT 被主链确认,批量取款成功。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文主要对 Plasma 框架中一些关键操作进行了比较详细的介绍,但如果不依托于某个实际的 Plasma 项目,对一些细节还是很难理解。因此在后面的文章中将会介绍 Plasma MVP 以及 Plasma Cash。</p><h2 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h2><ol><li><a href="https://plasma.io/" target="_blank" rel="noopener">https://plasma.io/</a></li><li><a href="https://ethresear.ch/t/plasma-is-plasma/2195" target="_blank" rel="noopener">https://ethresear.ch/t/plasma-is-plasma/2195</a></li><li><a href="https://ethresear.ch/t/simple-fast-withdrawals/2128" target="_blank" rel="noopener">https://ethresear.ch/t/simple-fast-withdrawals/2128</a></li><li><a href="https://ethresear.ch/t/basic-mass-exits-for-plasma-mvp/3316" target="_blank" rel="noopener">https://ethresear.ch/t/basic-mass-exits-for-plasma-mvp/3316</a></li></ol><p>本文的作者是盖盖,他的微信公众号: chainlab</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p> 这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要对 Plasma 一些关键操作的细节进行剖析。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/categories/ethereum/Plasma/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/tags/Plasma/"/>
<category term="扩容" scheme="https://learnblockchain.cn/tags/%E6%89%A9%E5%AE%B9/"/>
</entry>
<entry>
<title>深入理解Plasma(一)Plasma 框架</title>
<link href="https://learnblockchain.cn/2018/10/20/plasma-framework/"/>
<id>https://learnblockchain.cn/2018/10/20/plasma-framework/</id>
<published>2018-10-20T07:54:17.000Z</published>
<updated>2018-12-06T01:46:09.787Z</updated>
<content type="html"><![CDATA[<p>这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章作为开篇,主要目的是理解 Plasma 框架。</p><a id="more"></a><p>Plasma 作为以太坊的二层扩容框架,自从 2017 年被 Joseph Poon(Lightning Network 创始人)和 Vitalik Buterin (Ethereum 创始人)提出以来<a href="http://plasma.io/plasma.pdf" target="_blank" rel="noopener">[1]</a>,一直是区块链从业人员关注的焦点<a href="https://ethresear.ch/c/plasma" target="_blank" rel="noopener">[2]</a>。首先需要明确的是,Plasma 实质上是一套框架,而不是一个单独的项目,它为各种不同的项目实际项目提供链下(off-chain)解决方案。这也是为什么很多人对 Plasma 感到疑惑的一个重要原因,因为在缺乏实际应用场景的情况下很难将 Plasma 解释清楚。<br>因此,理解 Plasma 是一套框架是理解 Plasma 的关键。</p><h2 id="从区块链扩容谈起"><a href="#从区块链扩容谈起" class="headerlink" title="从区块链扩容谈起"></a>从区块链扩容谈起</h2><p>在介绍 Plasma 之前,不得不先介绍区块链扩容。我们都知道,比特币(Bitcoin)和以太坊(Ethereum)作为目前最广泛使用的区块链平台,面临的最大问题就是可扩展性(Scalability)。这里需要注意的是,区块链中的可扩展性问题并不是单独特指某个问题,而是区块链想要实现 Web3.0<a href="https://medium.com/l4-media/making-sense-of-web-3-c1a9e74dcae" target="_blank" rel="noopener">[3]</a> 的愿景,为亿万用户提供去中心化服务所要克服的一系列挑战。虽然以太坊号称是“世界计算机”,但这台“计算机”却是单线程的,每秒钟只能处理大约 15 条交易,与目前主流的 Visa 和 MasterCard 动辄每秒上万的吞吐量相比实在相形见绌。因此如何在保证区块链安全性的情况下,提高可扩展性是目前区块链发展亟待解决的问题之一。</p><p>目前关于区块链扩容的解决方案无外乎两个方向:一层(Layer 1)扩容和二层(Layer 2)扩容<a href="https://blog.ethereum.org/2018/01/02/ethereum-scalability-research-development-subsidy-programs/" target="_blank" rel="noopener">[4]</a>。一层扩容也被称为链上(on-chain)扩容,顾名思义,这类扩容方案需要更改区块链底层协议。但同时也意味着需要将区块链硬分叉。这类扩容方案就像将原来的单核 CPU 改装成多核 CPU,从而可以多线程处理计算任务,提高整个网络的吞吐量。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/layer1.jpg" width="320" height="150" alt="Layer 1 扩容"></p><p>目前最典型的一层扩容方案是 Vitalik 和他的研究团队提出的“Sharding(分片)”,也就是说将区块链划分成不同的部分(shards),每个部分独立处理交易。想要了解更多关于 Sharding 的信息,可以参考以太坊官方的 Wiki<a href="https://github.com/ethereum/wiki/wiki/Sharding-FAQs" target="_blank" rel="noopener">[5]</a>。</p><p>二层扩容也称链下(off-chain)扩容,同样非常好理解,这种扩容方案不需要修改区块链底层协议,而是通过将大量、频繁的计算工作转移到“链下”完成,并定期或在需要时将链下的计算结果提交到“链上”保证其最终性(finality)。二层扩容的核心思想是将底层区块链作为共识基础,使用智能合约或者其它手段作为链下和链上沟通的桥梁,当有欺诈行为发生时链下的用户仍然可以回到链上的某一状态。虽然将计算转移到链下会在一段时间内损失最终性,但这个代价是值得的,因为这样做不止可以极大提高区块链的灵活性和可扩展性,也极大降低了用户进行交易所需要的代价。将计算转移到链下也并不意味着完全放弃安全性,因为最终的安全性还是由底层所依赖的区块链来保证,因此二层扩容主要关注的问题就在于如何保证链上链下切换过程的安全性。这种思想最早被用在闪电网络(Lightning Network)当中作为比特币的其中一个扩容方案,并取得了很好的效果。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/layer2.jpg" width="320" height="150" alt="Layer 2 扩容"></p><p>本文所要介绍的 Plasma 就属于基于以太坊二层扩容方案,类似的解决方案还有 <a href="https://medium.com/l4-media/generalized-state-channels-on-ethereum-de0357f5fb44" target="_blank" rel="noopener">State Channels</a> 和 <a href="https://truebit.io/" target="_blank" rel="noopener">Trubit</a>。这些方案虽然面向的问题有所区别,但基本思想都是将复杂的计算转移到链下进行。那么,接下来我们将进入 Plasma 的世界,一窥究竟!</p><h2 id="理解-Plasma"><a href="#理解-Plasma" class="headerlink" title="理解 Plasma"></a>理解 Plasma</h2><p>在前文中我们已经明白 Plasma 是一种二层扩容框架,那么该如何进一步理解 Plasma 是什么?它区别于其它二层扩容方案的地方在哪呢?</p><p>Plasma 也被称为“链中链(blockchains in blockchains)”。任何人都可以在底层区块链之上创建不同的 Plasma 支持不同的业务需求,例如分布式交易所、社交网络、游戏等。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/Blockchains-of-blockchain.png" width="600" height="230" alt="Blockchains of Blockchain"></p><p>这里可以举一个例子来理解 Plasma。假如企鹅公司创建了一个 Plasma Chain 叫作 Game Chain。用户通过向 Game Chain 发送一些以太币换取 Token,用于购买皮肤等游戏内的增值商品。加入 Game Chain 的操作是在链上进行的,以太坊区块链将这部分资产锁定,转移到 Game Chain 上。之后每次我们购买虚拟商品的交易就会转移到链下进行,由企鹅公司记账。这种方式几乎跟我们现实生活中游戏内购的体验一样,不仅结算迅速,而且手续费低廉(相比于以太坊之上需要给矿工支付的手续费)。那么问题来了,如果企鹅公司从中作祟,修改账本,恶意占有我们的资产怎么办?这时我们就可以提交之前每次交易的凭证到以太坊区块链上,如果确实是企鹅恶意篡改了账本,那么我们不但能够成功取回自己的资产,还能获得之前企鹅公司创建 Game Chain 存入的部分或全部押金。</p><p>通过上面这个例子是不是已经明白 Plasma 大致是如何工作的了?但上面这个例子还是过于简单,只涉及用户自己和企鹅公司。下面我们使用区块链的语言对上面的例子进行解析。</p><p>首先,企鹅公司在以太坊主链之上创建了一系列智能合约作为主链和子链 Game Chain 通信的媒介。这些智能合约中不仅规定了子链中的一些基本的状态转换规则(例如如何惩罚作恶的节点),也记录了子链中的状态(子链中区块的哈希值)。之后企鹅公司可以搭建自己的子链(可以用以太坊搭建一套私链)。子链实际上是一个完全独立的区块链,可以拥有专门的矿工,使用不同于主链的共识算法,例如 PoS(Proof of Stake)等。</p><p>当子链创建完毕后,企鹅公司可以使用 ERC721 合约创建 token 作为游戏内的商品(就像 Cryptokitty)。但这里需要注意的是,所有数字资产必须在以太坊主链上创建,并通过 Plasma 子链的智能合约转移到子链中。用户也需要在主链上购买数字资产后转移到子链上。在上面这个例子中,Game Chain 的智能合约将主链上的资产锁定,之后在子链上生成等值的资产。之后用户就可以完全脱离主链,在子链上进行交易。企鹅公司在子链上扮演 operator 的角色,如果一切运行正常,子链中的矿工会正常打包区块,并在需要时由 operator 将区块的哈希值提交到主链作为子链的状态更新证明。在这个过程中,用户完全不需要和主链交互。</p><p>我们可以看到,将复杂的计算操作转移到链下确实使得整个交易过程变得简单。但没有强大的共识算法和庞大的参与者,资产在子链上是很不安全的。Plasma 给了我们一种避险机制,即使 operator 作恶,我们也能取回属于自己的资产。下图(来源自<a href="https://plasma.io/" target="_blank" rel="noopener">[1]</a>)简单说明了这个过程。图中,在第 4 个区块中的交易被篡改。由于 Alice 本地保存有 Plasma Chain 中所有的区块数据,因此她可以向主链提交一个含有“防伪证明(Fraud Proof)”的交易。如果证明生效,那么主链将状态从 4 号区块回滚到 3 号区块,一切恢复正常。Plasmas Chain 中的参与者也可以随时提交资产证明,返回到主链。</p><p><img src="https://raw.githubusercontent.com/gitferry/mastering-ethereum/master/Plasma-in-depth/images/Fraud-proof.png" width="700" height="350" alt="Blockchains of Blockchain"></p><p>到这里我们应该已经理解了,<strong>Plasma 所要做的工作并不是保护子链的安全,而是当有安全事故发生时,保证用户可以安全地取回自己的资产,并返回到主链上。并且采用一系列经济激励的方式减少作恶情况的发生</strong>。</p><p>下一篇文章将对 Plasma 运行过程的细节进行剖析。</p><h2 id="相关资源"><a href="#相关资源" class="headerlink" title="相关资源"></a>相关资源</h2><ol><li><a href="https://plasma.io/" target="_blank" rel="noopener">https://plasma.io/</a></li><li><a href="https://ethresear.ch/c/plasma" target="_blank" rel="noopener">https://ethresear.ch/c/plasma</a></li><li><a href="https://medium.com/l4-media/making-sense-of-web-3-c1a9e74dcae" target="_blank" rel="noopener">https://medium.com/l4-media/making-sense-of-web-3-c1a9e74dcae</a></li><li><a href="https://blog.ethereum.org/2018/01/02/ethereum-scalability-research-development-subsidy-programs/" target="_blank" rel="noopener">https://blog.ethereum.org/2018/01/02/ethereum-scalability-research-development-subsidy-programs/</a></li><li><a href="https://github.com/ethereum/wiki/wiki/Sharding-FAQs" target="_blank" rel="noopener">https://github.com/ethereum/wiki/wiki/Sharding-FAQs</a></li><li><a href="https://medium.com/l4-media/making-sense-of-ethereums-layer-2-scaling-solutions-state-channels-plasma-and-truebit-22cb40dcc2f4" target="_blank" rel="noopener">https://medium.com/l4-media/making-sense-of-ethereums-layer-2-scaling-solutions-state-channels-plasma-and-truebit-22cb40dcc2f4</a></li><li><a href="https://medium.com/@argongroup/ethereum-plasma-explained-608720d3c60e" target="_blank" rel="noopener">https://medium.com/@argongroup/ethereum-plasma-explained-608720d3c60e</a></li></ol><p>本文的作者是盖盖,他的微信公众号: chainlab</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章作为开篇,主要目的是理解 Plasma 框架。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/categories/ethereum/Plasma/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="Plasma" scheme="https://learnblockchain.cn/tags/Plasma/"/>
<category term="扩容" scheme="https://learnblockchain.cn/tags/%E6%89%A9%E5%AE%B9/"/>
</entry>
<entry>
<title>理解开发HD 钱包涉及的 BIP32、BIP44、BIP39</title>
<link href="https://learnblockchain.cn/2018/09/28/hdwallet/"/>
<id>https://learnblockchain.cn/2018/09/28/hdwallet/</id>
<published>2018-09-28T08:17:41.000Z</published>
<updated>2018-12-12T11:19:17.327Z</updated>
<content type="html"><![CDATA[<p>如果你还在被HD钱包、BIP32、BIP44、BIP39搞的一头雾水,来看看这边文章吧。</p><a id="more"></a><h2 id="数字钱包概念"><a href="#数字钱包概念" class="headerlink" title="数字钱包概念"></a>数字钱包概念</h2><p>钱包用来存钱的,在区块链中,我们的数字资产都会对应到一个账户地址上, 只有拥有账户的钥匙(私钥)才可以对资产进行消费(用私钥对消费交易签名)。<br>私钥和地址的关系如下:<img src="/images/9efa20eff737374479d9c6bb86db82b3.png" alt=""><br>(图来自精通比特币)<br>一句话概括下就是:<strong>私钥通过椭圆曲线生成公钥, 公钥通过哈希函数生成地址,这两个过程都是单向的。</strong></p><p>因此实际上,数字钱包实际是一个管理私钥(生成、存储、签名)的工具,注意钱包并不保存资产,资产是在链上的。</p><h2 id="如何创建账号"><a href="#如何创建账号" class="headerlink" title="如何创建账号"></a>如何创建账号</h2><p>创建账号关键是生成一个私钥, 私钥是一个32个字节的数, <strong>生成一个私钥在本质上在1到2^256之间选一个数字</strong>。<br>因此生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源,只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。</p><p>比如可以掷硬币256次,用纸和笔记录正反面并转换为0和1,随机得到的256位二进制数字可作为钱包的私钥。</p><p>从编程的角度来看,一般是通过在一个密码学安全的随机源(不建议大家自己去写一个随机数)中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。</p><blockquote><p>实际过程需要比较下是否小于n-1(n = 1.158 * 10^77, 略小于2^256),我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。这样得到的私钥就可以根据上面的方法进一步生成公钥及地址。</p></blockquote><h2 id="BIP32"><a href="#BIP32" class="headerlink" title="BIP32"></a>BIP32</h2><p>钱包也是一个私钥的容器,按照上面的方法,我们可以生成一堆私钥(一个人也有很多账号的需求,可以更好保护隐私),而每个私钥都需要备份就特别麻烦的。</p><blockquote><p>最早期的比特币钱包就是就是这样,还有一个昵称:“Just a Bunch Of Keys(一堆私钥)“</p></blockquote><p>为了解决这种麻烦,就有了<a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki" target="_blank" rel="noopener">BIP32 提议</a>: 根据一个随机数种子通过分层确定性推导的方式得到n个私钥,这样保存的时候,只需要保存一个种子就可以,私钥可以推导出来,如图:</p><p><img src="/images/450b5358b96ef5b32ec775efed901f2a.png" alt=""><br>(图来自精通比特币)上图中的孙秘钥就可以用来签发交易。</p><blockquote><p>补充说明下 BIP: Bitcoin Improvement Proposals 比特币改进建议, bip32是第32个改进建议。<br>BIP32提案的名字是:Hierarchical Deterministic Wallets, 就是我们所说的HD钱包。</p></blockquote><p>来分析下这个分层推导的过程,第一步推导主秘钥的过程:<br><img src="/images/3ec7468aa49d907b0ec66b5d8b41a0a1.png" alt=""></p><p>根种子输入到HMAC-SHA512算法中就可以得到一个可用来创造主私钥(m) 和 一个主链编码( a master chain code)这一步生成的秘钥(由私钥或公钥)及主链编码再加上一个索引号,将作为HMAC-SHA512算法的输入继续衍生出下一层的私钥及链编码,如下图:<img src="/images/a9a6e6a31f39e812f579a4c8bdf09347.png" alt=""></p><blockquote><p>衍生推导的方案其实有两个:一个用父私钥推导(称为强化衍生方程),一个用父公钥推导。同时为了区分这两种不同的衍生,在索引号也进行了区分,索引号小于2^31用于常规衍生,而2^31到2^32-1之间用于强化衍生,为了方便表示索引号i’,表示2^31+i。</p></blockquote><p>因此增加索引(水平扩展)及 通过子秘钥向下一层(深度扩展)可以无限生成私钥。</p><p>注意, 这个推导过程是确定(相同的输入,总是有相同的输出)也是单向的,子密钥不能推导出同层级的兄弟密钥,也不能推出父密钥。如果没有子链码也不能推导出孙密钥。现在我们已经对分层推导有了认识。</p><p>一句话概括下BIP32就是:<strong>为了避免管理一堆私钥的麻烦提出的分层推导方案。</strong></p><h3 id="秘钥路径及BIP44"><a href="#秘钥路径及BIP44" class="headerlink" title="秘钥路径及BIP44"></a>秘钥路径及BIP44</h3><p>通过这种分层(树状结构)推导出来的秘钥,通常用路径来表示,每个级别之间用斜杠 / 来表示,由主私钥衍生出的私钥起始以“m”打头。因此,第一个母密钥生成的子私钥是m/0。第一个公共钥匙是M/0。第一个子密钥的子密钥就是m/0/1,以此类推。</p><p>BIP44则是为这个路径约定了一个规范的含义(也扩展了对多币种的支持),BIP0044指定了包含5个预定义树状层级的结构:<br><code>m / purpose' / coin' / account' / change / address_index</code><br>m是固定的, Purpose也是固定的,值为44(或者 0x8000002C)<br><strong>Coin type</strong><br>这个代表的是币种,0代表比特币,1代表比特币测试链,60代表以太坊<br>完整的币种列表地址:<a href="https://github.com/satoshilabs/slips/blob/master/slip-0044.md" target="_blank" rel="noopener">https://github.com/satoshilabs/slips/blob/master/slip-0044.md</a><br><strong>Account</strong><br>代表这个币的账户索引,从0开始<br><strong>Change</strong><br>常量0用于外部链,常量1用于内部链(也称为更改地址)。外部链用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。 (所以一般使用0)<br><strong>address_index</strong><br>这就是地址索引,从0开始,代表生成第几个地址,官方建议,每个account下的address_index不要超过20</p><p>根据 <a href="https://github.com/ethereum/EIPs/issues/85" target="_blank" rel="noopener">EIP85提议的讨论</a>以太坊钱包也遵循BIP44标准,确定路径是<code>m/44'/60'/a'/0/n</code><br>a 表示帐号,n 是第 n 生成的地址,60 是在 <a href="https://github.com/satoshilabs/slips/blob/master/slip-0044.md" target="_blank" rel="noopener">SLIP44 提案</a>中确定的以太坊的编码。所以我们要开发以太坊钱包同样需要对比特币的钱包提案BIP32、BIP39有所了解。</p><p>一句话概括下BIP44就是:<strong>给BIP32的分层路径定义规范</strong></p><h2 id="BIP39"><a href="#BIP39" class="headerlink" title="BIP39"></a>BIP39</h2><p>BIP32 提案可以让我们保存一个随机数种子(通常16进制数表示),而不是一堆秘钥,确实方便一些,不过用户使用起来(比如冷备份)也比较繁琐,这就出现了<a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki" target="_blank" rel="noopener">BIP39</a>,它是使用助记词的方式,生成种子的,这样用户只需要记住12(或24)个单词,单词序列通过 PBKDF2 与 HMAC-SHA512 函数创建出随机种子作为 BIP32 的种子。</p><p>可以简单的做一个对比,下面那一种备份起来更友好:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 随机数种子</span><br><span class="line">090ABCB3A6e1400e9345bC60c78a8BE7 </span><br><span class="line">// 助记词种子</span><br><span class="line">candy maple cake sugar pudding cream honey rich smooth crumble sweet treat</span><br></pre></td></tr></table></figure></p><p>使用助记词作为种子其实包含2个部分:助记词生成及助记词推导出随机种子,下面分析下这个过程。</p><h3 id="生成助记词"><a href="#生成助记词" class="headerlink" title="生成助记词"></a>生成助记词</h3><p>助记词生成的过程是这样的:先生成一个128位随机数,再加上对随机数做的校验4位,得到132位的一个数,然后按每11位做切分,这样就有了12个二进制数,然后用每个数去查<a href="https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md" target="_blank" rel="noopener">BIP39定义的单词表</a>,这样就得到12个助记词,这个过程图示如下:</p><p><img src="/images/71c0af9474a51296096c3c806ca8f1a1.png" alt=""><br>(图来源于网络)</p><p>下面是使用bip39生成生成助记词的一段代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> bip39 = <span class="built_in">require</span>(<span class="string">'bip39'</span>)</span><br><span class="line"><span class="comment">// 生成助记词</span></span><br><span class="line"><span class="keyword">var</span> mnemonic = bip39.generateMnemonic()</span><br><span class="line"><span class="built_in">console</span>.log(mnemonic)</span><br></pre></td></tr></table></figure><h3 id="助记词推导出种子"><a href="#助记词推导出种子" class="headerlink" title="助记词推导出种子"></a>助记词推导出种子</h3><p>这个过程使用密钥拉伸(Key stretching)函数,被用来增强弱密钥的安全性,PBKDF2是常用的密钥拉伸算法中的一种。<br>PBKDF2基本原理是通过一个为随机函数(例如 HMAC 函数),把助记词明文和盐值作为输入参数,然后重复进行运算最终产生生成一个更长的(512 位)密钥种子。这个种子再构建一个确定性钱包并派生出它的密钥。</p><p>密钥拉伸函数需要两个参数:助记词和盐。盐可以提高暴力破解的难度。 盐由常量字符串 “mnemonic” 及一个可选的密码组成,注意使用不同密码,则拉伸函数在使用同一个助记词的情况下会产生一个不同的种子,这个过程图示图下:</p><p><img src="/images/d37f78f8f2d859369d99fc5e0a76c184.png" alt=""><br>(图来源于网络)</p><p>同样代码来表示一下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> hdkey = <span class="built_in">require</span>(<span class="string">'ethereumjs-wallet/hdkey'</span>)</span><br><span class="line"><span class="keyword">var</span> util = <span class="built_in">require</span>(<span class="string">'ethereumjs-util'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> seed = bip39.mnemonicToSeed(mnemonic, <span class="string">"pwd"</span>);</span><br><span class="line"><span class="keyword">var</span> hdWallet = hdkey.fromMasterSeed(seed);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> key1 = hdWallet.derivePath(<span class="string">"m/44'/60'/0'/0/0"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"私钥:"</span>+util.bufferToHex(key1._hdkey._privateKey));</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> address1 = util.pubToAddress(key1._hdkey._publicKey, <span class="literal">true</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"地址:"</span>+util.bufferToHex(address1));</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"校验和地址:"</span>+ util.toChecksumAddress(address1.toString(<span class="string">'hex'</span>)));</span><br></pre></td></tr></table></figure><p>校验和地址是<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md" target="_blank" rel="noopener">EIP-55</a>中定义的对大小写有要求的一种地址形式。</p><p>密码可以作为一个额外的安全因子来保护种子,即使助记词的备份被窃取,也可以保证钱包的安全(也要求密码拥有足够的复杂度和长度),不过另外一方面,如果我们忘记密码,那么将无法恢复我们的数字资产。</p><p>一句话概括下BIP39就是:<strong>通过定义助记词让种子的备份更友好</strong></p><p>我为大家录制了一个视频:<strong><a href="https://ke.qq.com/course/356068?tuin=bd898bbf" target="_blank" rel="noopener">以太坊去中心化网页钱包开发</a></strong>,从如何创建账号开始,深入探索BIP32、BIP44、BIP39等提案,以及如何存储私钥、发送离线签名交易和Token。</p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>HD钱包(Hierarchical Deterministic Wallets)是在BIP32中提出的为了避免管理一堆私钥的麻烦提出的分层推导方案。<br>而BIP44是给BIP32的分层增强了路径定义规范,同时增加了对多币种的支持。<br>BIP39则通过定义助记词让种子的备份更友好。</p><p>目前我们的市面上单到的以太币、比特币钱包基本都遵循这些标准。</p><p>最后推荐一个<a href="https://iancoleman.io/bip39/" target="_blank" rel="noopener">助记词秘钥生成器网站</a></p><p>欢迎来<a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">知识星球</a>提问,星球内已经聚集了300多位区块链技术爱好者。<br><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>如果你还在被HD钱包、BIP32、BIP44、BIP39搞的一头雾水,来看看这边文章吧。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="比特币" scheme="https://learnblockchain.cn/tags/%E6%AF%94%E7%89%B9%E5%B8%81/"/>
<category term="以太坊" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A/"/>
<category term="钱包" scheme="https://learnblockchain.cn/tags/%E9%92%B1%E5%8C%85/"/>
</entry>
<entry>
<title>如何使用Web3.js API 在页面中进行转账</title>
<link href="https://learnblockchain.cn/2018/09/12/web3-sendeth/"/>
<id>https://learnblockchain.cn/2018/09/12/web3-sendeth/</id>
<published>2018-09-12T09:22:34.000Z</published>
<updated>2018-12-06T01:46:09.817Z</updated>
<content type="html"><![CDATA[<p>本文介绍如何使用Web3.js API 在页面中进行转账,是我翻译的文档<a href="https://web3.learnblockchain.cn/0.2x.x/" target="_blank" rel="noopener">Web3.js 0.2x 中文版</a> 及 <a href="https://wiki.learnblockchain.cn/course/dapp.html" target="_blank" rel="noopener">区块链全栈-以太坊DAPP开发实战</a> 中Demo的文章说明。</p><a id="more"></a><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>阅读本文前,你应该对以太坊、智能合约、钱包的概念有所了解,如果你还不了解,建议你先看<a href="https://learnblockchain.cn/2017/11/20/whatiseth/">以太坊是什么</a><br>除此之外,你最好还了解一些HTML及JavaScript知识。</p><h2 id="转账UI-页面的编写"><a href="#转账UI-页面的编写" class="headerlink" title="转账UI 页面的编写"></a>转账UI 页面的编写</h2><p>转账UI主体的界面如图:<br><img src="/images/web3_sendeth_ui.jpg" alt=""></p><p>实现这个界面很简单,这里就不代码了。大家可以打开<a href="https://web3.learnblockchain.cn/transDemo.html" target="_blank" rel="noopener">Demo</a>,右击查看页面源码。</p><h2 id="用户环境检查"><a href="#用户环境检查" class="headerlink" title="用户环境检查"></a>用户环境检查</h2><p>既然需要使用Web3.js API 在页面中进行转账, 首先应该检查在浏览器环境有没有安装好钱包,并且钱包应该是解锁状态。</p><ol><li>先检查是否安装了MetaMask钱包:</li></ol><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">window</span>.addEventListener(<span class="string">'load'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">typeof</span> web3 !== <span class="string">'undefined'</span>) {</span><br><span class="line"> web3 = <span class="keyword">new</span> Web3(web3.currentProvider);</span><br><span class="line"> <span class="keyword">if</span> (web3.currentProvider.isMetaMask == <span class="literal">true</span>) {</span><br><span class="line"> <span class="comment">// "MetaMask可用"</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// "非MetaMask环境"</span></span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> $(<span class="string">"#env"</span>).html(<span class="string">"No web3? 需要安装<a href='https://metamask.io/'>MetaMask</a>!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>MetaMask推荐在window加载时,进行MetaMask的检查,当然在没有安装MetaMask时,也可以指定一个节点Provider来创建web3,可以参考<a href="https://web3.learnblockchain.cn/0.2x.x/#web3" target="_blank" rel="noopener">Web3.js 文档引入web3</a></p><ol><li>检查是否钱包已经解锁:<br>我们在发送交易之前应该先首先检查一下当前钱包的一个状态,检查钱包是否解锁(是否输入了密码进入了MetaMask),通常使用eth下面的getAccounts来进行检查,getAccounts是会返回账号的一个列表,如果当前账号列表里面有数据的话,说明钱包已经解锁可以获得到账号,如果账号拿到的列表是空的话,那么说明钱包没有解锁。</li></ol><p>可以把下面的代码加到上面的监听函数中:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">web3.eth.getAccounts(<span class="function"><span class="keyword">function</span> (<span class="params">err, accounts</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (accounts.length == <span class="number">0</span>) {</span><br><span class="line"> $(<span class="string">"#account"</span>).html(<span class="string">"请检查钱包是否解锁"</span>);</span><br><span class="line"> } </span><br><span class="line"> });</span><br></pre></td></tr></table></figure><h2 id="发送交易"><a href="#发送交易" class="headerlink" title="发送交易"></a>发送交易</h2><p>如果MetaMask钱包是解锁的,我们就可以来发送交易,发送交易使用<a href="https://web3.learnblockchain.cn/0.2x.x/web3.eth/#web3ethsendtransaction" target="_blank" rel="noopener">sendtransaction</a>这个方法。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">web3.eth.sendTransaction(transactionObject [, callback])</span><br></pre></td></tr></table></figure><p>第二个参数是回调函数用来获得发送交易的Hash值。</p><p>第一个参数是一个交易对象,交易对象里面有几个字段:</p><ul><li>from : 就是从哪个账号发送金额</li><li>to : 发动到到哪个账号</li><li>value 是发送的金额</li><li>gas: 设置gas limit</li><li>gasPrice: 设置gas 价格</li></ul><p>如果from没有的话,他就会用当前的默认账号, 如果是转账to和value是必选的两个字段。<br>在发送交易的时候弹出来MetaMask的一个授权的窗口,如果我们gas和gasPrice没有设置的话,就可以在MetaMask里面去设置。如果这两个gas和gas Price设置了的话,MetaMask就会使用我们设置的gas。</p><p>因此在发送交易的时候,关键是构造这样一个交易对象,JavaScrpt代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这里使用Metamask 给的gas Limit 及 gas 价</span></span><br><span class="line"><span class="keyword">var</span> fromAccount = $(<span class="string">'#fromAccount'</span>).val();</span><br><span class="line"><span class="keyword">var</span> toAccount = $(<span class="string">'#toAccount'</span>).val();</span><br><span class="line"><span class="keyword">var</span> amount = $(<span class="string">'#amount'</span>).val();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对输入的数字做一个检查</span></span><br><span class="line"><span class="keyword">if</span> (web3.isAddress(fromAccount) &&</span><br><span class="line"> web3.isAddress(toAccount) &&</span><br><span class="line"> amount != <span class="literal">null</span> && amount.length > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">var</span> message = {<span class="attr">from</span>: fromAccount, <span class="attr">to</span>:toAccount, <span class="attr">value</span>: web3.toWei(amount, <span class="string">'ether'</span>)};</span><br><span class="line"></span><br><span class="line"> web3.eth.sendTransaction(message, (err, res) => {</span><br><span class="line"> <span class="keyword">var</span> output = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">if</span> (!err) {</span><br><span class="line"> output += res;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> output = <span class="string">"Error"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>补充说明:<code>$('#fromAccount').val()</code>是使用JQuery用来获取用户输入内容,其次应该在实际构造发送交易之前对输入的参数做一个判断,<code>web3.isAddress</code>用来检查字符串是不是地址。另外对于一个向普通外部地址账号的转账,消耗的gas 是固定的21000。</p><h3 id="运行测试"><a href="#运行测试" class="headerlink" title="运行测试"></a>运行测试</h3><p>需要注意一点的是,由于安全原因,MetaMask只支持站点方式访问的页面,即通过http:// 来访问页面,在浏览器中通过file:// + 文件地址的方式是不行的。<br>因此需要把编写的代码放置到web服务器的目录下,自己试验下。</p><p>线上的Demo地址为<a href="https://web3.learnblockchain.cn/transDemo.html" target="_blank" rel="noopener">https://web3.learnblockchain.cn/transDemo.html</a></p><p>想好好系统学习以太坊DApp开发,这门视频课程<a href="https://wiki.learnblockchain.cn/course/dapp.html" target="_blank" rel="noopener">以太坊DAPP开发实战</a>不容错过。</p><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。<br>深入浅出区块链<a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">知识星球</a>,最专业的区块链问题技术社区,欢迎加入,作为星友福利,星友还可以加入我创建优质区块链技术群,群内聚集了300多位区块链技术大牛和爱好者。</p>]]></content>
<summary type="html">
<p>本文介绍如何使用Web3.js API 在页面中进行转账,是我翻译的文档<a href="https://web3.learnblockchain.cn/0.2x.x/" target="_blank" rel="noopener">Web3.js 0.2x 中文版</a> 及 <a href="https://wiki.learnblockchain.cn/course/dapp.html" target="_blank" rel="noopener">区块链全栈-以太坊DAPP开发实战</a> 中Demo的文章说明。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Dapp" scheme="https://learnblockchain.cn/categories/ethereum/Dapp/"/>
<category term="Dapp入门" scheme="https://learnblockchain.cn/tags/Dapp%E5%85%A5%E9%97%A8/"/>
<category term="Web3.js" scheme="https://learnblockchain.cn/tags/Web3-js/"/>
</entry>
<entry>
<title>程序员如何切入区块链去中心化应用开发</title>
<link href="https://learnblockchain.cn/2018/08/31/devDapp/"/>
<id>https://learnblockchain.cn/2018/08/31/devDapp/</id>
<published>2018-08-31T03:30:55.000Z</published>
<updated>2018-12-06T01:46:09.765Z</updated>
<content type="html"><![CDATA[<p>前段时间一个以太坊游戏应用:<a href="http://exitscam.me/play" target="_blank" rel="noopener">Fomo3D</a>异常火爆,在短短的几天内就吸引了几万的以太币投入游戏,第一轮游戏一个“黑客”用了一个非常巧妙的利用以太坊规则成为了最终赢家,拿走了1万多以太币奖金。</p><p>区块链应用的价值由这个游戏反映的淋漓尽致,Fomo3D游戏能够成功核心所依赖的是以太坊提供的一个可信、不可篡改平台。当游戏的规则确定之后,一切都按规则运行,无人可干预。今天这篇就来介绍一下程序员如何切入去中心化应用开发。</p><a id="more"></a><h2 id="中心化应用"><a href="#中心化应用" class="headerlink" title="中心化应用"></a>中心化应用</h2><p>作为对比,先来看看中心化应用,其实就是现有的互联网应用,为什么它是中心化应用,看看它的架构图:<br><img src="/images/capp.jpg" alt=""></p><p>平时我们接触的是应用的前端(或称客户端),前端可以是HTML5的web页面、 小程序、APP, 在前端展现的内容通常发送一个请求到服务器,服务器返回相应的内容给前端。在前端的动作同样也会转化请求发送到服务器,服务器处理之后返回数据到前端。也就是说我们所有看到的内容或者操作都是中心化的服务器控制,因此说是中心化应用。</p><h2 id="去中心化应用DAPP"><a href="#去中心化应用DAPP" class="headerlink" title="去中心化应用DAPP"></a>去中心化应用DAPP</h2><p>而去中心化应用有什么不同呢? 看看它的架构图:<br><img src="/images/dapp.jpg" alt=""><br>前端的表现上是一样的, 还是H5页面、 小程序、APP,DAPP和传统App关键是后端部分不同,是后端不再是一个中心化的服务器,而是分布式网络上任意节点,注意可以是 <strong>任意一个节点</strong>,在应用中给节点发送的请求通常称为 <strong>交易</strong>,交易和中心化下的请求有几个很大的不同是:交易的数据经过用户个人签名之后发送到节点,节点收到交易请求之后,会把 <strong>请求广播到整个网络</strong>,交易在网络达成共识之后,才算是真正的执行(真正其作用的执行不一是连接的后端节点,尽管后端也会执行)。以及中心化下的请求大多数都是同步的(及时拿到结果), 而交易大多数是异步的,这也是在开发去中心应用时需要注意的地方,</p><p>从节点上获得数据状态(比如交易的结果),一般是通过事件回调来获得。</p><h2 id="如何开发"><a href="#如何开发" class="headerlink" title="如何开发"></a>如何开发</h2><p>在开发中心化应用最重要两部分是 <strong>客户端UI表现</strong>和 <strong>后端服务程序</strong>, UI表现通过HTTP请求连接到后端服务程序,后端服务程序运行在服务器上,比如Nginx Apached等等。</p><p>开发一个去中心化应用最重要也是两部分: <strong>客户端UI表现</strong>及 <strong>智能合约</strong>,智能合约的作用就像后端服务程序,智能合约是运行在节点的EVM上, 客户端调用智能合约,是通过向节点发起RPC请求完成。</p><p>下面是一个对比:</p><pre><code> 客户端UI <=> 客户端UI HTTP <=> RPC 后端服务程序 <=> 智能合约Nginx/Apache <=> 节点</code></pre><p>因此对于去中心化应用来说,程序员可以从两个方面切入:</p><p>一个是 <strong>去中心化应用的客户端开发</strong>, 熟悉已经熟悉客户端软件(如Web\APP等)开发的同学,只需要了解一下客户端跟区块链节点通信的API接口,如果是在当前应用最广泛的区块链平台以太坊上开发去中心化应用,那么需要了解Web3<br>这个库,Web3对节点暴露出来的JSON-RPC接口进行了封装,比如Web3提供的功能有:获取节点状态,获取账号信息,调用合约、监听合约事件等等。</p><p>目前的主流语言都有Web3的实现,列举一些实现给大家参考:</p><ul><li><a href="https://github.com/ethereum/web3.js" target="_blank" rel="noopener">JavaScript Web3.js</a></li><li><a href="https://github.com/ethereum/web3.py" target="_blank" rel="noopener">Python Web3.py</a></li><li><a href="https://github.com/airalab/hs-web3" target="_blank" rel="noopener">Haskell hs-web3</a></li><li><a href="https://github.com/web3j/web3j" target="_blank" rel="noopener">Java web3j</a></li><li><a href="https://github.com/mslinn/web3j-scala" target="_blank" rel="noopener">Scala web3j-scala</a></li><li><a href="https://github.com/f-o-a-m/purescript-web3" target="_blank" rel="noopener">Purescript purescript-web3</a></li><li><a href="https://github.com/sc0Vu/web3.php" target="_blank" rel="noopener">PHP web3.php</a></li><li><a href="https://github.com/digitaldonkey/ethereum-php" target="_blank" rel="noopener">PHP ethereum-php</a></li></ul><p>另一个切入点是 <strong>智能合约的开发</strong>,在以太坊现在推荐的语言是Solidity,有一些同学对新学一门语言有一些畏惧,Solidity的语法其实很简洁,有过一两门其他语言基础(开发经验)的同学三五天就可以学会,我也录制了一个视频课程:<a href="https://wiki.learnblockchain.cn/course/solidity.html" target="_blank" rel="noopener">深入详解以太坊智能合约语言Solidity</a>。</p><p>下面用一个Hello合约,体会下Solidity的语法:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">contract Hello {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">hello</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">returns</span>(<span class="params">string</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello World"</span>; </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果把上面的contract关键字更改为class,就和其他语言定义一个类一样。</p><p>有兴趣的同学可以进一步学习一下这个DApp开发案例<a href="https://learnblockchain.cn/2018/04/15/web3-html/">Web3与智能合约交互实战</a>,</p><p>在DAPP的开发过程中,一些开发工具可以帮助我们事半功倍,如:Truffle开发框架以及Ganache工具来模拟节点等,这篇文章<a href="https://learnblockchain.cn/2018/01/12/first-dapp/">一步步教你开发、部署第一个去中心化应用</a></p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p>对于想切入到去中心化应用开发的同学,对区块链运行的原理了解肯定会是加分项,尤其是各类共识机制(<a href="https://learnblockchain.cn/2017/11/04/bitcoin-pow/">POW</a>,POS,DPOS等)的理解,P2P网络的理解,以及各类加密和Hash算法的运用。有一些同学想做区块链底层开发,对区块链运行的原理则是必须项。</p><p>欢迎来<a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">知识星球</a>提问,星球内已经聚集了300多位区块链技术爱好者。<br><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>前段时间一个以太坊游戏应用:<a href="http://exitscam.me/play" target="_blank" rel="noopener">Fomo3D</a>异常火爆,在短短的几天内就吸引了几万的以太币投入游戏,第一轮游戏一个“黑客”用了一个非常巧妙的利用以太坊规则成为了最终赢家,拿走了1万多以太币奖金。</p>
<p>区块链应用的价值由这个游戏反映的淋漓尽致,Fomo3D游戏能够成功核心所依赖的是以太坊提供的一个可信、不可篡改平台。当游戏的规则确定之后,一切都按规则运行,无人可干预。今天这篇就来介绍一下程序员如何切入去中心化应用开发。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Dapp" scheme="https://learnblockchain.cn/categories/ethereum/Dapp/"/>
<category term="Dapp入门" scheme="https://learnblockchain.cn/tags/Dapp%E5%85%A5%E9%97%A8/"/>
<category term="以太坊概念" scheme="https://learnblockchain.cn/tags/%E4%BB%A5%E5%A4%AA%E5%9D%8A%E6%A6%82%E5%BF%B5/"/>
</entry>
<entry>
<title>如何理解以太坊ABI - 应用程序二进制接口</title>
<link href="https://learnblockchain.cn/2018/08/09/understand-abi/"/>
<id>https://learnblockchain.cn/2018/08/09/understand-abi/</id>
<published>2018-08-09T09:08:39.000Z</published>
<updated>2018-12-06T01:46:09.814Z</updated>
<content type="html"><![CDATA[<p>很多同学不是很明白以太坊ABI是什么,他的作用是什么,读完本文就明白了。</p><a id="more"></a><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>阅读本文前,你应该对以太坊、智能合约有所了解,<br>如果你还不了解,建议你先看<a href="https://learnblockchain.cn/2017/11/20/whatiseth/">以太坊是什么</a>,也可以观看我们的视频:<a href="https://wiki.learnblockchain.cn/course/beginner.html" target="_blank" rel="noopener">零基础搞懂区块链</a>和<a href="https://wiki.learnblockchain.cn/course/solidity.html" target="_blank" rel="noopener">深入详解以太坊智能合约语言Solidity</a>, 可以系统全面学习理解以太坊、智能合约。</p><h2 id="ABI-是什么"><a href="#ABI-是什么" class="headerlink" title="ABI 是什么"></a>ABI 是什么</h2><p>ABI 全称是 Application Binary Interface,翻译过来就是:应用程序二进制接口,简单来说就是 以太坊的调用合约时的接口说明。还不是很理解,没关系。</p><h2 id="调用合约函数发生了什么"><a href="#调用合约函数发生了什么" class="headerlink" title="调用合约函数发生了什么"></a>调用合约函数发生了什么</h2><p>从外部施加给以太坊的行为都称之为向以太坊网络提交了一个交易, 调用合约函数其实是向合约地址(账户)提交了一个交易,这个交易有一个附加数据,这个附加的数据就是ABI的编码数据。<br><img src="/images/abi1.jpg" alt=""></p><blockquote><p>比特币的交易也可以附加数据,以太坊革命性的地方就是能把附加数据转化为都函数的执行。</p></blockquote><p>因此要想和合约交互,就离不开ABI数据。</p><h3 id="演示调用函数"><a href="#演示调用函数" class="headerlink" title="演示调用函数"></a>演示调用函数</h3><p>以下面以个最简单的合约为例,我们看看用参数 1 调用<code>set(uint x)</code>,这个交易附带的数据是什么。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.0</span>;</span><br><span class="line"></span><br><span class="line">contract SimpleStorage {</span><br><span class="line"> </span><br><span class="line"> uint storedData;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">uint x</span>) <span class="title">public</span> </span>{</span><br><span class="line"> storedData = x;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">get</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">constant</span> <span class="title">returns</span> (<span class="params">uint</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> storedData;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然第一步需要先把合约部署到以太坊网络(其实部署也是一个)上,然后用 “1” 作为参数调用set,如下图:<br><img src="/images/abi2.jpg" alt=""></p><p>然后我们打开etherscan查看<a href="https://ropsten.etherscan.io/tx/0xd773a6909808f99c5a26c0c890af8b0bb6d784f29a3af55e04fa35d44d7716e2" target="_blank" rel="noopener">交易详情数据</a>, 可以看到其附加数据如下图:<br><img src="/images/abi3.jpg" alt=""></p><p>这个数据就是ABI的编码数据:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0x60fe47b10000000000000000000000000000000000000000000000000000000000000001</span><br></pre></td></tr></table></figure></p><h2 id="ABI-编码分析"><a href="#ABI-编码分析" class="headerlink" title="ABI 编码分析"></a>ABI 编码分析</h2><p>我把上面交易的附加数据拷贝出来分析一下,这个数据可以分成两个子部分:</p><ul><li><p>函数选择器(4字节)<br>0x60fe47b1</p></li><li><p>第一个参数(32字节)<br>00000000000000000000000000000000000000000000000000000000000000001</p></li></ul><p>函数选择器值 实际是对函数签名字符串进行sha3(keccak256)哈希运算之后,取前4个字节,用代码表示就是:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bytes4(sha3(“set(uint256)”)) == <span class="number">0x60fe47b1</span></span><br></pre></td></tr></table></figure><p>参数部分则是使用对应的16进制数。</p><p>现在就好理解 附加数据怎么转化为对应的函数调用。</p><h2 id="ABI-编码函数"><a href="#ABI-编码函数" class="headerlink" title="ABI 编码函数"></a>ABI 编码函数</h2><p>那么怎么获得函数对应的ABI 数据呢, 有两种方法:</p><h3 id="Solidity-ABI-编码函数"><a href="#Solidity-ABI-编码函数" class="headerlink" title="Solidity ABI 编码函数"></a>Solidity ABI 编码函数</h3><p>一个是 solidity 提供了ABI的相关<a href="https://learnblockchain.cn/2018/03/14/solidity-api/">API</a>, 用来直接得到ABI编码信息,这些函数有:</p><ul><li>abi.encode(…) returns (bytes):计算参数的ABI编码。</li><li>abi.encodePacked(…) returns (bytes):计算参数的紧密打包编码</li><li>abi. encodeWithSelector(bytes4 selector, …) returns (bytes): 计算函数选择器和参数的ABI编码</li><li>abi.encodeWithSignature(string signature, …) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), …)</li></ul><p>通过ABI编码函数可以在不用调用函数的情况下,获得ABI编码值,下面通过一段代码来看看这些方法的使用:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.24</span>;</span><br><span class="line"></span><br><span class="line">contract testABI {</span><br><span class="line"> uint storedData;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">set</span>(<span class="params">uint x</span>) <span class="title">public</span> </span>{</span><br><span class="line"> storedData = x;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">abiEncode</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">constant</span> <span class="title">returns</span> (<span class="params">bytes</span>) </span>{</span><br><span class="line"> abi.encode(<span class="number">1</span>); <span class="comment">// 计算1的ABI编码</span></span><br><span class="line"> <span class="keyword">return</span> abi.encodeWithSignature(<span class="string">"set(uint256)"</span>, <span class="number">1</span>); <span class="comment">//计算函数set(uint256) 及参数1 的ABI 编码</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>大家可以运行运行下<code>abiEncode</code>函数,它的输出其实就是前面调用的附加数据。</p><h3 id="Web3-ABI-编码函数"><a href="#Web3-ABI-编码函数" class="headerlink" title="Web3 ABI 编码函数"></a>Web3 ABI 编码函数</h3><p>另一个web3提供相应的API,例如使用web3计算函数选择器的方式如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">web3.eth.abi.encodeFunctionSignature(<span class="string">'myMethod(uint256,string)'</span>);</span><br></pre></td></tr></table></figure><p>其完整的文档在<a href="http://web3js.readthedocs.io/en/1.0/web3-eth-abi.html" target="_blank" rel="noopener">这里</a>,这里不一一演示。</p><p>如果你想学习以太坊DApp开发,这门视频课程<a href="https://wiki.learnblockchain.cn/course/dapp.html" target="_blank" rel="noopener">以太坊DAPP开发实战</a>是你不错的选择。</p><p>欢迎来<a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">知识星球</a>提问,星球内已经聚集了300多位区块链技术爱好者。<br><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。</p>]]></content>
<summary type="html">
<p>很多同学不是很明白以太坊ABI是什么,他的作用是什么,读完本文就明白了。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="ABI" scheme="https://learnblockchain.cn/tags/ABI/"/>
</entry>
<entry>
<title>智能合约语言 Solidity 教程系列13 - 函数调用</title>
<link href="https://learnblockchain.cn/2018/08/09/solidity-callfun/"/>
<id>https://learnblockchain.cn/2018/08/09/solidity-callfun/</id>
<published>2018-08-09T03:17:17.000Z</published>
<updated>2018-12-06T01:46:09.796Z</updated>
<content type="html"><![CDATA[<p>这是Solidity教程系列文章第13篇介绍函数调用, 本文会介绍函数使用元组返回多个值,通过命名方式进行参数调用以及如何省略函数参数名称。</p><p>Solidity 系列完整的文章列表请查看<a href="https://learnblockchain.cn/categories/ethereum/Solidity/">分类-Solidity</a>。</p><a id="more"></a><h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a>写在前面</h2><p>Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,<br>如果你还不了解,建议你先看<a href="https://learnblockchain.cn/2017/11/20/whatiseth/">以太坊是什么</a></p><p>欢迎订阅<a href="https://xiaozhuanlan.com/blockchaincore" target="_blank" rel="noopener">区块链技术专栏</a>阅读更全面的分析文章。</p><h2 id="函数调用及参数"><a href="#函数调用及参数" class="headerlink" title="函数调用及参数"></a>函数调用及参数</h2><p>在<a href="https://xiaozhuanlan.com/topic/1293405678" target="_blank" rel="noopener">函数类型</a>一节中,我们介绍过Solidity 中有两种函数调用方式:内部函数调用和外部函数调用,这一节我们进一步介绍。</p><h2 id="内部函数调用(Internal-Function-Calls)"><a href="#内部函数调用(Internal-Function-Calls)" class="headerlink" title="内部函数调用(Internal Function Calls)"></a>内部函数调用(Internal Function Calls)</h2><p>内部调用,不会创建一个EVM消息调用。而是直接调用当前合约的函数,也可以递归调用。<br>如下面这个的例子:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.16</span>;</span><br><span class="line"></span><br><span class="line">contract C {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">g</span>(<span class="params">uint a</span>) <span class="title">public</span> <span class="title">pure</span> <span class="title">returns</span> (<span class="params">uint ret</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> f(); <span class="comment">// 直接调用</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) <span class="title">internal</span> <span class="title">pure</span> <span class="title">returns</span> (<span class="params">uint ret</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> g(<span class="number">7</span>) + f(); <span class="comment">// 直接调用及递归调用</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这些函数调用被转换为EVM内部的简单指令跳转(jumps)。 这样带来的一个好处是,当前的内存不会被回收。在一个内部调用时传递一个内存型引用效率将非常高的。当然,仅仅是同一个合约的函数之间才可通过内部的方式进行调用。</p><h2 id="外部函数调用-External-Function-Calls"><a href="#外部函数调用-External-Function-Calls" class="headerlink" title="外部函数调用(External Function Calls)"></a>外部函数调用(External Function Calls)</h2><p>外部调用,会创建EVM<strong>消息调用</strong>。<br>表达式this.g(8);和c.g(2)(这里的c是一个合约实例)是外部调用函数的方式,它会发起一个消息调用,而不是EVM的指令跳转。需要注意的是,在合约的构造器中,不能使用this调用函数,因为当前合约还没有创建完成。</p><p>其它合约的函数必须通过外部的方式调用。对于一个外部调用,所有函数的参数必须要拷贝到内存中。</p><p>当调用其它合约的函数时,可以通过选项<strong>.value()</strong>,和<strong>.gas()</strong>来分别指定要发送的以太币(以wei为单位)和gas值,如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.0</span>;</span><br><span class="line"></span><br><span class="line">contract InfoFeed {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">info</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">payable</span> <span class="title">returns</span> (<span class="params">uint ret</span>) </span>{ <span class="keyword">return</span> <span class="number">42</span>; }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">contract Consumer {</span><br><span class="line"> InfoFeed feed;</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">setFeed</span>(<span class="params">address addr</span>) <span class="title">public</span> </span>{</span><br><span class="line"> feed = InfoFeed(addr);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">callFeed</span>(<span class="params"></span>) <span class="title">public</span> </span>{</span><br><span class="line"> feed.info.value(<span class="number">10</span>).gas(<span class="number">800</span>)(); <span class="comment">// 附加以太币及gas来调用info</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>info()函数,必须使用payable关键字,否则不能通过value()来接收以太币。</p><p>表达式InfoFeed(addr)进行了一个显示的类型转换,表示给定的地址是合约InfoFeed类型,这里并不会执行构造器的初始化。<br>在进行显式的类型强制转换时需要非常小心,不要调用一个我们不知道类型的合约函数。</p><p>我们也可以使用<strong>function setFeed(InfoFeed _feed) { feed = _feed; }</strong>来直接进行赋值。<br>注意<strong>feed.info.value(10).gas(800)</strong>仅仅是对发送的以太币和gas值进行了设置,真正的调用是后面的括号()。<br>调用callFeed时,需要预先存入一定量的以太币,要不能会因余额不足报错。</p><blockquote><p>如果我们不知道被调用的合约源代码,和它们交互会有潜在的风险,即便被调用的合约继承自一个已知的父合约(继承仅仅要求正确实现接口,而不关注实现的内容)。<br>因为和他们交互,相当于把自己控制权交给被调用的合约,对方几乎可以利用它做任何事。<br>此外, 被调用的合约可以改变调用合约的状态变量,在编写函数时需要注意可重入性漏洞问题(可查看安全建议)。</p></blockquote><h2 id="函数参数"><a href="#函数参数" class="headerlink" title="函数参数"></a>函数参数</h2><p>与其他语言一样,函数可以提供参数作为输入(函数类型本身也可以作为参数); 与Javascript和C不同的是,solidity还可以返回任意数量的参数作为输出。</p><h3 id="输入参数"><a href="#输入参数" class="headerlink" title="输入参数"></a>输入参数</h3><p>输入参数的声明方式与变量相同, 未使用的参数可以省略变量名称。假设我们希望合约接受一种带有两个整数参数的外部调用,可以这样写:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.16</span>;</span><br><span class="line"></span><br><span class="line">contract Simple {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">taker</span>(<span class="params">uint _a, uint _b</span>) <span class="title">public</span> <span class="title">pure</span> </span>{</span><br><span class="line"> <span class="comment">// 使用 _a _b</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="输出参数"><a href="#输出参数" class="headerlink" title="输出参数"></a>输出参数</h3><p>输出参数的声明和输入参数一样,只不过它接在returns 之后,假设我们希望返回两个结果:两个给定整数的和及积,可以这样写:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.16</span>;</span><br><span class="line"></span><br><span class="line">contract Simple {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">arithmetics</span>(<span class="params">uint _a, uint _b</span>)</span></span><br><span class="line"><span class="function"> <span class="title">public</span></span></span><br><span class="line"><span class="function"> <span class="title">pure</span></span></span><br><span class="line"><span class="function"> <span class="title">returns</span> (<span class="params">uint o_sum, uint o_product</span>)</span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> o_sum = _a + _b;</span><br><span class="line"> o_product = _a * _b;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以省略输出参数的名称,也可以使用return语句指定输出值,return可以返回多个值(见下文)。<br>返回一个没有赋值的参数,则默认为0。</p><p>输入参数和输出参数可以在函数内表达式中使用,也可以作为被赋值的对象, 如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">contract Simple {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">taker</span>(<span class="params">uint _a, uint _b</span>) <span class="title">public</span> <span class="title">pure</span> <span class="title">returns</span> (<span class="params">uint _c</span>) </span>{</span><br><span class="line"> _a = <span class="number">1</span>;</span><br><span class="line"> _b = <span class="number">2</span>;</span><br><span class="line"> _c = <span class="number">3</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="返回多个值"><a href="#返回多个值" class="headerlink" title="返回多个值"></a>返回多个值</h3><p>当一个函数有多个输出参数时, 可以使用元组(tuple)来返回多个值。元组(tuple)是一个数量固定,类型可以不同的元素组成的一个列表(用小括号表示),使用return (v0, v1, …, vn) 语句,就可以返回多个值,返回值的数量需要和输出参数声明的数量一致。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params"></span>) <span class="title">public</span> <span class="title">pure</span> <span class="title">returns</span> (<span class="params">uint, bool, uint</span>) </span>{</span><br><span class="line"> <span class="comment">// 使用元组返回多个值</span></span><br><span class="line"> <span class="keyword">return</span> (<span class="number">7</span>, <span class="literal">true</span>, <span class="number">2</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">callf</span>(<span class="params"></span>) <span class="title">public</span> </span>{</span><br><span class="line"> uint x;</span><br><span class="line"> bool y;</span><br><span class="line"> uint z;</span><br><span class="line"> <span class="comment">// 使用元组给多个变量赋值</span></span><br><span class="line"> (x, y , z) = f();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="补充关于元组的介绍"><a href="#补充关于元组的介绍" class="headerlink" title="补充关于元组的介绍"></a>补充关于元组的介绍</h4><p>上面的代码中,使用了元组返回多个值及使用元组给多个变量赋值,给多个变量赋值通常也称为解构(解构的概念在函数式语言中较为常见),再来看看元组的一些用法,比如元组可以交换变量值,如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(x, y) = (y, x);</span><br></pre></td></tr></table></figure><p>元组支持省略一些元素, 如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(x, y, ) = (<span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure><p>开头的元素也可以省略,如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(, y, ) = (<span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span>);</span><br></pre></td></tr></table></figure><p>注意 (1,) 是一个一个元素的元组, (1) 只是1。</p><h3 id="使用命名参数调用"><a href="#使用命名参数调用" class="headerlink" title="使用命名参数调用"></a>使用命名参数调用</h3><p>函数调用的参数,可以通过指定名称的方式调用,使用花括号{} 包起来,参数顺序任意,但参数的类型和数量要与定义一致。<br>如:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.0</span>;</span><br><span class="line"></span><br><span class="line">contract C {</span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">f</span>(<span class="params">uint key, uint value</span>) <span class="title">public</span> </span>{</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">g</span>(<span class="params"></span>) <span class="title">public</span> </span>{</span><br><span class="line"> f({<span class="attr">value</span>: <span class="number">2</span>, <span class="attr">key</span>: <span class="number">3</span>}); <span class="comment">// 命名参数</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="省略函数参数名称"><a href="#省略函数参数名称" class="headerlink" title="省略函数参数名称"></a>省略函数参数名称</h3><p>没有使用的参数名称可以省略(一般常见于返回值)。这些参数依然在栈(stack)上存在,但不可访问。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">pragma solidity ^<span class="number">0.4</span><span class="number">.16</span>;</span><br><span class="line"></span><br><span class="line">contract C {</span><br><span class="line"> <span class="comment">// omitted name for parameter</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">func</span>(<span class="params">uint k, uint</span>) <span class="title">public</span> <span class="title">pure</span> <span class="title">returns</span>(<span class="params">uint</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> k;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><a href="https://learnblockchain.cn/">深入浅出区块链</a> - 系统学习区块链,打造最好的区块链技术博客。<br>如果想与我有更密切的交流可以选择加入我的<a href="https://t.xiaomiquan.com/RfAu7uj" target="_blank" rel="noopener">知识星球</a>(星球成员可加入微信技术交流群)</p>]]></content>
<summary type="html">
<p>这是Solidity教程系列文章第13篇介绍函数调用, 本文会介绍函数使用元组返回多个值,通过命名方式进行参数调用以及如何省略函数参数名称。</p>
<p>Solidity 系列完整的文章列表请查看<a href="https://learnblockchain.cn/categories/ethereum/Solidity/">分类-Solidity</a>。</p>
</summary>
<category term="以太坊" scheme="https://learnblockchain.cn/categories/ethereum/"/>
<category term="Solidity" scheme="https://learnblockchain.cn/categories/ethereum/Solidity/"/>
<category term="Solidity手册" scheme="https://learnblockchain.cn/tags/Solidity%E6%89%8B%E5%86%8C/"/>
</entry>
</feed>