Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

星火车品
117 0 0
web3.js与以太坊通信是通过rpc的方式实现的。
6 S3 v4 A- C. N以太坊节点本来提供了rpc的访问方式,但是因为以太坊节点的地址不确定,并且DApp需要访问钱包,所以用web3.js直接访问以太坊节点的rpc服务是不现实的。/ p2 {. ?8 k+ Y1 G& x3 S5 @
ganache-cli模拟了一个以太坊的测试节点并提供对外的rpc访问方式(就是例子里经常说的http://localhost:7545或者http://localhost:8545)。同时在其中内置了M个以太坊帐号,用于测试。
3 r6 @- `2 z7 b3 I5 b3 s! t- h) @2 IMetaMask是一个以太坊的网络钱包插件,它也提供了web3的访问方式。而且可以通过这个插件指定后面的以太坊节点是什么。因为MetaMask是个钱包插件,所以解决了DApp中的支付问题。所以现在的DApp都依赖它。# H4 p& l5 j' M+ A9 J! ~6 C
有一个以太坊教程,是在线学习的,大家可以去看看,如果自己本机上搞,开发DApp的基本过程都是一样的如下:; x! b4 X2 Z5 l! y, [7 t/ ]5 c
1、安装NodeJS
* w. f2 w0 @+ g9 J% X* O# s2、安装truffle:一个开发DApp的开发框架
' [" J1 S4 W# e5 u( @nmp install -g truffle
/ u: h. l4 s! u: r; M  C3、安装Ganache(原来用testrpc):在内存中模拟以太坊运行并对外提供rpc服务。
- g. V4 u$ W" qnpm install -g ganache-cli8 \. i# K# A+ I* W
4、运行ganache-cli6 _: b) O! ^) a- c
ganache-cli6 M  Q# ~3 a. @: `5 f1 f
5、生成一个DApp的项目" x/ b- a' A' i( d% _4 _" O
mkdir project1
' W. ]; r+ {  |' E2 ^truffle init
4 @8 g9 ~3 @. A+ T6 e* f如果想用truffle中的某个例子,可以用3 h$ Z$ q: \, N1 f
truffle unbox pet-shop& K) l8 [: [6 c' [- [# k
“pet-shop”是例子名称2 K9 y* e! V$ K/ n* \. c
6、编写智能合约
! Z8 a( E2 p7 q  l7 _+ Y具体如何用solidity编写智能合约可参考各种文章,这里不再重复。
+ r& G+ C5 r( p6 b$ ?; o7 \编写好的智能合约的Project1.sol文件放到contracts目录下
$ p6 r9 E0 d0 H7、编译和部署智能合约4 q. N4 Q  e0 ]7 ?' @8 f' Y# h, `
在migrations目录下创建文件2_deploy_contracts.js:
" N; Q9 h* B4 [( n& J# k5 Gvar Project1 = artifacts.require("Project1");. w6 i" M5 d# k4 X3 a/ V1 I
module.exports = function(deployer) {- U- m' K; X! f6 W/ G
  deployer.deploy(Project1);
* G7 ^) j0 v5 Q};; `% l! b% l: v( k- f5 x
之后执行:8 G+ d, ]9 U" g+ H$ H0 m6 _- |2 R# r
truffle compile; q- l" x- J2 w
truffle migrate; y$ R/ |/ N' H+ H
如果你的智能合约没有问题的话,现在你的以太坊智能合约应该已经部署到你用来测试的ganache中去了。
2 F; p. {* X# C  W这里可能遇到的问题是:默认的truffle生成的项目,测试用的ganache的地址和端口会被设置成http://localhost:7545,而实际上执行ganache-cli之后的服务端口是http://localhost:8545,需要在truffle.js中修改一下:
0 Q' t! S; M2 |) s7 Z7 lmodule.exports = {
; q# E. c7 H% r- x9 e! K) H// See ! r8 g) S& D  b; t
// for more about customizing your Truffle configuration!
5 _% w. p: i0 ~' r4 Anetworks: {6 S# Y6 ^6 Z$ C: A! m5 Y# C
development: {# R/ R4 J- }: L$ c; c5 V- p
host: “127.0.0.1”,. S, g: g5 L7 F% ], L
port: 7545,   //改成8545) ?+ @7 L* \. S9 A) T! w; P( N
network_id: “*” // Match any network id
, P7 ?( c# {6 ^% k& R}
& x9 G2 ^6 l6 o! m: M}
) H8 K% I0 ^! ]  B};, J: d+ q# Y" s& s% U
8、编写前端的js代码跟以太坊交互
, d6 @' Q) A9 l通常需要如下的辅助js库:
! D: K5 i! c9 }9 {+ @" f/ {: [
7 `& P3 y1 O% ~3 e  C' P, r  R9 Z. K8 D' w: Q2 v8 T
在此基础上,编辑你自己业务逻辑的js,通常命名为app.js,app.js的框架如下:
9 X: M& t/ s( JApp = {
: L0 n: s$ S5 u8 }9 z; S4 _web3Provider: null,8 J& x! O2 p$ J/ Q1 b3 i+ j
contracts: {},0 X5 R: D6 G' {
init: function() {3 L3 F9 z4 n, [. {6 M9 c' M" y
//初始化你自己的页面、变量等
: A1 ^% a& k6 y; x3 Areturn App.initWeb3();
$ C% o' h7 p" B: g8 p},8 x. N6 p9 Z8 E4 M( I* \6 b
initWeb3: function() {9 T! ~8 C! G& Y3 g2 o+ C
/*
! j3 r2 R3 e, O0 J( d* 初始化web3:( }0 i% r! Q0 x) b3 z' [8 g, _9 l
*/0 Q# l& q: V1 r
if (typeof web3 !== ‘undefined’){$ V" m1 Q1 D( \; w! G) Z7 d& B
//如果你的浏览器安装了MetaMask的钱包插件,那么插件会赋值web3.currentProvider8 P* w, D' k4 j  G  m  Y1 G
App.web3Provider = web3.currentProvider;
, d0 o8 P  x# K6 z, Z}. F0 f1 s8 K6 `# v1 Q* J! h( G4 F
else9 N4 i  G2 i. C0 [4 I* s% C
{
" Y- T9 m1 P4 ]: C9 p% \# |//如果没装插件,那么创建一个基于Http的provider,这里用到的就是用ganache-cli启动所提供的rpc服务,因为ganache-cli启动的时候绑定的是localhost,所以测试所使用的浏览器也要在本机。(如何让ganache-cli绑定其他地址我还没找到)
$ D( F# I8 `( y) {5 G* @9 z0 iApp.web3Provider = new Web3.providers.HttpProvider(‘http://localhost:8545’);8 ~7 \0 A3 p( }$ ]. A( y& R
}
( {" e8 m3 f8 f4 {web3 = new Web3(App.web3Provider);
% _: F& k: h  B# q9 B7 C4 h( @return App.initContract();% I% c" E4 i  L
},5 W# n* i. D) S3 W' O/ V4 a
initContract: function() {
0 N, g/ I& O5 n/*
5 m; g/ D1 d; G0 r  {* 初始化智能合约,实际上就是为你的智能合约创建一个对应的js对象,方便后续调用
. ~5 m( F6 T% o' \8 Y*/9 d/ ]5 ?$ m: t6 T% D
//通常的做法是使用你的智能合约编译之后生成的abi的json文件,该文件在用truffle compile之后,生成在build/contracts/目录下,因为我用了一个Division.sol,所以用Division.json,你可以根据你的实际情况来写。9 @; L. V0 T( i- k/ f, a+ A, w7 N
$.getJSON(‘Division.json‘, function(data) {* M! y4 [) ~( ~* r" L/ m, N
var DivisionArtifact = data;
) y6 Q) n0 r$ I+ Q" XApp.contracts.Division = TruffleContract(DivisionArtifact);
' d. H, q0 H3 V$ T; T/ B9 cApp.contracts.Division.setProvider(App.web3Provider);3 q( ~4 n8 _" g( _# z
//用智能合约中的信息来更新你的web应用,App.refreshPlots()是我例子中获取智能合约中信息并更新UI的函数
& ^/ M/ u6 o- n" y% |0 Qreturn App.refreshPlots();
' r9 w9 @# e8 n) X3 [});/ B" @  Q- I1 S
return App.bindEvents();
3 ^6 T/ Y0 T  C/ M' {},# \, T9 c+ F0 O" `! n
bindEvents: function() {3 V8 \, a0 w6 B0 S6 h  E& y: ]
/*. e2 E8 B. z1 m' N7 ]' ]. Y
* 事件绑定,这个可以根据你的UI来设置,例子中就是绑定一个button的点击操作
1 e% j# @# m5 g: ^9 x! [# d*/) h) s1 p! t2 v& |6 t# m# I$ O
$(document).on(‘click’, ‘.btn-adopt’, App.handlePlot);
) Y) B# d& E& [2 m3 D" \* P, w8 G},1 N; s/ K  f3 J! L
refreshPlots: function(plots, account) {
: D, F3 }# v0 V& r3 i9 I" U/ C/*6 P  Z: R, [" K: y: t# ~6 l
* 这个函数就是上面initContract中调用的用智能合约更新页面2 f, M! X* a( u8 l7 k
*/
6 f2 Q4 D# l3 ]- R: x( `        //继续使用division这个智能合约做例子! @/ S* m* e. m; P* |5 c
         var divisionInstance;( }1 d, q0 j1 z8 D" b7 }
         App.contracts.Division.deployed().then(function(instance){9 t. X& i+ b* X9 D7 y  ]) L  l
                 divisionInstance = instance;. N, A/ }2 ]2 G  @) o! {9 p2 W* f. M
                 //getGenPlots是Division的这个智能合约的一个查询函数(不需要gas),需要3个参数; B* c5 z* g$ o  i! |) ?
                 return divisionInstance.getGenPlots(0,0,2);
/ y6 l  p& c$ G) U: n) S  ^( C         }).then(function(results){  g5 w# _2 }$ D4 ]
                 //注意:这个地方有点意思,我原先理解也有问题,后来打印输出才搞明白
5 a  {7 C8 Z! t  \6 E6 C! n- y3 P2 g                 //智能合约返回的多个结果变量在这里就是一个results数组, o+ i. N$ y/ Z; h+ `
                 //数组的每个成员就是智能合约返回的每个结果变量9 q  `$ X: \: b1 D2 S; ~2 [
                 //以getGenPlots为例,Division.json中定义如下:
8 f- l! `% S  E9 V* C* E& D                 /*"name": "getGenPlots",4 Q, [4 S5 M$ u# ~6 m; e4 }2 m
                     "outputs": [
* v# F& w, S2 l  {                     {: M3 R8 U6 G  B( Z- D
                         "name": "",
! ^" z* b& V5 }) c! W                         "type": "uint64[]"
1 R/ b5 ?/ i# F, g                     },9 v6 q( j+ f( O: h" n1 m* C, I
                     {1 m$ o9 |+ x& G* F1 n
                         "name": "",
/ m% l1 I# O" |. q4 J  z5 H4 P                         "type": "address[]"
1 ?) S! ~& o8 V0 M/ F4 k                     },$ ?  A7 P5 C  A8 U3 K. x/ U
                     {  o6 Z) k% V$ _
                         "name": "",
1 V# n* ]. I( ?/ \6 F2 u0 U                         "type": "uint256[]"- m# p+ ~1 r) G* \0 k: P2 q. l
                     },) A4 I: \; n; W9 d  Q, `
                     {( g+ H$ H* ]9 }: |2 ^  V
                         "name": "",  `9 Q6 p; F$ a; l. C
                         "type": "uint8[]"8 z- G3 J2 g! }+ N2 G5 Y' s0 m
                     }
$ z- E& X3 x, `; x2 P                     ],* |! [. H- B+ i5 a! g8 j0 F
                     "payable": false,/ T  W& l4 e9 L9 ?
                     "stateMutability": "view",
  }% ~& _4 H/ G! F+ g                     "type": "function"
4 X! j3 O# w0 q3 {; V$ g                  */
: H- k4 j7 p# q3 ]- _% ]                  //那么:results[0]是uint64[]
. p+ E+ F4 O& O5 }; p. C! _) v                  //      results[1]是address[]...
5 O' l" b# O0 T& c8 m                 console.log(results[0].length);- f( ]. r) z, }$ b
         }).catch(function(err){+ @) P/ r  A4 W# S6 g9 k
                 console.log(err.message);
5 p# X3 j6 p8 N1 J6 w4 ^7 |         });
; T. ^1 h. p: U# {5 i },
% R& d' P7 m5 c+ B% g, c$ BhandlePlot: function(event) {4 ~) U/ u, S1 ^3 r* c
/*6 f  v% X0 w9 |4 o. w# S7 w% {* ^
* 这个函数就是上面bindEvents中调用的响应函数,演示要花eth的函数调用9 F# m( C' M; X* X! p4 i' h/ o
*/
3 N4 T9 y  D$ {7 f+ [    event.preventDefault();0 w% m5 B/ {) c6 \
    //从event中获取参数,这是jquery的东西,跟web3无关
8 x" \& X8 y4 q4 j- V; w    var plotId = parseInt($(event.target).data('id'));
+ |2 ~! F8 _) d4 ^7 O. N    var divisionInstance;( p3 o, b6 v+ @# l
     //获取以太坊帐号信息5 a6 n9 R- D7 `% D* P
     web3.eth.getAccounts(function(error,accounts){2 P1 i+ {- r# f" s9 q9 e1 z3 A! `- B
         if(error)
$ L  }* o% k/ R" a5 l  C. Z         {/ O" _5 c7 w1 J9 ?; ~* b
             console.log(error);
8 e1 T: K" `. i3 X. E/ D         }2 H( B6 z7 M4 s
         //我随便取帐号列表里的第3个帐号。. S5 M* z) S3 U2 R" b. u! M
         //因为我们连的是ganache-cli的rpc模拟服务,0 d) y: u1 H7 x
         //其中给我们预制了几个有eth的帐号. [, K8 M3 X1 ?: ?/ C" `
         //如果安装了MetaMask插件,应该获得的就是MetaMask里的帐号6 S8 v. b( a: O& m. b
         var account = accounts[2];+ k" c0 N/ K3 o6 }; m1 J8 q
         App.contracts.Division.deployed().then(function(instance){. j% W8 @6 y& U! i1 x0 X8 D
             divisionInstance = instance;0 l$ B: x& a5 I4 Q& s* N
             //调用智能合约的buyPlot函数,该函数需要2个参数,* F. B1 g+ [; N
             //后面的{}中的内容跟发起以太坊交易的时候所带的默认值。
1 t; v- @' x: K( E( ~: ]) ~             //from: 使用哪个以太坊帐号
. D" |. ^! p( o4 K% I. \             //value: 要使用的eth数量,以wei为单位(1eth=10^18wei)
: O0 Z9 u' Y, Q1 k* F$ ?( |( j             //gas: 矿工费,以wei为单位) @9 [" |5 }+ Y3 H& m6 k4 X( I$ I
             return divisionInstance.buyPlot(plotId, 3, {from: account, value: 100000000000000000, gas:6000000});( f( m: y3 Q1 d
        }).then(function(result){
  W0 Y/ U6 Z% E4 ~+ L9 T            //返回结果后重新更新UI" \9 i, ^" X1 Z( o( b4 S7 N: b
            return App.refreshPlots();# N  S+ m$ D$ x
        }).catch(function(error){: N6 u; [9 N0 \5 d2 h5 m" C
            console.log(error.message);0 R' X& C7 [9 u( E4 b9 ^- G5 X
        });4 x% s+ [# ]/ z8 O) N+ F! U
     });
1 |9 T9 Q6 `1 H' z }' x* }+ K5 U" }, x
};( w. X! H0 w% }6 U
测试你的基于Web的DApp是否正常,可以使用nodejs里面提供的lite-server模块来当简单的webserver来用。
" f/ I3 O! B# v( x安装lite-server,在你的truffle项目目录下,执行:
- Z* l7 D+ }5 Q  J9 ?4 s+ qnpm install lite-server
$ ~: V- r& ]% H$ t' r9 R! N  o) _安装完之后会在项目目录下声称node_modules目录,lite-server以及依赖的模块都在该目录下了。5 O" x5 L* G1 S# d& V- Z4 ?4 M
要运行lite-server,还需要编写项目目录下的package.json文件:$ W! F& g% Z9 x/ i
{
! _: {- K6 c0 m5 S4 _/ ^# Y     "name": "项目名称",6 r) P8 i0 g3 h5 I
     "version": "1.0.0",. T) X% L" I) }9 F
     "description": "",
, k4 E( y" O) P$ ^: w- V& y     "main": "truffle.js",
$ w& c! ?% s* W. {: _2 H     "directories": {
; M( G3 s6 o+ ~- ^- l, o         "test": "test". b8 j( c$ k/ b( @9 r
     },: j8 t( a* H3 h1 ]% H9 H8 r
     "scripts": {; Q" ^+ N4 V: F( T; |* {& Z
         "dev": "lite-server",
9 m: n. Z! a+ @: V) s0 h9 H) t         "test": "echo \"Error: no test specified\" && exit 1". j0 S: f6 Y1 J( _0 R
     },- x2 a. y! Q6 j9 u7 M
     "author": "",1 k; T+ X/ v6 @- [3 X1 j
     "license": "ISC",
" {: G/ r: S9 w, {* V3 W4 u" O& k     "devDependencies": {
) K  S! b: P( C; r( K# B" b1 G" J         "lite-server": "^2.3.0"
! }# O& U) E4 X$ u     },
& w6 P- G9 e( N3 a- x  R# s2 m     "dependencies": {2 Q: M! R( n( v# o+ [- ^% x
         "liteserver": "^0.3.0"" S  w/ D2 B- ~( }; K( b! [( ]3 H
     }& b. ^# P9 x* [8 v; S* q
}
1 H  G: `* s6 A( v( q+ |+ B还需要编写bs-config.json来配置一下lite-server
; C8 N& ^) q/ ^+ w( y{
% N. R2 b! w2 C) } "server": {0 o/ x0 }3 Z- b- R
"baseDir": ["./src", "./build/contracts"]6 y% g' ?" D3 i2 d' P' S4 [
}. S% S3 _. X- N: U& t
}
. q* |% M2 S/ q# \baseDir是用来设置lite-server所提供的web服务的文件路径的。这个设置表明你可以把你上面写的app.js,依赖的各种js放到./src目录下,然后写index.html,把app.js等集成进去,就大功告成了。4 a+ I2 Y7 c  s4 B+ R; s) D3 `
启动lite-server,执行:' ?* R$ ]+ d5 g
npm run dev% ~6 h" [  n/ _) i/ r* z& W% k
不仅启动了lite-server,而且会启动一个浏览器去打开页面。! K8 U& [5 s0 E; l/ e/ N, O
本文的目的是为了澄清一下写DApp的各项工具之间的架构关系,帮助技术人员更快的理解和实现自己的项目。% r, n7 F9 r8 w& u' g
具体的例子网上多如牛毛,就不去写业务的具体代码了。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12