以太坊預言機合約:設置和方向

已發表: 2022-03-11

以太坊智能合約不僅僅是“新熱點”。 我相信它們(或相關的東西)準備好在即將到來的互聯網新時代改變人類相互做生意的方式。 時間會證明情況是否如此。

這是一篇關於使用 Solidity 開發以太坊智能合約的三部分文章中的第一篇,最具體地探討了使用所謂的“預言機”合約——這些合約基本上是將數據注入區塊鏈以供其他智能合約使用的合約。

  • 第 1 部分:介紹使用 Truffle 進行開發,以及進一步實驗的項目設置
  • 第 2 部分:深入研究代碼以進行更深入的檢查
  • 第 3 部分:關於智能合約預言機的概念性討論

本系列的第 1 部分的目標不是深入了解預言機合約的概念、它們背後的哲學,甚至不深入了解它們的本質; 我們以太坊預言機教程這一部分的目標很簡單:

  • 讓您開始使用 Truffle 構建智能合約。
  • 構建一個智能合約項目,它將在第 2 部分和第 3 部分中為我們服務。
  • 介紹一些與以太坊智能合約和智能合約編碼相關的概念。
  • 使用 Truffle 和智能合約介紹編譯/運行/調試週期。

定義:甲骨文。 智能合約從區塊鏈以外的世界訪問數據的一種手段。 一種智能合約本身,預言機從外界獲取數據並將其放入區塊鏈以供其他智能合約使用。

本文的第一部分將包括設置所有先決條件。 然後,我們將建立一個單一的以太坊合約並使用 Truffle 對其進行測試。 最後,我們將預言機與客戶端分開並共同測試它們。

軟件要求

  • 任何主要的操作系統都可以工作,儘管某些安裝和設置在不同的系統上當然會有所不同。 我已經在 Ubuntu Linux (16.04) 上完成了所有這些工作。 我在 Windows 上設置環境也沒有問題。 我沒有嘗試過 Mac,但我知道在 Mac 上這樣做也很常見。
  • 不需要運行完整的 eth 節點; 我們將使用帶有自己的測試網的 Truffle。 如果您對自己的工作有所了解,則可以使用您選擇的任何其他測試網; 就本教程而言,Truffle 的本地開發測試網是最簡單且最易於訪問的。

知識要求

  • 區塊鏈工作原理的基本知識
  • 了解什麼是基於區塊鏈的智能合約
  • 一些關於智能合約開發的基本世界經驗會有所幫助,但如果你聰明且雄心勃勃,則不是必需的。 (我知道你是!)

本系列文章可以作為對智能合約的第一次介紹,但它很快就會進入更高級的概念。 如果這是您的第一個 eth 智能合約教程,請準備好快速爬升。 如果您感到自信,那就太好了; 如果沒有,請隨意獲得一兩個更簡單的“hello world”類型的教程。 對於初學者,請查看一篇或以前的以太坊文章和 Cryptozombies。

警告:智能合約空間如此新,變化很快。 撰寫本文時新的 Solidity 語法功能在您閱讀本文時可能已被棄用或過時。 Geth 版本可能來來去去。 Solidity 總是在添加新的語言特性並棄用舊的特性。 許多新功能目前正在開發中。 因此,如有必要,請做好準備,使本文中的信息適應未來的新形勢; 如果您認真學習智能合約開發,那麼我對您有信心。

示例應用程序的描述

用例:用戶投注拳擊比賽。

  • 用戶可以提取可下注的拳擊比賽列表。
  • 用戶可以選擇一場比賽並對獲勝者下注。
  • 用戶可以下注高於指定最小值的任何金額。
  • 如果用戶的選擇輸了,則用戶輸掉全部賭注。
  • 如果用戶的選擇獲勝,則用戶根據他/她的賭注大小和對比賽失敗者的總賭注獲得一部分底池,之後房屋(合約所有者)將獲得一小部分獎金.

什麼是以太坊預言機?

智能合約仍然是一種新事物。 它們還沒有成為主流,它們如何工作的許多方面還沒有敲定和標準化。 我將簡要解釋一下“神諭”這一理念背後的推動力——請耐心等待; 我們將在後面的部分更深入地討論它。

設計區塊鏈合約不像編寫客戶端-服務器應用程序。 一個重要的區別是與合約交互的數據必須已經在區塊鏈上。 沒有呼出區塊鏈。 不僅語言不支持,區塊鏈範式也不支持。 合約可以以基於以太坊的貨幣形式進行投注,將其存儲在合約中,並在宣布比賽獲勝時根據公式將其釋放到正確的錢包地址。 但是合同怎麼知道贏家呢? 它不能查詢 REST API 或類似的東西。 它只能使用已經在區塊鏈中的數據! 智能合約的許多用例都遇到了類似的問題——它們受到嚴重限制,除非它們能夠與區塊鏈之外的世界進行交互。

如果合約只能與區塊鏈上的數據交互,一個明顯的解決方案是將必要的數據注入到區塊鏈中。 這就是神諭。 預言機是另一個合約,它將數據注入區塊鏈,允許其他合約使用它。 雖然這可能會引發有關信任和不信任的問題,但現在只需接受這就是預言機的含義。 在本系列的第 3 部分中,我們將討論這些細微差別。 在我們的示例用例中,預言機將是將數據注入區塊鏈的合約,涉及 (a) 哪些匹配可用,以及 (b) 誰贏得了這些匹配,一旦決定。

設置以太坊開發環境

對於基本設置,我們將安裝:

  • Geth(暫時可選)
  • 松露
  • Ganache CLI(可選)
  • 開發環境(可選)

本文沒有足夠的篇幅作為環境設置的完整指南,而只是一個粗略的指南。 不過沒關係,因為您的特定操作系統已經有很多更完整的設置指南,而互聯網並不需要新的。 因此,我將帶您快速了解路徑,並為您提供一些資源,以便根據需要獲取更多詳細信息。 準備好按照您的系統要求和 Google 的指示安裝要求和先決條件。

Oracle 合同流程說明

安裝 Geth(可選)

Geth安裝截圖 單擊以查看全尺寸圖像。

Geth 是 Go-ethereum,以太坊核心軟件; 雖然這個練習完全沒有必要,但任何想成為以太坊開發者的人都應該擁有它並熟悉它。 如果您要將智能合約部署到實時以太坊網絡,這將是必要的。

  • http://www.talkcrypto.org/blog/2018/01/23/what-is-geth/
  • https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Ubuntu
  • https://github.com/ethereum/go-ethereum/wiki/Installation-instructions-for-Windows

安裝松露

Truffle 安裝截圖 單擊以查看全尺寸圖像。

Truffle 是我們將用於開發的主要內容,並且絕對是本指南的要求。 查找並按照您的操作系統的特定說明安裝 Truffle。 以下是一些希望對您有所幫助的鏈接。

  • https://truffleframework.com/docs/truffle/getting-started/installation
  • https://github.com/trufflesuite/truffle
  • https://truffleframework.com/tutorials/how-to-install-truffle-and-testrpc-on-windows-for-blockchain-development

安裝 Ganache CLI(可選)

Ganache CLI 安裝截圖 單擊以查看全尺寸圖像。

我建議安裝 Ganache CLI 以用作另一個測試工具,儘管我們實際上不會在我們的教程中使用它。 這是可選的。

https://github.com/trufflesuite/ganache-cli

以太坊開發環境

使用任何簡單的文本編輯器(如 Notepad++、gedit、vi 或您選擇的任何文本編輯器或 IDE)來完成整個教程都是不可能的。 我個人正在使用具有以下擴展的 Visual Studio Code:

  • 堅固性
  • Solidity 擴展
  • 材質圖標主題

注意:擴展不是必需的——它們只是為了更好的編碼環境。

設置代碼

項目設置

Truffle 是一個非常方便的工具,用於編譯智能合約,將它們遷移到區塊鏈,它還提供開發和調試實用程序。 為了與 Truffle 集成,需要進行一些項目設置。 現在我們將在 Truffle 和目錄結構中為我們的項目設置 shell。 只需坐下來,暫時按照機器人的步驟進行操作,然後儘情享受吧。

創建一個目錄來存放所有代碼; 稱之為oracle-example

在根目錄中,創建兩個子目錄,因為最終項目將包含兩個子項目。 創建目錄:

  • /oracle 示例/客戶端
  • /oracle 示例/oracle

進入客戶端文件夾,因為這是我們要開發的第一個項目。 在/oracle-example/client文件夾中打開一個終端(命令行)窗口。

運行命令truffle init

請注意,創建的許多文件中有 truffle -config.jstruffle.js 。 我們兩個都不需要,所以刪除truffle-config.js (只是為了避免混亂和混亂)。

我們需要編輯truffle.js ,以便將 Truffle 指向正確的測試方向。 將truffle.js的內容替換為以下內容:

 module.exports = { networks: { development: { host: "localhost", port: 8545, network_id: "*" // Match any network id } } };

https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/truffle.js

請注意,Truffle init 創建了一個名為migrations ( oracle-example/client/migrations ) 的目錄。 該文件夾內應該是一個名為1_initial_migration.js的文件。

在遷移目錄中添加另一個文件並將其命名為2_deploy_contracts.js ,內容如下:

 var BoxingBets = artifacts.require("BoxingBets"); module.exports = function(deployer) { deployer.deploy(BoxingBets); };

https://github.com/jrkosinski/oracle-example/tree/part1-step1

添加代碼

現在簡單的設置已經完成,我們準備開始編碼。 請記住,本文的這一部分仍然是介紹和設置,因此我們將快速瀏覽代碼。 我們將在第 2 部分對代碼進行更深入的解釋,並在第 3 部分對架構和概念進行更深入的討論。也就是說,我們將快速觸及代碼中明顯的一些核心概念。 仔細跟隨以跟上。

GitHub 上提供了該過程中此步驟的完整代碼:https://github.com/jrkosinski/oracle-example/tree/part1-step1

Solidity 中的合約

Solidity 中的“契約”大致類似於其他面向對象語言中的類。 該語言本身已與 Golang 和 JavaScript 等進行了比較。 Solidity 中的其他一些語言結構——我們稍後會有例子——是修飾符、庫和接口。 合約支持繼承(包括多重繼承)。 Solidity 合約文件具有 .sol 擴展名。

甲骨文接口

將此文件添加到您的項目中: /oracle-example/client/contracts/OracleInterface.sol

https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/contracts/OracleInterface.sol

oracle界面截圖 單擊以查看全尺寸圖像。

通常,oracle 接口就是這樣——一個接口。 對於第一次迭代,它只是 Solidity 項目中包含的一個簡單類,現在只是作為佔位符。 在我們成功編譯並在 Truffle 上運行合約之後,我們將在下一步中將其移出。 稍後我們將其轉換為實際接口後,函數實現將為空。

客戶合同

將此文件添加到您的項目中: /oracle-example/client/contracts/BoxingBets.sol

https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/contracts/BoxingBets.sol

這是使用拳擊比賽數據的合約,允許用戶查詢可用的比賽並對其下注。 在以後的迭代中,它將計算並支付獎金。

編譯運行

現在是時候看看我們是否第一次做對了!

編譯和遷移合約

/oracle-example/client/文件夾中打開一個終端

使用以下命令編譯代碼:

 truffle compile 
編譯和遷移合約的截圖 單擊以查看全尺寸圖像。
編譯和遷移合約的第二個截圖 單擊以查看全尺寸圖像。

替代方案:使用我的 recompile.sh shell 腳本 (https://github.com/jrkosinski/oracle-example/tree/part1-step1/client/recompile.sh)。

請注意,您會看到很多警告,因為我們的代碼還不是最終形式!

打開 Truffle 開發控制台:

 truffle develop

現在,在 Truffle 開發者控制台中,遷移到測試網絡:

 truffle(develop)> migrate

運行合約

在開發控制台提示符下,輸入以下代碼行:

 truffle(develop)> BoxingBets.deployed().then(inst => { instance = inst })

現在,“instance”是引用 BoxingBets 合約的變量,可用於調用其公共方法。

使用以下命令對其進行測試:

 truffle(develop)> instance.test(3, 4)

請注意,我們在BoxingBets.sol中包含了一個公共“測試”函數。 它將您傳遞給它的任何兩個數字相加,只是為了證明合約正在執行代碼,並且我們可以從 Truffle 開發控制台調用它。 如果我們得到一個看起來很理智的回應(見下文),那麼我們的工作就完成了(至少現在是這樣)。

分離以太坊神諭

如果到目前為止一切都成功了,那麼我們就完成了。 接下來我們要做的是將預言機合約與 BoxingBets 合約分開。 在實際使用中,預言機的合約將與區塊鏈上的客戶端合約分開存在,因此我們需要能夠:

以太坊預言機合約流程示意圖

  • 通過區塊鏈地址實例化它。
  • 動態更改客戶端合約用來引用預言機的預言機地址。

所以簡而言之,我們現在要做的就是將預言機和客戶端分成兩個獨立的區塊鏈合約實體,並讓它們相互通信。 客戶端將通過地址實例化預言機並調用它。

客戶合同

首先,我們將更改客戶端合約(client),使其引用到 oracle 的動態接口,而不是具體的類。 然後我們將確保它從外部合約實例化預言機。

進入/oracle-example/client/contracts/OracleInterface.sol 。 正如我們之前提到的,這目前還不是一個界面,但我們即將把它變成一個界面。 將其中的內容替換為以下內容:

https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts/OracleInterface.sol

 pragma solidity ^0.4.17; contract OracleInterface { enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg cancelled) Decided //index of participant who is the winner } function getPendingMatches() public view returns (bytes32[]); function getAllMatches() public view returns (bytes32[]); function matchExists(bytes32 _matchId) public view returns (bool); function getMatch(bytes32 _matchId) public view returns ( bytes32 id, string name, string participants, uint8 participantCount, uint date, MatchOutcome outcome, int8 winner); function getMostRecentMatch(bool _pending) public view returns ( bytes32 id, string name, string participants, uint participantCount, uint date, MatchOutcome outcome, int8 winner); function testConnection() public pure returns (bool); function addTestData() public; }

BoxingBets.sol中,我們將替換這一行:

 OracleInterface internal boxingOracle = new OracleInterface();

用這兩行:

 address internal boxingOracleAddr = 0; OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);

現在我們想要的是一種動態設置預言機地址的方法,以及一個我們可以調用以找出當前預言機地址的函數。 將這兩個函數添加到BoxingBets.sol

 /// @notice sets the address of the boxing oracle contract to use /// @dev setting a wrong address may result in false return value, or error /// @param _oracleAddress the address of the boxing oracle /// @return true if connection to the new oracle address was successful function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) { boxingOracleAddr = _oracleAddress; boxingOracle = OracleInterface(boxingOracleAddr); return boxingOracle.testConnection(); } /// @notice gets the address of the boxing oracle being used /// @return the address of the currently set oracle function getOracleAddress() external view returns (address) { return boxingOracleAddr; }

最後,為了測試客戶端和預言機之間的連接,我們可以將 BoxingBets 中的測試函數替換為一個測試預言機連接的函數:

 /// @notice for testing; tests that the boxing oracle is callable /// @return true if connection successful function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }

可擁有

請注意, setOracleAddress的定義後面有一個onlyOwner修飾符。 這限制了該函數被合約所有者以外的任何人調用,即使該函數是公共的。 這不是語言功能。 這是由 Ownable 合約提供給我們的,它是從 OpenZeppelin 的通用 Solidity 合約庫中提取出來的。 我們將在第 2 部分中詳細介紹,但為了方便使用onlyOwner修飾符,我們需要進行一些更改:

Ownable.sol從 https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts/Ownable.sol 複製到/oracle-example/client/contracts/中。

BoxingBets.sol的頂部添加對它的引用,如下所示:

 import "./Ownable.sol";

(您可以在導入OracleInterface.sol的行下方添加它。)

修改 BoxingBets 的合約聲明,使其繼承自 Ownable,由此:

 contract BoxingBets {

對此:

 contract BoxingBets is Ownable {

我們應該都準備好了。 完整代碼在這裡,以防您迷路:https://github.com/jrkosinski/oracle-example/tree/part1-step2/client/contracts

甲骨文合約

設置

現在 BoxingBets 合約試圖通過地址引用一個完全獨立的合約(即預言機),我們的下一個工作是創建該預言機合約。 所以現在我們要創建一個包含預言機合約的完整獨立項目。 這與我們已經為客戶合同項目所做的設置基本相同; 即設置Truffle進行編譯和開發。

您應該已經有一個名為/oracle-example/oracle/的文件夾,我們在上一步中創建了該文件夾(如果沒有,請立即創建該空目錄)。 在該目錄中打開一個終端。

  • 運行命令truffle init
  • 刪除/oracle-example/oracle/truffle-config.js
  • 像這樣編輯/oracle-example/oracle/truffle.js
 module.exports = { networks: { development: { host: "localhost", port: 8545, network_id: "*" // Match any network id } } };

請參閱此處的示例:https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/truffle.js

/oracle-example/oracle/migrations/中,創建一個名為2_deploy_contracts.js的文件,其內容如下:

 var BoxingOracle = artifacts.require("BoxingOracle"); module.exports = function(deployer) { deployer.deploy(BoxingOracle); };

請參閱此處的示例:https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/migrations/2_deploy_contracts.js

甲骨文代碼

對於此步驟,只需將以下三個文件從 https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/contracts/ 複製到您的/oracle-example/oracle/contracts/文件夾中:

  • BoxingOracle.sol:主要的預言機合約。
  • Ownable.sol:對於僅所有者功能,正如我們已經在客戶端合同中使用的那樣。
  • DateLib.sol:一個日期庫。 我們將在本系列的第 2 部分中更深入地研究它。

測試 Oracle

現在,在項目的當前迭代中,我們確實需要徹底測試我們的智能合約預言機,因為這將是我們構建項目其餘部分的基礎。 所以,既然我們已經設置了 oracle 項目並複制了代碼,我們將希望:

  • 編譯預言機。
  • 確保預言機運行。
  • 在 Truffle 控制台中運行一些函數以確保預言機按預期工作。

編譯和遷移 Oracle

仍然在打開/oracle-example/oracle/的終端中,運行以下命令。 同樣,這些步驟與我們已經完成的編譯和遷移客戶端合約的步驟相同。

 truffle compile

替代方案:使用我的 recompile.sh shell 腳本 (https://github.com/jrkosinski/oracle-example/tree/part1-step2/oracle/recompile.sh)。

打開 Truffle 開發控制台:

 truffle develop

遷移到測試網絡:

 truffle(develop)> migrate

運行和測試 Oracle

仍然在 Truffle 開發控制台中,輸入以下內容以捕獲指向預言機合約的可用指針:

 truffle(develop)> BoxingOracle.deployed().then(inst => { instance = inst })

現在我們可以(並且應該)在我們的預言機合約上運行一套測試來測試它。 嘗試依次運行以下命令並檢查結果。

 truffle(develop)> instance.testConnection() ... truffle(develop)> instance.getAllMatches() ... truffle(develop)> instance.addTestData() ... truffle(develop)> instance.getAllMatches() ...

此時鼓勵您查看 oracle 代碼,查看可用的公共方法,閱讀代碼中的註釋,並提出一些您自己的測試來運行(並在控制台中運行它們,如如上所示)。

測試和調試

現在我們準備好進行最後的測試:測試客戶端合約是否可以調用已經在區塊鏈上的預言機合約,並拉入並使用其數據。 如果所有這些都有效,那麼我們就有了一個客戶端-預言機對,我們可以將其用於進一步的實驗。 我們運行端到端測試的步驟:

  • 編譯並運行預言機合約
  • 編譯並運行客戶端合約
  • 獲取預言機合約地址
  • 在客戶端合約中設置預言機地址
  • 將測試數據添加到預言機合約
  • 測試我們是否可以在客戶端合約中檢索該數據

打開兩個終端窗口:

  • /oracle-example/client/中的一個
  • 另一個在/oracle-example/oracle/

我建議您保持/oracle-example/client/一個在左側打開, /oracle-example/oracle/一個在右側打開,並密切關注以避免混淆。

編譯並運行 Oracle 合約

/oracle-example/oracle/終端中執行以下命令:

 bash recompile.sh truffle develop truffle(develop)> migrate truffle(develop)> BoxingOracle.deployed().then(inst => { instance = inst })

編譯並運行客戶端合約

/oracle-example/client/終端中執行以下命令:

 bash recompile.sh truffle develop truffle(develop)> migrate truffle(develop)> BoxingBets.deployed().then(inst => { instance = inst })

獲取 Oracle 合約的地址

/oracle-example/oracle/終端中對 Truffle 執行以下命令:

 truffle(develop)> instance.getAddress()

複製作為此調用輸出的地址; 並在下一步中使用它。

在客戶端合約中設置 Oracle 地址

/oracle-example/client/終端中執行以下命令來松露:

 truffle(develop)> instance.setOracleAddress('<insert address here, single quotes included>')

並測試它:

 truffle(develop)> instance.testOracleConnection()

如果輸出為true ,那麼我們就可以開始了。

測試我們是否可以在客戶端合同中檢索該數據

/oracle-example/client/終端中執行以下命令來松露:

 truffle(develop)> instance.getBettableMatches()

它應該返回一個空數組,因為尚未將測試數據添加到 oracle 端。

執行以下命令在/oracle-example/oracle/終端中添加測試數據:

 truffle(develop)> instance.addTestData()

/oracle-example/client/終端中執行以下命令 truffle,看看我們是否可以從客戶端獲取新添加的測試數據:

 truffle(develop)> instance.getBettableMatches()

現在,如果您從getBettableMatches()返回的數組中獲取單個地址,並將它們插入getMatch()

此時鼓勵您查看客戶端代碼,查看可用的公共方法,閱讀代碼中的註釋,並提出一些您自己的測試來運行(並在控制台中運行它們,如更多)。

第一部分的結論

我們從這個練習中得到的結果是有限的,但我們的目標也是有限的,以保持現實的步伐。 我們的客戶還沒有能力下注、處理資金、分配獎金等。我們所擁有的——除了獲得的知識和經驗——是:

  • 一個功能強大的智能合約預言機
  • 可以連接到預言機並與之交互的客戶端
  • 進一步發展和學習的框架

這對於一篇短文來說還不錯。

本系列的第二部分中,我們將更深入地研究代碼並查看智能合約開發獨有的一些功能以及 Solidity 特有的一些語言功能。 許多在這部分中被忽略的東西將在下一節中解釋。

本系列的第三部分中,我們將討論一下智能合約的理念和設計,特別是與它們與預言機的使用有關。

進一步的可選步驟

單人實驗是一種很好的學習方式。 如果您正在考慮如何擴展本教程以獲得更多知識,這裡有一些簡單的建議(第 2 部分和第 3 部分將不涉及以下內容):

  • 將合約部署到 Ganache(以前的 testrpc)並運行相同的測試來驗證功能。
  • 將合約部署到 ropsten 或 rinkeby 測試網並運行相同的測試來驗證功能。
  • 為 oracle 或客戶端(或兩者)構建 web3js 前端。

祝你好運,如有任何問題,請隨時與我聯繫。 我不能保證一定會迅速回复,但我會盡力而為。