建立基于以太坊的私有网络和智能合约
V刘晨曦
发表于 2022-11-17 19:05:51
114
0
0
最近一段时间一直关注区块链的相关的领域和知识,今天本来想帮助小伙伴建立一个基于以太坊的智能合约Demo,发现很多过去的文档都已经过时了,无法正常工作。那就只能自己造个轮子,弄个版本新一些帮助大家入门。4 c9 m) c- ]7 k3 J5 N4 `
" R+ F! R8 O+ R/ a
本文以流程tutorial为主,不过多去讲技术原理,原理文章网络大把。
) o B" K7 d% b" u" F
目标5 ^0 L+ M6 C! L$ N
本文目标如下:2 c* H1 a+ x! {' @0 r
4 w" m V1 I; N0 U) s# W
建立私有以太坊,设置第一个节点,挖矿
$ |) K& i! S! u! k
完成一笔转账交易
建立简单的智能合约, [( z' R* h+ Y7 `
) Q: Z J+ U1 L% [) K
建立第二个网络节点5 P$ C g& j9 r: k
环境介绍8 P- R6 r9 m" U- q; E0 n
无论什么开发都离不开相应的环境,我尽可能将所有软件都升级到最新版本,以下是本文内容相关的环境:* C9 V& w$ @; `1 O: P. x
操作系统MacOS10.12.6' U H7 d" L% a) n5 O( z
4 x4 D. ]( t4 |9 q7 p1 m, D
Geth以太坊CLIhttps://github.com/ethereum/g…v1.67
" z. F, ]8 x4 z
Solidity智能合约编译器Version:0.4.15+commit.8b45bddb.Darwin.appleclang
. U# m9 b5 O$ o9 h6 {6 O" J+ B# o
安装
安装Node.js,这里不阐述了,源代码自己编译吧。
安装Geth,这里直接去官方网站下载最新的可执行程序,复制到/usr/local/bin就OK。1 w7 G3 W. w2 i( A7 n( l+ A
最后安装Solidity,本地要先有brew,才能进行安装:% B% p& H2 T" P- c/ F8 {7 j8 u$ ]8 m
3 U# h: [" n! {) W
brewtapethereum/ethereum( o' M' c* M- U& {. V# @ a' H
brewinstallsolidity
创建区块链; k Z5 e& }7 |2 \) |
4 H1 i1 X. ]/ ?9 o
创建自己的以太坊私有链很简单,新建一个目录,在目录中先建立自己的创世区块描述genesis.json文件。
文件内容如下
& u) [3 S% x" J8 Z {. x" Y% a
{
' `) r- k, e+ Z0 i
"config":{$ j6 N* G5 N& E8 ]
"chainId":2017,: b- ^% R6 X) x8 ?/ F1 C
" n9 m* c0 |8 P) k, J' z P& h
"homesteadBlock":0,* k, u" k/ V( Q3 k" h) w
. K, O6 s! G+ f3 b1 s7 b
"eip155Block":0,
0 R' Z* L: E& P8 g, ~* N
"eip158Block":0
},
"difficulty":"100",
"gasLimit":"2000000",
"alloc":{}+ v( F( G* N/ x2 f: r3 t
} B- Z% a! n. s# P8 p4 w
为什么自己创建创世区块描述,如果使用默认值,difficulty值非常高,这样挖矿要急死人的。* q9 f/ n: u( K9 Q4 p+ f1 L
0 V5 t E+ s# e! k; r* b4 s
首先创建两个账户,本文后面需要用到这两个账户,创建账户需要输入两次密码。8 C% M4 \2 U- y/ X/ A, u i
?>geth--datadirnode1accountnew
?>geth--datadirnode1accountnew0 j" y; v5 H- ?; x% [6 ~. k" i
( v' z/ W6 P0 f
用我们刚刚创建的描述文件,创建创世区块。0 E- l" N: m- k# r7 ]0 B
# g# h$ L1 W k% A' z
?>geth--datadirnode1--networkid27027initgenesis.json
使用console连接节点1并且记录log
?>geth--datadirnode1--networkid27027console2>>geth.log
使用geth完成挖矿和交易
连接成功后,看看有几个账户0 t4 h- U4 X. _* P8 A( E; j
>eth.accounts* ?: D" @/ [7 R, t
( h( x! T! K3 p8 Q6 X8 u
["0x65070d1d224114fd3c8358e9614fd948daecc428","0xf11167054eb5fb91dd7b46726380f0f0cb09a6d8"]
7 O! ~& k+ m9 m% ]( ^
查询下账户余额
3 Z/ Z7 q E# |* [- S0 g& @
>eth.getBalance(eth.accounts[0])7 k& N% c1 i7 ^. M( j; Z
0
, M* c$ a" C5 N6 r3 ]
第一个账户没有余额,accounts[0]默认情况是coinbase账户,也就是挖矿的收益归集账户,现在我们就来挖矿,赚取奖励,由于difficulty值很低,挖矿秒出基本。
) `+ m9 q- A( b. p6 k
>miner.start(2);admin.sleepBlocks(1);miner.stop();' K% `/ [* {* T; P
true/ b) C, A4 C0 P' N0 `. m
* A' |& J2 y. B! s1 k8 I# {
miner.start(2)开始挖矿,参数是开启挖矿的计算的线程数: `4 M& o5 P$ x
admin.sleepBlocks(1)挖到1个区块就停止) V0 o' ~2 f6 I" P5 h
# S9 `) q$ I6 d* T P
miner.stop()挖矿停止7 Y; H5 h+ k* V, ^# H
" N6 q4 Z) C; q" ~) x
第一次会创建DAG,这里会花费一些时间,关于DAG,详情见底部参考。出现true说明挖矿完毕,挖完查询余额。8 n, z/ K! P: l) Z# }# E
>web3.fromWei(eth.getBalance(eth.accounts[0]),'ether')
5( q" M' y5 i9 {7 }7 l8 Q
5个以太币在accounts[0],现在转2个给accounts[1],转账时候,单位是wei,但是注意,既然转账,别忘先解锁账户accounts[0],这里要输入账户密码。
>personal.unlockAccount(eth.accounts[0])9 L" Z: T( w' s: a) G. b* B8 p/ ^
- g V* M+ O3 F( c) H6 q
Unlockaccount0x65070d1d224114fd3c8358e9614fd948daecc4280 D/ e! p5 N$ d
Passphrase:) Z k4 i( [* F) A' r1 ]7 P
/ P" ~- Y ?- l3 n- F& U( m- q3 ]
true0 A8 |& v7 B( O3 A. z2 ~. d
>eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(2,"ether")})' D7 m+ x1 v- U# }( q
"0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645". s* Y4 j9 g3 O8 K
最后出现的是这个交易的hash,查一下有没有待处理的交易
>txpool.status3 j5 Z' {7 Y W
o! I; i1 b4 Y4 G+ _. ?+ ?
{* g6 ?6 F7 V. F3 y" ]$ q$ w
pending:1,3 e* G, _; m- G* ^$ B' D+ T3 x
queued:0
}
' g2 l" C* W$ b T" F8 L) F
>web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')
: G% Q8 m* u/ K1 r5 p% W+ m
0; X2 T" m! L U+ g. l
果然有一笔pending,查询账户accounts[1],并没有发现以太币,这里需要旷工来挖矿,打包这个交易到最新区块。交易才能生效,继续挖
>miner.start(2);admin.sleepBlocks(1);miner.stop();7 ?! J) k e8 C+ j: Z7 {% R
true$ d/ w/ V; }+ `0 t8 F% D7 k
" I4 f5 h$ _: s" w
查下余额
$ \% R# o1 K7 ^ N* \) l T
>web3.fromWei(eth.getBalance(eth.accounts[1]),'ether')
' ^ i5 c' j& Z) W% W! L5 ~4 B
2: L7 M# O% }( E; k) a) ?
# w" Z6 ]/ y2 T/ i" H
已经到账,再看下刚才交易的详情
+ [! J! o$ Y% a! v
>eth.getTransaction("0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645")+ R% N, U4 @" F& W. U8 j C
{: ]( b# m$ M }& r) n( n
blockHash:"0xd30fbefb48de05a458a909d9486402bfa4d1459619226a3f8b95aaf407669bb7",
blockNumber:2,0 ~3 X0 c* Z; P0 I; t# ~8 \* s4 k
from:"0x65070d1d224114fd3c8358e9614fd948daecc428",
gas:90000,
0 c( p/ r {- H% M! G# h" z/ |
gasPrice:18000000000,/ V; u2 ^9 O) |/ X# o6 _9 c% B
" j8 l5 V. H7 s @
hash:"0x3f190d2af25dff4e6b6fac9537f8f6152fdb193ca9afe228fbdc9301bbff5645",
$ R+ l7 ^0 g: G9 a7 x) E' x/ u' b
input:"0x",( U9 j% K/ V, ?/ [6 [, | y2 ^
. P# e3 f9 ]: Y' y' K$ @
nonce:0,8 M8 F% A2 M; j. p
8 F; o8 J( n1 P. V2 ?( P7 G
r:"0xd9b7c4830b9a7ae8ac922179c4e73e6bf2a52178ee0c01250bd940586334d412",
s:"0xa1b0058b63e1c0360eae6073791b1d63d4a737c71c5932b4b203e853a8185cd",
( g/ O: _1 I) \ B/ }
to:"0xf11167054eb5fb91dd7b46726380f0f0cb09a6d8",
0 {) S4 a0 t$ m) ]
transactionIndex:0,
# `1 r( v7 H3 `% K: O7 y
v:"0xfe5",
6 N; D6 S/ I* t. Y2 `6 c4 Z
value:2000000000000000000
/ N! a) C! f9 a; n' [
}# l1 k2 O H( C' w
8 E3 [' v) @/ B/ C& t6 X3 j1 B
到这里整个交易就完成了; w% u& m2 S* U( l& H$ h
简单的智能合约 j1 ]6 |! B" Z; H( C
0 M n1 r/ J7 Y! o _/ Q: b
下面我们来创建一个极简单的智能合约,geth1.6变化蛮大的,以前编译智能合约的方法都有一些问题,没什么简单的办法,browser-solidity是个不错的在线编译选择,我们还是选择在本地进行操作,前面已经通过brew安装了solidity,创建一个contract文件夹,在文件夹中创建一个hello.sol智能合约文件
pragmasolidity^0.4.13;
contractHello{
functionsum(uint_a,uint_b)returns(uinto_sum,stringo_author){
o_sum=_a+_b;4 N8 N$ [, J4 l
% r- X6 B) a8 h* M, y7 C" L
o_author="freewolf";0 p* o' `* `5 \; d& w- H% p u
# L% N: ?8 R3 C- c) t- x' W9 l
}
}9 D. f2 `( L/ l0 W5 K0 A
然后我们来编译,完成后,会多出两个文件,abi文件就是智能合约相关的接口,bin文件就是智能合约编译代码。
这里是Mac命令行环境,不是geth,?>开头的都是命令行6 A4 W/ {' G! f! x& C# Z$ F1 q
?>solc-o.--bin--abihello.sol
1 G' {! I! T6 _$ Q
?>ls
4 `' X( ~% F% j: K
Hello.abiHello.binhello.sol
- H$ L9 q- J0 P0 N0 j
在geth中加载这些文件很复杂,这里我们修改下刚生成的文件2 Q2 t% b9 m0 y* r9 x
Hello.abi文件内容修改成
" X1 }9 }& ~$ |8 P( r
varHelloContract=eth.contract([{"constant":false,"inputs":[{"name":"_a","type":"uint256"},{"name":"_b","type":"uint256"}],"name":"sum","outputs":[{"name":"o_sum","type":"uint256"},{"name":"o_author","type":"string"}],"payable":false,"type":"function"}])4 v1 _0 h# A2 T, ?, N7 C; I# T
- c. i7 d2 N1 Z/ V) i7 ~
Hello.bin文件内容修改成
8 S5 Y7 j; ^1 I+ t6 \& ]9 W
personal.unlockAccount(eth.accounts[0])
varhello=HelloContract.new({2 _1 J. y0 \7 f' E5 b9 L$ \
from:eth.accounts[0],- ^: s1 [! @4 d6 q' ~& z
data:"0x6060604052341561000f57600080fd5b5b61017a8061001f6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063cad0899b1461003e575b600080fd5b341561004957600080fd5b61006860048080359060200190919080359060200190919050506100eb565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b838110156100af5780820151818401525b602081019050610093565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b60006100f561013a565b82840191506040805190810160405280600881526020017f66726565776f6c6600000000000000000000000000000000000000000000000081525090505b9250929050565b6020604051908101604052806000815250905600a165627a7a72305820063cb95e17166637bd4ab62eae6b0e6c4e1fcd85a9c2e3be29aa75a272280b830029",3 N3 }7 [5 }/ L; r7 y
gas:500000
1 ^' {; T2 A% e: Y! w
})
- H/ `) {+ t" D+ k* K
注意别忘了data必须0x开头,New合约就得解锁自己的账户,这里解锁也写在这里了0 u' ^1 @- b. R9 `* m, {
( `& F/ M/ |# k. M5 `- {. y+ Q
回到geth,加载刚修改的文件,加载bin文件需要输入账户密码
/ o8 a7 R5 Y1 B
文件夹contract就在运行geth命令的目录. L: T2 x/ D( E5 e! }
) l- X2 i& w& p4 d
>loadScript("contract/Hello.abi")% J4 p( ~7 P l- |" p+ C8 E; F
3 t% Q3 r* E; s- W# Z& b& X
true) n; F7 ?8 F' q# Y: n5 j \# z" q
>loadScript("contract/Hello.bin")7 Y R/ y- [; X0 q8 j' r5 D; g
% z$ O' h7 i) P( a
Unlockaccount0x65070d1d224114fd3c8358e9614fd948daecc428
Passphrase:& k* }- X# s9 e0 W, h
6 a% j; M7 s7 I* ~
true/ x, v1 s; q) J+ M7 m9 L
现在智能合约已经部署到区块链上了,但是要挖矿才能生效,挖完就可以尽情玩耍了。
( i" T$ p0 N+ h% ^3 p# x' U
>hello. R# c) k) x# y, E; w3 q4 P
?# y! I9 X4 t+ s( k) q3 O O
{$ u( U) p; W( Y# r. P* U
; m5 t W5 J6 I5 u! t, Y
abi:[{4 X$ Q3 J( W& S/ j4 D1 H- {
constant:false,
3 L" R& A* L/ n$ Z S
inputs:[{...},{...}],
name:"sum",4 E; D v# N% U- k
outputs:[{...},{...}],4 B( [) e! y7 |1 e* n( p
payable:false,4 D5 [4 t J8 \! _
type:"function"
}],
# u; S6 t- W6 U
address:undefined,. A6 {9 m( D0 G: c! a$ S/ A
transactionHash:"0x783f5cae1f9b40f25da1260267d5e6f801d1746541b5f28f84684883723807b8"
}; J$ b) G$ C2 S+ ~# `4 ^* E
>hello.sum) y) g/ ^8 W& L/ @
undefined
4 _/ A7 A; O9 i
>miner.start(1);admin.sleepBlocks(1);miner.stop();! v! E. u" n9 e- T& h1 A! s
true
>hello.sum
function()1 J8 z7 N9 @4 E9 c. ^( x; }9 C
* p& n& N# r" g# ^
>hello.sum.call(1,2)- q0 Z, T' S" ]: b! V4 v
4 R9 N; V* [+ y& v l! [. \
[3,"freewolf"]
追加-如何建立其他节点
7 p* k0 p) }2 y3 G. d0 b4 r
追加一段,如何创建其他的P2P节点,首先在原先节点执行下面代码,查看当前节点信息1 Y9 @7 d' u: o6 z
8 G9 C, n) r4 }0 K
>admin.nodeInfo5 U( V' \8 f* `0 |9 R# \( E
{
+ \6 `+ }& D, S @5 ]" L4 D
enode:"enode://bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3@192.168.1.2:30303",
id:"bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3",! d; j$ W: U7 p- ]
8 f( Y' x' s3 v: d) H
ip:"192.168.1.2",
listenAddr:"[::]:30303",8 q5 M# K+ a! Z' f7 c" q& Y: v! G! }
9 ?% q E" ?+ _/ ]
name:"Geth/v1.6.7-stable-ab5646c5/darwin-amd64/go1.8.3",0 K1 O& Y4 M2 T6 z+ `3 ]0 F
ports:{0 `8 _- @% W2 D( E! t8 C
; ^5 g9 t( z3 q3 H/ [: n- e6 V" U
discovery:30303,
7 V: f* P7 F! s: U& q5 O
listener:30303
1 Z/ P) o# H K( F4 J
},, l8 G3 n7 p, n7 K
protocols:{! j* }* l2 e1 r( l) f
% c7 s7 ]- Y7 I1 c" B
eth:{7 o) C0 e+ E' J8 ^ E h- X" W/ J3 v
2 A; ~/ x' d: J/ W
difficulty:655652,7 ~+ u9 S7 e0 ?6 D8 ~
genesis:"0x7b0286b147e6b5b8710b8acff38053fdf1991a980da8ca73b4b359c28c7144fc",4 I* t& S. [6 U. N+ u0 F: `
3 D* Q6 v5 h8 I( E" m8 A" p" |
head:"0xd545cee3b9247b67c5d43728eddcbcfe9315dcf18cbc12187a7a178220829153",
; }) V# h+ r% v$ j+ E/ J
network:270271 l* y' {3 w" A: w: x" g% o$ H: @
* [5 B8 M2 A0 m4 O0 A
}9 R4 a N! L* c0 W6 s
+ R% m# g& a. `" b6 Y7 T' q# Z6 p+ D
}
$ D7 ^( M+ j6 J6 D! _
}: E( J9 ]) q. }6 U+ W
拿到node相关信息,就可以创建新节点了,这里就不过多解释了,前面基本都介绍过了
?>mkdirnode2
?>geth--datadirnode2accountnew* e6 x2 p- \( x
?>geth--datadirnode2--networkid27027initgenesis.json# r/ y. N" N+ m2 V. M F) p0 V% H
6 r5 i8 R8 n; ^ J% v% G1 Z; |
?>geth--datadirnode2--networkid27027--port30304--bootnodes"enode://bbd3d0f2afad68c3e4b2e79a6daddc6e8498b9266cc3e953bbb121bae40fe44b5e0377c0768b03e47ac04cead52235a12931bfb96528a8225be5808fc8c174b3@192.168.1.2:30303"console9 |: ~% W4 ^9 o
需要注意的也就是最后一行,写入你自己的node信息,其他的也没什么了,进入geth后,数据同步后,可以使用下面命令
3 O. _! g% n- d5 g" U5 o
>eth.getBlock('latest')
>admin.peers
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
成为第一个吐槽的人