以太坊DApp開發(fā)初探

文|李偉志

? ? ?? 關(guān)于“以太貓”的流行啡邑,相信不少人都有所耳聞,甚至入手養(yǎng)過幾只昂勒。從游戲性來說蜀细,其本質(zhì)就是一個(gè)簡單的收集交換類游戲,然鵝戈盈,是區(qū)塊鏈賦予了它魅力奠衔,讓用戶每一只貓永遠(yuǎn)不會(huì)消失、不被篡改奕谭,更重要的是可以炒(滑稽臉)涣觉,于是今天借此機(jī)會(huì)一探以太坊應(yīng)用DApp的開發(fā)過程以及開發(fā)中遇到的坑。

????????以太坊DApp介紹

????????以太坊是一個(gè)區(qū)塊鏈公有鏈平臺(tái)血柳,和比特幣類似官册,以太坊也有其代幣--以太幣,可在挖礦难捌、交易中獲得膝宁,然而,說到以太坊和比特幣的區(qū)別就是其支持智能合約根吁,一個(gè)智能合約由代碼和數(shù)據(jù)組成员淫,和其他編程語言中的類類似,一個(gè)以太坊分布式應(yīng)用DApp由眾多智能合約組成击敌,每個(gè)智能合約都有其獨(dú)特的地址介返,可以看做以太坊上的一個(gè)賬戶,可以存取以太幣沃斤,作用就像一個(gè)裁判圣蝎、中間人。一個(gè)簡單但不是很恰當(dāng)?shù)睦泳褪琴€博衡瓶,我和小明打賭明天會(huì)下雨徘公,輸?shù)娜私o贏的人一百塊,這種情況我們?cè)诂F(xiàn)實(shí)中一般會(huì)以下面兩種方法實(shí)現(xiàn):

????????依靠朋友間的信任哮针。等明天到了关面,根據(jù)下雨與否進(jìn)行交易。但這種方法一般不可行十厢,因?yàn)楫吘故桥笥训忍數(shù)娜藭?huì)自動(dòng)把昨天的打賭作為玩笑話,而贏的人也礙于面子不好意思要錢寿烟,所以交易無法達(dá)成澈驼。

????????依靠公證的第三方。OK筛武,我們這次認(rèn)真點(diǎn)缝其,找一個(gè)彼此都認(rèn)識(shí)的朋友小方作為公證,把我和小明的賭注一百塊都先存著徘六,等明天到了再給贏的人兩百塊内边。這種方法確實(shí)比第一種要好,但還是害怕就是第三方拿著兩個(gè)人的賭注夾帶私逃了待锈,這對(duì)交易雙方的損失更大漠其。

? ? ?? OK,智能合約就是為了解決以上的信任問題而誕生的竿音,由于智能合約存放于區(qū)塊鏈和屎,而區(qū)塊鏈具有的不可抵賴和不可篡改性,使得智能合約比現(xiàn)實(shí)中任意一個(gè)機(jī)構(gòu)的公信力都強(qiáng)春瞬。其實(shí)柴信,區(qū)塊鏈去中心化思想最大的優(yōu)勢(shì)就是解決了信任問題,而現(xiàn)實(shí)中最常見需要解決信任問題的場景莫過于涉及貨幣交易宽气,從以太坊的眾多DApp列表https://www.stateofthedapps.com/中看到随常,大多數(shù)都是關(guān)于交易、賭博性質(zhì)的應(yīng)用萄涯,可以說“以太貓”的橫空出世刷新了人們對(duì)于區(qū)塊鏈應(yīng)用的固有認(rèn)知绪氛。

開發(fā)準(zhǔn)備

開發(fā)以太坊DApp需要安裝以下環(huán)境或工具,以Mac OS X為例

$ brew install node以太坊DApp其他開發(fā)工具都是通過npm安裝的涝影,node.js大法好枣察,mac用戶可通過homebrew安裝。

$ npm install ethereumjs-testrpc以太坊提供的區(qū)塊鏈測試環(huán)境燃逻,所有節(jié)點(diǎn)都是虛擬的存在內(nèi)存中序目,啟動(dòng)后默認(rèn)創(chuàng)建10個(gè)賬戶。讀者也可以選擇安裝geth搭建私有鏈唆樊,使用真實(shí)節(jié)點(diǎn)存儲(chǔ)宛琅。

$ npm install web3以太坊提供讀寫區(qū)塊鏈數(shù)據(jù)的JavaScript接口,源碼地址:https://github.com/ethereum/web3.js/逗旁,通過web3.js我們可以訪問各個(gè)賬戶嘿辟、部署智能合約、調(diào)用合約方法片效、發(fā)起交易等等红伦。

$ npm install truffle第三方提供的開源以太坊DApp集成工具,源碼地址:https://github.com/trufflesuite/truffle淀衣,truffle工具會(huì)幫助我們編譯昙读、測試、打包和部署DApp項(xiàng)目中的所有合約膨桥,類似的還有Meteor(官方推薦工具蛮浑,但實(shí)用下來感覺沒有truffle方便唠叛,而且文檔也較少)。

以下是非必需工具

$ npm install truffle-contract基于web3.js封裝的JavaScript與智能合約交互接口沮稚,通過鏈?zhǔn)秸{(diào)用將對(duì)合約的各個(gè)操作串聯(lián)在一起艺沼,具體API參考源碼地址:https://github.com/trufflesuite/truffle-contract

$ npm install expressnode.js社區(qū)中基于connect流行的服務(wù)器開發(fā)框架,本文使用該框架搭建后臺(tái)服務(wù)器蕴掏,讀者可自行選擇其他框架障般。

編程語言

編寫一個(gè)DApp可以說是包括兩部分,合約部分和業(yè)務(wù)邏輯部分盛杰。

智能合約

Solidity挽荡,類JavaScript,這是以太坊推薦的旗艦語言即供,也是最流行的智能合約語言定拟,具體用法參考http://solidity.readthedocs.io/en/latest/,本文所有合約都使用該語言編寫募狂,另外測試办素、調(diào)試Solidity有一個(gè)非常好的在線IDE--Remixhttps://remix.ethereum.org/,由以太坊團(tuán)隊(duì)推出的祸穷。

Serpent性穿,類Python。

LLL雷滚,類Lisp需曾。

業(yè)務(wù)邏輯

業(yè)務(wù)邏輯部分即提供客戶端與智能合約交互的接口,相當(dāng)于目前BS結(jié)構(gòu)中的后臺(tái)邏輯祈远,因此業(yè)務(wù)邏輯部分可部署在中心服務(wù)器中呆万,而且在以太坊中每個(gè)智能合約函數(shù)的每一行代碼都有固定的gas費(fèi)用以及延時(shí)的,一些簡單的邏輯應(yīng)該交由業(yè)務(wù)邏輯處理车份,編寫業(yè)務(wù)邏輯目前提供有以下幾種語言:

JavaScript谋减,主要是基于Web3.js這個(gè)庫調(diào)用智能合約,本文例子也是使用JavaScript編寫的扫沼。

Go出爹,上述提到的以太坊私鏈搭建工具geth就是使用Go編寫的。

Python

Java

Ruby

Haskell

Rust

DApp實(shí)踐

廢話不多說缎除,下面我們通過一個(gè)DApp例子來窺探一下區(qū)塊鏈智能合約的魅力严就,demo源碼地址:https://github.com/Dave1991/QzoneBlockPet

Demo功能介紹

該demo是一個(gè)卡片收集類游戲器罐,業(yè)務(wù)場景為每個(gè)用戶都擁有一只隨機(jī)的寵物梢为,用戶通過收集卡片作用于寵物身上進(jìn)行裝扮,而卡片的收集來源分三種:

系統(tǒng)定期為隨機(jī)用戶生成卡片

與其他用戶交換卡片

在卡片商城中購買卡片

Demo目錄結(jié)構(gòu)

我們通過$ truffle init命令創(chuàng)建一個(gè)DApp項(xiàng)目,truffle會(huì)幫我們組織好一個(gè)DApp的目錄結(jié)構(gòu)铸董,如下所示祟印,其中app目錄為筆者添加的,用于存放業(yè)務(wù)邏輯代碼袒炉。

app

業(yè)務(wù)邏輯代碼旁理,后面再展開討論

varMigrations=artifacts.require("./Migrations.sol");varPetCard=artifacts.require("./PetCard.sol");varUserCenter=artifacts.require("./UserCenter.sol");module.exports=function(deployer){deployer.deploy(Migrations);deployer.deploy(PetCard);deployer.deploy(UserCenter);};

build

合約編譯生成目錄樊零,不要手動(dòng)修改

contracts

合約目錄我磁,后面展開討論

migrationstruffle部署配置文件,新的合約需要部署需要修改里面的配置文件1_initial_migration.js驻襟,該demo包含兩個(gè)合約夺艰,加上truffle部署時(shí)需要使用的合約,一共三個(gè)合約沉衣,代碼如下所示郁副,當(dāng)添加一個(gè)合約時(shí)需要在該文件中添加合約變量而且需要通過deployer部署到區(qū)塊鏈,需要注意的是這里當(dāng)前目錄是contracts目錄豌习。

test

合約的測試文件存谎,我們可以在該目錄中存放各個(gè)合約的測試代碼,類似于其他編程語言中的單元測試肥隆,該文章不展開討論既荚。

module.exports={networks:{development:{host:"localhost",port:8545,network\_id:"\*"http:// Match any network id}}};

truffle.js

區(qū)塊鏈網(wǎng)絡(luò)配置文件,在truffle部署合約時(shí)會(huì)使用該文件定義的地址栋艳,目前配的是testrpc默認(rèn)測試環(huán)境恰聘,如下所示:

Demo運(yùn)行方式

安裝上述提到的依賴(包括非必需)

$ testrpc啟動(dòng)區(qū)塊鏈測試環(huán)境,可以看到testrpc在內(nèi)存中為我們創(chuàng)建了10個(gè)虛擬賬戶以及對(duì)應(yīng)的私鑰吸占。

image.png

$ truffle compile編譯智能合約晴叨,底層調(diào)用的是solc編譯器,該編譯方式是增量的矾屯,如果要全量編譯兼蕊,可加上--all參數(shù)。

image.png

$ truffle migrate --reset部署所有智能合約件蚕,部署的環(huán)境由truffle.js定義孙技,和compile類似,migrate也是增量部署骤坐,如果要重新部署所有合約绪杏,可加上--reset參數(shù)。

image.png

$ cd app

$ npm start啟動(dòng)服務(wù)器

瀏覽器訪問localhost:8080纽绍,目前提供的接口詳見INTERFACE.md文件蕾久,下面展示其中兩個(gè)接口。

生成卡片

接口名稱方法路由參數(shù)

createRandomCardGET無

例子返回

/createRandomCard{"cardId":"2","code":"0x616161666","owner":"0x5727b589bca4500e896ffc82e3fedf56cae7017f","value":"52"}

獲取用戶所有卡片

接口名稱方法路由參數(shù)

getAllCardsForUserGET/:address

例子返回

/getAllCardsForUser/0xc3d9b7ea1e42b04dddf3475b464bb1abd5f8451f{"cardId":"0","code":"0x616161666","value":"4"}

需要注意的是上面兩個(gè)方法調(diào)用前都需要設(shè)置gas(以太坊交易手續(xù)費(fèi))拌夏,不過由于demo運(yùn)行在testrpc中所有賬戶的balance都是虛擬的僧著,業(yè)務(wù)邏輯直接從接口調(diào)用方賬戶扣除了gas履因,對(duì)其屏蔽了該過程,但如果正式部署到生產(chǎn)環(huán)境我們需要先詢問用戶是否愿意付該筆gas然后再真正調(diào)用合約接口盹愚,因此栅迄,以太坊的web3.js提供了estimateGas方法來預(yù)估合約函數(shù)執(zhí)行所需的gas。

編寫智能合約

智能合約使用Solidity語言編寫皆怕,語法有點(diǎn)類似于JavaScript毅舆,文件名以.sol結(jié)尾,通常來說一個(gè).sol文件定義一個(gè)合約愈腾,相當(dāng)于Java中一個(gè)文件定義一個(gè)public class憋活。一個(gè)合約通常包含兩部分,成員變量和成員函數(shù)虱黄。

進(jìn)入本demo的contracts目錄悦即,可以看見里面包含了以下文件:

Migrations.sol:truffle創(chuàng)建目錄時(shí)創(chuàng)建的合約,用于部署DApp

PetCard.sol:本demo核心合約橱乱,定義了寵物卡片合約

strings.sol:第三方定義的字符串類庫辜梳,本demo主要使用了其分割字符串的函數(shù)

UserCenter.sol:用戶中心合約,用于注冊(cè)用戶和查詢用戶

下面展示的是寵物卡片合約的部分代碼泳叠。

pragma solidity^0.4.17;contract PetCard{struct Card{bytes32 code;//卡片代碼作瞄,決定卡片的功能uint256 value;address owner;bool isSelling;uint sellingPrice;uint cardId;}enumErrorCode{ERROR_NO_ERROR,ERROR_INDEX_OUT_OF_RANGE,ERROR_WRONG_OWNER,ERROR_CARD_IS_SELLING,ERROR_CARD_IS_NOT_SELLING,ERROR_PRICE_NOT_ENOUGH}Card[]cards;address CEO;functionPetCard()publicpayable{CEO=msg.sender;}// 匿名函數(shù),當(dāng)外部調(diào)用找不到時(shí)調(diào)用該函數(shù)eventFallbackTrigged(bytes data);function()publicpayable{FallbackTrigged(msg.data);}eventBuyCardEvent(uint cardId,bool isSuccess,ErrorCode errorCode);// 從卡片商城中購買卡片functionbuyCard(uint cardId)publicpayable{address buyer=msg.sender;// 判斷card下標(biāo)是否合法析二,不合法時(shí)退款給買家if(cardId>=cards.length||cardId<0){buyer.transfer(msg.value);BuyCardEvent(cardId,false,ErrorCode.ERROR_INDEX_OUT_OF_RANGE);return;}Card storage card=cards[cardId];// 判斷消費(fèi)金額是否小于card價(jià)格if(msg.value=card.sellingPrice){card.owner.transfer(card.sellingPrice);}card.owner=buyer;card.isSelling=false;card.sellingPrice=0;BuyCardEvent(cardId,true,ErrorCode.ERROR_NO_ERROR);}// 獲取用戶所有卡片functiongetAllCardsForUser()publicconstantreturns(uint[]cardIds,bytes32[]codes,uint[]values,uint len){cardIds=newuint[](cards.length);codes=newbytes32[](cards.length);values=newuint[](cards.length);// codes = new string[](cards.length);len=0;for(uint i=0;i

定義卡片結(jié)構(gòu)與成員變量

合約內(nèi)部可以定義多個(gè)結(jié)構(gòu)體粉洼,關(guān)鍵字為struct,結(jié)構(gòu)體內(nèi)部也可定義成員變量叶摄,允許的類型和合約一樣属韧。此外,合約支持?jǐn)?shù)據(jù)類型包括以下幾種:

整型蛤吓,uintx / intx宵喂,其中x代表整型所占用的位數(shù),從8到256会傲,步長為8锅棕,如果我們直接使用uint / int,則與uint256 / int256等價(jià)淌山。

布爾型裸燎,bool,有true/false兩個(gè)值泼疑。

浮點(diǎn)型德绿,fixedMxN / ufixedMxN,浮點(diǎn)數(shù)在Solidity中支持得不是很好,它與其他語言中的浮點(diǎn)數(shù)并不一樣移稳,Solidity中浮點(diǎn)數(shù)在聲明時(shí)就必須確定長度蕴纳,而其他語言是可變的,M代表的是浮點(diǎn)數(shù)占用的總位數(shù)个粱,從8到256古毛,步長為8,N代表小數(shù)部分的長度都许,范圍是0-80稻薇。

定長字節(jié)型,bytesx梭稚,其中x代表變量所占字節(jié)長度颖低,范圍是1-32,當(dāng)變量打印出來時(shí)弧烤,顯示的是十六進(jìn)制。

變長字節(jié)型蹬敲,bytes或string暇昂,兩者區(qū)別在于bytes使用十六進(jìn)制標(biāo)識(shí),string是用UTF-8表示伴嗡。

地址急波,address, 等價(jià)于bytes20,而且Solidity為地址變量預(yù)設(shè)了幾個(gè)方法瘪校,例如澄暮,balance方法獲取地址對(duì)應(yīng)賬戶的余額,transfer方法轉(zhuǎn)賬以太幣到地址對(duì)應(yīng)的賬戶中阱扬,轉(zhuǎn)賬者為調(diào)用者泣懊,收款者為address,另一個(gè)方法send類似于transfer也是轉(zhuǎn)賬麻惶,但值得注意的是馍刮,當(dāng)transfer失敗時(shí),會(huì)回滾交易并拋出異常窃蹋,而send方法則不會(huì)卡啰。

枚舉,enum警没,和其他語言一樣匈辱,Solidity也支持枚舉值,語法也類似杀迹,可參考代碼中錯(cuò)誤碼枚舉值的定義亡脸。

根據(jù)上述的數(shù)據(jù)類型,我們定義卡片的結(jié)構(gòu)體,包括卡片代碼梗掰、卡片價(jià)值嵌言、卡片擁有者、卡片是否正在出售及穗、卡片出售價(jià)格以及卡片id摧茴。然后,定義了函數(shù)執(zhí)行可能會(huì)發(fā)生的錯(cuò)誤碼埂陆,還有一個(gè)卡片的集合以及合約的創(chuàng)建者CEO苛白。

struct Card{bytes32 code;//卡片代碼,決定卡片的功能uint256 value;address owner;bool isSelling;uint sellingPrice;uint cardId;}enumErrorCode{ERROR_NO_ERROR,ERROR_INDEX_OUT_OF_RANGE,ERROR_WRONG_OWNER,ERROR_CARD_IS_SELLING,ERROR_CARD_IS_NOT_SELLING,ERROR_PRICE_NOT_ENOUGH}Card[]cards;address CEO;

函數(shù)

在Solidity中函數(shù)的定義語法是

function函數(shù)名(參數(shù)列表) 修飾符returns(返回值列表)

這里值得注意的是焚虱,在函數(shù)生命中返回值列表我們可以聲明返回值的名字购裙,類似于形參,當(dāng)在函數(shù)體中給返回值變量賦值后鹃栽,我們可以不用寫return躏率,但如果寫了還是以return為主,同時(shí)民鼓,一個(gè)函數(shù)返回值支持多個(gè)薇芝,調(diào)用者拿到的將是一個(gè)返回值數(shù)組,和python有點(diǎn)像丰嘉。

另外夯到,EVM會(huì)給每個(gè)合約的函數(shù)傳入一個(gè)名為msg的對(duì)象,該對(duì)象包含幾個(gè)屬性饮亏,如sender是調(diào)用者賬戶地址耍贾、value是調(diào)用者執(zhí)行該函數(shù)支付的以太幣(單位是wei)、data是函數(shù)調(diào)用的描述路幸。除了data外荐开,其他屬性的值是由調(diào)用者傳入,詳見業(yè)務(wù)邏輯代碼的介紹劝赔。

構(gòu)建函數(shù)和匿名函數(shù)

和大部分語言一樣誓焦,Solidity中每個(gè)合約也有構(gòu)建函數(shù),在構(gòu)建函數(shù)中我們可以做一些初始化的操作着帽,在下面的代碼中我們注意到函數(shù)后有兩個(gè)修飾符杂伟,分別是public和payable,其中public說明該函數(shù)外部合約也可見仍翰,對(duì)應(yīng)的還有external赫粥,private,internal予借,要說到這四者的區(qū)別越平,需要查看函數(shù)的調(diào)用方式和可見性频蛔,本文就不展開了。然后payable說明該函數(shù)會(huì)涉及貨幣交易秦叛,同時(shí)當(dāng)我們?cè)谝粋€(gè)合約的其他函數(shù)中調(diào)用了轉(zhuǎn)賬操作晦溪,那么構(gòu)建函數(shù)必須也得聲明為payable。

匿名函數(shù)挣跋,也就是沒有名字的函數(shù)三圆,每個(gè)合約中最多可定義一個(gè),當(dāng)其他地方調(diào)用該合約不存在的函數(shù)或者出現(xiàn)異常時(shí)避咆,EVM(以太坊智能合約執(zhí)行虛擬機(jī))會(huì)自動(dòng)調(diào)用合約的匿名函數(shù)舟肉,同樣地,當(dāng)合約內(nèi)其他函數(shù)有轉(zhuǎn)賬操作時(shí)匿名函數(shù)也需要加上payable修飾查库。

functionPetCard()publicpayable{CEO=msg.sender;}// 匿名函數(shù)路媚,當(dāng)外部調(diào)用找不到時(shí)調(diào)用該函數(shù)eventFallbackTrigged(bytes data);function()publicpayable{FallbackTrigged(msg.data);}

事件

代碼中我們定義了多個(gè)event,每個(gè)event只需要定義其名字和參數(shù)列表即可以樊销,其作用相當(dāng)于其他語言中的log整慎,在函數(shù)中傳入實(shí)參即可記錄,雖說event的作用和log一樣现柠,但在Solidity中作用卻非同小可院领,因?yàn)楫?dāng)一個(gè)函數(shù)是以transaction的形式被調(diào)用,調(diào)用者是無法拿到返回值的够吩,因?yàn)閠ransaction的調(diào)用是異步的,EVM無法立刻執(zhí)行給出返回值丈氓,所以調(diào)用者只能通過event的記錄取得函數(shù)執(zhí)行后的數(shù)據(jù)周循,具體操作流程見業(yè)務(wù)邏輯代碼的介紹。

購買卡片

定義購買卡片的函數(shù)万俗,函數(shù)一開始我們寫了三個(gè)是否合法的判斷湾笛,這里可以使用require關(guān)鍵字對(duì)這些條件進(jìn)行限定,但由于筆者希望調(diào)用者可以接收到錯(cuò)誤信息闰歪,這里就使用了四個(gè)if判斷嚎研,并且使用了事件通知調(diào)用者,同時(shí)當(dāng)條件不滿足時(shí)我們需要做一些回滾操作库倘,例如將金額退還給調(diào)用者賬戶临扮。而當(dāng)條件滿足后,我們將卡片定價(jià)轉(zhuǎn)給賣家教翩,轉(zhuǎn)移卡片擁有者杆勇。

eventBuyCardEvent(uint cardId,bool isSuccess,ErrorCode errorCode);// 從卡片商城中購買卡片functionbuyCard(uint cardId)publicpayable{address buyer=msg.sender;// 判斷card下標(biāo)是否合法,不合法時(shí)退款給買家if(cardId>=cards.length||cardId<0){buyer.transfer(msg.value);BuyCardEvent(cardId,false,ErrorCode.ERROR_INDEX_OUT_OF_RANGE);return;}Card storage card=cards[cardId];// 判斷消費(fèi)金額是否小于card價(jià)格if(msg.value=card.sellingPrice){card.owner.transfer(card.sellingPrice);}card.owner=buyer;card.isSelling=false;card.sellingPrice=0;BuyCardEvent(cardId,true,ErrorCode.ERROR_NO_ERROR);}

遍歷卡片

該函數(shù)的作用是獲取所有屬于調(diào)用者賬戶的卡片饱亿,值得注意的是蚜退,該函數(shù)在EVM中是一個(gè)昂貴的操作闰靴,首先我們聲明了三個(gè)定長數(shù)組(定長是和臨時(shí)變量存儲(chǔ)的地方有關(guān)),每個(gè)長度都等于所有卡片數(shù)組的大小钻注,因此每個(gè)數(shù)組都已經(jīng)開銷了不少gas蚂且,然后遍歷又是一個(gè)耗時(shí)操作,又需要花費(fèi)gas幅恋,而且函數(shù)在編譯時(shí)并不知道cards的長度杏死,所以即使調(diào)用者使用estimategas函數(shù)預(yù)估該函數(shù)所需gas也是不準(zhǔn)確的,這對(duì)于調(diào)用者是危險(xiǎn)的佳遣,隨時(shí)都可能因?yàn)間as不夠而執(zhí)行失敗识埋。

functiongetAllCardsForUser()publicconstantreturns(uint[]cardIds,bytes32[]codes,uint[]values,uint len){cardIds=newuint[](cards.length);codes=newbytes32[](cards.length);//這里不能用string,solidity不支持定長的變長數(shù)組values=newuint[](cards.length);// codes = new string[](cards.length);len=0;for(uint i=0;i

生成卡片

這里生成卡片的邏輯交給業(yè)務(wù)層零渐,合約只負(fù)責(zé)根據(jù)參數(shù)創(chuàng)建一個(gè)新的卡片窒舟,最后通知調(diào)用者即業(yè)務(wù)層。

eventCreateNewCardEvent(uint cardId,bytes32 code,address owner,uint value);// 給用戶掉落新卡片functioncreateNewCardForUser(bytes32 code,uint value)public{Card memory card=Card({code:code,value:value,owner:msg.sender,isSelling:false,cardId:cards.length,sellingPrice:0});cards.push(card);CreateNewCardEvent(card.cardId,card.code,card.owner,card.value);}

編寫業(yè)務(wù)邏輯

合約編寫完成后诵盼,可先到Remix上測試惠豺,測試通過后再使用truffle編譯和部署到區(qū)塊鏈上。之后风宁,便是業(yè)務(wù)邏輯的編寫了洁墙。

由于truffle,web3等都是依賴于node.js戒财,為了一致性與方便性热监,本demo也是使用node.js構(gòu)建業(yè)務(wù)服務(wù)器,主要依賴的模塊是express和truffle-contract饮寞,前者用于更方便的業(yè)務(wù)路由和模塊化孝扛,后者用于更方便調(diào)用合約。

打開app目錄幽崩,我們會(huì)看到一下的文件結(jié)構(gòu):

PetCard.js:寵物卡片業(yè)務(wù)路由處理以及合約交互

UserCenter.js:用戶中心苦始,負(fù)責(zé)用戶注冊(cè)和獲取所有用戶的上層調(diào)用

UserCenterCore.js:用戶中心核心,負(fù)責(zé)業(yè)務(wù)層與合約層交互

Web3Provider.js:定義Web3連接的是區(qū)塊鏈地址

package.json:定義npm運(yùn)行所需要的命令和依賴

server.js:業(yè)務(wù)層總?cè)肟诨派辏?fù)責(zé)默認(rèn)頁面陌选、404頁面處理,以及各業(yè)務(wù)模塊的中轉(zhuǎn)路由蹄溉,還有定義服務(wù)器綁定的端口

下面我們主要看PetCard.js中業(yè)務(wù)層是如何與合約層進(jìn)行交互的咨油。

獲取合約示例

這一步我們首先獲取寵物卡片合約和用戶中心合約的實(shí)例,便于下面調(diào)用合約类缤,這里我們需要依賴truffle-contract還有本地的Web3Provider模塊臼勉。而truffle-contract的用法都是鏈?zhǔn)秸{(diào)用,通過then函數(shù)連接起來餐弱。

contract=require('truffle-contract');provider=require('./Web3Provider.js');express=require("express");constPetCard=contract(require('../../build/contracts/PetCard.json'));PetCard.setProvider(provider);varpetCard;PetCard.deployed().then(function(instance){petCard=instance;});varuserCenter;require('./UserCenterCore.js').then(function(instance){userCenter=instance;});varapp=module.exports=express();

購買卡片

從下面代碼中可以看到宴霸,業(yè)務(wù)層接受客戶端傳遞的路由參數(shù)囱晴,再傳入合約層,這里合約層函數(shù)的參數(shù)分兩種瓢谢,一種是自定義參數(shù)畸写,另一種就是EVM預(yù)設(shè)參數(shù),而預(yù)設(shè)參數(shù)是一個(gè)對(duì)象氓扛,需要在最后傳入枯芬,正如上面Solidity函數(shù)介紹,預(yù)設(shè)參數(shù)對(duì)象需要包括from為調(diào)用者地址采郎,value為傳入合約的以太幣千所。最后,由于這是直接通過合約實(shí)例調(diào)用函數(shù)蒜埋,是一個(gè)transaction操作淫痰,因此如上面Solidity事件介紹,我們需要從返回值的日志中獲取合約執(zhí)行后的數(shù)據(jù)整份。由于日志拿到的事件參數(shù)是一個(gè)對(duì)象待错,所以我們直接以json形式返回給客戶端即可,例如下面的返回就表示卡片購買失敗烈评,原因是卡片當(dāng)前不在銷售:{"cardId":"1","isSuccess":false,"errorCode":"4"}火俄。

app.get('/buyCard/:address/:cardId/:price',function(req,res){petCard.buyCard(req.params.cardId,{from:req.params.address,value:req.params.price}).then(function(result){if(result.logs.length>0){vareventObj=result.logs[0].args;res.send(JSON.stringify(eventObj));}});});

遍歷所有卡片

遍歷卡片的操作并不涉及永久寫入合約數(shù)據(jù)的操作,因此遍歷卡片這里我們不使用transaction讲冠,而使用call的形式瓜客,因此我們可以直接拿到函數(shù)的返回值,然后由于函數(shù)返回多個(gè)值竿开,因此result是一個(gè)數(shù)組忆家。這里需要注意的是,上面我們說到遍歷卡片時(shí)合約需要?jiǎng)?chuàng)建三個(gè)未知長度的數(shù)組,而且遍歷的次數(shù)也是未知的辈灼,因此许帐,estimategas函數(shù)預(yù)估的gas會(huì)不準(zhǔn)確,我們這里直接給一個(gè)比較大的gas值控淡。該接口返回的例子如:{"cardId":"0","code":"0x616161666","value":"4"}。

app.get('/getAllCardsForUser/:address',function(req,res){// 因?yàn)檫@需要?jiǎng)?chuàng)建未知長度數(shù)組,estimate 估計(jì)的gas會(huì)不準(zhǔn)確肌毅,該方法慎調(diào)petCard.getAllCardsForUser.call({from:req.params.address,gas:3000000}).then(function(result){if(result.length>=4){varcardIds=result[0],codes=result[1],values=result[2];varlen=result[3];varcards=[];for(vari=0;i

生成卡片

生成卡片的邏輯是在所有用戶隨機(jī)挑選一個(gè)用戶作為卡片的擁有者,然后卡片的code這里先簡單地寫死了一串姑原,后續(xù)可以想更好玩的code生成邏輯悬而,接著就是調(diào)用estimateGas函數(shù)估計(jì)所需的gas,最后才是真正調(diào)用合約函數(shù)锭汛,傳入預(yù)估的gas笨奠,其實(shí)比較好的交互應(yīng)該像以太貓那樣袭蝗,在進(jìn)行真正的調(diào)用之前告知用戶交易所需的gas,并可以讓用戶調(diào)整般婆,用戶確認(rèn)后再執(zhí)行合約函數(shù)到腥。下面是生成卡片調(diào)用后返回的一個(gè)例子:{"cardId":"2","code":"0x616161666","owner":"0x5727b589bca4500e896ffc82e3fedf56cae7017f","value":"52"}。

app.get('/createRandomCard',function(req,res){varallUsers,randomUser;userCenter.showAllPlayers.call().then(function(result){allUsers=result;randomIdx=Math.floor(Math.random()*allUsers.length);randomUser=allUsers[randomIdx];if(randomUser!=undefined){varcardCode="aaaforestlinbbb";varcardValue=Math.floor(Math.random()*100+1);petCard.createNewCardForUser.estimateGas(cardCode,cardValue).then(function(esti_gas){returnpetCard.createNewCardForUser(cardCode,cardValue,{from:randomUser,gas:esti_gas});}).then(function(rest){if(rest.logs.length>0){vareventObj=rest.logs[0].args;res.send(JSON.stringify(eventObj));}});}else{res.send("random user is undefined");}});});

總結(jié)DApp開發(fā)中遇到的坑

一個(gè)DApp開發(fā)流程介紹到此結(jié)束蔚袍,下面總結(jié)一下開發(fā)中值得注意的地方:

Solidity這個(gè)語言目前還不是很完善乡范,版本還是0.4.x,而且文檔相對(duì)其他語言較少啤咽,這里除了官網(wǎng)晋辆,還推薦兩個(gè)論壇區(qū)塊鏈技術(shù)博客以太坊愛好者供大家參考。

合約函數(shù)中慎用未知長度的數(shù)組以及遍歷操作宇整,比較耗費(fèi)gas瓶佳,而且對(duì)于調(diào)用者極不友好,無法預(yù)估gas没陡。

對(duì)于不需要寫操作的函數(shù)涩哟,我們可以加上constant修飾符或者調(diào)用時(shí)使用call的方法而非直接調(diào)用,不產(chǎn)生transaction盼玄,也就不需要寫入?yún)^(qū)塊鏈贴彼。

對(duì)于不需要的數(shù)組我們可以使用delete操作刪除整個(gè)數(shù)組或者某個(gè)元素,可以歸還一些gas埃儿,但是最好復(fù)用器仗,使用指示器標(biāo)記當(dāng)前使用的長度,因?yàn)閐elete操作本身也是需要耗費(fèi)gas的童番。

合約內(nèi)不適合做業(yè)務(wù)過重的操作精钮,如上面的生成卡片操作,應(yīng)該將邏輯放在業(yè)務(wù)層剃斧,畢竟在EVM中沒執(zhí)行一行代碼都是需要gas的轨香,合約應(yīng)該只有讀寫區(qū)塊鏈的操作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末幼东,一起剝皮案震驚了整個(gè)濱河市臂容,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌根蟹,老刑警劉巖脓杉,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異简逮,居然都是意外死亡球散,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門散庶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕉堰,“玉大人凌净,你說我怎么就攤上這事∴业疲” “怎么了泻蚊?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丑婿。 經(jīng)常有香客問我性雄,道長,這世上最難降的妖魔是什么羹奉? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任秒旋,我火速辦了婚禮,結(jié)果婚禮上诀拭,老公的妹妹穿的比我還像新娘迁筛。我一直安慰自己,他們只是感情好耕挨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布细卧。 她就那樣靜靜地躺著,像睡著了一般筒占。 火紅的嫁衣襯著肌膚如雪贪庙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天翰苫,我揣著相機(jī)與錄音止邮,去河邊找鬼。 笑死奏窑,一個(gè)胖子當(dāng)著我的面吹牛导披,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埃唯,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撩匕,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了墨叛?” 一聲冷哼從身側(cè)響起滑沧,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巍实,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哩牍,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棚潦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膝昆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丸边。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叠必,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妹窖,到底是詐尸還是另有隱情纬朝,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布骄呼,位于F島的核電站共苛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蜓萄。R本人自食惡果不足惜隅茎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫉沽。 院中可真熱鬧辟犀,春花似錦、人聲如沸绸硕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玻佩。三九已至出嘹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夺蛇,已是汗流浹背疚漆。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刁赦,地道東北人娶聘。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像甚脉,于是被迫代替她去往敵國和親丸升。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 【本文目標(biāo)】 通過本文的學(xué)習(xí)和時(shí)間牺氨,你將熟悉以太坊開發(fā)框架Truffle的配置和運(yùn)行狡耻,并借助Truffle完成一...
    筆名輝哥閱讀 13,032評(píng)論 8 55
  • 上一篇:以太坊Solidity開發(fā)入門(進(jìn)階篇) 第1章: 智能協(xié)議的永固性 到現(xiàn)在為止,我們講的 Solidit...
    那個(gè)大螺絲閱讀 4,026評(píng)論 0 8
  • 清純的眼眸 倔強(qiáng)的唇 烏黑的頭發(fā) 輪廓俏麗猴凹,惹人喜愛的臉頰 我曾與她在家的屋后吵嘴夷狰。她牽起哭泣的我的手。一起在蜻蜓...
    Serememorer閱讀 324評(píng)論 0 0
  • Redis二八定律 80%的業(yè)務(wù)訪問集中在20%的數(shù)據(jù)上郊霎,這是為了減輕數(shù)據(jù)庫的壓力和提高網(wǎng)站的數(shù)據(jù)訪問速度沼头。
    云天河9527閱讀 1,200評(píng)論 0 0
  • 價(jià)值交換,是商業(yè)的本質(zhì),也是我們生存的基礎(chǔ)进倍。一方和另外一方進(jìn)行價(jià)值交換土至,前提是:彼此有所需。一方有預(yù)算+問題猾昆,另一...
    逄格亮閱讀 190評(píng)論 0 0