Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

开发基于以太坊智能合约的DApp

星火车品
87 0 0
web3.js与以太坊通信是通过rpc的方式实现的。
, T- m8 ~; k$ d# ~6 ?0 H7 X, L以太坊节点本来提供了rpc的访问方式,但是因为以太坊节点的地址不确定,并且DApp需要访问钱包,所以用web3.js直接访问以太坊节点的rpc服务是不现实的。2 v, Q! G' F$ G/ `
ganache-cli模拟了一个以太坊的测试节点并提供对外的rpc访问方式(就是例子里经常说的http://localhost:7545或者http://localhost:8545)。同时在其中内置了M个以太坊帐号,用于测试。8 T0 i0 r" E+ q) o
MetaMask是一个以太坊的网络钱包插件,它也提供了web3的访问方式。而且可以通过这个插件指定后面的以太坊节点是什么。因为MetaMask是个钱包插件,所以解决了DApp中的支付问题。所以现在的DApp都依赖它。
) |0 x. K1 j1 h6 N& r' ^有一个以太坊教程,是在线学习的,大家可以去看看,如果自己本机上搞,开发DApp的基本过程都是一样的如下:
! Z) U; \: F& t; m8 X! t' E6 S' n) F, ~1、安装NodeJS
9 `! M/ l1 n' c+ x% `, _2、安装truffle:一个开发DApp的开发框架6 A* F% y1 Y4 [7 g# l
nmp install -g truffle
; I% M7 ~% q6 R+ a7 N# p1 X3、安装Ganache(原来用testrpc):在内存中模拟以太坊运行并对外提供rpc服务。0 J. E/ B# Q1 x3 M
npm install -g ganache-cli
0 H3 q5 q; i+ b6 U2 d4、运行ganache-cli
$ l9 t+ f' Q+ W0 t! q! a# Iganache-cli
) \# \% j5 }  K" }* D5、生成一个DApp的项目
1 y3 Y( b* W# X' tmkdir project1
7 C4 M" ^, K2 \8 k# c; R7 @7 btruffle init1 [$ v- L+ J( L- v3 c6 s
如果想用truffle中的某个例子,可以用' X5 o& E: @1 H. Y
truffle unbox pet-shop
7 g* F' S& b2 N% G“pet-shop”是例子名称
+ V; ?' z4 o. I4 u9 B! c, H9 ?6、编写智能合约
1 z, i. j. y6 V/ T具体如何用solidity编写智能合约可参考各种文章,这里不再重复。
; D( m3 C9 H; E% V  k编写好的智能合约的Project1.sol文件放到contracts目录下( q1 J3 w( n$ |/ _1 R6 C) X8 ?
7、编译和部署智能合约5 L/ C# ?: `$ k5 R0 ?8 L
在migrations目录下创建文件2_deploy_contracts.js:# }% z7 q% q5 q6 i
var Project1 = artifacts.require("Project1");- o5 e0 b' L# g: T
module.exports = function(deployer) {8 N! p$ B! Y2 E/ H8 o
  deployer.deploy(Project1);
+ I% y6 x7 X7 q7 L};
4 m4 A6 N8 j/ O" a之后执行:1 {: [* X$ C  b: A/ v
truffle compile  O& q9 _+ U$ }9 _' o# a& U
truffle migrate
5 N' |5 I$ q4 Q; }) |/ ~如果你的智能合约没有问题的话,现在你的以太坊智能合约应该已经部署到你用来测试的ganache中去了。! ~" L# r3 _1 ?. ]+ T
这里可能遇到的问题是:默认的truffle生成的项目,测试用的ganache的地址和端口会被设置成http://localhost:7545,而实际上执行ganache-cli之后的服务端口是http://localhost:8545,需要在truffle.js中修改一下:/ Q- N% T: t! B7 S# s" {
module.exports = {! _- l. F* i0 `, x
// See * i" q: h$ ?/ U  g! ~
// for more about customizing your Truffle configuration!3 }3 U" F& l, @- h' A4 ]
networks: {
/ w6 [# W7 f* z$ h- r& _, M; K( sdevelopment: {
" p. o0 v# e3 h0 h; k" N0 Zhost: “127.0.0.1”,. x' d( j- Z  W
port: 7545,   //改成8545
+ k7 E) b) ~3 o5 C$ F" h6 o6 cnetwork_id: “*” // Match any network id
8 v" _/ ~/ y$ N' R0 U; c* o}
. `7 `0 y5 v+ b}
% |3 u* |$ O6 T- ?% k};
4 u) J6 m% W4 e4 x8、编写前端的js代码跟以太坊交互
  j3 s* U; N6 g: B  A1 b2 y" H, H通常需要如下的辅助js库:
# i1 u1 n( c4 i) Z5 D/ b( V& @
7 V% n5 ?* v& l* g) C$ o5 O+ I' H, L7 G  t; ?3 L; X1 N: |
在此基础上,编辑你自己业务逻辑的js,通常命名为app.js,app.js的框架如下:" t. _9 {5 z  E/ m( r, y
App = {0 D& B+ |0 ], q2 Z+ [2 ]
web3Provider: null,# g# u0 I5 D# U6 Y8 @; Q; I
contracts: {},
7 S  i3 j" b* Z$ W, t+ W' Dinit: function() {+ @7 n; h$ Q: w* b6 a' x
//初始化你自己的页面、变量等
) _( M( s- @0 B% `, freturn App.initWeb3();: _4 ~; R& V, A4 I/ F) {
},
* R4 b. g0 @, r: S. o; BinitWeb3: function() {, j& E% [( b* Y) p( X, f, k/ D$ @
/*  I; o6 h9 ]4 E% E/ l% c
* 初始化web3:$ j, h/ p. N7 z2 f7 d
*/
- S4 @5 W1 Y4 rif (typeof web3 !== ‘undefined’){8 M- b6 U* R) `+ K  ?  v
//如果你的浏览器安装了MetaMask的钱包插件,那么插件会赋值web3.currentProvider
% q1 F+ k) c& {% e  u7 R3 bApp.web3Provider = web3.currentProvider;
: n) X, f" e7 S7 g% P}
% @6 B9 }" K9 s( D8 [) Aelse9 V2 G% ]/ L0 l9 m( E
{
; o3 \( }6 r9 I0 g. C5 P9 l//如果没装插件,那么创建一个基于Http的provider,这里用到的就是用ganache-cli启动所提供的rpc服务,因为ganache-cli启动的时候绑定的是localhost,所以测试所使用的浏览器也要在本机。(如何让ganache-cli绑定其他地址我还没找到)9 I% \5 U3 z8 t- e+ g/ a
App.web3Provider = new Web3.providers.HttpProvider(‘http://localhost:8545’);- J  `4 G1 O1 ^% y
}* K- d) A. t" P" |
web3 = new Web3(App.web3Provider);0 t% B3 z! j( q" r
return App.initContract();
# j9 |- T7 k0 s5 X" y: v- R7 l},
8 J/ @9 k+ R+ \% {7 L; JinitContract: function() {
1 b+ y! c, Z8 ?/*
5 \. F4 I2 J. ]2 @/ u* 初始化智能合约,实际上就是为你的智能合约创建一个对应的js对象,方便后续调用
6 K2 P  J/ ]+ w" z/ E*/
" [; U% ?9 u+ g2 J- {. w' ^7 u//通常的做法是使用你的智能合约编译之后生成的abi的json文件,该文件在用truffle compile之后,生成在build/contracts/目录下,因为我用了一个Division.sol,所以用Division.json,你可以根据你的实际情况来写。/ ]8 f/ O% L  U# S5 i/ o4 u
$.getJSON(‘Division.json‘, function(data) {
8 H$ x, o' x. x2 e- ^var DivisionArtifact = data;
0 w' r) g' O+ G$ ]4 I/ _, ?! dApp.contracts.Division = TruffleContract(DivisionArtifact);" n/ t& C( R8 l+ h' c
App.contracts.Division.setProvider(App.web3Provider);
% i8 e- o3 a2 x5 |//用智能合约中的信息来更新你的web应用,App.refreshPlots()是我例子中获取智能合约中信息并更新UI的函数1 a/ t9 R6 ~5 |: l5 A9 e% ^
return App.refreshPlots();
$ v5 L& i+ a0 T4 K( C* k4 D});
0 j5 D0 e6 |9 kreturn App.bindEvents();$ I8 b+ q) o2 C9 N- ?1 F
},
$ m+ C# K6 z" r% WbindEvents: function() {% E$ |8 H, t$ J. P4 }1 B* p' P, u3 c
/*
& G0 h/ o% [- s3 X' \* 事件绑定,这个可以根据你的UI来设置,例子中就是绑定一个button的点击操作5 g6 E: d; c( ~
*/
+ q8 @7 T4 l, Q. [) \0 e# ^$(document).on(‘click’, ‘.btn-adopt’, App.handlePlot);
8 G. ]8 V/ {5 B3 v: B' M- d},
: m5 j/ J5 l! m/ P2 `7 mrefreshPlots: function(plots, account) {
5 k/ a% ~, {! ~' R% }  P' H3 ?  b/*
- m4 J2 t6 E$ n' `/ }* 这个函数就是上面initContract中调用的用智能合约更新页面
  n7 @) j& V0 H0 [*/: v+ s- ~# c, s$ Y$ c& T
        //继续使用division这个智能合约做例子0 ^6 n& T& W, q; Y% w& \% F
         var divisionInstance;$ L" Z' P  O6 H- D
         App.contracts.Division.deployed().then(function(instance){
6 j! {. g: E, ~" a8 ^                 divisionInstance = instance;5 ^% p6 b' _, }7 D2 z
                 //getGenPlots是Division的这个智能合约的一个查询函数(不需要gas),需要3个参数
: R3 O* y- Z& b2 j                 return divisionInstance.getGenPlots(0,0,2);
! c  m$ F# s# [$ d: m* F' H$ T" B         }).then(function(results){
) N+ x+ |2 U0 c7 s, J. j" M: \                 //注意:这个地方有点意思,我原先理解也有问题,后来打印输出才搞明白
8 E% ]- e3 ~) i$ v1 {                 //智能合约返回的多个结果变量在这里就是一个results数组
& S( l8 H( w. ]4 L, C                 //数组的每个成员就是智能合约返回的每个结果变量/ w' x1 m7 d' m+ T
                 //以getGenPlots为例,Division.json中定义如下:
$ J4 r$ h3 ~; j% E0 |1 b+ [0 M                 /*"name": "getGenPlots",
9 s0 a' n: E* Y4 m& T! w1 q                     "outputs": [
! H" t% N/ {2 _" H0 m( W8 @) }                     {% z( w9 s  c9 S6 T3 R
                         "name": "",
% t; K4 m+ t* m" |                         "type": "uint64[]"
5 y( C: p& t' @: r9 C3 G                     },& {* R8 [# b6 K( `5 s/ n
                     {
% y& n5 ^- G/ r                         "name": "",! H/ e+ R7 y$ J# B
                         "type": "address[]"" W$ M$ D" j4 y' K% t* B, [, M
                     },- P8 e. I- W6 T6 J5 G$ Q
                     {
7 Q3 u: c+ \3 R3 a8 h1 S* \' n                         "name": "",, I. B. e% P; k4 m6 V+ x2 f' h
                         "type": "uint256[]"
  N2 `: w  K0 x6 j7 `7 d                     },
5 b/ `5 F/ W  a. Q3 K$ `                     {+ b" [7 m3 C9 {! z" D" z
                         "name": "",
- w) H4 N% X# o. Y                         "type": "uint8[]"
! I$ y! k. H& {                     }9 Z8 o& X/ I, {2 x) x6 i8 n  U
                     ],
3 w4 N% _% c) e1 O" [+ Q$ t+ c                     "payable": false,6 \( l' t" n6 r/ Q6 \$ g
                     "stateMutability": "view",
6 ^1 U, ?# w( \* }                     "type": "function") b8 a! k' o" w) `
                  */$ v# p( ?* Z# s) u, V1 n9 [- j0 a; A+ y
                  //那么:results[0]是uint64[]0 V. t$ @- C5 l" w
                  //      results[1]是address[]...
( X' u$ d$ q1 T" Y8 w                 console.log(results[0].length);
6 F; Z) _- \1 i) c5 V         }).catch(function(err){& j: H; Z: \/ M7 G4 G; _
                 console.log(err.message);
; u- c, y5 T" V. F4 |         });+ Z- s+ h( \, k
},
0 I* _$ _$ O, H/ a5 NhandlePlot: function(event) {
  p/ @& [- S- v. t/*" J% n  M, R  i, u8 ?3 a$ R
* 这个函数就是上面bindEvents中调用的响应函数,演示要花eth的函数调用
4 n; H3 j% |  I2 d4 W */
8 W1 B' I; `: Y. l& k" M    event.preventDefault();- g# u7 @& J3 s( s/ ]* I1 G2 c
    //从event中获取参数,这是jquery的东西,跟web3无关4 A5 D! o/ m% E! j3 e, u6 |3 z! U7 \
    var plotId = parseInt($(event.target).data('id'));# g. y9 {9 k2 M' i( e7 U2 w% N8 r
    var divisionInstance;3 F0 B- x7 F2 `- [
     //获取以太坊帐号信息2 x$ S. j, W% |( Y. {
     web3.eth.getAccounts(function(error,accounts){
& p0 j3 Z' O+ O$ I% Z         if(error), U& {! Q5 M2 J9 ^
         {
( ?% Q( @0 |: }& v& O8 w             console.log(error);* w( s/ m1 G7 {# i; h5 ?+ g' \
         }
5 k4 Q% U% C1 r/ f- @1 R         //我随便取帐号列表里的第3个帐号。! I+ d7 e9 d3 g) T9 O
         //因为我们连的是ganache-cli的rpc模拟服务,
9 Z# E! Q% Z2 S7 E7 ~/ i( a9 g) q         //其中给我们预制了几个有eth的帐号
$ M. j4 z+ {+ c# q         //如果安装了MetaMask插件,应该获得的就是MetaMask里的帐号
5 ]* V7 s6 B6 \( Q" ]& t3 D6 j. l         var account = accounts[2];, e* v4 Z6 W& K8 F6 F2 @
         App.contracts.Division.deployed().then(function(instance){
$ b; w! s& Y. q             divisionInstance = instance;- x/ V8 ^$ O2 {8 a* A
             //调用智能合约的buyPlot函数,该函数需要2个参数,
! S2 X, }! ^+ `' K; X3 _. |             //后面的{}中的内容跟发起以太坊交易的时候所带的默认值。5 A% N) G  l/ p  [2 P7 I0 [
             //from: 使用哪个以太坊帐号
) G" C3 Y( v9 [) x# Z# u+ P7 X0 e8 {             //value: 要使用的eth数量,以wei为单位(1eth=10^18wei)* t0 ^+ Y0 B! P1 N- C
             //gas: 矿工费,以wei为单位2 |3 Q. Q8 j: Y$ d2 |' g8 ?
             return divisionInstance.buyPlot(plotId, 3, {from: account, value: 100000000000000000, gas:6000000});
. i/ s9 S( w' H3 d        }).then(function(result){
& }4 m( M5 g  g" v& x# T            //返回结果后重新更新UI, O% n6 y% ^" O6 Z; J+ ^2 \9 t
            return App.refreshPlots();
  K2 ~. Y% t0 ?. n        }).catch(function(error){+ j1 y( A9 C0 H' C8 }+ Q+ u
            console.log(error.message);
! B' L/ ?- R) s7 }        });
; G6 K" g5 O4 n7 u' f     });4 ~. v# X4 c" T- x) S+ O
}; `) P6 U7 V; Z$ f! ~0 R" l/ p/ j( h
};
$ p% D. q) ?! b& {测试你的基于Web的DApp是否正常,可以使用nodejs里面提供的lite-server模块来当简单的webserver来用。
4 x0 P4 E, s2 f& L安装lite-server,在你的truffle项目目录下,执行:/ ]. B; S' g8 D) M% z/ I4 I
npm install lite-server( z$ d& r8 D* N, ?0 @( J5 W
安装完之后会在项目目录下声称node_modules目录,lite-server以及依赖的模块都在该目录下了。  I0 Q# e! b+ ]2 }
要运行lite-server,还需要编写项目目录下的package.json文件:
1 p% ~  t- g0 B3 p' ]9 f& A{# y# k. E, x3 h" Y+ w1 K3 X
     "name": "项目名称",0 M1 Q7 m0 T9 i/ M+ O5 k
     "version": "1.0.0",* b( v1 [' o! Y
     "description": "",
5 t7 Q1 T, P. X+ m' `     "main": "truffle.js",* }* k" r9 m' T& u' f: D
     "directories": {* `6 ?  v3 r. x* y
         "test": "test"9 P5 l. y/ F9 }( u0 B' C
     },
2 s4 h, U; k) I" v5 u' Y6 U     "scripts": {6 d- p$ K5 ^4 C$ r4 o3 @
         "dev": "lite-server",& S) S0 }0 L7 w- ]$ U" X- q
         "test": "echo \"Error: no test specified\" && exit 1"- o# w0 _2 w5 B* t0 w$ D9 C$ z1 V
     },5 _6 o. m1 S4 u
     "author": "",
& k  E1 G! [8 {7 U9 g* _7 J- u" l: u     "license": "ISC",
! r- n2 A; F/ a$ I" _8 C. t     "devDependencies": {' W% L( r; R# u/ G, `
         "lite-server": "^2.3.0"& ^2 @8 ]4 h! y6 {3 a
     },
, N- B) O. i% P3 x     "dependencies": {! Q7 n: G  Z( \& M# I2 A* m+ _3 ?
         "liteserver": "^0.3.0"" {# v% g" P& e1 f; U
     }. G3 }. p6 O6 a1 k" |" ^8 X; T
}
# T# A4 X9 }5 H' {) y5 t还需要编写bs-config.json来配置一下lite-server8 D, V5 R" |# J% S' y- V2 N' u
{
) F6 ]: u& {- Y: o% O% ^# v "server": {1 [- d* ~% ?3 U8 v: c
"baseDir": ["./src", "./build/contracts"]
0 B: L! ?; n3 N$ H/ `( i }
+ x2 y' x9 y+ H& y" w3 a}- {9 D/ f& \1 V3 C2 Q
baseDir是用来设置lite-server所提供的web服务的文件路径的。这个设置表明你可以把你上面写的app.js,依赖的各种js放到./src目录下,然后写index.html,把app.js等集成进去,就大功告成了。$ K% [6 w, A" W1 V% b3 h
启动lite-server,执行:
. \! [' ?: M9 p9 i0 l( Lnpm run dev
  O( E7 d- n7 w- w% v不仅启动了lite-server,而且会启动一个浏览器去打开页面。
1 Z, b+ S! g( v& e" d: O本文的目的是为了澄清一下写DApp的各项工具之间的架构关系,帮助技术人员更快的理解和实现自己的项目。, j2 s4 [- Q: s: c# I7 F+ D5 e. e
具体的例子网上多如牛毛,就不去写业务的具体代码了。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12