forked from xilibi2003/learnblockchain
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
798 lines (798 loc) · 435 KB
/
search.xml
File metadata and controls
798 lines (798 loc) · 435 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
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[区块链技术工坊 - 线下区块链技术分享]]></title>
<url>%2F2018%2F12%2F13%2Ftechnical-workshop-2%2F</url>
<content type="text"><![CDATA[区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。欢迎大家关注微信:upchainedu 及时获取活动信息。 [第二期]深度探索以太坊智能合约讲师:以太零CTO 钟瑞仙 主要内容包含: 1. 以太坊账户介绍 2. 交易数据⾥里data字段的编码规则 3. 智能合约属性的索引和存储 4. 预编译合约介绍及汇编调⽤ 点击下载PPT及完整课程视频]]></content>
<categories>
<category>project</category>
</categories>
<tags>
<tag>project</tag>
</tags>
</entry>
<entry>
<title><![CDATA[区块链技术工坊 - 线下区块链技术分享]]></title>
<url>%2F2018%2F12%2F13%2Ftechnical-workshop-3%2F</url>
<content type="text"><![CDATA[区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。欢迎大家关注微信:upchainedu 及时获取活动信息。 [第三期]高TPS与去中心化存储带来的机遇讲师:星际区块(深圳)CEO 谢建怀 主要内容: 1. 高TPS能让我们做更多有意思的东西 2. 第三代区块链技术能落地的思考 3. 去中心化存储能在工程上带来哪些应用 4. 区块链应用的项目探索(基于EOS、FIBOS和IPFS 应用) 点击下载PPT及完整课程视频]]></content>
<categories>
<category>project</category>
</categories>
<tags>
<tag>project</tag>
</tags>
</entry>
<entry>
<title><![CDATA[区块链技术工坊 - 线下区块链技术分享]]></title>
<url>%2F2018%2F12%2F13%2Ftechnical-workshop-4%2F</url>
<content type="text"><![CDATA[区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。欢迎大家关注微信:upchainedu 及时获取活动信息。 [第四期] 以太坊零手续费及其安全防御的实现讲师:以太零CTO 钟瑞仙 主要内容: 1. 以太坊⼿手续费简介 2. 零⼿手续费的必要性 3. 零⼿手续费的实现 4. 零⼿手续费带来的安全问题及其解决⽅方案 5. 零⼿手续费的副作⽤ 点击下载PPT及完整课程视频]]></content>
<categories>
<category>project</category>
</categories>
<tags>
<tag>project</tag>
</tags>
</entry>
<entry>
<title><![CDATA[区块链技术工坊 - 线下区块链技术分享]]></title>
<url>%2F2018%2F12%2F13%2Ftechnical-workshop-1%2F</url>
<content type="text"><![CDATA[区块链技术工坊由一群热爱区块链技术的开发者组织,在全国各主要城市每周举办线下区块链技术分享活动。深圳地区由HiBlock、小牛新能源、登链学院联合主办,由以太零、Qtum、FIBOS、AckBlock、HPB赞助。欢迎大家关注微信:upchainedu 及时获取活动信息。 [第一期]以太坊钱包开发讲师:登链学院 熊丽兵 分享大纲: 1. 私钥 地址 及账号 2. 什么是HD钱包(分层确定性钱包) 3. 助记词及私钥保存 4. 如何测量gasLimit及设定gasPrice 5. 如何发送签名交易及转移Token 点击下载PPT及完整课程视频]]></content>
<categories>
<category>project</category>
</categories>
<tags>
<tag>project</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Fabric 网络环境启动过程详解]]></title>
<url>%2F2018%2F11%2F21%2Ffabric_startup%20_process%2F</url>
<content type="text"><![CDATA[这篇文章对Fabric的网络环境启动过程进行讲解,也就是我们上节讲到的启动测试Fabric网络环境时运行network_setup.sh这个文件的执行流程 Fabric网络环境启动过程详解上一节我们讲到 fabric网络环境的启动测试,主要是使用 ./network_setup.sh up 这个命令,所以fabric网络环境启动的重点就在network_setup.sh这个文件中。接下来我们就分析一下network_setup.sh这个文件。network_setup.sh其中包括两个部分,一个是利用generateArtifacts.sh脚本文件配置组织关系和颁发证书、公/私钥、通道证书等,另一个是docker-compose-cli.yaml用于根据配置启动集群并测试chaincode的示例代码。下面是具体的流程图介绍: 首先看下generateArtifacts.sh脚本文件,它包含三个函数,分别是: 123456781.generateCerts: 该函数使用cryptogen工具根据crypto-config.yaml来生成公私钥和证书信息等。2.replacePrivateKey: 将docker-compose-e2e-template.yaml文档中的ca私钥替换成具体的私钥。3.generateChannelArtifacts: 使用configtxgen工具根据configtx.yaml文件来生成创世区块和通道相关信息,更新锚节点。 接着是docker-compose-cli.yaml文件 docker-compose-cli.yaml文件根据组织关系启动docker集群,并在cli容器中执行command命令运行./scripts/script.sh脚本文件。 那./scripts/script.sh脚本具体做了什么呢? 1234561. createChannel:创建channel。2. joinChannel:将每个peer节点加入channel。3. updateAnchorPeers:更新锚节点4. installChaincode:部署chaincode。5. instantiateChaincode:初始化chaincode。6. chaincodeQuery:chaincode查询 另外docker-compose-cli.yaml这个文件还有一个配置项是需要注意的地方,那就是: 1file: base/docker-compose-base.yaml 这里的docker-compose-base.yaml其实就是Orderer和peer的基础配置文件,包括指定端口等。 几个重要的配置文件1.crypto-config.yaml基于crypto-config.yaml(此文件在../fabric/examples/e2e_cli中)生成公、私钥和证书信息,并保存在crypto-config文件夹中。另外crypto-config.yaml还定义了组织成员以及组织下的peer节点个数。 crypto-config.yaml文件讲解: 字段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等。文件内容如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 # --------------------------------------------------------------------------- # Orderer # --------------------------------------------------------------------------- - Name: Orderer Domain: example.com # --------------------------------------------------------------------------- # "Specs" - See PeerOrgs below for complete description # --------------------------------------------------------------------------- Specs: - Hostname: orderer# ---------------------------------------------------------------------------# "PeerOrgs" - Definition of organizations managing peer nodes# ---------------------------------------------------------------------------PeerOrgs: # --------------------------------------------------------------------------- # Org1 # --------------------------------------------------------------------------- - Name: Org1 Domain: org1.example.com # --------------------------------------------------------------------------- # "Specs" # --------------------------------------------------------------------------- # Uncomment this section to enable the explicit definition of hosts in your # configuration. Most users will want to use Template, below # # Specs is an array of Spec entries. Each Spec entry consists of two fields: # - Hostname: (Required) The desired hostname, sans the domain. # - CommonName: (Optional) Specifies the template or explicit override for # the CN. By default, this is the template: # # "{{.Hostname}}.{{.Domain}}" # # which obtains its values from the Spec.Hostname and # Org.Domain, respectively. # --------------------------------------------------------------------------- # Specs: # - Hostname: foo # implicitly "foo.org1.example.com" # CommonName: foo27.org5.example.com # overrides Hostname-based FQDN set above # - Hostname: bar # - Hostname: baz # --------------------------------------------------------------------------- # "Template" # --------------------------------------------------------------------------- # Allows for the definition of 1 or more hosts that are created sequentially # from a template. By default, this looks like "peer%d" from 0 to Count-1. # You may override the number of nodes (Count), the starting index (Start) # or the template used to construct the name (Hostname). # # Note: Template and Specs are not mutually exclusive. You may define both # sections and the aggregate nodes will be created for you. Take care with # name collisions # --------------------------------------------------------------------------- Template: Count: 2 # Start: 5 # Hostname: {{.Prefix}}{{.Index}} # default # --------------------------------------------------------------------------- # "Users" # --------------------------------------------------------------------------- # Count: The number of user accounts _in addition_ to Admin # --------------------------------------------------------------------------- Users: Count: 1 # --------------------------------------------------------------------------- # Org2: See "Org1" for full specification # --------------------------------------------------------------------------- - Name: Org2 Domain: org2.example.com Template: Count: 2 Users: Count: 1 注:peer:Fabric 网络中的节点,表现为一个运行着的docker容器。可以与网络中的其他peer进行通信,每个peer都在本地保留一份ledger的副本。它是org下的组织成员。org:一个组织,它可以由一个或多个peer组成。Orderer :联盟成员共享的中心化节点。用来对交易进行排序,是 Fabric 共识机制的重要组成部分。 2.configtx.yaml基于configtx.yaml(此文件在../fabric/examples/e2e_cli中)生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。还可以指定背书策略。 configtx.yaml文件讲解: 官方提供的examples/e2e_cli/configtx.yaml这个文件里面配置了由2个Org参与的Orderer共识配置TwoOrgsOrdererGenesis,以及由2个Org参与的Channel配置:TwoOrgsChannel。 另外我们可以在此文件的Orderer部分设置共识的算法是Solo还是Kafka,以及共识时区块大小,超时时间等,我们使用默认值即可,不用更改。而Peer节点的配置包含了MSP的配置,锚节点的配置。如果我们有更多的Org,或者有更多的Channel,那么就可以根据模板进行对应的修改。 Policies配置也要特别注意,该配置项定义了不同角色的权限,Reader,Writer以及Admin分别对应读,写,以及admin权限,读权限角色只能从别的peer节点同步账本而不能发起交易,只有writer定义项下的角色才拥有发起交易的也就是调用chaincode的invoke方法的权限(不一定都是invoke方案,只要涉及到chaincode中状态修改的方法,都只有拥有writer权限或admin权限的角色才能调用)。以该配置的Organizations配置下的Org1配置为例,”OR(‘Org1MSP.admin’, ‘Org1MSP.client’)”,表示org1的msp服务中的admin或者client角色拥有发起交易的权限。文件内容如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149# Copyright IBM Corp. All Rights Reserved.## SPDX-License-Identifier: Apache-2.0#---################################################################################## Profile## - Different configuration profiles may be encoded here to be specified# as parameters to the configtxgen tool#################################################################################Profiles: TwoOrgsOrdererGenesis: Orderer: <<: *OrdererDefaults Organizations: - *OrdererOrg Consortiums: SampleConsortium: Organizations: - *Org1 - *Org2 TwoOrgsChannel: Consortium: SampleConsortium Application: <<: *ApplicationDefaults Organizations: - *Org1 - *Org2################################################################################## Section: Organizations## - This section defines the different organizational identities which will# be referenced later in the configuration.#################################################################################Organizations: # SampleOrg defines an MSP using the sampleconfig. It should never be used # in production but may be used as a template for other definitions - &OrdererOrg # DefaultOrg defines the organization which is used in the sampleconfig # of the fabric.git development environment Name: OrdererOrg # ID to load the MSP definition as ID: OrdererMSP # MSPDir is the filesystem path which contains the MSP configuration MSPDir: crypto-config/ordererOrganizations/example.com/msp - &Org1 # DefaultOrg defines the organization which is used in the sampleconfig # of the fabric.git development environment Name: Org1MSP # ID to load the MSP definition as ID: Org1MSP MSPDir: crypto-config/peerOrganizations/org1.example.com/msp AnchorPeers: # AnchorPeers defines the location of peers which can be used # for cross org gossip communication. Note, this value is only # encoded in the genesis block in the Application section context - Host: peer0.org1.example.com Port: 7051 - &Org2 # DefaultOrg defines the organization which is used in the sampleconfig # of the fabric.git development environment Name: Org2MSP # ID to load the MSP definition as ID: Org2MSP MSPDir: crypto-config/peerOrganizations/org2.example.com/msp AnchorPeers: # AnchorPeers defines the location of peers which can be used # for cross org gossip communication. Note, this value is only # encoded in the genesis block in the Application section context - Host: peer0.org2.example.com Port: 7051################################################################################## SECTION: Orderer## - This section defines the values to encode into a config transaction or# genesis block for orderer related parameters#################################################################################Orderer: &OrdererDefaults # Orderer Type: The orderer implementation to start # Available types are "solo" and "kafka" OrdererType: solo Addresses: - orderer.example.com:7050 # Batch Timeout: The amount of time to wait before creating a batch BatchTimeout: 2s # Batch Size: Controls the number of messages batched into a block BatchSize: # Max Message Count: The maximum number of messages to permit in a batch MaxMessageCount: 10 # Absolute Max Bytes: The absolute maximum number of bytes allowed for # the serialized messages in a batch. AbsoluteMaxBytes: 98 MB # Preferred Max Bytes: The preferred maximum number of bytes allowed for # the serialized messages in a batch. A message larger than the preferred # max bytes will result in a batch larger than preferred max bytes. PreferredMaxBytes: 512 KB Kafka: # Brokers: A list of Kafka brokers to which the orderer connects # NOTE: Use IP:port notation Brokers: - 127.0.0.1:9092 # Organizations is the list of orgs which are defined as participants on # the orderer side of the network Organizations:################################################################################## SECTION: Application## - This section defines the values to encode into a config transaction or# genesis block for application related parameters#################################################################################Application: &ApplicationDefaults # Organizations is the list of orgs which are defined as participants on # the application side of the network Organizations: 本文的作者是lgy 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>Fabric</category>
<category>联盟链</category>
</categories>
<tags>
<tag>Fabric</tag>
</tags>
</entry>
<entry>
<title><![CDATA[联盟链初识以及Fabric环境搭建流程]]></title>
<url>%2F2018%2F11%2F21%2Ffabric_introduction%2F</url>
<content type="text"><![CDATA[这篇文章首先简单介绍了联盟链是什么,再详细的介绍了Fabric环境搭建的整个流程。 区块链分类以参与方式分类,区块链可以分为:公有链、联盟链和私有链。 定义我们知道区块链就是一个分布式的,去中心化的公共数据库(或称公共账本)。而联盟链是区块链的一个分支,所以它本身也是一个分布式的,去中心化的公共数据库,跟其他链的区别就是它是针对特定群体的成员和有限的第三方,其内部指定多个预选节点为记账人,其共识过程受到预选节点控制的区块链 本质联盟链本质仍然是一种私有链,只不过它要比单个小组织开发的私有链更大,但是却没有公有链这么大的规模,可以理解为它是介于公有链和私有链的一种区块链。 联盟链的特点 交易速度快 我们知道对于公有链来说,要想达成共识,必须得由区块链中的所有节点来决定,本身公有链的节点数量就非常庞大,所以处理速度很慢。但对于联盟链来说,由于其节点不多的原因,而且只要当网络上2/3的节点达成共识,就可以完成交易,交易速度自然也就快很多。 数据默认不会公开 不同于公有链,联盟链上的信息并不是所有有访问条件的人就可以访问的,联盟链的数据只限于联盟里的机构及其用户才有权限进行访问。 部分去中心化与公有链不同,联盟链某种程度上只属于联盟内部的所有成员所有,且很容易达成共识,因为其节点数毕竟是有限的。 联盟链项目R3:由40多加银行参与的区块链联盟R3,包括世界著名的银行(如摩根大通、高盛、瑞信、伯克莱、汇丰银行等),IT巨头(如IBM、微软)。 超级账本(Hyperledger):由 Linux基金会在2015年12月主导发起该项目, 成员包括金融,银行,物联网,供应链,制造和科技行业的领头羊。 Fabric介绍我们知道智能合约比较成功的就是以太坊了。以太坊主要是公有链,其实对企业应用来说并不是特别合适,而且本身并没有权限控制功能,面向企业的,主要还是HyperLedger Fabric,当然还有R3的Corda。这里我们主要是讲Fabric。Fabric是一个面向企业应用的区块链框架,基于Fabric的开发可以粗略分为几个层面: 1. 参与Fabric的底层开发,这主要是fabric,fabric-ca和sdk等核心组件。2. 参与Fabric周边生态的开发,如支持如支持fabric的工具explorer, composer等。3. 利用fabric平台开发应用,这就是利用fabirc提供的各种sdk来为应用服务(应用开发) 大部分企业会参与2-3的内容,以3为主来服务应用场景,以2为辅。因为现在除了区块链核心功能尚未完善外,对区块链的管理,运维,监控,测试,优化,调试等工具非常匮乏。企业将不得不面对自己开发一些工作。 Fabric环境依赖Fabric官方推荐的开发环境是基于docker搭建的,使用docker搭建需要一下前置条件: docker一一Docker version 17.06.2-ce 或以上版本 Docker Compose一一1.14或以上版本 Go一一1.10或以上版本, Node.js一一8.9.x或以上版本 Python一一主要是python-pip Fabric环境搭建具体步骤这里使用的是Ubuntu 16.04.4版本 1.安装go及环境变量配置(1)下载最新版本的go二进制文件 1$ wget https://dl.google.com/go/go1.9.2.linux-amd64.tar.gz (2)解压文件 1$ sudo tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz (3)配置环境变量 1vim ~/.profile 添加以下内容: 1234export PATH=$PATH:/usr/local/go/binexport GOROOT=/usr/local/goexport GOPATH=$HOME/goexport PATH=$PATH:$HOME/go/bin 编辑保存并退出vi后,记得使这些环境变量生效 1source ~/.profile 2.安装DockerFabric的chaincode是运行在docker里的。 (1) 由于apt官方库里的docker版本可能比较旧,所以先卸载可能存在的旧版本: 1sudo apt-get remove docker docker-engine docker-ce docker.io (2) 更新apt包索引: 1sudo apt-get update (3) 安装以下包以使apt可以通过HTTPS使用存储库(repository): 1sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common (4) 添加Docker官方的GPG密钥: 1234curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -备注:可验证秘钥指纹 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88使用如下命令验证:sudo apt-key fingerprint 0EBFCD88 (5) 使用下面的命令来设置stable存储库: 1sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" (6) 再更新一下apt包索引: 1sudo apt-get update (7) 安装最新版本的Docker CE: 12345sudo apt-get install -y docker-ce注意:在生产系统上,可能会需要应该安装一个特定版本的Docker CE,而不是总是使用最新版本:列出可用的版本:apt-cache madison docker-ce选择要安装的特定版本,第二列是版本字符串,第三列是存储库名称,它指示包来自哪个存储库,以及扩展它的稳定性级别。要安装一个特定的版本,将版本字符串附加到包名中,并通过等号(=)分隔它们:sudo apt-get install docker-ce=<VERSION> (8) 测试是否安装成功 1docker --version (9) 使用阿里提供的镜像,否则后面下载Fabric镜像会非常慢cd到/etc/docker目录下,创建文件daemon.json,输入下面的内容: 123{ "registry-mirrors": ["https://obou6wyb.mirror.aliyuncs.com"]} 保存并退出,接着执行: 12sudo systemctl daemon-reloadsudo systemctl restart docker (10) 查看docker服务是否启动: 1systemctl status docker (11) 若未启动,则启动docker服务: 1sudo service docker start或者sudo systemctl start docker 3.安装最新版本的Docker-compose(1) Docker-compose是支持通过模板脚本批量创建Docker容器的一个组件。在安装Docker-Compose之前,需要安装Python-pip,运行脚本: 1sudo apt-get install python-pip (2) 安装Docker-compose: 1pip install docker-compose (3) 验证是否成功: 1sudo docker-compose --version 安装Docker还可以参考此篇文章 4.Fabric源码下载(1) 新建存放测试、部署代码的目录。 1mkdir -p ~/go/src/github.com/hyperledger/ (2) cd到刚创建的目录 1cd ~/go/src/github.com/hyperledger (3) 下载Fabric,这里使用使用git命令下载源码: 1git clone https://github.com/hyperledger/fabric.git 特别注意这里:直接使用上面的git clone下载会非常慢,因为github.global.ssl.fastly.Net域名被限制了。只要找到这个域名对应的ip地址,然后在hosts文件中加上ip–>域名的映射,刷新DNS缓存就可以了。解决办法:步骤【1】:查询域名global-ssl.fastly.Net和 github.com 公网地址可以使用https://www.ipaddress.com/ 这个查。分别查找下面这两个域名的ip地址: 12github.global.ssl.fastly.netgithub.com 步骤【2】:将ip地址添加到hosts文件 1sudo vim /etc/hosts 在文件下方输入下面内容并保存,前面两个ip就是我们刚才上面查找到的ip: 12151.101.185.194 github.global.ssl.fastly.net192.30.253.113 github.com 步骤【3】:修改完hosts还不会立即生效,你需要刷新DNS缓存,告诉电脑我的hosts文件已经修改了。输入指令:sudo /etc/init.d/networking restart 即可,如果不行也可以尝试重启一下电脑。接下来再去git clone就快很多了。 (4) 由于Fabric一直在更新,新版本的并不稳定,所有我们并不需要最新的源码,需要切换到v1.0.0版本的源码: 1git checkout v1.0.0 5.下载Fabric Docker镜像(1) 前面步骤4下载完成后,我们可以看到当前工作目录(~/go/src/github.com/hyperledger/)下多了一个fabric的文件夹,接下来我们cd到~/go/src/github.com/hyperledger/fabric/examples/e2e_cli目录下执行: 1source download-dockerimages.sh -c x86_64-1.0.0 -f x86_64-1.0.0 (注:一定要下载完所有镜像并且镜像版本要和Fabric版本一致如何没有下载问继续执行source download-dockerimages.sh命令直到在完如图所有镜像),执行完所有会用到的Fabric docker镜像都会下载下来了。运行以下命令检查下载的镜像列表: 1docker images 注意:如果下载时遇到权限问题,需要切换到root用户下:su root(2) 重启Docker 1service docker restart 6.测试Fabric环境是否成功在~/go/src/github.com/hyperledger/fabric/examples/e2e_cli下执行如下命令启动测试 1./network_setup.sh up 这个指令具体进行了如下操作: 编译生成Fabric公私钥、证书的程序,程序在目录:fabric/release/linux-amd64/bin 基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。基于configtx.yaml生成创世区块和通道相关信息,并保存在channel-artifacts文件夹。 基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。基于crypto-config.yaml生成公私钥和证书信息,并保存在crypto-config文件夹中。 基于docker-compose-cli.yaml启动1Orderer+4Peer+1CLI的Fabric容器。基于docker-compose-cli.yaml启动1Orderer+4Peer+1CLI的Fabric容器。在CLI启动的时候,会运行scripts/script.sh文件,这个脚本文件包含了创建Channel,加入Channel,安装Example02,运行Example02等功能。 运行完如果出现下图所示,说明整个Fabric网络已经通了。 这里记录本人测试Fabric环境是否成功时遇到的问题1. 如果发现运行 ./network_setup.sh up命令 后提示在…fabric/release/linux-amd64/bin文件夹下找不到指定文件解决办法:可以在~/go/src/github.com/hyperledger/fabric/scripts文件下找到 bootstrap.1.0.0.sh文件,手动运行它 ./bootstrap.1.0.0.sh, 此时可以在当前文件夹生成一个bin文件夹,bin里面的文件就是我们需要的,将它拷贝到前面的…fabric/release/linux-amd64/bin文件夹下 2. 如果出现:Error on outputBlock: Error writing genesis block: open ./channel-artifacts/genesis.block: is a directory不能生成创世块的错误。解决办法:可以在~/go/src/github.com/hyperledger/fabric/examples/e2e_cli/channel-artifacts目录下,将genesis.block这个目录删除,rm -rf genesis.block/ 3. 如果出现:.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:解决办法:执行./network_setup.sh down 清除网络后再启动即可 测试Fabric网络接下来我们手动测试下Fabric网络,Fabric提供了SDK和CLI两种交互方式,这里我们使用的是CLI。这里我们使用官方提供的小例子进行测试,在官方例子中,channel名字是mychannel,链码(智能合约)的名字是mycc。首先要登录到CLI这个容器中,才能执行Fabric的CLI命令: 1docker exec -it cli bash 这时用户名变为root@caa22f87a5bf,当前目录变为/opt/go/src/github.com/hyperledger/fabric/peer#,接着可执行peer命令,体验区块链的命令行使用方式。 1.查看a账户的余额 1peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}' 此时我们可以看到控制台输出有: 1Query Result: 90 这里90就是a账户的余额 2.调用链码,转账 这里我们让b账户向a账户转账10: 1peer 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"]}' 转账成功后,我们可以看到有输出如下提示: 1DEBU 009 ESCC invoke result: version:1 response:<status:200 message:"OK" 接下来我们使用前面的命令继续查看a账户的余额,输出结果如下: 1Query Result: 100 很明显我们已经转账成功了。 退出cli容器: 直接执行 1exit 最后如果我们要关闭Fabric网络,cd到~/go/src/github.com/hyperledger/fabric/examples/e2e_cli下(注意这里的路径按自己前面创建的,不一定要和我一样),执行: 1./network_setup.sh down 参考文章https://blog.csdn.net/github_34965845/article/details/80610060https://www.cnblogs.com/preminem/p/7729497.htmlhttps://www.cnblogs.com/gao90/p/8692642.htmlhttps://blog.csdn.net/so5418418/article/details/78355868https://blog.csdn.net/iflow/article/details/77951610https://blog.csdn.net/vivian_ll/article/details/79966210 本文的作者是lgy 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。 深入浅出区块链知识星球最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。]]></content>
<categories>
<category>Fabric</category>
<category>联盟链</category>
</categories>
<tags>
<tag>Fabric</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Fabric1.0 交易流程]]></title>
<url>%2F2018%2F11%2F21%2Ffabric_transaction_process%2F</url>
<content type="text"><![CDATA[这篇文章详细介绍fabric的交易流程,以图片加文字的形式呈现。 Fabric 1.0交易流程Fabric中的所有交易都是通过chaincode执行 应用程序客户端通过SDK调用证书服务(CA)服务,进行注册和登记,并获取身份证书。 应用程序客户端通过SDK创建好交易提案(Proposal),交易提案把带有本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等信息发送给背书(Endorser)节点。 背书(Endorser)节点收到交易提案(Proposal)后,开始进行验证,验证的内容如下: 交易预案是完好的 该预案以前没有提交过(防止重放攻击) 携带的签名是合法的 交易发起者是否满足区块链写策略, 即ACL 权限检查 满足以上要求后,背书节点把’交易预案’作为输入参数,调用chaincode中的函数,chaincode根据当前的账本状态计算出一个’交易结果’,该结果包括返回值,读写集。此时,区块链账本并不会被更新。’交易结果’在被签名后与一个是/否的背书结果一同返回,称之为’预案回复’。 应用程序客户端收到背书(Endorser)节点返回的信息后,判断提案结果是否一致,以及是否收到足够多的背书节点返回的结果(参照指定的背书策略执行),如果没有足够的背书,则中止处理,这个交易就会被舍弃。否则,将交易提案、模拟交易结果和背书信息打包组成一个交易并签名发给Orderer节点(一个排序服务)。 Orderer节点对来自客户端(SDK)的交易信息进行共识排序,分通道对’交易消息’按时间排序,并按通道将交易打包成块,发送给提交(Committer)节点。 提交(Committer)节点收到区块后,会对区块中的每笔交易进行校验,检查交易依赖的输入输出是否符合当前区块链的状态,验证背书策略是否满足,验证完成后将区块追加到本地的区块链,更新账本,并修改世界状态。具体过程如下: 运行验证逻辑(VSCC检查背书策略) 在区块中指明哪些交易是有效和无效的。 在内存或文件系统上把区块加入区块链 将区块内的有效交易写入状态数据库。 发出Event消息,使得客户端通过SDK监听知道哪些交易是有效的或无效的。 本文的作者是lgy 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>Fabric</category>
<category>联盟链</category>
</categories>
<tags>
<tag>Fabric</tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入理解Plasma(四)Plasma Cash]]></title>
<url>%2F2018%2F11%2F16%2Fplasma-cash%2F</url>
<content type="text"><![CDATA[这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍在 Plasma 框架下的项目 Plasma Cash。 在上一篇文章中我们已经理解了 Plasma 的最小实现 Plasma MVP 如何使用 UTXO 模型实现 Plasma 链下扩容的核心思想。但由于 Plasma MVP 本身过于简单,并不能用于实际的生产环境中。2018 年 3 月,在巴黎举行的以太坊开发者大会上,Vitalik 发布了 Plasma Cash 模型[1],可以视为对 Plasma MVP 的改进。Plasma Cash 与 Plasma MVP 的主要区别是每次存款操作都会产生一个唯一的 coin ID 对应转移到侧链上的资产,并使用一种称为稀疏梅克尔树(Sparse Merkle Tree)的数据结构存储交易历史。由此带来的好处是用户不需要关注子链上的每个动态,只需要关注跟自己的 token 有关的动态。在下文中将介绍具体细节。 存款(Deposits)Plasma Cash 中的每次存款操作都会对应产生一个 NFT(non-fungible token)[2]。NFT 可以简单理解为“不可互换的 token”,即每个 token 都是独一无二的,由唯一的 ID 标记。以太坊官方为 NFT 提供了 ERC721 标准[3],在之前火爆到阻塞以太坊的 CryptoKitties 就是由 ERC721 合约实现的。 在 Plasma Cash 中,当用户向 Plasma 合约发送存款交易时,合约会生成一个与存款等值的 token,并给这个 token 分配一个唯一的 ID。如果一个用户分别执行两次存款操作,且每次存款都是 5 ETH,那么他将得到相等价值的两个完全不同的 token。和 Plasma MVP 一样,每次存款操作都会使得 Plasma 合约产生一个只包含这个存款交易的区块。 Plasma Cash 区块Plasma Cash 中的每个 token 都被分配唯一的 ID,因此可以按 ID 的顺序存储每个 token 的交易历史。Plasma Cash 的区块按 token ID 的顺序给每个 token 分配了一个插槽(slot),每个插槽会记录这个 token 是否被交易的信息。例如在下图(来源[4])的区块中,包含 4 个 token,id 分别是 #1,#2,#3,#4。其中 #1,#2,#3 被标记为没有被花费,而 #4 由用户 A 发送给用户 B。 从上面这个例子中我们可以看到,每个插槽记录了其所对应的 token 在当前区块中的交易状态,所有存储了某个 token 的交易状态的区块按时间顺序连在一起就构成了这个 token 的全部交易历史。每当一个 token 被分配了一个 id,之后的所有交易状态都会被保存在每个区块相同的插槽中,也不会被其它 token 取代。因此,用户只需要关注每个区块中存储属于自己的 token 的状态,完全不用关心别的插槽存储的内容。 交易与验证由于 Plasma Cash 中的节点只追踪属于自己的 token 的交易历史,因此当有交易发生时,token 的发送者要向接收者提供关于这个 token 所有的交易历史(从存款交易开始)以便接收者验证。从下图(来源[4])的例子中可以看到 4 个区块中所记录的 4 个 token 的交易历史。 截止到区块 #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 被安全交易的前提。 稀疏梅克尔树(Sparse Merkle Tree)在上文中我们已经了解到一个交易的成功的前提是需要发送方提供关于一个 token 的完整交易历史。完整的交易历史既包括这个 token 在哪些区块被交易的信息,也包括这个 token 在哪些区块没有被交易的信息。我们都知道,在区块链中,使用梅克尔树(Merkle Tree,MT)构造梅克尔证明(Merkel Proof, MP)可以在 O(logN)的时间复杂度验证一个交易是否存在一个区块中。但想要证明一个交易没有存在一个区块中,使用标准的梅克尔树却没那么容易。因此,Plasma Cash 中使用了一种称为稀疏梅克尔树(Sparse Merkle Tree,SMT)的数据结构存储交易数据,能够在O(logN)的时间复杂度验证一个交易不存在。 SMT 实际上一点都不复杂,它的叶子节点是按数据集中的元素序号顺序排列的。如果某个叶子节点对应的元素为空,那么该叶子节点将存储一个特定的值(例如 0 的哈希值)。一个简单的 SMT 示例如下图(来源[5])所示。 扩展到 Plasma Cash 中,SMT 的叶子节点对应了区块中给每个 token 分配的插槽,按照每个 token 的 ID 排序。每个叶子节点存储对应的 token 的交易信息,如果 token 在这个区块中没有被交易,则相应的叶子节点存储的值为 null。 以上图为例,如果需要证明交易 A 存在,就像在标准的 MT 中一样,需要构造 MP:H(null) 和 H(H(null) + H(D))。如果需要证明 B 不存在,同样很简单,我们已经知道 B 的位置是第二个叶子节点,如果 B 不存在,那么该节点存储的值应该为 null。因此就像在标准的 MT 中证明存在的 MP 一样,只不过需要加上 H(null) 作为 MP 的一部分,即 MP:H(null)、H(A)和 H(H(null)+H(D))。 取款/退出(Withdrawl/Exit)Plasma Cash 中的取款操作在流程上跟 Plasma MVP 大体相同,都要从提交取款申请开始,经历争议期之后才能完成。由于 Plasma Cash 中采用的数据结构不同,在取款时需要提交的 token 所有权证明不同,因此当争议发生时需要提交的争议证明也不同。 提交取款申请在向 Plasma 合约提交关于某个 token 的取款申请时,需要提供关于这个 token 最近的两次交易证明。例如,在上图中,假如 G 想要取走 token #2 到主链,那么他需要提交关于 F => G 以及 E => F 的 Merkle Proof。 提交争议取款者在提交了取款申请之后同样需要支付一定的保证金,并等待一段时间的争议期。在这期间如果有其它节点提交了有效的争议证明,那么取款者不但无法完成取款操作,也会损失全部或部分的保证金。 目前 Plasma Cash 支持三种争议证明,分别应对三种不同的攻击场景(具体会在后文分析): 已花费证明。如果能证明正在取款的 token 已经被花费,那么取款立即被取消; 双花证明。如果能证明取款申请中提供的两次交易证明中间还有别的交易,即发生了双花,那么取款立即被取消; 非法交易历史证明。用户还可以对正在取款的 token 的其它交易历史提出争议。这种争议不会立刻阻断取款,而是强制取款者提交其它交易证明来反驳争议,如果没有在规定时间内反驳,则取款被取消。 攻击场景在这一节将讨论已有的 3 种攻击场景以及如何构造争议分别应对这些攻击[6]。在这里假设 Plasma Cash 中存在不可信的 operator 接收所有的交易并构造区块。 发送交易后立即退出如下图(来源[7])所示,假设攻击者 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 的交易是不能被当做争议证明的,只有最接近被争议的交易的下一个交易证明有效。 双花攻击双花攻击需要 operator 配合,将含有已经被花费的 token 的交易打包入下一个区块中。如下图所示(来源[7]),攻击者 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。 取款包含非法交易历史这种攻击需要联合比较多的同谋者。如下图所示,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 必须在一定的时间内提供合法的交易证明,否则取款失效。 相关项目 Talk is cheap, show me your code. 目前已经有许多机构和公司已经实现了 Plasma Cash,但实现的语言和细节有所不同: Loom Network [8] Omisego [9] Wolk [10] Lucidity [11] 总结本篇介绍了 Plasma 框架下的基于 NFT 的项目 Plasma Cash。Plasma Cash 给每个新转移的 token 分配一个唯一的 token ID,并且用稀疏梅克尔树存储交易,使得用户可以只关注跟自己的 token 有关的动态,而不需要关注其它 token。Plasma Cash 可以被看作 Plasma 逐渐迈向成熟的一步,已经有很多公司使用 Plasma Cash 搭建自己的平台和应用,例如 Loomnetwork 公司搭建了自己的 Plasma Cash 子链并且编写了 SDK 支撑开发者在上面开发新的应用。然而 Plasma Cash 本身仍然存在较多的问题,例如 token 无法被分隔合并、需要提交的证明过长等。在接下来的文章中还会继续跟进 Plasma 最新的进展。 相关资源 https://ethresear.ch/t/plasma-cash-plasma-with-much-less-per-user-data-checking/1298 https://en.wikipedia.org/wiki/Non-fungible_token http://erc721.org/ https://github.com/ethsociety/learn-plasma https://medium.com/@kelvinfichter/whats-a-sparse-merkle-tree-acda70aeb837 https://karl.tech/plasma-cash-simple-spec/ https://github.com/loomnetwork/plasma-paper/blob/master/plasma_cash.pdf https://github.com/loomnetwork/plasma-cash https://github.com/omisego/plasma-cash https://github.com/wolkdb/deepblockchains/tree/master/Plasmacash https://github.com/luciditytech/lucidity-plasma-cash 本文的作者是盖盖,他的微信公众号: chainlab 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Plasma</category>
</categories>
<tags>
<tag>以太坊</tag>
<tag>Plasma</tag>
<tag>扩容</tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入理解Plasma(三)Plasma MVP]]></title>
<url>%2F2018%2F11%2F03%2Fplasma-mvp%2F</url>
<content type="text"><![CDATA[这一系列文章将围绕以太坊的二层扩容框架 Plasma,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要介绍 Plasma 的一个最小实现 Plasma MVP(Minima Viable Plasma)。 在上一篇文章中我们已经理解了 Plasma 中的一些关键操作,但是 Plasma 是一套框架,如果脱离了实际的应用,仍然很难彻底理解它。因此本篇将详细介绍 Plama 的第一个项目 Plasma MVP(Minimal Viable Plasma),即在 Plasma 框架下的最基础的实现。Plasma MVP 是 Vitalic 和他的团队在 2018 年初提出的基于 UTXO 模型实现的 Plasma 设计标准[1],它以最简单的方式实现了链下交易,但无法支持复杂的计算,例如脚本(Script)和智能合约。在阅读下面的内容之前,请确保已经理解了这个系列之前的文章。 整个 Plasma MVP 的生命周期可以通过下面这幅图表现出来: Plasma 合约首先需要将 Plasma 合约部署到主链(以太坊)上作为主链和子链沟通的媒介。Plasma 合约会处理由子链提交的区块,并且将区块的哈希值存在主链上。除此之外,还会处理用户的存款(deposit)、取款(withdrawal/exit)以及争议(challenge)操作。 Plasma 合约中主要包括的数据结构有: Owner:合约的拥有者(即部署合约交易的发送者)的地址,即部署合约交易的发送者; Plasma 区块列表:每个 Plasma 区块中存储了(1)区块的 Merkle root(2)区块提交的时间; 退出列表:即提交了退出申请的列表,每个退出申请存储了(1)申请者的地址(2)申请退出的 UTXO 的位置。 Plasma 合约中主要包括的函数有: submitBlock(bytes32 root):向主链提交一个区块,仅仅提交区块中所有交易的 Merkle root; deposit():生成一个只包含一个交易的区块,这个交易中包含与 msg.value 值相等的 UTXO; startExit():执行给定 UTXO 的退出操作; challengeExit():向某个正在执行的退出提出争议。 Operator在前面的文章中我们已经知道 Plasma 子链是一个独立的区块链,那么也就有独立的共识机制。在 Plasma MVP 中采用的共识机制就是 PoA(Proof of Authority),即参与共识的只有唯一一个矿工,称为 Operator。Operator 负责处理所有子链上发生的交易,将其打包成区块存储在子链上,并且周期性地向 Plasma 合约提交区块,将子链上的状态(区块的哈希值)提交到主链共识。那么,既然 Operator 是唯一的矿工,这不就意味着 Plasma 违背了去中心化的初衷了吗?其实,这是去中心化向执行效率的妥协。在之前的文章中也提到过,Plasma 的安全基础依赖于底层的区块链,只要底层的区块链能够保证安全,那么在 Plasma 子链上发生的最差结果也只是迫使用户退出子链,而不会造成资产损失。 Operator 可以采用最简单的 REST API 方式实现,子链中的用户可以通过调用简单的 API 获取到子链中区块的数据。 存款(deposit)用户 Alice 通过存款(deposit)操作向 Plasma 合约发送带有一定数额的以太币或 ERC20 token 加入 Plasma Chain,这时 Plasma 合约会执行 deposit() 函数,生成一个只包含一个交易的区块,这个交易的 UTXO 记录了 Alice 从主链转移到子链的数额。当这个区块被主链确认后,Alice 就可以使用新生成的 UTXO 向其它用户发送交易了。 交易(transaction)在 Plasma MVP 中,所有用户发送的交易都是直接发送给 Operator,当积累了一定数量的交易后,由 Operator 将交易打包成区块。这里需要注意的是,由于 Plasma MVP 采用的是 UTXO 模型,所以即使交易的收款方不存在,交易也是成立的。 在子链上 Alice 向 Bob 发送一个交易的流程如下: Alice 首先需要得到 Bob 在子链上的地址; Alice 将一个或多个 UTXO 作为输入构造交易发送到 Bob 的地址,并对交易签名; 等待该交易被打包到区块中; Alice 向 Bob 发送确认消息,并且使用相同的私钥签名。 生成区块在 Plasma MVP 中,一个 Plasma 区块产生的情况只有两种:一种是 Operator 打包生成区块,另外一种是当用户执行 deposit 操作时,由 Plasma 合约直接生成一个只包含一个交易的区块。 监视子链为了保证子链上资产的安全,用户需要周期性地检查子链上的数据,保证没有恶意交易产生。用户需要运行一种自动化的软件(例如钱包),每隔一段时间下载子链中的区块数据,检查每个区块中的交易,如果有恶意交易产生,立即退出子链。 取款/退出(withdrawal/exit)当 Alice 想要退出子链时,需要向 Plasma 合约发送一个 exit 交易,申请中需要包含(1)所要退出的 UTXO 的位置,包括区块号(blknum)、区块内交易号(txindex)以及交易内输出号(outindex)(2)包含该 UTXO 的交易(3)该交易的 Merkle proof(4)用于生成该 UTXO 所涉及的之前一系列交易的确认签名。除此之外,exit 交易中还要包含“退出押金(exit bond)”。如果这个 exit 被 challenge 成功,那么取款的操作将被取消,而且退出押金将被发送给提出 challenge 的用户。 之后这个申请会被放入一个优先队列中,通过这个公式计算优先级: Priority = blknum 1000000000 + txindex 10000 + oindex 之所以采用这种优先队列的方式处理取款顺序的原因是保证旧的 UTXO 总能优先于新的 UTXO 被取出。也就是说,当有恶意交易(例如双花等)产生时,所有在恶意交易发生之前的交易都可以被优先取出。那么如何解决在恶意交易之后被确认的交易的取款问题呢?Plasma MVP 采用了“确认签名(Confirmation Signatures)”的机制,在下一小节我们将介绍这一机制是如何工作的。 确认签名(Confirmation Signatures)在 Plasma MVP 中,用户的退出顺序以所要退出的 UTXO 所在的交易的位置为准。假如 operator 作恶,在一个合法的交易之前插入一个非法的交易,那么当用户执行取款时,由于非法交易可以先被取出,因此当执行到该用户的交易时,可能 Plasma 合约中的资产已经被取空。为了解决这个问题,Plasma MVP 采用了“确认签名”机制,例如当 Alice 产生一个交易时,她首先会对交易签名。当该交易被打包入区块后,Alice 还需要对该交易进行一次签名,即“确认签名”。 引入确认签名机制后,当 Alice 发现在一个区块中自己的合法交易之前存在非法交易时,可以拒绝对自己的交易进行“确认签名”,同时申请取款。这样可以使得当前的交易失效,保证自己之前“确认签名”后的交易可以优先于非法交易之前取出。 这种确认签名机制极大地破坏了用户体验,用户每产生一个交易都要经历签名->等待确认->确认签名。而且由于确认签名也需要占据 Plasma 区块的空间,因此也降低了子链的可扩展性。为了解决这个问题,Plasma 的研究人员提出了扩展版本 More Viable Plasma 移除了确认签名的要求[2]。 争议(Challenge)每个取款操作都会经历一个争议期。例如在 Alice 的某个 UTXO 退出子链的过程中,如果 Bob 在争议期内发现有恶意行为发生,他可以提出一个争议(challenge)。一个争议需要给出针对的 UTXO 的位置,以及该 UTXO 被花费的证明,即该 UTXO 已经存在于某个交易中,且这个交易已经被打包到区块。 合约通过调用 challengeExit() 函数执行一个争议,争议成功后会取消正在执行的取款操作,并将提交取款申请所冻结的押金发送给 Bob。 攻击场景在 Plasma 子链中主要存在两种攻击场景: Alice 试图忽视在子链中转移给 Bob 的资产,使用最初加入 Plasma 子链时的交易证明向主链提出取款申请。 Operator 生成一个恶意交易,占有其他用户的资产,并且尝试退出子链。 下面对这两个攻击场景进行分析,观察 Plasma MVP 如何保证资产的安全性: 场景1 Alice 使用最初加入子链时生成的交易作为证据向主链提出取款申请; Bob(或者其他任意用户)拥有 Alice 申请退出的 UTXO 被花费的交易证明,并将此作为证据向主链提出一个争议; 争议生效,Alice 的退出申请被驳回,同时将 Alice 申请退出的押金发送给 Bob; Alice 的攻击失效。 场景2 Operator 创建了一个非法交易,并且将其打包生成区块之后在主链得到确认; Operator 提交取款申请,打算将 Alice 的资产取走; 在争议期内,Alice 发现了 Operator 的恶意行为,立即提出取款申请,退出子链; 由于 Alice 的申请优先级较高,因此会在 Operator 之前退出; Operator 的攻击失效。 相关项目 Talk is cheap, show me your code. 目前已经有许多机构和公司已经实现了 Plasma MVP,但实现的语言和细节有所不同: FourthState Lab[3] Omisego[4] Kyokan[5] 总结本文介绍了 Plasma 的最小实现版本 Plasma MVP,虽然采用最简单的 UTXO 模型,但已经足够体现出 Plasma 的核心思想。在 Plasma MVP 中,用户资产的安全主要依赖于用户及时发现恶意行为,并退出子链。接下来的文章将会介绍另外一个稍微复杂一点的项目,Plasma Cash。 相关资源 https://ethresear.ch/t/minimal-viable-plasma/426 https://ethresear.ch/t/more-viable-plasma/2160 https://github.com/fourthstate https://github.com/omisego/plasma-mvp https://github.com/kyokan/plasma 本文的作者是盖盖,他的微信公众号: chainlab 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Plasma</category>
</categories>
<tags>
<tag>以太坊</tag>
<tag>Plasma</tag>
<tag>扩容</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊钱包开发系列4 - 发送Token(代币)]]></title>
<url>%2F2018%2F10%2F26%2Feth-web-wallet_4%2F</url>
<content type="text"><![CDATA[以太坊去中心化网页钱包开发系列,点链接观看视频课程,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:创建钱包账号、账号Keystore文件导入导出、展示钱包信息及发起签名交易、发送Token(代币),本文是第四篇,Token(代币、通证)是以太坊的一大特色,既然开发钱包,则发送Token 功能必不可少。 合约 ABI 信息首先我们需要明白,进行Token转账的时候,其实是在调用合约的转账函数,而要调用一个合约的函数,需要知道合约的 ABI 信息。 其次 通常我们所说的Token, 其实指的是符合 ERC20 标准接口的合约, ERC20 接口定义如下: 1234567891011121314151617181920contract ERC20Interface { string public constant name = "Token Name"; string public constant symbol = "SYM"; uint8 public constant decimals = 0; function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function approve(address spender, uint tokens) public returns (bool success); function transfer(address to, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens);} ABI 全称是 Application Binary Interface,它就是合约接口的描述,因此有了合约的接口定义,就可以很容易通过编译拿到ABI 信息,比如像下图在Remix 的编译选项卡就可以直接复制ABI。 生成的 ABI 描述大概长这样: 12345678910111213141516171819202122232425262728293031323334353637[... { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "tokenOwner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, ...] 它是一个JSON形式的数组,数组里每一个元素,都是对函数接口的描述,在外部调用合约的时候就需要遵从这个接口,以上面的接口为例,通常一个接口描述包含下述几个字段: name: 函数会事件的名称 type: 可取值有function,constructor,fallback,event inputs: 函数的输入参数,每个参数对象包含下述属性: name: 参数名称 type: 参数的规范型(Canonical Type)。 outputs: 一系列的类似inputs的对象,如果无返回值时,可以省略。 constant: true表示函数声明自己不会改变状态变量的值。 payable: true表示函数可以接收ether,否则表示不能。 接下来在构造合约对象就需要是使用ABI。 构造合约对象ethers.js 构造合约对象很简单,仅需要提供三个参数给ethers.Contract构造函数,代码如下: 123var abi = [...];var addr = "0x...";var contract = new ethers.Contract(address, abi, provider); 合约的地址在合约部署之后,可以获得,关于Token合约部署及ERC20相关的概念,这里不展开讲,不熟悉的同学,可以参考我另一篇文章创建自己的数字货币。 只有就可以是使用 contract 对象来调用Token合约的函数。 获取Token余额及转移Token获取Token余额结合UI来实现以下获取Token余额,UI如下: 在HTML里,定义的标签如下: 123456<tr> <th>TT Token:</th> <td> <input type="text" readonly="readonly" class="readonly" id="wallet-token-balance" value="0.0" /></div> </td></tr> 对应的逻辑代码也很简单: 12345 var tokenBalance = $('#wallet-token-balance'); // 直接调用合约方法contract.balanceOf(activeWallet.address).then(function(balance){ tokenBalance.val(balance);}); 转移Token转移Token的UI效果如下: 界面的HTML代码如下: 1234567891011121314151617<h3>转移代币:</h3><table> <tr> <th>发送至:</th> <td><input type="text" placeholder="(target address)" id="wallet-token-send-target-address" /></td> </tr> <tr> <th>金额:</th> <td><input type="text" placeholder="(amount)" id="wallet-token-send-amount" /></td> </tr> <tr> <td> </td> <td> <div id="wallet-token-submit-send" class="submit disable">发送</div> </td> </tr></table> 上面定义了两个文本输入框及一个“发送“按钮,在逻辑处理部分,转移Token代币尽管和获取余额类似,同样是调用合约的方法,不过转移代币需要发起一个交易,因此需要测量gas 消耗。点击发送时运行一下(关键)代码: 1234567891011121314151617181920212223242526var inputTargetAddress = $('#wallet-token-send-target-address');var inputAmount = $('#wallet-token-send-amount');var submit = $('#wallet-token-submit-send');var targetAddress = ethers.utils.getAddress(inputTargetAddress.val());var amount = inputAmount.val();submit.click(function() {// 先计算transfer 需要的gas 消耗量,这一步有默认值,非必须。 contract.estimate.transfer(targetAddress, amount) .then(function(gas) { // 必须关联一个有过签名钱包对象 let contractWithSigner = contract.connect(activeWallet); // 发起交易,前面2个参数是函数的参数,第3个是交易参数 contractWithSigner.transfer(targetAddress, amount, { gasLimit: gas, // 偷懒,直接是用 2gwei gasPrice: ethers.utils.parseUnits("2", "gwei"), }).then(function(tx) { console.log(tx); // 介绍刷新上面的Token余额,重置输入框 }); });} 上述有一个地方都要注意一下,在合约调用 transfer 之前, 需要连接一个signer,因为发起交易的时候需要用它来进行签名,在ethers.js API里 Wallet 是 signer(抽象类)的实现类。 所有会更改区块链数据的函数都需要关联签名器,如果是视图函数则只需要连接provider。 ethers.js 的 Contract 提供了一个非常方便方法:contract.estimate.functionName 来计算预测交易的gasLimit。 在发起交易的时候,可以提供一个可选的Overrides参数,在这个参数里可以指定如交易的 gasLimit 、 gasPrice,如果我们不指定这个参数时,会默认使用 contract.estimate 获得的值作为 gasLimit,以及 provider.getGasPrice() 的值来指定 gasPrice。 哈哈,恭喜大家,到这里这里就完整的实现了一个基于以太坊去中心化网页钱包。 这是一条硬广,欢迎订阅深入浅出区块链技术小专栏,目前仅需69元, 订阅就可以查看完整源码,还有其他惊喜哦~。戳链接收看详细的视频课程讲解。 参考文档 Ethers.js 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。 深入浅出区块链知识星球最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>以太坊</tag>
<tag>钱包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊钱包开发系列3 - 展示钱包信息及发起签名交易]]></title>
<url>%2F2018%2F10%2F26%2Feth-web-wallet_3%2F</url>
<content type="text"><![CDATA[以太坊去中心化网页钱包开发系列,点链接观看视频课程,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:创建钱包账号、账号Keystore文件导入导出、展示钱包信息及发起签名交易、发送Token(代币),这是第三篇介绍使用ethers.js的钱包对象获取相关信息及发起你离线交易。 使用 Provider 连接以太坊网络我们前面两篇文章介绍创建(或导入)钱包账号的过程都是是离线的,即不需要依赖以太坊网络即可创建钱包账号,但如果想获取钱包账号的相关信息,比如余额、交易记录,发起交易的话,就需要让钱包连上以太坊的网络。 不管是在 Web3 中,还是Ethers.js 都是使用 Provider 来进行网络连接的,Ethers.js 提供了集成多种 Provider 的方式: Web3Provider: 使用一个已有的web3 兼容的Provider,如有MetaMask 或 Mist提供。 EtherscanProvider 及 InfuraProvider: 如果没有自己的节点,可以使用Etherscan 及 Infura 的Provider,他们都是以太坊的基础设施服务提供商,Ethers.js 还提供了一种更简单的方式:使用一个默认的provider, 他会自动帮我们连接Etherscan 及 Infura。 1let defaultProvider = ethers.getDefaultProvider('ropsten'); 连接Provider, 通常有一个参数network网络名称,取值有: homestead, rinkeby, ropsten, kovan, 关于Provider的更多用法,可以参考Ethers.js Provider。 JsonRpcProvider 及 IpcProvider: 如果有自己的节点可以使用,可以连接主网,测试网络,私有网络或Ganache,这也是本系列文章使用的方式。 使用钱包连接Provider的方法如下: 12345// 连接本地的geth 节点,8545是geth 的端口var provider = new ethers.providers.JsonRpcProvider("http://127.0.0.1:8545");// wallet 为前两篇文章中生成的钱包对象, activeWallet就是后面可以用来请求余额发送交易的对象var activeWallet = wallet.connect(App.provider); 启动geth的需要注意一下,需要使用 --rpc --rpccorsdomain 开启 RPC通信及跨域, 展示钱包详情:查询余额及Nonce连接到以太坊网络之后,就可以向网络请求余额以及获取账号交易数量,使用一下API: 12345activeWallet.getBalance().then(function(balance) {});activeWallet.getTransactionCount().then(function(transactionCount) {}); activeWallet就是后面可以用来请求发送交易的对象 1234567891011121314151617181920212223<h3>钱包详情:</h3><table> <tr><th>地址:</th> <td> <input type="text" readonly="readonly" class="readonly" id="wallet-address" value="" /></div> </td> </tr> <tr><th>余额:</th> <td> <input type="text" readonly="readonly" class="readonly" id="wallet-balance" value="0.0" /></div> </td> </tr> <tr><th>Nonce:</th> <td> <input type="text" readonly="readonly" class="readonly" id="wallet-transaction-count" value="0" /></div> </td> </tr> <tr><td> </td> <td> <div id="wallet-submit-refresh" class="submit">刷新</div> </td> </tr></table> js处理的逻辑就是获取信息之后,填充相应的控件,代码如下: 1234567891011121314151617181920var inputBalance = $('#wallet-balance');var inputTransactionCount = $('#wallet-transaction-count');$("#wallet-submit-refresh").click(function() {// 获取余额时, 包含当前正在打包的区块 activeWallet.getBalance('pending').then(function(balance) { // 单位转换 wei -> ether inputBalance.val(ethers.utils.formatEther(balance, { commify: true })); }, function(error) { }); activeWallet.getTransactionCount('pending').then(function(transactionCount) { inputTransactionCount.val(transactionCount); }, function(error) { });});// 模拟一次点击获取数据$("#wallet-submit-refresh").click(); 发送签名交易之前我们有一篇文章:如何使用Web3.js API 在页面中进行转账介绍过发起交易,不过当时的签名是利用MetaMask来完成的,现在我们要完成一个钱包,必须要发送一个签名交易,签名交易也称为离线交易(因为这个过程可以离线进行:在离线状态下对交易进行签名,然后把签名后的交易进行广播)。 尽管 Ethers.js 提供了非常简洁的API来发送签名交易,但是探究下简洁API背后的细节依然会对我们有帮助,这个过程大致可分为三步: 构造交易 交易签名 发送(广播)交易 构造交易先来看看一个交易长什么样子: 12345678910const txParams = { nonce: '0x00', gasPrice: '0x09184e72a000', gasLimit: '0x2710', to: '0x0000000000000000000000000000000000000000', value: '0x00', data: '0x7f7465737432000000000000000000000000000000000000000000000000000000600057', // EIP 155 chainId - mainnet: 1, ropsten: 3 chainId: 3} 发起交易的时候,就是需要填充每一个字段,构建这样一个交易结构。to 和 value: 很好理解,就是用户要转账的目标及金额。data: 是交易时附加的消息,如果是对合约地址发起交易,这会转化为对合约函数的执行,可参考:如何理解以太坊ABInonce: 交易序列号chainId: 链id,用来去区分不同的链(分叉链)id可在EIP-55查询。 nonce 和 chainId 有一个重要的作用就是防止重放攻击,如果没有nonce的活,收款人可能把这笔签名过的交易再次进行广播,没有chainId的话,以太坊上的交易可以拿到以太经典上再次进行广播。 gasPrice和gasLimit: Gas是以太坊的工作计费机制,是由交易发起者给矿工打包的费用。上面几个参数的设置比较固定,Gas的设置(尤其是gasPrice)则灵活的多。 gasLimit 表示预计的指令和存储空间的工作量,如果工作量没有用完,会退回交易发起者,如果不够会发生out-of-gas 错误。一个普通转账的交易,工作量是固定的,gasLimit为21000,合约执行gasLimit则是变化的,也许有一些人会认为直接设置为高一点,反正会退回,但如果合约执行出错,就会吃掉所有的gas。幸运的是web3 和 ethers.js 都提供了测算Gas Limit的方法,下一遍发送代币 gasPrice是交易发起者是愿意为工作量支付的单位费用,矿工在选择交易的时候,是按照gasPrice进行排序,先服务高出价者,因此如果出价过低会导致交易迟迟不能打包确认,出价过高对发起者又比较亏。 web3 和 ethers.js 提供一个方法 getGasPrice() 用来获取最近几个历史区块gas price的中位数,也有一些第三方提供预测gas price的接口,如:gasPriceOracle 、 ethgasAPI、 etherscan gastracker,这些服务通常还会参考当前交易池内交易数量及价格,可参考性更强, 常规的一个做法是利用这些接口给用户一个参考值,然后用户可以根据参考值进行微调。 交易签名在构建交易之后,就是用私钥对其签名,代码如下: 123const tx = new EthereumTx(txParams)tx.sign(privateKey)const serializedTx = tx.serialize() 代码参考ethereumjs-tx 发送(广播)交易然后就是发送(广播)交易,代码如下: 1234web3.eth.sendRawTransaction(serializedTx, function (err, transactionHash) { console.log(err); console.log(transactionHash);}); 通过这三步就完成了发送签名交易的过程,ethers.js 里提供了一个简洁的接口,来完成所有这三步操作(强调一下,签名已经在接口里帮我们完成了),接口如下: 1234567activeWallet.sendTransaction({ to: targetAddress, value: amountWei, gasPrice: activeWallet.provider.getGasPrice(), gasLimit: 21000, }).then(function(tx) { }); 用ethers.js 实现发送交易先来看看发送交易的UI界面: 1234567891011121314<h3>以太转账:</h3><table> <tr> <th>发送至:</th> <td><input type="text" placeholder="(target address)" id="wallet-send-target-address" /></td> </tr> <tr> <th>金额:</th> <td><input type="text" placeholder="(amount)" id="wallet-send-amount" /></td> </tr> <tr> <td> </td> <td> <div id="wallet-submit-send" class="submit disable">发送</div> </td> </tr></table> 上面主要定义了两个文本输入框及一个“发送“按钮,在点击发送时运行一下(关键)代码: 123456789101112131415161718var inputTargetAddress = $('#wallet-send-target-address');var inputAmount = $('#wallet-send-amount');var submit = $('#wallet-submit-send');submit.click(function() {// 得到一个checksum 地址 var targetAddress = ethers.utils.getAddress(inputTargetAddress.val());// ether -> wei var amountWei = ethers.utils.parseEther(inputAmount.val()); activeWallet.sendTransaction({ to: targetAddress, value: amountWei, // gasPrice: activeWallet.provider.getGasPrice(), (可用默认值) // gasLimit: 21000, }).then(function(tx) { console.log(tx); });}) 哈哈哈~, 干活介绍到这里,现在夹带一点私货,有到了推广时间了,完整源码请订阅深入浅出区块链技术小专栏查看,赶紧订阅吧,走过路过,不容错过。戳链接收看详细的视频课程讲解。 参考文档 ethereum-tx EIP-55 Ethers.js 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。 深入浅出区块链知识星球最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>以太坊</tag>
<tag>钱包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊钱包开发系列2 - 账号Keystore文件导入导出]]></title>
<url>%2F2018%2F10%2F25%2Feth-web-wallet_2%2F</url>
<content type="text"><![CDATA[以太坊去中心化网页钱包开发系列,点链接观看视频课程,将从零开始开发出一个可以实际使用的钱包,本系列文章是理论与实战相结合,一共有四篇:创建钱包账号、账号Keystore文件导入导出、展示钱包信息及发起签名交易、发送Token(代币),这是第二篇,主要介绍钱包账号导出与导入,将对Keystore文件的生成的原理进行介绍。 如何导入Geth创建的账号?在上一篇文章,介绍了如何使用私钥及助记词来创建账号,如果是使用已有的私钥及助记词,这其实也是账号导入的过程。 有一些同学会问,我的账号是Geth生成的,如何导入到钱包呢?使用Geth的同学,应该知道Geth在创建账号时会生成一个对应keystore JSON文件,Keystore文件存储加密后的私钥信息,因此我们需要做的就是导入这个Keystore文件,这个文件通常在同步区块数据的目录下的keystore文件夹(如: ~/.ethereum/keystore)里。 尽管在ethers.js 中,简单的使用一个函数就可以完成keystore文件的导入,不过理解Keystore 文件的作用及原理还是非常有必要的,当然如果你是在没有兴趣,可以直接跳到本文最后一节:使用ethers.js 实现账号导出导入。 详细解读 Keystore 文件为什么需要 Keystore 文件通过这篇文章理解开发HD 钱包涉及的 BIP32、BIP44、BIP39,私钥其实就代表了一个账号,最简单的保管账号的方式就是直接把私钥保存起来,如果私钥文件被人盗取,我们的数字资产将洗劫一空。 Keystore 文件就是一种以加密的方式存储密钥的文件,这样的发起交易的时候,先从Keystore 文件是使用密码解密出私钥,然后进行签名交易。这样做之后就会安全的多,因为只有黑客同时盗取 keystore 文件和密码才能盗取我们的数字资产。 Keystore 文件如何生成的 以太坊是使用对称加密算法来加密私钥生成Keystore文件,因此对称加密秘钥(注意它其实也是发起交易时需要的解密秘钥)的选择就非常关键,这个秘钥是使用KDF算法推导派生而出。因此在完整介绍Keystore 文件如何生成前,有必要先介绍一下KDF。 使用 KDF 生成秘钥密码学KDF(key derivation functions),其作用是通过一个密码派生出一个或多个秘钥,即从 password 生成加密用的 key。 其实在理解开发HD 钱包涉及的 BIP32、BIP44、BIP39中介绍助记词推导出种子的PBKDF2算法就是一种KDF函数,其原理是加盐以及增加哈希迭代次数。 而在Keystore中,是用的是Scrypt算法,用一个公式来表示的话,派生的Key生成方程为: 1DK = Scrypt(salt, dk_len, n, r, p) 其中的 salt 是一段随机的盐,dk_len 是输出的哈希值的长度。n 是 CPU/Memory 开销值,越高的开销值,计算就越困难。r 表示块大小,p 表示并行度。 Litecoin 就使用 scrypt 作为它的 POW 算法 实际使用中,还会加上一个密码进行计算,用一张图来表示这个过程就是: 对私钥进行对称加密上面已经用KDF算法生成了一个秘钥,这个秘钥就是接着进行对称加密的秘钥,这里使用的对称加密算法是 aes-128-ctr,aes-128-ctr 加密算法还需要用到一个参数初始化向量 iv。 Keystore文件好了,我们现在结合具体 Keystore文件的内容,就很容易理解了Keystore 文件怎么产生的了。 123456789101112131415161718192021{ "address":"856e604698f79cef417aab...", "crypto":{ "cipher":"aes-128-ctr", "ciphertext":"13a3ad2135bef1ff228e399dfc8d7757eb4bb1a81d1b31....", "cipherparams":{ "iv":"92e7468e8625653f85322fb3c..." }, "kdf":"scrypt", "kdfparams":{ "dklen":32, "n":262144, "p":1, "r":8, "salt":"3ca198ce53513ce01bd651aee54b16b6a...." }, "mac":"10423d837830594c18a91097d09b7f2316..." }, "id":"5346bac5-0a6f-4ac6-baba-e2f3ad464f3f", "version":3} 来解读一下各个字段: address: 账号地址 version: Keystore文件的版本,目前为第3版,也称为V3 KeyStore。 id : uuid crypto: 加密推倒的相关配置. cipher 是用于加密以太坊私钥的对称加密算法。用的是 aes-128-ctr 。 cipherparams 是 aes-128-ctr 加密算法需要的参数。在这里,用到的唯一的参数 iv。 ciphertext 是加密算法输出的密文,也是将来解密时的需要的输入。 kdf: 指定使用哪一个算法,这里使用的是 scrypt。 kdfparams: scrypt函数需要的参数 mac: 用来校验密码的正确性, mac= sha3(DK[16:32], ciphertext) 下面一个小节单独分析。 我们来完整梳理一下 Keystore 文件的产生: 使用scrypt函数 (根据密码 和 相应的参数) 生成秘钥 使用上一步生成的秘钥 + 账号私钥 + 参数 进行对称加密。 把相关的参数 和 输出的密文 保存为以上格式的 JSON 文件 如何确保密码是对的?当我们在使用Keystore文件来还原私钥时,依然是使用kdf生成一个秘钥,然后用秘钥对ciphertext进行解密,其过程如下: 此时细心的同学会发现,无论使用说明密码,来进行这个操作,都会生成一个私钥,但是最终计算的以太坊私钥到底是不是正确的,却不得而知。 这就是 keystore 文件中 mac 值的作用。mac 值是 kdf输出 和 ciphertext 密文进行SHA3-256运算的结果,显然密码不同,计算的mac 值也不同,因此可以用来检验密码的正确性。检验过程用图表示如下: 现在我们以解密的角度完整的梳理下流程,就可以得到以下图: 用ethers.js 实现账号导出导入ethers.js 直接提供了加载keystore JSON来创建钱包对象以及加密生成keystore文件的方法,方法如下: 123456789// 导入keystore Json ethers.Wallet.fromEncryptedJson(json, password, [progressCallback]).then(function(wallet) { // wallet }); // 使用钱包对象 导出keystore Json wallet.encrypt(pwd, [progressCallback].then(function(json) { // 保存json }); 现在结合界面来完整的实现账号导出及导入,先看看导出,UI图如下: HTML 代码如下: 1234567891011121314<h3>KeyStore 导出:</h3><table> <tr> <th>密码:</th> <td><input type="text" placeholder="(password)" id="save-keystore-file-pwd" /></td> </tr> <tr> <td> </td> <td> <div id="save-keystore" class="submit">导出</div> </td> </tr></table> 上面主要定义了一个密码输入框和一个导出按钮,点击“导出”后,处理逻辑代码如下: 12345678910111213141516// "导出" 按钮,执行exportKeystore函数 $('#save-keystore').click(exportKeystore); exportKeystore: function() { // 获取密码 var pwd = $('#save-keystore-file-pwd'); // wallet 是上一篇文章中生成的钱包对象 wallet.encrypt(pwd.val()).then(function(json) { var blob = new Blob([json], {type: "text/plain;charset=utf-8"}); // 使用了FileSaver.js 进行文件保存 saveAs(blob, "keystore.json"); }); } FileSaver.js 是可以用来在页面保存文件的一个库。 再来看看导入keystore 文件, UI图如下: 1234567891011121314151617 <h2>加载账号Keystore文件</h2><table> <tr> <th>Keystore:</th> <td><div class="file" id="select-wallet-drop">把Json文件拖动到这里</div><input type="file" id="select-wallet-file" /></td> </tr> <tr> <th>密码:</th> <td><input type="password" placeholder="(password)" id="select-wallet-password" /></td> </tr> <tr> <td> </td> <td> <div id="select-submit-wallet" class="submit disable">解密</div> </td> </tr></table> 上面主要定义了一个文件输入框、一个密码输入框及一个“解密“按钮,因此处理逻辑包含两部分,一是读取文件,二是解析加载账号,关键代码如下: 123456789101112131415 // 使用FileReader读取文件,var fileReader = new FileReader(); fileReader.onload = function(e) { var json = e.target.result; // 从加载 ethers.Wallet.fromEncryptedJson(json, password).then(function(wallet) { }, function(error) { }); };fileReader.readAsText(inputFile.files[0]); 哈哈哈,有到了推广时间了,完整源码请订阅深入浅出区块链技术小专栏查看,赶紧订阅吧,走过路过,不容错过。 戳链接收看详细的视频课程讲解。 参考文档what-is-an-ethereum-keystore-file 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。 深入浅出区块链知识星球最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>以太坊</tag>
<tag>钱包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊钱包开发系列1 - 创建钱包账号]]></title>
<url>%2F2018%2F10%2F25%2Feth-web-wallet_1%2F</url>
<content type="text"><![CDATA[以太坊去中心化网页钱包开发系列,详细的视频课程讲解直接戳链接,本系列将从零开始开发出一个可以实际使用的钱包,本系列是理论与实战相结合,文章一共有四篇:创建钱包账号、账号Keystore文件导入导出、展示钱包信息及发起签名交易、发送Token(代币),这是第一篇,主要介绍钱包将实现哪些功能及怎么创建钱包账号,本钱包是基于ethers.js 进行开发。 去中心化网页钱包先明确一下定义,什么是去中心化钱包,账号秘钥的管理,交易的签名,都是在客户端完成, 即私钥相关的信息都是在用户手中,钱包的开发者接触不到私钥信息。 对应的中心化钱包则是私钥由中心服务器托管,如交易所的钱包就是这种。 网页钱包,或者叫web钱包,是指钱包以网页的形式展现,去中心化网页钱包则交易的签名等操作是在浏览器里完成。其他形式的钱包,如Android钱包或iOS钱包其开发思路和web钱包一样,因此文本对开发其他平台的钱包也有参考意义,不过本系列文章主要侧重在钱包功能的实现,并未过多考虑用户体验。 钱包功能一个钱包通常主要包含的功能有: 账号管理(主要是私钥的管理):创建账号、账号导入导出 账号信息展示:如以太币余额、Token(代币)余额。 转账功能:发送以太币及发送Token(代币) 这些功能将基于 ethers.js 进行开发, ethers.js 和web3.js 一样,也是一套和以太坊区块链进行交互的库,不仅如此,ethers.js 还对BIP 39等相关的提案进行了实现,可以在这个链接阅读其文档。 这些功能主要表现为钱包的两个界面,一个界面是:账号管理,一个界面是进行账号信息展示及转账。下面逐个进行介绍 创建钱包账号读过上一篇文章理解开发HD 钱包涉及的 BIP32、BIP44、BIP39的同学,会知道创建账号,可以有两种方式: 直接生成32个字节的数当成私钥 通过助记词进行确定性推导出私钥 使用随机数作为私钥创建钱包账号即方式一,可以使用ethers.utils.randomBytes生成一个随机数,然后使用这个随机数来创建钱包,如代码: 123var privateKey = ethers.utils.randomBytes(32);var wallet = new ethers.Wallet(privateKey);console.log("账号地址: " + wallet.address); 上面代码的 wallet 是 ethers 中的一个钱包对象,它除了有代码中出现的.address 属性之外,还有如 获取余额、发送交易等方法,在后面的文章会进行介绍。 注意ethers.utils.randomBytes 生成的是一个字节数组,如果想用十六进制数显示出来表示,需要转化为BigNumber代码如下: 12let keyNumber = ethers.utils.bigNumberify(privateKey);console.log(randomNumber._hex); 现在我们结合界面,完整的实现创建账号,其效果图如下,加载私钥时创建账号。 界面代码(HTML)代码如下(主要是在表格中定义个一个输入框及一个按钮): 123456789101112<table> <tr> <th>私钥:</th> <td><input type="text" placeholder="(private key)" id="select-privatekey" /></td> </tr> <tr> <td> </td> <td> <div id="select-submit-privatekey" class="submit">加载私钥</div> </td> </tr></table> 对应的逻辑代码(JavaScript)如下: 123456789101112131415// 使用JQuery获取两个UI标签 var inputPrivatekey = $('#select-privatekey'); var submit = $('#select-submit-privatekey');// 生成一个默认的私钥 let randomNumber = ethers.utils.bigNumberify(ethers.utils.randomBytes(32)); inputPrivatekey.val(randomNumber._hex);// 点击“加载私钥”时, 创建对应的钱包 submit.click(function() { var privateKey = inputPrivatekey.val(); if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; } var wallet = new ethers.Wallet(privateKey)); }); 如果用户提供一个已有账号的私钥,则会导入其原有账号。 通过助记词方式创建钱包账号这是目前主流常见钱包的方式,关于助记词推导过程请阅读理解开发HD 钱包涉及的 BIP32、BIP44、BIP39。 我们需要先生成一个随机数,然后用随机数生成助记词,随后用助记词创建钱包账号,设计到的API有: 12345678910var rand = ethers.utils.randomBytes(16);// 生成助记词var mnemonic = ethers.utils.HDNode.entropyToMnemonic(rand);var path = "m/44'/60'/0'/0/0";// 通过助记词创建钱包ethers.Wallet.fromMnemonic(mnemonic, path); 现在我们结合界面来实现一下通过助记词方式创建钱包账号,其效果图如下: 界面代码(HTML)代码如下(主要是在表格中定义个两个输入框及一个按钮): 12345678910111213141516<table> <tr> <th>助记词:</th> <td><input type="text" placeholder="(mnemonic phrase)" id="select-mnemonic-phrase" /></td> </tr> <tr> <th>Path:</th> <td><input type="text" placeholder="(path)" id="select-mnemonic-path" value="m/44'/60'/0'/0/0" /></td> </tr> <tr> <td> </td> <td> <div id="select-submit-mnemonic" class="submit">推倒</div> </td> </tr></table> 对应的逻辑代码(JavaScript)如下: 1234567891011121314151617 var inputPhrase = $('#select-mnemonic-phrase'); var inputPath = $('#select-mnemonic-path'); var submit = $('#select-submit-mnemonic');// 生成助记词 var mnemonic = ethers.utils.HDNode.entropyToMnemonic(ethers.utils.randomBytes(16)); inputPhrase.val(mnemonic); submit.click(function() { // 检查助记词是否有效。 if (!ethers.utils.HDNode.isValidMnemonic(inputPhrase.val())) { return; }// 通过助记词创建钱包对象 var wallet = ethers.Wallet.fromMnemonic(inputPhrase.val(), inputPath.val()); }); 同样用户可以提供一个其保存的助记词来导入其钱包,有一些遗憾的是,ethers.js 暂时不支持通过添加密码作为Salt来保护种子(也可能是我没有找到,如果知道的同学,希望反馈下),如果需要此功能可以引入bip39 和 ethereumjs-wallet 库来实现,代码可参考理解开发HD 钱包涉及的 BIP32、BIP44、BIP39。 小结其实 ethers 还提供了一个更简单的方法来创建钱包: 12// 直接创建一个随机钱包ethers.Wallet.createRandom(); 完整源码请订阅深入浅出区块链技术小专栏查看, 哈哈,是不是有一点鸡贼,创作不易呀。戳链接收看详细的视频课程讲解。 参考文档:ethers.js 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。 深入浅出区块链知识星球最专业技术问答社区,加入社区还可以在微信群里和300多位区块链技术爱好者一起交流。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>以太坊</tag>
<tag>钱包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入理解Plasma(二)Plasma 细节]]></title>
<url>%2F2018%2F10%2F24%2Fplasma-in-detail%2F</url>
<content type="text"><![CDATA[这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章主要对 Plasma 一些关键操作的细节进行剖析。 在上一篇文章中我们已经理解了什么是 Plasma 框架以及它是如何运行的,这一篇文章将对其运行过程中的一些关键部分,包括 Plasma 提交区块的过程,当有恶意行为发生时如何构建防伪证明以及如何退出 Plasma 子链等进行剖析。需要注意的是,由于 Plasma 是一套框架,因此本文只剖析 Plasma 项目的共性,每一部分的实现细则还是需要参考实际的项目,例如 Plasma MVP(Minimal-Viable-Plasma)和 Plasma Cash 等。 存款(Deposit)Plasma 的主要思想就是将大部分计算过程都转移到链下进行,用户只有在进入和退出 Plasma Chain 的时候需要跟主链上的智能合约交互,这也是所有 Plasma 应用的标准流程。 用户在将主链的资产(如以太币或者其它 ERC20 合约发布的 token)转移到 Plasma Chain 的过程称为存款(Deposit),具体做法是直接向主链上的 Plasma 合约发送以太币或 token。Plasma 合约收到 Deposit 交易后会在子链上创建跟 Deposit 数额一致的交易,并将其打包进区块中,作为存款确认的证明。这个过程如下图所示(来源自[1])。 当用户看到子链上自己之前存款的交易被确认后,就可以在子链上使用这笔资产(给子链上的其他用户发送交易或者退出子链等)。 状态确认(State Commitment)当大部分都转移到链下进行时,需要某种机制确保链下状态的更新得到确认,这样才能保证当有恶意行为发生时,主链可以保证用户不会受到损失。这就是为什么需要状态确认(State Commitment),即子链周期性地将状态更新情况提交到主链进行共识。 然而,将子链中的所有交易都同步到主链显然违反了 Plasma 的初衷,在 Plasma 中,实际向主链提交的是 Merkle Tree 的根哈希。因此子链中的实际交易情况被隐藏,在主链上只能看到子链区块的哈希值。 当有恶意行为发生时,子链网络中的所有用户都可以向主链提交防伪证明,证明成立后,含有恶意交易的区块将被回滚。 防伪证明(Fraud Proof)Plasma 的一个关键设计之一就是允许用户构造防伪证明(Fraud Proof)。防伪证明的意义在于只要发布区块的节点构造了含有恶意交易的区块,那么就要承担被惩罚的风险。每当新的区块被提交到主链上时,会留出一段时间给用户提交防伪证明,如果在这段时间内没有证明被提交,则认为新的区块被验证合法。如果有防伪证明检测到区块中存在恶意交易,则该区块将被舍弃,回滚到上一个被验证合法的区块。Plasma 中的防伪证明主要有以下(但不限于)几种: 资产可花费证明 交易签名有效性证明 存取款证明 至于每种防伪证明的具体形式,则依赖于实际 Plasma 应用的实现细则。 如下图所示(来源自[1]),子链中每个节点都存有 1-4 个区块的数据。假设区块 1-3 已经被验证合法,而区块 4 中存在恶意交易,那么每个节点都可以使用 1-4 个区块中的数据构造防伪证明提交到主链,主链验证后将子链中的状态回滚到区块 1-3。 防伪证明还可以使用零知识证明(zk-SNARKs 或者 STARKs)来构造,但由于目前通过零知识证明生成证明的时间和空间还有待优化,目前设计的 Plasma 并不依赖零知识证明。零知识证明在 Plasma 中的应用是一个很有前景的研究方向,感兴趣的读者可以参考以太坊研究团队关于这方面的研究[2])。 取款(Withdrawal)取款(Withdrawal),顾名思义,就是从子链中的资产取回到主链上,因此取款也被称为退出(Exit)。取款操作可以说是 Plasma 框架中最重要的一步,因为它确保用户可以安全地将子链上的资产转移到主链上。之前的存款以及状态确认步骤已经产生了一些交易数据,并在主链上得到同步,用户可以利用这些数据构造资产证明,之后执行简单取款(Simple Withdrawal)操作退出子链。当有扣留(Withholding)攻击发生(即子链上的矿工恶意扣留区块,意图双花攻击等)时,用户可能无法成功获取数据构造资产证明,这时需要执行批量取款(Mass Withdrawal)操作退出子链。 需要注意的是,当子链中有取款操作发生时,跟这个取款操作相关的账号或者交易都将被禁止。 简单取款(Simple Withdrawal)执行简单取款的条件是所要取回的资产已经在主链和子链上确认。 一个简单取款的执行主要有以下几个步骤: 向主链上的 Plasma 智能合约发送已签名的取款交易。取款的数额可以包含多个输出(UTXO模型),但所有输出必须在同一个子链当中,而且每个输出的余额必须全部取出,不能只取出一部分。取款数额的一部分还需要被当作押金,作为恶意行为的惩罚。 当取款请求发送后,需要等待一段“争议期(Challenge Period)”,这期间其它节点可以构造证据证明该取款中的部分或全部数额已经被花费。如果争议期内有节点提供证明成功,那么取款被取消,而且押金被扣除。 接下来可能还要等待一段时间,直到所有区块高度较低的取款操作都完成。这是为了保证所有取款操作按照“先来后到”的顺序执行。 当争议期结束后,如果没有争议被提出,则认为取款操作成立,取款者将子链资产成功取回到主链。 快速取款(Fast Withdrawal)快速取款(Fast Withdrawal)跟简单取款相比的差别主要是引入一个中间人,白皮书上称为 Liquidity Provider,这里简称为 LP。如果一个用户不想等待很长的争议期(目前的实现至少要一周),那么它可以选择从 LP 这里直接取款,只需要消耗一个交易确认的时间,代价是需要支付给 LP 一定的费用。由于 Plasma 白皮书上关于快速取款的描述太过晦涩,这里主要参考 kfichter 提出的 Simple Fast Withdrawal[3] 来介绍快速取款是如何实现的。 为了实现快速取款,取款方和 LP 可以利用一个流动合约(liquidity contract)。假设取款方是 Alice,她想要执行快速取款将 10 以太币从子链转移到主链。她首先向流动合约发送 10 以太币,注意这里的交易是在子链上进行的。当这个交易被子链打包成区块后,Alice 可以调用合约中的某个退出函数,这时 Alice 将获取一个代表她这笔资产的 token。Bob 作为 LP,他检查了子链上数据之后证明 Alice 的取款没有问题之后愿意以 9 以太币的价格购买这个 token。Alice 将 token 卖给 Bob,获得了 9 以太币,Bob 赚取了 1 以太币。 需要注意的是,实现快速取款的前提条件是没有拜占庭行为发生,即没有扣留区块攻击发生,因为 LP 需要验证取款方的交易历史。 批量取款(Mass Withdrawal)当子链中有拜占庭行为(例如,区块扣留攻击)发生时,将会影响以后生成防伪证明,因此网络中的每个用户都有责任快速退出子链。虽然批量取款(Mass Withdrawal)操作不是必要选择,但当大量用户执行取款时很可能会造成主链拥塞,也会消耗更多的 gas,因此批量取款是当子链受到攻击时更好的选择。 批量取款操作由于所采用的模型(UTXO 模型或者账户模型)不同会有较大的差别,而且目前关于批量取款的操作细节也正在研讨当中,因此这里只对批量取款做简单介绍,想要了解目前研究状态可以参考[4]。 当子链中有拜占庭行为发生时,用户之间可以共同协作执行批量取款。这时会有一个节点扮演取款处理人(Exit Processor)的角色,简称为 EP,负责当前某个批量操作(可以同时有多个批量取款操作发生,但同一个取款申请不能存在于多个批量取款),并且可以收取服务费作为报酬。EP 将构造一个位图(bitmap,即一串0/1)记录哪些资产要执行取款。之后 EP 将利用现有的区块数据检查每个取款是否合法,之后将构造一个批量退出初始化交易(Mass Exit Initiation Transaction,MEIT),并将其发送到主链上。在 MEIT 被主链确认之前,每个用户都可以对这个交易提出异议。当争议期结束,MEIT 被主链确认,批量取款成功。 总结本文主要对 Plasma 框架中一些关键操作进行了比较详细的介绍,但如果不依托于某个实际的 Plasma 项目,对一些细节还是很难理解。因此在后面的文章中将会介绍 Plasma MVP 以及 Plasma Cash。 相关资源 https://plasma.io/ https://ethresear.ch/t/plasma-is-plasma/2195 https://ethresear.ch/t/simple-fast-withdrawals/2128 https://ethresear.ch/t/basic-mass-exits-for-plasma-mvp/3316 本文的作者是盖盖,他的微信公众号: chainlab 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Plasma</category>
</categories>
<tags>
<tag>以太坊</tag>
<tag>Plasma</tag>
<tag>扩容</tag>
</tags>
</entry>
<entry>
<title><![CDATA[深入理解Plasma(一)Plasma 框架]]></title>
<url>%2F2018%2F10%2F20%2Fplasma-framework%2F</url>
<content type="text"><![CDATA[这一系列文章将围绕以太坊的二层扩容框架,介绍其基本运行原理,具体操作细节,安全性讨论以及未来研究方向等。本篇文章作为开篇,主要目的是理解 Plasma 框架。 Plasma 作为以太坊的二层扩容框架,自从 2017 年被 Joseph Poon(Lightning Network 创始人)和 Vitalik Buterin (Ethereum 创始人)提出以来[1],一直是区块链从业人员关注的焦点[2]。首先需要明确的是,Plasma 实质上是一套框架,而不是一个单独的项目,它为各种不同的项目实际项目提供链下(off-chain)解决方案。这也是为什么很多人对 Plasma 感到疑惑的一个重要原因,因为在缺乏实际应用场景的情况下很难将 Plasma 解释清楚。因此,理解 Plasma 是一套框架是理解 Plasma 的关键。 从区块链扩容谈起在介绍 Plasma 之前,不得不先介绍区块链扩容。我们都知道,比特币(Bitcoin)和以太坊(Ethereum)作为目前最广泛使用的区块链平台,面临的最大问题就是可扩展性(Scalability)。这里需要注意的是,区块链中的可扩展性问题并不是单独特指某个问题,而是区块链想要实现 Web3.0[3] 的愿景,为亿万用户提供去中心化服务所要克服的一系列挑战。虽然以太坊号称是“世界计算机”,但这台“计算机”却是单线程的,每秒钟只能处理大约 15 条交易,与目前主流的 Visa 和 MasterCard 动辄每秒上万的吞吐量相比实在相形见绌。因此如何在保证区块链安全性的情况下,提高可扩展性是目前区块链发展亟待解决的问题之一。 目前关于区块链扩容的解决方案无外乎两个方向:一层(Layer 1)扩容和二层(Layer 2)扩容[4]。一层扩容也被称为链上(on-chain)扩容,顾名思义,这类扩容方案需要更改区块链底层协议。但同时也意味着需要将区块链硬分叉。这类扩容方案就像将原来的单核 CPU 改装成多核 CPU,从而可以多线程处理计算任务,提高整个网络的吞吐量。 目前最典型的一层扩容方案是 Vitalik 和他的研究团队提出的“Sharding(分片)”,也就是说将区块链划分成不同的部分(shards),每个部分独立处理交易。想要了解更多关于 Sharding 的信息,可以参考以太坊官方的 Wiki[5]。 二层扩容也称链下(off-chain)扩容,同样非常好理解,这种扩容方案不需要修改区块链底层协议,而是通过将大量、频繁的计算工作转移到“链下”完成,并定期或在需要时将链下的计算结果提交到“链上”保证其最终性(finality)。二层扩容的核心思想是将底层区块链作为共识基础,使用智能合约或者其它手段作为链下和链上沟通的桥梁,当有欺诈行为发生时链下的用户仍然可以回到链上的某一状态。虽然将计算转移到链下会在一段时间内损失最终性,但这个代价是值得的,因为这样做不止可以极大提高区块链的灵活性和可扩展性,也极大降低了用户进行交易所需要的代价。将计算转移到链下也并不意味着完全放弃安全性,因为最终的安全性还是由底层所依赖的区块链来保证,因此二层扩容主要关注的问题就在于如何保证链上链下切换过程的安全性。这种思想最早被用在闪电网络(Lightning Network)当中作为比特币的其中一个扩容方案,并取得了很好的效果。 本文所要介绍的 Plasma 就属于基于以太坊二层扩容方案,类似的解决方案还有 State Channels 和 Trubit。这些方案虽然面向的问题有所区别,但基本思想都是将复杂的计算转移到链下进行。那么,接下来我们将进入 Plasma 的世界,一窥究竟! 理解 Plasma在前文中我们已经明白 Plasma 是一种二层扩容框架,那么该如何进一步理解 Plasma 是什么?它区别于其它二层扩容方案的地方在哪呢? Plasma 也被称为“链中链(blockchains in blockchains)”。任何人都可以在底层区块链之上创建不同的 Plasma 支持不同的业务需求,例如分布式交易所、社交网络、游戏等。 这里可以举一个例子来理解 Plasma。假如企鹅公司创建了一个 Plasma Chain 叫作 Game Chain。用户通过向 Game Chain 发送一些以太币换取 Token,用于购买皮肤等游戏内的增值商品。加入 Game Chain 的操作是在链上进行的,以太坊区块链将这部分资产锁定,转移到 Game Chain 上。之后每次我们购买虚拟商品的交易就会转移到链下进行,由企鹅公司记账。这种方式几乎跟我们现实生活中游戏内购的体验一样,不仅结算迅速,而且手续费低廉(相比于以太坊之上需要给矿工支付的手续费)。那么问题来了,如果企鹅公司从中作祟,修改账本,恶意占有我们的资产怎么办?这时我们就可以提交之前每次交易的凭证到以太坊区块链上,如果确实是企鹅恶意篡改了账本,那么我们不但能够成功取回自己的资产,还能获得之前企鹅公司创建 Game Chain 存入的部分或全部押金。 通过上面这个例子是不是已经明白 Plasma 大致是如何工作的了?但上面这个例子还是过于简单,只涉及用户自己和企鹅公司。下面我们使用区块链的语言对上面的例子进行解析。 首先,企鹅公司在以太坊主链之上创建了一系列智能合约作为主链和子链 Game Chain 通信的媒介。这些智能合约中不仅规定了子链中的一些基本的状态转换规则(例如如何惩罚作恶的节点),也记录了子链中的状态(子链中区块的哈希值)。之后企鹅公司可以搭建自己的子链(可以用以太坊搭建一套私链)。子链实际上是一个完全独立的区块链,可以拥有专门的矿工,使用不同于主链的共识算法,例如 PoS(Proof of Stake)等。 当子链创建完毕后,企鹅公司可以使用 ERC721 合约创建 token 作为游戏内的商品(就像 Cryptokitty)。但这里需要注意的是,所有数字资产必须在以太坊主链上创建,并通过 Plasma 子链的智能合约转移到子链中。用户也需要在主链上购买数字资产后转移到子链上。在上面这个例子中,Game Chain 的智能合约将主链上的资产锁定,之后在子链上生成等值的资产。之后用户就可以完全脱离主链,在子链上进行交易。企鹅公司在子链上扮演 operator 的角色,如果一切运行正常,子链中的矿工会正常打包区块,并在需要时由 operator 将区块的哈希值提交到主链作为子链的状态更新证明。在这个过程中,用户完全不需要和主链交互。 我们可以看到,将复杂的计算操作转移到链下确实使得整个交易过程变得简单。但没有强大的共识算法和庞大的参与者,资产在子链上是很不安全的。Plasma 给了我们一种避险机制,即使 operator 作恶,我们也能取回属于自己的资产。下图(来源自[1])简单说明了这个过程。图中,在第 4 个区块中的交易被篡改。由于 Alice 本地保存有 Plasma Chain 中所有的区块数据,因此她可以向主链提交一个含有“防伪证明(Fraud Proof)”的交易。如果证明生效,那么主链将状态从 4 号区块回滚到 3 号区块,一切恢复正常。Plasmas Chain 中的参与者也可以随时提交资产证明,返回到主链。 到这里我们应该已经理解了,Plasma 所要做的工作并不是保护子链的安全,而是当有安全事故发生时,保证用户可以安全地取回自己的资产,并返回到主链上。并且采用一系列经济激励的方式减少作恶情况的发生。 下一篇文章将对 Plasma 运行过程的细节进行剖析。 相关资源 https://plasma.io/ https://ethresear.ch/c/plasma https://medium.com/l4-media/making-sense-of-web-3-c1a9e74dcae https://blog.ethereum.org/2018/01/02/ethereum-scalability-research-development-subsidy-programs/ https://github.com/ethereum/wiki/wiki/Sharding-FAQs https://medium.com/l4-media/making-sense-of-ethereums-layer-2-scaling-solutions-state-channels-plasma-and-truebit-22cb40dcc2f4 https://medium.com/@argongroup/ethereum-plasma-explained-608720d3c60e 本文的作者是盖盖,他的微信公众号: chainlab 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Plasma</category>
</categories>
<tags>
<tag>以太坊</tag>
<tag>Plasma</tag>
<tag>扩容</tag>
</tags>
</entry>
<entry>
<title><![CDATA[理解开发HD 钱包涉及的 BIP32、BIP44、BIP39]]></title>
<url>%2F2018%2F09%2F28%2Fhdwallet%2F</url>
<content type="text"><![CDATA[如果你还在被HD钱包、BIP32、BIP44、BIP39搞的一头雾水,来看看这边文章吧。 数字钱包概念钱包用来存钱的,在区块链中,我们的数字资产都会对应到一个账户地址上, 只有拥有账户的钥匙(私钥)才可以对资产进行消费(用私钥对消费交易签名)。私钥和地址的关系如下:(图来自精通比特币)一句话概括下就是:私钥通过椭圆曲线生成公钥, 公钥通过哈希函数生成地址,这两个过程都是单向的。 因此实际上,数字钱包实际是一个管理私钥(生成、存储、签名)的工具,注意钱包并不保存资产,资产是在链上的。 如何创建账号创建账号关键是生成一个私钥, 私钥是一个32个字节的数, 生成一个私钥在本质上在1到2^256之间选一个数字。因此生成密钥的第一步也是最重要的一步,是要找到足够安全的熵源,即随机性来源,只要选取的结果是不可预测或不可重复的,那么选取数字的具体方法并不重要。 比如可以掷硬币256次,用纸和笔记录正反面并转换为0和1,随机得到的256位二进制数字可作为钱包的私钥。 从编程的角度来看,一般是通过在一个密码学安全的随机源(不建议大家自己去写一个随机数)中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。 实际过程需要比较下是否小于n-1(n = 1.158 * 10^77, 略小于2^256),我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。这样得到的私钥就可以根据上面的方法进一步生成公钥及地址。 BIP32钱包也是一个私钥的容器,按照上面的方法,我们可以生成一堆私钥(一个人也有很多账号的需求,可以更好保护隐私),而每个私钥都需要备份就特别麻烦的。 最早期的比特币钱包就是就是这样,还有一个昵称:“Just a Bunch Of Keys(一堆私钥)“ 为了解决这种麻烦,就有了BIP32 提议: 根据一个随机数种子通过分层确定性推导的方式得到n个私钥,这样保存的时候,只需要保存一个种子就可以,私钥可以推导出来,如图: (图来自精通比特币)上图中的孙秘钥就可以用来签发交易。 补充说明下 BIP: Bitcoin Improvement Proposals 比特币改进建议, bip32是第32个改进建议。BIP32提案的名字是:Hierarchical Deterministic Wallets, 就是我们所说的HD钱包。 来分析下这个分层推导的过程,第一步推导主秘钥的过程: 根种子输入到HMAC-SHA512算法中就可以得到一个可用来创造主私钥(m) 和 一个主链编码( a master chain code)这一步生成的秘钥(由私钥或公钥)及主链编码再加上一个索引号,将作为HMAC-SHA512算法的输入继续衍生出下一层的私钥及链编码,如下图: 衍生推导的方案其实有两个:一个用父私钥推导(称为强化衍生方程),一个用父公钥推导。同时为了区分这两种不同的衍生,在索引号也进行了区分,索引号小于2^31用于常规衍生,而2^31到2^32-1之间用于强化衍生,为了方便表示索引号i’,表示2^31+i。 因此增加索引(水平扩展)及 通过子秘钥向下一层(深度扩展)可以无限生成私钥。 注意, 这个推导过程是确定(相同的输入,总是有相同的输出)也是单向的,子密钥不能推导出同层级的兄弟密钥,也不能推出父密钥。如果没有子链码也不能推导出孙密钥。现在我们已经对分层推导有了认识。 一句话概括下BIP32就是:为了避免管理一堆私钥的麻烦提出的分层推导方案。 秘钥路径及BIP44通过这种分层(树状结构)推导出来的秘钥,通常用路径来表示,每个级别之间用斜杠 / 来表示,由主私钥衍生出的私钥起始以“m”打头。因此,第一个母密钥生成的子私钥是m/0。第一个公共钥匙是M/0。第一个子密钥的子密钥就是m/0/1,以此类推。 BIP44则是为这个路径约定了一个规范的含义(也扩展了对多币种的支持),BIP0044指定了包含5个预定义树状层级的结构:m / purpose' / coin' / account' / change / address_indexm是固定的, Purpose也是固定的,值为44(或者 0x8000002C)Coin type这个代表的是币种,0代表比特币,1代表比特币测试链,60代表以太坊完整的币种列表地址:https://github.com/satoshilabs/slips/blob/master/slip-0044.mdAccount代表这个币的账户索引,从0开始Change常量0用于外部链,常量1用于内部链(也称为更改地址)。外部链用于在钱包外可见的地址(例如,用于接收付款)。内部链用于在钱包外部不可见的地址,用于返回交易变更。 (所以一般使用0)address_index这就是地址索引,从0开始,代表生成第几个地址,官方建议,每个account下的address_index不要超过20 根据 EIP85提议的讨论以太坊钱包也遵循BIP44标准,确定路径是m/44'/60'/a'/0/na 表示帐号,n 是第 n 生成的地址,60 是在 SLIP44 提案中确定的以太坊的编码。所以我们要开发以太坊钱包同样需要对比特币的钱包提案BIP32、BIP39有所了解。 一句话概括下BIP44就是:给BIP32的分层路径定义规范 BIP39BIP32 提案可以让我们保存一个随机数种子(通常16进制数表示),而不是一堆秘钥,确实方便一些,不过用户使用起来(比如冷备份)也比较繁琐,这就出现了BIP39,它是使用助记词的方式,生成种子的,这样用户只需要记住12(或24)个单词,单词序列通过 PBKDF2 与 HMAC-SHA512 函数创建出随机种子作为 BIP32 的种子。 可以简单的做一个对比,下面那一种备份起来更友好:1234// 随机数种子090ABCB3A6e1400e9345bC60c78a8BE7 // 助记词种子candy maple cake sugar pudding cream honey rich smooth crumble sweet treat 使用助记词作为种子其实包含2个部分:助记词生成及助记词推导出随机种子,下面分析下这个过程。 生成助记词助记词生成的过程是这样的:先生成一个128位随机数,再加上对随机数做的校验4位,得到132位的一个数,然后按每11位做切分,这样就有了12个二进制数,然后用每个数去查BIP39定义的单词表,这样就得到12个助记词,这个过程图示如下: (图来源于网络) 下面是使用bip39生成生成助记词的一段代码: 1234var bip39 = require('bip39')// 生成助记词var mnemonic = bip39.generateMnemonic()console.log(mnemonic) 助记词推导出种子这个过程使用密钥拉伸(Key stretching)函数,被用来增强弱密钥的安全性,PBKDF2是常用的密钥拉伸算法中的一种。PBKDF2基本原理是通过一个为随机函数(例如 HMAC 函数),把助记词明文和盐值作为输入参数,然后重复进行运算最终产生生成一个更长的(512 位)密钥种子。这个种子再构建一个确定性钱包并派生出它的密钥。 密钥拉伸函数需要两个参数:助记词和盐。盐可以提高暴力破解的难度。 盐由常量字符串 “mnemonic” 及一个可选的密码组成,注意使用不同密码,则拉伸函数在使用同一个助记词的情况下会产生一个不同的种子,这个过程图示图下: (图来源于网络) 同样代码来表示一下: 123456789101112var hdkey = require('ethereumjs-wallet/hdkey')var util = require('ethereumjs-util')var seed = bip39.mnemonicToSeed(mnemonic, "pwd");var hdWallet = hdkey.fromMasterSeed(seed);var key1 = hdWallet.derivePath("m/44'/60'/0'/0/0");console.log("私钥:"+util.bufferToHex(key1._hdkey._privateKey));var address1 = util.pubToAddress(key1._hdkey._publicKey, true);console.log("地址:"+util.bufferToHex(address1));console.log("校验和地址:"+ util.toChecksumAddress(address1.toString('hex'))); 校验和地址是EIP-55中定义的对大小写有要求的一种地址形式。 密码可以作为一个额外的安全因子来保护种子,即使助记词的备份被窃取,也可以保证钱包的安全(也要求密码拥有足够的复杂度和长度),不过另外一方面,如果我们忘记密码,那么将无法恢复我们的数字资产。 一句话概括下BIP39就是:通过定义助记词让种子的备份更友好 我为大家录制了一个视频:以太坊去中心化网页钱包开发,从如何创建账号开始,深入探索BIP32、BIP44、BIP39等提案,以及如何存储私钥、发送离线签名交易和Token。 小结HD钱包(Hierarchical Deterministic Wallets)是在BIP32中提出的为了避免管理一堆私钥的麻烦提出的分层推导方案。而BIP44是给BIP32的分层增强了路径定义规范,同时增加了对多币种的支持。BIP39则通过定义助记词让种子的备份更友好。 目前我们的市面上单到的以太币、比特币钱包基本都遵循这些标准。 最后推荐一个助记词秘钥生成器网站 欢迎来知识星球提问,星球内已经聚集了300多位区块链技术爱好者。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>以太坊</tag>
<tag>钱包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何使用Web3.js API 在页面中进行转账]]></title>
<url>%2F2018%2F09%2F12%2Fweb3-sendeth%2F</url>
<content type="text"><![CDATA[本文介绍如何使用Web3.js API 在页面中进行转账,是我翻译的文档Web3.js 0.2x 中文版 及 区块链全栈-以太坊DAPP开发实战 中Demo的文章说明。 写在前面阅读本文前,你应该对以太坊、智能合约、钱包的概念有所了解,如果你还不了解,建议你先看以太坊是什么除此之外,你最好还了解一些HTML及JavaScript知识。 转账UI 页面的编写转账UI主体的界面如图: 实现这个界面很简单,这里就不代码了。大家可以打开Demo,右击查看页面源码。 用户环境检查既然需要使用Web3.js API 在页面中进行转账, 首先应该检查在浏览器环境有没有安装好钱包,并且钱包应该是解锁状态。 先检查是否安装了MetaMask钱包: 123456789101112window.addEventListener('load', function() { if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); if (web3.currentProvider.isMetaMask == true) { // "MetaMask可用" } else { // "非MetaMask环境" } } else { $("#env").html("No web3? 需要安装<a href='https://metamask.io/'>MetaMask</a>!"); }} MetaMask推荐在window加载时,进行MetaMask的检查,当然在没有安装MetaMask时,也可以指定一个节点Provider来创建web3,可以参考Web3.js 文档引入web3 检查是否钱包已经解锁:我们在发送交易之前应该先首先检查一下当前钱包的一个状态,检查钱包是否解锁(是否输入了密码进入了MetaMask),通常使用eth下面的getAccounts来进行检查,getAccounts是会返回账号的一个列表,如果当前账号列表里面有数据的话,说明钱包已经解锁可以获得到账号,如果账号拿到的列表是空的话,那么说明钱包没有解锁。 可以把下面的代码加到上面的监听函数中: 12345web3.eth.getAccounts(function (err, accounts) { if (accounts.length == 0) { $("#account").html("请检查钱包是否解锁"); } }); 发送交易如果MetaMask钱包是解锁的,我们就可以来发送交易,发送交易使用sendtransaction这个方法。 1web3.eth.sendTransaction(transactionObject [, callback]) 第二个参数是回调函数用来获得发送交易的Hash值。 第一个参数是一个交易对象,交易对象里面有几个字段: from : 就是从哪个账号发送金额 to : 发动到到哪个账号 value 是发送的金额 gas: 设置gas limit gasPrice: 设置gas 价格 如果from没有的话,他就会用当前的默认账号, 如果是转账to和value是必选的两个字段。在发送交易的时候弹出来MetaMask的一个授权的窗口,如果我们gas和gasPrice没有设置的话,就可以在MetaMask里面去设置。如果这两个gas和gas Price设置了的话,MetaMask就会使用我们设置的gas。 因此在发送交易的时候,关键是构造这样一个交易对象,JavaScrpt代码如下: 1234567891011121314151617181920// 这里使用Metamask 给的gas Limit 及 gas 价var fromAccount = $('#fromAccount').val();var toAccount = $('#toAccount').val();var amount = $('#amount').val();// 对输入的数字做一个检查if (web3.isAddress(fromAccount) && web3.isAddress(toAccount) && amount != null && amount.length > 0) { var message = {from: fromAccount, to:toAccount, value: web3.toWei(amount, 'ether')}; web3.eth.sendTransaction(message, (err, res) => { var output = ""; if (!err) { output += res; } else { output = "Error"; } }} 补充说明:$('#fromAccount').val()是使用JQuery用来获取用户输入内容,其次应该在实际构造发送交易之前对输入的参数做一个判断,web3.isAddress用来检查字符串是不是地址。另外对于一个向普通外部地址账号的转账,消耗的gas 是固定的21000。 运行测试需要注意一点的是,由于安全原因,MetaMask只支持站点方式访问的页面,即通过http:// 来访问页面,在浏览器中通过file:// + 文件地址的方式是不行的。因此需要把编写的代码放置到web服务器的目录下,自己试验下。 线上的Demo地址为https://web3.learnblockchain.cn/transDemo.html 想好好系统学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战不容错过。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。深入浅出区块链知识星球,最专业的区块链问题技术社区,欢迎加入,作为星友福利,星友还可以加入我创建优质区块链技术群,群内聚集了300多位区块链技术大牛和爱好者。]]></content>
<categories>
<category>以太坊</category>
<category>Dapp</category>
</categories>
<tags>
<tag>Dapp入门</tag>
<tag>Web3.js</tag>
</tags>
</entry>
<entry>
<title><![CDATA[程序员如何切入区块链去中心化应用开发]]></title>
<url>%2F2018%2F08%2F31%2FdevDapp%2F</url>
<content type="text"><![CDATA[前段时间一个以太坊游戏应用:Fomo3D异常火爆,在短短的几天内就吸引了几万的以太币投入游戏,第一轮游戏一个“黑客”用了一个非常巧妙的利用以太坊规则成为了最终赢家,拿走了1万多以太币奖金。 区块链应用的价值由这个游戏反映的淋漓尽致,Fomo3D游戏能够成功核心所依赖的是以太坊提供的一个可信、不可篡改平台。当游戏的规则确定之后,一切都按规则运行,无人可干预。今天这篇就来介绍一下程序员如何切入去中心化应用开发。 中心化应用作为对比,先来看看中心化应用,其实就是现有的互联网应用,为什么它是中心化应用,看看它的架构图: 平时我们接触的是应用的前端(或称客户端),前端可以是HTML5的web页面、 小程序、APP, 在前端展现的内容通常发送一个请求到服务器,服务器返回相应的内容给前端。在前端的动作同样也会转化请求发送到服务器,服务器处理之后返回数据到前端。也就是说我们所有看到的内容或者操作都是中心化的服务器控制,因此说是中心化应用。 去中心化应用DAPP而去中心化应用有什么不同呢? 看看它的架构图:前端的表现上是一样的, 还是H5页面、 小程序、APP,DAPP和传统App关键是后端部分不同,是后端不再是一个中心化的服务器,而是分布式网络上任意节点,注意可以是 任意一个节点,在应用中给节点发送的请求通常称为 交易,交易和中心化下的请求有几个很大的不同是:交易的数据经过用户个人签名之后发送到节点,节点收到交易请求之后,会把 请求广播到整个网络,交易在网络达成共识之后,才算是真正的执行(真正其作用的执行不一是连接的后端节点,尽管后端也会执行)。以及中心化下的请求大多数都是同步的(及时拿到结果), 而交易大多数是异步的,这也是在开发去中心应用时需要注意的地方, 从节点上获得数据状态(比如交易的结果),一般是通过事件回调来获得。 如何开发在开发中心化应用最重要两部分是 客户端UI表现和 后端服务程序, UI表现通过HTTP请求连接到后端服务程序,后端服务程序运行在服务器上,比如Nginx Apached等等。 开发一个去中心化应用最重要也是两部分: 客户端UI表现及 智能合约,智能合约的作用就像后端服务程序,智能合约是运行在节点的EVM上, 客户端调用智能合约,是通过向节点发起RPC请求完成。 下面是一个对比: 客户端UI <=> 客户端UI HTTP <=> RPC 后端服务程序 <=> 智能合约 Nginx/Apache <=> 节点 因此对于去中心化应用来说,程序员可以从两个方面切入: 一个是 去中心化应用的客户端开发, 熟悉已经熟悉客户端软件(如Web\APP等)开发的同学,只需要了解一下客户端跟区块链节点通信的API接口,如果是在当前应用最广泛的区块链平台以太坊上开发去中心化应用,那么需要了解Web3这个库,Web3对节点暴露出来的JSON-RPC接口进行了封装,比如Web3提供的功能有:获取节点状态,获取账号信息,调用合约、监听合约事件等等。 目前的主流语言都有Web3的实现,列举一些实现给大家参考: JavaScript Web3.js Python Web3.py Haskell hs-web3 Java web3j Scala web3j-scala Purescript purescript-web3 PHP web3.php PHP ethereum-php 另一个切入点是 智能合约的开发,在以太坊现在推荐的语言是Solidity,有一些同学对新学一门语言有一些畏惧,Solidity的语法其实很简洁,有过一两门其他语言基础(开发经验)的同学三五天就可以学会,我也录制了一个视频课程:深入详解以太坊智能合约语言Solidity。 下面用一个Hello合约,体会下Solidity的语法: 12345contract Hello { function hello() public returns(string) { return "Hello World"; }} 如果把上面的contract关键字更改为class,就和其他语言定义一个类一样。 有兴趣的同学可以进一步学习一下这个DApp开发案例Web3与智能合约交互实战, 在DAPP的开发过程中,一些开发工具可以帮助我们事半功倍,如:Truffle开发框架以及Ganache工具来模拟节点等,这篇文章一步步教你开发、部署第一个去中心化应用 补充对于想切入到去中心化应用开发的同学,对区块链运行的原理了解肯定会是加分项,尤其是各类共识机制(POW,POS,DPOS等)的理解,P2P网络的理解,以及各类加密和Hash算法的运用。有一些同学想做区块链底层开发,对区块链运行的原理则是必须项。 欢迎来知识星球提问,星球内已经聚集了300多位区块链技术爱好者。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Dapp</category>
</categories>
<tags>
<tag>Dapp入门</tag>
<tag>以太坊概念</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何理解以太坊ABI - 应用程序二进制接口]]></title>
<url>%2F2018%2F08%2F09%2Funderstand-abi%2F</url>
<content type="text"><![CDATA[很多同学不是很明白以太坊ABI是什么,他的作用是什么,读完本文就明白了。 写在前面阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么,也可以观看我们的视频:零基础搞懂区块链和深入详解以太坊智能合约语言Solidity, 可以系统全面学习理解以太坊、智能合约。 ABI 是什么ABI 全称是 Application Binary Interface,翻译过来就是:应用程序二进制接口,简单来说就是 以太坊的调用合约时的接口说明。还不是很理解,没关系。 调用合约函数发生了什么从外部施加给以太坊的行为都称之为向以太坊网络提交了一个交易, 调用合约函数其实是向合约地址(账户)提交了一个交易,这个交易有一个附加数据,这个附加的数据就是ABI的编码数据。 比特币的交易也可以附加数据,以太坊革命性的地方就是能把附加数据转化为都函数的执行。 因此要想和合约交互,就离不开ABI数据。 演示调用函数以下面以个最简单的合约为例,我们看看用参数 1 调用set(uint x),这个交易附带的数据是什么。 1234567891011121314pragma solidity ^0.4.0;contract SimpleStorage { uint storedData; function set(uint x) public { storedData = x; } function get() public constant returns (uint) { return storedData; }} 当然第一步需要先把合约部署到以太坊网络(其实部署也是一个)上,然后用 “1” 作为参数调用set,如下图: 然后我们打开etherscan查看交易详情数据, 可以看到其附加数据如下图: 这个数据就是ABI的编码数据:10x60fe47b10000000000000000000000000000000000000000000000000000000000000001 ABI 编码分析我把上面交易的附加数据拷贝出来分析一下,这个数据可以分成两个子部分: 函数选择器(4字节)0x60fe47b1 第一个参数(32字节)00000000000000000000000000000000000000000000000000000000000000001 函数选择器值 实际是对函数签名字符串进行sha3(keccak256)哈希运算之后,取前4个字节,用代码表示就是: 1bytes4(sha3(“set(uint256)”)) == 0x60fe47b1 参数部分则是使用对应的16进制数。 现在就好理解 附加数据怎么转化为对应的函数调用。 ABI 编码函数那么怎么获得函数对应的ABI 数据呢, 有两种方法: Solidity ABI 编码函数一个是 solidity 提供了ABI的相关API, 用来直接得到ABI编码信息,这些函数有: abi.encode(…) returns (bytes):计算参数的ABI编码。 abi.encodePacked(…) returns (bytes):计算参数的紧密打包编码 abi. encodeWithSelector(bytes4 selector, …) returns (bytes): 计算函数选择器和参数的ABI编码 abi.encodeWithSignature(string signature, …) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), …) 通过ABI编码函数可以在不用调用函数的情况下,获得ABI编码值,下面通过一段代码来看看这些方法的使用: 1234567891011121314pragma solidity ^0.4.24;contract testABI { uint storedData; function set(uint x) public { storedData = x; } function abiEncode() public constant returns (bytes) { abi.encode(1); // 计算1的ABI编码 return abi.encodeWithSignature("set(uint256)", 1); //计算函数set(uint256) 及参数1 的ABI 编码 }} 大家可以运行运行下abiEncode函数,它的输出其实就是前面调用的附加数据。 Web3 ABI 编码函数另一个web3提供相应的API,例如使用web3计算函数选择器的方式如下: 1web3.eth.abi.encodeFunctionSignature('myMethod(uint256,string)'); 其完整的文档在这里,这里不一一演示。 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 欢迎来知识星球提问,星球内已经聚集了300多位区块链技术爱好者。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>ABI</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列13 - 函数调用]]></title>
<url>%2F2018%2F08%2F09%2Fsolidity-callfun%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第13篇介绍函数调用, 本文会介绍函数使用元组返回多个值,通过命名方式进行参数调用以及如何省略函数参数名称。 Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 函数调用及参数在函数类型一节中,我们介绍过Solidity 中有两种函数调用方式:内部函数调用和外部函数调用,这一节我们进一步介绍。 内部函数调用(Internal Function Calls)内部调用,不会创建一个EVM消息调用。而是直接调用当前合约的函数,也可以递归调用。如下面这个的例子: 1234567891011pragma solidity ^0.4.16;contract C { function g(uint a) public pure returns (uint ret) { return f(); // 直接调用 } function f() internal pure returns (uint ret) { return g(7) + f(); // 直接调用及递归调用 }} 这些函数调用被转换为EVM内部的简单指令跳转(jumps)。 这样带来的一个好处是,当前的内存不会被回收。在一个内部调用时传递一个内存型引用效率将非常高的。当然,仅仅是同一个合约的函数之间才可通过内部的方式进行调用。 外部函数调用(External Function Calls)外部调用,会创建EVM消息调用。表达式this.g(8);和c.g(2)(这里的c是一个合约实例)是外部调用函数的方式,它会发起一个消息调用,而不是EVM的指令跳转。需要注意的是,在合约的构造器中,不能使用this调用函数,因为当前合约还没有创建完成。 其它合约的函数必须通过外部的方式调用。对于一个外部调用,所有函数的参数必须要拷贝到内存中。 当调用其它合约的函数时,可以通过选项.value(),和.gas()来分别指定要发送的以太币(以wei为单位)和gas值,如: 1234567891011121314151617pragma solidity ^0.4.0;contract InfoFeed { function info() public payable returns (uint ret) { return 42; }}contract Consumer { InfoFeed feed; function setFeed(address addr) public { feed = InfoFeed(addr); } function callFeed() public { feed.info.value(10).gas(800)(); // 附加以太币及gas来调用info }} info()函数,必须使用payable关键字,否则不能通过value()来接收以太币。 表达式InfoFeed(addr)进行了一个显示的类型转换,表示给定的地址是合约InfoFeed类型,这里并不会执行构造器的初始化。在进行显式的类型强制转换时需要非常小心,不要调用一个我们不知道类型的合约函数。 我们也可以使用function setFeed(InfoFeed _feed) { feed = _feed; }来直接进行赋值。注意feed.info.value(10).gas(800)仅仅是对发送的以太币和gas值进行了设置,真正的调用是后面的括号()。调用callFeed时,需要预先存入一定量的以太币,要不能会因余额不足报错。 如果我们不知道被调用的合约源代码,和它们交互会有潜在的风险,即便被调用的合约继承自一个已知的父合约(继承仅仅要求正确实现接口,而不关注实现的内容)。因为和他们交互,相当于把自己控制权交给被调用的合约,对方几乎可以利用它做任何事。此外, 被调用的合约可以改变调用合约的状态变量,在编写函数时需要注意可重入性漏洞问题(可查看安全建议)。 函数参数与其他语言一样,函数可以提供参数作为输入(函数类型本身也可以作为参数); 与Javascript和C不同的是,solidity还可以返回任意数量的参数作为输出。 输入参数输入参数的声明方式与变量相同, 未使用的参数可以省略变量名称。假设我们希望合约接受一种带有两个整数参数的外部调用,可以这样写: 1234567pragma solidity ^0.4.16;contract Simple { function taker(uint _a, uint _b) public pure { // 使用 _a _b }} 输出参数输出参数的声明和输入参数一样,只不过它接在returns 之后,假设我们希望返回两个结果:两个给定整数的和及积,可以这样写: 123456789101112pragma solidity ^0.4.16;contract Simple { function arithmetics(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) { o_sum = _a + _b; o_product = _a * _b; }} 可以省略输出参数的名称,也可以使用return语句指定输出值,return可以返回多个值(见下文)。返回一个没有赋值的参数,则默认为0。 输入参数和输出参数可以在函数内表达式中使用,也可以作为被赋值的对象, 如: 1234567contract Simple { function taker(uint _a, uint _b) public pure returns (uint _c) { _a = 1; _b = 2; _c = 3; }} 返回多个值当一个函数有多个输出参数时, 可以使用元组(tuple)来返回多个值。元组(tuple)是一个数量固定,类型可以不同的元素组成的一个列表(用小括号表示),使用return (v0, v1, …, vn) 语句,就可以返回多个值,返回值的数量需要和输出参数声明的数量一致。 123456789101112function f() public pure returns (uint, bool, uint) { // 使用元组返回多个值 return (7, true, 2);}function callf() public { uint x; bool y; uint z; // 使用元组给多个变量赋值 (x, y , z) = f();} 补充关于元组的介绍上面的代码中,使用了元组返回多个值及使用元组给多个变量赋值,给多个变量赋值通常也称为解构(解构的概念在函数式语言中较为常见),再来看看元组的一些用法,比如元组可以交换变量值,如: 1(x, y) = (y, x); 元组支持省略一些元素, 如: 1(x, y, ) = (1, 2, 4); 开头的元素也可以省略,如: 1(, y, ) = (1, 2, 4); 注意 (1,) 是一个一个元素的元组, (1) 只是1。 使用命名参数调用函数调用的参数,可以通过指定名称的方式调用,使用花括号{} 包起来,参数顺序任意,但参数的类型和数量要与定义一致。如: 1234567891011pragma solidity ^0.4.0;contract C { function f(uint key, uint value) public { // ... } function g() public { f({value: 2, key: 3}); // 命名参数 }} 省略函数参数名称没有使用的参数名称可以省略(一般常见于返回值)。这些参数依然在栈(stack)上存在,但不可访问。 12345678pragma solidity ^0.4.16;contract C { // omitted name for parameter function func(uint k, uint) public pure returns(uint) { return k; }} 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果想与我有更密切的交流可以选择加入我的知识星球(星球成员可加入微信技术交流群)]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列12 - 库的使用]]></title>
<url>%2F2018%2F08%2F09%2Fsolidity-library%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第12篇介绍库的使用:库与合约的不同,使用库的正姿势。 Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 库库与合约类似,它也部署在一个指定的地址上(仅被部署一次,当代码在不同的合约可反复使用),然后通过EVM的特性DELEGATECALL (Homestead之前是用CALLCODE)来复用代码。库函数在被调用时,库代码是在发起合约(下文称主调合约:主动发起DELEGATECALL调用的合约)的上下文中执行的,使用this将会指向到主调合约,而且库代码可以访问主调合约的存储(storage)。 因为库合约是一个独立的代码,它仅可以访问主调合约明确提供的状态变量,否则,没办法法去知道这些状态变量。 对比普通合约来说,库存在以下的限制(这些限制将来也可能在将来的版本被解除): 无状态变量(state variables)。 不能继承或被继承 不能接收以太币 不能销毁一个库 不会修改状态变量(例如被声明view或pure)库函数只能通过直接调用(如不用DELEGATECALL),是因为其被认为是状态无关的。 库有许多使用场景。两个主要的场景如下: 如果有许多合约,它们有一些共同代码,则可以把共同代码部署成一个库。这将节省gas,因为gas也依赖于合约的规模。因此,可以把库想象成使用其合约的父合约。使用父合约(而非库)切分共同代码不会节省gas,因为在Solidity中,继承通过复制代码工作。 库可用于给数据类型添加成员函数。(参见下一节Using for) 由于库被当作隐式的父合约(不过它们不会显式的出现在继承关系中,但调用库函数和调用父合约的方式是非常类似的,如库L有函数f(),使用L.f()即可访问)。库里面的内部(internal)函数被复制给使用它的合约;同样按调用内部函数的调用方式,这意味着所有内部类型可以传进去,memory类型则通过引用传递,而不是拷贝的方式。 同样库里面的结构体structs和枚举enums也会被复制给使用它的合约。因此,如果一个库里只包含内部函数或结构体或枚举,则不需要部署库,因为库里面的所有内容都被复制给使用它的合约。 下面的例子展示了如何使用库。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546pragma solidity ^0.4.16;library Set { // 定义了一个结构体,保存主调函数的数据(本身并未实际存储的数据)。 struct Data { mapping(uint => bool) flags; } // self是一个存储类型的引用(传入的会是一个引用,而不是拷贝的值),这是库函数的特点。 // 参数名定为self 也是一个惯例,就像调用一个对象的方法一样. function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // 已存在 self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; }}contract C { Set.Data knownValues; function register(uint value) public { // 库函数不需要实例化就可以调用,因为实例就是当前的合约 require(Set.insert(knownValues, value)); } // 在这个合约中,如果需要的话可以直接访问knownValues.flags,} 当然,我们也可以不按上面的方式来使用库函数,可以不定义结构体,可以不使用storage类型引用的参数,还可以在任何位置有多个storage的引用类型的参数。 调用Set.contains,Set.remove,Set.insert都会编译为以DELEGATECALL的方式调用外部合约和库。如果使用库,需要注意的是一个真实的外部函数调用发生了。尽管msg.sender,msg.value,this还会保持它们在主调合约中的值(在Homestead之前,由于实际使用的是CALLCODE,msg.sender,msg.value会变化)。 下面的例子演示了在库中如何使用memory类型和内部函数(inernal function)来实现一个自定义类型,而不会用到外部函数调用(external function)。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152pragma solidity ^0.4.16;library BigInt { struct bigint { uint[] limbs; } function fromUint(uint x) internal pure returns (bigint r) { r.limbs = new uint[](1); r.limbs[0] = x; } function add(bigint _a, bigint _b) internal pure returns (bigint r) { r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); uint carry = 0; for (uint i = 0; i < r.limbs.length; ++i) { uint a = limb(_a, i); uint b = limb(_b, i); r.limbs[i] = a + b + carry; if (a + b < a || (a + b == uint(-1) && carry > 0)) carry = 1; else carry = 0; } if (carry > 0) { // too bad, we have to add a limb uint[] memory newLimbs = new uint[](r.limbs.length + 1); for (i = 0; i < r.limbs.length; ++i) newLimbs[i] = r.limbs[i]; newLimbs[i] = carry; r.limbs = newLimbs; } } function limb(bigint _a, uint _limb) internal pure returns (uint) { return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; } function max(uint a, uint b) private pure returns (uint) { return a > b ? a : b; }}contract C { using BigInt for BigInt.bigint; function f() public pure { var x = BigInt.fromUint(7); var y = BigInt.fromUint(uint(-1)); var z = x.add(y); }} 合约的源码中不能添加库地址,它是在编译时向编译器以参数形式提供的(这些地址须由链接器(linker)填进最终的字节码中,使用命令行编译器来进行联接 TODO)。如果地址没有以参数的方式正确给到编译器,编译后的字节码将会仍包含一个这样格式的占们符Set__(其中Set是库的名称)。可以通过手动将所有的40个符号替换为库的十六进制地址。 Using for 指令指令using A for B;用来把库函数(从库A)关联到类型B。这些函数将会把调用函数的实例作为第一个参数。语法类似,python中的self变量一样。例如:A库有函数 add(B b1, B b2),则使用Using A for B指令后,如果有B b1就可以使用b1.add(b2)。 using A for * 表示库A中的函数可以关联到任意的类型上。 在这两种情形中,所有函数,即使第一个参数的类型与调用函数的对象类型不匹配的,也会被关联上。类型检查是在函数被调用时执行,以及函数重载是也会执行检查。 using A for B; 指令仅在当前的作用域有效,且暂时仅仅支持当前的合约这个作用域,后续也非常有可能解除这个限制,允许作用到全局范围。如果能作用到全局范围,通过引入一些模块(module),数据类型将能通过库函数扩展功能,而不需要每个地方都得写一遍类似的代码了。 下面我们使用Using for 指令方式重写上一节Set的例子: 123456789101112131415161718192021222324252627282930313233343536373839404142434445 pragma solidity ^0.4.16;// 库合约代码和上一节一样library Set { struct Data { mapping(uint => bool) flags; } function insert(Data storage self, uint value) public returns (bool) { if (self.flags[value]) return false; // already there self.flags[value] = true; return true; } function remove(Data storage self, uint value) public returns (bool) { if (!self.flags[value]) return false; // not there self.flags[value] = false; return true; } function contains(Data storage self, uint value) public view returns (bool) { return self.flags[value]; }}contract C { using Set for Set.Data; // 这是一个关键的变化 Set.Data knownValues; function register(uint value) public { // 现在 Set.Data都对应的成员方法 // 效果和Set.insert(knownValues, value)相同 require(knownValues.insert(value)); }} 同样可以使用Using for的方式来对基本类型(elementary types)进行扩展: 12345678910111213141516171819202122232425262728293031pragma solidity ^0.4.16;library Search { function indexOf(uint[] storage self, uint value) public view returns (uint) { for (uint i = 0; i < self.length; i++) if (self[i] == value) return i; return uint(-1); }}contract C { using Search for uint[]; uint[] data; function append(uint value) public { data.push(value); } function replace(uint _old, uint _new) public { // 进行库调用 uint index = data.indexOf(_old); if (index == uint(-1)) data.push(_new); else data[index] = _new; }} 需要注意的是所有库调用都实际上是EVM函数调用。这意味着,如果传的是memory类型的,或者是值类型,那么进行一次拷贝,即使是self变量,解决方法是使用存储(storage)类型的引用来避免拷贝内容。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果想与我有更密切的交流可以选择加入我的知识星球(星球成员可加入微信技术交流群)]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python实现一条基于POS算法的区块链]]></title>
<url>%2F2018%2F08%2F07%2Fpython-blockchain-with-pos%2F</url>
<content type="text"><![CDATA[区块链中的共识算法在比特币公链架构解析中,就曾提到过为了实现去中介化的设计,比特币设计了一套共识协议,并通过此协议来保证系统的稳定性和防攻击性。 并且我们知道,截止目前使用最广泛,也是最被大家接受的共识算法,是我们先前介绍过的POW(proof of work)工作量证明算法。目前市值排名前二的比特币和以太坊也是采用的此算法。 虽然POW共识算法取得了巨大的成功,但对它的质疑也从来未曾停止过。 其中最主要的一个原因就是电力消耗。据不完全统计,基于POW的挖矿机制所消耗的电量是非常巨大的,甚至比绝大多数国家耗电量还要多。这对我们的资源造成了极大的浪费,此外随着比特大陆等公司的强势崛起,造成了算力的高度集中。 基于以上种种原因,更多的共识算法被提出来 POS、DPOS、BPFT等等。 今天我们就来认识POS(proof of stake)算法。 Proof of stake,译为权益证明。你可能已经猜到了,权益证明简单理解就是拥有更多token的人,有更大的概率获得记账权利,然后获得奖励。 这个概率具体有多大呢? 下面我们在代码实现中会展示,分析也放在后面。 当然,POS是会比POW更好吗? 会更去中心化吗? 现在看来未必,所以我们这里也不去对比谁优谁劣。 我们站在中立的角度,单纯的来讨论讨论POS这种算法。 代码实战生成一个Block既然要实现POS算法,那么就难免要生成一条链,链又是由一个个Block生成的,所以下面我们首先来看看如何生成Block,当然在前面的内容里面,关于如何生成Block,以及交易、UTXO等等都已经介绍过了。由于今天我们的核心是实现POS,所以关于Block的生成,我们就用最简单的实现方式,好让大家把目光聚焦在核心的内容上面。 我们用三个方法来实现生成一个合法的区块 calculate_hash 计算区块的hash值 is_block_valid 校验区块是否合法 generate_block 生成一个区块 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253from hashlib import sha256from datetime import datetimedef generate_block(oldblock, bpm, address): """ :param oldblock: :param bpm: :param address: :return: """ newblock = { "Index": oldblock["Index"] + 1, "BPM": bpm, "Timestamp": str(datetime.now()), "PrevHash": oldblock["Hash"], "Validator": address } newblock["Hash"] = calculate_hash(newblock) return newblockdef calculate_hash(block): record = "".join([ str(block["Index"]), str(block["BPM"]), block["Timestamp"], block["PrevHash"] ]) return sha256(record.encode()).hexdigest()def is_block_valid(newblock, oldblock): """ :param newblock: :param oldblock: :return: """ if oldblock["Index"] + 1 != newblock["Index"]: return False if oldblock["Hash"] != newblock["PrevHash"]: return False if calculate_hash(newblock) != newblock["Hash"]: return False return True 这里为了更灵活,我们没有用类的实现方式,直接采用函数来实现了Block生成,相信很容易看懂。 创建一个TCP服务器由于我们需要用权益证明算法来选择记账人,所以需要从很多Node(节点)中选择记账人,也就是需要一个server让节点链接上来,同时要同步信息给节点。因此需要一个TCP长链接。 123456from socketserver import BaseRequestHandler, ThreadingTCPServerdef run(): # start a tcp server serv = ThreadingTCPServer(('', 9090), HandleConn) serv.serve_forever() 在这里我们用了python内库socketserver来创建了一个TCPServer。 需要注意的是,这里我们是采用的多线程的创建方式,这样可以保证有多个客户端同时连接上来,而不至于被阻塞。当然,这里这个server也是存在问题的,那就是有多少个客户端连接,就会创建多少个线程,更好的方式是创建一个线程池。由于这里是测试,所以就采用更简单的方式了。 相信大家已经看到了,在我们创建TCPServer的时候,使用到了HandleConn,但是我们还没有定义,所以接下来我们就来定义一个HandleConn ##消息处理器下面我们来实现Handler函数,Handler函数在跟Client Node通信的时候,需要我们的Node实现下面的功能 Node可以输入balance(token数量) 也就是股权数目 Node需要能够接收广播,方便Server同步区块以及记账人信息 添加自己到候选人名单 (候选人为持有token的人) 输入BPM生成Block 验证一个区块的合法性 感觉任务还是蛮多的,接下来我们看代码实现 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556import threadingfrom queue import Queue, Empty# 定义变量block_chain = []temp_blocks = []candidate_blocks = Queue() # 创建队列,用于线程间通信announcements = Queue()validators = {}My_Lock = threading.Lock()class HandleConn(BaseRequestHandler): def handle(self): print("Got connection from", self.client_address) # validator address self.request.send(b"Enter token balance:") balance = self.request.recv(8192) try: balance = int(balance) except Exception as e: print(e) t = str(datetime.now()) address = sha256(t.encode()).hexdigest() validators[address] = balance print(validators) while True: announce_winner_t = threading.Thread(target=annouce_winner, args=(announcements, self.request,), daemon=True) announce_winner_t.start() self.request.send(b"\nEnter a new BPM:") bpm = self.request.recv(8192) try: bpm = int(bpm) except Exception as e: print(e) del validators[address] break # with My_Lock: last_block = block_chain[-1] new_block = generate_block(last_block, bpm, address) if is_block_valid(new_block, last_block): print("new block is valid!") candidate_blocks.put(new_block) self.request.send(b"\nEnter a new BPM:\n") annouce_blockchain_t = threading.Thread(target=annouce_blockchain, args=(self.request,), daemon=True) annouce_blockchain_t.start() 这段代码,可能对大多数同学来说是有难度的,在这里我们采用了多线程的方式,同时为了能够让消息在线程间通信,我们使用了队列。 这里使用队列,也是为了我们的系统可以更好的拓展,后面如果可能,这一节的程序很容易拓展为分布式系统。 将多线程里面处理的任务拆分出去成独立的服务,然后用消息队列进行通信,就是一个简单的分布式系统啦。(是不是很激动?) 由于这里有难度,所以代码还是讲一讲吧 123456789101112# validator address self.request.send(b"Enter token balance:") balance = self.request.recv(8192) try: balance = int(balance) except Exception as e: print(e) t = str(datetime.now()) address = sha256(t.encode()).hexdigest() validators[address] = balance print(validators) 这一段就是我们提到的Node 客户端添加自己到候选人的代码,每链接一个客户端,就会添加一个候选人。 这里我们用添加的时间戳的hash来记录候选人。 当然也可以用其他的方式,比如我们代码里面的client_address 1234567891011121314151617181920announce_winner_t = threading.Thread(target=annouce_winner, args=(announcements, self.request,), daemon=True) announce_winner_t.start()def annouce_winner(announcements, request): """ :param announcements: :param request: :return: """ while True: try: msg = announcements.get(block=False) request.send(msg.encode()) request.send(b'\n') except Empty: time.sleep(3) continue 然后接下来我们起了一个线程去广播获得记账权的节点信息到所有节点。 1234567891011121314151617self.request.send(b"\nEnter a new BPM:") bpm = self.request.recv(8192) try: bpm = int(bpm) except Exception as e: print(e) del validators[address] break # with My_Lock: last_block = block_chain[-1] new_block = generate_block(last_block, bpm, address) if is_block_valid(new_block, last_block): print("new block is valid!") candidate_blocks.put(new_block) 根据节点输入的BPM值生成一个区块,并校验区块的有效性。 将有效的区块放到候选区块当中,等待记账人将区块添加到链上。 123456789101112131415161718annouce_blockchain_t = threading.Thread(target=annouce_blockchain, args=(self.request,), daemon=True) annouce_blockchain_t.start()def annouce_blockchain(request): """ :param request: :return: """ while True: time.sleep(30) with My_Lock: output = json.dumps(block_chain) try: request.send(output.encode()) request.send(b'\n') except OSError: pass 最后起一个线程,同步区块链到所有节点。 看完了,节点跟Server交互的部分,接下来是最重要的部分, POS算法实现123456789101112131415161718192021222324252627282930313233343536373839def pick_winner(announcements): """ 选择记账人 :param announcements: :return: """ time.sleep(10) while True: with My_Lock: temp = temp_blocks lottery_pool = [] # if temp: for block in temp: if block["Validator"] not in lottery_pool: set_validators = validators k = set_validators.get(block["Validator"]) if k: for i in range(k): lottery_pool.append(block["Validator"]) lottery_winner = choice(lottery_pool) print(lottery_winner) # add block of winner to blockchain and let all the other nodes known for block in temp: if block["Validator"] == lottery_winner: with My_Lock: block_chain.append(block) # write message in queue. msg = "\n{0} 赢得了记账权利\n".format(lottery_winner) announcements.put(msg) break with My_Lock: temp_blocks.clear() 这里我们用pick_winner 来选择记账权利,我们根据token数量构造了一个列表。 一个人获得记账权利的概率为:1p = mount['NodeA']/mount['All'] 文字描述就是其token数目在总数中的占比。 比如总数有100个,他有10个,那么其获得记账权的概率就是0.1, 到这里核心的部分就写的差不多了,接下来,我们来添加节点,开始测试吧 测试POS的记账方式在测试之前,起始还有一部分工作要做,前面我们的run方法需要完善下,代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041def run(): # create a genesis block t = str(datetime.now()) genesis_block = { "Index": 0, "Timestamp": t, "BPM": 0, "PrevHash": "", "Validator": "" } genesis_block["Hash"] = calculate_hash(genesis_block) print(genesis_block) block_chain.append(genesis_block) thread_canditate = threading.Thread(target=candidate, args=(candidate_blocks,), daemon=True) thread_pick = threading.Thread(target=pick_winner, args=(announcements,), daemon=True) thread_canditate.start() thread_pick.start() # start a tcp server serv = ThreadingTCPServer(('', 9090), HandleConn) serv.serve_forever()def candidate(candidate_blocks): """ :param candidate_blocks: :return: """ while True: try: candi = candidate_blocks.get(block=False) except Empty: time.sleep(5) continue temp_blocks.append(candi)if __name__ == '__main__': run() 添加节点连接到TCPServer为了充分减少程序的复杂性,tcp client我们这里就不实现了,可以放在后面拓展部分。 毕竟我们这个系统是很容易扩展的,后面我们拆分了多线程的部分,在实现tcp client就是一个完整的分布式系统了。 所以,我们这里用linux自带的命令 nc,不知道nc怎么用的同学可以google或者 man nc 启动服务 运行 python pos.py 打开3个终端 分别输入下面命令 nc localhost 9090 终端如果输出1Enter token balance: 说明你client已经链接服务器ok啦. 测试POS的记账方式接下来依次按照提示操作。 balance可以按心情来操作,因为这里是测试,我们输入100,紧接着会提示输入BPM,我们前面提到过,输入BPM是为了生成Block,那么就输入吧,随便输入个9. ok, 接下来就稍等片刻,等待记账。输出如同所示依次在不同的终端,根据提示输入数字,等待消息同步。 生成区块链下面是我这边获得的3个block信息。 总结在上面的代码中,我们实现了一个完整的基于POS算法记账的链,当然这里有许多值得扩展与改进的地方。 python中多线程开销比较大,可以改成协程的方式 TCP建立的长链接是基于TCPServer,是中心化的方式,可以改成P2P对等网络 链的信息不够完整 系统可以拓展成分布式,让其更健壮 大概列了以上几点,其他还有很多可以拓展的地方,感兴趣的朋友可以先玩玩, 后者等到我们后面的教程。 (广告打的措手不及,哈哈) 当然了,语言不是重点,所以在这里,我也实现了go语言的版本源码地址 go语言的实现感觉要更好理解一点,也显得要优雅一点。这也是为什么go语言在分布式领域要更抢手的原因之一吧! 项目地址 https://github.com/csunny/py-bitcoin/ 参考 https://medium.com/@mycoralhealth/code-your-own-proof-of-stake-blockchain-in-go-610cd99aa658 我的专栏专注区块链底层技术开发,P2P网络、加密技术、MerkleTree、DAG、DHT等等,另外对于分布式系统的学习也很有帮助。欢迎大家交流。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>区块链</tag>
<tag>权益证明</tag>
<tag>Python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[什么是EOS(柚子)]]></title>
<url>%2F2018%2F07%2F17%2Fwhatiseos%2F</url>
<content type="text"><![CDATA[是时候给写写EOS了,现在EOS主网已经上线,尽管我个人不是很喜欢EOS项目(不过也一直在关注EOS),但是不可否认EOS这个争议性很大的项目给区块链世界带来的变化。 写在前面阅读本文前,如果了解过比特币及以太坊,可以更好的理解本文。欢迎订阅专栏:区块链技术指引你从头开始学区块链技术。 本文出现EOS是指EOS.io公链项目,不是指以太坊上的EOS Token。 EOS 简介EOS: Enterprise Operation System 中文意思为:商业级区块链操作系统。 尽管以太坊创造性引入智能合约概念,极大的简化了区块链应用的开发,但以太坊平台依然有一个很大的限制,就是交易确认时间及交易吞吐量比较小,从而严重影响了以太坊进行商业应用。 交易吞吐量有一个专门的词:TPS (transaction per second 每秒的交易量) 比特币的TPS 是大概7,并且最少几十分钟交易才能被确认,以太坊的TPS大概是20左右,交易的确认一般需要几分钟的时间。不过比特币以太坊也在不断进化以提高TPS,比如比特币的闪电网络,以太坊的Sharding技术(分片)以及Plasma技术(分层)。 EOS 项目的目标是建立可以承载商业级智能合约与应用的区块链基础设施,成为区块链世界的“底层操作系统”。EOS通过石墨烯技术解决延迟和数据吞吐量问题,TPS可达到数千,交易的确认时间也只有数秒。同时声称未来使用并行链的方式,最高可以达到数百万TPS。 EOS 设计了一套账户权限管理系统,EOS不再使用的地址作为账户,可以直接使用字符作为账户名,并设计了一套的账户权限体系。 此外,在 EOS 上转账交易及运行智能合约不需要消耗 EOS代币。而是EOS 系统当中,抵押代币获取对应的资源,来执行相应交易,在EOS运行程序完全免费的说是不准确的。 值的一提的是EOS项目其ICO也是基于以太坊ERC20 Token进行的,其ICO 时间长达355天,作为一个当时还未上线的项目,融资额达到40亿美元是前所未有。 充满争议的技术天才BMEOS的主要开发者为丹尼尔·拉瑞莫(Daniel Larimer), 绰号BM(GitHub的昵称:ByteMaster), 它是EOS的项目方,BlockOne公司的CTO。和V神一样,也是一个神奇的人物,网络上两人因理念不合有多次论战。BM有一句牛B 轰轰的话:我终生的使命,是致力于找到一些加密经济的解决方案,给所有人的财产、自由、平等带来保障。 BM成功创立过三个区块链项目:BitShares、Steem 以及EOS,是一个技术天才,也是一个多变的人。2009年的BM也准备的数字货币一展身手,在其研究比特币之后,2010年BM提出了一些比特币的问题,并想要改进,结果比特币的创始人中本聪(Satoshi Nakamoto)怼会了他“If you don’t believe me or don’t get it, I don’t have time to try to convince you, sorry.”(懂不懂随你,我可没时间理你)。于是BM开始着手创建自己的区块链项目,这就是2013年发布的 BitShares 比特股,世界上第一个数字货币去中心化交易所。 BitShares在2014年上线时,是当时的明星项目,也由于bug太多、糟糕的体验以及BM在进行个别版本升级的时候都不提供向下兼容,用户逐渐流失,更要命的是,BM利用自己超过1/3的记账节点,在没有达成社区共识的情况下,强行分叉增发了BitShares发行总量。尽管BM在技术提供了改进,发布了石墨烯工具集,不过最终社区投票决定让BM离开了BitShares。 离开BitShares的BM,于2016年创立了区块链项目Steem,去中心化社交网站Steemit就是基于Steem创建,在Steemit的运营期间,BM和Steemit的CEO Ned有过多次口水战。在2017年,BM离开了自己创建的Steem项目(也许除了BM自己,没有人能知道他离开Steem的真实原因),选择与布鲁默联合创办了BlockOne公司打造EOS项目。 石墨烯(Graphene)与 DPOS和BitShares、Steem 一样,EOS底层使用的也是石墨烯技术,石墨烯是一个开源的区块链底层库,也出自BM之手,它采用的是 DPOS(Delegated Proof-of-Stake 股份授权证明机制 )的共识机制。在比特币及以太坊网络中,任何人都可以参与记账,而DPOS为了提高出块速度TPS,限制了参与记账了人数,在DPOS中,记账者不在称为矿工,而是改称为见证人 Witness,现在EOS中,又有一个新词:Block Producer,简称BP,大家翻译为超级节点(本文中依旧会使用见证人这个词,超级节点更像是一个市场营销用词)。 在EOS中,见证人的个数是21个,BitShares中是101个,BitShares的出块时间打开是 1.5秒,在EOS中,出块时间提高到了0.5秒。 和Pow及Pos共识机制矿工可以自由选择参与挖矿不同,DPOS下节点需要参与见证人选举,只有赢得选举的节点才能负责出块,在EOS中,赢得选举21个节点见证人轮流出块。另外还有100个备用见证人(候选节点),在21个见证人出现问题后做替补。EOS的发行总量是10亿, 见证人在完成打包交易区块后,可以领取到区块的奖励,区块的奖励来自对发行量的通胀增发,通胀率每年接近5%。 BM特色的去中心化我个人理解的区块链,它最大的革命性就是他的中立性,其运行不应该受到任何人的干扰,在POW共识中,矿工、项目方(开发者)以及交易方他们是相互独立的存在。 在EOS中,BM本人拥有巨量的选票,他可以在一定程度上左右见证人的选举,同时BM还为EOS制定了宪法,要求所有的见证人必须遵照宪法。因此BM某种程度上可以左右EOS系统的运行。 本文是个人对EOS的理解,受我自己视野局限也许理解有偏差,欢迎大家批准指正,我的微信: xlbxiong。 EOS相关资料: EOS开发者资源 官方网站 Github 代码 我们为区块链爱者这提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳区块链视频教程。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>EOS</category>
</categories>
<tags>
<tag>EOS入门</tag>
<tag>柚子</tag>
</tags>
</entry>
<entry>
<title><![CDATA[搭建智能合约开发环境Remix IDE及使用]]></title>
<url>%2F2018%2F06%2F07%2Fremix-ide%2F</url>
<content type="text"><![CDATA[目前开发智能的IDE, 首推还是Remix, 而Remix官网, 总是由于各种各样的(网络)原因无法使用,本文就来介绍一下如何在本地搭建智能合约开发环境remix-ide并介绍Remix的使用。 写在前面Remix 是以太坊智能合约编程语言Solidity IDE,阅读本文前,你应该对以太坊、智能合约有所了解,如果还不了解,建议先看以太坊是什么。 Remix IDE 介绍Remix IDE 是一款基于浏览器的IDE,跟有些开发聊的时候,发现有一些同学对浏览器的IDE,有一些偏见,其实Atom编辑器就是基于web技术开发的一款编辑器(Atom可以看做一个没有地址栏的浏览器),其实基于浏览器的IDE,有一个很大的好处就是不用安装,打开即用。 Remix IDE的功能全面(传统IDE有的功能这里都有),比如: 代码提示补全,代码高亮 代码警告、错误提示 运行日志输出 代码调试 … Remix IDE 安装 更新: Remix 现在提供了一个APP, 叫Remix APP, 如果是Mac 电脑,可以直接使用其提供的发布包,地址为:https://github.com/horizon-games/remix-app/releases 如果你有很好的网络环境,可以直接访问Remix官网。要不能还是还是像我一样老老实实把Remix IDE安装在本地,我发现要想成功安装选择对应的版本很关键,具体的版本要求如下: 123456$ node --versionv7.10.1$ npm --version4.2.0$ nvm --version0.33.11 nvm 安装nvm 是一个node 版本工具,我们可以使用nvm来安装不同版本的node。nvm 官方安装方法如下: 命令行中输入: 1curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash 在当前用户profile文件,如(~/.bash_profile, ~/.zshrc, ~/.profile, or ~/.bashrc)添加加载nvm的脚本: 12export NVM_DIR="$HOME/.nvm"[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 重启下命令行,输入nvm 试试,应该可以看到 nvm 命令的帮助 使用nvm 安装node因为Remix IDE 要求使用node 7.10.1, 命令行输入一下命令进行安装: 1nvm install 7 安装完成之后,使用node –version 和 npm –version检查下版本号,是否和刚刚列出版本要求一致,在版本一值的qing 命令行安装Remix ide方法1直接使用npm安装,这也是我安装使用的方法。12npm install remix-ide -gremix-ide 如果出现错误:Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules’可以尝试用以下方法解决:1sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share} 如果我们使用的是正确的node 和npm 版本的话,应该都可以安装成功,安装成功之后,remix-ide命令用来启动ide. 方法2remix-ide的github 上还提供了另一个方法进行安装,通过clone 代码来安装,方法如下: 12345git clone https://github.com/ethereum/remix-ide.gitcd remix-idenpm installnpm run setupremix # this will clone https://github.com/ethereum/remix for you and link it to remix-idenpm start Remix ide 使用Remix IDE 默认是使用8080端口启动的,启动之后在浏览器打开:http://localhost:8080/, 如图: 和大多数IDE一样,最左边是文件浏览,中间是代码编辑区域,右边是功能区域,下边是日志区域。在右侧的功能区域,常用的是Compile、Run及Debuger几个标签页(Tab)。 在Compile页,会动态的显示当前编辑区域合约的编译信息,如显示错误和警告。编译的直接码信息及ABI接口可以通过点击Details查看到。在这篇文章里 也有截图说明。在Run页,可以部署合约,以及调用合约函数等,使用非常简单,我们前面也有多篇文章讲解。Debuger页在下面调试一节单独讲解。 Remix ide 加载本地磁盘文件这是一个非常用的功能,但发现使用的人非常少,通过加载本地磁盘文件,就可以方便代码管理工具(如 git)管理我们的合约代码。我详细介绍下如何这个功能怎么使用? 使用在线版本的Remix可以使用这个功能, 不过需要安装一下remixd, 安装使用命令npm install -g remixd。 在需要的本地合约代码的目录下启动remix-ide, Remix IDE 会自动把当前目录做为共享目录。如果是使用在线的Remix,需要使用命令remixd -s shared-folder 来指定共享目录。 加载共享目录,在文件浏览区域上有,有这样一个图标,他用来加载本地共享目录,如图: 调试在合约编写过程中,合约调试是必不可少的一部分,为了模拟调试的过程,我故意在代码中加入一ge错误的逻辑代码如下: 1234567891011121314pragma solidity ^0.4.0;contract SimpleStorage { uint storedData; function set(uint x) public { storedData += x; // 错误的,多加了一个加号 } function get() public constant returns (uint) { return storedData; }} 加入了错误的逻辑之后,我第2次调用set函数,合约状态变量的值,可能会出错(如果第一次不是用参数0去调用的话)。注意如果需要调试合约,在部署合约的环境应该选择:JavaScript VM。 开始调试在我们每次执行一个交易(不管是方式调用还是函数执行)的时候,在日志都会输出一条记录,如图: 点击上图中的“Debug”按钮,在Remix右侧的功能区域会切换到调试面板,如下图:调试过程过程中,有下面几项需要重点关注: Transactions: 可以查看交易及交易的执行过程,并且提供了7个调试的按钮,如下图: 为了方便介绍,我为每个按钮编了号,每个按钮的含义是: 后退一步(不进入函数内部) 后退一步(进入函数内部) 前进一步(进入函数内部) 前进一步(不进入函数内部) 跳到上一个断点 跳出当前调用 跳到下一个断点 Solidity Locals:显示当前上下文的局部变量的值, 如图: Solidity State: 显示当前执行合约的状态变量,如下图: 在本例中,我们跟踪运行步骤的时候,可以看到局部变量的值为2,赋值给状态变量之后,状态变量的值更改为了3,所以可以判断运行当前语句的时候出错了。 Step detail: 显示当前步骤的gas详情等,如下图: 设置断点这部分为小专栏读者准备,欢迎订阅小专栏区块链技术查看。 参考链接 remix-ide github Remix Document 我们还为区块链技术爱好者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程及以太坊智能合约开发。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果你学习区块链中遇到问题,欢迎加入知识星球深入浅出区块链问答社区,作为星友福利,星友可加入我创建的区块链技术群,群内已经聚集了300多位区块链技术牛人和爱好者。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>IDE</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Solidity 教程系列11 - 视图函数、虚函数讲解]]></title>
<url>%2F2018%2F05%2F17%2Fsolidity-functions%2F</url>
<content type="text"><![CDATA[Solidity 教程系列第11篇 - Solidity 视图函数、虚函数讲解。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 视图函数(View Functions)一个函数如果它不修改状态变量,应该声明为view函数,不过下面几种情况认为是修改了状态: 写状态变量 触发事件(events) 创建其他的合约 call调用附加了以太币 调用了任何没有view或pure修饰的函数 使用了低级别的调用(low-level calls) 使用了包含特定操作符的内联汇编 看一个例子: 1234567891011121314pragma solidity ^0.4.16;contract C { uint public data = 0; function f(uint a, uint b) public view returns (uint) { return a * (b + 42) + now; } // 错误做法,虽然可以编译通过 function df(uint a) public view { data = a; }} 有几个地方需要注意一下: 声明为view 和声明为constant是等价的,constant是view的别名,constant在计划Solidity 0.5.0版本之后会弃用(constant这个词有歧义,view 也更能表达返回值可视)。 访问函数都被标记为view。 当前编译器并未强制要求声明为view,但建议大家对于不会修改状态的函数的标记为view。 纯函数(Pure Functions)函数可以声明为view,表示它即不读取状态,也不修改状态,除了上一节介绍的几种修改状态的情况,以下几种情况被认为是读取了状态: 读状态变量 访问了 this.balance 或 \.balance 访问了block, tx, msg 的成员 (msg.sig 和 msg.data除外). 调用了任何没有pure修饰的函数 使用了包含特定操作符的内联汇编 看一个例子: 1234567pragma solidity ^0.4.16;contract C { function f(uint a, uint b) public pure returns (uint) { return a * (b + 42); }} 尽管view 和 pure 修饰符编译器并未强制要求使用,view 和 pure 修饰也不会带来gas 消耗的改变,但是更好的编码习惯让我们跟容易发现智能合约中的错误。 参考文献官方文档-函数 欢迎来知识星球提问,星球内已经聚集了300多位区块链技术爱好者。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
<category>Solidity</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Solidity</tag>
</tags>
</entry>
<entry>
<title><![CDATA[详解 Solidity 事件Event - 完全搞懂事件的使用]]></title>
<url>%2F2018%2F05%2F09%2Fsolidity-event%2F</url>
<content type="text"><![CDATA[很多同学对Solidity 中的Event有疑问,这篇文章就来详细的看看Solidity 中Event到底有什么用? 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么,另外本文在监听合约事件是对上一篇Web3与智能合约交互实战进行补充,如果阅读了上一篇可以更好的理解本文。 什么是事件Evnet事件是以太坊虚拟机(EVM)日志基础设施提供的一个便利接口。当被发送事件(调用)时,会触发参数存储到交易的日志中(一种区块链上的特殊数据结构)。这些日志与合约的地址关联,并记录到区块链中.来捋这个关系:区块链是打包一系列交易的区块组成的链条,每一个交易“收据”会包含0到多个日志记录,日志代表着智能合约所触发的事件。 在DAPP的应用中,如果监听了某事件,当事件发生时,会进行回调。不过要注意:日志和事件在合约内是无法被访问的,即使是创建日志的合约。 在Solidity 代码中,使用event 关键字来定义一个事件,如: 1event EventName(address bidder, uint amount); 这个用法和定义函数式一样的,并且事件在合约中同样可以被继承。触发一个事件使用emit(说明,之前的版本里并不需要使用emit),如: 1emit EventName(msg.sender, msg.value); 触发事件可以在任何函数中调用,如: 12345function testEvent() public { // 触发一个事件 emit EventName(msg.sender, msg.value); } 监听事件通过上面的介绍,可能大家还是不清楚事件有什么作用,如果你跟过Web3与智能合约交互实战这篇文章,你会发现点击”Updata Info”按钮之后,虽然调用智能合约成功,但是当前的界面并没有得到更新。使用事件监听,就可以很好的解决这个问题,让看看如何实现。 修改合约,定义事件及触发事件先回顾一下合约代码: 12345678910111213141516pragma solidity ^0.4.21;contract InfoContract { string fName; uint age; function setInfo(string _fName, uint _age) public { fName = _fName; age = _age; } function getInfo() public constant returns (string, uint) { return (fName, age); } } 首先,需要定义一个事件: 1234event Instructor( string name, uint age ); 这个事件中,会接受两个参数:name 和 age , 也就是需要跟踪的两个信息。 然后,需要在setInfo函数中,触发Instructor事件,如: 12345function setInfo(string _fName, uint _age) public { fName = _fName; age = _age; emit Instructor(_fName, _age);} 在Web3与智能合约交互实战, 点击”Updata Info”按钮之后,会调用setInfo函数,函数时触发Instructor事件。 使用Web3监听事件,刷新UI现在需要使用Web3监听事件,刷新UI。先回顾下之前的使用Web3和智能合约交互的代码: 1234567891011121314151617181920212223242526272829<script> if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { // set the provider you want from Web3.providers web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); } web3.eth.defaultAccount = web3.eth.accounts[0]; var infoContract = web3.eth.contract(ABI INFO); var info = infoContract.at('CONTRACT ADDRESS'); info.getInfo(function(error, result){ if(!error) { $("#info").html(result[0]+' ('+result[1]+' years old)'); console.log(result); } else console.error(error); }); $("#button").click(function() { info.setInfo($("#name").val(), $("#age").val()); });</script> 现在可以不需要 info.getInfo()来获取信息,而改用监听事件获取信息,先定义一个变量引用事件: 1var instructorEvent = info.Instructor(); 然后使用.watch()方法来添加一个回调函数: 12345678instructorEvent.watch(function(error, result) { if (!error) { $("#info").html(result.args.name + ' (' + result.args.age + ' years old)'); } else { console.log(error); } }); 代码更新之后,可以在浏览器查看效果,这是点击”Updata Info”按钮之后,会及时更新界面,如图: 完整的代码请订阅小专栏区块链技术查看。 事件高级用法-过滤器有时我们会有这样的需求:获取当前所有姓名及年龄记录,或者是,要过滤出年龄28岁的记录,应该如何做呢?以及另外一个常见的场景:想要获取到代币合约中所有的转账记录,也同样需要使用事件过滤器功能,这部分内容请大家订阅小专栏区块链技术阅读。 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 参考文章https://coursetro.com/posts/code/100/Solidity-Events-Tutorial---Using-Web3.js-to-Listen-for-Smart-Contract-Eventshttps://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果你想和我有密切的联系,欢迎加入知识星球深入浅出区块链,我会在星球为大家解答技术问题,作为星友福利,星友可加入我创建的区块链技术群,群内已经聚集了300多位区块链技术牛人和爱好者。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Solidity</tag>
<tag>Event</tag>
<tag>web3</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约最佳实践 之 Solidity 编码规范]]></title>
<url>%2F2018%2F05%2F04%2Fsolidity-style-guide%2F</url>
<content type="text"><![CDATA[每一门语言都有其相应的编码规范, Solidity 也一样, 下面官方推荐的规范及我的总结,供大家参考,希望可以帮助大家写出更好规范的智能合约。 命名规范避免使用小写的l,大写的I,大写的O 应该避免在命名中单独出现,因为很容易产生混淆。 合约、库、事件、枚举及结构体命名 合约、库、事件及结构体命名应该使用单词首字母大写的方式,这个方式也称为:帕斯卡命名法或大驼峰式命名法,比如:SimpleToken, SmartBank, CertificateHashRepository,Player。 函数、参数、变量及修饰器函数、参数、变量及修饰器应该使用首单词小写后面单词大写的方式,这个方式也称为:(小)驼峰式命名法,是一种混合大小写的方式,如: 函数名应该如:getBalance,transfer,verifyOwner,addMember。 参数和变量应该如:initialSupply,senderAddress,account,isPreSale。 修饰器应该如:onlyAfter,onlyOwner。 代码格式相关缩进使用空格(spaces)而不是Tab, 缩进应该是4个空格 空行合约之间应该有空行,例如: 12345678910111213contract A { ...} contract B { ...} contract C { ...} 而不是使用: 12345678910contract A { ...}contract B { ...} contract C { ...} 函数之间应该有空行,例如: 123456789contract A { function spam() public { ... } function ham() public { ... }} 没有实现的话,空行可以省去,如: 1234contract A { function spam() public; function ham() public;} 而不是: 12345678contract A { function spam() public { ... } function ham() public { ... }} 左括号应该跟定义在一行定义包括合约定义、函数定义、库定义、结构体定义等等,例如推荐使用: 123456 contract Coin { struct Bank { address owner; uint balance; }} 而不是: 1234567contract Coin{ struct Bank { address owner; uint balance; }} 左括号应该跟条件控制在一行在使用if, else, while, for 时,推荐的写法是: 1234567if (...) { ...}for (...) { ...} 而不是: 12345678910if (...){ ...}while(...){}for (...) { ...;} 如果控制语句内只有一行,括号可省略,如: 12if (x < 10) x += 1; 但像下面一个语句有多方就不能省略,如: 12345if (x < 10) someArray.push(Coin({ name: 'spam', value: 42 })); 表达式内的空格 一个单行的表达里,在小括号、中括号、大括号里应该避免不必要的空格,例如推荐使用: 1spam(ham[1], Coin({name: "ham"})); 而不是: 1spam( ham[ 1 ], Coin( { name: "ham" } ) ); 有一种例外是,结尾的括号跟在结束的分号后面, 应该加一个空格,如下面的方式也是推荐的: 1function singleLine() public { spam(); } 分号;前不应该有空格,例如推荐使用: 1function spam(uint i, Coin coin) public; 而不是: 1function spam(uint i , Coin coin) public ; 不要为对齐添加不必要的空格,例如推荐使用: 123x = 1;y = 2;long_variable = 3; 而不是: 123x = 1;y = 2;long_variable = 3; 回退函数不应该有空格,例如推荐使用: 123456789101112function() public { ...}``` 而不是:```jsfunction () public { ...} 控制每一行长度每行不应该太长,最好在79(或99)个字符以内,函数的参数应该是单独的行,且只有一个缩进,例如推荐的方式是: 12345thisFunctionCallIsReallyLong( longArgument1, longArgument2, longArgument3); 而不是: 12345678910111213141516171819202122232425thisFunctionCallIsReallyLong(longArgument1, longArgument2, longArgument3);thisFunctionCallIsReallyLong(longArgument1, longArgument2, longArgument3);thisFunctionCallIsReallyLong( longArgument1, longArgument2, longArgument3);thisFunctionCallIsReallyLong(longArgument1,longArgument2,longArgument3);thisFunctionCallIsReallyLong( longArgument1, longArgument2, longArgument3); 对应的赋值语句应该是这样写: 123456 thisIsALongNestedMapping[being][set][to_some_value] = someFunction( argument1, argument2, argument3, argument4); 而不是: 1234thisIsALongNestedMapping[being][set][to_some_value] = someFunction(argument1, argument2, argument3, argument4); 事件定义也应该遵循同样的原则,例如应该使用: 123456789101112131415event LongAndLotsOfArgs( adress sender, adress recipient, uint256 publicKey, uint256 amount, bytes32[] options);LongAndLotsOfArgs( sender, recipient, publicKey, amount, options); 而不是: 1234567891011event LongAndLotsOfArgs(adress sender, adress recipient, uint256 publicKey, uint256 amount, bytes32[] options);LongAndLotsOfArgs(sender, recipient, publicKey, amount, options); 文件编码格式推荐使用utf-8 及 ASCII 编码 引入文件应该在最上方建议使用: 1234567891011import "owned";contract A { ...}contract B is owned { ...} 而不是: 1234567891011contract A { ...}import "owned";contract B is owned { ...} 函数编写规范函数的顺序在编写函数的时候,应该让大家容易找到构造函数,回退函数,官方推荐的的函数顺序是: 构造函数 回退函数 (如果有) 外部函数(external) 公有函数(public) 内部函数(internal) 私有函数(private) 同一类函数时,constant函数放在后面, 例如推荐方式为: 1234567891011121314151617181920212223242526 contract A { // 构造函数 function A() public { ... } // 回退函数 function() public { ... } // 外部函数 // ... // 带有constant 外部函数 // ... // 公有函数 // ... // 内部函数 // ... // 私有函数 // ...} 而不是下面的函数顺序: 1234567891011121314151617181920212223 contract A { // 外部函数 // ... // 公有函数 // ... // 内部函数 // ... function A() public { ... } function() public { ... } // 私有函数 // ...} 明确函数的可见性所有的函数(包括构造函数)应该在定义的时候明确函数的可见性,例如应该使用: 123function explicitlyPublic(uint val) public { doSomething();} 而不是 123function implicitlyPublic(uint val) { doSomething();} 可见性应该在修饰符前面函数的可见性应该写在自定义的函数修饰符前面,例如: 123function kill() public onlyowner { selfdestruct(owner);} 而不是 123function kill() onlyowner public { selfdestruct(owner);} 区分函数和事件为了防止函数和事件(Event)产生混淆,声明一个事件使用大写并加入前缀(可使用LOG)。对于函数, 始终以小写字母开头,构造函数除外。 1234567// 不建议event Transfer() {}function transfer() {}// 建议event LogTransfer() {}function transfer() external {} 常量常量应该使用全大写及下划线分割大词的方式,如:MAX_BLOCKS,TOKEN_NAME, CONTRACT_VERSION。 参考文献Solidity style-guide 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果你想和我有密切的联系,欢迎加入知识星球深入浅出区块链,我会在星球为大家解答技术问题,作为星友福利,星友可加入我创建的区块链技术群,群内已经聚集了200多位区块链技术牛人和爱好者。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[美链BEC合约漏洞技术分析]]></title>
<url>%2F2018%2F04%2F25%2Fbec-overflow%2F</url>
<content type="text"><![CDATA[这两天币圈链圈被美链BEC智能合约的漏洞导致代币价值几乎归零的事件刷遍朋友圈。这篇文章就来分析下BEC智能合约的漏洞 漏洞攻击交易我们先来还原下攻击交易,这个交易可以在这个链接查询到。我截图给大家看一下: 攻击者向两个账号转移57896044618…000.792003956564819968个BEC,相当于BEC凭空进行了一个巨大的增发,几乎导致BEC价格瞬间归零。下面我们来分析下这个攻击过程。 合约漏洞分析我们先来看看BEC智能合约的代码,BEC在合约中加入一个批量转账的函数,它的实现如下: 123456789101112function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) { uint cnt = _receivers.length; uint256 amount = uint256(cnt) * _value; require(cnt > 0 && cnt <= 20); require(_value > 0 && balances[msg.sender] >= amount); balances[msg.sender] = balances[msg.sender].sub(amount); for (uint i = 0; i < cnt; i++) { balances[_receivers[i]] = balances[_receivers[i]].add(_value); Transfer(msg.sender, _receivers[i], _value); } return true; 这个函数的作用是,调用者传入若干个地址和转账金额,在经过一些条件检查之后,对msg.sender的余额进行减操作,对每一个对每一个传入的地址进行加操作,以实现BEC的转移。问题出在 uint256 amount = uint256(cnt) * _value; 这句代码,当传入值_value过大时(接近uint256的取值范围的最大值),uint256 amount = uint256(cnt) * _value计算时会发生溢出,导致amount实际的值是一个非常小的数(此时amount不再是cnt * _value的实际值),amount很小,也使得后面对调用者余额校验可正常通过(即require(_value > 0 && balances[msg.sender] >= amount)语句通过)。 我们来结合实际攻击交易使用的参数来分析一下: batchTransfer的参数_value值为16进制的800000000000000000000...,参数_receivers数组的大小为2,相乘之后刚好可超过uint256所能表示的整数大小上限,引发溢出问题amount实际的值为0,后面的转账操作实际上msg.sender的余额减0, 而对两个账号进行了加16进制的800000000000000000000...,最终的结果是相当于增发了2 * 16进制的800000000000000000000...。 实际上对于这种整数溢出漏洞,最简单的方法是采用 SafeMath 数学计算库来避免。有趣的是BEC智能合约代码中,其实其他的都使用了SafeMath, 而关键的uint256 amount = uint256(cnt) * _value却没有使用。心痛程序员,也心痛韭菜。这句代码改为uint256 amount = _value.mul(uint256(cnt));就可以防止溢出问题 所以在做加减乘除的时候请记得一定使用:SafeMath,代码在这里 溢出补充说明溢出补充说明为小专栏订阅用户福利,小专栏的文章内介绍了什么时候会发生上溢,什么时候会发生下溢,并且给出了代码事例。大家可请前往我的小专栏阅读。 知识星球深入浅出区块链做好的区块链技术问答社区,欢迎来提问,作为星球成员福利,成员可加入区块链技术付费交流群。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Web3与智能合约交互实战]]></title>
<url>%2F2018%2F04%2F15%2Fweb3-html%2F</url>
<content type="text"><![CDATA[Web3与智能合约交互实战 写在前面在最初学习以太坊的时候,很多人都是自己创建以太坊节点后,使用geth与之交互。这种使用命令行交互的方法虽然让很多程序员感到兴奋(黑客帝国的既视感?),但不可能指望普通用户通过命令行使用Dapp。因此,我们需要一种友好的方式(比如一个web页面)来与智能合约交互,于是问题的答案就是web3.js。 Web3.jsWeb3.js是以太坊官方的Javascript API,可以帮助智能合约开发者使用HTTP或者IPC与本地的或者远程的以太坊节点交互。实际上就是一个库的集合,主要包括下面几个库: web3-eth用来与以太坊区块链和智能合约交互 web3-shh用来控制whisper协议与p2p通信以及广播 web3-bzz用来与swarm协议交互 web3-utils包含了一些Dapp开发有用的功能 Web3与geth通信使用的是 JSON-RPC ,这是一种轻量级的RPC(Remote Procedure Call)协议,整个通信的模型可以抽象为下图。 搭建测试链在开发初期,我们并没有必要使用真实的公链,为了开发效率,一般选择在本地搭建测试链。在本文我们选择的Ganache(在此之前使用的是testrpc,Ganache属于它的升级版),一个图形化测试软件(也有命令行版本),可以一键在本地搭建以太坊区块链测试环境,并且将区块链的状态通过图形界面显示出来,Ganache的运行界面如下图所示。 从图中可以看到Ganache会默认创建10个账户,监听地址是http://127.0.0.1:7545,可以实时看到Current Block、Gas Price、Gas Limit等信息。 创建智能合约目前以太坊官方全力支持的智能合约开发环境是Remix IDE,我们在合约编辑页面编写如下代码: 12345678910111213141516pragma solidity ^0.4.21;contract InfoContract { string fName; uint age; function setInfo(string _fName, uint _age) public { fName = _fName; age = _age; } function getInfo() public constant returns (string, uint) { return (fName, age); } } 代码很简单,就是简单的给name和age变量赋值与读取,接下来切换到 run 的 tab 下,将Environment切换成Web3 Provider,并输入我们的测试链的地址http://127.0.0.1:7545,这里对这三个选项做一简单说明: Javascript VM:简单的Javascript虚拟机环境,纯粹练习智能合约编写的时候可以选择 Injected Web3:连接到嵌入到页面的Web3,比如连接到MetaMask Web3 Provider:连接到自定义的节点,如私有的测试网络。 如果连接成功,那么在下面的Account的选项会默认选择 Ganache 创建的第一个账户地址。接下来我们点击Create就会将我们的智能合约部署到我们的测试网中。接下来 Remix 的页面不要关闭,在后面编写前端代码时还要用到合约的地址以及ABI信息。 安装Web3在这之前,先在终端创建我们的项目: 12> mkdir info> cd info 接下来使用 node.js 的包管理工具 npm 初始化项目,创建package.json 文件,其中保存了项目需要的相关依赖环境。 1> npm init 一路按回车直到项目创建完成。最后,运行下面命令安装web.js: 1> npm install web3 注意: 在实际安装过程中我发现web3在安装完成后并没有 /node_modules/web3/dist/we3.min.js 文件,这个问题在 issue#1041中有体现,但官方好像一直没解决。不过可以在这里下载所需的文件,解压后将dist文件夹的内容拷贝到 /node_modules/web3路径下。 创建 UI在项目目录下创建index.html,在这里我们将创建基础的 UI,功能包括name和age的输入框,以及一个按钮,这些将通过 jQuery 实现: 123456789101112131415161718192021222324252627282930313233343536373839<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" type="text/css" href="main.css"> <script src="./node_modules/web3/dist/web3.min.js"></script></head><body> <div class="container"> <h1>Info Contract</h1> <h2 id="info"></h2> <label for="name" class="col-lg-2 control-label">Name</label> <input id="name" type="text"> <label for="name" class="col-lg-2 control-label">Age</label> <input id="age" type="text"> <button id="button">Update Info</button> </div> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> <script> // Our future code here.. </script></body></html> 接下来需要编写main.css文件设定基本的样式: 1234567891011121314151617181920212223242526272829body { background-color:#F0F0F0; padding: 2em; font-family: 'Raleway','Source Sans Pro', 'Arial';}.container { width: 50%; margin: 0 auto;}label { display:block; margin-bottom:10px;}input { padding:10px; width: 50%; margin-bottom: 1em;}button { margin: 2em 0; padding: 1em 4em; display:block;}#info { padding:1em; background-color:#fff; margin: 1em 0;} ##使用Web3与智能合约交互UI 创建好之后,在<script>标签中间编写web.js的代码与智能合约交互。首先创建web3实例,并与我们的测试环境连接: 12345678<script> if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { // set the provider you want from Web3.providers web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); }</script> 这段代码是web3.js Github提供的样例,意思是如果web3已经被定义,那么就可以直接当作我们的 provider 使用。如果没有定义,则我们手动指定 provider。 这里可能会存在疑问:为什么 web3 会被事先定义呢?实际上,如果你使用类似 MetaMask(一个 Chrome 上的插件,迷你型以太坊钱包)这样的软件,provider 就会被自动植入。 在上面代码的基础上,接下来设置默认的以太坊账户: 1web3.eth.defaultAccount = web3.eth.accounts[0]; 在上文中我们使用 Ganache 已经创建了 10 个账户了,这里我们选择第一个账户当作默认账户。 接下来需要让我们的web3知道我们的合约是什么样的,这里需要用到合约的 ABI(Application Binary Interface)。ABI可以使我们调用合约的函数,并且从合约中获取数据。 在上文中我们已经在 Remix 中创建了我们的合约,这时重新回到 Remix,在 Compile 的 tab 下我们点击Details 出现的页面中我们可以拷贝合约的ABI,如下图所示。将其复制到代码中: 1var infoContract = web3.eth.contract(PASTE ABI HERE!); 接下来转到 run 的tab,拷贝合约的地址,将其复制到下面的代码中: 1var info = InfoContract.at('PASTE CONTRACT ADDRESS HERE'); 完成这些我们就可以调用合约中的函数了,下面我们使用 jQuery 与我们的合约进行交互: 12345678910111213info.getInfo(function(error, result){ if(!error) { $("#info").html(result[0]+' ('+result[1]+' years old)'); console.log(result); } else console.error(error);});$("#button").click(function() { info.setInfo($("#name").val(), $("#age").val());}); 以上的代码就简单地实现了对合约中两个函数的调用,分别读取和显示name和age变量。 到此我们就完成了全部的代码,完整代码可以在 InfoContract 中找到。在浏览器中打开index.html测试效果如下图(输入名字和年龄后刷新)。 本文中点击”Updata Info”按钮之后,虽然调用智能合约成功,但是当前的界面并没有得到更新,下一篇文章会介绍Web3监听合约事件更新界面。 本文的作者是盖盖,他的微信公众号: chainlab 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 参考文献 Interacting with a Smart Contract through Web3.js (Tutorial)) 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果你想和我有密切的联系,欢迎加入知识星球深入浅出区块链,我会在星球为大家解答技术问题,作为星友福利,星友可加入我创建的区块链技术群,群内已经聚集了300多位区块链技术牛人和爱好者。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>以太坊</tag>
<tag>Web3</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列10 - 完全理解函数修改器]]></title>
<url>%2F2018%2F04%2F09%2Fsolidity-modify%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第10篇,带大家完全理解Solidity的函数修改器。Solidity系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 函数修改器(Function Modifiers)函数修改器(Modifiers)可以用来改变一个函数的行为。比如用于在函数执行前检查某种前置条件。 如果熟悉Python的同学,会发现函数修改器的作用和Python的装饰器很相似。 修改器是一种可被继承合约属性,同时还可被继承的合约重写(override)。下面我们来看一段示例代码: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546pragma solidity ^0.4.11;contract owned { function owned() public { owner = msg.sender; } address owner; // 定义了一个函数修改器,可被继承 // 修饰时,函数体被插入到 “_;” 处 // 不符合条件时,将抛出异常 modifier onlyOwner { require(msg.sender == owner); _; }}contract mortal is owned { // 使用继承的`onlyOwner` function close() public onlyOwner { selfdestruct(owner); }}contract priced { // 函数修改器可接收参数 modifier costs(uint price) { if (msg.value >= price) { _; } }}contract Register is priced, owned { mapping (address => bool) registeredAddresses; uint price; function Register(uint initialPrice) public { price = initialPrice; } // 需要提供payable 以接受以太 function register() public payable costs(price) { registeredAddresses[msg.sender] = true; } function changePrice(uint _price) public onlyOwner { price = _price; }} 上面onlyOwner就是定义的一个函数修改器,当用这个修改器区修饰一个函数时,则函数必须满足onlyOwner的条件才能运行,这里的条件是:必须是合约的创建这才能调用函数,否则抛出异常。我们在实现一个可管理、增发、兑换、冻结等高级功能的代币文章中就使用了这个函数修改器。 多个修改器如果同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。 在修改器中或函数内的显式的return语句,仅仅跳出当前的修改器或函数。返回的变量会被赋值,但执行流会在前一个修改器后面定义的”_”后继续执行, 如: 12345678910111213141516contract Mutex { bool locked; modifier noReentrancy() { require(!locked); locked = true; _; locked = false; } // 防止递归调用 // return 7 之后,locked = false 依然会执行 function f() public noReentrancy returns (uint) { require(msg.sender.call()); return 7; }} 修改器的参数可以是任意表达式。在此上下文中,所有的函数中引入的符号,在修改器中均可见。但修改器中引入的符号在函数中不可见,因为它们有可能被重写。 深入理解修改器的执行次序再来看一个复杂一点的例子,来深入理解修改器: 1234567891011121314151617181920212223242526272829303132333435pragma solidity ^0.4.11;contract modifysample { uint a = 10; modifier mf1 (uint b) { uint c = b; _; c = a; a = 11; } modifier mf2 () { uint c = a; _; } modifier mf3() { a = 12; return ; _; a = 13; } function test1() mf1(a) mf2 mf3 public { a = 1; } function test2() public constant returns (uint) { return a; } } 上面的智能合约运行test1()之后,状态变量a的值是多少, 是1, 11, 12,还是13呢?答案是 11, 大家可以运行下test2获取下a值。 我们来分析一下 test1, 它扩展之后是这样的: 12345678uint c = b; uint c = a; a = 12; return ; _; a = 13;c = a;a = 11; 这个时候就一目了然了,最后a 为11, 注意第5及第6行是不是执行的。 参考文献官方文档-Function Modifiers 如果你想和认识我,和我建立联系,欢迎加入知识星球深入浅出区块链,我会在星球为大家解答技术问题,作为星友福利,星友可加入我创建的区块链技术群,群内已经聚集了200多位区块链技术爱好者。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列9 - 错误处理]]></title>
<url>%2F2018%2F04%2F07%2Fsolidity-errorhandler%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第9篇介绍Solidity 错误处理。Solidity系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 什么是错误处理错误处理是指在程序发生错误时的处理方式,Solidity处理错误和我们常见的语言不一样,Solidity是通过回退状态的方式来处理错误。发生异常时会撤消当前调用(及其所有子调用)所改变的状态,同时给调用者返回一个错误标识。注意捕捉异常是不可能的,因此没有try … catch…。 为什么Solidity处理错误要这样设计呢?我们可以把区块链理解为是全球共享的分布式事务性数据库。全球共享意味着参与这个网络的每一个人都可以读写其中的记录。如果想修改这个数据库中的内容,就必须创建一个事务,事务意味着要做的修改(假如我们想同时修改两个值)只能被完全的应用或者一点都没有进行。学习过数据库的同学,应该理解事务的含义,如果你对事务一词不是很理解,建议你搜索一下“数据库事务“。Solidity错误处理就是要保证每次调用都是事务性的。 如何处理Solidity提供了两个函数assert和require来进行条件检查,如果条件不满足则抛出异常。assert函数通常用来检查(测试)内部错误,而require函数来检查输入变量或合同状态变量是否满足条件以及验证调用外部合约返回值。另外,如果我们正确使用assert,有一个Solidity分析工具就可以帮我们分析出智能合约中的错误,帮助我们发现合约中有逻辑错误的bug。 除了可以两个函数assert和require来进行条件检查,另外还有两种方式来触发异常: revert函数可以用来标记错误并回退当前调用 使用throw关键字抛出异常(从0.4.13版本,throw关键字已被弃用,将来会被淘汰。) 当子调用中发生异常时,异常会自动向上“冒泡”。 不过也有一些例外:send,和底层的函数调用call, delegatecall,callcode,当发生异常时,这些函数返回false。 注意:在一个不存在的地址上调用底层的函数call,delegatecall,callcode 也会返回成功,所以我们在进行调用时,应该总是优先进行函数存在性检查。 在下面通过一个示例来说明如何使用require来检查输入条件,以及assert用于内部错误检查: 1234567891011pragma solidity ^0.4.0;contract Sharer { function sendHalf(address addr) public payable returns (uint balance) { require(msg.value % 2 == 0); // 仅允许偶数 uint balanceBeforeTransfer = this.balance; addr.transfer(msg.value / 2); // 如果失败,会抛出异常,下面的代码就不是执行 assert(this.balance == balanceBeforeTransfer - msg.value / 2); return this.balance; }} 我们实际运行下,看看异常是如何发生的: 首先打开Remix,贴入代码,点击创建合约。如下图: 运行测试1:附加1wei (奇数)去调用sendHalf,这时会发生异常,如下图: 运行测试2:附加2wei 去调用sendHalf,运行正常。 运行测试3:附加2wei以及sendHalf参数为当前合约本身,在转账是发生异常,因为合约无法接收转账,错误提示上图类似。 assert类型异常在下述场景中自动产生assert类型的异常: 如果越界,或负的序号值访问数组,如i >= x.length 或 i < 0时访问x[i] 如果序号越界,或负的序号值时访问一个定长的bytesN。 被除数为0, 如5/0 或 23 % 0。 对一个二进制移动一个负的值。如:5<<i; i为-1时。 整数进行可以显式转换为枚举时,如果将过大值,负值转为枚举类型则抛出异常 如果调用未初始化内部函数类型的变量。 如果调用assert的参数为false require类型异常在下述场景中自动产生require类型的异常: 调用throw 如果调用require的参数为false 如果你通过消息调用一个函数,但在调用的过程中,并没有正确结束(gas不足,没有匹配到对应的函数,或被调用的函数出现异常)。底层操作如call,send,delegatecall或callcode除外,它们不会抛出异常,但它们会通过返回false来表示失败。 如果在使用new创建一个新合约时出现第3条的原因没有正常完成。 如果调用外部函数调用时,被调用的对象不包含代码。 如果合约没有payable修饰符的public的函数在接收以太币时(包括构造函数,和回退函数)。 如果合约通过一个public的getter函数(public getter funciton)接收以太币。 如果.transfer()执行失败 当发生require类型的异常时,Solidity会执行一个回退操作(指令0xfd)。当发生assert类型的异常时,Solidity会执行一个无效操作(指令0xfe)。在上述的两种情况下,EVM都会撤回所有的状态改变。是因为期望的结果没有发生,就没法继续安全执行。必须保证交易的原子性(一致性,要么全部执行,要么一点改变都没有,不能只改变一部分),所以需要撤销所有操作,让整个交易没有任何影响。 注意assert类型的异常会消耗掉所有的gas, 而require从大都会版本(Metropolis, 即目前主网所在的版本)起不会消耗gas。 参考文献 Solidity 错误处理 欢迎来我的知识星球深入浅出区块链讨论区块链技术,同时我也会为大家提供区块链技术解答,作为星友福利,星友可加入区块链技术付费交流群。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[剖析非同质化代币ERC721-全面解析ERC721标准]]></title>
<url>%2F2018%2F03%2F23%2Ftoken-erc721%2F</url>
<content type="text"><![CDATA[什么是ERC-721?现在我们看到的各种加密猫猫狗狗都是基于ERC-721创造出来的,每只都是一个独一无二的ERC-721代币,不过ERC-721在区块链世界远不止猫猫狗狗,它更大的想象空间在于将物理世界的资产映射到区块链上。本文就来剖析下什么是ERC721. ERC721是什么在创建代币一篇,我们讲到过ERC20代币,和ERC20一样,ERC721同样是一个代币标准,ERC721官方简要解释是Non-Fungible Tokens,简写为NFTs,多翻译为非同质代币。 ERC721 是由Dieter Shirley 在2017年9月提出。Dieter Shirley 正是谜恋猫CryptoKitties背后的公司Axiom Zen的技术总监。因此谜恋猫也是第一个实现了ERC721 标准的去中心化应用。ERC721号提议已经被以太坊作为标准接受,但该标准仍处于草稿阶段。本文介绍的ERC721标准基于最新(2018/03/23官方提议。 那怎么理解非同质代币呢? 非同质代表独一无二,谜恋猫为例,每只猫都被赋予拥有基因,是独一无二的(一只猫就是一个NFTs),猫之间是不能置换的。这种独特性使得某些稀有猫具有收藏价值,也因此受到追捧。 ERC20代币是可置换的,且可细分为N份(1 = 10 * 0.1), 而ERC721的Token最小的单位为1,无法再分割。 如果同一个集合的两个物品具有不同的特征,这两个物品是非同质的,而同质是某个部分或数量可以被另一个同等部分或数量所代替。 非同质性其实广泛存在于我们的生活中,如图书馆的每一本,宠物商店的每一只宠物,歌手所演唱的歌曲,花店里不同的花等等,因此ERC721合约必定有广泛的应用场景。通过这样一个标准,也可建立跨功能的NFTs管理和销售平台(就像有支持ERC20的交易所和钱包一样),使生态更加强大。 ERC721标准ERC721最为一个合约标准,提供了在实现ERC721代币时必须要遵守的协议,要求每个ERC721标准合约需要实现ERC721及ERC165接口,接口定义如下: 1234567891011121314151617181920pragma solidity ^0.4.20;interface ERC721 /* is ERC165 */ { event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool);} 接口说明: balanceOf(): 返回由_owner 持有的NFTs的数量。 ownerOf(): 返回tokenId代币持有者的地址。 approve(): 授予地址_to具有_tokenId的控制权,方法成功后需触发Approval 事件。 setApprovalForAll(): 授予地址_operator具有所有NFTs的控制权,成功后需触发ApprovalForAll事件。 getApproved()、isApprovedForAll(): 用来查询授权。 safeTransferFrom(): 转移NFT所有权,一次成功的转移操作必须发起 Transer 事件。函数的实现需要做一下几种检查: 调用者msg.sender应该是当前tokenId的所有者或被授权的地址 _from 必须是 _tokenId的所有者 _tokenId 应该是当前合约正在监测的NFTs 中的任何一个 _to 地址不应该为 0 如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256("onERC721Received(address,uint256,bytes)"))抛出异常。一个可接收NFT的合约必须实现ERC721TokenReceiver接口:1234interface ERC721TokenReceiver { /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);} transferFrom(): 用来转移NFTs, 方法成功后需触发Transfer事件。调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。此函数实现时需要检查上面条件的前4条。 ERC165 标准ERC721标准同时要求必须符合ERC165标准 ,其接口如下:123interface ERC165 { function supportsInterface(bytes4 interfaceID) external view returns (bool);} ERC165同样是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样再与合约进行交互的时候可以先调用此接口进行查询。interfaceID为函数选择器,计算方式有两种,如:bytes4(keccak256('supportsInterface(bytes4)'));或ERC165.supportsInterface.selector,多个函数的接口ID为函数选择器的异或值。关于ERC165,这里不深入介绍,有兴趣的同学可以阅读官方提案。 可选实现接口:ERC721MetadataERC721Metadata 接口用于提供合约的元数据:name , symbol 及 URI(NFT所对应的资源)。其接口定义如下:12345interface ERC721Metadata /* is ERC721 */ { function name() external pure returns (string _name); function symbol() external pure returns (string _symbol); function tokenURI(uint256 _tokenId) external view returns (string);} 接口说明: name(): 返回合约名字,尽管是可选,但强烈建议实现,即便是返回空字符串。 symbol(): 返回合约代币符号,尽管是可选,但强烈建议实现,即便是返回空字符串。 tokenURI(): 返回_tokenId所对应的外部资源文件的URI(通常是IPFS或HTTP(S)路径)。外部资源文件需要包含名字、描述、图片,其格式的要求如下:123456789101112131415161718{ "title": "Asset Metadata", "type": "object", "properties": { "name": { "type": "string", "description": "Identifies the asset to which this NFT represents", }, "description": { "type": "string", "description": "Describes the asset to which this NFT represents", }, "image": { "type": "string", "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive.", } }} tokenURI通常是被web3调用,以便在应用层做相应的查询和展示。 可选实现接口:ERC721EnumerableERC721Enumerable的主要目的是提高合约中NTF的可访问性,其接口定义如下:12345interface ERC721Enumerable /* is ERC721 */ { function totalSupply() external view returns (uint256); function tokenByIndex(uint256 _index) external view returns (uint256); function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);} 接口说明: totalSupply(): 返回NFT总量 tokenByIndex(): 通过索引返回对应的tokenId。 tokenOfOwnerByIndex(): 所有者可以一次拥有多个的NFT, 此函数返回_owner拥有的NFT列表中对应索引的tokenId。 补充说明NTF IDsNTF ID,即tokenId,在合约中用唯一的uint265进行标识,每个NFT的ID在智能合约的生命周期内不允许改变。推荐的实现方式有: 从0开始,每新加一个NFT,NTF ID加1 使用sha3后uuid 转换为 NTF ID 与ERC-20的兼容性ERC721标准尽可能遵循 ERC-20 的语义,但由于同质代币与非同质代币之间的根本差异,并不能完全兼容ERC-20。 交易、挖矿、销毁在实现transter相关接口时除了满足上面的的条件外,我们可以根据需要添加自己的逻辑,如加入黑名单等。同时挖矿、销毁尽管不是标准的一部分,我们可以根据需要实现。 参考实现参考实现为订阅用户专有福利,请订阅我的小专栏:区块链技术查看。 参考文献 EIPS-165 EIPS-721 欢迎来我的知识星球深入浅出区块链讨论区块链,作为星友福利,星友可加入区块链技术付费交流群。深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Token</tag>
<tag>ERC721</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何搭建以太坊私有链]]></title>
<url>%2F2018%2F03%2F18%2Fcreate_private_blockchain%2F</url>
<content type="text"><![CDATA[在开发以太坊时,很多时候需要搭建一条以太坊私有链,通过本文一起看看如何在Mac上进行搭建。 写在前面阅读本文前,你应该对以太坊语言有所了解,如果你还不了解,建议你先看以太坊是什么 go-ethereum客户端安装Go-ethereum客户端通常被称为Geth,它是个命令行界面,执行在Go上实现的完整以太坊节点。Geth得益于Go语言的多平台特性,支持在多个平台上使用(比如Windows、Linux、Mac)。Geth是以太坊协议的具体落地实现,通过Geth,你可以实现以太坊的各种功能,如账户的新建编辑删除,开启挖矿,ether币的转移,智能合约的部署和执行等等。所以,我们选择geth工具来进行开发。由于本人是mac,所以优先使用mac进行开发啦。mac中geth安装如下: 12brew tap ethereum/ethereumbrew install ethereum 检查是否安装成功 1geth --help 如果输出一些帮助提示命令,则说明安装成功。其他平台可参考Geth 安装 搭建私有链以太坊支持自定义创世区块,要运行私有链,我们就需要定义自己的创世区块,创世区块信息写在一个json格式的配置文件中。首先将下面的内容保存到一个json文件中,例如genesis.json。json文件内容如下: 1234567891011121314151617{ "config": { "chainId": 10, "homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0 }, "alloc" : {}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00"} 初始化:写入创世区块准备好创世区块json配置文件后,需要初始化区块链,将上面的创世区块信息写入到区块链中。首先要新建一个目录data0用来存放区块链数据(其实,这个目录data0就相当于一个根节点。当我们基于genesis.json生成根节点后,其他人就可以来连接此根节点,从而能进行交易)。data0目录结构如图所示: 接下来进入privatechain目录中,执行初始化命令: 12cd privatechaingeth --datadir data0 init genesis.json 上面的命令的主体是 geth init,表示初始化区块链,命令可以带有选项和参数,其中–datadir选项后面跟一个目录名,这里为 data0,表示指定数据存放目录为 data0, genesis.json是init命令的参数。 运行上面的命令,会读取genesis.json文件,根据其中的内容,将创世区块写入到区块链中。如果看到log信息中含有Successfully wrote genesis state字样,说明初始化成功。 初始化成功后的目录如下:其中geth/chaindata中存放的是区块数据,keystore中存放的是账户数据。 启动私有链节点初始化完成后,就有了一条自己的私有链,之后就可以启动自己的私有链节点并做一些操作,在终端中输入以下命令即可启动节点: 1geth --datadir data0 --networkid 1108 console 上面命令的主体是geth console,表示启动节点并进入交互式控制台,–datadir选项指定使用data0作为数据目录,–networkid选项后面跟一个数字,这里是1108,表示指定这个私有链的网络id为1108。网络id在连接到其他节点的时候会用到,以太坊公网的网络id是1,为了不与公有链网络冲突,运行私有链节点的时候要指定自己的网络id(上面命令可能会运行失败,我直接重启mac,再进入到privateChain目录中,简单粗暴)。 运行上面的命令后,就启动了区块链节点并进入了Javascript Console:这是一个交互式的Javascript执行环境,在这里面可以执行Javascript代码,其中>是命令提示符。在这个环境里也内置了一些用来操作以太坊的Javascript对象,可以直接使用这些对象。这些对象主要包括: eth:包含一些跟操作区块链相关的方法net:包含以下查看p2p网络状态的方法admin:包含一些与管理节点相关的方法miner:包含启动&停止挖矿的一些方法personal:主要包含一些管理账户的方法txpool:包含一些查看交易内存池的方法web3:包含了以上对象,还包含一些单位换算的方法 玩转Javascript Console进入以太坊Javascript Console后,就可以使用里面的内置对象做一些操作,这些内置对象提供的功能很丰富,比如查看区块和交易、创建账户、挖矿、发送交易、部署智能合约等。接下来介绍几个常用功能,下面的操作中,前面带>的表示在Javascript Console中执行的命令。 创建账户前面只是搭建了私有链,并没有自己的账户,可以在js console中输入eth.accounts来验证: 12> eth.accounts[] 此时没有账户,接下来使用personal对象来创建一个账户: 1234> personal.newAccount()> Passphrase:> Repeat passphrase:"0x4a3b0216e1644c1bbabda527a6da7fc5d178b58f" Passphrase其实就是密码的意思,输入两次密码后,就创建了一个账户。再次执行命令: 1234> personal.newAccount()> Passphrase:> Repeat passphrase:"0x46b24d04105551498587e3c6ce2c3341d5988938" 这时候再去看账户,就有两个了。 12> eth.accounts["0x4a3b0216e1644c1bbabda527a6da7fc5d178b58f", "0x46b24d04105551498587e3c6ce2c3341d5988938"] 账户默认会保存在数据目录的keystore文件夹中。查看目录结构,发现data0/keystore中多了两个文件,这两个文件就对应刚才创建的两个账户,这是json格式的文本文件,可以打开查看,里面存的是私钥经过密码加密后的信息。 json文件中信息格式如下: 123456789101112131415161718192021{ "address": "4a3b0216e1644c1bbabda527a6da7fc5d178b58f", "crypto": { "cipher": "aes-128-ctr", "ciphertext": "238d6d48126b762c8f13e84622b1bbb7713f7244c2f24555c99b76396fae8355", "cipherparams": { "iv": "d0f5a3d3e6c1eeec77bf631bc938725d" }, "kdf": "scrypt", "kdfparams": { "dklen": 32, "n": 262144, "p": 1, "r": 8, "salt": "70dc72c4eb63bea50f7637d9ff85bb53f6ca8ace17f4245feae9c0bc9abaad82" }, "mac": "bd7fc0c937c39f1cbbf1ca654c33b53d7f9c644c6dacfeefe1641d2f3decea04" }, "id": "57803d82-0cd4-4a78-9c29-9f9252fdcf60", "version": 3} 查看账户余额eth对象提供了查看账户余额的方法: 1234> eth.getBalance(eth.accounts[0])0> eth.getBalance(eth.accounts[1])0 目前两个账户的以太币余额都是0,要使账户有余额,可以从其他账户转账过来,或者通过挖矿来获得以太币奖励。 启动&停止挖矿通过miner.start()来启动挖矿: 1> miner.start(10) 其中start的参数表示挖矿使用的线程数。第一次启动挖矿会先生成挖矿所需的DAG文件,这个过程有点慢,等进度达到100%后,就会开始挖矿,此时屏幕会被挖矿信息刷屏。 如果想停止挖矿,并且进度已经达到100%之后,可以在js console中输入 1miner.stop(): 注意:输入的字符会被挖矿刷屏信息冲掉,没有关系,只要输入完整的miner.stop()之后回车,即可停止挖矿。 挖到一个区块会奖励5个以太币,挖矿所得的奖励会进入矿工的账户,这个账户叫做coinbase,默认情况下coinbase是本地账户中的第一个账户: 12> eth.coinbase"0x4a3b0216e1644c1bbabda527a6da7fc5d178b58f" 现在的coinbase是账户0,要想使挖矿奖励进入其他账户,通过miner.setEtherbase()将其他账户设置成coinbase即可: 1234> miner.setEtherbase(eth.accounts[1])true> eth.coinbase"0x46b24d04105551498587e3c6ce2c3341d5988938" 挖到区块以后,账户0里面应该就有余额了: 12> eth.getBalance(eth.accounts[0])2.31e+21 getBalance()返回值的单位是wei,wei是以太币的最小单位,1个以太币=10的18次方个wei。要查看有多少个以太币,可以用web3.fromWei()将返回值换算成以太币: 12> web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')2310 发送交易截止目前,账户一的余额还是0: 12> eth.getBalance(eth.accounts[1])0 可以通过发送一笔交易,从账户0转移10个以太币到账户1: 12345678> amount = web3.toWei(10,'ether')"10000000000000000000"> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})Error: authentication needed: password or unlock at web3.js:3143:20 at web3.js:6347:15 at web3.js:5081:36 at <anonymous>:1:1 这里报错了,原因是账户每隔一段时间就会被锁住,要发送交易,必须先解锁账户,由于我们要从账户0发送交易,所以要解锁账户0: 1234> personal.unlockAccount(eth.accounts[0])Unlock account 0x4a3b0216e1644c1bbabda527a6da7fc5d178b58fPassphrase: true 输入创建账户时设置的密码,就可以成功解锁账户。然后再发送交易: 12345> amount = web3.toWei(10,'ether')"10000000000000000000"> eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:amount})INFO [03-07|11:13:11] Submitted transaction fullhash=0x1b21bba16dd79b659c83594b0c41de42debb2738b447f6b24e133d51149ae2a6 recipient=0x46B24d04105551498587e3C6CE2c3341d5988938"0x1b21bba16dd79b659c83594b0c41de42debb2738b447f6b24e133d51149ae2a6" 我们去查看账户1中的余额: 12> eth.getBalance(eth.accounts[1])0 发现还没转过去,此时交易已经提交到区块链,但还未被处理,这可以通过查看txpool来验证: 12345> txpool.status{ pending: 1, queued: 0} 其中有一条pending的交易,pending表示已提交但还未被处理的交易。 要使交易被处理,必须要挖矿。这里我们启动挖矿,然后等待挖到一个区块之后就停止挖矿: 1> miner.start(1);admin.sleepBlocks(1);miner.stop(); 当miner.stop()返回true后,txpool中pending的交易数量应该为0了,说明交易已经被处理了,而账户1应该收到币了: 12> web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')10 查看交易和区块eth对象封装了查看交易和区块信息的方法。 查看当前区块总数: 12> eth.blockNumber463 通过区块号查看区块: 1234567891011121314151617181920212223> eth.getBlock(66){ difficulty: 135266, extraData: "0xd783010802846765746886676f312e31308664617277696e", gasLimit: 3350537, gasUsed: 0, hash: "0x265dfcc0649bf6240812256b2b9b4e3ae48d51fd8e43e25329ac111556eacdc8", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x4a3b0216e1644c1bbabda527a6da7fc5d178b58f", mixHash: "0xaf755722f62cac9b483d3437dbc795f2d3a02e28ec03d39d8ecbb6012906263c", nonce: "0x3cd80f6ec5c2f3e9", number: 66, parentHash: "0x099776a52223b892d13266bb3aec3cc04c455dc797185f0b3300d39f9fc0a8ec", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 535, stateRoot: "0x0c9feec5a201c8c98618331aecbfd2d4d93da1c6064abd0c41ae649fc08d8d06", timestamp: 1520391527, totalDifficulty: 8919666, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: []} 通过交易hash查看交易: 1234567891011121314151617> eth.getTransaction("0x1b21bba16dd79b659c83594b0c41de42debb2738b447f6b24e133d51149ae2a6"){ blockHash: "0x1cb368a27cc23c786ff5cdf7cd4351d48f4c8e8aea2e084a5e9d7c480449c79a", blockNumber: 463, from: "0x4a3b0216e1644c1bbabda527a6da7fc5d178b58f", gas: 90000, gasPrice: 18000000000, hash: "0x1b21bba16dd79b659c83594b0c41de42debb2738b447f6b24e133d51149ae2a6", input: "0x", nonce: 0, r: "0x31d22686e0d408a16497becf6d47fbfdffe6692d91727e5b7ed3d73ede9e66ea", s: "0x7ff7c14a20991e2dfdb813c2237b08a5611c8c8cb3c2dcb03a55ed282ce4d9c3", to: "0x46b24d04105551498587e3c6ce2c3341d5988938", transactionIndex: 0, v: "0x38", value: 10000000000000000000} 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>私有链</category>
</categories>
<tags>
<tag>以太坊</tag>
<tag>私有链</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何编写一个可升级的智能合约]]></title>
<url>%2F2018%2F03%2F15%2Fcontract-upgrade%2F</url>
<content type="text"><![CDATA[区块链信任基础的数据不可修改的特性,让它传统应用程序有一个很大的不同的地方是一经发布于区块链上就无法修改(不能直接在原有的合约上直接修改再重新发布)。 写在前面阅读本文前,你应该对以太坊、智能合约及Solidity语言有所了解,如果你还不了解,建议你先看以太坊是什么 当智能合约出现bug一方面正式由于智能合约的不可修改的特性,因为只要规则确定之后,没人能够修改它,大家才能够信任它。但另一方面,如果规则的实现有Bug, 可能会造成代币被盗,或是调用消耗大量的gas。这时就需要我们去修复错误。 我们知道一个智能合约包含两部分: 代码逻辑和数据,而代码逻辑又是最容易出问题的部分, 如在实现如下合约时,由于手抖在写addTen()时,10写成了11。 12345678910111213pragma solidity ^0.4.18;contract MyContract { mapping (address => uint256) public balanceOf; function setBlance(address _address,uint256 v) public { balanceOf[_address] = v; } function addTen(address addr) public returns (uint){ return balanceOf[addr] + 11; }} 假如我们在部署之后发现了这个问题,想要修复这个bug的话,只好重新部署合约,可是这时会有一个尴尬的问题,原来的合约已经有很多人使用,如果部署新的合约,老合约的数据将会丢失。 数据合约及控制合约那么如何解决上面的问题了,一个解决方案是分离合约中的数据,使用一个单独的合约来存储数据(下文称数据合约),使用一个单独的合约写业务逻辑(下文称控制合约)。我们来看看代码如何实现。 12345678910111213141516171819202122pragma solidity ^0.4.18;contract DataContract { mapping (address => uint256) public balanceOf; function setBlance(address _address,uint256 v) public { balanceOf[_address] = v; }}contract ControlContract { DataContract dataContract; function ControlContract(address _dataContractAddr) public { dataContract = DataContract(_dataContractAddr); } function addTen(address addr) public returns (uint){ return dataContract.balanceOf(addr) + 11; }} 现在我们有两个合约DataContract 专门用来存数据,ControlContract用来处理逻辑,并利用DataContract来读写数据。通过这样的设计,可以在更新控制合约后保持数据合约不变,这样就不会丢失数据,也不用迁移数据。 读写控制通过DataContract我们可以单独更新合约逻辑,不过你也许发现了一个新的问题,DataContract的数据不仅仅可以被ControlContract读写,还可以被其他的合约读写,因此需要对DataContract添加读写控制。我们给DataContract添加一个mapping, 用来控制哪些地址可以访问数据,同时添加了修饰器及设置访问的方法,代码如下: 1234567891011121314151617181920212223242526272829pragma solidity ^0.4.18;contract DataContract { mapping (address => uint256) public balanceOf; mapping (address => bool) accessAllowed; function DataContract() public { accessAllowed[msg.sender] = true; } function setBlance(address _address,uint256 v) public { balanceOf[_address] = v; } modifier platform() { require(accessAllowed[msg.sender] == true); _; } function allowAccess(address _addr) platform public { accessAllowed[_addr] = true; } function denyAccess(address _addr) platform public { accessAllowed[_addr] = false; }}... 订阅我的小专栏可参看合约的完整代码。 部署方法如下: 先部署DataContract合约 使用DataContract合约地址作为部署ControlContract合约的参数 用ControlContract合约地址作为参数调用DataContract合约的allowAccess方法。如果需要更新控制合约(如修复了addTen)则重新执行第2-3步,同时对老的控制合约执行denyAccess()。 更多当我们在实现数据合约时,它包含的逻辑应该越少越好,并且应该是严格测试过的,因为一旦数据合约部署之后,就没法更改。大多数情况下,和用户交互的是DApp, 因此当控制合约升级之后,需要升级DApp,使之关联新的控制合约。 尽管合约可以通过本文的方式升级,但我们依然要谨慎升级,因为升级表示你可以重写逻辑,会降低用户对你的信任度。本文介绍升级方法更多的是一种思路,实际项目中可能会对应多个控制合约及数据合约。 欢迎来我的知识星球讨论区块链技术。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列8 - Solidity API]]></title>
<url>%2F2018%2F03%2F14%2Fsolidity-api%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第8篇介绍Solidity API,它们主要表现为内置的特殊的变量及函数,存在于全局命名空间里。 Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 Solidity API 主要表现为Solidity 内置的特殊的变量及函数,他们存在于全局命名空间里,主要分为以下几类: 有关区块和交易的属性 ABI编码函数 有关错误处理 有关数学及加密功能 地址相关 合约相关 下面详细讲解下 区块和交易的属性(Block And Transaction Properties)用来提供一些区块链当前的信息。 blockhash(uint blockNumber) returns (bytes32):返回给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。 block.coinbase (address): 当前块矿工的地址。 block.difficulty (uint):当前块的难度。 block.gaslimit (uint):当前块的gaslimit。 block.number (uint):当前区块的块号。 block.timestamp (uint): 当前块的Unix时间戳(从1970/1/1 00:00:00 UTC开始所经过的秒数) gasleft() (uint256): 获取剩余gas。 msg.data (bytes): 完整的调用数据(calldata)。 msg.gas (uint): 当前还剩的gas(弃用)。 msg.sender (address): 当前调用发起人的地址。 msg.sig (bytes4):调用数据(calldata)的前四个字节(例如为:函数标识符)。 msg.value (uint): 这个消息所附带的以太币,单位为wei。 now (uint): 当前块的时间戳(block.timestamp的别名) tx.gasprice (uint) : 交易的gas价格。 tx.origin (address): 交易的发送者(全调用链) 注意:msg的所有成员值,如msg.sender,msg.value的值可以因为每一次外部函数调用,或库函数调用发生变化(因为msg就是和调用相关的全局变量)。 不应该依据 block.timestamp, now 和 block.blockhash来产生一个随机数(除非你确实需要这样做),这几个值在一定程度上被矿工影响(比如在赌博合约里,不诚实的矿工可能会重试去选择一个对自己有利的hash)。 对于同一个链上连续的区块来说,当前区块的时间戳(timestamp)总是会大于上一个区块的时间戳。 为了可扩展性的原因,你只能查最近256个块,所有其它的将返回0. ABI编码函数Solidity 提供了一下函数,用来直接得到ABI编码信息,这些函数有: * abi.encode(...) returns (bytes):计算参数的ABI编码。 * abi.encodePacked(...) returns (bytes):计算参数的紧密打包编码 * abi. encodeWithSelector(bytes4 selector, ...) returns (bytes): 计算函数选择器和参数的ABI编码 * abi.encodeWithSignature(string signature, ...) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), ...) 通过ABI编码函数可以在不用调用函数的情况下,获得ABI编码值,下面通过一段代码来看看这些方式的使用: 12345678pragma solidity ^0.4.24;contract testABI { function abiEncode() public constant returns (bytes) { abi.encode(1); // 计算 1 的ABI编码 return abi.encodeWithSignature("set(uint256)", 1); //计算函数set(uint256) 及参数1 的ABI 编码 }} 错误处理 assert(bool condition)用于判断内部错误,条件不满足时抛出异常 require(bool condition):用于判断输入或外部组件错误,条件不满足时抛出异常 require(bool condition, string message)同上,多了一个错误信息。 revert():终止执行并还原改变的状态 revert(string reason)同上,提供一个错误信息。 之前老的错误处理方式用throw 及 if … throw,这种方式会消耗掉所有剩余的gas。目前throw 的方式已经被弃用。 数学及加密功能 addmod(uint x, uint y, uint k) returns (uint):计算(x + y) % k,加法支持任意的精度且不会在2**256处溢出,从0.5.0版本开始断言k != 0。 mulmod(uint x, uint y, uint k) returns (uint):计算 (x y) % k, 乘法支持任意的精度且不会在2*256处溢出, 从0.5.0版本开始断言k != 0。 keccak256(…) returns (bytes32):使用以太坊的(Keccak-256)计算HASH值。紧密打包参数。 sha256(…) returns (bytes32):使用SHA-256计算hash值,紧密打包参数。 sha3(…) returns (bytes32):keccak256的别名 ripemd160(…) returns (bytes20):使用RIPEMD-160计算HASH值。紧密打包参数。 ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):通过椭圆曲线签名来恢复与公钥关联的地址,或者在错误时返回零。可用于签名数据的校验,如果返回结果是签名者的公匙地址,那么说明数据是正确的。 ecrecover函数需要四个参数,需要被签名数据的哈希结果值,r,s,v分别来自签名结果串。r = signature[0:64]s = signature[64:128]v = signature[128:130]其中v取出来的值或者是00或01。要使用时,我们先要将其转为整型,再加上27,所以我们将得到27或28。在调用函数时v将填入27或28。 用javascript表达如下:1234567var msg = '0x8CbaC5e4d803bE2A3A5cd3DbE7174504c6DD0c1C'var hash = web3.sha3(msg)var sig = web3.eth.sign(address, h).slice(2)var r = `0x${sig.slice(0, 64)}`var s = `0x${sig.slice(64, 128)}`var v = web3.toDecimal(sig.slice(128, 130)) + 27 订阅区块链技术专栏可以参考到完整的使用例子。 紧密打包参数(tightly packed)意思是说参数不会补位,是直接连接在一起的,下面几个是相等的。 123456keccak256("ab", "c")keccak256("abc")keccak256(0x616263) // hexkeccak256(6382179)keccak256(97, 98, 99) //ascii 如果需要填充,可以使用显式类型转换:keccak256(“\x00\x12”) 与keccak256(uint16(0x12))相同。 注意,常量将使用存储它们所需的最少字节数来打包,例如keccak256(0) == keccak256(uint8(0))和keccak256(0x12345678) == keccak256(uint32(0x12345678)) 在私链(private blockchain)上运行sha256,ripemd160或ecrecover可能会出现Out-Of-Gas报错。因为私链实现了一种预编译合约,合约要在收到第一个消息后才会真正存在(虽然他们的合约代码是硬编码的)。而向一个不存在的合约发送消息,所以才会导致Out-Of-Gas的问题。一种解决办法(workaround)是每个在你真正使用它们之前先发送1 wei到这些合约上来完成初始化。在官方和测试链上没有这个问题。 地址相关 .balance (uint256):Address的余额,以wei为单位。 .transfer(uint256 amount):发送给定数量的ether到某个地址,以wei为单位。失败时抛出异常。 .send(uint256 amount) returns (bool):发送给定数量的ether到某个地址,以wei为单位, 失败时返回false。 .call(…) returns (bool):发起底层的call调用。失败时返回false。 .callcode(…) returns (bool):发起底层的callcode调用,失败时返回false。不鼓励使用,未来可能会移除。 .delegatecall(…) returns (bool):发起底层的delegatecall调用,失败时返回false 更多信息参考地址篇。 警告:send() 执行有一些风险:如果调用栈的深度超过1024或gas耗光,交易都会失败。因此,为了保证安全,必须检查send的返回值,如果交易失败,会回退以太币。如果用transfer会更好。 合约相关 this(当前合约的类型):表示当前合约,可以显式的转换为Address selfdestruct(address recipient):销毁当前合约,并把它所有资金发送到给定的地址。 suicide(address recipient):selfdestruct的别名 另外,当前合约里的所有函数均可支持调用,包括当前函数本身。 参考文档 Special Variables and Functions 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果想与我有更密切的交流可以选择加入我的知识星球(星球成员可加入微信技术交流群)]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何通过以太坊智能合约来进行众筹(ICO)]]></title>
<url>%2F2018%2F02%2F28%2Fico-crowdsale%2F</url>
<content type="text"><![CDATA[前面我们有两遍文章写了如何发行代币,今天我们讲一下如何使用代币来公开募资,即编写一个募资合约。 写在前面本文所讲的代币是使用以太坊智能合约创建,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 众筹先简单说下众筹的概念:一般是这样的,我一个非常好的想法,但是我没有钱来做这事,于是我把这个想法发给大家看,说:我做这件事需要5百万,大家有没有兴趣投些钱,如果大家在30天内投够了5百万我就开始做,到时大家都是原始股东,如果募资额不到5百万,大家投的钱就还给大家。 现在ICO众筹已经被各路大佬拿来割韭菜而被玩坏了(不管有无达标,都把钱卷走)。 其实区块链技术本事非常适合解决众筹的信任问题,借助于智能合约,可以实现当募资额完成时,募资款自动打到指定账户,当募资额未完成时,可退款。这个过程不需要看众筹大佬的人品,不用依靠第三方平台信用担保。 代币传统的众筹在参与之后通常不容易交易(参与之后无法转给其他人),而通过用代币来参与众筹,则很容易进行交易,众筹的参与人可随时进行买卖,待众筹项目实施完成的时候,完全根据代币持有量进行回馈。 举个例子说明下,大家会更容易理解,有这一个众筹:A有技术做一个能监测健康的指环,为此向公众募资200百万,募资时100块对应一个代币,约定在指环上市之后,代币的持有人可以用一个代币来兑换一个指环。而指环的研发周期是一年,因此在指环还未上市的一年里,众筹的参与人可以随时交易所持有的代币。 众筹智能合约代码接下来就看看如何实现一个众筹智能合约。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104pragma solidity ^0.4.16;interface token { function transfer(address receiver, uint amount);}contract Crowdsale { address public beneficiary; // 募资成功后的收款方 uint public fundingGoal; // 募资额度 uint public amountRaised; // 参与数量 uint public deadline; // 募资截止期 uint public price; // token 与以太坊的汇率 , token卖多少钱 token public tokenReward; // 要卖的token mapping(address => uint256) public balanceOf; bool fundingGoalReached = false; // 众筹是否达到目标 bool crowdsaleClosed = false; // 众筹是否结束 /** * 事件可以用来跟踪信息 **/ event GoalReached(address recipient, uint totalAmountRaised); event FundTransfer(address backer, uint amount, bool isContribution); /** * 构造函数, 设置相关属性 */ function Crowdsale( address ifSuccessfulSendTo, uint fundingGoalInEthers, uint durationInMinutes, uint finneyCostOfEachToken, address addressOfTokenUsedAsReward) { beneficiary = ifSuccessfulSendTo; fundingGoal = fundingGoalInEthers * 1 ether; deadline = now + durationInMinutes * 1 minutes; price = finneyCostOfEachToken * 1 finney; tokenReward = token(addressOfTokenUsedAsReward); // 传入已发布的 token 合约的地址来创建实例 } /** * 无函数名的Fallback函数, * 在向合约转账时,这个函数会被调用 */ function () payable { require(!crowdsaleClosed); uint amount = msg.value; balanceOf[msg.sender] += amount; amountRaised += amount; tokenReward.transfer(msg.sender, amount / price); FundTransfer(msg.sender, amount, true); } /** * 定义函数修改器modifier(作用和Python的装饰器很相似) * 用于在函数执行前检查某种前置条件(判断通过之后才会继续执行该方法) * _ 表示继续执行之后的代码 **/ modifier afterDeadline() { if (now >= deadline) _; } /** * 判断众筹是否完成融资目标, 这个方法使用了afterDeadline函数修改器 * */ function checkGoalReached() afterDeadline { if (amountRaised >= fundingGoal) { fundingGoalReached = true; GoalReached(beneficiary, amountRaised); } crowdsaleClosed = true; } /** * 完成融资目标时,融资款发送到收款方 * 未完成融资目标时,执行退款 * */ function safeWithdrawal() afterDeadline { if (!fundingGoalReached) { uint amount = balanceOf[msg.sender]; balanceOf[msg.sender] = 0; if (amount > 0) { if (msg.sender.send(amount)) { FundTransfer(msg.sender, amount, false); } else { balanceOf[msg.sender] = amount; } } } if (fundingGoalReached && beneficiary == msg.sender) { if (beneficiary.send(amountRaised)) { FundTransfer(beneficiary, amountRaised, false); } else { //If we fail to send the funds to beneficiary, unlock funders balance fundingGoalReached = false; } } }} 部署及说明在部署这个合约之前,我们需要先部署一个代币合约,请参考一步步教你创建自己的数字货币。 创建众筹合约我们需要提供一下几个参数:ifSuccessfulSendTo: 募资成功后的收款方(其实这里可以默认为合约创建者)fundingGoalInEthers: 募资额度, 为了方便我们仅募3个etherdurationInMinutes: 募资时间finneyCostOfEachToken 每个代币的价格, 这里为了方便使用了单位finney及值为:1 (1 ether = 1000 finney)addressOfTokenUsedAsReward: 代币合约地址。如:本文使用的参数为: 1"0xc6f9ea59d424733e8e1902c7837ea75e20abfb49",3, 100, 1,"0xad8972e2b583f580fc52f737b98327eb65d08f8c" 参与人投资的时候实际购买众筹合约代币,所以需要先向合约预存代币,代币的数量为:募资额度 / 代币的价格 , 这里为:3 * 1000/1 = 3000 (当能也可以大于3000)。向合约预存代币可以使用myetherwallet钱包,或在remix中重新加载代币合约,执行代币合约tranfer()函数进行代币转账,转账的地址就是我们创建合约的地址。如使用myetherwallet转账如图: 投资人向众筹合约转账(发送以太币)即是参与众筹行为,转账时,会执行Fallback回退函数(即无名函数)向其账户打回相应的代币。 safeWithdrawl() 可以被参与人或收益人调用,如果融资不达标参与人可收回之前投资款,如果融资达标收益人可以拿到所有的融资款。 扩展上面是一个很正规的募资合约。接下来讲两个募资合约的扩展,如何实现无限募资合约及割韭菜合约。这部分内容独家发布在我的小专栏区块链技术 如何创建代币发行代币,现在也录制了对应的视频教程:通过代币学以太坊智能合约开发,目前我们也在招募体验师,可以点击链接了解。 如果你在学习中遇到问题,欢迎到我的知识星球提问,作为星球成员福利,成员可加入区块链技术付费交流群。 参考文档 Create a crowdsale 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Token</tag>
<tag>ICO</tag>
</tags>
</entry>
<entry>
<title><![CDATA[什么是拜占庭将军问题]]></title>
<url>%2F2018%2F02%2F05%2Fbitcoin-byzantine%2F</url>
<content type="text"><![CDATA[接触区块链的同学,多少都听说过拜占庭将军问题,经常看到或听到某某区块链使用某某算法解决了拜占庭将军问题,那么究竟什么是拜占庭将军问题呢? 什么是拜占庭将军问题也被称为“拜占庭容错”、“拜占庭将军问题”。拜占庭将军问题是Leslie Lamport(2013年的图灵讲得主)用来为描述分布式系统一致性问题(Distributed Consensus)在论文中抽象出来一个著名的例子。 这个例子大意是这样的: 拜占庭帝国想要进攻一个强大的敌人,为此派出了10支军队去包围这个敌人。这个敌人虽不比拜占庭帝国,但也足以抵御5支常规拜占庭军队的同时袭击。这10支军队在分开的包围状态下同时攻击。他们任一支军队单独进攻都毫无胜算,除非有至少6支军队(一半以上)同时袭击才能攻下敌国。他们分散在敌国的四周,依靠通信兵骑马相互通信来协商进攻意向及进攻时间。困扰这些将军的问题是,他们不确定他们中是否有叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们才能保证有多于6支军队在同一时间一起发起进攻,从而赢取战斗? 拜占庭将军问题中并不去考虑通信兵是否会被截获或无法传达信息等问题,即消息传递的信道绝无问题。Lamport已经证明了在消息可能丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。所以,在研究拜占庭将军问题的时候,已经假定了信道是没有问题的. 问题分析单从上面的说明可能无法理解这个问题的复杂性,我们来简单分析一下: 先看在没有叛徒情况下,假如一个将军A提一个进攻提议(如:明日下午1点进攻,你愿意加入吗?)由通信兵通信分别告诉其他的将军,如果幸运中的幸运,他收到了其他6位将军以上的同意,发起进攻。如果不幸,其他的将军也在此时发出不同的进攻提议(如:明日下午2点、3点进攻,你愿意加入吗?),由于时间上的差异,不同的将军收到(并认可)的进攻提议可能是不一样的,这是可能出现A提议有3个支持者,B提议有4个支持者,C提议有2个支持者等等。 再加一点复杂性,在有叛徒情况下,一个叛徒会向不同的将军发出不同的进攻提议(通知A明日下午1点进攻, 通知B明日下午2点进攻等等),一个叛徒也会可能同意多个进攻提议(即同意下午1点进攻又同意下午2点进攻)。 叛徒发送前后不一致的进攻提议,被称为“拜占庭错误”,而能够处理拜占庭错误的这种容错性称为「Byzantine fault tolerance」,简称为BFT。 相信大家已经可以明白这个问题的复杂性了。 中本聪的解决方案在出现比特币之前,解决分布式系统一致性问题主要是Lamport提出的Paxos算法或其衍生算法。Paxos类算法仅适用于中心化的分布式系统,这样的系统的没有不诚实的节点(不会发送虚假错误消息,但允许出现网络不通或宕机出现的消息延迟)。 中本聪在比特币中创造性的引入了“工作量证明(POW : Proof of Work)”来解决这个问题,有兴趣可进一步阅读工作量证明。通过工作量证明就增加了发送信息的成本,降低节点发送消息速率,这样就以保证在一个时间只有一个节点(或是很少)在进行广播,同时在广播时会附上自己的签名。这个过程就像一位将军A在向其他的将军(B、C、D…)发起一个进攻提议一样,将军B、C、D…看到将军A签过名的进攻提议书,如果是诚实的将军就会立刻同意进攻提议,而不会发起自己新的进攻提议。 以上就是比特币网络中是单个区块(账本)达成共识的方法(取得一致性)。 理解了单个区块取得一致性的方法,那么整个区块链(总账本)如果达成一致也好理解。我们稍微把将军问题改一下:假设攻下一个城堡需要多次的进攻,每次进攻的提议必须基于之前最多次数的胜利进攻下提出的(只有这样敌方已有损失最大,我方进攻胜利的可能性就更大),这样约定之后,将军A在收到进攻提议时,就会检查一下这个提议是不是基于最多的胜利提出的,如果不是(基于最多的胜利)将军A就不会同意这样的提议,如果是的,将军A就会把这次提议记下来。 这就是比特币网络最长链选择。 经济学分析工作量证明其实相当于提高了做叛徒(发布虚假区块)的成本,在工作量证明下,只有第一个完成证明的节点才能广播区块,竞争难度非常大,需要很高的算力,如果不成功其算力就白白的耗费了(算力是需要成本的),如果有这样的算力作为诚实的节点,同样也可以获得很大的收益(这就是矿工所作的工作),这也实际就不会有做叛徒的动机,整个系统也因此而更稳定。 很多人批评工作量证明造成巨大的电力浪费,促使人们去探索新的解决一致性(共识)问题的机制:权益证明机制(POS: Proof of Stake)是一个代表。在拜占庭将军问题的角度来看,它同样提高了做叛徒的成本,因为账户需要首先持有大量余额才能有更多的几率广播区块,POS不是本文重点,以后在讲。 共识算法的核心就是解决拜占庭将军问题(分布式网络一致性问题)。 扩展阅读The Byzantine Generals Problem 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果想与我有更密切的交流可以选择加入我的知识星球(星球成员可加入微信技术交流群)]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>共识协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列7 - 以太单位及时间单位]]></title>
<url>%2F2018%2F02%2F02%2Fsolidity-unit%2F</url>
<content type="text"><![CDATA[这是Solidity教程系列文章第7篇介绍以太单位及时间单位,系列带你全面深入理解Solidity语言。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 欢迎订阅区块链技术专栏阅读更全面的分析文章。 货币单位(Ether Units)一个数字常量(字面量)后面跟随一个后缀wei, finney,szabo或ether,这个后缀就是货币单位。不同的单位可以转换。不含任何后缀的默认单位是wei。不同的以太币单位转换关系如下: 1 ether == 10^3 finney == 1000 finney 1 ether == 10^6 szabo 1 ether == 10^18 wei 插曲:以太币单位其实是密码学家的名字,是以太坊创始人为了纪念他们在数字货币的领域的贡献。他们分别是:wei: Wei Dai 戴伟 密码学家 ,发表 B-moneyfinney: Hal Finney 芬尼 密码学家、工作量证明机制(POW)提出szabo: Nick Szabo 尼克萨博 密码学家、智能合约的提出者 我们可以使用一下代码验证一个转换关系:123456789101112131415161718192021222324pragma solidity ^0.4.16;contract testUnit { function tf() public pure returns (bool) { if (1 ether == 1000 finney){ return true; } return false; } function ts() public pure returns (bool) { if (1 ether == 1000000 szabo){ return true; } return false; } function tgw() public pure returns (bool) { if (1 ether == 1000000000000000000 wei){ return true; } return false; }} 时间单位(Time Units)时间单位: seconds, minutes, hours, days, weeks, years均可做为后缀,并进行相互转换,规则如下: 1 == 1 seconds (默认是seconds为单位) 1 minutes == 60 seconds 1 hours == 60 minutes 1 days == 24 hours 1 weeks = 7 days 1 years = 365 days 使用这些单位进行日期计算需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的预言(oracle)来更新从而得到一个精确的日历库。 这些后缀不能用于变量。如果想对输入的变量说明其不同的单位,可以使用下面的方式:1234567891011121314pragma solidity ^0.4.16;contract testTUnit { function currTimeInSeconds() public pure returns (uint256){ return now; } function f(uint start, uint daysAfter) public { if (now >= start + daysAfter * 1 days) { // ... } }} 参考文档 units 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。如果想与我有更密切的交流可以选择加入我的知识星球(星球成员可加入微信技术交流群)]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[实现一个可管理、增发、兑换、冻结等高级功能的代币]]></title>
<url>%2F2018%2F01%2F27%2Fcreate-token2%2F</url>
<content type="text"><![CDATA[本文主要介绍代币高级功能的实现: 代币管理、代币增发、代币兑换、资产冻结、Gas自动补充。 写在前面在上一篇:一步步教你创建自己的数字货币(代币)进行ICO中我们实现一个最基本功能的代币,本文将在上一遍文章的基础上,讲解如果添加更多的高级功能。 实现代币的管理者虽然区块链是去中心化的,但是实现对代币(合约)的管理,也在许多应用中有需求,为了对代币进行管理,首先需要给合约添加一个管理者。 我们来看看如果实现,先创建一个owned合约。 1234567891011121314151617contract owned { address public owner; function owned() { owner = msg.sender; } modifier onlyOwner { require(msg.sender == owner); _; } // 实现所有权转移 function transferOwnership(address newOwner) onlyOwner { owner = newOwner; }} 这个合约重要的是加入了一个函数修改器(Function Modifiers)onlyOwner,函数修改器是一个合约属性,可以被继承,还能被重写。它用于在函数执行前检查某种前置条件。关于函数修改器可进一步阅读Solidity 教程系列10 - 完全理解函数修改器 如果熟悉Python的同学,会发现函数修改器的作用和Python的装饰器很相似。 然后让代币合约继承owned以拥有onlyOwner修改器,代码如下:1234567891011contract MyToken is owned { function MyToken( uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol, address centralMinter ) { if(centralMinter != 0 ) owner = centralMinter; }} 代币增发实现代币增发,代币增发就如同央行印钞票一样,想必很多人都需要这样的功能。 给合约添加以下的方法:123456function mintToken(address target, uint256 mintedAmount) onlyOwner { balanceOf[target] += mintedAmount; totalSupply += mintedAmount; Transfer(0, owner, mintedAmount); Transfer(owner, target, mintedAmount); } 注意onlyOwner修改器添加在函数末尾,这表示只有ower才能调用这用函数。他的功能很简单,就是给指定的账户增加代币,同时增加总供应量。 资产冻结有时为了监管的需要,需要实现冻结某些账户,冻结后,其资产仍在账户,但是不允许交易,之道解除冻结。给合约添加以下的变量和方法(可以添加到合约的任何地方,但是建议把mapping加到和其他mapping一起,event也是如此):1234567mapping (address => bool) public frozenAccount;event FrozenFunds(address target, bool frozen);function freezeAccount(address target, bool freeze) onlyOwner { frozenAccount[target] = freeze; FrozenFunds(target, freeze);} 单单以上的代码还无法冻结,需要把他加入到transfer函数中才能真正生效,因此修改transfer函数1234function transfer(address _to, uint256 _value) { require(!frozenAccount[msg.sender]); ...} 这样在转账前,对发起交易的账号做一次检查,只有不是被冻结的账号才能转账。 代币买卖(兑换)可以自己的货币中实现代币与其他数字货币(ether 或其他tokens)的兑换机制。有了这个功能,我们的合约就可以在一买一卖中赚利润了。 先来设置下买卖价格1234567uint256 public sellPrice;uint256 public buyPrice;function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner { sellPrice = newSellPrice; buyPrice = newBuyPrice;} setPrices()添加了onlyOwner修改器,注意买卖的价格单位是wei(最小的货币单位: 1 eth = 1000000000000000000 wei) 添加来添加买卖函数:123456789101112131415161718function buy() payable returns (uint amount){ amount = msg.value / buyPrice; // calculates the amount require(balanceOf[this] >= amount); // checks if it has enough to sell balanceOf[msg.sender] += amount; // adds the amount to buyer's balance balanceOf[this] -= amount; // subtracts amount from seller's balance Transfer(this, msg.sender, amount); // execute an event reflecting the change return amount; // ends function and returns}function sell(uint amount) returns (uint revenue){ require(balanceOf[msg.sender] >= amount); // checks if the sender has enough to sell balanceOf[this] += amount; // adds the amount to owner's balance balanceOf[msg.sender] -= amount; // subtracts the amount from seller's balance revenue = amount * sellPrice; msg.sender.transfer(revenue); // sends ether to the seller: it's important to do this last to prevent recursion attacks Transfer(msg.sender, this, amount); // executes an event reflecting on the change return revenue; // ends function and returns} 加入了买卖功能后,要求我们在创建合约时发送足够的以太币,以便合约有能力回购市面上的代币,否则合约将破产,用户没法先合约卖代币。 实现Gas的自动补充以太坊中的交易时需要gas(支付给矿工的费用,费用以ether来支付)。而如果用户没有以太币,只有代币的情况(或者我们想向用户隐藏以太坊的细节),就需要自动补充gas的功能。这个功能将使我们代币更加好用。 自动补充的逻辑是这样了,在执行交易之前,我们判断用户的余额(用来支付矿工的费用),如果用户的余额非常少(低于某个阈值时)可能影响到交易进行,合约自动售出一部分代币来补充余额,以帮助用户顺利完成交易。 先来设定余额阈值:12345uint minBalanceForAccounts; function setMinBalance(uint minimumBalanceInFinney) onlyOwner { minBalanceForAccounts = minimumBalanceInFinney * 1 finney; } finney 是货币单位 1 finney = 0.001eth然后交易中加入对用户的余额的判断。1234567function transfer(address _to, uint256 _value) { ... if(msg.sender.balance < minBalanceForAccounts) sell((minBalanceForAccounts - msg.sender.balance) / sellPrice); if(_to.balance<minBalanceForAccounts) // 可选,让接受者也补充余额,以便接受者使用代币。 _to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));} 代码部署高级功能完整代码请前往我的小专栏, 项目的完整的部署方法参考上一篇,不同的是创建合约时需要预存余额,如图: 专栏已经有多篇文章介绍Remix Solidity IDE的使用,这里就不一一截图演示了,请大家自己测试验证。 如何创建代币发行代币,现在也录制了对应的视频教程:通过代币学以太坊智能合约开发,现在我们在招募体验师,可以点击链接了解详情。 如果你在创建代币的过程中遇到问题,欢迎到我的知识星球提问,作为星球成员福利,成员可加入区块链技术付费交流群。 参考文档 Create your own crypto-currency with ethereum 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Token</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一步步教你创建自己的数字货币(代币)进行ICO]]></title>
<url>%2F2018%2F01%2F12%2Fcreate_token%2F</url>
<content type="text"><![CDATA[本文从技术角度详细介绍如何基于以太坊ERC20创建代币的流程. 写在前面本文所讲的代币是使用以太坊智能合约创建,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 代币Token如果不那么追求精确的定义,代币就是数字货币,比特币、以太币就是一个代币。利用以太坊的智能合约可以轻松编写出属于自己的代币,代币可以代表任何可以交易的东西,如:积分、财产、证书等等。因此不管是出于商业,还是学习很多人想创建一个自己的代币,先贴一个图看看创建的代币是什么样子。 今天我们就来详细讲一讲怎样创建一个这样的代币。 ERC20 Token也许你经常看到ERC20和代币一同出现, ERC20是以太坊定义的一个代币标准。要求我们在实现代币的时候必须要遵守的协议,如指定代币名称、总量、实现代币交易函数等,只有支持了协议才能被以太坊钱包支持。其接口如下: 12345678910111213141516contract ERC20Interface { string public constant name = "Token Name"; string public constant symbol = "SYM"; uint8 public constant decimals = 18; // 18 is the most common number of decimal places function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens);} 简单说明一下:name : 代币名称symbol: 代币符号decimals: 代币小数点位数,代币的最小单位, 18表示我们可以拥有 .0000000000000000001单位个代币。totalSupply() : 发行代币总量。balanceOf(): 查看对应账号的代币余额。transfer(): 实现代币交易,用于给用户发送代币(从我们的账户里)。transferFrom(): 实现代币用户之间的交易。allowance(): 控制代币的交易,如可交易账号及资产。approve(): 允许用户可花费的代币数。 编写代币合约代码代币合约代码: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980pragma solidity ^0.4.16;interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }contract TokenERC20 { string public name; string public symbol; uint8 public decimals = 18; // 18 是建议的默认值 uint256 public totalSupply; mapping (address => uint256) public balanceOf; // mapping (address => mapping (address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Burn(address indexed from, uint256 value); function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public { totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; name = tokenName; symbol = tokenSymbol; } function _transfer(address _from, address _to, uint _value) internal { require(_to != 0x0); require(balanceOf[_from] >= _value); require(balanceOf[_to] + _value > balanceOf[_to]); uint previousBalances = balanceOf[_from] + balanceOf[_to]; balanceOf[_from] -= _value; balanceOf[_to] += _value; Transfer(_from, _to, _value); assert(balanceOf[_from] + balanceOf[_to] == previousBalances); } function transfer(address _to, uint256 _value) public { _transfer(msg.sender, _to, _value); } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; return true; } function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } function burn(uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; totalSupply -= _value; Burn(msg.sender, _value); return true; } function burnFrom(address _from, uint256 _value) public returns (bool success) { require(balanceOf[_from] >= _value); require(_value <= allowance[_from][msg.sender]); balanceOf[_from] -= _value; allowance[_from][msg.sender] -= _value; totalSupply -= _value; Burn(_from, _value); return true; }} 代码的详细解读,请订阅我的小专栏。 部署在开发测试智能合约时,MetaMask和Remix Solidity IDE是两个非常好用的工具,今天就用他们来完成部署。 安装和配置MetaMask请参考开发、部署第一个去中心化应用,不同的上本文选择了以太坊的测试网络Ropsten,如果你没有余额请点击购买buy,进入的网站可以送一些测试以太币给你,配置好之后,界面应该如下: 浏览器打开Remix Solidity IDE,复制以上源码粘贴上,在右侧选项参考如图的设置:注意Environment和Account和MetaMask保持一致,然后选择合约TokenERC20,填入你想要的发行量,名称及代号,就可以创建合约了。这时MetaMask会弹出一个交易确认框,点SUBMIT。待合约部署交易确认之后,复制合约地址。 打开Metamask界面,切换到TOKENS,点添加合约,出现如下对话框:填入刚刚复制的地址,点ADD,这时你就可以看到你创建的代币了,如图: 哈哈,你已经完成了代币的创建和部署(正式网络和测试网络部署方法一样),可以在Etherscan查询到我们刚刚部署的代币。可以用它进行ICO了,从此走上人生巅峰(玩笑话,不鼓励大家发行无意义的代币)。 代币交易由于MetaMask插件没有提供代币交易功能,同时考虑到很多人并没有以太坊钱包或是被以太坊钱包网络同步问题折磨,今天我用网页钱包来讲解代币交易。 进入网页钱包地址, 第一次进入有一些安全提示需要用户确认。 进入之后,按照下图进行设置: 连接上之后,如图需要添加代币,填入代币合约地址。 进行代币转账交易在接下来的交易确认也,点击确认即可。 交易完成后,可以看到MetaMask中代币余额减少了,如图: 代币交易是不是很简单,只要明白了交易流程,使用其他的钱包也是一样的道理。 如何创建代币发行代币,现在也录制了对应的视频教程:通过代币学以太坊智能合约开发,现在我们在招募体验师可以点击链接了解。如果你在创建代币的过程中遇到问题,我的知识星球可为大家解答问题,作为星球成员福利,成员还可加入区块链技术付费交流群。 参考文档 代币标准 Create your own crypto-currency with ethereum 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>智能合约</category>
</categories>
<tags>
<tag>智能合约</tag>
<tag>Token</tag>
</tags>
</entry>
<entry>
<title><![CDATA[一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店]]></title>
<url>%2F2018%2F01%2F12%2Ffirst-dapp%2F</url>
<content type="text"><![CDATA[今天我们来编写一个完整的去中心化(区块链)应用(Dapps), 本文可以和编写智能合约结合起来看。 写在前面阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之外,你最好还了解一些HTML及JavaScript知识。 本文通过实例教大家来开发去中心化应用,应用效果如图: 从本文,你可以学习到: 搭建智能合约开发环境 创建Truffle项目 编写智能合约 编译和部署智能合约到区块链 如何通过Web3和智能合约交互 MetaMask 的使用 小专栏用户在教程结尾处可以下载完整的Dapp代码。 项目背景Pete有一个宠物店,有16只宠物,他想开发一个去中心化应用,让大家来领养宠物。在truffle box中,已经提供了pet-shop的网站部分的代码,我们只需要编写合约及交互部分。 环境搭建 安装Node 安装 Truffle :npm install -g truffle 安装Ganache Ganache(或Ganache CLI)已经取代了 testrpc。 创建项目 建立项目目录并进入 12> mkdir pet-shop-tutorial> cd pet-shop-tutorial 使用truffle unbox 创建项目 123456789101112 > truffle unbox pet-shop Downloading... Unpacking... Setting up... Unbox successful. Sweet!Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test Run dev server: npm run dev 这一步需要等待一会 也可以使用truffle init 来创建一个全新的项目。 项目目录结构contracts/ 智能合约的文件夹,所有的智能合约文件都放置在这里,里面包含一个重要的合约Migrations.sol(稍后再讲)migrations/ 用来处理部署(迁移)智能合约 ,迁移是一个额外特别的合约用来保存合约的变化。test/ 智能合约测试用例文件夹truffle.js/ 配置文件 其他代码可以暂时不用管 编写智能合约智能合约承担着分布式应用的后台逻辑和存储。智能合约使用solidity编写,可阅读solidity系列文章 在contracts目录下,添加合约文件Adoption.sol1234567891011121314151617181920pragma solidity ^0.4.17;contract Adoption { address[16] public adopters; // 保存领养者的地址 // 领养宠物 function adopt(uint petId) public returns (uint) { require(petId >= 0 && petId <= 15); // 确保id在数组长度内 adopters[petId] = msg.sender; // 保存调用这地址 return petId; } // 返回领养者 function getAdopters() public view returns (address[16]) { return adopters; }} 编译部署智能合约Truffle集成了一个开发者控制台,可用来生成一个开发链用来测试和部署智能合约。 编译Solidity是编译型语言,需要把可读的Solidity代码编译为EVM字节码才能运行。dapp的根目录pet-shop-tutorial下,1> truffle compile 输出12Compiling ./contracts/Adoption.sol...Writing artifacts to ./build/contracts 部署编译之后,就可以部署到区块链上。在migrations文件夹下已经有一个1_initial_migration.js部署脚本,用来部署Migrations.sol合约。Migrations.sol 用来确保不会部署相同的合约。 现在我们来创建一个自己的部署脚本2_deploy_contracts.js 12345var Adoption = artifacts.require("Adoption");module.exports = function(deployer) { deployer.deploy(Adoption);}; 在执行部署之前,需要确保有一个区块链运行, 可以使用Ganache来开启一个私链来进行开发测试,默认会在7545端口上运行一个开发链。Ganache 启动之后是这样: 接下来执行部署命令:1> truffle migrate 执行后,有一下类似的输出,12345678910111213141516Using network 'develop'.Running migration: 1_initial_migration.js Deploying Migrations... ... 0x3076b7dac65afc44ec51508bf6f2b6894f833f0f9560ecad2d6d41ed98a4679f Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0Saving successful migration to network... ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956Saving artifacts...Running migration: 2_deploy_contracts.js Deploying Adoption... ... 0x2c6ab4471c225b5473f2079ee42ca1356007e51d5bb57eb80bfeb406acc35cd4 Adoption: 0x345ca3e014aaf5dca488057592ee47305d9b3e10Saving successful migration to network... ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0Saving artifacts... 在打开的Ganache里可以看到区块链状态的变化,现在产生了4个区块。这时说明已经智能合约已经部署好了。 测试现在我们来测试一下智能合约,测试用例可以用 JavaScript or Solidity来编写,这里使用Solidity。 在test目录下新建一个TestAdoption.sol,编写测试合约12345678910111213141516171819202122232425262728293031323334pragma solidity ^0.4.17;import "truffle/Assert.sol"; // 引入的断言import "truffle/DeployedAddresses.sol"; // 用来获取被测试合约的地址import "../contracts/Adoption.sol"; // 被测试合约contract TestAdoption { Adoption adoption = Adoption(DeployedAddresses.Adoption()); // 领养测试用例 function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(8); uint expected = 8; Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded."); } // 宠物所有者测试用例 function testGetAdopterAddressByPetId() public { // 期望领养者的地址就是本合约地址,因为交易是由测试合约发起交易, address expected = this; address adopter = adoption.adopters(8); Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded."); } // 测试所有领养者 function testGetAdopterAddressByPetIdInArray() public { // 领养者的地址就是本合约地址 address expected = this; address[16] memory adopters = adoption.getAdopters(); Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded."); }} Assert.sol 及 DeployedAddresses.sol是Truffle框架提供,在test目录下并不提供truffle目录。 TestAdoption合约中添加adopt的测试用例 运行测试用例在终端中,执行1truffle test 如果测试通过,则终端输出:123456789101112131415Using network 'develop'.Compiling ./contracts/Adoption.sol...Compiling ./test/TestAdoption.sol...Compiling truffle/Assert.sol...Compiling truffle/DeployedAddresses.sol... TestAdoption ✓ testUserCanAdoptPet (62ms) ✓ testGetAdopterAddressByPetId (53ms) ✓ testGetAdopterAddressByPetIdInArray (73ms) 3 passing (554ms) 创建用户接口和智能合约交互我们已经编写和部署及测试好了我们的合约,接下我们为合约编写UI,让合约真正可以用起来。 在Truffle Box pet-shop里,已经包含了应用的前端代码,代码在src/文件夹下。 在编辑器中打开src/js/app.js可以看到用来管理整个应用的App对象,init函数加载宠物信息,就初始化web3.web3是一个实现了与以太坊节点通信的库,我们利用web3来和合约进行交互。 初始化web3接下来,我们来编辑app.js修改initWeb3():删除注释,修改为:123456789101112initWeb3: function() { // Is there an injected web3 instance? if (typeof web3 !== 'undefined') { App.web3Provider = web3.currentProvider; } else { // If no injected web3 instance is detected, fall back to Ganache App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545'); } web3 = new Web3(App.web3Provider); return App.initContract();} 代码中优先使用Mist 或 MetaMask提供的web3实例,如果没有则从本地环境创建一个。 实例化合约使用truffle-contract会帮我们保存合约部署的信息,就不需要我们手动修改合约地址,修改initContract()代码如下:123456789101112131415initContract: function() { // 加载Adoption.json,保存了Adoption的ABI(接口说明)信息及部署后的网络(地址)信息,它在编译合约的时候生成ABI,在部署的时候追加网络信息 $.getJSON('Adoption.json', function(data) { // 用Adoption.json数据创建一个可交互的TruffleContract合约实例。 var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact); // Set the provider for our contract App.contracts.Adoption.setProvider(App.web3Provider); // Use our contract to retrieve and mark the adopted pets return App.markAdopted(); }); return App.bindEvents();} 处理领养修改markAdopted()代码:123456789101112131415161718markAdopted: function(adopters, account) { var adoptionInstance; App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; // 调用合约的getAdopters(), 用call读取信息不用消耗gas return adoptionInstance.getAdopters.call(); }).then(function(adopters) { for (i = 0; i < adopters.length; i++) { if (adopters[i] !== '0x0000000000000000000000000000000000000000') { $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true); } } }).catch(function(err) { console.log(err.message); });} 修改handleAdopt()代码:123456789101112131415161718192021222324252627handleAdopt: function(event) { event.preventDefault(); var petId = parseInt($(event.target).data('id')); var adoptionInstance; // 获取用户账号 web3.eth.getAccounts(function(error, accounts) { if (error) { console.log(error); } var account = accounts[0]; App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; // 发送交易领养宠物 return adoptionInstance.adopt(petId, {from: account}); }).then(function(result) { return App.markAdopted(); }).catch(function(err) { console.log(err.message); }); });} 在浏览器中运行安装 MetaMaskMetaMask 是一款插件形式的以太坊轻客户端,开发过程中使用MetaMask和我们的dapp进行交互是个很好的选择,通过此链接安装,安装完成后,浏览器工具条会显示一个小狐狸图标。 配置钱包在接受隐私说明后,会出现页面如下: 这里我们通过还原一个Ganache为我们创建好的钱包,作为我们的开发测试钱包。点击页面的 Import Existing DEN,输入Ganache显示的助记词。1candy maple cake sugar pudding cream honey rich smooth crumble sweet treat 然后自己想要的密码,点击OK。如图: 连接开发区块链网络默认连接的是以太坊主网(左上角显示),选择Custom RPC,添加一个网络:http://127.0.0.1:7545,点返回后,显示如下:这是左上角显示为Private Network,账号是Ganache中默认的第一个账号。 至此MetaMask的安装,配置已经完成。 安装和配置lite-server接下来需要本地的web 服务器提供服务的访问, Truffle Box pet-shop里提供了一个lite-server可以直接使用,我们看看它是如何工作的。bs-config.json指示了lite-server的工作目录。12345{ "server": { "baseDir": ["./src", "./build/contracts"] }} ./src 是网站文件目录./build/contracts 是合约输出目录 以此同时,在package.json文件的scripts中添加了dev命令:1234"scripts": { "dev": "lite-server", "test": "echo \"Error: no test specified\" && exit 1"}, 当运行npm run dev的时候,就会启动lite-server 启动服务1> npm run dev 会自动打开浏览器显示我们的dapp,如本文的第一张图。现在领养一直宠物看看,当我们点击Adopt时,MetaMask会提示我们交易的确认,如图: 点击Submit确认后,就可以看到成功领养了这次宠物。 在MetaMask中,也可以看到交易的清单: 好了,恭喜你,即将成为一名去中心化式应用开发者的你已经成为迈出了坚实的一步。如果学习中遇到问题,欢迎来我的知识星球交流。 想全面学习去中心化应用的同学可以学习我们的视频课程区块链全栈-以太坊DAPP开发实战。 参考文档 Truffle手册 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>以太坊</category>
<category>Dapp</category>
</categories>
<tags>
<tag>Dapp入门</tag>
<tag>以太坊概念</tag>
</tags>
</entry>
<entry>
<title><![CDATA[区块链技术学习指引]]></title>
<url>%2F2018%2F01%2F11%2Fguide%2F</url>
<content type="text"><![CDATA[本文为博客文章索引,小白必看。有新文章时会更新本文,建议大家加入收藏夹中,如果你觉得本站不错,欢迎你转发给朋友。 引言给迷失在如何学习区块链技术的同学一个指引,区块链技术是随比特币诞生,因此要搞明白区块链技术,应该先了解下比特币。但区块链技术不单应用于比特币,还有非常多的现实应用场景,想做区块链应用开发,可进一步阅读以太坊系列。 比特币如果你是还不知比特币是什么,那就看看比特币是什么 基础入门接下来可以通过下面这几篇文章了解比特币大概的运行原理: 区块链记账原理 通过这篇可以了解到区块链是一个怎样的结构 比特币所有权及隐私问题 通过这篇可以了解到地址私钥 非对称加密应用 等概念 比特币如何挖矿 通过这篇了解工作量证明 比特币如何达成共识 - 最长链的选择 通过这篇可以了解共识机制。 补充阅读 什么是拜占庭将军问题 进阶在基础入门之后,可以进一步阅读以下几篇,理解分布式网络,交易验证。 分析比特币网络:一种去中心化、点对点的网络架构 比特币区块结构 Merkle 树及简单支付验证分析 比特币脚本及交易分析 - 智能合约雏形 看完上面这些,区块链应该理解差不多了,就可以尝试实现一个简单的区块链了。参考这篇用Python从零开始创建区块链。 以太坊一个技术要落地还得靠应用, 以太坊就这样一个建立在区块链技术之上,去中心化的应用平台。可以阅读几下几篇,这部分以开发为主,需要大家多发时间实践。 以太坊开发入门 智能合约开发环境搭建及Hello World合约 搭建智能合约开发环境Remix IDE及使用 以太坊客户端Geth命令用法-参数详解 Geth控制台使用实战及Web3.js使用 如何搭建以太坊私有链 智能合约及应用开发 程序员如何切入区块链去中心化应用开发 一步步教你开发、部署第一个Dapp应用 一步步教你创建自己的数字货币(代币)进行ICO 实现一个可管理、增发、兑换、冻结等高级功能的代币 如何通过以太坊智能合约来进行众筹(ICO) 剖析非同质化代币ERC721–全面解析ERC721标准 Web3与智能合约交互实战 Web3监听合约事件 如何编写一个可升级的智能合约 美链BEC合约漏洞技术分析 钱包开发系列 理解开发HD 钱包涉及的 BIP32、BIP44、BIP39 以太坊钱包开发系列1 - 创建钱包账号 以太坊钱包开发系列1 - 账号Keystore文件导入导出 以太坊钱包开发系列2 - 展示钱包信息及发起签名交易 以太坊钱包开发系列3 - 发送Token(代币) 以太扩容 深入理解Plasma(一)Plasma 框架 深入理解Plasma(二)Plasma 细节 深入理解Plasma(三)Plasma MVP 深入理解Plasma(四)Plasma Cash Solidity语言教程Solidity语言是开发智能合约最广泛的语言,本专栏应该是国内最深度介绍Solidity的文章了。当然还有一个选择是购买我的新书:精通以太坊智能合约 Solidity 教程系列1 - 类型介绍 Solidity 教程系列2 - 地址类型介绍 Solidity 教程系列3 - 函数类型介绍 Solidity 教程系列4 - 数据存储位置分析 Solidity 教程系列5 - 数组介绍 Solidity 教程系列6 - 结构体与映射 Solidity 教程系列7 - 以太单位及时间单位 Solidity 教程系列8 - Solidity API Solidity 教程系列9 - 错误处理 Solidity 教程系列10 - 完全理解函数修改器 Solidity 教程系列11 - 视图函数、虚函数讲解 Solidity 教程系列12 - 库的使用 Solidity 教程系列13 - 函数调用 智能合约最佳实践 之 Solidity 编码规范 如何理解以太坊ABI - 应用程序二进制接口 柚子EOS 什么是EOS 更多精彩内容想要系统学习以太坊智能合约,可以购买的新书:精通以太坊智能合约 首先强烈建议大家订阅深入浅出区块链技术小专栏,目前仅需69元(时不时会进行涨价哦), 部分源码和进阶内容仅在小专栏开放,订阅小专栏还有其他惊喜哦~。 我们推出了高质量的视频课程,视频课程要比文章内容更丰富,也更容易理解。欢迎关注登链学院公众号: edupchain,同时招募课程体验师还有少量名额,了解课程及体验师详情 如果在学习过程中遇到问题,欢迎来国内最专业的区块链问答及交流社区:知识星球《深入浅出区块链》,提问不限于博客文章内容。同时星友还有一个专属的微信技术交流群,目前微信群已经聚集了300多位区块链技术牛人和爱好者,形成为非常好的积极氛围。 想围观我朋友圈的,可以加我微信:xlbxiong (温馨提示:微信不提供技术问答服务,提问请到知识星球社区感谢理解)。 one more thing :我们还承接智能合约、DAPP、公链开发等业余。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。]]></content>
<categories>
<category>目录</category>
</categories>
<tags>
<tag>如何学习</tag>
<tag>目录</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列6 - 结构体与映射]]></title>
<url>%2F2017%2F12%2F27%2Fsolidity-structs%2F</url>
<content type="text"><![CDATA[Solidity 教程系列第6篇 - Solidity 结构体与映射。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 本系列文章一部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,另一部分是Solidity深入分析,这部分请订阅区块链技术专栏阅读。 结构体(Structs)Solidity提供struct来定义自定义类型,自定义的类型是引用类型。我们看看下面的例子:1234567891011121314151617181920212223242526272829303132333435363738394041424344pragma solidity ^0.4.11;contract CrowdFunding { // 定义一个包含两个成员的新类型 struct Funder { address addr; uint amount; } struct Campaign { address beneficiary; uint fundingGoal; uint numFunders; uint amount; mapping (uint => Funder) funders; } uint numCampaigns; mapping (uint => Campaign) campaigns; function newCampaign(address beneficiary, uint goal) public returns (uint campaignID) { campaignID = numCampaigns++; // campaignID 作为一个变量返回 // 创建一个结构体实例,存储在storage ,放入mapping里 campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } function contribute(uint campaignID) public payable { Campaign storage c = campaigns[campaignID]; // 用mapping对应项创建一个结构体引用 // 也可以用 Funder(msg.sender, msg.value) 来初始化. c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); c.amount += msg.value; } function checkGoalReached(uint campaignID) public returns (bool reached) { Campaign storage c = campaigns[campaignID]; if (c.amount < c.fundingGoal) return false; uint amount = c.amount; c.amount = 0; c.beneficiary.transfer(amount); return true; }} 上面是一个简化版的众筹合约,但它可以让我们理解structs的基础概念,struct可以用于映射和数组中作为元素。其本身也可以包含映射和数组等类型。 不能声明一个struct同时将自身struct作为成员,这个限制是基于结构体的大小必须是有限的。但struct可以作为mapping的值类型成员。 注意在函数中,将一个struct赋值给一个局部变量(默认是storage类型),实际是拷贝的引用,所以修改局部变量值的同时,会影响到原变量。 当然,也可以直接通过访问成员修改值,而不用一定赋值给一个局部变量,如campaigns[campaignID].amount = 0 映射(Mappings)映射类型,一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)。键类型允许除映射、变长数组、合约、枚举、结构体外的几乎所有类型()。值类型没有任何限制,可以为任何类型包括映射类型。 映射可以被视作为一个哈希表,所有可能的键会被虚拟化的创建,映射到一个类型的默认值(二进制的全零表示)。在映射表中,并不存储键的数据,仅仅存储它的keccak256哈希值,这个哈希值在查找值时需要用到。正因为此,映射是没有长度的,也没有键集合或值集合的概念。 映射类型,仅能用来作为状态变量,或在内部函数中作为storage类型的引用。 可以通过将映射标记为public,来让Solidity创建一个访问器。通过提供一个键值做为参数来访问它,将返回对应的值。映射的值类型也可以是映射,使用访问器访问时,要提供这个映射值所对应的键,不断重复这个过程。来看一个例子: 1234567891011121314151617pragma solidity ^0.4.0;contract MappingExample { mapping(address => uint) public balances; function update(uint newBalance) public { balances[msg.sender] = newBalance; }}contract MappingUser { function f() public returns (uint) { MappingExample m = new MappingExample(); m.update(100); return m.balances(this); }} 注意:映射并未提供迭代输出的方法,可以自行实现一个这样的数据结构。参考iterable mapping 参考文档Solidity官方文档 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列5 - 数组介绍]]></title>
<url>%2F2017%2F12%2F21%2Fsolidity-arrays%2F</url>
<content type="text"><![CDATA[Solidity 教程系列第5篇 - Solidity 数组介绍。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 本文前半部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,后半部分对官方文档中没有提供代码的知识点补充代码说明(订阅专栏阅读)。 数组(Arrays)数组可以声明时指定长度,也可以是动态变长。对storage存储的数组来说,元素类型可以是任意的,类型可以是数组,映射类型,结构体等。但对于memory的数组来说。如果作为public函数的参数,它不能是映射类型的数组,只能是支持ABI的类型。 一个元素类型为T,固定长度为k的数组,可以声明为T[k],而一个动态大小(变长)的数组则声明为T[]。还可以声明一个多维数组,如声明一个类型为uint的数组长度为5的变长数组(5个元素都是变长数组),可以声明为uint[][5]。(注意,相比非区块链语言,多维数组的长度声明是反的。) 要访问第三个动态数组的第二个元素,使用x[2][1]。数组的序号是从0开始的,序号顺序与定义相反。 bytes和string是一种特殊的数组。bytes类似byte[],但在外部函数作为参数调用中,bytes会进行压缩打包。string类似bytes,但不提供长度和按序号的访问方式(目前)。所以应该尽量使用bytes而不是byte[]。 可以将字符串s通过bytes(s)转为一个bytes,可以通过bytes(s).length获取长度,bytes(s)[n]获取对应的UTF-8编码。通过下标访问获取到的不是对应字符,而是UTF-8编码,比如中文编码是多字节,变长的,所以下标访问到的只是其中的一个编码。类型为数组的状态变量,可以标记为public,从而让Solidity创建一个访问器,如果要访问数组的某个元素,指定数字下标就好了。(稍后代码事例) 创建内存数组可使用new关键字创建一个memory的数组。与stroage数组不同的是,你不能通过.length的长度来修改数组大小属性。我们来看看下面的例子:123456789101112pragma solidity ^0.4.16;contract C { function f(uint len) public pure { uint[] memory a = new uint[](7); //a.length = 100; // 错误 bytes memory b = new bytes(len); // Here we have a.length == 7 and b.length == len a[6] = 8; }} 数组常量及内联数组数组常量,是一个数组表达式(还没有赋值到变量)。下面是一个简单的例子:12345678910pragma solidity ^0.4.16;contract C { function f() public pure { g([uint(1), 2, 3]); } function g(uint[3] _data) public pure { // ... }} 通过数组常量,创建的数组是memory的,同时还是定长的。元素类型则是使用刚好能存储的元素的能用类型,比如[1, 2, 3],只需要uint8即可存储,它的类型是uint8[3] memory。 由于g()方法的参数需要的是uint(默认的uint表示的其实是uint256),所以需要对第一个元素进行类型转换,使用uint(1)来进行这个转换。 还需注意的一点是,定长数组,不能与变长数组相互赋值,我们来看下面的代码:12345678910// 无法编译pragma solidity ^0.4.0;contract C { function f() public { // The next line creates a type error because uint[3] memory // cannot be converted to uint[] memory. uint[] x = [uint(1), 3, 4]; }} 已经计划在未来移除这样的限制。当前因为ABI传递数组还有些问题。 成员length属性数组有一个.length属性,表示当前的数组长度。storage的变长数组,可以通过给.length赋值调整数组长度。memory的变长数组不支持。不能通过访问超出当前数组的长度的方式,来自动实现改变数组长度。memory数组虽然可以通过参数,灵活指定大小,但一旦创建,大小不可调整。 push方法storage的变长数组和bytes都有一个push方法(string没有),用于附加新元素到数据末端,返回值为新的长度。 限制情况当前在external函数中,不能使用多维数组。 另外,基于EVM的限制,不能通过外部函数返回动态的内容。123contract C { function f() returns (uint[]) { ... } } 在这个的例子中,如果通过web.js调用能返回数据,但从Solidity中调用不能返回数据。一种绕过这个问题的办法是使用一个非常大的静态数组。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556pragma solidity ^0.4.16;contract ArrayContract { uint[2**20] m_aLotOfIntegers; // 这里不是两个动态数组的数组,而是一个动态数组里,每个元素是长度为二的数组。 bool[2][] m_pairsOfFlags; // newPairs 存在 memory里,因为是函数参数 function setAllFlagPairs(bool[2][] newPairs) public { m_pairsOfFlags = newPairs; } function setFlagPair(uint index, bool flagA, bool flagB) public { // 访问不存在的index会抛出异常 m_pairsOfFlags[index][0] = flagA; m_pairsOfFlags[index][1] = flagB; } function changeFlagArraySize(uint newSize) public { // 如果新size更小, 移除的元素会被销毁 m_pairsOfFlags.length = newSize; } function clear() public { // 销毁 delete m_pairsOfFlags; delete m_aLotOfIntegers; // 同销毁一样的效果 m_pairsOfFlags.length = 0; } bytes m_byteData; function byteArrays(bytes data) public { // byte arrays ("bytes") are different as they are stored without padding, // but can be treated identical to "uint8[]" m_byteData = data; m_byteData.length += 7; m_byteData[3] = byte(8); delete m_byteData[2]; } function addFlag(bool[2] flag) public returns (uint) { return m_pairsOfFlags.push(flag); } function createMemoryArray(uint size) public pure returns (bytes) { // Dynamic memory arrays are created using `new`: uint[2][] memory arrayOfPairs = new uint[2][](size); // Create a dynamic byte array: bytes memory b = new bytes(200); for (uint i = 0; i < b.length; i++) b[i] = byte(i); return b; }} 补充事例说明事例代码及讲解,请订阅区块链技术查看。 参考文档Solidity官方文档-数组 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列4 - 数据存储位置分析]]></title>
<url>%2F2017%2F12%2F21%2Fsolidity_reftype_datalocation%2F</url>
<content type="text"><![CDATA[Solidity教程系列第4篇 - Solidity数据位置分析。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 这部分的内容官方英文文档讲的不是很透,因此我在参考Solidity官方文档(当前最新版本:0.4.20)的同时加入了深入分析部分,欢迎订阅专栏。 数据位置(Data location)在系列第一篇,我们提到 Solidity 类型分为两类:值类型(Value Type) 及 引用类型(Reference Types),前面我们已经介绍完了值类型,接下来会介绍引用类型。 引用类型是一个复杂类型,占用的空间通常超过256位, 拷贝时开销很大,因此我们需要考虑将它们存储在什么位置,是memory(内存中,数据不是永久存在)还是storage(永久存储在区块链中)所有的复杂类型如数组(arrays)和结构体(struct)有一个额外的属性:数据的存储位置(data location)。可为memory和storage。 根据上下文的不同,大多数时候数据位置有默认值,也通过指定关键字storage和memory修改它。 函数参数(包含返回的参数)默认是memory。局部复杂类型变量(local variables)和 状态变量(state variables) 默认是storage。 局部变量:局部作用域(越过作用域即不可被访问,等待被回收)的变量,如函数内的变量。状态变量:合约内声明的公有变量 还有一个存储位置是:calldata,用来存储函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。 数据位置指定非常重要,因为他们影响着赋值行为。在memory和storage之间或与状态变量之间相互赋值,总是会创建一个完全独立的拷贝。而将一个storage的状态变量,赋值给一个storage的局部变量,是通过引用传递。所以对于局部变量的修改,同时修改关联的状态变量。另一方面,将一个memory的引用类型赋值给另一个memory的引用,不会创建拷贝(即:memory之间是引用传递)。 注意:不能将memory赋值给局部变量。 对于值类型,总是会进行拷贝。 下面看一段代码:1234567891011121314151617181920212223242526pragma solidity ^0.4.0;contract C { uint[] x; // x的存储位置是storage // memoryArray的存储位置是 memory function f(uint[] memoryArray) public { x = memoryArray; // 从 memory 复制到 storage var y = x; // storage 引用传递局部变量y(y 是一个 storage 引用) y[7]; // 返回第8个元素 y.length = 2; // x同样会被修改 delete x; // y同样会被修改 // 错误, 不能将memory赋值给局部变量 // y = memoryArray; // 错误,不能通过引用销毁storage // delete y; g(x); // 引用传递, g可以改变x的内容 h(x); // 拷贝到memory, h无法改变x的内容 } function g(uint[] storage storageArray) internal {} function h(uint[] memoryArray) public {}} 总结强制的数据位置(Forced data location) 外部函数(External function)的参数(不包括返回参数)强制为:calldata 状态变量(State variables)强制为: storage 默认数据位置(Default data location) 函数参数及返回参数:memory 复杂类型的局部变量:storage 深入分析storage 存储结构是在合约创建的时候就确定好了的,它取决于合约所声明状态变量。但是内容可以被(交易)调用改变。 Solidity 称这个为状态改变,这也是合约级变量称为状态变量的原因。也可以更好的理解为什么状态变量都是storage存储。 memory 只能用于函数内部,memory 声明用来告知EVM在运行时创建一块(固定大小)内存区域给变量使用。 storage 在区块链中是用key/value的形式存储,而memory则表现为字节数组 关于栈(stack)EVM是一个基于栈的语言,栈实际是在内存(memory)的一个数据结构,每个栈元素占为256位,栈最大长度为1024。值类型的局部变量是存储在栈上。 不同存储的消耗(gas消耗) storage 会永久保存合约状态变量,开销最大 memory 仅保存临时变量,函数调用之后释放,开销很小 stack 保存很小的局部变量,几乎免费使用,但有数量限制。 参考资料Solidity官方文档-类型 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列3 - 函数类型]]></title>
<url>%2F2017%2F12%2F12%2Fsolidity_func%2F</url>
<content type="text"><![CDATA[Solidity 教程系列第三篇 - Solidity 函数类型介绍。Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 本文前半部分是参考Solidity 官方文档(当前最新版本:0.4.20)进行翻译,后半部分函数可见性( public, external, internal, privite )深度分析(仅针对专栏订阅用户)。 函数类型(Function Types)函数也是一种类型,且属于值类型。可以将一个函数赋值给一个函数类型的变量。还可以将一个函数作为参数进行传递。也可以在函数调用中返回一个函数。函数类型有两类:内部(internal)和外部(external)函数 内部(internal)函数只能在当前合约内被调用(在当前的代码块内,包括内部库函数,和继承的函数中)。外部(external)函数由地址和函数方法签名两部分组成,可作为外部函数调用的参数,或返回值。 函数类型定义如下:1function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)] 如果函数不需要返回,则省去returns ()函数类型默认是internal, 因此internal可以省去。但以此相反,合约中函数本身默认是public的, 仅仅是当作类型名使用时默认是internal的。 有两个方式访问函数,一种是直接用函数名f, 一种是this.f, 前者用于内部函数,后者用于外部函数。 如果一个函数变量没有初始化,直接调用它将会产生异常。如果delete了一个函数后调用,也会发生同样的异常。 如果外部函数类型在Solidity的上下文环境以外的地方使用,他们会被视为function类型。它会编码为20字节的函数所在地址,和在它之前的4字节的函数方法签名一起作为bytes24类型。合约中的public的函数,可以使用internal和external两种方式来调用。internal访问形式为f, external访问形式为this.f 成员: 属性 selector public (或 external) 函数有一个特殊的成员selector, 它对应一个ABI 函数选择器。 1234567pragma solidity ^0.4.16;contract Selector {function f() public view returns (bytes4) { return this.f.selector;}} 下面的代码显示内部(internal)函数类型的使用: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748pragma solidity ^0.4.16;library ArrayUtils { // internal functions can be used in internal library functions because // they will be part of the same code context function map(uint[] memory self, function (uint) pure returns (uint) f) internal pure returns (uint[] memory r) { r = new uint[](self.length); for (uint i = 0; i < self.length; i++) { r[i] = f(self[i]); } } function reduce( uint[] memory self, function (uint, uint) pure returns (uint) f ) internal pure returns (uint r) { r = self[0]; for (uint i = 1; i < self.length; i++) { r = f(r, self[i]); } } function range(uint length) internal pure returns (uint[] memory r) { r = new uint[](length); for (uint i = 0; i < r.length; i++) { r[i] = i; } }}contract Pyramid { using ArrayUtils for *; function pyramid(uint l) public pure returns (uint) { return ArrayUtils.range(l).map(square).reduce(sum); } function square(uint x) internal pure returns (uint) { return x * x; } function sum(uint x, uint y) internal pure returns (uint) { return x + y; }} 下面的代码显示外部(external)函数类型的使用:1234567891011121314151617181920212223242526272829pragma solidity ^0.4.11;contract Oracle { struct Request { bytes data; function(bytes memory) external callback; } Request[] requests; event NewRequest(uint); function query(bytes data, function(bytes memory) external callback) public { requests.push(Request(data, callback)); NewRequest(requests.length - 1); } function reply(uint requestID, bytes response) public { // Here goes the check that the reply comes from a trusted source requests[requestID].callback(response); }}contract OracleUser { Oracle constant oracle = Oracle(0x1234567); // known contract function buySomething() { oracle.query("USD", this.oracleResponse); } function oracleResponse(bytes response) public { require(msg.sender == address(oracle)); // Use the data }} 函数可见性分析 public - 任意访问 private - 仅当前合约内 internal - 仅当前合约及所继承的合约 external - 仅外部访问(在内部也只能用外部访问方式访问) public 还是 external 最佳实践请订阅区块链技术查看。 参考文档Solidity官方文档-类型 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列2 - 地址类型介绍]]></title>
<url>%2F2017%2F12%2F12%2Fsolidity2%2F</url>
<content type="text"><![CDATA[Solidity教程系列第二篇 - Solidity地址类型介绍.Solidity 系列完整的文章列表请查看分类-Solidity。 写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么 本文前半部分是参考Solidity官方文档(当前最新版本:0.4.20)进行翻译,后半部分是结合实际合约代码实例说明类型的使用(仅针对专栏订阅用户)。 地址类型(Address)地址类型address是一个值类型, 地址: 20字节(一个以太坊地址的长度),地址类型也有成员,地址是所有合约的基础支持的运算符: <=, <, ==, !=, >= 和 > 注意:从0.5.0开始,合约不再继承自地址类型,但仍然可以显式转换为地址。 地址类型的成员 balance 属性及transfer() 函数这里是地址类型相关成员的快速索引 balance用来查询账户余额,transfer()用来发送以太币(以wei为单位)。 如: 123address x = 0x123;address myAddress = this;if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); 注解:如果x是合约地址,合约的回退函数(fallback 函数)会随transfer调用一起执行(这个是EVM特性),如果因gas耗光或其他原因失败,转移交易会还原并且合约会抛异常停止。 关于回退函数(fallback 函数),简单来说它是合约中无函数名函数,下面代码事例中,进进一步讲解回退函数(fallback) 的使用。 send() 函数 send 与transfer对应,但更底层。如果执行失败,transfer不会因异常停止,而send会返回false。 警告:send() 执行有一些风险:如果调用栈的深度超过1024或gas耗光,交易都会失败。因此,为了保证安全,必须检查send的返回值,如果交易失败,会回退以太币。如果用transfer会更好。 call(), callcode() 和 delegatecall() 函数 为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数,每个参数会按规则(ABI协议)打包成32字节并一一拼接到一起。一个例外是:如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名而直接使用。如果仅想发送消息体,需要避免第一个参数是4个字节。如下面的例子: 123address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;nameReg.call("register", "MyName");nameReg.call(bytes4(keccak256("fun(uint256)")), a); call函数返回一个bool值,以表明执行成功与否。正常结束返回true,异常终止返回false。但无法获取到结果数据,因为需要提前知道返回的数据的编码和数据大小(因不知道对方使用的协议格式,所以也不会知道返回的结果如何解析)。 还可以提供.gas()修饰器进行调用: 1namReg.call.gas(1000000)("register", "MyName"); 类似还可以提供附带以太币: 1nameReg.call.value(1 ether)("register", "MyName"); 修饰器可以混合使用,修饰器调用顺序无所谓。 1nameReg.call.gas(1000000).value(1 ether)("register", "MyName"); 注解:目前还不能在重载函数上使用gas或value修饰符,A workaround is to introduce a special case for gas and value and just re-check whether they are present at the point of overload resolution.(这句我怕翻译的不准确,引用原文) 同样我们也可以使用delegatecall(),它与call方法的区别在于,仅仅是代码会执行,而其它方面,如(存储,余额等)都是用的当前的合约的数据。delegatecall()方法的目的是用来执行另一个合约中的库代码。所以开发者需要保证两个合约中的存储变量能兼容,来保证delegatecall()能顺利执行。在homestead阶段之前,仅有一个受限的callcode()方法可用,但callcode未提供对msg.sender,msg.value的访问权限。 上面的这三个方法call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。 .gas() 在call(), callcode() 和 delegatecall() 函数下都可以使用, delegatecall()不支持.value() 注解:所有合约都继承了address的成员,因此可以使用this.balance查询余额。callcode不鼓励使用,以后会移除。 警告:上述的函数都是底层的函数,使用时要异常小心。当调用一个未知的,可能是恶意的合约时,当你把控制权交给它,它可能回调回你的合约,所以要准备好在调用返回时,应对你的状态变量可能被恶意篡改的情况。 地址常量(Address Literals)一个能通过地址合法性检查(address checksum test)十六进制常量就会被认为是地址,如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。而不能通过地址合法性检查的39到41位长的十六进制常量,会提示一个警告,被视为普通的有理数常量。 地址合法性检查定义在EIP-55 合约事例讲解合约事例代码123456789101112131415161718192021222324252627282930313233343536373839404142434445pragma solidity ^0.4.0;contract AddrTest{ event logdata(bytes data); function() payable { logdata(msg.data); } function getBalance() returns (uint) { return this.balance; } uint score = 0; function setScore(uint s) public { score = s; } function getScore() returns ( uint){ return score; }}contract CallTest{ function deposit() payable { } event logSendEvent(address to, uint value); function transferEther(address towho) payable { towho.transfer(10); logSendEvent(towho, 10); } function callNoFunc(address addr) returns (bool){ return addr.call("tinyxiong", 1234); } function callfunc(address addr) returns (bool){ bytes4 methodId = bytes4(keccak256("setScore(uint256)")); return addr.call(methodId, 100); } function getBalance() returns (uint) { return this.balance; } } 代码运行及讲解代码运行及讲解,请订阅区块链技术查看。 本文现在有对应的视频教程,如果文章没看明白,可以选择观看视频学习,戳视频介绍。 参考文档Solidity官方文档-类型 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币如何达成共识 - 最长链的选择]]></title>
<url>%2F2017%2F12%2F07%2Fbitcoin-sonsensus%2F</url>
<content type="text"><![CDATA[比特币没有中心机构,几乎所有的完整节点都有一份公共总帐本,那么大家如何达成共识:确认哪一份才是公认权威的总账本呢? 为什么要遵守协议这其实是一个经济问题,在经济活动中的每个人都是自私自利的,追求的是利益的最大化,一个节点工作量只有在其他的节点认同其是有效的(打包的新区块,其他的节点只有验证通过才会加入到区块链中,并在网络上传播),才能够过得收益,而只有遵守规则才会得到其他的节点认同。因此,基于逐利,节点就会自发的遵守协议。共识就是数以万计的独立节点遵守了简单的规则(通过异步交互)自发形成的。 共识:共同遵守的协议规范 去中心化共识在工作量证明一篇,我们了解通过工作量证明来竞争记账,权威的总帐本是怎么达到共识的,没有完全说清楚,今天补上,实际上,比特币的共识由所有节点的4个独立过程相互作用而产生: 每个节点(挖矿节点)依据标准对每个交易进行独立验证 挖矿节点通过完成工作量证明,将交易记录独立打包进新区块 每个节点独立的对新区块进行校验并组装进区块链 每个节点对区块链进行独立选择,在工作量证明机制下选择累计工作量最大的区块链 共识最终目的是保证比特币不停的在工作量最大的区块链上运转,工作量最大的区块链就是权威的公共总帐本。 第1 2 3步在比特币如何挖矿-工作量证明一篇有提到过,下面着重讲第4步。 最长链的选择先来一个定义,把累计了最多难度的区块链。在一般情况下,也是包含最多区块的那个链称为主链每一个(挖矿)节点总是选择并尝试延长主链。 分叉当有两名矿工在几乎在相同的时间内,各自都算得了工作量证明解,便立即传播自己的“获胜”区块到网络中,先是传播给邻近的节点而后传播到整个网络。每个收到有效区块的节点都会将其并入并延长区块链。当这个两个区块传播时,一些节点首先收到#3458A, 一些节点首先收到#3458B,这两个候选区块(通常这两个候选区块会包含几乎相同的交易)都是主链的延伸,分叉就会产生,这时分叉出有竞争关系的两条链,如图:两个块都收到的节点,会把其中有更多工作量的一条会继续作为主链,另一条作为备用链保存(保存是因为备用链将来可能会超过主链难度称为新主链)。 分叉解决收到#3458A的(挖矿)节点,会立刻以这个区块为父区块来产生新的候选区块,并尝试寻找这个候选区块的工作量证明解。同样地,接受#3458B区块的节点会以这个区块为链的顶点开始生成新块,延长这个链(下面称为B链)。这时总会有一方抢先发现工作量证明解并将其传播出去,假设以#3458B为父区块的工作量证明首先解出,如图: 当原本以#3458A为父区块求解的节点在收到#3458B, #3459B之后,会立刻将B链作为主链(因为#3458A为顶点的链已经不是最长链了)继续挖矿。 节点也有可能先收到#3459B,再收到#3458B,收到#3459B时,会被认为是“孤块“(因为还找不到#3459B的父块#3458B)保存在孤块池中,一旦收到父块#3458B时,节点就会将孤块从孤块池中取出,并且连接到它的父区块,让它作为区块链的一部分。 比特币将区块间隔设计为10分钟,是在更快速的交易确认和更低的分叉概率间作出的妥协。更短的区块产生间隔会让交易确认更快地完成,也会导致更加频繁地区块链分叉。与之相对地,长的间隔会减少分叉数量,却会导致更长的确认时间。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>共识协议</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约语言 Solidity 教程系列1 - 类型介绍]]></title>
<url>%2F2017%2F12%2F05%2Fsolidity1%2F</url>
<content type="text"><![CDATA[现在的Solidity中文文档,要么翻译的太烂,要么太旧,决定重新翻译下。尤其点名批评极客学院名为《Solidity官方文档中文版》的翻译,机器翻译的都比它好,大家还是别看了。 写在前面Solidity是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解,如果你还不了解,建议你先看以太坊是什么Solidity教程会是一系列文章,本文是第一篇:介绍Solidity的变量类型。Solidity 系列完整的文章列表请查看分类-Solidity。 本文前半部分是参考Solidity官方文档(当前最新版本:0.4.24)进行翻译,后半部分是结合实际合约代码实例说明类型的使用(仅针对专栏订阅用户)。 类型Solidity是一种静态类型语言,意味着每个变量(本地或状态变量)需要在编译时指定变量的类型(或至少可以推倒出类型)。Solidity提供了一些基本类型可以用来组合成复杂类型。 Solidity类型分为两类: 值类型(Value Type) - 变量在赋值或传参时,总是进行值拷贝。 引用类型(Reference Types) 值类型(Value Type)值类型包含: 布尔类型(Booleans) 整型(Integers) 定长浮点型(Fixed Point Numbers) 定长字节数组(Fixed-size byte arrays) 有理数和整型常量(Rational and Integer Literals) 字符串常量(String literals) 十六进制常量(Hexadecimal literals) 枚举(Enums) 函数类型(Function Types) 地址类型(Address) 地址常量(Address Literals) 函数类型及地址类型(Address)有单独的博文,请点击查看。 布尔类型(Booleans)布尔(bool):可能的取值为常量值true和false。 布尔类型支持的运算符有: !逻辑非 && 逻辑与 || 逻辑或 == 等于 != 不等于 注意:运算符&&和||是短路运算符,如f(x)||g(y),当f(x)为真时,则不会继续执行g(y)。 整型(Integers)int/uint: 表示有符号和无符号不同位数整数。支持关键字uint8 到 uint256 (以8步进),uint 和 int 默认对应的是 uint256 和 int256。 支持的运算符: 比较运算符: <=, < , ==, !=, >=, > (返回布尔值:true 或 false) 位操作符: &,|,^(异或),~(位取反) 算术操作符:+,-,一元运算-,一元运算+,,/, %(取余数), **(幂), << (左移位), >>(右移位) 说明: 整数除法总是截断的,但如果运算符是字面量(字面量稍后讲),则不会截断。 整数除0会抛异常。 移位运算的结果的正负取决于操作符左边的数。x << y 和 x 2**y 是相等, x >> y 和 x / 2**y 是相等的。 不能进行负移位,即操作符右边的数不可以为负数,否则会抛出运行时异常。 注意:Solidity中,右移位是和除等价的,因此右移位一个负数,向下取整时会为0,而不像其他语言里为无限负小数。 定长浮点型(Fixed Point Numbers)注意:定长浮点型 Solidity(发文时)还不完全支持,它可以用来声明变量,但不可以用来赋值。 fixed/ufixed: 表示有符号和无符号的固定位浮点数。关键字为ufixedMxN 和 ufixedMxN。M表示这个类型要占用的位数,以8步进,可为8到256位。N表示小数点的个数,可为0到80之间 支持的运算符: 比较运算符: <=, < , ==, !=, >=, > (返回布尔值:true 或 false) 算术操作符:+,-,一元运算-,一元运算+,,/, %(取余数)注意:它和大多数语言的float和double不一样,*M是表示整个数占用的固定位数,包含整数部分和小数部分。因此用一个小位数(M较小)来表示一个浮点数时,小数部分会几乎占用整个空间。 定长字节数组(Fixed-size byte arrays)关键字有:bytes1, bytes2, bytes3, …, bytes32。(以步长1递增)byte代表bytes1。 支持的运算符: 比较符: <=, <, ==, !=, >=, > (返回bool) 位操作符: &, |, ^ (按位异或),~(按位取反), << (左移位), >> (右移位) 索引(下标)访问: 如果x是bytesI,当0 <= k < I ,则x[k]返回第k个字节(只读)。 移位运算和整数类似,移位运算的结果的正负取决于操作符左边的数,且不能进行负移位。如可以-5<<1, 不可以5<<-1 成员变量:.length:表示这个字节数组的长度(只读)。 补充小技巧: 如何在Remix中把内容传递给函数的定长字节数组参数,答案是使用16进制形式(0x1122)传递。如果参数是定长字节数组的数组 bytes32[],则在Remix中参数内容传递形式为:[“0x00”,”0x0a”]。 变长(动态分配大小)字节数组(Dynamically-sized byte array) bytes:动态分配大小字节数组, 参见Arrays,不是值类型! string:动态分配大小UTF8编码的字符类型,参看Arrays。不是值类型! 根据经验:bytes用来存储任意长度的字节数据,string用来存储任意长度的(UTF-8编码)的字符串数据。如果长度可以确定,尽量使用定长的如byte1到byte32中的一个,因为这样更省空间。 有理数和整型常量(Rational and Integer Literals) 也有人把Literals翻译为字面量 整型常量是有一系列0-9的数字组成,10进制表示,比如:8进制是不存在的,前置0在Solidity中是无效的。 10进制小数常量(Decimal fraction literals)带了一个., 在.的两边至少有一个数字,有效的表示如:1., .1 和 1.3. 科学符号也支持,基数可以是小数,指数必须是整数, 有效的表示如: 2e10, -2e10, 2e-10, 2.5e1。 数字常量表达式本身支持任意精度,也就是可以不会运算溢出,或除法截断。但当它被转换成对应的非常量类型,或者将他们与非常量进行运算,则不能保证精度了。如:(2*800 + 1) - 2*800的结果为1(uint8整类) ,尽管中间结果已经超过计算机字长。另外:.5 * 8的结果是4,尽管有非整形参与了运算。 只要操作数是整形,整型支持的运算符都适用于整型常量表达式。如果两个操作数是小数,则不允许进行位运算,指数也不能是小数。 注意:Solidity对每一个有理数都有一个数值常量类型。整数常量和有理数常量从属于数字常量。所有的数字常表达式的结果都属于数字常量。所以1 + 2和2 + 1都属于同样的有理数的数字常量3 警告:整数常量除法,在早期的版本中是被截断的,但现在可以被转为有理数了,如5/2的值为 2.5 注意:数字常量表达式,一旦其中含有常量表达式,它就会被转为一个非常量类型。下面代码中表达式的结果将会被认为是一个有理数:12uint128 a = 1;uint128 b = 2.5 + a + 0.5; 上述代码编译不能通过,因为b会被编译器认为是小数型。 字符串常量字符串常量是指由单引号,或双引号引起来的字符串 (“foo” or ‘bar’)。字符串并不像C语言,包含结束符,”foo”这个字符串大小仅为三个字节。和整数常量一样,字符串的长度类型可以是变长的。字符串可以隐式的转换为byte1,…byte32 如果适合,也会转为bytes或string。 字符串常量支持转义字符,比如\n,\xNN,\uNNNN。其中\xNN表示16进制值,最终转换合适的字节。而\uNNNN表示Unicode编码值,最终会转换为UTF8的序列。 十六进制常量(Hexadecimal literals)十六进制常量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串,内容是十六进制字符串,如hex”001122ff”。它的值会用二进制来表示。 十六进制常量和字符串常量类似,也可以转换为字节数组。 枚举(Enums)在Solidity中,枚举可以用来自定义类型。它可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。枚举类型应至少有一名成员。下面是一个枚举的例子:123456789101112131415161718192021222324pragma solidity ^0.4.0;contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice; ActionChoices constant defaultChoice = ActionChoices.GoStraight; function setGoStraight() { choice = ActionChoices.GoStraight; } // Since enum types are not part of the ABI, the signature of "getChoice" // will automatically be changed to "getChoice() returns (uint8)" // for all matters external to Solidity. The integer type used is just // large enough to hold all enum values, i.e. if you have more values, // `uint16` will be used and so on. function getChoice() returns (ActionChoices) { return choice; } function getDefaultChoice() returns (uint) { return uint(defaultChoice); }} 代码实例通过合约代码实例说明类型的使用,请订阅区块链技术查看。 本文现在有对应的视频教程,如果文章没看明白,可以选择观看视频学习。 参考文档Solidity官方文档-类型 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>Solidity</category>
</categories>
<tags>
<tag>Solidity手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Geth 控制台使用及 Web3.js 使用实战]]></title>
<url>%2F2017%2F12%2F01%2Fgeth_cmd_short%2F</url>
<content type="text"><![CDATA[在开发以太坊去中心化应用,免不了和以太坊进行交互,那就离不开Web3。Geth 控制台(REPL)实现了所有的web3 API及Admin API,使用好 Geth 就是必修课。结合Geth命令用法阅读效果更佳。 写在前面阅读本文之前,你需要对以太坊(区块链)有初步的了解,如果你不知道以太坊是什么,请先阅读以太坊是什么。如果你在我的小专栏之外的地方阅读到本文,你可能只能阅读本文的节选,阅读完整全文请订阅小专栏区块链技术 geth控制台初探 - 启动、退出安装参考智能合约开发环境搭建最简单启动方式如下:1$ geth console geth控制台启动成功之后,可以看到>提示符。退出输入exit geth 日志控制重定向日志到文件使用geth console启动是,会在当前的交互界面下时不时出现日志。可以使用以下方式把日志输出到文件。1$ geth console 2>>geth.log 可以新开一个命令行终端输入以下命令查看日志:1$ tail -f geth.log 重定向另一个终端也可以把日志重定向到另一个终端,先在想要看日志的终端输入:1$ tty 就可以获取到终端编号,如:/dev/ttys003然后另一个终端使用:1$ geth console 2>> /dev/ttys003 启动geth, 这是日志就输出到另一个终端。如果不想看到日志还可以重定向到空终端:1$ geth console 2>> /dev/null 日志级别控制使用–verbosity可以控制日志级别,如不想看到日志还可以使用:1$ geth --verbosity 0 console 启动一个开发模式测试节点1geth --datadir /home/xxx/testNet --dev console 技巧:如果我们经常使用一个方式来启动,可以把命令存为一个bash脚本。~/bin你可以放一些常用的脚本,并把~/bin加入到环境变量PATH里。 连接geth节点另外一个启动geth的方法是连接到一个geth节点:123$ geth attach ipc:/some/custom/path$ geth attach http://191.168.1.1:8545$ geth attach ws://191.168.1.1:8546 如连接刚刚打开的开发模式节点使用:1geth attach ipc:testNet/geth.ipc 更多内容请前往区块链技术小专栏查看全文链接。 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>geth</category>
</categories>
<tags>
<tag>Geth使用</tag>
<tag>Web3.js</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊客户端Geth命令用法-参数详解]]></title>
<url>%2F2017%2F11%2F29%2Fgeth_cmd_options%2F</url>
<content type="text"><![CDATA[Geth在以太坊智能合约开发中最常用的工具(必备开发工具),一个多用途的命令行工具。熟悉Geth可以让我们有更好的效率,大家可收藏起来作为Geth命令用法手册。 本文主要是对geth help的翻译,基于最新的geth 1.7.3-stable版本。 如果你还不知道geth是什么,请先阅读入门篇:以太坊是什么。更多geth实战使用方法请参考Geth控制台使用实战及Web3.js使用以下开始正文。 命令用法geth [选项] 命令 [命令选项] [参数…] 版本:1.7.3-stable 命令:account 管理账户 attach 启动交互式JavaScript环境(连接到节点) bug 上报bug Issues console 启动交互式JavaScript环境 copydb 从文件夹创建本地链 dump Dump(分析)一个特定的块存储 dumpconfig 显示配置值 export 导出区块链到文件 import 导入一个区块链文件 init 启动并初始化一个新的创世纪块 js 执行指定的JavaScript文件(多个) license 显示许可信息 makecache 生成ethash验证缓存(用于测试) makedag 生成ethash 挖矿DAG(用于测试) monitor 监控和可视化节点指标 removedb 删除区块链和状态数据库 version 打印版本号 wallet 管理Ethereum预售钱包 help,h 显示一个命令或帮助一个命令列表 ETHEREUM选项:--config value TOML 配置文件 --datadir “xxx” 数据库和keystore密钥的数据目录 --keystore keystore存放目录(默认在datadir内) --nousb 禁用监控和管理USB硬件钱包 --networkid value 网络标识符(整型, 1=Frontier, 2=Morden (弃用), 3=Ropsten, 4=Rinkeby) (默认: 1) --testnet Ropsten网络:预先配置的POW(proof-of-work)测试网络 --rinkeby Rinkeby网络: 预先配置的POA(proof-of-authority)测试网络 --syncmode "fast" 同步模式 ("fast", "full", or "light") --ethstats value 上报ethstats service URL (nodename:secret@host:port) --identity value 自定义节点名 --lightserv value 允许LES请求时间最大百分比(0 – 90)(默认值:0) --lightpeers value 最大LES client peers数量(默认值:20) --lightkdf 在KDF强度消费时降低key-derivation RAM&CPU使用 开发者(模式)选项:--dev 使用POA共识网络,默认预分配一个开发者账户并且会自动开启挖矿。 --dev.period value 开发者模式下挖矿周期 (0 = 仅在交易时) (默认: 0) ETHASH 选项:--ethash.cachedir ethash验证缓存目录(默认 = datadir目录内) --ethash.cachesinmem value 在内存保存的最近的ethash缓存个数 (每个缓存16MB ) (默认: 2) --ethash.cachesondisk value 在磁盘保存的最近的ethash缓存个数 (每个缓存16MB) (默认: 3) --ethash.dagdir "" 存ethash DAGs目录 (默认 = 用户hom目录) --ethash.dagsinmem value 在内存保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 1) --ethash.dagsondisk value 在磁盘保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 2) 交易池选项:--txpool.nolocals 为本地提交交易禁用价格豁免 --txpool.journal value 本地交易的磁盘日志:用于节点重启 (默认: "transactions.rlp") --txpool.rejournal value 重新生成本地交易日志的时间间隔 (默认: 1小时) --txpool.pricelimit value 加入交易池的最小的gas价格限制(默认: 1) --txpool.pricebump value 价格波动百分比(相对之前已有交易) (默认: 10) --txpool.accountslots value 每个帐户保证可执行的最少交易槽数量 (默认: 16) --txpool.globalslots value 所有帐户可执行的最大交易槽数量 (默认: 4096) --txpool.accountqueue value 每个帐户允许的最多非可执行交易槽数量 (默认: 64) --txpool.globalqueue value 所有帐户非可执行交易最大槽数量 (默认: 1024) --txpool.lifetime value 非可执行交易最大入队时间(默认: 3小时) 性能调优的选项:--cache value 分配给内部缓存的内存MB数量,缓存值(最低16 mb /数据库强制要求)(默认:128) --trie-cache-gens value 保持在内存中产生的trie node数量(默认:120) 帐户选项:--unlock value 需解锁账户用逗号分隔 --password value 用于非交互式密码输入的密码文件 API和控制台选项:--rpc 启用HTTP-RPC服务器 --rpcaddr value HTTP-RPC服务器接口地址(默认值:“localhost”) --rpcport value HTTP-RPC服务器监听端口(默认值:8545) --rpcapi value 基于HTTP-RPC接口提供的API --ws 启用WS-RPC服务器 --wsaddr value WS-RPC服务器监听接口地址(默认值:“localhost”) --wsport value WS-RPC服务器监听端口(默认值:8546) --wsapi value 基于WS-RPC的接口提供的API --wsorigins value websockets请求允许的源 --ipcdisable 禁用IPC-RPC服务器 --ipcpath 包含在datadir里的IPC socket/pipe文件名(转义过的显式路径) --rpccorsdomain value 允许跨域请求的域名列表(逗号分隔)(浏览器强制) --jspath loadScript JavaScript加载脚本的根路径(默认值:“.”) --exec value 执行JavaScript语句(只能结合console/attach使用) --preload value 预加载到控制台的JavaScript文件列表(逗号分隔) 网络选项:--bootnodes value 用于P2P发现引导的enode urls(逗号分隔)(对于light servers用v4+v5代替) --bootnodesv4 value 用于P2P v4发现引导的enode urls(逗号分隔) (light server, 全节点) --bootnodesv5 value 用于P2P v5发现引导的enode urls(逗号分隔) (light server, 轻节点) --port value 网卡监听端口(默认值:30303) --maxpeers value 最大的网络节点数量(如果设置为0,网络将被禁用)(默认值:25) --maxpendpeers value 最大尝试连接的数量(如果设置为0,则将使用默认值)(默认值:0) --nat value NAT端口映射机制 (any|none|upnp|pmp|extip:<IP>) (默认: “any”) --nodiscover 禁用节点发现机制(手动添加节点) --v5disc 启用实验性的RLPx V5(Topic发现)机制 --nodekey value P2P节点密钥文件 --nodekeyhex value 十六进制的P2P节点密钥(用于测试) 矿工选项:--mine 打开挖矿 --minerthreads value 挖矿使用的CPU线程数量(默认值:8) --etherbase value 挖矿奖励地址(默认=第一个创建的帐户)(默认值:“0”) --targetgaslimit value 目标gas限制:设置最低gas限制(低于这个不会被挖?) (默认值:“4712388”) --gasprice value 挖矿接受交易的最低gas价格 --extradata value 矿工设置的额外块数据(默认=client version) GAS价格选项:--gpoblocks value 用于检查gas价格的最近块的个数 (默认: 10) --gpopercentile value 建议gas价参考最近交易的gas价的百分位数,(默认: 50) 虚拟机的选项:--vmdebug 记录VM及合约调试信息 日志和调试选项:--metrics 启用metrics收集和报告 --fakepow 禁用proof-of-work验证 --verbosity value 日志详细度:0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3) --vmodule value 每个模块详细度:以 <pattern>=<level>的逗号分隔列表 (比如 eth/*=6,p2p=5) --backtrace value 请求特定日志记录堆栈跟踪 (比如 “block.go:271”) --debug 突出显示调用位置日志(文件名及行号) --pprof 启用pprof HTTP服务器 --pprofaddr value pprof HTTP服务器监听接口(默认值:127.0.0.1) --pprofport value pprof HTTP服务器监听端口(默认值:6060) --memprofilerate value 按指定频率打开memory profiling (默认:524288) --blockprofilerate value 按指定频率打开block profiling (默认值:0) --cpuprofile value 将CPU profile写入指定文件 --trace value 将execution trace写入指定文件 WHISPER实验选项:--shh 启用Whisper --shh.maxmessagesize value 可接受的最大的消息大小 (默认值: 1048576) --shh.pow value 可接受的最小的POW (默认值: 0.2) 弃用选项:--fast 开启快速同步 --light 启用轻客户端模式 其他选项:–help, -h 显示帮助 版权:Copyright 2013-2017 The go-ethereum Authors 翻译说明 有些参数翻译可能有不准确的地方,请大家指正。 原文会尽量随geth升级保持更新,原始链接:https://learnblockchain.cn/2017/11/29/geth_cmd_options/ 如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
<category>geth</category>
</categories>
<tags>
<tag>Geth命令用法</tag>
<tag>Geth命令参数详解</tag>
<tag>Geth手册</tag>
</tags>
</entry>
<entry>
<title><![CDATA[智能合约开发环境搭建及Hello World合约]]></title>
<url>%2F2017%2F11%2F24%2Finit-env%2F</url>
<content type="text"><![CDATA[如果你对于以太坊智能合约开发还没有概念(本文会假设你已经知道这些概念),建议先阅读入门篇。就先学习任何编程语言一样,入门的第一个程序都是Hello World。今天我们来一步一步从搭建以太坊智能合约开发环境开始,讲解智能合约的Hello World如何编写。 开发环境搭建Solidity安装强烈建议新手使用Remix -Solidity IDE来进行开发。Remix 是一个基于浏览器的Solidity,就可以不用安装Solidity,本文的Hello World教程也将基于Remix Solidity IDE来进行。 如果你想自己安装请参考Solidity安装指引。 更新,开发环境搭建还可以看另一篇文章: 搭建智能合约开发环境Remix IDE及使用。 geth 安装Mac下安装命令如下:其他平台参考:geth官方安装指引12brew tap ethereum/ethereumbrew install ethereum brew 是 Mac 下的包管理工具,和Ubuntu里的apt-get类似 安装完以后,就是把geth控制台启动。 启动环境在入门篇讲过,geth是一个以太坊客户端,现在利用geth启动一个以太坊(开发者)网络节点。 1geth --datadir testNet --dev console 2>> test.log 执行命名后,会进入geth控制台,这时光标停在一个向右的箭头处,像这样: 命令参数说明(更多命令详解可阅读Geth命令用法-参数详解篇):–dev 启用开发者网络(模式),开发者网络会使用POA共识,默认预分配一个开发者账户并且会自动开启挖矿。–datadir 后面的参数是区块数据及秘钥存放目录。第一次输入命令后,它会放在当前目录下新建一个testNet目录来存放数据。console 进入控制台2>> test.log 表示把控制台日志输出到test.log文件 为了更好的理解,建议新开一个命令行终端,实时显示日志:1tail -f test.log 准备账户部署智能合约需要一个外部账户,我们先来看看分配的开发者账户,在控制台使用以下命令查看账户:1> eth.accounts 回车后,返回一个账户数组,里面有一个默认账户,如: 也可以使用personal.listAccounts查看账户, 再来看一下账户里的余额,使用一下命令:1> eth.getBalance(eth.accounts[0]) eth.accounts[0]表示账户列表第一个账户回车后,可以看到大量的余额,如:1.15792089237316195423570985008687907853269… e+77 开发者账户因余额太多,如果用这个账户来部署合约时会无法看到余额变化,为了更好的体验完整的过程,这里选择创建一个新的账户。 创建账户使用以下命令创建账户:1> personal.newAccount("TinyXiong") TinyXiong为新账户的密码,回车后,返回一个新账户。 这时我们查看账户列表:1> eth.accounts 可以看到账户数组你包含两个账户,新账户在第二个(索引为1)位置。 现在看看账户的余额:12> eth.getBalance(eth.accounts[1])0 回车后,返回的是0,新账户是0。结果如: 给新账户转账我们知道没有余额的账户是没法部署合约的,那我们就从默认账户转1以太币给新账户,使用以下命令(请使用你自己eth.accounts对应输出的账户):1eth.sendTransaction({from: '0xb0ebe17ef0e96b5c525709c0a1ede347c66bd391', to: '0xf280facfd60d61f6fd3f88c9dee4fb90d0e11dfc', value: web3.toWei(1, "ether")}) 在打开的tail -f test.log日志终端里,可以同时看到挖矿记录再次查看新账户余额,可以新账户有1个以太币 解锁账户在部署合约前需要先解锁账户(就像银行转账要输入密码一样),使用以下命令:1personal.unlockAccount(eth.accounts[1],"TinyXiong"); “TinyXiong” 是之前创建账户时的密码解锁成功后,账户就准备完毕啦,接下来就是编写合约代码。 编写合约代码现在我们来开始编写第一个智能合约代码,solidity代码如下: 123456789101112pragma solidity ^0.4.18;contract hello { string greeting; function hello(string _greeting) public { greeting = _greeting; } function say() constant public returns (string) { return greeting; }} 简单解释下,我们定义了一个名为hello的合约,在合约初始化时保存了一个字符串(我们会传入hello world),每次调用say返回字符串。把这段代码写(拷贝)到Browser-Solidity,如果没有错误,点击Details获取部署代码,如: 在弹出的对话框中找到WEB3DEPLOY部分,点拷贝,粘贴到编辑器后,修改初始化字符串为hello world。 solidity在博文写作时(2017/11/24),版本为0.4.18,solidity发展非常快,solidity版本之间有可能不能兼容,这是你可以在Browser-Solidity的Settings里选择对应的编译器版本。Browser-Solidity也不停的更新中,截图可能和你看到的界面不一样。 部署合约Browser-Solidity生成的代码,拷贝到编辑器里修改后的代码如下: 1234567891011121314var _greeting = "Hello World" ;var helloContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"say","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_greeting","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);var hello = helloContract.new( _greeting, { from: web3.eth.accounts[1], data: '0x6060604052341561000f57600080fd5b6040516102b83803806102b8833981016040528080518201919050508060009080519060200190610041929190610048565b50506100ed565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008957805160ff19168380011785556100b7565b828001600101855582156100b7579182015b828111156100b657825182559160200191906001019061009b565b5b5090506100c491906100c8565b5090565b6100ea91905b808211156100e65760008160009055506001016100ce565b5090565b90565b6101bc806100fc6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063954ab4b214610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc61017c565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101725780601f1061014757610100808354040283529160200191610172565b820191906000526020600020905b81548152906001019060200180831161015557829003601f168201915b5050505050905090565b6020604051908101604052806000815250905600a165627a7a723058204a5577bb3ad30e02f7a3bdd90eedcc682700d67fc8ed6604d38bb739c0655df90029', gas: '4700000' }, function (e, contract){ console.log(e, contract); if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); } }); 第1行:修改字符串为Hello World第2行:修改合约变量名第3行:修改合约实例变量名,之后可以直接用实例调用函数。第6行:修改部署账户为新账户索引,即使用新账户来部署合约。第8行:准备付的gas费用,IDE已经帮我们预估好了。第9行:设置部署回调函数。 拷贝回geth控制台里,回车后,看到输出如:1Contract mined! address: 0x79544078dcd9d560ec3f6eff0af42a9fc84c7d19 transactionHash: 0xe2caab22102e93434888a0b8013a7ae7e804b132e4a8bfd2318356f6cf0480b3 说明合约已经部署成功。 在打开的tail -f test.log日志终端里,可以同时看到挖矿记录 现在我们查看下新账户的余额:1> eth.getBalance(eth.accounts[1]) 是不是比之前转账的余额少呀! 运行合约12> hello.say()"Hello World" 输出Hello World,我们第一个合约Hello World,成功运行了。 运行截图如下: 本文会随geth,solidity语言版本升级保持更新,查看本文原始链接:https://learnblockchain.cn/2017/11/24/init-env/ 第一个合约的意义更重要的是体验智能合约开发流程,对于初学者一些可以选择先放弃一些细节,开发流程打通之后,可以增强信心进行下一步的学习。如果你想学习以太坊DApp开发,这门视频课程以太坊DAPP开发实战是你不错的选择。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>环境</tag>
<tag>geth安装</tag>
<tag>第一个智能合约</tag>
</tags>
</entry>
<entry>
<title><![CDATA[以太坊是什么 - 以太坊开发入门指南]]></title>
<url>%2F2017%2F11%2F20%2Fwhatiseth%2F</url>
<content type="text"><![CDATA[很多同学已经跃跃欲试投入到区块链开发队伍当中来,可是又感觉无从下手,本文将基于以太坊平台,以通俗的方式介绍以太坊开发中涉及的各晦涩的概念,轻松带大家入门。 写在前面阅读本文前,你应该大概了解区块链是什么,如果你还不了解,欢迎订阅专栏:区块链技术指引你从头开始学区块链技术。 以太坊是什么以太坊(Ethereum)是一个建立在区块链技术之上, 去中心化应用平台。它允许任何人在平台中建立和使用通过区块链技术运行的去中心化应用。 对这句话不理解的同学,姑且可以理解为以太坊是区块链里的Android,它是一个开发平台,让我们就可以像基于Android Framework一样基于区块链技术写应用。 在没有以太坊之前,写区块链应用是这样的:拷贝一份比特币代码,然后去改底层代码如加密算法,共识机制,网络协议等等(很多山寨币就是这样,改改就出来一个新币)。以太坊平台对底层区块链技术进行了封装,让区块链应用开发者可以直接基于以太坊平台进行开发,开发者只要专注于应用本身的开发,从而大大降低了难度。 目前围绕以太坊已经形成了一个较为完善的开发生态圈:有社区的支持,有很多开发框架、工具可以选择。 智能合约什么是智能合约以太坊上的程序称之为智能合约, 它是代码和数据(状态)的集合。 智能合约可以理解为在区块链上可以自动执行的(由消息驱动的)、以代码形式编写的合同(特殊的交易)。 智能合约英文是Smart Contract,和人工智能( AI:Artificial Intelligence )的智能没有关系,最早尼克萨博在95年就提出智能合约的概念,它的概念很简单,就是将法律条文写成可执行代码。当时并没有区块链,不过智能合约与区块链最配,我们知道合同都是要一式两份、三或四份,不能控制在某一方手中,这也就是去中心化。 在比特币脚本中,我们讲到过比特币的交易是可以编程的,但是比特币脚本有很多的限制,能够编写的程序也有限,而以太坊则更加完备(在计算机科学术语中,称它为是“图灵完备的”),让我们就像使用任何高级语言一样来编写几乎可以做任何事情的程序(智能合约)。 智能合约非常适合对信任、安全和持久性要求较高的应用场景,比如:数字货币、数字资产、投票、保险、金融应用、预测市场、产权所有权管理、物联网、点对点交易等等。目前除数字货币之外,真正落地的应用还不多(就像移动平台刚开始出来一样),相信1到3年内,各种杀手级会慢慢出现。 编程语言:Solidity智能合约的官方推荐的编程语言是Solidity,文件扩展名以.sol结尾。Solidity语言和JavaScript很相似,用它来开发合约并编译成以太坊虚拟机字节代码。 还有Viper,Serpent,LLL及Bamboo,建议大家还是使用Solidity。更新:Serpent官方已经不再推荐,建议Serpent的用户转换到Viper,他们都是类Python语言。 Browser-Solidity是一个浏览器的Solidity IDE, 大家可以点进去看看,以后我们更多文章介绍Solidity这个语言。 运行环境:EVMEVM(Ethereum Virtual Machine)以太坊虚拟机是以太坊中智能合约的运行环境。 Solidity之于EVM,就像之于跟JVM的关系一样,这样大家就容易理解了。以太坊虚拟机是一个隔离的环境,外部无法接触到在EVM内部运行的代码。 而EVM运行在以太坊节点上,当我们把合约部署到以太坊网络上之后,合约就可以在以太坊网络中运行了。 合约的编译以太坊虚拟机上运行的是合约的字节码形式,需要我们在部署之前先对合约进行编译,可以选择Browser-Solidity Web IDE或solc编译器。 合约的部署在以太坊上开发应用时,常常要使用到以太坊客户端(钱包)。平时我们在开发中,一般不接触到客户端或钱包的概念,它是什么呢? 以太坊客户端(钱包)以太坊客户端,其实我们可以把它理解为一个开发者工具,它提供账户管理、挖矿、转账、智能合约的部署和执行等等功能。 EVM是由以太坊客户端提供的 Geth是典型的开发以太坊时使用的客户端,基于Go语言开发。 Geth提供了一个交互式命令控制台,通过命令控制台中包含了以太坊的各种功能(API)。Geth的使用我们之后会有文章介绍,这里大家先有个概念。 Geth控制台和Chrome浏览器开发者工具里的面的控制台是类似的,不过Geth控制台是跑在终端里。相对于Geth,Mist则是图形化操作界面的以太坊客户端。 如何部署智能合约的部署是指把合约字节码发布到区块链上,并使用一个特定的地址来标示这个合约,这个地址称为合约账户。 以太坊中有两类账户: 外部账户该类账户被私钥控制(由人控制),没有关联任何代码。 合约账户该类账户被它们的合约代码控制且有代码与之关联。 和比特币使用UTXO的设计不一样,以太坊使用更为简单的账户概念。两类账户对于EVM来说是一样的。 外部账户与合约账户的区别和关系是这样的:一个外部账户可以通过创建和用自己的私钥来对交易进行签名,来发送消息给另一个外部账户或合约账户。在两个外部账户之间传送消息是价值转移的过程。但从外部账户到合约账户的消息会激活合约账户的代码,允许它执行各种动作(比如转移代币,写入内部存储,挖出一个新代币,执行一些运算,创建一个新的合约等等)。只有当外部账户发出指令时,合同账户才会执行相应的操作。 合约部署就是将编译好的合约字节码通过外部账号发送交易的形式部署到以太坊区块链上(由实际矿工出块之后,才真正部署成功)。 运行合约部署之后,当需要调用这个智能合约的方法时只需要向这个合约账户发送消息(交易)即可,通过消息触发后智能合约的代码就会在EVM中执行了。 Gas和云计算相似,占用区块链的资源(不管是简单的转账交易,还是合约的部署和执行)同样需要付出相应的费用(天下没有免费的午餐对不对!)。以太坊上用Gas机制来计费,Gas也可以认为是一个工作量单位,智能合约越复杂(计算步骤的数量和类型,占用的内存等),用来完成运行就需要越多Gas。任何特定的合约所需的运行合约的Gas数量是固定的,由合约的复杂度决定。而Gas价格由运行合约的人在提交运行合约请求的时候规定,以确定他愿意为这次交易愿意付出的费用:Gas价格(用以太币计价) * Gas数量。 Gas的目的是限制执行交易所需的工作量,同时为执行支付费用。当EVM执行交易时,Gas将按照特定规则被逐渐消耗,无论执行到什么位置,一旦Gas被耗尽,将会触发异常。当前调用帧所做的所有状态修改都将被回滚, 如果执行结束还有Gas剩余,这些Gas将被返还给发送账户。 如果没有这个限制,就会有人写出无法停止(如:死循环)的合约来阻塞网络。 因此实际上(把前面的内容串起来),我们需要一个有以太币余额的外部账户,来发起一个交易(普通交易或部署、运行一个合约),运行时,矿工收取相应的工作量费用。 以太坊网络有些着急的同学要问了,没有以太币,要怎么进行智能合约的开发?可以选择以下方式: 选择以太坊官网测试网络Testnet测试网络中,我们可以很容易获得免费的以太币,缺点是需要发很长时间初始化节点。 使用私有链创建自己的以太币私有测试网络,通常也称为私有链,我们可以用它来作为一个测试环境来开发、调试和测试智能合约。通过上面提到的Geth很容易就可以创建一个属于自己的测试网络,以太币想挖多少挖多少,也免去了同步正式网络的整个区块链数据。 使用开发者网络(模式)相比私有链,开发者网络(模式)下,会自动分配一个有大量余额的开发者账户给我们使用。 使用模拟环境另一个创建测试网络的方法是使用testrpc,testrpc是在本地使用内存模拟的一个以太坊环境,对于开发调试来说,更方便快捷。而且testrpc可以在启动时帮我们创建10个存有资金的测试账户。进行合约开发时,可以在testrpc中测试通过后,再部署到Geth节点中去。 更新:testrpc 现在已经并入到Truffle 开发框架中,现在名字是Ganache CLI。 Dapp:去中心化的应用程序以太坊社区把基于智能合约的应用称为去中心化的应用程序(Decentralized App)。如果我们把区块链理解为一个不可篡改的数据库,智能合约理解为和数据库打交道的程序,那就很容易理解Dapp了,一个Dapp不单单有智能合约,比如还需要有一个友好的用户界面和其他的东西。 TruffleTruffle是Dapp开发框架,他可以帮我们处理掉大量无关紧要的小事情,让我们可以迅速开始写代码-编译-部署-测试-打包DApp这个流程。 总结我们现在来总结一下,以太坊是平台,它让我们方便的使用区块链技术开发去中心化的应用,在这个应用中,使用Solidity来编写和区块链交互的智能合约,合约编写好后之后,我们需要用以太坊客户端用一个有余额的账户去部署及运行合约(使用Truffle框架可以更好的帮助我们做这些事情了)。为了开发方便,我们可以用Geth或testrpc来搭建一个测试网络。 注:本文中为了方便大家理解,对一些概念做了类比,有些严格来不是准确,不过我也认为对于初学者,也没有必要把每一个概念掌握的很细致和准确,学习是一个逐步深入的过程,很多时候我们会发现,过一段后,我们会对同一个东西有不一样的理解。 本文完,这些概念你都明白了么?现在你可以开始开发了,看看智能合约开发环境搭建及Hello World合约 我们还为区块链技术爱好者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程及以太坊智能合约开发。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论,作为星友福利,星友可加入区块链技术交流群,群内已经聚集了300多位区块链技术牛人和爱好者。]]></content>
<categories>
<category>以太坊</category>
</categories>
<tags>
<tag>以太坊概念</tag>
<tag>以太坊入门</tag>
<tag>ethereum</tag>
</tags>
</entry>
<entry>
<title><![CDATA[非对称加密技术- RSA算法数学原理分析]]></title>
<url>%2F2017%2F11%2F15%2Fasy-encryption%2F</url>
<content type="text"><![CDATA[非对称加密技术,在现在网络中,有非常广泛应用。加密技术更是数字货币的基础。 所谓非对称,就是指该算法需要一对密钥,使用其中一个(公钥)加密,则需要用另一个(私钥)才能解密。但是对于其原理大部分同学应该都是一知半解,今天就来分析下经典的非对称加密算法 - RSA算法。通过本文的分析,可以更好的理解非对称加密原理,可以让我们更好的使用非对称加密技术。 题外话:本博客一直有打算写一系列文章通俗的密码学,昨天给站点上https, 因其中使用了RSA算法,就查了一下,发现现在网上介绍RSA算法的文章都写的太难理解了,反正也准备写密码学,就先写RSA算法吧,下面开始正文。 RSA算法原理RSA算法的基于这样的数学事实:两个大质数相乘得到的大数难以被因式分解。如:有很大质数p跟q,很容易算出N,使得 N = p * q,但给出N, 比较难找p q(没有很好的方式, 只有不停的尝试) 这其实也是单向函数的概念 下面来看看数学演算过程: 选取两个大质数p,q,计算N = p q 及 φ ( N ) = φ (p) φ (q) = (p-1) * (q-1) 三个数学概念:质数(prime numbe):又称素数,为在大于1的自然数中,除了1和它本身以外不再有其他因数。互质关系:如果两个正整数,除了1以外,没有其他公因子,我们就称这两个数是互质关系(coprime)。φ(N):叫做欧拉函数,是指任意给定正整数N,在小于等于N的正整数之中,有多少个与N构成互质关系。 如果n是质数,则 φ(n)=n-1。如果n可以分解成两个互质的整数之积, φ(n) = φ(p1p2) = φ(p1)φ(p2)。即积的欧拉函数等于各个因子的欧拉函数之积。 选择一个大于1 小于φ(N)的数e,使得 e 和 φ(N)互质 e其实是1和φ(N)之前的一个质数 计算d,使得de=1 mod φ(N) 等价于方程式 ed-1 = k φ(N) 求一组解。 d 称为e的模反元素,e 和 φ(N)互质就肯定存在d。 模反元素是指如果两个正整数a和n互质,那么一定可以找到整数b,使得ab被n除的余数是1,则b称为a的模反元素。可根据欧拉定理证明模反元素存在,欧拉定理是指若n,a互质,则:a^φ(n) ≡ 1(mod n) 及 a^φ(n) = a * a^(φ(n) - 1), 可得a的 φ(n)-1 次方,就是a的模反元素。 (N, e)封装成公钥,(N, d)封装成私钥。假设m为明文,加密就是算出密文c: m^e mod N = c (明文m用公钥e加密并和随机数N取余得到密文c)解密则是: c^d mod N = m (密文c用密钥解密并和随机数N取余得到明文m) 私钥解密这个是可以证明的,这里不展开了。 加解密步骤具体还是来看看步骤,举个例子,假设Alice和Bob又要相互通信。 Alice 随机取大质数P1=53,P2=59,那N=53*59=3127,φ(N)=3016 取一个e=3,计算出d=2011。 只将N=3127,e=3 作为公钥传给Bob(公钥公开) 假设Bob需要加密的明文m=89,c = 89^3 mod 3127=1394,于是Bob传回c=1394。 (公钥加密过程) Alice使用c^d mod N = 1394^2011 mod 3127,就能得到明文m=89。 (私钥解密过程) 假如攻击者能截取到公钥n=3127,e=3及密文c=1394,是仍然无法不通过d来进行密文解密的。 安全性分析那么,有无可能在已知n和e的情况下,推导出d?123 1. ed≡1 (mod φ(n))。只有知道e和φ(n),才能算出d。 2. φ(n)=(p-1)(q-1)。只有知道p和q,才能算出φ(n)。 3. n=pq。只有将n因数分解,才能算出p和q。 如果n可以被因数分解,d就可以算出,因此RSA安全性建立在N的因式分解上。大整数的因数分解,是一件非常困难的事情。只要密钥长度足够长,用RSA加密的信息实际上是不能被解破的。 补充模运算规则 模运算加减法: (a + b) mod p = (a mod p + b mod p) mod p (a - b) mod p = (a mod p - b mod p) mod p 模运算乘法: (a b) mod p = (a mod p b mod p) mod p 模运算幂 a ^ b mod p = ((a mod p)^b) mod p 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>其他</category>
</categories>
<tags>
<tag>非对称加密</tag>
<tag>数学</tag>
<tag>RSA算法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币脚本及交易分析 - 智能合约雏形]]></title>
<url>%2F2017%2F11%2F10%2Fbitcoin-script%2F</url>
<content type="text"><![CDATA[大家都有转过账,每笔交易是这样的:张三账上减¥200,李四账上加¥200。在比特币区块链中,交易不是这么简单,交易实际是通过脚本来完成,以承载更多的功能个,这也是为什么比特币被称为是一种“可编程的货币”。本文就来分析一下交易是如何实现可编程的。 未花费的交易输出(UTXO)先引入一个概念:未花费的交易输出——UTXO(Unspent Transaction Output) 其实比特币的交易都是基于UTXO上的,即交易的输入是之前交易未花费的输出,这笔交易的输出可以被当做下一笔新交易的输入。 挖矿奖励属于一个特殊的交易(称为coinbase交易),可以没有输入。UTXO是交易的基本单元,不能再分割。在比特币没有余额概念,只有分散到区块链里的UTXO 随着钱从一个地址被移动到另一个地址的同时形成了一条所有权链,像这样: 比特币脚本比特币交易是首先要提供一个用于解锁UTXO(用私钥去匹配锁定脚本)的脚本(常称为解锁脚本:Signature script),这也叫交易输入,交易的输出则是指向一个脚本(称为锁定脚本:PubKey script),这个脚本表达了:谁的签名(签名是常见形式,并不一定必须是签名)能匹配这个输出地址,钱就支付给谁。 每一个比特币节点会通过同时执行这解锁和锁定脚本(不是当前的锁定脚本,是指上一个交易的锁定脚本)来验证一笔交易,脚本组合结果为真,则为有效交易。 当解锁版脚本与锁定版脚本的设定条件相匹配时,执行组合有效脚本时才会显示结果为真 如最为常见类型的比特币交易脚本(支付到公钥哈希:P2PKH(Pay-to-Public-Key-Hash))组合是这样: 常见交易脚本验证过程比特币交易脚本语言是一种基于逆波兰表示法的基于栈的执行语言(不知道逆波兰和栈的同学去翻大学数据结构课本,你也可跳过这个部分)。 比特币脚本语言包含基本算数计算、基本逻辑(比如if…then)、报错以及返回结果和一些加密指令,不支持循环。想了解更多语言细节可参考:比特币脚本 脚本语言通过从左至右地处理每个项目的方式执行脚本。 下面用两个图说明下常见类型的比特币交易脚本验证执行过程:上图为解锁脚本运行过程(主要是入栈)上图为锁定脚本运行过程(主要是出栈),最后的结果为真,说明交易有效。 交易分析实际上比特币的交易被设计为可以纳入多个输入和输出。 交易结构我们来看看完整的交易结构, 交易的锁定时间定义了能被加到区块链里的最早的交易时间。在大多数交易里,它被设置成0,用来表示立即执行。如果锁定时间不是0并且小于5亿,就被视为区块高度,意指在这个指定的区块高度之前,该交易不会被包含在区块链里。如果锁定时间大于5亿,则它被当作是一个Unix纪元时间戳(从1970年1月1日以来的秒数),并且在这个指定时间之前,该交易不会被包含在区块链里。 交易的数据结构没有交易费的字段,交易费通过所有输入的总和,以及所有输出的总和之间的差来表示,即: 交易费 = 求和(所有输入) - 求和(所有输出) 交易输入结构刚刚我们提过输入需要提供一个解锁脚本,现在来看看一个交易的输入结构: 我们结合整个交易的结构里看输入结构就是这样子: 交易输出结构刚刚我们提过输出是指向一个解锁脚本,具体交易的输出结构为:我们结合整个交易的结构里看输出结构就是这样子: 交易哈希计算在比特币区块结构Merkle 树及简单支付验证分析 讲到区块结构,区块结构包含多个交易的哈希。那么交易哈希是怎么计算的呢? 交易结构各字段序列化为字节数组 把字节数组拼接为支付串 对支付串计算两次SHA256 得到交易hash 了解详情可进一步参考如何计算交易Hash?及如何创建Hash? 现在是不是对完整的交易到区块有了更清晰的认识。 智能合约雏形 - 应用场景说明由于交易是通过脚本来实现,脚本语言可以表达出无数的条件变种。 比特币的脚本目前常用的主要分为两种,一种是常见的P2PKH(支付给公钥哈希),另一种是P2SH(Pay-to-Script-Hash支付脚本哈希)。P2SH支付中,锁定脚本被密码学哈希所取代,当一笔交易试图支付UTXO时,要解锁支付脚本,它必须含有与哈希相匹配的脚本。 这里不展开技术细节,下面说明一些应用场景,以便大家有更直观的认识。 多重签名应用合伙经营中,如只有一半以上的的股东同意签名就可以进行支付,可为公司治理提供管控便利,同时也能有效防范盗窃、挪用和遗失。 用于担保和争端调解,一个买家想和他不认识或不信任的某人交易,在一般情况交易正常进行时,买家不想任何第三方参与。那交易双方可以发起支付,但如果交易出现问题时,那第三方就可以根据裁定,使用自己的签名和裁定认可的一方共同签名来兑现这笔交易。 保证合同保证合同是建造公众商品时的集资办法,公众商品是指一旦建成,任何人都可以免费享受到好处。标准的例子是灯塔,所有人都认同应该建造一个,但是对于个人航海者来说灯塔太贵了,灯塔同时也会方便其他航海者。一个解决方案是向所有人集资,只有当筹集的资金超过所需的建造成本时,每个人才真正付钱,如果集资款不足,则谁都不用付钱。 依靠预言假如老人想让他孙子继承遗产,继承时间是在他死后或者在孙子年满18岁时(也是一个带锁定时间交易),无论哪个条件先满足,他的孙子都可以得到遗产。因为比特币节点可依靠预言对死亡条件进行判断,预言是指具有密钥对的服务器,当用户自定义的表达式被证明是真的,它能按照要求对交易签名。 相信随着区块链的普及,会对未来的交易模式和商业结构带来巨大的影响。不过由于比特币的脚本语言不是图灵完备的,交易模式依旧有限,以太坊就是为解决这一问题而出现,后面我们会有大量介绍以太坊的文章。 参考文献 & 补充阅读 精通比特币 廖雪峰的深入理解比特币交易的脚本 比特币合同 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币脚本</tag>
<tag>交易结构</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币区块结构Merkle树及简单支付验证分析]]></title>
<url>%2F2017%2F11%2F09%2Fmerkle%2F</url>
<content type="text"><![CDATA[在比特币网络中,不是每个节点都有能力储存完整的区块链数据,受限于存储空间的的限制,很多节点是以SPV(Simplified Payment Verification简单支付验证)钱包接入比特币网络,通过简单支付验证可以在不必存储完整区块链下对交易进行验证,本文将分析区块结构Merkle树及如何进行交易验证。 区块结构在工作量证明中出现过一个区块信息截图: 细心的同学一定已经在里面发现了很多未讲的其他信息,如:时间戳,版本号,交易次数,二进制哈希树根(Merkle根)等。 我们来看看一个区块结构到底是怎样的: 如上图(下文称:区块结构图)所示:每个数据区块包含区块头和区块体。区块头封装了当前版本号、前一区块哈希值、当前区块PoW要求的随机数(Nonce)、时间戳、以及Merkle根信息。区块体则包括当前区块经过验证的、 区块创建过程中生成的所有交易记录。这些记录通过 Merkle树的哈希过程生成唯一的Merkle根并记入区块头. 区块哈希值实际上并不包含在区块的数据结构里,其实区块打包时只有区块头被用于计算哈希(从网络被接收时由每个节点计算出来),常说的区块哈希值实际是区块头哈希值,它可以用来唯一、明确地标识一个区块。 区块头是80字节,而平均每个交易至少是250字节,而且平均每个区块包含2000个交易。因此,包含完整交易的区块比区块头的4千倍还要大。SPV节点只下载区块头,不下载包含在每个区块中的交易信息。这样的不含交易信息的区块链,大小只有完整区块链的几千分之1,那SPV节点是如何验证交易的呢? 哈希验证上面先留一个引子,先来回顾下哈希函数,记账原理我们知道原始信息任何微小的变化都会哈希完全不同的哈希值。 简单文件验证我们通常用哈希来检验下载的文件是否完整,我经常看到这样的下载页面:可以看到下载链接后面提供了一个MD5(MD5也是一种Hash算法),这样我们可以在下载之后对文件计算MD5,如果MD5与提供的MD5相等,说明文件有没有被损坏,这个验证过程相信大家都能理解。 多点文件验证(哈希列表)现在复杂度提高一点,在P2P网络中下载时,会把大文件切成小文件,同时从多个机器上下载数据,这个时候怎么验证数据呢? 以BT下载为例,在下载真正的数据之前,我们会先下载一个哈希列表的(每个下小块计算出一个哈希),如果有一个小块数据在传输过程中损坏了,那我只要重新下载这一个数据块就行了,这时有一个问题就出现了,那么多的哈希,怎么保证它们本身(哈希列表中的哈希值)都是正确地呢? 答案是把每个小块数据的哈希值拼到一起,然后对这个长字符串在作一次哈希运算,得到哈希列表的根哈希。只要根哈希校对比一样就说明验哈希列表是正确的,再通过哈希列表校验小数据块,如果所有的小数据块验证通过则说明大文件没有被损坏。 Merkle树验证交易的过程和文件验证很相似,可以人为每个交易是一个小数据块,但比特币使用Merkle树的方式进行验证,相对于哈希列表,Merkle树是一种哈希二叉树,它的明显的一个好处是可以单独拿出一个分支来(作为一个小树)对部分数据进行校验,更加高效。 我们回看下上面的区块结构图,区块体就包含这样一个Merkle树,Merkle树被用来归纳一个区块中的所有交易。 每个叶子节点是每个交易信息的哈希,往上对相邻的两个哈希合并成字符串再哈希,继续类似的操作直到只剩下顶部的一个节点,即Merkle根,存入区块头。 因为Merkle树是二叉树,所以它需要偶数个叶子节点。如果仅有奇数个交易需要归纳,那最后的交易就会被复制一份以构成偶数个叶子节点,这种偶数个叶子节点的树也被称为平衡树。 简化支付验证SPV节点不保存所有交易也不会下载整个区块,仅仅保存区块头,我们来看看它是如何对交易数据进行验证的。 假如要验证区块结构图中交易6,SPV节点会通过向相邻节点索要(通过Merkleblock消息)包括从交易6哈希值沿Merkle树上溯至区块头根哈希处的哈希序列 (即哈希节点6, 5, 56, 78, 5678, 1234 1~8 - 称为认证路径) 来确认交易的存在性和正确性。(在N个交易组成的区块中确认任一交易只需要计算log2(N)个字节的哈希值,非常快速高效) 大家明白了吗? 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>区块结构</tag>
<tag>Merkle树</tag>
<tag>SPV简单支付验证</tag>
</tags>
</entry>
<entry>
<title><![CDATA[分析比特币网络:一种去中心化、点对点的网络架构]]></title>
<url>%2F2017%2F11%2F07%2Fbitcoin-p2p%2F</url>
<content type="text"><![CDATA[比特币采用了基于互联网的点对点(P2P:peer-to-peer)分布式网络架构。比特币网络可以认为是按照比特币P2P协议运行的一系列节点的集合。本文来分析下比特币网络,了解它跟传统中心化网络的区别,以及比特币网络是如何发现相邻节点的。 中心化网络为了更好的理解P2P网络,我们先来看看传统的中心化模型: 这是一种典型的星型(“中心化”)结构,我们常见B/S及C/S网络架构就是这种模型,C1 、C2 、C3等之间没法直接的连接,C节点如果要连接必须要通过中心化S节点做为桥梁。中心化节点充当服务者、中介作用,比如我们没有办法把资金直接从一个人转移给另一个人,必须通过银行这个中介。 P2P网络P2P网络是指位于同一网络中的每台计算机都彼此对等,各个节点共同提供网络服务,不存在任何“特殊”节点,每个网络节点以扁平(flat)的拓扑结构相互连通。 对比中心化网络,在P2P网络中不存在任何服务端(server)、中央化的服务。P2P网络的节点之间交互连接、协同,每个节点在对外提供服务的同时也使用网络中其他节点所提供的服务,每个节点即是服务端又是客户端。P2P网络模型除应用于比特币网络,使用广泛的BT下载就是基于P2P网络。 P2P网络不仅仅去除了中心化带来的风险(中心化可能作恶),还可以提高传输的效率。(中心化网络当能也有优点) 如何发现节点既然每个网络节点都是平等的(是指在网络层面上节点是平等的,但各节点在功能上可以有不同的分工, 如钱包节点、挖矿节点等),不存在任何“特殊”中心节点,那么当新的网络节点启动后,它是如何跟其他的节点建立连接,从而加入到比特币网络呢? 在中心化网络中,新加入的节点只要连接“特殊”的中心节点就可以加入网络。 为了能够加入到比特币网络,比特币客户端会做一下几件事情: 节点会记住它最近成功连接的网络节点,当重新启动后它可以迅速与先前的对等节点网络重新建立连接。 节点会在失去已有连接时尝试发现新节点。 当建立一个或多个连接后,节点将一条包含自身IP地址消息发送给其相邻节点。相邻节点再将此消息依次转发给它们各自的相邻节点,从而保证节点信息被多个节点所接收、保证连接更稳定。 新接入的节点可以向它的相邻节点发送获取地址getaddr消息,要求它们返回其已知对等节点的IP地址列表。节点可以找到需连接到的对等节点。 在节点启动时,可以给节点指定一个正活跃节点IP, 如果没有,客户端也维持一个列表,列出了那些长期稳定运行的节点。这样的节点也被称为种子节点(其实和BT下载的种子文件道理是一样的),就可以通过种子节点来快速发现网络中的其他节点。 节点通信简述比特币节点通常采用TCP协议、使用8333端口与相邻节点建立连接, 建立连接时也会有认证“握手”的通信过程,用来确定协议版本,软件版本,节点IP,区块高度等。 当节点连接到相邻节点后,接着就开始跟相邻节点同步区块链数据(轻量级钱包应用其实不会同步所有区块数据),节点们会交换一个getblocks消息,它包含本地区块链最顶端的哈希值。如果某个节点识别出它接收到的哈希值并不属于顶端区块,而是属于一个非顶端区块的旧区块,就说其自身的本地区块链比其他节点的区块链更长,并告诉其他节点需要补充区块,其他节点发送getdata消息来请求区块,验证后更新到本地区块链中。 我们还为初学者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>p2p</tag>
<tag>去中心化</tag>
<tag>比特币网络</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币如何挖矿(挖矿原理)-工作量证明]]></title>
<url>%2F2017%2F11%2F04%2Fbitcoin-pow%2F</url>
<content type="text"><![CDATA[在区块链记账原理 一篇,我们了解到记账是把交易记录、交易时间、账本序号、上一个Hash值等信息计算Hash打包的过程。我们知道所有的计算和存贮是需要消耗计算机资源的,既然要付出成本,那节点为什么还要参与记账呢?在中本聪(比特币之父)的设计里,完成记账的节点可以获得系统给与的一定数量的比特币奖励,这个奖励的过程也就是比特币的发行过程,因此大家形象的把记账称为“挖矿”,本文将详细讨论这个过程。 记账工作由于记账是有奖励的,每次记账都可以给自己凭空增加一定数量的个比特币(当前是12.5比特币,博文写作时每个比特币是4万人民币以上,大家可以算算多少钱),因此就出现大家争相记账,大家一起记账就会引起问题:出现记账不一致的问题,比特币系统引入工作量证明来解决这个问题,规则如下: 一段时间内(10分钟左右,具体时间会与密码学难题难度相互影响)只有一人可以记账成功 通过解决密码学难题(即工作量证明)竞争获得唯一记账权 其他节点复制记账结果 不过在进行工作量证明之前,记账节点会做进行如下准备工作: 收集广播中还没有被记录账本的原始交易信息 检查每个交易信息中付款地址有没有足够的余额 验证交易是否有正确的签名 把验证通过的交易信息进行打包记录 添加一个奖励交易:给自己的地址增加12.5比特币 如果节点争夺记账权成功的话,就可以得到12.5比特币的奖励。 工作量证明区块链记账原理我们了解到,每次记账的时候会把上一个块的Hash值和当前的账页信息一起作为原始信息进行Hash。如果仅仅是这样,显然每个人都可以很轻松的完成记账。为了保证10分钟左右只有一个人可以记账,就必须要提高记账的难度,使得Hash的结果必须以若干个0开头。同是为了满足这个条件,在进行Hash时引入一个随机数变量。用伪代码表示一下:1Hash(上一个Hash值,交易记录集) = 456635BCD 1Hash(上一个Hash值,交易记录集,随机数) = 0000aFD635BCD 我们知道改变Hash的原始信息的任何一部分,Hash值也会随之不断的变化,因此在运算Hash时,不断的改变随机数的值,总可以找的一个随机数使的Hash的结果以若干个0开头(下文把这个过程称为猜谜),率先找到随机数的节点就获得此次记账的唯一记账权。 计算量分析(这部分可选阅读)我们简单分析下记账难度有多大,Hash值是由数字和大小写字母构成的字符串,每一位有62种可能性(可能为26个大写字母、26个小写字母,10个数字中任一个),假设任何一个字符出现的概率是均等的,那么第一位为0的概率是1/62(其他位出现什么字符先不管),理论上需要尝试62次Hash运算才会出现一次第一位为0的情况,如果前两2位为0,就得尝试62的平方次Hash运算,以n个0开头就需要尝试62的n次方次运算。我们结合当前实际区块#493050信息来看看: 注:数据来源于https://blockchain.info我们可以看到Hash值以18个0开头,理论上需要尝试62的18次方次,这个数是非常非常巨大的,我已经算不清楚了,应该是亿亿级别以上了。如此大的计算量需要投入大量的计算设备、电力等,目前应该没有单矿工独立参与挖矿了,基本都是由矿工联合起来组成矿池进行挖矿(矿池里的矿工按算力百分比来分收益)。 从经济的角度讲,只有挖矿还有收益(比特币价格不断上涨也让收益变大),就会有新的矿工加入,从而加剧竞争,提高算力难度,挖矿就需要耗费更多的运算和电力,相互作用引起最终成本会接近收益。 题外话:国内由于电力成本较低,相对收益更高,中国的算力占整个网络的一半以上 验证在节点成功找到满足的Hash值之后,会马上对全网进行广播打包区块,网络的节点收到广播打包区块,会立刻对其进行验证。 如果验证通过,则表明已经有节点成功解迷,自己就不再竞争当前区块打包,而是选择接受这个区块,记录到自己的账本中,然后进行下一个区块的竞争猜谜。网络中只有最快解谜的区块,才会添加的账本中,其他的节点进行复制,这样就保证了整个账本的唯一性。 假如节点有任何的作弊行为,都会导致网络的节点验证不通过,直接丢弃其打包的区块,这个区块就无法记录到总账本中,作弊的节点耗费的成本就白费了,因此在巨大的挖矿成本下,也使得矿工自觉自愿的遵守比特币系统的共识协议,也就确保了整个系统的安全。 进阶阅读比特币区块结构Merkle树及简单支付验证分析,可以详细了解区块结构如何验证交易。 说明矿工的收益其实不仅仅包含新发行的12.5比特币奖励,同时还有交易费收益(本文忽略一些细节是为了让主干更清晰)。 有兴趣的同学可以看看图中区块都包含了那些信息,红箭头标示出的是本文涉及的信息。 本文中有提到共识协议,比特币共识协议主要是由工作量证明和最长链机制 两部分组成,请阅读比特币如何达成共识 - 最长链的选择。 我们还为初学者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币</tag>
<tag>挖矿</tag>
<tag>工作量证明</tag>
<tag>共识机制</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币所有权及隐私问题-非对称加密应用]]></title>
<url>%2F2017%2F11%2F02%2Fbitcoin-own%2F</url>
<content type="text"><![CDATA[比特币系统是如何确定某个账户的比特币是属于谁的?谁可以支付这个账户比特币? 如果你对这个问题还不是很明白,那就一起来看看吧。 银行系统我们先来回顾下现实的银行系统: 首先我们需要把我们的个人信息(如身份证)给银行,银行给我们开立相对应的账户,银行在开户的时候确立了对账户的所有权。 进行支付的时候,银行对交易双方完成转账(银行在开户的时候已经知道我们对应的账户)。 同时银行会对账户信息进行保密(这点其实不能保证)。 匿名账本那么比特币如何在没有第三方银行的参与下,在确保隐私的同时如何确定账户所有权的呢? 实际上比特币的账户是用地址来表示,账本上不显示个人信息,转账是把比特币从一个地址转移到另一个地址。转账记录如这样:12345{ "付款地址":"2A39CBa2390FDe" "收款地址":"AAC9CBa239aFcc" "金额":"0.2btc"} 接下来问题就变为了 谁有权用某个地址进行付款。 支付和所有权 实际是同一个问题,如果此比特币只有我可以用来支付,那么说明我拥有所有权 地址与私钥比特币的解决方案是,谁拥有某个地址的私钥(如果完全没有加密概念的人,可以简单的把私钥当作密码),谁就能用这个地址进行支付。(所以私钥一定保管好,如果私钥泄漏,比特币就可能丢失) 比特币地址和私钥是一个非对称的关系,私钥经过一系列运算(其中有两次Hash)之后,可以得到地址, 但是无法从地址反推得到私钥。1234地址: 2A39CBa2390FDe私钥: sdgHsdniNIhdsgaKIhkgnakgaihNKHIskdgalHash(Hash(fun(sdgHsdniNIhdsgaKIhkgnakgaihNKHIskdgal))) -> 2A39CBa2390FDe 银行系统银行账号和密码是完全独立的,无法互相推导,转出时需要同时验证账号和密码 还是上面交易的例子:12345{ "付款地址":"2A39CBa2390FDe", "收款地址":"AAC9CBa239aFcc", "金额":"0.2btc"} 只有拥有地址2A39CBa2390FDe的私钥才能进行支付。 非对称加密技术这个时候问题就变为了,如何证明你拥有某个地址的私钥(在不泄漏私钥的情况下)。 对交易信息进行签名实际在签名之前,会先对交易信息进行Hash运算得到摘要信息,然后对摘要信息进行签名。过程大概是这样:1.对交易进行hash, 得到一个摘要信息(Hash值) 12345hash(' {"付款地址":"2A39CBa2390FDe", "收款地址":"AAC9CBa239aFcc", "金额":"0.2btc" }') -> 8aDB23CDEA6 2.用私钥对交易摘要进行签名(付款方在安全的环境下进行,以避免私钥泄密), 用代码表示大概是这样。1234#参数1为交易摘要#参数2为私钥#返回签名信息sign("8aDB23CDEA6", "J78sknJhidhLIqdngalket") -> "3cdferdadgadg" 广播在签名运算之后,付款节点就开始在全网进行广播:我支付了0.2btc到AAC9CBa239aFcc,签名信息是3cdferdadgadg,你们来确认一下吧。 广播过程实际上是发信息到相连的其它节点,其它节点在验证通过后再转发到与之相连的节点,这样的扩散过程。 广播的信息包含了交易原始信息和签名信息 验证其它节点在收到广播信息之后,会验证签名信息是不是付款方用私钥对交易原始信息签名产生的,如果验证通过说明确实是付款方本人发出的交易,说明交易有效,才会记录到账本中去。(实际还会验证付款账号有没有足够的余额,我们暂时忽略这点)验证过程实际是签名过程的逆运算,用代码表示大概过程是这样的: 1234#参数1为签名信息#参数2为付款方地址#返回交易摘要verify("3cdferdadgadg", "2A39CBa2390FDe") -> "8aDB23CDEA6" 如果验证输出的信息和原始交易信息的hash一致,则验证通过,记录账本,用代码表示大概是这样: 12345678if(verify("3cdferdadgadg", "2A39CBa2390FDe") == hash('{"付款地址":"2A39CBa2390FDe", "收款地址":"AAC9CBa239aFcc", "金额":"0.2btc"}')) : # 写入账本 # 广播else: # donothing 大家可以理解为付款地址为公钥,签名过程即为用私钥对交易摘要的加密过程,验证过程为用公钥解密的过程(为方便大家理解,严格来讲是不准确的)。 补充说明上面为了更好的理解,我对一些信息进行了简化。 比特币系统使用了椭圆曲线签名算法,算法的私钥由32个字节随机数组成,通过私钥可以计算出公钥,公钥经过一序列哈希算法和编码算法得到比特币地址,地址也可以理解为公钥的摘要。 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>非对称加密</tag>
<tag>比特币</tag>
<tag>所有权问题</tag>
<tag>隐私问题</tag>
</tags>
</entry>
<entry>
<title><![CDATA[用Python从零开始创建区块链]]></title>
<url>%2F2017%2F10%2F27%2Fbuild_blockchain_by_python%2F</url>
<content type="text"><![CDATA[本文主要内容翻译自Learn Blockchains by Building One本文原始链接,转载请注明出处。作者认为最快的学习区块链的方式是自己创建一个,本文就跟随作者用Python来创建一个区块链。 对数字货币的崛起感到新奇的我们,并且想知道其背后的技术——区块链是怎样实现的。 但是完全搞懂区块链并非易事,我喜欢在实践中学习,通过写代码来学习技术会掌握得更牢固。通过构建一个区块链可以加深对区块链的理解。 准备工作本文要求读者对Python有基本的理解,能读写基本的Python,并且需要对HTTP请求有基本的了解。 我们知道区块链是由区块的记录构成的不可变、有序的链结构,记录可以是交易、文件或任何你想要的数据,重要的是它们是通过哈希值(hashes)链接起来的。 如果你还不是很了解哈希,可以查看这篇文章 环境准备环境准备,确保已经安装Python3.6+, pip , Flask, requests安装方法:1pip install Flask==0.12.2 requests==2.18.4 同时还需要一个HTTP客户端,比如Postman,cURL或其它客户端。 参考源代码(原代码在我翻译的时候,无法运行,我fork了一份,修复了其中的错误,并添加了翻译,感谢star) 开始创建Blockchain新建一个文件 blockchain.py,本文所有的代码都写在这一个文件中,可以随时参考源代码 Blockchain类首先创建一个Blockchain类,在构造函数中创建了两个列表,一个用于储存区块链,一个用于储存交易。 以下是Blockchain类的框架:12345678910111213141516171819202122class Blockchain(object): def __init__(self): self.chain = [] self.current_transactions = [] def new_block(self): # Creates a new Block and adds it to the chain pass def new_transaction(self): # Adds a new transaction to the list of transactions pass @staticmethod def hash(block): # Hashes a Block pass @property def last_block(self): # Returns the last Block in the chain pass Blockchain类用来管理链条,它能存储交易,加入新块等,下面我们来进一步完善这些方法。 块结构每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明(稍后解释)以及前一个区块的Hash值。 以下是一个区块的结构:12345678910111213block = { 'index': 1, 'timestamp': 1506057125.900785, 'transactions': [ { 'sender': "8527147fe1f5426f9dd545de4b27ee00", 'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f", 'amount': 5, } ], 'proof': 324984774000, 'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"} 到这里,区块链的概念就清楚了,每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。不理解的话,慢慢消化,可参考区块链记账原理 加入交易接下来我们需要添加一个交易,来完善下new_transaction方法12345678910111213141516171819class Blockchain(object): ... def new_transaction(self, sender, recipient, amount): """ 生成新交易信息,信息将加入到下一个待挖的区块中 :param sender: <str> Address of the Sender :param recipient: <str> Address of the Recipient :param amount: <int> Amount :return: <int> The index of the Block that will hold this transaction """ self.current_transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 方法向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。 创建新块当Blockchain实例化后,我们需要构造一个创世块(没有前区块的第一个区块),并且给它加上一个工作量证明。每个区块都需要经过工作量证明,俗称挖矿,稍后会继续讲解。 为了构造创世块,我们还需要完善new_block(), new_transaction() 和hash() 方法: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566import hashlibimport jsonfrom time import timeclass Blockchain(object): def __init__(self): self.current_transactions = [] self.chain = [] # Create the genesis block self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): """ 生成新块 :param proof: <int> The proof given by the Proof of Work algorithm :param previous_hash: (Optional) <str> Hash of previous Block :return: <dict> New Block """ block = { 'index': len(self.chain) + 1, 'timestamp': time(), 'transactions': self.current_transactions, 'proof': proof, 'previous_hash': previous_hash or self.hash(self.chain[-1]), } # Reset the current list of transactions self.current_transactions = [] self.chain.append(block) return block def new_transaction(self, sender, recipient, amount): """ 生成新交易信息,信息将加入到下一个待挖的区块中 :param sender: <str> Address of the Sender :param recipient: <str> Address of the Recipient :param amount: <int> Amount :return: <int> The index of the Block that will hold this transaction """ self.current_transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 @property def last_block(self): return self.chain[-1] @staticmethod def hash(block): """ 生成块的 SHA-256 hash值 :param block: <dict> Block :return: <str> """ # We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest() 通过上面的代码和注释可以对区块链有直观的了解,接下来我们看看区块是怎么挖出来的。 理解工作量证明新的区块依赖工作量证明算法(PoW)来构造。PoW的目标是找出一个符合特定条件的数字,这个数字很难计算出来,但容易验证。这就是工作量证明的核心思想。 为了方便理解,举个例子: 假设一个整数 x 乘以另一个整数 y 的积的 Hash 值必须以 0 结尾,即 hash(x * y) = ac23dc…0。设变量 x = 5,求 y 的值? 用Python实现如下: 123456from hashlib import sha256x = 5y = 0 # y未知while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0": y += 1print(f'The solution is y = {y}') 结果是y=21. 因为:1hash(5 * 21) = 1253e9373e...5e3600155e860 在比特币中,使用称为Hashcash的工作量证明算法,它和上面的问题很类似。矿工们为了争夺创建区块的权利而争相计算结果。通常,计算难度与目标字符串需要满足的特定字符的数量成正比,矿工算出结果后,会获得比特币奖励。当然,在网络上非常容易验证这个结果。 实现工作量证明让我们来实现一个相似PoW算法,规则是:寻找一个数 p,使得它与前一个区块的 proof 拼接成的字符串的 Hash 值以 4 个零开头。 12345678910111213141516171819202122232425262728293031323334353637import hashlibimport jsonfrom time import timefrom uuid import uuid4class Blockchain(object): ... def proof_of_work(self, last_proof): """ 简单的工作量证明: - 查找一个 p' 使得 hash(pp') 以4个0开头 - p 是上一个块的证明, p' 是当前的证明 :param last_proof: <int> :return: <int> """ proof = 0 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof @staticmethod def valid_proof(last_proof, proof): """ 验证证明: 是否hash(last_proof, proof)以4个0开头? :param last_proof: <int> Previous Proof :param proof: <int> Current Proof :return: <bool> True if correct, False if not. """ guess = f'{last_proof}{proof}'.encode() guess_hash = hashlib.sha256(guess).hexdigest() return guess_hash[:4] == "0000" 衡量算法复杂度的办法是修改零开头的个数。使用4个来用于演示,你会发现多一个零都会大大增加计算出结果所需的时间。 现在Blockchain类基本已经完成了,接下来使用HTTP requests来进行交互。 Blockchain作为API接口我们将使用Python Flask框架,这是一个轻量Web应用框架,它方便将网络请求映射到 Python函数,现在我们来让Blockchain运行在基于Flask web上。 我们将创建三个接口: /transactions/new 创建一个交易并添加到区块 /mine 告诉服务器去挖掘新的区块 /chain 返回整个区块链 创建节点我们的“Flask服务器”将扮演区块链网络中的一个节点。我们先添加一些框架代码: 1234567891011121314151617181920212223242526272829303132333435363738394041import hashlibimport jsonfrom textwrap import dedentfrom time import timefrom uuid import uuid4from flask import Flaskclass Blockchain(object): ...# Instantiate our Nodeapp = Flask(__name__)# Generate a globally unique address for this nodenode_identifier = str(uuid4()).replace('-', '')# Instantiate the Blockchainblockchain = Blockchain()@app.route('/mine', methods=['GET'])def mine(): return "We'll mine a new Block" @app.route('/transactions/new', methods=['POST'])def new_transaction(): return "We'll add a new transaction"@app.route('/chain', methods=['GET'])def full_chain(): response = { 'chain': blockchain.chain, 'length': len(blockchain.chain), } return jsonify(response), 200if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) 简单的说明一下以上代码: 第15行: 创建一个节点. 第18行: 为节点创建一个随机的名字. 第21行: 实例Blockchain类. 第24–26行: 创建/mine GET接口。 第28–30行: 创建/transactions/new POST接口,可以给接口发送交易数据. 第32–38行: 创建 /chain 接口, 返回整个区块链。 第40–41行: 服务运行在端口5000上. 发送交易发送到节点的交易数据结构如下:12345{ "sender": "my address", "recipient": "someone else's address", "amount": 5} 之前已经有添加交易的方法,基于接口来添加交易就很简单了 123456789101112131415161718192021222324import hashlibimport jsonfrom textwrap import dedentfrom time import timefrom uuid import uuid4from flask import Flask, jsonify, request...@app.route('/transactions/new', methods=['POST'])def new_transaction(): values = request.get_json() # Check that the required fields are in the POST'ed data required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'Missing values', 400 # Create a new Transaction index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) response = {'message': f'Transaction will be added to Block {index}'} return jsonify(response), 201 挖矿挖矿正是神奇所在,它很简单,做了一下三件事: 计算工作量证明PoW 通过新增一个交易授予矿工(自己)一个币 构造新区块并将其添加到链中 123456789101112131415161718192021222324252627282930313233343536import hashlibimport jsonfrom time import timefrom uuid import uuid4from flask import Flask, jsonify, request...@app.route('/mine', methods=['GET'])def mine(): # We run the proof of work algorithm to get the next proof... last_block = blockchain.last_block last_proof = last_block['proof'] proof = blockchain.proof_of_work(last_proof) # 给工作量证明的节点提供奖励. # 发送者为 "0" 表明是新挖出的币 blockchain.new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Forge the new Block by adding it to the chain block = blockchain.new_block(proof) response = { 'message': "New Block Forged", 'index': block['index'], 'transactions': block['transactions'], 'proof': block['proof'], 'previous_hash': block['previous_hash'], } return jsonify(response), 200 注意交易的接收者是我们自己的服务器节点,我们做的大部分工作都只是围绕Blockchain类方法进行交互。到此,我们的区块链就算完成了,我们来实际运行下 运行区块链你可以使用cURL 或Postman 去和API进行交互 启动server:12$ python blockchain.py* Runing on http://127.0.0.1:5000/ (Press CTRL+C to quit) 让我们通过请求 http://localhost:5000/mine 来进行挖矿 通过post请求,添加一个新交易 如果不是使用Postman,则用一下的cURL语句也是一样的:12345$ curl -X POST -H "Content-Type: application/json" -d '{ "sender": "d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address", "amount": 5}' "http://localhost:5000/transactions/new" 在挖了两次矿之后,就有3个块了,通过请求 http://localhost:5000/chain 可以得到所有的块信息。 1234567891011121314151617181920212223242526272829303132333435363738{ "chain": [ { "index": 1, "previous_hash": 1, "proof": 100, "timestamp": 1506280650.770839, "transactions": [] }, { "index": 2, "previous_hash": "c099bc...bfb7", "proof": 35293, "timestamp": 1506280664.717925, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] }, { "index": 3, "previous_hash": "eff91a...10f2", "proof": 35089, "timestamp": 1506280666.1086972, "transactions": [ { "amount": 1, "recipient": "8bbcb347e0634905b0cac7955bae152b", "sender": "0" } ] } ], "length": 3} 一致性(共识)我们已经有了一个基本的区块链可以接受交易和挖矿。但是区块链系统应该是分布式的。既然是分布式的,那么我们究竟拿什么保证所有节点有同样的链呢?这就是一致性问题,我们要想在网络上有多个节点,就必须实现一个一致性的算法。 注册节点在实现一致性算法之前,我们需要找到一种方式让一个节点知道它相邻的节点。每个节点都需要保存一份包含网络中其它节点的记录。因此让我们新增几个接口: /nodes/register 接收URL形式的新节点列表 /nodes/resolve 执行一致性算法,解决任何冲突,确保节点拥有正确的链 我们修改下Blockchain的init函数并提供一个注册节点方法:1234567891011121314151617181920...from urllib.parse import urlparse...class Blockchain(object): def __init__(self): ... self.nodes = set() ... def register_node(self, address): """ Add a new node to the list of nodes :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000' :return: None """ parsed_url = urlparse(address) self.nodes.add(parsed_url.netloc) 我们用 set 来储存节点,这是一种避免重复添加节点的简单方法。 实现共识算法前面提到,冲突是指不同的节点拥有不同的链,为了解决这个问题,规定最长的、有效的链才是最终的链,换句话说,网络中有效最长链才是实际的链。 我们使用一下的算法,来达到网络中的共识12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667...import requestsclass Blockchain(object) ... def valid_chain(self, chain): """ Determine if a given blockchain is valid :param chain: <list> A blockchain :return: <bool> True if valid, False if not """ last_block = chain[0] current_index = 1 while current_index < len(chain): block = chain[current_index] print(f'{last_block}') print(f'{block}') print("\n-----------\n") # Check that the hash of the block is correct if block['previous_hash'] != self.hash(last_block): return False # Check that the Proof of Work is correct if not self.valid_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True def resolve_conflicts(self): """ 共识算法解决冲突 使用网络中最长的链. :return: <bool> True 如果链被取代, 否则为False """ neighbours = self.nodes new_chain = None # We're only looking for chains longer than ours max_length = len(self.chain) # Grab and verify the chains from all the nodes in our network for node in neighbours: response = requests.get(f'http://{node}/chain') if response.status_code == 200: length = response.json()['length'] chain = response.json()['chain'] # Check if the length is longer and the chain is valid if length > max_length and self.valid_chain(chain): max_length = length new_chain = chain # Replace our chain if we discovered a new, valid chain longer than ours if new_chain: self.chain = new_chain return True return False 第一个方法 valid_chain() 用来检查是否是有效链,遍历每个块验证hash和proof. 第2个方法 resolve_conflicts() 用来解决冲突,遍历所有的邻居节点,并用上一个方法检查链的有效性, 如果发现有效更长链,就替换掉自己的链 让我们添加两个路由,一个用来注册节点,一个用来解决冲突。12345678910111213141516171819202122232425262728293031323334@app.route('/nodes/register', methods=['POST'])def register_nodes(): values = request.get_json() nodes = values.get('nodes') if nodes is None: return "Error: Please supply a valid list of nodes", 400 for node in nodes: blockchain.register_node(node) response = { 'message': 'New nodes have been added', 'total_nodes': list(blockchain.nodes), } return jsonify(response), 201@app.route('/nodes/resolve', methods=['GET'])def consensus(): replaced = blockchain.resolve_conflicts() if replaced: response = { 'message': 'Our chain was replaced', 'new_chain': blockchain.chain } else: response = { 'message': 'Our chain is authoritative', 'chain': blockchain.chain } return jsonify(response), 200 你可以在不同的机器运行节点,或在一台机机开启不同的网络端口来模拟多节点的网络,这里在同一台机器开启不同的端口演示,在不同的终端运行一下命令,就启动了两个节点:http://localhost:5000 和 http://localhost:5001 12pipenv run python blockchain.pypipenv run python blockchain.py -p 5001 然后在节点2上挖两个块,确保是更长的链,然后在节点1上访问接口/nodes/resolve ,这时节点1的链会通过共识算法被节点2的链取代。 好啦,你可以邀请朋友们一起来测试你的区块链 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>其他</category>
</categories>
<tags>
<tag>python</tag>
<tag>创建区块链</tag>
<tag>翻译</tag>
</tags>
</entry>
<entry>
<title><![CDATA[区块链记账原理]]></title>
<url>%2F2017%2F10%2F25%2Fwhatbc%2F</url>
<content type="text"><![CDATA[区块链(1.0)是一个基于密码学安全的分布式账本,是一个方便验证,不可篡改的账本。 通常认为与智能合约相结合的区块链为区块链2.0, 如以太坊是典型的区块链2.0 很多人只了解过比特币,不知道区块链,比特币实际是一个使用了区块链技术的应用,只是比特币当前太热,把区块链技术的光芒给掩盖了。区块链才是未来,期望各位开发人员少关心币价,多关心技术。 本文将讲解区块链1.0技术是如何实现的。 哈希函数在讲区块链记账之前,先说明一下哈希函数。哈希函数:Hash(原始信息) = 摘要信息原始信息可以是任意的信息, hash之后会得到一个简短的摘要信息 哈希函数有几个特点: 同样的原始信息用同一个哈希函数总能得到相同的摘要信息 原始信息任何微小的变化都会哈希出面目全非的摘要信息 从摘要信息无法逆向推算出原始信息 举例说明:Hash(张三借给李四100万,利息1%,1年后还本息 …..) = AC4635D34DEF账本上记录了AC4635D34DEF这样一条记录。 可以看出哈希函数有4个作用: 简化信息很好理解,哈希后的信息变短了。 标识信息可以使用AC4635D34DEF来标识原始信息,摘要信息也称为原始信息的id。 隐匿信息账本是AC4635D34DEF这样一条记录,原始信息被隐匿。 验证信息假如李四在还款时欺骗说,张三只借给李四10万,双方可以用AC4635D34DEF来验证原始信息 哈希函数的这4个作用在区块链技术里有广泛的运用。(哈希函数是一组函数或算法,以后会发文章专门介绍哈希) 区块链记账方法假设有一个账页序号为0的账页交易记录如下: 账号 入账 出账 余额 备注说明 王二 100 190 收到xxx货款 张三 100 30 xxxx 李四 120 90 170 xxxx 记账时间为:2017-10-22 10:22:02 区块链在记账是会把账页信息(包含序号、记账时间、交易记录)作为原始信息进行Hash, 得到一个Hash值,如:787635ACD, 用函数表示为:1Hash(序号0、记账时间、交易记录) = 787635ACD 账页信息和Hash值组合在一起就构成了第一个区块。 比特币系统里约10分钟记一次账,即每个区块生成时间大概间隔10分钟 在记第2个账页的时候,会把上一个块的Hash值和当前的账页信息一起作为原始信息进行Hash,即: 1Hash(上一个Hash值、序号1、记账时间、交易记录) = 456635BCD 这样第2个区块不仅包含了本账页信息,还间接的包含了第一个区块的信息。依次按照此方法继续记账,则最新的区块总是间接包含了所有之前的账页信息。 所有这些区块组合起来就形成了区块链,这样的区块链就构成了一个便于验证(只要验证最后一个区块的Hash值就相当于验证了整个账本),不可更改(任何一个交易信息的更改,会让所有之后的区块的Hash值发生变化,这样在验证时就无法通过)的总账本。 记账有成本,想了解节点为什么要记账,请看这篇:在比特币如何挖矿-工作量证明 我们还为区块链技术爱好者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程及以太坊智能合约开发。 深入浅出区块链系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>区块链</tag>
<tag>原理</tag>
<tag>哈希</tag>
<tag>如何记账</tag>
</tags>
</entry>
<entry>
<title><![CDATA[比特币是什么]]></title>
<url>%2F2017%2F10%2F23%2Fwhatisbitcoin%2F</url>
<content type="text"><![CDATA[对于比特币也许一千个人有一千种理解。本文作为入门篇(写给完全没有了解过比特币概念的新手,老手可忽略),我尽量用简单易懂的语言来介绍比特币。 到底什么是比特币,它到底是怎么运行的呢。 比特币是什么 比特币是一种基于分布式网络的数字货币。比特币系统(广义的比特币)则是用来构建这种数字货币的网络系统,是一个分布式的点对点网络系统。 本文主要讲解狭义的比特币概念。 数字货币是什么凯恩斯在《货币论》上讲,货币可以承载债务,价格的一般等价物。货币的本质是等价物,它可以是任何东西,如:一张纸,一个数字,只要人们认可它的价值。人民币,美元等作为国家信用货币,其价值由国家主权背书。而数字货币是一种不依赖信用和实物的新型货币,它的价值由大家的共识决定。比特币就是一种数字货币。(我们在网银,微信,支付宝的金额,准确来讲,它是信用货币的数字化,不是数字货币,不过央行也在研究比特币,准备发行数字货币) 运行原理大家知道,在银行系统的数据库里记录着跟我们身份id对应的财产,下文称这样的记录为账本,如张三的卡10月1日转入1w, 余额10w。比特币系统也同样有这样的账本,不同银行由单一的组织负责记录,比特币的记账由所有运行系统的人(即节点,可以简单理解为一台电脑)共同参与记录,每个节点都保存(同步)一份完整的账本。同时使用简单多数原则,来保证账本的一致性。举个例子:如果有人在自己电脑上把自己的余额从1万改为1百万,他这个账本和大多数人的账本不一致,就会被比特币系统认为是无效的。 比特币使用区块链技术来支撑整个系统的运行,有兴趣的同学,可以详细阅读下这几篇博文: 区块链记账原理 比特币所有权问题 比特币如何挖矿 还可进阶阅读:分析比特币网络:一种去中心化、点对点的网络架构,可以详细了解比特币网络。比特币区块结构Merkle树及简单支付验证分析,可以详细了解区块结构如何验证交易。 我们还为初学者提供了系统的区块链视频教程,觉得文章学习不过瘾的同学可以戳入门视频教程. 深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。我的知识星球为各位解答区块链技术问题,欢迎加入讨论。]]></content>
<categories>
<category>比特币</category>
</categories>
<tags>
<tag>比特币</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前言-如何学习区块链]]></title>
<url>%2F2017%2F10%2F20%2F%E5%89%8D%E8%A8%80%2F</url>
<content type="text"><![CDATA[区块链未来3到5年应该会出现行业井喷式发展,相应所需的人才必定水涨船高,每一个开发人员都不应该错过这样的机会。区块链涉及的技术很多,很多开发人员看了一些资料后,感觉好像懂了,又好像没懂。如何系统的学习区块链技术,是很多想从事区块链开发的程序员的问题,我们来一起讨论下,希望可以帮助更多的人掌握区块链开发技术。 确定方向从事区块链开发也有很多方向,如:区块链应用开发人员、区块链架构师、底层核心开发、共识算法研究等等。 方向不同,需要学习的内容就不一样,如果做基于区块链应用开发,只需要了解一门编程语言(nodejs, Go, Python, C++ 等), 大概了解区块链的原理,不一定要深入,当能理解越深入开发应用就越顺。如果做区块链基础开发,就需要了解加密算法,P2P通信,共识算法等等。 投入时间学习-动手实践由于区块链涉及的技术很多,可以相对各个技术有一个概念了解,再逐步深入原理。 当你在学习了解概念的时候,必定会产生很多疑问, 例如我们经常可以看到一句: 比特币的共识机制是通过工作量证明(POW)来实现的。就有了新疑问:什么是工作量证明,进一步了解,它是通过验证的一个特定结果,就能确认参与者完成了相应的工作量(不理解没关系,可以简单为,张三考试考了100分,就确认张三肯定好好学习了)。这时又有了新的疑问,比特币在验证什么样的结果,这时你又需要了解密码学和Hash。 逐步深入的过程也是解答疑问的过程,需要我们善用Google搜索。 如果觉得已进理解一个概念或原理时,可以尝试动手实现它,如在理解挖矿后,可以写代码模拟挖矿过程。 学习是一个长期的过期,没有捷径,必须得多读书,读代码,写代码。 学习资源介绍下面是一些学习资源的介绍,相信对大家有帮助 比特币:一种点对点的电子现金系统-英文 比特币:一种点对点的电子现金系统-中文 以太坊白皮书-英文 以太坊白皮书-中文 区块链技术指南-电子书 区块链开发指南-纸书 比特币 - 官网 以太坊 - github 超级账本Hyperledger ETHFANS - 社区 深入浅出区块链]]></content>
<categories>
<category>其他</category>
</categories>
<tags>
<tag>如何学习</tag>
</tags>
</entry>
</search>