1. 程式人生 > >分享實錄|以太坊開發需知

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

image

1

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

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

  • 自建節點,還是信任第三方節點?

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

由於加入了新的設計單元:智慧合約,我們將面對

  • 設計的複雜度

    合約的升級問題:因為智慧合約一旦釋出就無法更改,萬一需要更新合約錯誤或規則,怎麼辦?

    合約的組織問題。

  • 與一般程式碼不同,合約的好壞直接與金錢掛鉤

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

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

並且,以太坊本身的限制同樣也會影響到整個應用系統的設計和選型:

  • 交易確認需要時間: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