以太坊平臺(tái)上的Hello World DApp

1. 前言

DApp(Decentralized Application): 后臺(tái)運(yùn)行在去中心化的點(diǎn)對(duì)點(diǎn)網(wǎng)絡(luò),與此相對(duì)的app,后臺(tái)是跑在一個(gè)中心server上的。以太坊上的DApp,就是通過(guò)智能合約,和區(qū)塊鏈進(jìn)行交互。

2. 環(huán)境準(zhǔn)備

搭建完ethereum私有鏈之后,就可以進(jìn)行開發(fā)啦,想想就很雞凍~不過(guò),還是得準(zhǔn)備一下開發(fā)環(huán)境先。

3. 項(xiàng)目介紹

一個(gè)寵物店,有16只寵物,現(xiàn)在開發(fā)一個(gè)去中心化應(yīng)用,讓大家來(lái)領(lǐng)養(yǎng)寵物。
在truffle box中,已經(jīng)提供了pet-shop的網(wǎng)站部分的代碼,我們只需要編寫合約及交互部分。項(xiàng)目UI先睹為快:


3.1 創(chuàng)建項(xiàng)目目錄

mkdir pet-shop-tutorial
cd pet-shop-tutorial

3.2 使用truffle unbox 創(chuàng)建項(xiàng)目

truffle unbox pet-shop

這一步可能需要花點(diǎn)時(shí)間,因?yàn)樗枰ハ螺dnode_modules, 請(qǐng)耐心等待...

結(jié)果:

Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

  Compile:        truffle compile
  Migrate:        truffle migrate
  Test contracts: truffle test
  Run dev server: npm run dev

3.3 項(xiàng)目結(jié)構(gòu)

  • contracts 智能合約存放文件夾
  • migrations 處理智能合約的部署
  • test 測(cè)試用例
  • truffle.js 部署時(shí)候的配置文件

3.4 編寫智能合約

contracts/Adoption.

pragma solidity ^0.4.17;

contract Adoption {

  address[16] public adopters;  // 地址數(shù)組,分別對(duì)應(yīng)寵物0-15的領(lǐng)養(yǎng)人的地址

  // 領(lǐng)養(yǎng)寵物
  function adopt(uint petId) public returns (uint) {
    require(petId >= 0 && petId <= 15);  // 確保id在數(shù)組長(zhǎng)度內(nèi)

    adopters[petId] = msg.sender;        // 保存領(lǐng)養(yǎng)者的地址
    return petId;
  }

  // 返回領(lǐng)養(yǎng)者
  function getAdopters() public view returns (address[16]) {
    return adopters;
  }

}

這里用來(lái)編寫Ethereum智能合約的語(yǔ)言叫Solidity, 暫時(shí)不用細(xì)究具體的語(yǔ)法,這里的例子也很簡(jiǎn)單明了,繼續(xù)往下走~

3.5 編譯部署

3.5.1 編譯

把Solidity代碼編譯為EVM字節(jié)碼,在pet-shop目錄下面:

truffle compile

輸出:

Compiling .\contracts\Adoption.sol...
Compiling .\contracts\Migrations.sol...

Compilation warnings encountered:

/D/githome/blockchain/pet-shop/contracts/Migrations.sol:11:3: Warning: No visibility specified. Defaulting to "public".
  function Migrations() {
  ^
Spanning multiple lines.
,/D/githome/blockchain/pet-shop/contracts/Migrations.sol:15:3: Warning: No visibility specified. Defaulting to "public".
  function setCompleted(uint completed) restricted {
  ^
Spanning multiple lines.
,/D/githome/blockchain/pet-shop/contracts/Migrations.sol:19:3: Warning: No visibility specified. Defaulting to "public".
  function upgrade(address new_address) restricted {
  ^
Spanning multiple lines.

Writing artifacts to .\build\contracts

這里出現(xiàn)一些warning, 但是無(wú)關(guān)緊要。這里的warinig的意思就是類似在java里面寫方法沒(méi)寫public修飾符,但是編譯的時(shí)候默認(rèn)會(huì)當(dāng)成public的編譯。

編譯完成后,會(huì)多出來(lái)一個(gè)build文件夾,里面的contracts就是編譯好的代碼,待會(huì)部署就是依賴這些文件。

3.5.2 部署前的準(zhǔn)備

在migrations目錄下面,已經(jīng)存在一個(gè)文件1_initial_migration.js。如果沒(méi)有這個(gè)文件的話,也可以通過(guò)truffle init命令來(lái)生成。這個(gè)文件的作用,就是部署Migrations.sol這個(gè)合約。關(guān)于這個(gè)合約,truffle官方的介紹是:

You must deploy this contract inside your first migration in order to take advantage of the Migrations feature.

既然是必須,那就照著做咯。

下面要部署我們的Adoption.sol,我們也要寫一個(gè)專門部署這個(gè)合約的部署文件出來(lái),名字叫2_deploy_adoption.js咯。這里的起名有點(diǎn)學(xué)問(wèn),truffle會(huì)按照你起的的部署文件名字順序部署。

From here, you can create new migrations with increasing numbered prefixes to deploy other contracts and perform further deployment steps.

在migrations目錄下面,新建2_deploy_adoption.js:

var Adoption = artifacts.require("Adoption");

module.exports = function(deployer) {
  deployer.deploy(Adoption);
};

然后,在項(xiàng)目根目錄有個(gè)叫做truffle.js的部署配置文件:

module.exports = {
  // See <http://truffleframework.com/docs/advanced/configuration>
  // for more about customizing your Truffle configuration!
  networks: {
    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "1024",
      gas: 3141593
    }
  }
};

在這個(gè)文件,指定了你將要部署你的合約到哪個(gè)地方。按照之前搭建環(huán)境,我把它的端口從7545改成8545,networkid從*改成1024。gas代表愿意出多少單位的gas來(lái)部署你的合約,default是4712388。我把它改成了我的私鏈創(chuàng)世塊的gasLimit(如在這里不指定gas, 而你的創(chuàng)世塊中的gasLimit又比default值小,到時(shí)候部署會(huì)報(bào)一個(gè)錯(cuò):exceeds block gas limit)其他的具體的配置參數(shù),還可以參考truffle官方介紹。

最后, 進(jìn)入到geth控制臺(tái)創(chuàng)建一個(gè)賬戶, 如果有賬戶了,可以跳過(guò)。

personal.newAccount()

連續(xù)兩次輸入密碼后,一個(gè)賬戶就已經(jīng)創(chuàng)建好啦,控制臺(tái)打印出來(lái)的就是你的賬戶地址,記住密碼不要忘了~再啰嗦多點(diǎn),剛才創(chuàng)建的賬號(hào)信息,已經(jīng)存在了節(jié)點(diǎn)數(shù)據(jù)庫(kù)的一個(gè)叫做keystore的文件夾下面,文件名類似UTC--2018-02-08T16-35-23.044654600Z--9d7578d663e204c90c2b419c05a02046104446f2, 是一個(gè)時(shí)間戳+地址的格式。交易的時(shí)候,Ethereum會(huì)用你剛才的密碼和這個(gè)賬戶文件結(jié)合做數(shù)字簽名,然后打包進(jìn)交易信息廣播出去。接到交易信息的節(jié)點(diǎn),會(huì)驗(yàn)證這個(gè)交易的合法性,然后挖礦保存交易~

說(shuō)到這里,還得看看你的賬戶有沒(méi)有余額。部署智能合約是要給錢的,這個(gè)錢在Ethereum里面叫做gas,gas是從以太幣轉(zhuǎn)換得來(lái)的。
[圖片上傳失敗...(image-573dfe-1522567562814)]

所以說(shuō)到底,就是要求你的賬戶得有以太幣Ether。在geth控制臺(tái)查看一下你的賬戶余額:

web3.fromWei(eth.getBalance(eth.coinbase),"Ether")

這里打印出來(lái)的余額,單位是“Ether”, 以太幣。如果想看gas有多少,直接eth.getBalance(eth.coinbase)就可以了。還有如果你的賬戶沒(méi)錢,那就去挖一下礦吧。

miner.start()

如果你是第一次挖礦,要等一段時(shí)間初始化好才能出礦。差不多的話,就可以停止挖了。

miner.stop()

至此,部署準(zhǔn)備工作完成。

3.5.3 部署

部署前,確保你的私鏈環(huán)境已經(jīng)起來(lái)。然后,還得保證你的私鏈上有節(jié)點(diǎn)在挖礦。因?yàn)椴渴鹬悄芎霞s,其實(shí)也是發(fā)送交易,得有礦工把你的交易保存到具體區(qū)塊并確認(rèn)才算真正部署成功。接著,在項(xiàng)目根目錄下面執(zhí)行命名:

truffle migrate

有可能,你會(huì)看到下面部署失敗的日志:

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... undefined
Error encountered, bailing. Network state unknown. Review successful transactions manually.
Error: authentication needed: password or unlock

為了安全性,Ethereum在一段時(shí)間后會(huì)自動(dòng)鎖住賬戶。所以解決的辦法是,進(jìn)入geth交互模式,去解鎖你的默認(rèn)賬號(hào)eth.coinbase,因?yàn)椴渴鸬臅r(shí)候,默認(rèn)是用這個(gè)賬戶去部署的,除非你在truffle.js指定一個(gè)賬戶去部署,那你就去解鎖相對(duì)應(yīng)的賬戶~

personal.unlockAccount(eth.coinbase)

OK,再敲一遍部署的命令。

Using network 'development'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0xbf625c89f59a08341ed9ed6df0fa5401fa0789689edd8bfc9f3148430c3bb1b4
  Migrations: 0xbda1a6c2e10478dff136eeac357391ff43777554
Saving successful migration to network...
  ... 0xe16b7e83871dd81765c30f3a6e8987a16aab20fa635534a719600b65f2a33485
Saving artifacts...
Running migration: 2_deploy_contract.js
  Deploying Adoption...
  ... 0x2e5e196e78713c2699689b1664cc5fb6e52a730ccd2b65d28db56d456a2cb487
  Adoption: 0xaf5df9828eea7b6ea8e5f614e1e93ce3346b4e37
Saving successful migration to network...
  ... 0x81b036154d713852c59c7f0d183e23272cd753b201749d713166ae692035b799
Saving artifacts...

部署成功。這時(shí)候,我一般會(huì)miner.stop()一下, 因?yàn)樗接墟湹囊蕴珟挪辉诙?,夠用就好。而且挖礦越多,后面越難挖。因?yàn)槊總€(gè)block的difficulty逐漸增大, 那么挖礦需要算出的nonce就越大,就意味著出礦時(shí)間要相對(duì)長(zhǎng),不利于后面開發(fā)調(diào)試。具體每個(gè)block的信息,可以通過(guò)eth.getBlock(i)來(lái)查看。

3.6 測(cè)試

3.6.1 編寫測(cè)試

truffle已經(jīng)提供好測(cè)試框架給我們啦。在test文件夾新建:TestAdoption.sol

pragma solidity ^0.4.17;

import "truffle/Assert.sol";   // 引入的斷言
import "truffle/DeployedAddresses.sol";  // 用來(lái)獲取被測(cè)試合約的地址
import "../contracts/Adoption.sol";      // 被測(cè)試合約

contract TestAdoption {
  Adoption adoption = Adoption(DeployedAddresses.Adoption());

  // 領(lǐng)養(yǎng)測(cè)試用例
  function testUserCanAdoptPet() public {
    uint returnedId = adoption.adopt(8);

    uint expected = 8;
    Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
  }

  // 寵物所有者測(cè)試用例
  function testGetAdopterAddressByPetId() public {
    // 期望領(lǐng)養(yǎng)者的地址就是本合約地址,因?yàn)榻灰资怯蓽y(cè)試合約發(fā)起交易,
    address expected = this;
    address adopter = adoption.adopters(8);
    Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
  }

    // 測(cè)試所有領(lǐng)養(yǎng)者
  function testGetAdopterAddressByPetIdInArray() public {
  // 領(lǐng)養(yǎng)者的地址就是本合約地址
    address expected = this;
    address[16] memory adopters = adoption.getAdopters();
    Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
  }
}

3.6.2 運(yùn)行測(cè)試

前提:賬戶解鎖,有礦工挖礦

truffle test --network development

--network development指定用truffle.js的develeopment配置u運(yùn)行測(cè)試。

結(jié)果:

Using network 'development'.

Compiling .\contracts\Adoption.sol...
Compiling .\test\TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...


  TestAdoption
    √ testUserCanAdoptPet (3017ms)
    √ testGetAdopterAddressByPetId (6017ms)
    √ testGetAdopterAddressByPetIdInArray (1006ms)


  3 passing (17s)

這里不得記錄一個(gè)坑, 最初跑測(cè)試的時(shí)候遇到的:

TestAdoption
    1) "before all" hook: prepare suite


  0 passing (4s)
  1 failing

  1) TestAdoption "before all" hook: prepare suite:
     Error: The contract code couldn't be stored, please check your gas amount.
      at Object.callback (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\contract.js:147:1)
      at C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\method.js:142:1
      at C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\requestmanager.js:89:1
      at C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\truffle-provider\wrapper.js:134:1
      at XMLHttpRequest.request.onreadystatechange (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\web3\lib\web3\httpprovider.js:128:1)
      at XMLHttpRequestEventTarget.dispatchEvent (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:64:1)
      at XMLHttpRequest._setReadyState (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:354:1)
      at XMLHttpRequest._onHttpResponseEnd (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:509:1)
      at IncomingMessage.<anonymous> (C:\Users\LIRI7\AppData\Roaming\npm\node_modules\truffle\build\webpack:\~\xhr2\lib\xhr2.js:469:1)
      at endReadableNT (_stream_readable.js:1056:12)
      at _combinedTickCallback (internal/process/next_tick.js:138:11)
      at process._tickCallback (internal/process/next_tick.js:180:9)

有人提過(guò)相同的issue, 但是這個(gè)對(duì)我還是沒(méi)有幫助,問(wèn)題沒(méi)得到解決,但問(wèn)題基本鎖定是gas搞的鬼。直到我看到這篇文章, 我把初始化創(chuàng)世塊的genesis.json中的gasLimit改成一個(gè)比較大的值(從0x2fefd9改成0x8000000)并重新搭建一條私鏈后,問(wèn)題就神奇地解決了, 感動(dòng)得淚流滿面哇~

3.7 UI

當(dāng)我們的智能合約ready后,就可以開始實(shí)現(xiàn)UI部分了。在truffle框架中,前端的代碼寫在src下面。在這個(gè)pet-shop中,開箱已經(jīng)有部分可用的代碼了,現(xiàn)在我們只需編寫和智能合約交互的部分。這里要用到的是web3.js, Ethereum的JavaScript API, 通過(guò)web3.js, 我們可以和已經(jīng)部署好的智能合約進(jìn)行交互。

下面修改src/js/app.js。

3.7.1 初始化web3

找到initWeb3這個(gè)function,實(shí)現(xiàn)如下:

initWeb3: function() {
    
    // Is there an injected web3 instance?
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider;
    } else {
      // If no injected web3 instance is detected, fall back to Ganache
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  }

代碼中優(yōu)先使用MistMetaMask為瀏覽器注入的web3實(shí)例,如果沒(méi)有則從本地環(huán)境創(chuàng)建一個(gè)。這里的8545就是本地節(jié)點(diǎn)監(jiān)聽的rpc端口。

3.7.2 實(shí)例化合約

找到initContract, 實(shí)現(xiàn)如下:

initContract: function() {
  // 加載Adoption.json,保存了Adoption的ABI(接口說(shuō)明)信息及部署后的網(wǎng)絡(luò)(地址)信息,它在編譯合約的時(shí)候生成ABI,在部署的時(shí)候追加網(wǎng)絡(luò)信息
  $.getJSON('Adoption.json', function(data) {
    // 用Adoption.json數(shù)據(jù)創(chuàng)建一個(gè)可交互的TruffleContract合約實(shí)例。
    var AdoptionArtifact = data;
    App.contracts.Adoption = TruffleContract(AdoptionArtifact);

    // Set the provider for our contract
    App.contracts.Adoption.setProvider(App.web3Provider);

    // Use our contract to retrieve and mark the adopted pets
    return App.markAdopted();
  });
  return App.bindEvents();
}

3.7.3 標(biāo)記領(lǐng)養(yǎng)狀態(tài)

修改markAdopted方法:

markAdopted: function(adopters, account) {
    var adoptionInstance;

    App.contracts.Adoption.deployed().then(function(instance) {
      adoptionInstance = instance;

      // 調(diào)用合約的getAdopters(), 用call讀取信息不用消耗gas
      return adoptionInstance.getAdopters.call();
    }).then(function(adopters) {
      for (i = 0; i < adopters.length; i++) {
        if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
          $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
        }
      }
    }).catch(function(err) {
      console.log(err.message);
    });
  }

3.7.4 處理領(lǐng)養(yǎng)事件

修改handleAdopt 方法:

handleAdopt: function(event) {
    event.preventDefault();

    var petId = parseInt($(event.target).data('id'));

    var adoptionInstance;

    // 獲取用戶賬號(hào)
    web3.eth.getAccounts(function(error, accounts) {
      if (error) {
        console.log(error);
      }

      var account = accounts[0];// 用第一個(gè)賬號(hào)領(lǐng)養(yǎng)

      App.contracts.Adoption.deployed().then(function(instance) {
        adoptionInstance = instance;

        // 發(fā)送交易領(lǐng)養(yǎng)寵物
        return adoptionInstance.adopt(petId, {from: account});
      }).then(function(result) {
        return App.markAdopted();
      }).catch(function(err) {
        console.log(err.message);
      });
    });
  }

3.8 運(yùn)行APP

在pet-shop目錄下,運(yùn)行npm run dev, 會(huì)啟動(dòng)lite-server

> lite-server

** browser-sync config **
{ injectChanges: false,
  files: [ './**/*.{html,htm,css,js}' ],
  watchOptions: { ignored: 'node_modules' },
  server:
   { baseDir: [ './src', './build/contracts' ],
     middleware: [ [Function], [Function] ] } }
[Browsersync] Access URLs:
 -------------------------------------
       Local: http://localhost:3003
    External: http://10.222.49.22:3003
 -------------------------------------
          UI: http://localhost:3004
 UI External: http://10.222.49.22:3004
 -------------------------------------
[Browsersync] Serving files from: ./src
[Browsersync] Serving files from: ./build/contracts
[Browsersync] Watching files...

瀏覽器打開http://localhost:3003, 可以看到16個(gè)寵物正在等著你去領(lǐng)養(yǎng)。點(diǎn)擊領(lǐng)養(yǎng),會(huì)發(fā)現(xiàn)按鈕的文字變成success并不可再點(diǎn)擊。如果領(lǐng)養(yǎng)不成功,很有可能是你的賬戶鎖住了,此時(shí)你需要去解鎖你對(duì)應(yīng)的賬戶。還有一個(gè)原因會(huì)造成領(lǐng)養(yǎng)不成功,那就是私鏈上沒(méi)有節(jié)點(diǎn)在挖礦,交易無(wú)法保存。此時(shí)miner.start()就可以了~

寵物領(lǐng)養(yǎng)的數(shù)據(jù)已經(jīng)保存至區(qū)塊鏈,即使你的節(jié)點(diǎn)重啟,領(lǐng)養(yǎng)的數(shù)據(jù)還是會(huì)在,就跟歷史一樣,發(fā)生了就是發(fā)生了,無(wú)法篡改,而且已經(jīng)同步到區(qū)塊鏈上的各個(gè)其他節(jié)點(diǎn)。

如果你修改了合約重新編譯部署,那之前的領(lǐng)養(yǎng)數(shù)據(jù)就...也還是在區(qū)塊鏈上保存著的,只是新的合約無(wú)法再獲取到之前舊合約的數(shù)據(jù)。當(dāng)然,如果你保存了舊部署之后的ABI, 也就是build目錄下面的json文件,用于replace掉現(xiàn)在的build文件夾下面的json文件,那么你就可以穿梭回舊版本的pet-shop了,從UI可以發(fā)現(xiàn),領(lǐng)養(yǎng)的數(shù)據(jù)還是在的。

3.9 結(jié)束

參考文檔:

http://truffleframework.com/tutorials/pet-shop

https://xiaozhuanlan.com/topic/4875690231

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 安裝以太坊節(jié)點(diǎn)客戶端 推薦 go-ethereum, 這是 go 語(yǔ)言實(shí)現(xiàn)的以太坊客戶端,可在這里下載二進(jìn)...
    yanging閱讀 3,817評(píng)論 0 6
  • 孩子是未長(zhǎng)大的成人,成人是年齡大的孩子。對(duì)成年人要像對(duì)孩子一樣,要管,不要慣! ——《中國(guó)教師》雜志社優(yōu)質(zhì)...
    96c3d102ed6c閱讀 1,314評(píng)論 0 0
  • 沒(méi)想到我如此厭惡。只望平安歸來(lái)給你爸媽一個(gè)交代,我亦再無(wú)思想包袱,足矣?;蛟S陌路天涯,是我們最好的選擇。
    等待木槿花開閱讀 327評(píng)論 0 1
  • 小時(shí)候,油兒飯?jiān)惆楹眯┠?,那時(shí)每年總有那么兩三月,蔬菜會(huì)變得極其珍貴。自給自足的小山村,從來(lái)沒(méi)有買菜的傳統(tǒng)和習(xí)慣...
    Adger028閱讀 677評(píng)論 4 6

友情鏈接更多精彩內(nèi)容