開發(fā)和部署以太坊DApp(寵物商店)

1、背景介紹以及相關(guān)問題

  • 以太貓游戲介紹
    CryptoKitties(以太貓)是2017年11月上線的一款以太坊區(qū)塊鏈虛擬養(yǎng)貓游戲,用戶可以花費以太幣買賣并繁殖不同品種的虛擬寵物貓,一只虛擬寵物貓在市場最高標(biāo)價為340萬美元。上線不到10天一躍成為以太坊上交易量最高的DApp,12月還出現(xiàn)了嚴(yán)重的擁堵事件,也因此暴露了區(qū)塊鏈存在的問題。

    以太貓網(wǎng)址:https://www.cryptokitties.co/

    CryptoKitties(以太貓)

  • 以太坊性能優(yōu)化介紹
    為了改善以太坊的效率、吞吐率和并發(fā)性等問題,出現(xiàn)了三種以太坊性能優(yōu)化的代表性技術(shù),分別是雷電網(wǎng)絡(luò),分片技術(shù)和Casper共識機制。

    • 雷電網(wǎng)絡(luò)(Raiden Network):側(cè)鏈,基于以太坊的鏈下交易方案;將小額交易轉(zhuǎn)移到鏈下,達(dá)到一定量或者時間后再關(guān)閉狀態(tài)通道并進行結(jié)算,從而解決以太坊中轉(zhuǎn)賬交易的速度、費用和隱私問題。
    • 分片技術(shù)(Sharding):思路是每個節(jié)點只處理一部分交易,比如一部分賬戶發(fā)起的交易,從而減輕節(jié)點的計算和存儲負(fù)擔(dān)。
    • Casper共識機制:由于PoW(工作量證明)消耗大量算力和電力,以太坊基金會一致積極推進使用PoS(權(quán)益證明)代替PoW,以太坊官方將它的PoS共識機制稱為Casper。
  • 開發(fā)寵物商店DApp背景

    以太貓游戲很火,可以通過開發(fā)類似功能的寵物商店DApp,將學(xué)會一下內(nèi)容:

    • 搭建開發(fā)環(huán)境(使用到Truffle框架)
    • 編寫和部署智能合約到區(qū)塊鏈
    • Web3和智能合約的交互
    • MetaMask的使用

    項目背景:Peter有一個寵物店,店里有16只寵物等待領(lǐng)養(yǎng),他希望用以太坊技術(shù)來開發(fā)一款去中心化應(yīng)用DApp,讓大家來領(lǐng)養(yǎng)寵物。
    分。
    在本項目中已經(jīng)提供了網(wǎng)站結(jié)構(gòu)和樣式,我們只需要編寫智能合約和前端邏輯。
    前提條件:需要具備基本的以太坊和智能合約相關(guān)基礎(chǔ)知識,以及具備HTML和JavaScript的基本使用
    適用對象:DApp開發(fā)新手

    2、搭建開發(fā)環(huán)境

注意:以下操作均在MacOS上面操作

  • 安裝Node.js

    官網(wǎng):https://nodejs.org/en/
    安裝很簡單,只需要下載安裝包直接安裝即可,可以先通過終端檢查安裝情況再安裝,沒有對應(yīng)結(jié)果顯示再安裝:

    wenzildeiMac:~ wenzil$ npm -v
    3.10.10
    wenzildeiMac:~ wenzil$ node -v
    v6.9.5
    
  • 安裝Truffle:

    npm install -g truffle
    

3、通過Truffle創(chuàng)建項目

建一個項目的目錄,然后進入到該目錄,如

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

創(chuàng)建一個Truffle項目

wenzildeiMac:pet-shop-tutorial wenzil$ truffle unbox pet-shop
Downloading...
Unpacking...
Setting up...
Unbox successful. Sweet!

Commands:

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

4、項目目錄結(jié)構(gòu)

  • contrcts/:放智能合約Solidity代碼的文件夾
  • migrations/:部署智能合約的腳本
  • tests/:存放用于測試的智能合約文件
  • truffle.js:Truffle默認(rèn)的配置文件

5、編寫智能合約

在contracts目錄下,創(chuàng)建一個名為Adoption.sol的合約文件

pragma solidity ^ 0.4 .17;

contract Adoption {
  // address類型指向的是以太坊地址,存儲為20個字節(jié)的值
  // 定義了一個固定長度16為的數(shù)組,也就是16個領(lǐng)養(yǎng)寵物的人對應(yīng)的以太坊地址
  address[16] public adopters;

  // 領(lǐng)養(yǎng)寵物
  function adopt(uint petId) public returns(uint) {
    // require函數(shù)用來檢查petId,確保petId在0~15之間,防止數(shù)組下標(biāo)越界
    require(petId >= 0 && petId <= 15);
    // msg.sender表示調(diào)用此函數(shù)或者智能合約的地址
    adopters[petId] = msg.sender;
    // 返回petId作為確認(rèn)
    return petId;
  }

  // 獲取領(lǐng)養(yǎng)人
  // 確保返回類型是adopters指定的類型->address[16]
  function getAdopters() public view returns(address[16]) {
    return adopters;
  }
}

配置以太坊客戶端本地環(huán)境:
打開truffle.js配置文件,修改端口為9545.


truffle.js配置文件端口.png

6、編譯智能合約和編寫部署腳本

Truffle集成了一個叫Truffle Develop的開發(fā)者控制臺,可以用來部署和測試智能合約。

  • 編譯智能合約

    Solidity是一種編譯型語言,意味著我們需要將我的Solidity編譯成以太坊虛擬機(EVM)執(zhí)行的字節(jié)碼。
    打開終端,確保在包含DApp目錄下執(zhí)行如下命令:

    truffle develop
    
    wenzildeiMac:pet-shop-tutorial wenzil$ truffle develop
    Truffle Develop started at http://127.0.0.1:9545/
    
    Accounts:
    (0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
    (1) 0xf17f52151ebef6c7334fad080c5704d77216b732
    (2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
    (3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
    (4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
    (5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
    (6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
    (7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
    (8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
    (9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de
    
    Private Keys:
    (0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3
    (1) ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f
    (2) 0dbbe8e4ae425a6d2687f1a7e3ba17bc98c673636790f1b8ad91193c05875ef1
    (3) c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c
    (4) 388c684f0ba1ef5017716adb5d21a053ea8e90277d0868337519f97bede61418
    (5) 659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63
    (6) 82d052c865f5763aad42add438569276c00d3d88a2d062d36b2bae914d58b8c8
    (7) aa3680d5d48a8283413f7a108367c7299ca73f553735860a87b08f39395618b7
    (8) 0f62d96d6675f32685bbdb8ac13cda7c23436f63efbb9d07700d8669ff12b7c4
    (9) 8d5366123cb560bb606379f90a0bfd4769eecc0557f1b362dcae9012b548b1e5
    
    Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat
    
    ??  Important ??  : This mnemonic was created for you by Truffle. It is not secure.
    Ensure you do not use it on production blockchains, or else you risk losing funds.
    
    truffle(develop)> 
    

    然后輸入"compile"進行編譯:

    truffle(develop)> compile
    Compiling ./contracts/Adoption.sol...
    Compiling ./contracts/Migrations.sol...
    
    Compilation warnings encountered:
    
    /Users/wenzil/Desktop/study/pet-shop-tutorial/contracts/Migrations.sol:11:3: Warning: Defining constructors as functions with the same name as the contract is deprecated. Use "constructor(...) { ... }" instead.
      function Migrations() public {
      ^ (Relevant source part starts here and spans across multiple lines).
    
    Writing artifacts to ./build/contracts
    

    出現(xiàn)了警告,可以先不用管,也可以按照我另外一篇《以太坊開發(fā)簡介(下)》涉及到的來修改。

  • 編寫部署腳本
    剛才已經(jīng)成功編譯了智能合約,然后可以部署到區(qū)塊鏈了。

    在migrations文件夾下已經(jīng)有一個1_initial_migration.js部署腳本,用來部署Migrations.sol智能合約。
    然后新建一個部署腳本,比如文件名為"2_deploy_contracts.js",格式為"2_文件名.js"

    var Adoption = artifacts.require("Adoption");
    
      module.exports = function(deployer) {
      deployer.deploy(Adoption);
    };
    

7、部署智能合約

執(zhí)行"migrate"命令部署智能合約:

truffle(develop)> migrate
Using network 'develop'.

Running migration: 1_initial_migration.js
  Deploying Migrations...
  ... 0x76c32a791031a1c9995cd0721c514f3f51ad9dca09945962ff0674c9f99eb2cd
  Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network...
  ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Adoption...
  ... 0x40b2148c4afb07fb51f6f3d7db3b960e33685e40e6e16e64175912da3f590352
  Adoption: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network...
  ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts...

8、測試智能合約

在test目錄下新建一個TestAdoption.sol,編寫測試合約

pragma solidity ^ 0.4.17;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";

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

  // 測試領(lǐng)養(yǎng)方法
  function testUserCanAdoptPet() {
    uint returnedId = adoption.adopt(8);
    uint expected = 8;
    Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
  }

  // 測試根據(jù)給定寵物id獲取領(lǐng)養(yǎng)人的函數(shù)
  function testGetAdopterAddressByPetId() public {
    // 期望領(lǐng)養(yǎng)者的地址就是本合約地址,因為交易是由測試合約發(fā)起交易,
    address expected = this;
    address adopter = adoption.adopters(8);
    Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
  }

  // 測試獲取所有領(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.");
  }
}

然后輸入"test"進行測試:

truffle(develop)> test
Using network 'develop'.

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

Compilation warnings encountered:

/Users/wenzil/Desktop/study/pet-shop-tutorial/test/TestAdoption.sol:11:3: Warning: No visibility specified. Defaulting to "public". 
  function testUserCanAdoptPet() {
  ^ (Relevant source part starts here and spans across multiple lines).
,/Users/wenzil/Desktop/study/pet-shop-tutorial/contracts/Adoption.sol:20:3: Warning: Function state mutability can be restricted to view
  function getAdopters() public returns(address[16]) {
  ^ (Relevant source part starts here and spans across multiple lines).



  TestAdoption
    ? testUserCanAdoptPet (113ms)
    ? testGetAdopterAddressByPetId (207ms)
    ? testGetAdopterAddressByPetIdInArray (141ms)


  3 passing (1s)

9、創(chuàng)建與智能合約交互的UI

現(xiàn)在,我們已經(jīng)創(chuàng)建了智能合約,將其部署到我們的本地測試區(qū)塊鏈中,并確認(rèn)我們可以通過控制臺與它進行交互,現(xiàn)在是時候創(chuàng)建一個UI,讓Peter有一些東西可以用于他的寵物店!

這個應(yīng)用程序的前端代碼在pet-shop項目目錄里。存在于src/目錄中。打開"src/js/app.js"修改initWeb3()
修改app.js的initWeb3函數(shù),如下

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
      // 優(yōu)先調(diào)用MetaMask提供的web3的實例
      App.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
    }
    web3 = new Web3(App.web3Provider);

    return App.initContract();
  },

修改initContract函數(shù),如下

  initContract: function() {
    // 加載Adoption.json,保存了Adoption的ABI(接口說明)信息及部署后的網(wǎng)絡(luò)(地址)信息,它在編譯合約的時候生成ABI,在部署的時候追加網(wǎng)絡(luò)信息
    $.getJSON('Adoption.json', function(data) {
      // Get the necessary contract artifact file and instantiate it with truffle-contract
      // 用Adoption.json數(shù)據(jù)創(chuàng)建一個可交互的TruffleContract合約實例。
      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();
  },

修改markAdopted函數(shù),如下:

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);
    });
  }

修改handleAdopt函數(shù),如下:

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

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

    var adoptionInstance;

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

      var account = accounts[0];

      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);
      });
    });
  }

10、啟動lite-server

新建一個終端,進入到pet-shop-tutorial根目錄,執(zhí)行如下命令:

npm run dev

運行結(jié)果顯示如下:

wenzildeiMac:pet-shop-tutorial wenzil$ npm run dev

> pet-shop@1.0.0 dev /Users/wenzil/Desktop/study/pet-shop-tutorial
> 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:3000
    External: http://192.168.1.100:3000
 --------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.1.100:3001
 --------------------------------------
[Browsersync] Serving files from: ./src
[Browsersync] Serving files from: ./build/contracts
[Browsersync] Watching files...
18.05.21 00:32:36 200 GET /index.html
18.05.21 00:32:37 200 GET /css/bootstrap.min.css
18.05.21 00:32:37 200 GET /js/bootstrap.min.js
18.05.21 00:32:37 200 GET /js/web3.min.js
18.05.21 00:32:37 200 GET /js/app.js
18.05.21 00:32:37 200 GET /js/truffle-contract.js
18.05.21 00:32:39 404 GET /favicon.ico

11、MetaMask的使用

選擇Custom RPC,添加IP地址("http://127.0.0.1:9545/")
作為客戶自定義RPC網(wǎng)絡(luò),默認(rèn)帳號為以太幣數(shù)量為0,可以回去查看"truffle develop"生成的帳號的私鑰,如:

Private Keys:
(0) c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3

然后選擇"Import Account"導(dǎo)入私鑰,獲取對應(yīng)的帳號,發(fā)現(xiàn)有100個以太幣,當(dāng)然這里的賬戶名可以隨意修改。


MetaMask導(dǎo)入私鑰

12、啟動服務(wù),前端測試領(lǐng)養(yǎng)寵物

修改"src/index.html"里面jQuery的地址,因為谷歌被墻,其網(wǎng)站的jQuery引用文件不可用,可以換為本地的或者其他網(wǎng)站的jQuery地址。
如圖:


修改jQuery引用文件地址

然后打開"http://localhost:3000/"
刷新頁面,發(fā)現(xiàn)原來的空白頁面多了很多寵物狗,如圖:

寵物商店前端頁面

點擊某個寵物的"Adopt"按鈕,會彈出交易確認(rèn)彈框,點擊"SUBMIT"即可。


測試領(lǐng)養(yǎng)寵物

提交之后如果成功扣除了以太幣,而且頁面沒有自動刷新的話,手動刷新下,發(fā)現(xiàn)"Adopt"按鈕變成了"Success"。


測試領(lǐng)養(yǎng)成功

備注:如果發(fā)現(xiàn)提交失敗(如上圖的"Failed"),可以先檢查IP和端口是否一致,然后先關(guān)閉lite-server,之后進入到"truffle develop"重新編譯或者部署合約,最后啟動lite-server再進行測試。

PS:剛?cè)肟拥男“祝芏嗖欢?,還請各位大佬多賜教,謝謝!

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

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

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