Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

星火车品
73 0 0
web3.js与以太坊通信是通过rpc的方式实现的。
0 K$ e0 h5 k) k) h8 _* W4 z) g以太坊节点本来提供了rpc的访问方式,但是因为以太坊节点的地址不确定,并且DApp需要访问钱包,所以用web3.js直接访问以太坊节点的rpc服务是不现实的。: S# C2 H1 r8 T. X5 t
ganache-cli模拟了一个以太坊的测试节点并提供对外的rpc访问方式(就是例子里经常说的http://localhost:7545或者http://localhost:8545)。同时在其中内置了M个以太坊帐号,用于测试。; v7 ~. e, e5 S2 ~) w
MetaMask是一个以太坊的网络钱包插件,它也提供了web3的访问方式。而且可以通过这个插件指定后面的以太坊节点是什么。因为MetaMask是个钱包插件,所以解决了DApp中的支付问题。所以现在的DApp都依赖它。
: r9 T2 I4 D4 K2 f+ y$ ~有一个以太坊教程,是在线学习的,大家可以去看看,如果自己本机上搞,开发DApp的基本过程都是一样的如下:* K# X- W# A: L7 u/ [! w8 |
1、安装NodeJS/ y2 Q8 ~5 G. l' W
2、安装truffle:一个开发DApp的开发框架9 H2 h2 i5 J# j$ x( g2 o. W6 D
nmp install -g truffle& R) L; H  X) e7 K5 Q& g' y
3、安装Ganache(原来用testrpc):在内存中模拟以太坊运行并对外提供rpc服务。- o0 T5 o( V+ M% k2 Y% T
npm install -g ganache-cli
& d) T' |# ~, T8 {2 t* k4、运行ganache-cli
  @2 q7 s8 A2 k1 nganache-cli
: t* [! X2 ?. E# q( w( K' l3 U* @5、生成一个DApp的项目$ W' L) y8 L( e* p- C$ [! R
mkdir project1, R# v. s) Q8 x$ A- I$ x* @& H
truffle init
" m( ^! k% X0 C2 u如果想用truffle中的某个例子,可以用( H; G/ |; n0 Q) P5 l
truffle unbox pet-shop
8 e8 H) e: e; q) _9 w“pet-shop”是例子名称
8 C; u3 ^/ z2 R4 U% h3 G6、编写智能合约
# d: z5 y$ o4 B  L/ J4 p. N- ]. f具体如何用solidity编写智能合约可参考各种文章,这里不再重复。
6 N1 `! {/ Q8 \3 t% A# y编写好的智能合约的Project1.sol文件放到contracts目录下
# [: I; `5 K6 _0 I+ _7、编译和部署智能合约
6 S, A* `3 ]% N. v在migrations目录下创建文件2_deploy_contracts.js:; b4 k; _1 L+ s; h0 C# \, \7 t
var Project1 = artifacts.require("Project1");
9 j# K! _8 C/ D* l' E/ omodule.exports = function(deployer) {+ [- r: ^6 u. E3 G3 V
  deployer.deploy(Project1);3 {5 \6 e, L; x8 d# `- c  @9 Q+ R
};/ ?) Q1 K# h7 w- o
之后执行:4 X- e6 H0 n, Z" k: w, O; Y2 H3 c
truffle compile
  u1 r( g; O% c% ^truffle migrate3 c2 V5 V5 H* i% f$ G# q9 W! S, A1 k
如果你的智能合约没有问题的话,现在你的以太坊智能合约应该已经部署到你用来测试的ganache中去了。: T5 [- ?5 M9 I5 _6 `
这里可能遇到的问题是:默认的truffle生成的项目,测试用的ganache的地址和端口会被设置成http://localhost:7545,而实际上执行ganache-cli之后的服务端口是http://localhost:8545,需要在truffle.js中修改一下:) L( D4 A8 \3 V
module.exports = {4 d% t: p/ x+ d. n# V
// See
3 E: f- d& M, |" k. V' b+ w; i// for more about customizing your Truffle configuration!
4 n- X8 a. {) S. Nnetworks: {3 k, s+ o6 ^& G7 z: _' y
development: {, {# ^" _' h3 l
host: “127.0.0.1”,: ]; t+ `* l6 c/ X  i4 j9 T
port: 7545,   //改成8545
5 G! k7 u4 ]! |4 fnetwork_id: “*” // Match any network id
3 I2 d6 {7 r: V4 D# y+ D}: Y6 Z% V( e4 i% |# ^
}5 ?# R; ~( P& u
};0 [) Y1 h( T  L, K  _; E2 C
8、编写前端的js代码跟以太坊交互
1 Y; O' |) d7 E- X通常需要如下的辅助js库:
9 `* F: c2 I  i& D
4 q- j* b1 h8 ~
' p+ X" ]) R3 [+ Y4 C+ ~* K$ B在此基础上,编辑你自己业务逻辑的js,通常命名为app.js,app.js的框架如下:& s: |' T# v) f0 Z+ n6 D
App = {5 X- ^! L; Y4 A/ t; o4 L
web3Provider: null,
+ o9 U& g/ t& e7 `. B; Wcontracts: {},) l: F, J0 a" ~# v4 G+ ?& i2 d
init: function() {
( e: M  k0 k: J//初始化你自己的页面、变量等
# C5 Q* B) l6 `return App.initWeb3();1 N# q3 s& E7 B$ R6 C; f
},3 a. ?2 q5 _# p* c
initWeb3: function() {
' s4 I! e8 i9 b) ], M5 ]/*7 t7 S/ T1 Y- r/ N1 ^
* 初始化web3:9 o/ Q: [/ o) d' ]3 r
*/
, T8 l6 t1 R: \, aif (typeof web3 !== ‘undefined’){
( e# r% s% q) Y/ p//如果你的浏览器安装了MetaMask的钱包插件,那么插件会赋值web3.currentProvider
( S9 l+ v7 e' x( K6 dApp.web3Provider = web3.currentProvider;
) @& `# ?- i; x}0 Y9 D3 o5 z5 C( x. n  V/ C7 G$ U
else! v8 V- w% B& M$ B- M7 ~
{: b5 Y. Z8 i: V! w5 n  L, c
//如果没装插件,那么创建一个基于Http的provider,这里用到的就是用ganache-cli启动所提供的rpc服务,因为ganache-cli启动的时候绑定的是localhost,所以测试所使用的浏览器也要在本机。(如何让ganache-cli绑定其他地址我还没找到)
0 X: u. c+ R+ _5 p; d8 [' o5 kApp.web3Provider = new Web3.providers.HttpProvider(‘http://localhost:8545’);
" J( h0 l  i& v+ I# l: W}3 h, o9 m; @( g" m" M7 M
web3 = new Web3(App.web3Provider);
, y; c# {: h1 M. xreturn App.initContract();% _9 s* o# ~( `$ X$ t  |, D
},
( q3 A8 W2 x- }( j' b: Q  SinitContract: function() {4 T! Q: f* m# L. \+ L3 ^( v
/*
; e9 v: p$ k1 Z! A( {- f8 ]* 初始化智能合约,实际上就是为你的智能合约创建一个对应的js对象,方便后续调用+ }  J4 g* E1 U, {
*/
4 J2 X/ U6 p% X6 k8 v% x//通常的做法是使用你的智能合约编译之后生成的abi的json文件,该文件在用truffle compile之后,生成在build/contracts/目录下,因为我用了一个Division.sol,所以用Division.json,你可以根据你的实际情况来写。
  l) k7 h! Y* J( p0 @7 Z$.getJSON(‘Division.json‘, function(data) {2 s4 i# t# y0 H
var DivisionArtifact = data;( z/ R8 C2 \; \0 D$ m: m
App.contracts.Division = TruffleContract(DivisionArtifact);
3 w4 x. V: i* M9 [/ n; ^5 TApp.contracts.Division.setProvider(App.web3Provider);
5 ]: x& S" A4 \6 i//用智能合约中的信息来更新你的web应用,App.refreshPlots()是我例子中获取智能合约中信息并更新UI的函数
  J( L# i8 R  J* jreturn App.refreshPlots();
4 u4 h1 A& b7 Y5 U; Q});, W; A0 B: m5 N( t
return App.bindEvents();: Z% M( `3 ~% \) x4 }
},. h% F8 J4 T: u' `6 _
bindEvents: function() {' u8 }& ^* ]) J. D
/*
- H1 d7 F# g9 f! F% x( I  Y* 事件绑定,这个可以根据你的UI来设置,例子中就是绑定一个button的点击操作
- d" u3 Z' x# ]' @& H+ J*/& p( U+ A/ T4 y4 k* j, `3 ?
$(document).on(‘click’, ‘.btn-adopt’, App.handlePlot);
% M% Q6 V, E8 r' Z},
2 l( ?* f/ y# {' U" O/ J' |refreshPlots: function(plots, account) {4 y/ z: L" ]" k: K) e
/*+ e( S. \5 `/ r
* 这个函数就是上面initContract中调用的用智能合约更新页面
+ Y' L$ h0 c7 @8 W; l3 x" D9 b*/# i( Q5 j4 {: F/ k- l
        //继续使用division这个智能合约做例子
" _& l: t  f& ^* j         var divisionInstance;
, t9 Q: c$ h& f) u; F9 Z         App.contracts.Division.deployed().then(function(instance){0 Y0 |8 i+ Y  J9 [4 X$ S) B
                 divisionInstance = instance;
" C0 r% F$ h: Y4 Y/ V# b7 V                 //getGenPlots是Division的这个智能合约的一个查询函数(不需要gas),需要3个参数5 ~; v# U* B; m9 _0 L
                 return divisionInstance.getGenPlots(0,0,2);7 y& [6 I* S' M; R" |) \# o
         }).then(function(results){% |/ l  n: u9 e8 }1 J. J
                 //注意:这个地方有点意思,我原先理解也有问题,后来打印输出才搞明白
" l& d2 z* ^/ m7 c! W4 a1 \  }                 //智能合约返回的多个结果变量在这里就是一个results数组
2 ?% k+ |; f9 ^- F0 a! }$ ?+ S                 //数组的每个成员就是智能合约返回的每个结果变量  _* z. @+ b+ q9 R6 y2 W# F
                 //以getGenPlots为例,Division.json中定义如下:
+ `+ ?' u" k$ C) f2 ^                 /*"name": "getGenPlots",
- t3 w2 E% i+ T) R) [# W                     "outputs": [
  g8 @4 I* ^2 E) }                     {
- h1 c% o1 P) ~3 z: N, `                         "name": "",5 [8 @( X# f/ p; W  N
                         "type": "uint64[]"
* S1 j0 s' r9 ^+ N  L                     },1 b: x% F% l- W) V# D
                     {8 m- u& Q: p9 _1 a
                         "name": "",  w* V% t9 v- r! K, i
                         "type": "address[]"
2 K" o* J3 ~" h3 q& [; U7 ~                     },, n+ w( O* ^$ J" p
                     {
' M% G% P" N% l7 e5 A                         "name": "",
9 P4 w! R% ?  m% L( o                         "type": "uint256[]"  |6 B9 E5 U0 ]9 g" @  w! F
                     },) {- Y) f3 P  q. m
                     {
( S- A; u6 d/ P, a/ }8 F) D! Y+ O                         "name": "",
, B# C$ p: N" C' h                         "type": "uint8[]"! B3 ~% A1 N4 T  T. Z1 F
                     }
$ i' v5 C) N& O1 s" n* w, u+ J                     ],' }0 g- h5 f  _% _4 j9 ]$ g; H
                     "payable": false,9 a% T& Y, k& ~
                     "stateMutability": "view",
9 u% R. x1 j6 R/ {* o* \                     "type": "function"
7 U- S! Q! O, @& Y" j! _5 v                  */
/ Z1 h. _" W8 N5 D! S                  //那么:results[0]是uint64[]
! F+ `" S0 R! E: D# E: P                  //      results[1]是address[]...
: W# V) g+ T9 s9 f                 console.log(results[0].length);5 ]0 G8 H$ G# ?% P
         }).catch(function(err){' B0 n8 e/ e% G; u
                 console.log(err.message);
# H" D4 V6 f% @         });
$ }0 h. T% G, M },: S. L7 Q4 e6 b' U( R: r
handlePlot: function(event) {
6 p. M0 F. Q* T7 f+ M- N" R/*
+ R3 z" A3 }+ y! s9 c" Q' _. D * 这个函数就是上面bindEvents中调用的响应函数,演示要花eth的函数调用& u: }: M- v9 K0 _
*/
# ~: p1 g, C: k8 M/ S    event.preventDefault();$ F7 ?( H  u5 w1 M1 ^" ]0 Q
    //从event中获取参数,这是jquery的东西,跟web3无关
6 c8 h! I$ Z" @8 k" f. b    var plotId = parseInt($(event.target).data('id'));+ ]! z& d8 ~! p* ~8 T. T
    var divisionInstance;
4 h6 c# u$ W# J$ d$ c8 \! }     //获取以太坊帐号信息: a1 E# F* @2 Y+ c
     web3.eth.getAccounts(function(error,accounts){4 j% Q6 o! L. ~" I
         if(error)* b, \& R& o  o, k
         {
" ^% j: Z1 {% a9 Q! i             console.log(error);
' s* F' e9 S" T         }
# f1 l, H: z0 n  B( j7 D! z         //我随便取帐号列表里的第3个帐号。: W, C" Y9 o/ M; A, v' j5 I& b
         //因为我们连的是ganache-cli的rpc模拟服务,* F; `% {0 l6 ^6 Y( q9 s
         //其中给我们预制了几个有eth的帐号: l2 l( H. h. I5 B! J
         //如果安装了MetaMask插件,应该获得的就是MetaMask里的帐号7 ?2 C$ m( t' F. u% D! A
         var account = accounts[2];
8 R- V! A2 T* i! H         App.contracts.Division.deployed().then(function(instance){. f# o  m& [' W  s, x
             divisionInstance = instance;& @5 T; C/ y. f2 ~0 \( _
             //调用智能合约的buyPlot函数,该函数需要2个参数,5 }) h% v2 F$ O/ h6 x
             //后面的{}中的内容跟发起以太坊交易的时候所带的默认值。
; ?, w% ^- j, _: D+ s+ }             //from: 使用哪个以太坊帐号6 L7 v6 t1 \+ ^* o8 i! A( ?4 C
             //value: 要使用的eth数量,以wei为单位(1eth=10^18wei)0 o0 a0 X6 d2 d$ R* d& q& ]$ V7 v
             //gas: 矿工费,以wei为单位4 |: L7 Y1 U: h$ v# e
             return divisionInstance.buyPlot(plotId, 3, {from: account, value: 100000000000000000, gas:6000000});
2 c2 c% X+ N% |2 ]0 R/ ~% l        }).then(function(result){2 F. _! r. r# O, l
            //返回结果后重新更新UI
; c0 R/ X+ F" N/ W/ |# W            return App.refreshPlots();
. Z, R5 w, b0 s4 @% O' U, \4 t        }).catch(function(error){7 ]' D3 r7 C5 k5 j
            console.log(error.message);! Q( C5 [" k$ n4 w, q, F$ d% ]1 r) A
        });4 l# ^" M* g' A/ t
     });* w) O( C/ ~) c" E
}' }3 C% E6 b" n6 F) G6 D
};$ S' X7 k. x8 K3 [3 Q0 c( B6 T
测试你的基于Web的DApp是否正常,可以使用nodejs里面提供的lite-server模块来当简单的webserver来用。3 k$ U$ t  U- y4 L: r; z: o5 B
安装lite-server,在你的truffle项目目录下,执行:
! o7 f$ S: e& F" @0 dnpm install lite-server
; S) Q7 ]5 |# e- s- c( `安装完之后会在项目目录下声称node_modules目录,lite-server以及依赖的模块都在该目录下了。
1 y" _- ]% [4 m8 Z$ k: A要运行lite-server,还需要编写项目目录下的package.json文件:4 Y# y$ ~8 X9 l9 u4 Y
{, N2 {! F0 w( s- m& i4 P& _& x: F2 B7 N
     "name": "项目名称",5 E/ V  K4 x1 o9 k& L' O
     "version": "1.0.0",
" j! w! N# t! R$ L" u2 R6 j3 z4 u     "description": "",( V3 g% P! Z+ p' w: m- e
     "main": "truffle.js",5 B  q9 y# N% N) M* m
     "directories": {; {6 @: q# o) M! J! o; C  U/ s) Y9 N
         "test": "test", F  y7 Q3 ~6 {9 c+ [
     },/ O% O, g& B0 A: Y5 `8 S1 I1 V
     "scripts": {( ?- z! j/ _) C; }  l* p: a3 ?
         "dev": "lite-server",- Y. n& ^+ G9 w, ?( p0 i' [
         "test": "echo \"Error: no test specified\" && exit 1"
$ T4 U/ x- ?7 a     },# [+ S; m+ v) F
     "author": "",
: G# S3 g0 \8 h* P( p& Q     "license": "ISC",
% Q" K" _. F# \8 h. v     "devDependencies": {7 Y7 g1 u- V) N8 x6 p
         "lite-server": "^2.3.0"
  J. N6 ~6 v- k3 T     },
4 I5 R0 ]9 U  t+ C5 }6 U* @+ k     "dependencies": {' g! b9 v; E2 d  t
         "liteserver": "^0.3.0"" c  Y' X* |( Y& Q) }! V# a
     }6 B) s, L0 U( j: j* K
}+ u0 H2 [6 Z* b
还需要编写bs-config.json来配置一下lite-server
# M1 C$ w  K+ u+ @  c{; p. i  c0 y" R( {7 `4 w2 T
"server": {, ^  u3 j  ~  Y
"baseDir": ["./src", "./build/contracts"]! T8 Z" U( e2 O. g7 `: I
}
( I4 M5 B' M& H$ K( B}
8 A3 N7 p& b; s% BbaseDir是用来设置lite-server所提供的web服务的文件路径的。这个设置表明你可以把你上面写的app.js,依赖的各种js放到./src目录下,然后写index.html,把app.js等集成进去,就大功告成了。' K" x7 B5 G" X# H, R3 v6 ~
启动lite-server,执行:3 x3 r4 \* [$ S. U
npm run dev
* R! g; L1 b( _( q/ @- w, C' G/ D4 K不仅启动了lite-server,而且会启动一个浏览器去打开页面。, {6 i" `8 \* f) A& p% B5 K
本文的目的是为了澄清一下写DApp的各项工具之间的架构关系,帮助技术人员更快的理解和实现自己的项目。
# z8 w5 K$ I. k5 U具体的例子网上多如牛毛,就不去写业务的具体代码了。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

星火车品 初中生
  • 粉丝

    0

  • 关注

    0

  • 主题

    12