1. 文章摘要
【本文目標】
通過逐步的指導和截圖舉證,一步步帶領一個技術小白完成一個寵物商店DAPP應用的開發和部署。
【環境前置條件】
參考《第一課 如何在WINDOWS環境下搭建以太坊開發環境》,已完成Ubuntu的安裝,已完成TRUFFLE,Ganache-cli,lite-server的安裝; 本案例是通過WINDOWS的XSHELL客戶端同本機的Ubuntu命令操作進行的。 已在本地WIDOWS環境完成MetaMask輕錢包客戶端的安裝。 最好遵循從頭開始的課程學習順序。不過如果你想半途插入實操學習,問題也不大,遇到障礙時反向找對應文章的指導內容即可完成。
【技術收穫】
從本實踐中,你可以學習到: 搭建智慧合約開發環境 建立Truffle專案 編寫智慧合約 編譯和部署智慧合約到區塊鏈 如何通過Web3和智慧合約互動 MetaMask 的使用
【實操課程列表】
第二課 如何實現以太坊最簡智慧合約“Hello World”的執行
第六課 技術小白如何開發一個DAPP區塊鏈應用(以寵物商店為例)
第七課 技術小白如何在45分鐘內發行通證(TOKEN)並上線交易
【說明】未列出的課程為知識普及的非實操類課程,所有區塊鏈文章參考“區塊鏈入口”專欄。
2. 下載/編寫TRUFFLE框架的智慧合約
專案背景
Pete有一個寵物店,有16只寵物狗,他想開發一個去中心化應用,讓大家來領養寵物。 在truffle box中,已經提供了pet-shop的網站部分的程式碼,我們只需要編寫合約及互動部分。 【官網原始參考】 程式碼框架下載:truffleframework.com/boxes/pet-s… 開發指導:truffleframework.com/tutorials/p…
環境搭建
環境需要NodeJS, Truffle,Ganache-Cli, Lite-Server, Meta-Mask等程式,按照步驟可以參考《第一課 如何在WINDOWS環境下搭建以太坊開發環境》 搜尋對應關鍵字在已安裝的UBUNTU作業系統完成安裝。
- 安裝Node:
sudo apt-get install curl curl -sL deb.nodesource.com/setup_6.x | sudo -E bash sudo apt-get install -y nodejs
- 安裝 Truffle :
npm install -g truffle
- 安裝Ganache
sudo npm install -g ganache-cli
【說明】Ganache(或Ganache CLI)已經取代了 testrpc。
4.Lite-Server, Meta-Mask安裝
本文後面章節描述。
建立專案
- 建立專案目錄並進入
mkdir dapp-guide-pet-shop
cd dapp-guide-pet-shop
複製程式碼
- 使用truffle unbox 建立專案 下載官方程式程式碼
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
複製程式碼
專案目錄結構
contracts/
智慧合約的資料夾,所有的智慧合約檔案都放置在這裡,裡面包含一個重要的合約Migrations.sol(稍後再講)
migrations/
用來處理部署(遷移)智慧合約 ,遷移是一個額外特別的合約用來儲存合約的變化。
test/
智慧合約測試用例資料夾
truffle.js/
配置檔案
其他程式碼可以暫時不用管
【說明】如果想了解TRUFFLE框架更詳細的內容,可參考文章《第四課 以太坊開發框架Truffle從入門到實戰》
編寫智慧合約
智慧合約承擔著分散式應用的後臺邏輯和儲存。智慧合約使用solidity編寫。
在contracts目錄下,新增合約檔案Adoption.sol
pragma solidity ^0.4.17;
contract Adoption {
address[16] public adopters; // 儲存領養者的地址
// 領養寵物
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15); // 確保id在陣列長度內
adopters[petId] = msg.sender; // 儲存呼叫這地址
return petId;
}
// 返回領養者
function getAdopters() public view returns (address[16]) {
return adopters;
}
}
複製程式碼
編譯部署智慧合約
Truffle整合了一個開發者控制檯,可用來生成一個開發鏈用來測試和部署智慧合約。
編譯
Solidity是編譯型語言,需要把可讀的Solidity程式碼編譯為EVM位元組碼才能執行。 進入dapp的根目錄dapp-guide-pet-shop執行命令,
> truffle compile
複製程式碼
輸出
Compiling ./contracts/Adoption.sol...
Writing artifacts to ./build/contracts
複製程式碼
編寫部署程式碼
編譯之後,就可以部署到區塊鏈上。 在migrations資料夾下已經有一個1_initial_migration.js部署指令碼,用來部署Migrations.sol合約。 Migrations.sol 用來確保不會部署相同的合約。
現在我們來建立一個自己的部署指令碼2_deploy_contracts.js
var Adoption = artifacts.require("Adoption");
module.exports = function(deployer) {
deployer.deploy(Adoption);
};
複製程式碼
安裝啟動Ganache測試環境
在執行部署之前,需要確保有一個區塊鏈執行, 可以使用 Ganache來開啟一個私鏈來進行開發測試。 參考文章《第一課 如何在WINDOWS環境下搭建以太坊開發環境》 對應的章節,完成ganache-cli的安裝。
sudo npm install -g ganache-cli
在新的命令列視窗執行ganache-cli程式:
cd /usr/work/Ganache ganache-cli >> trace.log
這個>>目的是把ganache-cli的結果輸出到檔案,便於後面複製錢包地址用於檢視ETH餘額。作者實踐時把這2行命令合併成一行執行。
ganache-cli >> /usr/work/Ganache/trace.log
這個命令視窗用於執行Ganache程式輸出,不可關閉。需要新開一個命令視窗用於執行TRUFFLE的命令。
開啟trace.log檔案,可檢視預設啟動的10個錢包賬戶和對應的私鑰地址:Available Accounts
==================
(0) 0x7554cc8c721712adde43e67a5669225bbe8f21f6
(1) 0x3e6ce6a43c1fa565b5b90963bef090625d3edc6c
(2) 0xeb2eb6ed4b325e77f5a597497ec5ffaa2f5c2650
(3) 0x1de061d5f225533f7e3c38a5905a6ca2ecb3e55a
(4) 0x3ec2e1ed3f47fc7ab9cca1fe09afe9fd1feb789b
(5) 0xf27a303880b73a0a287a2e5dc1286098fb49ed63
(6) 0xf3c007932e1de894503166aee3cbf85b4aff0188
(7) 0xe7655733659c14c7c83fb71bd40dc51796592d96
(8) 0x507722000223ca96ac646198709b9ae3f7f49a5a
(9) 0x82b087a83f72cc7dd5ca6ac7787a366b2c3ff143
Private Keys
==================
(0) 629551aa45c594ce822c5b4a378d01cf46fb57c15b69a61eb400a4867ffab002
(1) a90103a95ed805acc52782eaa29eb061f6c2a9431fed3ac18a683ea3143a29b6
(2) c2b13ce370fb235997dc4783ce591a22e5b3909e934dc7c0f61797ce57d5059d
(3) 841d0e1d3dc658ad2f308c7292b4d5c40da158d170cf3ed9001c64b8352cd0c2
(4) 255225aedba340b6ec62e6c86a6202535d7382704b129d022461c69b8341d2dd
(5) c3fd784fc7a46edb5e7cba9e90120e51152a3fbe2d8b97a0c3106791d3bbe87e
(6) f8f90b056c419464d48dcd12ac8e326a31ebe300ea5e245876fe0308c511fbac
(7) 09e20553971b044c1fda8ac88f2ed1dcdff6af0889fc05c2ad4374c52d4f52d7
(8) 142643d0fb6f4a78e0fd291a568b784869927d8f8eee87bf3fbdbf493ec3f425
(9) 8f89d4a05582193cc32f800ecf9419f35b5384abc5db42afd4ec016a54d27716
複製程式碼
配置以太坊客戶端本地環境
ruffle.js是truffle的配置檔案,位於dapp-guide-pet-shop目錄下,啟動好以太坊本地結點以後,我們需要讓truffle去識別它並使用它,這就需要在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: "*" // Match any network id
}
}
};
複製程式碼
執行部署命令
接下來在新的命令視窗執行部署命令
cd /usr/work/dapp-guide-pet-shop truffle migrate
執行後,有一下類似的輸出,
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x29612ceea67bc946cc6ae82afbedc546f9a53ba8cab5d804f9025fb8f15e48f8
Migrations: 0x8af912046664ba26738b811c34068d42216528c0
Saving successful migration to network...
... 0x6c968a3c492439ab22028e1956360a6b73a02716c436b15234cd29804dac7298
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Adoption...
... 0x27448e4f75b608015f3670e3650cf607c882fe7f1a32f98e8a94bf7a406c871c
Adoption: 0xe42f434105a7e0eacf4f4229c76e1e135d536db6
Saving successful migration to network...
... 0x9375cd6fa143d01a520c5ed0d46cfe859f482b01e37cf678d751c5db6a278e5f
Saving artifacts...
複製程式碼
檢視Ganache-cli的輸出檔案:trace.log,可以看到區塊鏈狀態的變化,現在產生了4個區塊。
net_version
eth_accounts
eth_accounts
net_version
net_version
eth_sendTransaction
Transaction: 0x29612ceea67bc946cc6ae82afbedc546f9a53ba8cab5d804f9025fb8f15e48f8
Contract created: 0x8af912046664ba26738b811c34068d42216528c0
Gas usage: 268407
Block Number: 1
Block Time: Tue Apr 10 2018 09:10:18 GMT+0800 (CST)
eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction
Transaction: 0x6c968a3c492439ab22028e1956360a6b73a02716c436b15234cd29804dac7298
Gas usage: 41981
Block Number: 2
Block Time: Tue Apr 10 2018 09:10:19 GMT+0800 (CST)
eth_getTransactionReceipt
eth_accounts
net_version
net_version
eth_sendTransaction
Transaction: 0x27448e4f75b608015f3670e3650cf607c882fe7f1a32f98e8a94bf7a406c871c
Contract created: 0xe42f434105a7e0eacf4f4229c76e1e135d536db6
Gas usage: 247573
Block Number: 3
Block Time: Tue Apr 10 2018 09:10:19 GMT+0800 (CST)
eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction
Transaction: 0x9375cd6fa143d01a520c5ed0d46cfe859f482b01e37cf678d751c5db6a278e5f
Gas usage: 26981
Block Number: 4
Block Time: Tue Apr 10 2018 09:10:20 GMT+0800 (CST)
eth_getTransactionReceipt
複製程式碼
這時說明已經智慧合約已經部署好了。
測試
現在我們來測試一下智慧合約,測試用例可以用 JavaScript or Solidity來編寫,這裡使用Solidity。
在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());
// 領養測試用例
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(8);
uint expected = 8;
Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}
// 寵物所有者測試用例
function testGetAdopterAddressByPetId() public {
// 期望領養者的地址就是本合約地址,因為交易是由測試合約發起交易,
address expected = this;
address adopter = adoption.adopters(8);
Assert.equal(adopter, expected, "Owner of pet ID 8 should be recorded.");
}
// 測試所有領養者
function testGetAdopterAddressByPetIdInArray() public {
// 領養者的地址就是本合約地址
address expected = this;
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}
}
複製程式碼
Assert.sol 及 DeployedAddresses.sol是Truffle框架提供,在test目錄下並不提供truffle目錄。
TestAdoption合約中新增adopt的測試用例
執行測試用例
在終端中,執行
truffle test
複製程式碼
如果測試通過,則終端輸出:
Using network 'development'.
Compiling ./contracts/Adoption.sol...
Compiling ./test/TestAdoption.sol...
Compiling truffle/Assert.sol...
Compiling truffle/DeployedAddresses.sol...
Compilation warnings encountered:
truffle/Assert.sol:1563:9: Warning: Use of the "var" keyword is deprecated.
var nstr = _itoa(value, 10);
^------^
,truffle/Assert.sol:1580:9: Warning: Use of the "var" keyword is deprecated.
var nstr = _utoa(value, 10);
^------^
,truffle/Assert.sol:1597:9: Warning: Use of the "var" keyword is deprecated.
var nstr = _ltoa(value);
^------^
,truffle/Assert.sol:1347:13: Warning: Invoking events without "emit" prefix is deprecated.
TestEvent(true, "");
^-----------------^
,truffle/Assert.sol:1349:13: Warning: Invoking events without "emit" prefix is deprecated.
TestEvent(false, message);
^-----------------------^
TestAdoption
✓ testUserCanAdoptPet (246ms)
✓ testGetAdopterAddressByPetId (231ms)
✓ testGetAdopterAddressByPetIdInArray (287ms)
3 passing (2s)
複製程式碼
3. 建立使用者介面和智慧合約互動
我們已經編寫和部署及測試好了我們的合約,接下我們為合約編寫UI,讓合約真正可以用起來。
在Truffle Box pet-shop
裡,已經包含了應用的前端程式碼,程式碼在src/
資料夾下。
在編輯器中開啟src/js/app.js
可以看到用來管理整個應用的App物件,init函式載入寵物資訊,就初始化web3.
web3是一個實現了與以太坊節點通訊的庫,我們利用web3來和合約進行互動。
初始化web3
接下來,我們來編輯app.js修改initWeb3(): 刪除註釋,修改為:
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();
}
複製程式碼
程式碼中優先使用 MetaMask提供的web3例項,如果沒有則從本地環境建立一個。
例項化合約
使用truffle-contract會幫我們儲存合約部署的資訊,就不需要我們手動修改合約地址,修改initContract()程式碼如下:
initContract: function() {
// 載入Adoption.json,儲存了Adoption的ABI(介面說明)資訊及部署後的網路(地址)資訊,它在編譯合約的時候生成ABI,在部署的時候追加網路資訊
$.getJSON('Adoption.json', function(data) {
// Get the necessary contract artifact file and instantiate it with truffle-contract
// 用Adoption.json資料建立一個可互動的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()程式碼:
markAdopted: function(adopters, account) {
var adoptionInstance;
App.contracts.Adoption.deployed().then(function(instance) {
adoptionInstance = instance;
// 呼叫合約的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()程式碼:
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;
// 傳送交易領養寵物
return adoptionInstance.adopt(petId, {from: account});
}).then(function(result) {
return App.markAdopted();
}).catch(function(err) {
console.log(err.message);
});
});
}
複製程式碼
4. 安裝和配置lite-server
【定義】lite-server 是一個全功能的網站架設工具軟體包輕量級的,僅適用於開發 的 node 伺服器, 它僅支援 web app。 它能夠為你開啟瀏覽器, 當你的html或是JavaScript檔案變化時,它會識別到並自動幫你重新整理瀏覽器, 還能使用套接字自動注入變化的CSS, 當路由沒有被找到時,它將自動後退頁面。 參考文章《第一課 如何在WINDOWS環境下搭建以太坊開發環境》的"(8)安裝 lite-server 【可選】”章節完成lite-server的安裝。
bs-config.json檔案指示了lite-server的工作目錄。
{
"server": {
"baseDir": ["./src", "./build/contracts"]
}
}
複製程式碼
./src
是網站檔案目錄
./build/contracts
是合約輸出目錄
以此同時,在package.json檔案的scripts中新增了dev命令:
{
"name": "pet-shop",
"version": "1.0.0",
"description": "",
"main": "truffle.js",
"directories": {
"test": "test"
},
"scripts": {
"dev": "lite-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"lite-server": "^2.3.0"
}
}
複製程式碼
當在新的命令視窗執行npm run dev的時候,就會啟動lite-server
cd /usr/work/dapp-guide-pet-shop npm run dev
正常的執行結果有如下類似輸出內容:
> pet-shop@1.0.0 dev /usr/work/dapp-guide-pet-shop
> 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.80.144:3000
---------------------------------------
UI: http://localhost:3001
UI External: http://192.168.80.144:3001
---------------------------------------
[Browsersync] Serving files from: ./src
[Browsersync] Serving files from: ./build/contracts
[Browsersync] Watching files...
複製程式碼
5. 安裝 MetaMask和配置區塊鏈網路
安裝 MetaMask
【定義】MetaMask 是一款外掛形式的以太坊輕客戶端,開發過程中使用MetaMask和我們的dapp進行互動是個很好的選擇。 作者是在本地WINDOWS的CHROME瀏覽器上安裝MetaMask錢包工具,具體的安裝方法參考文章《第一課 如何在WINDOWS環境下搭建以太坊開發環境》的“(7)安裝 MetaMask 【可選】”章節。 說明下,最詳細的MetaMask安裝/配置文章可參考歐陽哥哥的《以太坊錢包MetaMask使用教程》
配置錢包
1. 接受隱私條款 點選瀏覽器位址列右側,MetaMask的狐狸頭圖示,第一次使用時,會出一個隱私提示,如下圖:
2. 接受服務條款
點選Accept
按鈕,顯示的是MetaMask的服務條款,如下圖2-2:
Accept
按鈕預設是灰色的,將滾動條拉到底部,就可以點選Accept
按鈕了
3、建立新賬號
- 建立新賬號:輸入一串8位以上的密碼,再次重複輸入,點選
CREATE
按鈕,即可完成。 下面以建立新賬號為例:
在點選CREATE
按鈕後,MetaMask會為使用者建立12個英文助記詞,一定要儲存好這些助記詞,點選SAVE SEED WORDS AS FILE
可以將助詞詞以檔案的形式儲存到本地,建議使用紙筆手工記錄並收藏於安全的地方。
連線開發區塊鏈網路
預設連線的是以太坊主網(左上角顯示),選擇Custom RPC,新增Ubuntu的IP地址作為客戶自定義RPC網路
連結測試機以太坊客戶端成功,選單有連結成功的橙色√提示。
這是左上角顯示為Private Network,此時顯示的是預設的Account 1空賬號,ETH數量為0。匯入Ganache-cli的第一個錢包賬號
檢視“trace.log”檔案,可以Ganache-cli的第一個預設錢包地址為
(0) 629551aa45c594ce822c5b4a378d01cf46fb57c15b69a61eb400a4867ffab002
(1) a90103a95ed805acc52782eaa29eb061f6c2a9431fed3ac18a683ea3143a29b6
複製程式碼
選擇MetaMask的“Import Account”輸入私鑰,獲取賬號,可以檢視對應ETH餘額。
第一個錢包賬戶的餘額:第二個錢包賬戶的餘額:
至此MetaMask的安裝,配置已經完成。
6. 領養寵物
修改.\dapp-guide-pet-shop\src\index.html
檔案引用jquery的地址
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<!--
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
-->
<script src="http://libs.baidu.com/jquery/2.1.1/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
複製程式碼
在WINDOWS瀏覽器輸入測試網站地址 http://192.168.80.144:3000/ 可以看到寵物商店的入口介面:
點選“Adopt”按鈕,領養這個最漂亮的狗狗寶貝。MetaMask會提示我們交易的確認,如圖: 點選“SUBMIT”按鈕成功後,這個狗狗的按鈕變為"SUCCESS",該狗狗已被包養了。 檢視當前開啟的賬號ACCOUNT6,發現金額不再是100ETH,而是99.999ETH了。 恭喜你,開發併成功部署了一個DAPP區塊鏈應用程式,並且領養了幾隻小狗狗。7. 知識對接服務
我們在知識星球開通了區塊鏈入門專欄,用於存放本專案的工程原始碼等內容,並建立專項微信群用於技術交流,歡迎加入。
8. 參考
1),Truffle官網文件