如何建立區塊鏈應用程序 – 2019年以太坊待辦事項列表

今天我將向您展示如何構建您的第一個區塊鏈應用程序讓我們創建一個由以太坊智能合約提供支持的待辦事項列表。首先,我們將使用Solidity編程語言創建智能合約。然後,我們將針對智能合約編寫測試,並將其部署到區塊鏈。最後,我們將為todo列表創建一個客戶端應用程序。
您可以在上面的90分鐘視頻中觀看我構建完整的區塊鏈應用程序。我還將指導您完成本教程中的分步說明。在我們開始構建dApp之前,讓我先解釋一下區塊鏈應用程序是如何工作的。

區塊鏈應用程序如何工作?

我為本教程選擇了一個todo列表應用程序,因為它是學習任何新編程語言的最常用方法之一。它將教會我們如何從區塊鏈中讀取和寫入數據,以及執行管理我們的待辦事項列表應用程序行為的業務邏輯。它將教你關於區塊鏈如何工作以及如何編寫以太坊智能合約的基礎知識。

為了理解區塊鏈應用程序的工作原理,讓我們首先看一下待辦事項列表如何作為Web應用程序工作。要訪問待辦事項列表,您將使用可通過Internet與Web伺服器通信的Web瀏覽器。伺服器包含待辦事項列表的所有代碼和數據。

Web應用程序圖以下列出了您在伺服器上可以找到的內容:

  • HTML,CSS和JavaScript中的客戶端文件
  • 後端代碼負責應用程序的業務邏輯
  • 將任務存儲在待辦事項列表中的資料庫

該伺服器是一個中心化實體,可以完全控制應用程序的各個方面。任何對伺服器具有完全訪問許可權的人都可以隨時更改代碼或數據的任何部分。區塊鏈應用程序的工作方式完全不同。todo列表中的所有代碼和數據都不在於中心化伺服器上。相反,它分布在區塊鏈中。所有代碼和數據都在區塊鏈上共享和不可更改。

為了說明這一點,讓我們來看看我們基於區塊鏈的待辦事項列表是如何工作的。

區塊鏈應用圖要訪問區塊鏈待辦事項列表,我們將使用Web瀏覽器與客戶端應用程序進行通信,該應用程序將使用HTML,CSS和JavaScript編寫。客戶端應用程序將直接與區塊鏈通信,而不是與後端Web伺服器通信。

什麼是區塊鏈?

區塊鏈是彼此交談的計算機或節點的對等網路。它是一個分散式網路,所有參與者都共同負責運行網路。每個網路參與者都在區塊鏈上維護代碼和數據的副本。所有這些數據都包含在稱為「塊」的記錄包中,這些記錄被「鏈接在一起」以組成區塊鏈。網路上的所有節點都確保這些數據是安全且不可更改的,這與中心化應用程序不同,後者可以隨時更改代碼和數據。這就是區塊鏈如此強大的原因由於區塊鏈負責存儲數據,因此它基本上是一個資料庫。而且因為它是一個彼此交談的計算機網路,所以它是一個網路。

我還應該強調傳統Web應用程序和區塊鏈應用程序之間的另一個基本區別:您不是應用程序本身的用戶,而是塊狀網路的用戶。應用程序不管理任何用戶數據。這是區塊鏈的責任

什麼是智能合約

區塊鏈上的所有代碼都包含在智能合約中,這些是在區塊鏈上運行的程序。它們是區塊鏈應用程序的構建塊。我們將在本教程中編寫一份智能合約,以便為我們的待辦事項清單提供支持。它將負責從區塊鏈中獲取我們的待辦事項列表中的所有任務,添加新任務和完成任務。

智能合約是用一種名為Solidity的編程語言編寫的,它看起來很像JavaScript。智能合約中的所有代碼都是不可變的或不可更改的。一旦我們將智能合約部署到區塊鏈,我們將無法更改或更新任何代碼。這是一項設計功能,可確保代碼無信任且安全。我經常將智能合約與網路上的微服務進行比較。它們充當從區塊鏈讀取和寫入數據的介面,以及執行業務邏輯。它們是公共可訪問的,這意味著任何有權訪問blockchian的人都可以訪問他們的界面。

區塊鏈Todo List如何運作

讓我們回顧一下,我們將在本教程中構建應用程序的工作原理。我們將為todo列表創建一個客戶端應用程序,它將直接與區塊鏈對話。我們將在本教程中使用以太坊區塊鏈,我們可以通過將客戶端應用程序連接到單個以太坊節點來訪問它。我們將在Solidity中寫一個智能合約,為todo列表提供支持,我們將把它部署到以太坊區塊鏈。我們還將使用以太坊錢包與我們的個人帳戶連接到區塊鏈網路,以便與待辦事項列表應用程序進行交互。

應用預覽

這是我們將在本教程中構建的待辦事項列表應用程序的預覽。我們將能夠列出待辦事項列表中的所有任務,創建新任務並完成它們。

以太坊Todo列表應用程序

安裝依賴項

現在讓我們安裝構建項目所需的所有依賴項。首先,我們將設置一個人區塊鏈來在本地開發應用程序。

Ganache個人區塊鏈

依賴關係是個人區塊鏈,它是一個本地開發區塊鏈,可用於模仿公共區塊鏈的行為。我建議使用Ganache作為您在以太坊開發中的個人區塊鏈。它將允許您部署智能合約,開發應用程序和運行測試。它可以在Windows,Mac和Linux上使用,如桌面應用程序和命令行工具

Ganache個人區塊鏈應用程序我將引導您完成本教程中的桌面應用程序設置。您可以在此處找到適用於您的操作系統的最新版本。下載歸檔軟體包後,解壓縮安裝程序並運行設置步驟。安裝完成後,無論何時打開它都會看到此屏幕:

Ganache個人區塊鏈應用程序好極了?現在您正在運行個人區塊鏈網路您可以看到有關運行Ganache的伺服器的一些詳細信息,以及連接到網路的帳戶列表。每個帳戶已被記入100以太。這節省了大量時間如果您是從頭開始創建自己的個人區塊鏈網路,或者在測試網路上開發應用程序,則必須手動創建所有10個帳戶並使用以太坊為每個帳戶貸記。值得慶幸的是,Ganache已經為我們做了這件事,所以我們不必擔心。

Node.js的

現在您正在運行私有區塊鏈,您需要配置環境以開發智能合約。您需要的第一個依賴項是節點包管理器,或NPM,它隨Node.js一起提供。您可以通過轉到終端並鍵入以下內容來查看是否已安裝節點:

$node -v

如果您還沒有安裝節點,可以訪問Node.js網站下載它。

Truffle框架

現在讓我們安裝Truffle Framework,它提供了一套工具,用於使用Solidity編程語言開發以太坊智能聯繫人。

Truffle區塊鏈智能合約開發框架以下是我們通過Truffle Framework獲得的所有功能的概述:

  • 智能合約管理 – 使用Solidity編程語言編寫智能合約,並將其編譯為在以太坊Virtal Machine(EVM)上運行的位元組碼。
  • 自動化測試 – 針對您的智能合約編寫測試,以確保它們按照您希望的方式運行。這些測試可以用JavaScript或Solidity編寫,並且可以針對Truffle配置的任何網路運行,包括公共區塊鏈網路。
  • 部署和遷移 – 編寫腳本以將智能合約遷移和部署到任何公共以太坊區塊鏈網路。
  • 網路管理 – 連接到任何公共以太坊區塊鏈網路,以及您可能用於開發目的的任何個人區塊鏈網路。
  • 開發控制台 – 使用Truffle控制台與JavaScript運行時環境中的智能合約進行交互。您可以連接到在網路配置中指定的任何區塊鏈網路來執行此操作。
  • Script Runner – 編寫可以使用JavaScript針對公共區塊鏈網路運行的自定義腳本。您可以在此文件中編寫任意代碼並在項目中運行它。
  • 客戶端開發 – 將您的Truffle項目配置為託管與部署到區塊鏈的智能合約對話的客戶端應用程序。

您可以在命令行中使用NPM安裝Truffle,如下所示。注意:使用下面指定的Truffle的確切版本非常重要,以便按照本教程進行操作。

$npm install -g truffle@5.0.2
Metamask以太坊錢包

現在是時候將您的Web瀏覽器變成區塊鏈瀏覽器了。大多數主要的Web瀏覽器目前都沒有連接到區塊鏈網路,所以我們必須安裝允許他們這樣做的瀏覽器擴展。

Metamask以太坊錢包瀏覽器擴展我將使用Google ChromeMetamask擴展程序。要安裝Metamask,請訪問此鏈接或在Google Chrome網上應用店中搜索Metamask Chrome插件。安裝完成後,請確保在擴展列表中選中它。安裝後,您會在Chrome瀏覽器的右上角看到狐狸圖標。如果卡住了,請參考視頻演示

Metamask還允許我們在連接區塊鏈時管理我們的個人賬戶,以及管理我們需要為交易支付的以太基金。

項目設置

本教程這部分的隨附視頻片段從9:26開始。

現在讓我們創建項目我將首先創建一個項目目錄,然後像這樣輸入:

$mkdir eth-todo-list
$cd eth-todo-list

現在我們初始化一個新的Truffle項目來開發我們的項目,如下所示:

$ truffle init

大您的終端輸出應顯示項目已成功創建。您可以打開文本編輯器,並在運行該命令後看到創建了一些新文件和目錄。現在讓我們創建一個package.json文件來安裝項目所需的一些開發依賴項。您可以從命令行執行此操作,如下所示:

$touch package.json

只需將下面的代碼複製並粘貼到您的package.json文件中,即可為項目引導所有依賴項:

{
  "name": "eth-todo-list",
  "version": "1.0.0",
  "description": "區塊鏈Todo List Powered By Ethereum",
  "main": "truffle-config.js",
  "directories": {
    "test": "test"
  },
  "scripts": {
    "dev": "lite-server",
    "test": "echo \"Error: no test specified\" && sexit 1"
  },
  "author": "gregory@dappuniversity.com",
  "license": "ISC",
  "devDependencies": {
    "bootstrap": "4.1.3",
    "chai": "^4.1.2",
    "chai-as-promised": "^7.1.1",
    "chai-bignumber": "^2.0.2",
    "lite-server": "^2.3.0",
    "nodemon": "^1.17.3",
    "truffle": "5.0.2",
    "truffle-contract": "3.0.6"
  }
}

現在,您可以從命令行安裝依賴項,如下所示:

$npm install

既然已經安裝了依賴項,那麼讓我們檢查一下我們剛剛創建的項目目錄結構:

Truffle項目目錄結構

  • 合約目錄:這是所有智能聯繫人所在的地方。我們已經有一個遷移合約來處理我們到區塊鏈的遷移。
  • 遷移目錄:這是所有遷移文件所在的位置。這些遷移類似於需要遷移來更改資料庫狀態的其他Web開發框架。每當我們將智能合約部署到區塊鏈時,我們都會更新區塊鏈的狀態,因此需要遷移。
  • node_modules目錄:這是我們剛安裝的所有Node依賴項的主頁。
  • 測試目錄:這是我們為智能合約編寫測試的地方。
  • truffle-config.js文件:這是我們Truffle項目的主要配置文件,我們將處理網路配置等事務。

現在讓我們開始開發管理我們的待辦事項列表的智能合約。我們可以通過在contracts目錄中創建一個新文件來完成此操作,如下所示:

$touch ./contracts/TodoList.sol

在這裡,讓我們開發我們的todo list智能合約。首先,我們首先指定這樣的版本:

pragma solidity ^0.5.0;

現在我們可以像這樣聲明智能合約:

pragma solidity ^0.5.0;

contract TodoList {
    // Code goes here...
}

我們創建了一個智能合約,TodoList後面跟著花括弧。我們將在其中添加智能合約的所有代碼。我們要做的就是跟蹤待辦事項列表中的任務數量。這將允許我們編寫一些簡單的代碼,以幫助我們確保項目正確設置,並且我們的代碼正在處理區塊鏈。我們只需創建一個狀態變數taskCount來跟蹤這樣的任務數量:

pragma solidity ^0.5.0;

contract TodoList {
  uint taskCount;
}

taskCount是一種稱為「狀態變數」的特殊變數。我們存儲在此狀態變數中的任何數據都將寫入區塊鏈中的存儲。它改變了智能合約的狀態,並且在整個智能合約中具有範圍,而不是只在函數內部具有範圍的局部變數。我們可以0為此狀態變數設置默認值,如下所示:

pragma solidity ^0.5.0;

contract TodoList {
  uint taskCount = 0;
}

現在,我們可以創建一種在合約之外訪問此狀態變數值的方法。我們可以使用publicSolidity中調用的特殊修飾符關鍵字來完成此操作。當我們這樣做時,Solidity將神奇地創建一個taskCount()函數,以便我們可以在智能合約之外訪問此變數的值。當我們在控制台,客戶端應用程序和測試文件內部與智能合約進行交互時,這將非常有用。

現在讓我們編譯智能合約並確保沒有錯誤:

$truffle compile

好極了?你剛剛寫了第一份以太坊智能合約。您應該注意到,只要您在以下路徑中編譯智能合約,就會生成一個新文件:`./build/contracts/TodoList.json`。該文件是智能合約ABI文件,代表「抽象二進位介面」。這個文件有很多責任,但我將在這裡強調兩個:

  • 它包含可以在以太坊虛擬機(EVM)上運行的Solidity智能合約代碼的編譯位元組碼版本,即以太坊節點。
  • 它包含可以向客戶端JavaScript應用程序等外部客戶端公開的智能合約函數的JSON表示。

我們的下一個目標是訪問Truffle控制台內的智能合約。但是,我們無法運行Truffle控制台,因為我們的應用程序尚未連接到我們在依賴項部分中設置的Ganache個人區塊鏈網路。要談談Truffle控制台內個人區塊鏈網路上的智能合約,我們必須做一些事情:

  • 更新我們項目的配置文件以指定我們要連接的個人區塊鏈網路(Ganache)。
  • 創建一個遷移腳本,告訴Truffle如何將智能合約部署到個人區塊鏈網路。
  • 運行新創建的遷移腳本,將智能合約部署到個人區塊鏈網路。

首先,我們將更新項目配置文件,以指定我們想要在第一部分中設置的個人區塊鏈網路。找到該文件truffle-config.js並粘貼以下代碼:

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*" // Match any network id
    }
  },
  solc: {
    optimizer: {
      enabled: true,
      runs: 200
    }
  }
}

注意:這些應與Ganache個人區塊鏈網路提供的默認設置相匹配。如果您更改了Ganache設置頁面中的任何設置,例如埠,則應在此處反映這些設置。

接下來,我們將在遷移目錄中創建遷移腳本,以將智能合約部署到個人區塊鏈網路。從項目根目錄,從命令行創建一個新文件,如下所示:

$touch migrations/2_deploy_contracts.js

讓我解釋一下這個文件的作用。每當我們創建新的智能合約時,我們都會更新區塊鏈的狀態。記住,我說區塊鏈從根本上說是一個資料庫。因此,每當我們永久地改變它時,我們必須它從一個狀態遷移到另一個狀態。這與您可能在其他Web應用程序開發框架中執行的資料庫遷移非常相似。

請注意,我們使用數字對遷移目錄中的所有文件進行編號,以便Truffle知道執行它們的順序。在這個新創建的遷移文件中,您可以使用此代碼來部署智能合約:

var TodoList = artifacts.require("./TodoList.sol");

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

首先,我們需要我們創建的契約,並將其分配給名為「TodoList」的變數。接下來,我們將其添加到已部署合約的清單中,以確保在我們運行遷移時部署它。現在讓我們從命令行運行這個遷移腳本,如下所示:

$truffle migrate

現在我們已成功將智能合約遷移到個人區塊鏈網路,讓我們打開控制台與智能合約進行交互。您可以從命令行打開Truffle控制台,如下所示:

$truffle console

現在我們進入控制台,讓我們得到我們部署的智能合約的實例,看看我們是否可以taskCount從合約中讀取。從控制台,運行以下代碼:

todoList = await TodoList.deployed()

TodoList是我們在遷移文件中創建的變數的名稱。我們使用該deployed()函數檢索了部署的合約實例,並將其分配給了todoList。另外,請注意await關鍵字的使用。我們必須以非同步方式與區塊鏈交互。因此,JavaScript是與區塊鏈智能合約進行客戶端交互的絕佳選擇。有幾種策略可以在JavaScript中處理非同步操作。最受歡迎的方式之一是async/await我在這裡選擇的模式。Truffle最近在Truffle控制台內發布了對此的支持。您可以在此處閱讀有關async / await模式的更多信息。

首先,我們可以獲得部署到區塊鏈的智能合約的地址,如下所示:

todoList.address
// => '0xABC123...'

現在我們可以taskCount從存儲中讀取這樣的值:

takCount = await app.taskCount()
// => 0

好極了?您已成功完成此tutorail的第一部分。您已完成以下所有操作:

  • 設置機器進行區塊鏈開發
  • 創建了一個新的Truffle項目
  • 創建了你的第一份智能合約
  • 與您在區塊鏈上新創建的智能合約互動

如果您遇到任何步驟,請隨意從github克隆此部分的項目代碼。您也可以從此處開始參考本節的視頻教程。

列出任務

本教程這一部分的隨附視頻片段從22:47開始。

現在讓我們開始列出待辦事項列表中的任務。以下是我們將在本節中完成的所有步驟:

  1. 編寫代碼以列出智能合約中的任務
  2. 列出Truffle控制台內智能合約的任務
  3. 列出客戶端應用程序中的任務
  4. 為列出任務編寫測試

為了列出智能合約中的任務,我們需要一種方法來模擬任務的可靠性。Solidity允許您使用結構定義自己的數據類型。我們可以使用這個強大的功能對任意數據建模。我們將使用結構為我們的待辦事項列表建模任務,如下所示:

pragma solidity ^0.5.0;

contract TodoList {
  uint public taskCount = 0;

  struct Task {
    uint id;
    string content;
    bool completed;
  }
}

首先,我們使用struct關鍵字後跟新結構的名稱對任務建模Task。注意,這不代表a的實例Task,而只是Task結構的定義。花括弧中包含的行定義了Taskstruct 的屬性:

  • uint id – 這是結構的唯一標識符。它id就像傳統的資料庫記錄一樣。注意,我們將此標識符的數據類型聲明為a uint,表示「無符號整數」。這只是意味著它是一個非負整數。它前面沒有「標誌」,即一個-+標誌,暗示它總是積極的。
  • string content – 這是字元串中包含的待辦事項列表中的任務文本。
  • bool completed– 這是待辦事項列表的複選框狀態,即true/false。如果是true,則任務將「完成」或從待辦事項列表中檢出。

現在我們已經建模了一個任務,我們需要一個地方將所有任務放在待辦事項列表中我們希望將它們存儲在區塊鏈中,以便智能合約的狀態將是持久的。我們可以使用狀態變數訪問區塊鏈的存儲,就像我們一樣taskCount。我們將創建一個tasks狀態變數。它將使用一種特殊的Solidity數據結構,稱為映射,如下所示:

pragma solidity ^0.5.0;

contract TodoList {
  uint public taskCount = 0;

  struct Task {
    uint id;
    string content;
    bool completed;
  }

  mapping(uint => Task) public tasks;
}

Solidity中的映射很像關聯數組或其他編程語言中的哈希。它創建了存儲在區塊鏈中的鍵值對。我們將使用唯一ID作為密鑰。價值將是它自己的任務。這將允許我們查找任何任務id

現在讓我們創建一個創建任務的功能。這將允許我們默認將新任務添加到待辦事項列表,以便我們可以在控制台中列出它們。

pragma solidity ^0.5.0;

contract TodoList {
  uint public taskCount = 0;

  struct Task {
    uint id;
    string content;
    bool completed;
  }

  mapping(uint => Task) public tasks;

  function createTask(string memory _content) public {
    taskCount ++;
    tasks[taskCount] = Task(taskCount, _content, false);
  }

}

我將解釋這個功能:

  • 首先,我們使用function關鍵字創建函數,並為其命名createTask()
  • 我們允許函數接受一個被調用的參數_content,該參數將是任務的文本。我們指定此參數將是string數據類型,並且它將保持不變memory
  • 我們將函數可見性設置為public可以在智能合約之外調用,例如在控制台中,或者從客戶端調用,例如
  • 在函數內部,我們id為新任務創建一個。我們只需取現有值taskCount並將其遞增1。
  • 現在我們通過調用Task(taskCount, _content, false);和傳入新任務的值來創建一個新的任務結構。
  • 接下來,我們存儲在blockchain新任務通過將其加入到tasks這樣的映射:task[taskCount] = ...

現在,只要將智能合約部署到區塊鏈,我們就希望將一個任務添加到待辦事項列表中,這樣它就會有一個我們可以在控制台中檢查的默認任務。我們可以通過調用createTask()智能合約的構造函數內部的函數來做到這一點:


    contract TodoList {
      // ....

      constructor() public {
        createTask("Check out dappuniversity.com");
      }

      // ....

    }

我們使用constructor關鍵字創建構造函數,如上所示。無論何時初始化合約(即部署到區塊鏈),此函數都只運行一次。在此函數內部,我們創建了一個新的默認任務,其字元串內容為「Check out dappuniversity.com」

現在讓我們將這個智能合約部署到區塊鏈。為此,我們必須部署代碼的新副本。請記住,智能合約代碼是不可變的它無法改變。因此,我們必須在進行代碼更改時創建新的智能合約。幸運的是,Truffle提供了一個快捷方式來幫助解決這個問題。我們可以重新運行這樣的遷移:

$ truffle migrate --reset

中提琴現在我們在區塊鏈上有一份新的智能合約副本。現在讓我們在控制台中列出任務。

$ truffle console

在控制台內部,讓我們獲得新智能合約的部署副本。

todoList = await TodoList.deployed()

現在我們可以通過調用tasks()函數從todo列表中獲取任務。這將允許我們從tasks映射中訪問值idid當我們調用這個函數時,我們將簡單地傳入列表中的第一個任務:

task = await todoList.tasks(1)

好極了?如何在控制台中檢查此任務的值。?

現在我們已經將這個智能合約遷移到區塊鏈,讓我們創建客戶端代碼以與todo list智能合約進行交互。您需要為項目創建以下文件:

  • bs-config.json
  • src/index.html
  • src/app.js

我們將逐個填寫所有這些文件的代碼。我們使用lite-server為客戶端提供所有項目文件。我們需要告訴lite-server所有這些文件所在的位置。我們可以通過更新bs-config.json文件中lite-server的browsersync配置來實現。將此配置粘貼到項目文件中:

{
  "server": {
    "baseDir": [
      "./src",
      "./build/contracts"
    ],
    "routes": {
      "/vendor": "./node_modules"
    }
  }
}

此配置告訴lite-server將srcbuild/contracts目錄中的所有文件公開給Web伺服器的根目錄。它還為node_modules目錄中顯示在vendor路徑中的任何文件添加別名。這將允許我們將任何項目依賴項(如bootstrap)引入客戶端並使用該vendor路由,我們將立即看到。

現在讓我們為我們的待辦事項列表填寫HTML代碼。本教程主要關注區塊鏈技術,因此我不想在HTML和CSS部分上花費太多時間。我只需在此處粘貼HTML代碼:


<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <title>Dapp University | Todo Listtitle>

    
    <link href="vendor/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">

    
    
    

    <style>
      main {
        margin-top: 60px;
      }

      #content {
        display: none;
      }

      form {
        width: 350px;
        margin-bottom: 10px;
      }

      ul {
        margin-bottom: 0px;
      }

      #completedTaskList .content {
        color: grey;
        text-decoration: line-through;
      }
    style>
  head>
  <body>
    <nav class="navbar navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
      <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://www.dappuniversity.com/free-download" target="_blank">Dapp University | Todo Lista>
      <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap d-none d-sm-none d-sm-block">
          <small><a class="nav-link" href="#"><span id="account">span>a>small>
        li>
      ul>
    nav>
    <div class="container-fluid">
      <div class="row">
        <main role="main" class="col-lg-12 d-flex justify-content-center">
          <div id="loader" class="text-center">
            <p class="text-center">Loading...p>
          div>
          <div id="content">
         
            <ul id="taskList" class="list-unstyled">
              <div class="taskTemplate" class="checkbox" style="display: none">
                <label>
                  <input type="checkbox" />
                  <span class="content">Task content goes here...span>
                label>
              div>
            ul>
            <ul id="completedTaskList" class="list-unstyled">
            ul>
          div>
        main>
      div>
    div>

    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js">script>
    
    <script src="vendor/bootstrap/dist/js/bootstrap.min.js">script>
    <script src="vendor/truffle-contract/dist/truffle-contract.js">script>
    <script src="app.js">script>
  body>
html>

這個文件支持我們項目所需的所有HTML。我已經注釋掉了我們將在後面部分中啟用的表單代碼。該文件引入了項目的所有依賴項,如引導模板框架,它將允許我們創建漂亮的UI元素,而無需編寫太多的CSS。它還使用Truffle Conract庫,它允許我們使用JavaScript與todo list智能合約進行交互。

現在讓我們填寫本節的JavaScript代碼。我們將代碼添加到新創建的app.js文件中,如下所示:

App = {
  loading: false,
  contracts: {},

  load: async () => {
    await App.loadWeb3()
    await App.loadAccount()
    await App.loadContract()
    await App.render()
  },

  // https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
  loadWeb3: async () => {
    if (typeof web3 !== 'undefined') {
      App.web3Provider = web3.currentProvider
      web3 = new Web3(web3.currentProvider)
    } else {
      window.alert("Please connect to Metamask.")
    }
    // Modern dapp browsers...
    if (window.ethereum) {
      window.web3 = new Web3(ethereum)
      try {
        // Request account access if needed
        await ethereum.enable()
        // Acccounts now exposed
        web3.eth.sendTransaction({/* ... */})
      } catch (error) {
        // User denied account access...
      }
    }
    // Legacy dapp browsers...
    else if (window.web3) {
      App.web3Provider = web3.currentProvider
      window.web3 = new Web3(web3.currentProvider)
      // Acccounts always exposed
      web3.eth.sendTransaction({/* ... */})
    }
    // Non-dapp browsers...
    else {
      console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
    }
  },

  loadAccount: async () => {
    // Set the current blockchain account
    App.account = web3.eth.accounts[0]
  },

  loadContract: async () => {
    // Create a JavaScript version of the smart contract
    const todoList = await $.getJSON('TodoList.json')
    App.contracts.TodoList = TruffleContract(todoList)
    App.contracts.TodoList.setProvider(App.web3Provider)

    // Hydrate the smart contract with values from the blockchain
    App.todoList = await App.contracts.TodoList.deployed()
  },

  render: async () => {
    // Prevent double render
    if (App.loading) {
      return
    }

    // Update app loading state
    App.setLoading(true)

    // Render Account
    $('#account').html(App.account)

    // Render Tasks
    await App.renderTasks()

    // Update loading state
    App.setLoading(false)
  },

  renderTasks: async () => {
    // Load the total task count from the blockchain
    const taskCount = await App.todoList.taskCount()
    const $taskTemplate = $('.taskTemplate')

    // Render out each task with a new task template
    for (var i = 1; i <= taskCount; i++) {
      // Fetch the task data from the blockchain
      const task = await App.todoList.tasks(i)
      const taskId = task[0].toNumber()
      const taskContent = task[1]
      const taskCompleted = task[2]

      // Create the html for the task
      const $newTaskTemplate = $taskTemplate.clone()
      $newTaskTemplate.find('.content').html(taskContent)
      $newTaskTemplate.find('input')
                      .prop('name', taskId)
                      .prop('checked', taskCompleted)
                      // .on('click', App.toggleCompleted)

      // Put the task in the correct list
      if (taskCompleted) {
        $('#completedTaskList').append($newTaskTemplate)
      } else {
        $('#taskList').append($newTaskTemplate)
      }

      // Show the task
      $newTaskTemplate.show()
    }
  },

  setLoading: (boolean) => {
    App.loading = boolean
    const loader = $('#loader')
    const content = $('#content')
    if (boolean) {
      loader.show()
      content.hide()
    } else {
      loader.hide()
      content.show()
    }
  }
}

$(() => {
  $(window).load(() => {
    App.load()
  })
})

讓我解釋一下這段代碼。我們創建了一個新App對象,其中包含運行JavaScript應用程序所需的所有函數。我將在這裡解釋重要的功能。有關完整說明,請觀看我在40:25解釋視頻中的JavaScript代碼。另請注意,我已經注釋掉了幾行代碼,我們將在後面的部分中啟用它們。

  • loadWeb3() web3.js是一個JavaScript庫,允許我們的客戶端應用程序與區塊鏈交談。我們在這裡配置web3。這是Metamask指定的默認Web3配置。如果您不完全了解這裡發生的事情,請不要擔心。這是Metamask建議的複製和粘貼實現。
  • loadContract()這是我們從區塊鏈載入智能合約數據的地方。我們使用Truffle Contract庫創建智能結構的JavaScript表示。然後我們用web3載入智能合約數據。這將允許我們列出待辦事項列表中的任務。
  • renderTasks()這是我們實際列出待辦事項列表中的任務的地方。請注意,我們創建了一個for循環來單獨訪問每個任務。那是因為我們無法從智能合約中獲取整個任務映射。我們必須首先確定taskCount並逐個獲取每個任務。

現在讓我們啟動Web伺服器並確保項目將在瀏覽器中載入。

$ npm run dev

好極了?您已成功載入客戶端應用程序。?請注意,您的應用程序顯示「正在載入…」。那是因為我們還沒有登錄到區塊鏈為了連接到區塊鏈,我們需要將其中一個帳戶從Ganache導入Metamask。您可以在43:55觀看我在視頻中設置Metamask

以太坊Todo列表載入與Metamask連接後,您應該會看到所有合約和帳戶數據都已載入。

已載入以太坊Todo列表?轟有你的待辦事項列表?

測試

現在讓我們編寫一個基本測試,以確保todo list smart conract正常工作。首先,讓我解釋為什麼在開發智能合約時測試非常重要。我們希望確保合約沒有錯誤,原因如下:

  1. 以太坊區塊鏈上的所有代碼都是不可變的; 它無法改變。如果合約包含任何錯誤,我們必須禁用它並部署新副本。此新副本與舊合約的狀態不同,它將具有不同的地址。
  2. 部署合約會產生氣體,因為它會創建一個事務並將數據寫入區塊鏈。這會花費以太,我們希望盡量減少我們必須支付的以太坊數量。
  3. 如果我們寫入區塊鏈的任何合約函數都包含錯誤,那麼調用此函數的帳戶可能會浪費以太,並且它可能不會按照預期的方式運行。

讓我們創建一個這樣的測試文件:

$ test/TodoList.test.js

我們將使用Mocha測試框架Chai斷言庫在此文件中的Javascript中編寫所有測試。這些與Truffle框架捆綁在一起。我們將在Javascript中編寫所有這些測試,以模擬與智能合約的客戶端交互,就像我們在控制台中所做的那樣。以下是測試的所有代碼:

const TodoList = artifacts.require('./TodoList.sol')

contract('TodoList', (accounts) => {
  before(async () => {
    this.todoList = await TodoList.deployed()
  })

  it('deploys successfully', async () => {
    const address = await this.todoList.address
    assert.notEqual(address, 0x0)
    assert.notEqual(address, '')
    assert.notEqual(address, null)
    assert.notEqual(address, undefined)
  })

  it('lists tasks', async () => {
    const taskCount = await this.todoList.taskCount()
    const task = await this.todoList.tasks(taskCount)
    assert.equal(task.id.toNumber(), taskCount.toNumber())
    assert.equal(task.content, 'Check out dappuniversity.com')
    assert.equal(task.completed, false)
    assert.equal(taskCount.toNumber(), 1)
  })
})

讓我解釋一下這段代碼。首先,我們要求合約並將其分配給變數,就像我們在遷移文件中所做的那樣。接下來,我們調用「契約」函數,並在回調函數中編寫所有測試。此回調函數提供了一個「帳戶」變數,表示我們的區塊鏈上的所有帳戶,由Ganache提供。

第一個測試通過檢查其地址來檢查合約是否已正確部署到區塊鏈。

下一個測試通過檢查我們在初始化函數中創建的默認任務來檢查智能合約是否正確地列出了任務。

現在讓我們從命令行運行測試,如下所示:

$truffle test

是的,他們通過了?如果您遇到困難,我可以跟隨我,因為我在視頻中編寫這些測試以獲得進一步的解釋。

創建任務

本部分教程的隨附視頻片段於1:05:07開始。

我們已經創建了一個用於創建任務的函數,但它還沒有完成。那是因為我想在創建新任務的任何時候觸發事件。Solidity允許我們觸發外部消費者可以訂閱的任意事件。它將允許我們在客戶端應用程序內部監聽這些事件等…讓我們創建一個TaskCreated()事件並在createTask()函數中創建新任務時觸發它,如下所示:

pragma solidity ^0.5.0;

contract TodoList {

  // ...

  event TaskCreated(
    uint id,
    string content,
    bool completed
  );

  // ...

  function createTask(string memory _content) public {
    taskCount ++;
    tasks[taskCount] = Task(taskCount, _content, false);
    emit TaskCreated(taskCount, _content, false);
  }

}

現在讓我們創建一個測試,以確保在創建新任務時觸發此事件。我們將在創建新任務時檢查交易收據。這將包含將包含事件數據的所有日誌信息。我們可以像這樣在我們的測試中檢查這些數據,以確保事件被正確觸發:

it('creates tasks', async () => {
  const result = await this.todoList.createTask('A new task')
  const taskCount = await this.todoList.taskCount()
  assert.equal(taskCount, 2)
  const event = result.logs[0].args
  assert.equal(event.id.toNumber(), 2)
  assert.equal(event.content, 'A new task')
  assert.equal(event.completed, false)
})

現在讓我們進行測試:

$ truffle test

是的,他們通過了?現在讓我們將代碼更改後的智能合約的新副本部署到區塊鏈:

$ truffle migrate --reset

現在讓我們更新客戶端代碼。我們首先取消注釋index.html文件中的表單代碼:

<form onSubmit="App.createTask(); return false;">
  <input id="newTask" type="text" class="form-control" placeholder="Add task..." required>
  <input type="submit" hidden="">
form>

現在我們將createTask()app.js文件中添加一個函數,如下所示:

createTask: async () => {
  App.setLoading(true)
  const content = $('#newTask').val()
  await App.todoList.createTask(content)
  window.location.reload()
},

現在您應該能夠從客戶端應用程序添加新任務請注意,表單上沒有「提交」按鈕。我把它留下來簡化用戶界面。您必須按鍵盤上的「enter」鍵才能提交任務。完成後,您會看到彈出Metamask確認。您必須簽署此事務才能創建任務。

完成任務

本部分教程的附帶視頻片段從1:16:40開始。

現在我們在本教程中要做的最後一件事是「檢查」待辦事項列表中的任務。一旦我們這樣做,他們就會出現在「已完成」的列表中,儘管如此。首先,我們將更新智能合約。我們將添加一個TaskComplted()事件,並在一個新toggleCompleted()函數中觸發它,如下所示:

pragma solidity ^0.5.0;

contract TodoList {

  // ...

  event TaskCompleted(
    uint id,
    bool completed
  );

  // ...

  function toggleCompleted(uint _id) public {
    Task memory _task = tasks[_id];
    _task.completed = !_task.completed;
    tasks[_id] = _task;
    emit TaskCompleted(_id, _task.completed);
  }

}

現在我們將編寫一個這樣的測試:

it('toggles task completion', async () => {
  const result = await this.todoList.toggleCompleted(1)
  const task = await this.todoList.tasks(1)
  assert.equal(task.completed, true)
  const event = result.logs[0].args
  assert.equal(event.id.toNumber(), 1)
  assert.equal(event.completed, true)
})

現在讓我們進行測試:

$ truffle test

是的,它過去了?現在讓我們將代碼更改後的智能合約的新副本部署到區塊鏈:

$ truffle migrate --reset

現在讓我們更新客戶端代碼。首先,我們將取消注釋renderTasks()函數內的事件監聽器:

$newTaskTemplate.find('input')
  .prop('name', taskId)
  .prop('checked', taskCompleted)
  .on('click', App.toggleCompleted)

現在我們將toggleCompleted()app.js文件中添加一個函數,如下所示:

toggleCompleted: async (e) => {
  App.setLoading(true)
  const taskId = e.target.name
  await App.todoList.toggleCompleted(taskId)
  window.location.reload()
},

現在,在客戶端應用程序中找到一個任務,然後單擊複選框。簽署此交易後,它將從待辦事項列表中檢查任務

恭喜?您已成功構建了由以太坊智能合約提供支持的完整堆棧區塊鏈應用程序您可以將完整的源代碼下載到本教程這裡,並觀看完整的視頻在這裡

原文:https://www.dappuniversity.com/articles/blockchain-app-tutorial

提示:投資有風險,入市需謹慎,本資訊不作為投資理財建議。請理性投資,切實提高風險防範意識;如有發現的違法犯罪線索,可積極向有關部門舉報反映。
你可能還喜歡