分享實錄|以太坊開發需知

image

1

以太坊開發與傳統應用開發的差別

相比起傳統應用而言,以太坊開發引入了新的基礎設施,由此必不可少的帶來了部署和運維的複雜度,好比做爲系統設計者,咱們須要作出選擇:javascript

  • 自建節點,仍是信任第三方節點?前端

  • 公有鏈、聯盟鏈、私有鏈?java

因爲加入了新的設計單元:智能合約,咱們將面對node

  • 設計的複雜度git

    合約的升級問題:由於智能合約一旦發佈就沒法更改,萬一須要更新合約錯誤或規則,怎麼辦?程序員

    合約的組織問題。github

  • 與通常代碼不一樣,合約的好壞直接與金錢掛鉤web

    不安全的合約會形成客戶的金錢損失,立竿見影。算法

    合約的每一步都須要消耗gas,不講究的合約會形成執行成本高居不下。typescript

而且,以太坊自己的限制一樣也會影響到整個應用系統的設計和選型:

  • 交易確認須要時間:20筆/秒

  • 交易易受外界影響

    交易費的高低

    流行應用會形成網絡擁堵,從影響交易的確認

相比起傳統CS編程,與以太坊進行交互要複雜得多:

  • 須要有錢包帳戶

  • 發出去的交易須要簽名

  • 因爲整個過程是異步爲主,所以交易須要驗證

對於區塊鏈自己的定位,一樣也會影響設計:

  • 僅僅用做數據共享和防篡改的基礎設施?

  • 圍繞區塊鏈打造價值網絡?

    Token設計模式

    Token引入對於業務自己帶來的影響

這一點尤爲差別巨大,不僅僅像傳統開發那樣僅僅只須要瞭解用戶的業務就能夠開足馬力前進。Token設計自己須要必定的經濟常識,雖然說這部分能夠由專業背景的人來設計,但對於開發者和架構師而言,不瞭解必要的基礎知識確定會對開發的順利進行有阻礙。

2

以太坊Dapp的典型技術架構

Serverless風格

image

這種架構很是明瞭,客戶端直接與部署在以太坊節點上的智能合約打交道就行了。它的優勢和缺點都很明顯:

優勢:

  • 輕量級

  • 運維簡單

  • 完全的去中心化

缺點:

  • 胖客戶端:交互 + 業務邏輯

  • 智能合約難以承載複雜業務邏輯

典型場景:投票、博彩、小遊戲等

CS + 區塊鏈

image

這種架構至關於傳統CS(注:這裏的傳統相對於區塊鏈應用而言,所以像桌面客戶端 + 服務器、Web系統、先後端、移動互聯網應用等都屬於本文中所說的傳統應用。)融入了區塊鏈,客戶端和服務器都和區塊鏈直接交互。

爲何客戶端也須要跟區塊鏈直接交互?緣由很簡單:區塊鏈應用的帳戶信息(尤爲是私鑰)通常都由用戶本身保管,不會放在服務器上。服務器上只會存放系統本身的帳戶信息。

這種系統的優缺點以下:

優勢:

  • 傳統應用和區塊鏈融合

  • 適用複雜業務邏輯,服務器徹底能夠包含複雜業務邏輯,合約只承載與價值流轉相關的商業規則。

缺點

  • 重量級

  • 運維負擔重

  • 部分中心化,話說回來,在我看來,中心化算不上太壞,由於中心化自己表明了專業化。

典型場景:具備複雜業務邏輯的應用系統,如物品溯源、信用質押、供應鏈金融等等。

Server + 區塊鏈

image

這種架構至關於上面的一種變體:客戶端委託服務器完成與區塊鏈相關的交互,甚至於客戶端徹底都不知道區塊鏈的存在。爲什麼不推薦採用這種架構呢?緣由很明顯:它要求客戶端絕對信任服務器。

這種架構的優缺點以下:

優勢:

  • 對客戶端屏蔽了區塊鏈的複雜度

缺點:

  • 私鑰中心化管理

典型場景:客戶端絕對信任服務器

最後,說說關於密鑰的存放:

  • 若合約部署於第三方節點,如Infura,毫無疑問只能是本身管理。

  • 假如是自建節點,那麼你有兩種選擇

    方式1:託管

    方式2:自管

同時出於保障資金安全的角度:

  • 控制託管帳戶的可用資金,每當可用資金用完,從自管帳戶中轉入

  • 對於自管帳戶,最好也分散風險,創建多個自管帳戶,將資金分散其中,避免被一鍋端。

3

以太坊應用的開發流程

以太坊應用的開發流程以下圖,相比起傳統開發流程沒有本質的區別,只是測試過程相對繁瑣:先本地環境測試,再上測試網試運行,最後部署於主網。只是因爲合約的更新麻煩,所以建議儘可能提早多作一些測試,將問題提早消滅掉。

image

4

以太坊開發注意事項

談完差別,看過架構和展現了開發流程以後,接下來就進入正題,說說本文的重點:以太坊開發的那些坑。

智能合約

智能合約開發的經常使用工具:

  • Solidity + Truffle + VS Code

  • 經常使用類庫:

    Token和ICO相關:OpenZepplin和TokenMarketNet/ICO

    可升級合約:ZOS

關於合約的執行成本,我以前寫過一篇文章(https://www.jianshu.com/p/cfaa4fdb32ac)有詳細介紹,這裏就再也不贅述,請參見原文,避免沒必要要的金錢損失。

關於合約的安全,我在這篇文章中(https://www.jianshu.com/p/ec5ad71e28aa)略有說起。但遠遠不夠,這段時間以來,我也翻閱了相關資料,整理以下:

  • Overflow & Underflow,使用OpenZeppelin的SafeMath lib

  • 可見性和delegatecall說明以下,相關推薦:優先external, 並留意避免在delegatecall中包含惡意代碼

    public,無限制

    external,僅外部調用

    private,僅本合約內

    internal,相似protect

    delegatecall,相似js中的apply,被調用代碼和調用合約處於一個上下文

  • 可重入性(DAO攻擊),利用CDI模式

    檢查 -> 更改合約狀態 -> 支付

  • 優先使用pull模式,而非push/send模式

    withdraw 優於 send/transfer

  • 避免使用隨機數、now和block.blockhash做爲合約邏輯

    分佈式網絡的時鐘問題

  • 注意短地址攻擊,檢查message.data的合法性

    地址不足會用金額部分數據補0

  • 利用Modifier完成權限方面的校驗

至於合約的設計和組織:

  • 單一大合約 VS 合約模塊化

  • Hub – Spoke模式

  • 使用mapping保存合約數據

  • 合約升級的主要模式

    Proxy

    數據合約 + 控制合約

Truffle

Truffle做爲開發智能合約的利器,不只僅提供了對於合約開發和測試的支持,它還能夠做爲合約遷移和部署的工具。這裏主要講講部署的經常使用套路。

通常的Truffle例子中大多隻是部署單個合約,但有時咱們須要部署多個合約,而且這些合約之間有前後依賴關係時,須要順序部署:

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

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

module.exports = function(deployer) {

    deployer.deploy(Storage)

        .then(() => Storage.deployed())

        // deployer.deploy(`ContractName`, [`constructor params`])

        .then(() => deployer.deploy(InfoManager, Storage.address));

}

假如要在部署以後當即執行合約代碼:

deployer.deploy(Storage)
   .then(() => Storage.deployed())
   .then((instance) => {
       instance.addData("Hello", "world") 
   })

若是要部署到不一樣的網絡環境,能夠採用以下命令:

truffle migrate --network network_id

此時須要在truffle.js中設置好合適的network_id,部署腳本以下:

module.exports = function(deployer, network) {

    if (network == "live") {

        // do one thing

    } else if (network == "development") {

        // do other thing

    }

}

若是要換帳戶部署,則:

module.exports = function(deployer, network, accounts) {

    var defaultAccount;

    if (network == "live") {

        defaultAccount = accounts[0]

    } else {

        defaultAccount = accounts[1]

    }

}

而且每每會跟HDWalletProvider結合使用。

同時把合約部署到Infura上也會用到它:

const HDWalletProvider = require("truffle-hdwallet-provider");

module.exports = {

  networks: {

    "ropsten-infura": {

      provider: () => new HDWalletProvider("<passphrase>", "https://ropsten.infura.io/<key>"),

      network_id: 3,

      gas: 4700000

    }

  }

};

若是合約用到了lib,則:

deployer.deploy(MyLibrary);

deployer.link(MyLibrary, MyContract);

deployer.deploy(MyContract);

WEB3J

對於Java和Android開發者,若是要開發以太坊應用,離不開web3j,它的大體使用流程以下:

image

但請注意

  • 合約的部署建議直接用Truffle完成,如前所述,Truffle不只僅只是開發,它提供了對於合約的一整套生命週期管理。

  • 生成錢包可選步驟,可使用外部現有錢包帳戶

  • 合約自己的測試建議用Truffle

  • 此處測試專一於應用自己邏輯和合約邏輯的集成測試

  • 測試建議用Ganache

對於新手,一個經常犯的錯誤就是選錯TransactionManager,它一旦選錯,將交易致使。假如你發起交易,而交易沒有發出去,同時報諸如:TransactionHashMissMatched,那麼十有八九就是這個問題。

TransactionManager有兩種:

  • ClientTransactionManager,適用於私鑰放在以太坊客戶端,由它來簽名併發送交易的場合。典型如:geth和ganache中帳戶。

  • RawTransactionManager,適用於由應用客戶端本身簽名併發送交易的場合。典型如:私鑰不在自有geth節點和使用第三方節點。

在使用RawTransactionManager時,須要注意設置好合適的chainid。

有時,交易發出以後,發現長時間處於Pending狀態,那麼請檢查(假如不是網絡擁堵的狀況):

  • 是否設置了合適的gasprice

  • 是否設置了合適的nonce,它有點相似數據庫中的sequence,一旦用過就不能再被使用。

同時,還須要留意有多少節點接受了交易所在區塊。接受的節點越多,交易越不可能被回滾。確認算法:當前區塊高度 - TX所處區塊高度 > 指定塊數,對於Ganache測試環境,這個值能夠是0。

假如你的交易比較重要,可能須要根據交易的重要程度,動態調整這個值。

最後,避免使用send方法,使用sendAsync,並結合CompletableFuture。

其餘工具

假如你的工具棧是javascript/typescript,那麼:

  • web3.js,基礎

  • truffle-contract,更好的合約抽象

  • ethers.js,更高抽象層次的以太坊交互接口

從某些方面來說,ethers.js與web3.js有重疊,但前者對錢包開發提供了更友好的接口。

假如前端頁面想將MetaMask直接集成進來,即遇到以太坊交互時直接激活MetaMask,那麼能夠用下面的代碼:

// Adapted from https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#partly_sunny-web3---ethereum-browser-environment-check

window.addEventListener('load', function() {

  // Checking if Web3 has been injected by the browser (Mist/MetaMask)

  if (typeof web3 !== 'undefined') {

    // Use Mist/MetaMask's provider

    window.web3 = new Web3(web3.currentProvider);

  } else {

    // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)

    window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

  }

  // Now you can start your app &amp; access web3 freely:  startApp()

})

合約部署

從大的方面講,合約部署有兩種選擇,但各自都有其優缺點:

私有節點

優勢:

  • 徹底掌控

  • 適用於應用不能隨意訪問第三方節點的場合

缺點:

  • 節點安全自我保證

  • 帳本同步問題,如斷電重啓;節點與外部斷開一段時間才發現,由此致使的分叉

  • 若私鑰寄存在節點,存在安全風險

第三方(如infura)

優勢:

  • 運維負擔甩給第三方

  • 不需分享私鑰

缺點:

  • 須要信任第三方節點,由於第三方有可能不把交易發出去,返回僞造信息。

  • 非完整API,如infura不支持filter

  • API調用頻率有限制

這裏沒有誰優誰劣,只能根據本身的需求權衡後選擇。

5

總結

總的來說:

  • 區塊鏈開發與傳統應用開發差別很大

  • 智能合約設計不等同於

    數據庫設計

    傳統OO設計

  • 與以太坊的交互不是簡單的請求調用

  • 實踐出真知

    多看、多聽、多交流

    選擇優秀類庫

    測試、測試、再測試

6

參考資料

參考連接:

內容來源:簡書

做者 | 胡鍵

本文源自我在10月27日HiBlock線下沙龍的分享,同時也能夠算是到目前爲止來自實際項目的一線總結,但願其中的內容可以幫助後來者少踩些坑,節約寶貴的時間。

image

相關文章
相關標籤/搜索