以太坊DApp開發指南
DApp架構設計
如上圖,DApp的架構我們可以簡單分為以上三種型別:輕錢包模式、重錢包模式和相容模式。
輕錢包模式
輕錢包模式下我們需要有一個開放Http RPC協議的節點與錢包通訊,這個節點可以是任意鏈上的節點。輕錢包通常會作為一個瀏覽器外掛存在,外掛在執行時會自動注入Web3框架,DApp可以通過Web3與區塊鏈節點通訊。當DApp只是單純的獲取資料時是不需要錢包介入的,但是當DApp需要傳送交易到鏈上時需要通過錢包完成對交易簽名的過程。
優點:不需要使用者同步區塊鏈節點就可以使用
缺點:需要一個公開的節點提供服務,可能會存在安全性問題
重錢包模式
重錢包會自己同步並持有一個區塊鏈節點,提供一個瀏覽器環境,其他與錢包相似。
優點:自己持有並同步節點,安全性高
缺點:需要持有一個全量的區塊鏈節點
相容模式
相容模式可以在輕錢包和重錢包下同時使用,與錢包通訊的節點可以選擇在錢包外本地持有,也可以自己搭建服務持有並公佈節點。
DApp開發
理解了DApp的架構設計就可以開始一步步的搭建我們的DApp了,這裡我們不選擇用各種成熟的框架。從最基礎的開始,會更容易理解核心的思想。選擇一個輕量級的錢包外掛MetaMask,安裝並建立自己的賬號。
MetaMask預設會提供以下節點可以使用:
- Main Ethereum Network
- Ropsten Test Network
- Kovan Test Network
- Rinkeby Test Network
- Localhost 8545
當然你也可以手動新增自己的節點
編寫並編譯智慧合約
以太坊提供一個圖靈完備的開發環境,理論上可以構建任意複雜的智慧合約,但是也要考慮到越複雜的邏輯越容易出錯,並且會消耗更多的Gas,因此在設計上需要謹慎考慮。關於智慧合約的編寫這裡不再贅述。這裡有一個簡單的合約:
pragma solidity ^0.4.21;
/**
Footmark
*/
contract Footmark {
struct Log {
uint time;
string text;
}
mapping (address=>mapping(address=>Log)) private logs;
function Footmark() public {
}
// Leave a message to somebody
function leaveMessage(string text,address to) public returns(uint time) {
bytes memory textBytes = bytes(text);
require(textBytes.length > 0 && textBytes.length < 100);
time = now;
logs[msg.sender][to] = Log(time, text);
return time;
}
// Leave a message to myself
function enter(string text) public returns(uint time) {
return leaveMessage(text, msg.sender);
}
// Lookup message from somebody
function lookupFrom(address from) public view returns(uint time, string text) {
return (logs[from][msg.sender].time,logs[from][msg.sender].text);
}
// Lookup message from myself to somebody
function lookupTo(address to) public view returns(uint time, string text) {
return (logs[msg.sender][to].time,logs[msg.sender][to].text);
}
}
邏輯非常簡單,任何人都可以在該合約中給其他人留言,所有人都可以檢視留給自己的資訊或者自己留給其他人的資訊。
接下來我們編譯一下我們剛剛寫的智慧合約。各種框架都有提供合約編譯的功能,比如Truffle。為了方便了解合約的編譯過程,我們使用比較基礎的Solidity的編譯器solc來完成。
如果通過
npm install -g solc
方式安裝,會另外得到一個命令列工具solcjs,當然直接引用solc模組是可以用js指令碼完成編譯的
var solc = require('solc')
var input = 'pragma solidity ^0.4.21;contract Footmark {struct Log {uint time;string text;}mapping (address=>mapping(address=>Log)) private logs;function Footmark() public {}function leaveMessage(string text,address to) public returns(uint time) {bytes memory textBytes = bytes(text);require(textBytes.length > 0 && textBytes.length < 100);time = now;logs[msg.sender][to] = Log(time, text);return time;}function enter(string text) public returns(uint time) {return leaveMessage(text, msg.sender);}function lookupFrom(address from) public view returns(uint time, string text) {return (logs[from][msg.sender].time,logs[from][msg.sender].text);}function lookupTo(address to) public view returns(uint time, string text) {return (logs[msg.sender][to].time,logs[msg.sender][to].text);}}'
// Setting 1 as second paramateractivates the optimiser
var output = solc.compile(input, 1)
for (var contractName in output.contracts) {
// code and ABI that are needed by web3
console.log(contractName + ': ' + output.contracts[contractName].bytecode)
console.log(contractName + '; ' + JSON.parse(output.contracts[contractName].interface))
}
方便期間我們使用命令列編譯
solcjs Footmark.sol --abi --bin
會得到兩個檔案,後續我們會用到這兩個檔案的內容
- Footmark_sol_Creation.bin :編譯後的binary code
- Footmark_sol_Creation.abi :編譯後的abi
還有一種更方便和直觀的合約編譯方式http://remix.ethereum.org/
編譯的過程和結果都非常直觀,更方便的一點是可以幫助開發者及時發現問題
合約部署
合約的部署需要藉助Web3框架來完成,對於以太坊節點來說合約的部署會被視作一次交易,合約的內容會被儲存在鏈上,因此部署過程需要藉助錢包來完成交易簽名,部署程式碼如下:
let abi = [{"constant":true,"inputs":[{"name":"to","type":"address"}],"name":"lookupTo","outputs":[{"name":"time","type":"uint256"},{"name":"text","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"text","type":"string"}],"name":"enter","outputs":[{"name":"time","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"text","type":"string"},{"name":"to","type":"address"}],"name":"leaveMessage","outputs":[{"name":"time","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"from","type":"address"}],"name":"lookupFrom","outputs":[{"name":"time","type":"uint256"},{"name":"text","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"}];
let binaryData = '0x6060604052341561000f57600080fd5b61082f8061001e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063762db2b4146100675780639d4ff8ad14610120578063b2c21c9114610191578063bcea56e014610221575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506102da565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b838110156100e45780820151818401526020810190506100c9565b50505050905090810190601f1680156101115780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561012b57600080fd5b61017b600480803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610486565b6040518082815260200191505060405180910390f35b341561019c57600080fd5b61020b600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610499565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b610258600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061058a565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561029e578082015181840152602081019050610283565b50505050905090810190601f1680156102cb5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b60006102e4610736565b6000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001546000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101808054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104765780601f1061044b57610100808354040283529160200191610476565b820191906000526020600020905b81548152906001019060200180831161045957829003601f168201915b5050505050905091509150915091565b60006104928233610499565b9050919050565b60006104a361074a565b839050600081511180156104b8575060648151105b15156104c357600080fd5b4291506040805190810160405280838152602001858152506000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008201518160000155602082015181600101908051906020019061057c92919061075e565b509050508191505092915050565b6000610594610736565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600001546000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600101808054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156107265780601f106106fb57610100808354040283529160200191610726565b820191906000526020600020905b81548152906001019060200180831161070957829003601f168201915b5050505050905091509150915091565b602060405190810160405280600081525090565b602060405190810160405280600081525090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061079f57805160ff19168380011785556107cd565b828001600101855582156107cd579182015b828111156107cc5782518255916020019190600101906107b1565b5b5090506107da91906107de565b5090565b61080091905b808211156107fc5760008160009055506001016107e4565b5090565b905600a165627a7a72305820578c1cdd7be62a4f2138d05a15b241857bc9f554fe0f176a194620b04cd8344e0029';
var creationContract = web3.eth.contract(abi);
var creation = creationContract.new(
{
from: web3.eth.accounts[0],
data: binaryData,
gas: '4700000'
}, function (e, contract){
console.log(e, contract);
if (typeof contract.address !== 'undefined') {
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
}
});
這裡的abi和binaryData來自上一步的編譯結果
這裡有以太坊測試網路Ropsten的部署結果:
0xc59565465f95Cd80E7317dd5a03929E6900090Ff
DApp開發
完成合約的編譯和部署之後就可以進行接下來的DApp的開發了。
用之前提到過的MetaMask外掛可以實現Chrome瀏覽器的輕量級錢包功能。MetaMask會在DApp執行環境中注入Web3框架,如果對MetaMask有強依賴的化我們只需要判斷web3物件是否存在即可。
if (typeof web3 === "undefined") {
alert('未檢測到Web3環境,請使用整合以太坊錢包的瀏覽器檢視');
return;
}
環境檢測成功後就可以準備合約呼叫相關的依賴了,編譯合約的時候生成的ABI描述檔案可以直接構造為Javascript物件
let footmarkABI = [
{
"constant": false,
"inputs": [
{
"name": "text",
"type": "string"
}
],
"name": "enter",
"outputs": [
{
"name": "time",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "text",
"type": "string"
},
{
"name": "to",
"type": "address"
}
],
"name": "leaveMessage",
"outputs": [
{
"name": "time",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "text",
"type": "string"
}
],
"name": "LeaveMessage",
"type": "event"
},
{
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"constant": true,
"inputs": [
{
"name": "from",
"type": "address"
}
],
"name": "lookupFrom",
"outputs": [
{
"name": "time",
"type": "uint256"
},
{
"name": "text",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "to",
"type": "address"
}
],
"name": "lookupTo",
"outputs": [
{
"name": "time",
"type": "uint256"
},
{
"name": "text",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
];
export default footmarkABI;
ABI是對合約介面的描述,呼叫線上合約的時候需要用到,當然生成的合約地址也是必要資料
data: function() {
return {
account: '',
messageTo: '',
messageContent: '',
lookupMessageTo: '',
lookupMessageFrom: '',
contractInstance: null,
contractAddress: '0xc59565465f95Cd80E7317dd5a03929E6900090Ff'
};
}
// 例項化合約
if(!this.contractInstance) {
var contract = web3.eth.contract(footmarkABI);
this.contractInstance = contract.at(this.contractAddress);
}
到這裡合約就得到了一個例項化的合約物件,可以呼叫合約的方法,完成相應的功能。
獲取錢包當前賬戶
getAccount() {
web3.eth.getAccounts((error, accounts) => {
if (!error) {
this.account = accounts[0];
}
});
}
給其他人留言
leaveMessageTo() {
this.contractInstance.leaveMessage(this.messageContent, this.messageTo, { from: self.account }, (error, result) => {
if (!error) {
var event = this.contractInstance.LeaveMessage((eerror, eresult) => {
event.stopWatching();
alert("留言成功!");
});
} else {
alert("留言失敗!");
}
});
alert("交易簽發,靜等回覆。。。");
}
獲取其他人給自己的留言
getMessageFrom() {
this.contractInstance.lookupFrom(this.lookupMessageFrom, { from: self.account }, (error, result) => {
let time = result[0].toNumber() * 1000;
let msg = result[1];
let date = new Date(time);
if (time > 0) {
alert(date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + '\n' + result[1]);
} else {
alert("未查詢到留言!");
}
});
}
獲取自己給其他人的留言
getMessageTo() {
this.contractInstance.lookupTo(this.lookupMessageTo, { from: self.account }, (error, result) => {
let time = result[0].toNumber() * 1000;
let msg = result[1];
let date = new Date(time);
if (time > 0) {
alert(date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + '\n' + result[1]);
} else {
alert("未查詢到留言!");
}
});
}
功能完成DApp就可以釋出了,當然這只是一個功能非常簡單的DApp,這裡只是把DApp的開發流程描述了一下,隱藏了一些比較繁瑣的細節,比如交易簽名(錢包幫我們完成,其實我們也可以自己來做,後續有時間再把這塊內容詳細介紹一下)、交易狀態監聽等等。
Done
這裡有DApp的合約資訊(Ropsten網路):0xc59565465f95Cd80E7317dd5a03929E6900090Ff
和DApp的釋出版本:
http://dawntech.top/contract
相關文章
- 如何使用Meteor開發以太坊DappAPP
- 理解以太坊DApp及開發工具APP
- 以太坊:Dapp及相關開發工具介紹APP
- 開發者的以太坊入門指南 | Jeth 以太坊系列線下活動
- 給 Web 開發人員的以太坊入坑指南Web
- 給Web開發人員的以太坊入坑指南Web
- 以太坊DApp開發入門教程——區塊鏈投票系統APP區塊鏈
- 基於以太坊的58同城 | DApp開發與應用案例APP
- DAPP系統開發原始碼規則解析 | 如何在以太坊搭建DAPP開發去中心化程式?APP原始碼中心化
- 以太坊開發計劃
- 以太坊Solidity程式語言開發框架————16、Truffle命令指南Solid框架
- [譯] 以太坊入門指南
- EthBox以太坊開發套件,一鍵安裝部署以太坊開發環境套件開發環境
- 開發者的以太坊入門指南 | Jeth 以太坊系列線下活動第三期
- 以太坊智慧合約開發環境搭建以及第一個Dapp開發環境APP
- 開發者的以太坊進階指南 | Jeth 以太坊系列線下活動第四期北京場
- 開發者的以太坊入門指南 | Jeth 以太坊系列線下活動第二期-杭州場
- 寫給前端的區塊鏈開發入門指南:零基礎開發基於以太坊智慧合約的 ICO DApp前端區塊鏈APP
- java使用spring boot和web3j開發以太坊區塊鏈dappJavaSpring BootWeb區塊鏈APP
- python利用web3.py開發以太坊應用dapp的實戰教程PythonWebAPP
- 實戰:以太坊 DApp 開發與構建 - 李明 | Jeth 第二期APP
- 以太坊 DApp 開發入門,如何搭建一個區塊鏈投票系統。APP區塊鏈
- 3.5 以太坊開發環境搭建開發環境
- 以太坊智慧合約開發第二篇:理解以太坊相關概念
- 全棧開發以太坊應用的完整教程指南與原始碼 - Dabit全棧原始碼
- 1小時搞明白以太坊 DAPP 開發 - 熊麗兵 | Jeth 第三期APP
- 1小時搞明白以太坊 DAPP 開發 – 熊麗兵 | Jeth 第三期APP
- 以太坊DApp如何用IPFS儲存並呼叫資料APP
- windows 以太坊開發框架Truffle環境搭建Windows框架
- 開發以太坊遇到的幾個問題
- 基於以太坊的Token開發步驟
- 以太坊學習筆記————4、以太坊發展歷史回顧筆記
- 基於以太坊上實現DApp的登入註冊APP
- 以太坊Solidity程式語言開發框架————5、移植Solid框架
- php工程師進行以太坊開發的教程PHP工程師
- eth以太坊智慧合約交易平臺開發
- ubuntu環境下搭建以太坊開發環境Ubuntu開發環境
- Conflux與以太坊合約開發工具區別UX