以太坊教程:搭建环境、编写编译一个智能合约
dancing520
发表于 2022-11-6 21:10:54
634
0
0
8 V" |; e3 y" W, _' l
以太坊是什么1 _+ \; W. p/ d$ \- ^
* l$ s+ H" h: H: k3 i
以太坊(Ethereum)是一个开源的有智能合约功能的公共区块链平台。通过其专用加密货币以太币(Ether)提供去中心化的虚拟机(“以太虚拟机”EthereumVirtualMachine)来处理点对点合约。5 [% D5 H9 S3 Y
以太坊的概念首次在2013至2014年间由程序员VitalikButerin,受比特币启发后提出,大意为“下一代加密货币与去中心化应用平台”,在2014年通过ICO众筹得以开始发展。目前以太币是市值第二高的加密货币,仅次于比特币。
以太坊区块链是什么?
以太坊区块链有2个主要组件:+ F: ]+ {8 S8 C8 Z& [/ E
- P: y3 w( o" l) g* z
数据存储:网络中每笔交易都存储在区块链上。当你部署合约时,就是一笔交易。当你执行合约功能时,也是另一笔交易。所有的这些交易都是公开的,每个人都可以看到并进行验证。这个数据永远也无法篡改。为了确保网络中的所有节点都有着同一份数据拷贝,并且没有向区块链中写入任何的无效数据,以太坊使用一个叫做工作量证明的算法来保证网络安全。
代码:就数据的层面而言,区块链就是存储交易。在以太坊的世界里,你可以通过一个叫Solidity的语言编写逻辑/应用代码(也就是智能合约)。然后用solidity编译器将代码编译为以太坊字节码,并将字节码部署到区块链上(也有一些其他的语言可以写合约,不过solidity是到目前为止用得最多也是相对更容易的选择)。所以,以太坊不仅仅会存储交易数据,它还会存储和执行智能合约代码。/ X" j: |7 F' h4 k. Q: b
) h$ y: @# ]+ s) z
可以简单的理解以太坊区块链的作用就是存储数据和代码,并在EVM(EthereumVirtualMachine,以太坊虚拟机)中执行代码。/ T3 O8 I) d7 u U# K
% k: t1 f. L4 V6 j6 P7 i+ V8 R, c
要准备的基础知识0 i. F n9 A ~% ?3 ^
+ D% v' ?! T+ B
为了进行以太坊开发,你应该对以下语言/技术有基本了解:+ ?' I. C e- {1 i1 s
( l9 ~! v; }" S" W. w& ^
熟悉某种面向对象语言(如Python,Java,go)5 z5 \, c Y* s7 d
. A' O% ?: o% f! |9 d' A6 X/ q7 i4 X
HTML/CSS/Javascript9 d* w& o2 H W' A$ N
基本的命令行交互如Linuxshell命令. E5 C5 I9 G) d+ V: g2 x
: K; e! r- ]" w' J
理解数据库的基本概念
+ D( y- g- j* x3 [' R! c L
为了构建以太坊去中心化应用即Dapp(Decentralizedapplication),以太坊有一个非常方便的JavaScript库即web3.js,你也可以在一些js框架中直接引入该库构建应用,比如react,angular,vue等。
示例:一个以太坊投票应用; X R# O! @$ w& E1 L
# g6 H; K- w: R" L! g( n0 f0 D: S( K
以太坊教程示例中,我们将会构建一个简单的去中心化投票应用。所谓去中心化应用,就是一个不只存在于某一中心化服务器上的应用。在网络中成百上千的电脑上,会运行着非常多的应用副本,这使得它几乎不可能出现宕机的情况。你将会构建一个投票应用,在这个应用中,你可以初始化参与选举的候选者,并对候选者投票,而这些投票将会被记录在区块链上。你将会经历编写投票合约,部署到区块链并与之交互的整个过程。你将会了解什么是一个合约,将合约部署到区块链上并与之交互意味着什么。
) [( j. T2 P& J8 ]5 y4 u
本质上,区块链就像是一个分布式数据库,这个数据库维护了一个不断增长的记录链表。如果熟悉关系型数据库,你应该知道一张表里有很多行的数据。现在,对数据进行批(batch)量处理(比如每批100行),并将每个处理的批次相连。就可以形成一个区块链了!在区块链里,每个批次的数据就叫一个块(block),块里的每一行就叫一笔交易(transaction)。- u* R# @ q$ ~; ~+ V& u W
; t8 z/ r, H. F {/ \- R6 R6 |, r
现在,你对以太坊已经有了基本了解,我们可以开始构建投票的dapp了。这将会加强你对以太坊的认识,并且初略了解以太坊的功能。9 o+ K5 c( ~" W' |
以太坊开发环境搭建
7 h4 N a; \7 e C: n
Linux( b7 z5 x$ i; R7 [$ _
; {( v, ~1 \# h7 R
示例是Ubuntu16.04下的学习环境搭建,你只需要成功安装了nodejs和npm,就可以继续项目的下一步了。; q: ]7 \2 o8 A( p) n |
我们通过npm安装ganache和web3包来为以太坊教程提供支撑。我们也需要安装solc来编译合约。7 o. v* n) _2 z
下面是安装过程:
' x& _$ K+ m# k, s1 n, ?$ w7 @5 V
$sudoapt-getupdate, T# \! W: }" m3 u8 i; a
$curl-sLhttps://deb.nodesource.com/setup_7.x-onodesource_setup.sh
6 u& O+ x2 ]2 R! @4 m1 e( ~# K9 h
$sudobashnodesource_setup.sh
) {) x# d; J! p) n; ^* }
$sudoapt-getinstallnodejs6 M, j& k* w7 F, F2 H6 ^+ W
9 a3 |* R6 S. R1 a2 {3 E$ y9 U
$node--version
8 N+ o3 Q. N& j, B& _' C
v7.4.0
; p6 l9 U6 Y: u- v: i
$npm--version
6 m" ]9 b' l2 D' ]- i
4.0.5& w8 _2 a' n. d0 n; N- f
% S, f: d7 a$ {* x6 K. s" Z4 h5 \
$mkdir-pethereum_voting_dapp/chapter17 U. j1 [( O/ _6 ^0 E" Y
! M% l5 I/ ~ D$ j) D3 w5 }
$cdethereum_voting_dapp/chapter1
$npminstallganache-cliweb3@0.20.1solc) @1 p ` |* |
$node_modules/.bin/ganache-cli- I7 _, y6 ~! `) j, ^
. O/ n; K" h( G; B2 \$ M* i+ e
如果安装成功,运行命令node_modules/.bin/ganache-cli,应该能够看到下面的输出。4 c7 c2 f/ C/ V A i( [9 b6 D2 m
; @7 _+ ~- o6 c, w
GanacheCLIv6.0.3(ganache-core:2.0.2)5 P: P- w/ e* j6 J( O; b
AvailableAccounts. d3 |' s- E. E9 c) O' v
==================
(0)0x5c252a0c0475f9711b56ab160a1999729eccce97
" `8 x* ~2 w2 p `- d; \' X; n% i
(1)0x353d310bed379b2d1df3b727645e200997016ba3
(2)0xa3ddc09b5e49d654a43e161cae3f865261cabd23
(3)0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5
# E, r. m. O+ O! Q6 ~) C% f
(4)0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798
(5)0xda695959ff85f0581ca924e549567390a0034058; i8 P8 O+ h* x3 @; p9 y* d
(6)0xd4ee63452555a87048dcfe2a039208d113323790
* d9 q# B9 N- S
(7)0xc60c8a7b752d38e35e0359e25a2e0f6692b10d148 P! C8 I( j( s6 w" o( Q
(8)0xba7ec95286334e8634e89760fab8d2ec1226bf42
(9)0x208e02303fe29be3698732e92ca32b88d80a2d36! W ~2 T* @; |; E/ a
PrivateKeys. L! d! i2 Q; N( o/ z( p+ N$ q5 B
==================" q, o5 L6 V, @" |4 ?$ s8 I, f
(0)a6de9563d3db157ed9926a993559dc177be74a23fd88ff5776ff0505d21fed2b
& P" F8 ?+ Y6 e/ s& z% M
(1)17f71d31360fbafbc90cad906723430e9694daed3c24e1e9e186b4e3ccf4d603. R+ x& q2 j% p/ r
0 Y2 l" M* g2 B1 B9 I
(2)ad2b90ce116945c11eaf081f60976d5d1d52f721e659887fcebce5c81ee6ce991 m2 f r! [/ x4 l
* i) ^# \. Q( @, z# }- C
(3)68e2288df55cbc3a13a2953508c8e0457e1e71cd8ae62f0c78c3a5c929f35430' N5 Z4 x# M7 A" |" ], k2 c) J2 e
' i. g6 H; \2 J6 B
(4)9753b05bd606e2ffc65a190420524f2efc8b16edb8489e734a607f589f0b67a89 S% r- F+ n) J7 j& _/ J ]9 a2 _
(5)6e8e8c468cf75fd4de0406a1a32819036b9fa64163e8be5bb6f7914ac71251cc
(6)c287c82e2040d271b9a4e071190715d40c0b861eb248d5a671874f3ca6d978a94 V, {% t5 Q4 s
(7)cec41ef9ccf6cb3007c759bf3fce8ca485239af1092065aa52b703fd04803c9d
(8)c890580206f0bbea67542246d09ab4bef7eeaa22c3448dcb7253ac2414a5362a. P+ j! ]8 W- G; W4 H7 E9 \
(9)eb8841a5ae34ff3f4248586e73fcb274a7f5dd2dc07b352d2c4b71132b3c73f0
HDWallet
- g+ L' A" _/ u
==================9 B) z* _' K3 w4 |
Mnemonic:cancelbettershockladycapablemaincrunchalcoholderivealarmduckumbrella! w7 H7 d" Y% h* ?2 H
+ s! V9 X; v. }
BaseHDPath:m/44'/60'/0'/0/{account_index}7 X8 g5 l9 B5 ?3 W$ ~
Listeningonlocalhost:8545/ o0 z# ~# h: i, X) Q
为了便于测试,ganache默认会创建10个账户,每个账户有100个以太。如果你还不懂什么是账户,把它想象成存钱的银行账户就可以了(以太(Ether,ETH)就是以太坊生态系统中的钱/货币)。你需要用这个账户创建交易,发送/接收以太。
MacOS& i, G, w. G' {! \3 [9 e
如果你还没有安装homebrew,请按照https://brew.sh/的指示安装homebrew。homebrew是一个包管理器,它可以帮助我们安装开发所需的所有其他软件。按照下面的指示安装所有其他所需的包。
' m7 V; o5 ~% v5 \* S: L; i
$brewupdate
0 u0 A2 u, D+ D1 y8 a- `' w+ R9 [
$brewinstallnodejs$ A2 Z1 ^9 Y( G: T5 ]* m
; E& d% |3 ]$ i- i
$node--version
. q+ D1 i# ~& M, \1 H, K) Z M- Y0 D
v7.10.0
$npm--version+ I3 S0 ? {: c5 X% ~+ d: P% j9 k& O
4.2.01 c) y$ K- J' ^' i6 d
$mkdir-pethereum_voting_dapp/chapter1
$cdethereum_voting_dapp/chapter1
$npminstallganache-cliweb3@0.20.1solc
/ L# L" h+ \2 j$ B+ u
$node_modules/.bin/ganache-cli$ o6 R) t( B G D" ^& b
我们通过npm安装ganache和web3包。我们也需要安装solc来编译合约。
X; `% t% e7 s
如果安装成功,运行命令node_modules/.bin/ganache-cli,应该能够看到右图所示的输出。* }' G- [: e+ j; G! M5 C/ G: G
7 G3 ?: }$ k# e9 g
GanacheCLIv6.0.3(ganache-core:2.0.2)8 \+ b, H5 m; n, a8 z
AvailableAccounts
1 y5 a% y; \ G; v
==================
# S0 M8 i8 _4 ~8 Z' V
(0)0x5c252a0c0475f9711b56ab160a1999729eccce97
(1)0x353d310bed379b2d1df3b727645e200997016ba37 [& }. U4 A3 S/ T4 {, @6 A8 q7 m0 O, I
(2)0xa3ddc09b5e49d654a43e161cae3f865261cabd239 q3 S/ p9 c' y3 d6 [ c
, y/ ?( n- \. T; C# n8 H
(3)0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5 s' A6 j1 l; O8 J* N
(4)0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798
( x Z @" H0 S5 |% V0 Q
(5)0xda695959ff85f0581ca924e549567390a0034058; o) H9 J% e8 q
9 j; t: y6 N1 W( n! S
(6)0xd4ee63452555a87048dcfe2a039208d1133237903 m# x- i/ I1 T; G9 a) o( W# J
: v% S9 q+ g, H/ j/ L" h7 u9 p2 _1 X
(7)0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14. {5 {9 _0 I( {9 y5 H9 Q" t
- w7 S/ I+ D0 y7 T9 x8 s, R
(8)0xba7ec95286334e8634e89760fab8d2ec1226bf42
(9)0x208e02303fe29be3698732e92ca32b88d80a2d36/ W6 r# _. X) E: i! r# V' a# D4 |1 k
' x9 v8 | X/ O4 t
PrivateKeys" `1 n- ]! V, k; |) w
==================
' M% W% l) O8 [. S. W6 j
(0)a6de9563d3db157ed9926a993559dc177be74a23fd88ff5776ff0505d21fed2b1 d7 H9 N0 J* y0 W! Z
4 [8 t( o4 k$ d9 F% J+ a( O
(1)17f71d31360fbafbc90cad906723430e9694daed3c24e1e9e186b4e3ccf4d603; T0 ?( G+ B6 k( M
(2)ad2b90ce116945c11eaf081f60976d5d1d52f721e659887fcebce5c81ee6ce993 I( B# m* n$ I# R+ K7 V/ J
- q5 D4 l. k1 Z1 k& \% I! M
(3)68e2288df55cbc3a13a2953508c8e0457e1e71cd8ae62f0c78c3a5c929f35430) S4 O$ T9 D) d# H- M& }! m
(4)9753b05bd606e2ffc65a190420524f2efc8b16edb8489e734a607f589f0b67a8
; g: g7 P/ \% p5 A8 u2 N
(5)6e8e8c468cf75fd4de0406a1a32819036b9fa64163e8be5bb6f7914ac71251cc
(6)c287c82e2040d271b9a4e071190715d40c0b861eb248d5a671874f3ca6d978a9
(7)cec41ef9ccf6cb3007c759bf3fce8ca485239af1092065aa52b703fd04803c9d
! Z H9 ?! W/ R
(8)c890580206f0bbea67542246d09ab4bef7eeaa22c3448dcb7253ac2414a5362a
( d5 S: Z1 Y7 a6 C7 O/ A
(9)eb8841a5ae34ff3f4248586e73fcb274a7f5dd2dc07b352d2c4b71132b3c73f0
7 x2 O2 W8 Z0 b$ _
HDWallet
==================3 Q8 y" {( ~$ k5 h6 W
Mnemonic:cancelbettershockladycapablemaincrunchalcoholderivealarmduckumbrella
BaseHDPath:m/44'/60'/0'/0/{account_index}- G% d# R6 W. ~% F9 ^
Listeningonlocalhost:8545$ W* K* O' y& a0 e( \* L7 r
' ~, ] r8 q# T1 Q0 p) {" A
为了便于测试,ganache默认会创建10个账户,每个账户有100个以太。如果你还不懂什么是以太坊账户,把它想象成存钱的银行账户就可以了(以太(Ether,ETH)就是以太坊生态系统中的钱/货币)。你需要用这个账户创建交易,发送/接收以太。& j( T; V- j. u# Y2 E5 j" _
Windows' ?% Y7 H! g- @2 \5 q$ H# Y
安装VisualStudioCommunityEdition。如果你选择定制安装,那么至少应该安装VisualC++(目前的版本是VS2017)& I, W5 L! r. C6 B' v4 d
7 {3 X- G7 d% b/ T) C' l9 b" z
安装WindowsSDKforWindows
安装Python2.7如果你还没有安装的话,并且确保将它加入到环境变量PATH- U7 x* y1 |2 j% M1 B
安装git如果你还没有安装并加入到PATH
) M4 V2 [" B5 u. s% I
安装OpenSSL。确保选择了正确的安装包,并且只安装完整版(而不是轻装版)。你必须将OpenSSL安装到推荐安装的位置–不要改变安装路径8 A+ n! u* A) @, K4 E6 K" c* A
8 N7 a& U7 p2 y! \# A2 C8 J
下载和安装nodev8.1.2。不推荐使用版本v6.11.0搭配VS20178 ]0 F) ?1 [9 s
执行命令npminstallganache-cliweb3@0.20.1solc+ {3 I$ Q0 {( F% O- c( f7 s' x5 C5 T
SolidityContracts- c, S- {* s6 [4 _; U
现在已经安装好ganache并运行,我们将会开始编写第一个以太坊智能合约。
我们会使用solidity编程语言来编写合约。如果你熟悉面向对象编程,学习用solidity写合约应该非常简单。我们会写一个叫做Voting的合约(可以把合约看成是面对对象编程语言的一个类),这个合约有以下内容:
一个构造函数,用来初始化一些候选者。- h5 d( b7 M. W/ G3 i
# j* j* T5 `* s" ]
一个用来投票的方法(对投票数加1)# a% H! Z2 N4 r
" @( K& }5 n, l i
一个返回候选者所获得的总票数的方法8 `/ x: T& u6 a8 `7 s# J
当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与web世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在,并且数据仍在。新的部署将会创建合约的一个新的实例。
pragmasolidity^0.4.18;
- I0 |) d* P8 d, P2 e
contractVoting{4 z: y3 t# e% i
mapping(bytes32=>uint8)publicvotesReceived;
4 u" C( X' t0 i- u6 K
bytes32[]publiccandidateList;. M% r% J: m2 i4 ]/ p
functionVoting(bytes32[]candidateNames)public{
! b P( {* \ s' A# |6 }3 t
candidateList=candidateNames;
0 ]' p: L/ E& w8 D* S
}
functiontotalVotesFor(bytes32candidate)viewpublicreturns(uint8){: z$ `/ Z: }6 G
# A4 B/ z* @4 g& _
require(validCandidate(candidate));
returnvotesReceived[candidate];* ]/ Y6 ]0 R6 h$ j! K
3 t+ U6 j% I& c+ m7 ]1 k
}2 ?5 Z% L9 F' ]' H9 b
8 T9 `! v) f- e1 S! k& u' I
functionvoteForCandidate(bytes32candidate)public{
require(validCandidate(candidate));' J+ R- Q% ^! c# a5 e, G
Z5 Q0 l) z \8 a& b" k% X' ~
votesReceived[candidate]+=1;
}) e- J0 v& _( r' E, F2 `( e
; p5 g& b5 F$ [2 @+ Q1 G. Y
functionvalidCandidate(bytes32candidate)viewpublicreturns(bool){
for(uinti=0;i4 }" K; B- t @7 Q
将右侧代码拷贝到一个叫做Voting.sol的文件中,并保存到chapter1目录下面。 ]# |4 W7 R0 f: H' G# v c: S
代码和解释
Line1.我们必须指定代码将会哪个版本的编译器进行编译
* Z, Z" t1 a7 ~* A/ ]& P1 O
Line3.mapping相当于一个关联数组或者是字典,是一个键值对。mappingvotesReceived的键是候选者的名字,类型为bytes32。mapping的值是一个未赋值的整型,存储的是投票数。$ B6 Y$ |5 g/ O
! B; ~$ X. Q, |7 m' K: p* E/ c' p. u
Line4.在很多编程语言中,仅仅通过votesReceived.keys就可以获取所有的候选者姓名。但是,但是在solidity中没有这样的方法,所以我们必须单独管理一个候选者数组candidateList。
8 e9 c. N& e) L8 w! {# h
Line14.注意到votesReceived[key]有一个默认值0,所以你不需要将其初始化为0,直接加1即可。3 D- K- a! @: }2 }. {! a; J( W0 t) `! v; P. \
0 n2 U' @' Y) n
你也会注意到每个函数有个可见性说明符(visibilityspecifier)(比如本例中的public)。这意味着,函数可以从合约外调用。如果你不想要其他任何人调用这个函数,你可以把它设置为私有(private)函数。如果你不指定可见性,编译器会抛出一个警告。最近solidity编译器进行了一些改进,如果用户忘记了对私有函数进行标记导致了外部可以调用私有函数,编译器会捕获这个问题。这里可以看到所有的可见性说明符。9 o0 d3 B% |0 j# U+ g) \7 Q( C
你也会在一些函数上看到一个修饰符view。它通常用来告诉编译器函数是只读的(也就是说,调用该函数,区块链状态并不会更新)。所有的修饰符都可以在这里看到。/ A" V' l/ _; F p* ]7 f
编译智能合约% H3 a: k4 Q3 H
我们将会使用上一节安装的solc库来编译代码。如果你还记得的话,之前我们提到过web3js是一个库,它能够让你通过RPC与区块链进行交互。我们将会在node控制台里用这个库部署合约,并与区块链进行交互。8 J% i1 Y; `1 k8 O; g
' g6 o: J$ [, h
首先,在终端中运行node进入node控制台,初始化web3对象,并向区块链查询获取所有的账户。
& P5 p: i3 \* A" }: H1 U* J
确保与此同时ganache已经在另一个窗口中运行
5 h; ^ `( r: P7 g
为了编译合约,先从Voting.sol中加载代码并绑定到一个string类型的变量,然后像下边这样对合约进行编译。
! H# o+ \5 X8 l$ @
$node$ _! D4 x, Z+ R( t+ r, X4 G3 P
_+ Q- `- I3 V7 I0 u V/ ]
Inthenodeconsole
# C3 O# I m& d- B* b# `
>Web3=require('web3')
: J8 \7 }8 h7 @
>web3=newWeb3(newWeb3.providers.HttpProvider("http://localhost:8545"));
>web3.eth.accounts: t& Q. v9 B: q$ n8 W
['0x5c252a0c0475f9711b56ab160a1999729eccce97'
'0x353d310bed379b2d1df3b727645e200997016ba3'$ p. U" F- q( i% y3 ?+ S0 }) H, }
'0xa3ddc09b5e49d654a43e161cae3f865261cabd23'
" r+ r4 ~0 @8 I
'0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5'; v4 K' j: ]' n1 R! ]. B
# f" W* k8 ?+ A b
'0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798'
: P3 n+ @1 u. b
'0xda695959ff85f0581ca924e549567390a0034058'
' L4 m' W% r' P6 e6 ?9 ?
'0xd4ee63452555a87048dcfe2a039208d113323790'' y( ]+ I$ r# j) q
'0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14'
H* ~$ N5 K# U. x* B7 g# _
'0xba7ec95286334e8634e89760fab8d2ec1226bf42'+ P! S9 i+ g0 d: y
'0x208e02303fe29be3698732e92ca32b88d80a2d36']0 @' O% l% s0 \; W* D% e
>code=fs.readFileSync('Voting.sol').toString()( r3 u8 \9 ]* N" _) j
# z8 ^: G3 e9 R. s% k9 C3 b
>solc=require('solc')
2 z: O0 Y. B ~2 w6 G/ G
>compiledCode=solc.compile(code)2 b2 V6 S7 f* f) _- B
# X4 w1 q; P/ ^& ^8 R' X% |5 @
当你成功地编译好合约,打印compiledCode对象(直接在node控制台输入compiledCode就可以看到内容),你会注意到有两个重要的字段,它们很重要,你必须要理解:: h+ ^7 C1 m9 w* U
1.compiledCode.contracts[’:Voting’].bytecode:这就是Voting.sol编译好后的字节码。也是要部署到区块链上的代码。+ N) a9 _" } P, e! \; \1 w
1 i9 V! G! Y W2 G8 ~1 q" E: `$ Q1 |
2.compiledCode.contracts[’:Voting’].interface:这是一个合约的接口或者说模板(叫做abi定义),它告诉了用户在这个合约里有哪些方法。在未来无论何时你想要跟任意一个合约进行交互,你都会需要这个abi定义。你可以在这里看到ABI的更多内容。8 K, J, ^; c' u% f' @
$ e& }; {4 L H
教程参考汇智网的DAPP开发入门教程,如果大家等不及博客更新,也可以直接访问这个以太坊教程。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人