以太坊DApp開發指南

weixin_34050427發表於2018-04-29

DApp架構設計

3018197-122a660b53f960ec.png
DApp架構.png

如上圖,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/

3018197-72402da804e66ea2.jpg
Footmark.jpg

3018197-9833252c03b0f814.jpg
Footmark_detail.jpg

編譯的過程和結果都非常直觀,更方便的一點是可以幫助開發者及時發現問題

合約部署

合約的部署需要藉助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

相關文章