Chainlink去中心化預言機橋接區塊鏈與現實世界


Chainlink去中心化預言機橋接區塊鏈與現實世界


本文由深入淺出區塊鏈技術提供

Chainlink 是一個去中心化的預言機網絡,它可以讓區塊鏈中的智能合約安全地 訪問外部世界的數據。在這個文章中,我們將探索 chainlink 預言機網絡的搭建,並學習如何使用預置或自定義的適配器實現智能合約與外部世界數據的橋接。

智能合約被鎖定在區塊鏈裡,與外部世界隔離開來。然而在許多應用中,智能合約的運行需要依賴於外部真實世界的信息。

以 Ocean 協議為例:只有當提供的數據被證明是可以使用時,數據提供商才可以得到代幣獎勵。因此一個可以橋接區塊鏈和現實世界的預言機(Oracle)網絡 就非常必要了。

Chainlink 是一個去中心化的 Oracle 網絡,它可以讓區塊鏈中的智能合約安全地訪問外部世界的數據:

Chainlink去中心化預言機橋接區塊鏈與現實世界

在這個教程中,我們將探索 chainlink 網絡的搭建以及其適配器的使用方法,我們 在 Kovan 測試鏈搭建了一個用於概念驗證的演示環境,所有的代碼可以從 這裡[1]下載。我們使用 truffle v5.0.3 和 Node.js v8.11.1。

1、Chainlink 架構概述

Chainlink 網絡的主要組成部分如下:

• Chainlink 預言機合約:預言機智能合約部署在區塊鏈網絡上,它接收來自合約的 Link 代幣支付並向 Chainlink 節點分發事件

• Chainlink 節點:Chainlink 節點是運行在區塊鏈和外部數據源之間的鏈下中間件, 它提供真實世界的數據,執行來自請求器合約的請求

• Chainlink 適配器:適配器是應用相關的軟件,它負責與數據源交互並執行特定的任務。chainlink 適配器可以部署在 serverless 平臺,例如 amazon lambda 或 Google cloud functions

Chainlink去中心化預言機橋接區塊鏈與現實世界

值得指出的是,每個來自請求器合約的請求都必須包含一個任務 ID,用來唯一的標識 一個特定的工作流水線。Chainlink 節點依賴於任務 ID 來識別與數據源交互所需的適配器 以及處理數據所需的工作流。

2、使用 Chainlink 內置的適配器

在這一部分,我們使用 Chainlinkg 預置的適配器來展示如何集成 Chainlink 並向其 提交請求。

2.1 安裝 Chainlink 包

在項目根目錄,執行如下命令安裝 chainlink 包:

<code>$ npm install github:smartcontractkit/chainlink --save/<code>

另外,Chainlink 官方最近增加了一個新的 NPM 包用於 Chainlink 合約,可以如下 命令安裝:

<code>$ npm install chainlink.js — save/<code>

2.2 在 Kovan 測試鏈部署請求器合約

要訪問 Chainlink 的預言機合約,需要構造一個用於發送 Link 代幣並提交請求的 請求器合約。

我們創建了一個請求器合約示例,可以在這裡下載。

<code>
constructor() public {
// Set the address for the LINK token in Kovan network.
setLinkToken(0xa36085F69e2889c224210F603D836748e7dC0088);
// Set the address of the Oracle contract in Kovan network.
setOracle(0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e);
}...

/*
* Create a request and send it to default Oracle contract
*/

function createRequest(
bytes32 _jobId,
string _url,
string _path,
int256 _times
)
public
onlyOwner
returns (bytes32 requestId)
{
// create request instance
Chainlink.Request memory req = newRequest(_jobId, this, this.fulfill.selector);
// fill in the pass-in parameters

req.add("url", _url);
req.add("path", _path);
req.addInt("times", _times);
// send request & payment to Chainlink oracle
requestId = chainlinkRequestTo(getOracle(), req, ORACLE_PAYMENT);
// emit event message
emit requestCreated(msg.sender, _jobId, requestId);
}/<code>

請求器合約中的關鍵函數是 createRequest 函數,它創建請求並設置必要的參數:

• Job Id:特定作業流水線的唯一標識符。可以在這裡查看內置適配器的完整清單:https://docs.chain.link/docs/addresses-and-job-specs

• URL:可以返回 JSON 數據的 Web API 的訪問端結點

• path:JSON 數據字段選擇路徑,用來聲明使用數據中的哪一部分

• times:數據倍乘係數。該操作將浮點數轉換為整數,因為 solidity 智能合約僅接受整數

2.3 在 Kovan 測試鏈部署請求器合約

執行如下命令在以太坊 Kovan 測試鏈部署請求器合約:

<code>$ truffle migrate --network kovan
...
Deploying 'OceanRequester'
--------------------------
> transaction hash: 0x6e228163e73828c58c8287fec72c551289516a1d8e9300aab5dcc99d848f6146
> Blocks: 0 Seconds: 16
> contract address: 0x04E4b02EA2662F5BF0189912e6092d317d6388F3
> account: 0x0E364EB0Ad6EB5a4fC30FC3D2C2aE8EBe75F245c
> balance: 2.703082875853937168
> gas used: 1439461
> gas price: 10 gwei
> value sent: 0 ETH
> total cost: 0.01439461 ETH
> Saving artifacts

-------------------------------------

> Total cost: 0.01439461 ETH/<code>

2.4 向請求器合約存入 LINK 代幣

Chainlink 官方提供了一些代幣 faucet。在 Kovan 測試鏈上可以訪問 https://kovan.chain.link/ 獲取一些測試用的 LINK 代幣。

只需要輸入合約地址或錢包地址,Chainlink 的 faucet 就會轉 100 個 LINK 代幣進去:

Chainlink去中心化預言機橋接區塊鏈與現實世界

2.5 從合約請求數據

我們創建了一個 JavaScript 腳本來與請求器合約交互,以便創建並提交請求給 Chainlink 網絡。可以在這裡下載 JavaScript 腳本。

<code>contract("OceanRequester", (accounts) => {
const LinkToken = artifacts.require("LinkToken.sol");
const OceanRequester = artifacts.require("OceanRequester.sol");
const jobId = web3.utils.toHex("2c6578f488c843588954be403aba2deb");
const url = "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD,EUR,JPY";
const path = "USD";
const times = 100;
let link, ocean;

beforeEach(async () => {

link = await LinkToken.at("0xa36085F69e2889c224210F603D836748e7dC0088");
ocean = await OceanRequester.at("0x04E4b02EA2662F5BF0189912e6092d317d6388F3");

});


describe("query the initial token balance", () => {
it("create a request and send to Chainlink", async () => {
let tx = await ocean.createRequest(jobId, url, path, times);
request = h.decodeRunRequest(tx.receipt.rawLogs[3]);
...
data = await ocean.getRequestResult(request.id)
console.log("Request is fulfilled. data := " + data)
...
});
});
});/<code>

上面的代碼中,關鍵參數已經加粗顯式。任務 ID“2c6578f488c843588954be403aba2deb” 標識了用於從 URL 提取 JSON 數據、拷貝指定字段值並轉換化為 SOlidity 支持的 uint256 類型的 Chainlink 適配器。

例如,返回的 JSON 格式數據看起來像這樣:

<code>{USD":142.33,"EUR":126.69,"JPY":15765.39}/<code>

path 參數設置為 USD 表示該字段的值需要提供給請求器合約。

我們可以運行該腳本像 Chainlinkg 網絡提交請求並從指定的 URL 提取數據。Chainlinkg 節點大概需要 2 秒鐘來執行該請求,其中包含區塊確認的時間。

Chainlink去中心化預言機橋接區塊鏈與現實世界

3、使用自定義的 Chainlink 適配器

前面的部分看起來乾淨簡潔。但是,Chainlink 內置的適配器很有限,不能 滿足各種區塊鏈應用的要求。因此,需要為不同的應用場景創建定製的適配器。

在這一部分,我們學習如何開發自己的適配器,並學習如何將其嵌入 Chainlink 體系中。可以在這裡[2]找到一些外部適配器 的參考實現,或者查看這裡的指南[3]。

下圖展示了包含外部適配器的 Chainlink 網絡架構:

Chainlink去中心化預言機橋接區塊鏈與現實世界

區塊鏈開發者需要完成以下工作:

• 將預言機合約部署到區塊鏈網絡

• 開發定製適配器並部署到 AWS lambda 或 GCP functions,提供用於交互的 URL 端結點

• 運行一個新的 CHainlink 節點並在該節點的配置中註冊定製的適配器的 URL 端結點

• 在 Chainlink 節點中為該任務創建一個任務描述,以便其監聽預言機合約並觸發正確 的工作流水線

• 在鏈上預言機合約中註冊新的 CHainlink 節點

• 創建一個新的請求器合約來提交請求

下面我們逐步來實現。

3.1 在 Kovan 測試鏈部署預言機合約

在我們的概念驗證系統中,需要一個預言機合約與 Chainlinkg 節點交互。為此,我們 在 Kovan 測試鏈上部署這個合約:https://github.com/oceanprotocol/Nautilus/blob/master/4-chainlink。

<code>3_oracle_migration.js
=====================
Deploying 'Oracle'
------------------
> transaction hash: 0xd281b18c4be0be9b2bdbfed4bae090aab5c86027564f048785b1f971cf0b6f2c
> Blocks: 0 Seconds: 8
> contract address: 0x698EFB00F79E858724633e297d5188705512e506
> account: 0x0E364EB0Ad6EB5a4fC30FC3D2C2aE8EBe75F245c
> balance: 2.262907885853937168
> gas used: 1311430
> gas price: 10 gwei
> value sent: 0 ETH
> total cost: 0.0131143 ETH
> Saving artifacts

-------------------------------------
> Total cost: 0.0131143 ETH/<code>

3.2 創建一個新的外部適配器

在這個概念驗證系統中,我們使用一個由 OracleFinder 開發的外部適配器 CryptoCompareExternalAdapter。對應更一般性的應用,Thomas Hodges 創建了一個用 NodeJS 開發的外部適配器模板:

https://github.com/thodges-gh/CL-EA-NodeJS-Template

<code>$ git clone https://github.com/OracleFinder/CryptoCompareExternalAdapter.git
$ cd CryptoCompareExternalAdapter/
$ npm install
$ zip -r chainlink-cloud-adapter.zip ./<code>

壓縮文件 chainlink-cloud-adapter.zip 創建後就可以部署了。作為示例,我們將 這個外部適配器部署到 Google Cloud Functions。在登錄之後,參考下圖創建一個 新的函數並上傳 chainlink-cloud-adapter.zip:

Chainlink去中心化預言機橋接區塊鏈與現實世界

為這個外部適配器生成的 URL 訪問端結點需要提供給 chainlink 節點:

<code>https://us-central1-macro-mercury-234919.cloudfunctions.net/coinmarketcap-adapter/<code>

現在使用 Google Cloud Functions 的控制檯,我們可以測試適配器以確保它可以 正常運行:

Chainlink去中心化預言機橋接區塊鏈與現實世界

現在,外部適配器已經在 Google Cloud 平臺運行起來,它等待執行來自 Chainlink 節點的請求。

3.3 在新的 chainlink 節點中註冊適配器 url

我們需要運行一個新的 chainlink 節點,以便可以訪問外部適配器,步驟如下:

1.安裝 Parity 並接入 Kovan 網絡:

<code>
$ docker pull parity/parity:stable
$ mkdir ~/.parity-kovan
$ docker run -h eth --name eth -p 8546:8546 \\
-v ~/.parity-kovan:/home/parity/.local/share/io.parity.ethereum/ \\
-it parity/parity:stable --chain=kovan \\
--ws-interface=all --ws-origins="all" --light \\
--base-path /home/parity/.local/share/io.parity.ethereum//<code>

1.創建 Chainlink 節點的管理賬號

<code>
$ docker pull smartcontract/chainlink:latest
$ mkdir -p ~/.chainlink-kovan/tls
$ openssl req -x509 -out ~/.chainlink-kovan/tls/server.crt -keyout ~/.chainlink-kovan/tls/server.key \\
-newkey rsa:2048 -nodes -sha256 \\
-subj '/CN=localhost' -extensions EXT -config printf "[dn]\\nCN=localhost\\n[req]\\ndistinguished_name = dn\\n[EXT]\\nsubjectAltName=DNS:localhost\\nkeyUsage=digitalSignature\\nextendedKeyUsage=serverAuth")
/<code>

1.創建 Chainlink 節點的配置信息

<code>
echo "ROOT=/chainlink
LOG_LEVEL=debug
ETH_URL=ws://eth:8546
ETH_CHAIN_ID=42
MIN_OUTGOING_CONFIRMATIONS=2
MIN_INCOMING_CONFIRMATIONS=0
LINK_CONTRACT_ADDRESS=0xa36085F69e2889c224210F603D836748e7dC0088
TLS_CERT_PATH=/chainlink/tls/server.crt
TLS_KEY_PATH=/chainlink/tls/server.key
ALLOW_ORIGINS=*" > .env/<code>

1.運行 chainlink 節點

<code>$ docker run --link eth -p 6689:6689 -v ~/.chainlink-kovan:/chainlink -it --env-file=.env smartcontract/chainlink n/<code> 

訪問 https://localhost:6689 打開 Chainlink 節點的 GUI 配置界面,使用 前面創建的管理賬號登入,儀表盤看起來像這樣:

Chainlink去中心化預言機橋接區塊鏈與現實世界

1.在 chainlink 節點中註冊外部適配器

在 Bridges 選項卡,我們需要創建一個新的橋接器並填寫橋接 url:

Chainlink去中心化預言機橋接區塊鏈與現實世界

結果看起來是這樣:

Chainlink去中心化預言機橋接區塊鏈與現實世界

3.4 為外部適配器創建任務描述

在 chainlink 節點上,很重要的一個步驟是創建一個新的任務描述,參考:https://docs.chain.link/docs/job-specifications

有了任務描述,Chainlink 節點可以監聽來自預言機合約的事件消息並觸發 在任務描述中定義的流水線。我們的任務描述:

<code>
{
"initiators": [
{
"type": "RunLog",
"params": { "address": "0x698efb00f79e858724633e297d5188705512e506" }
}
],

"tasks": [
{
"type": "coinmarketcap",
"confirmations": 0,
"params": {}
},
{
"type": "Copy",
"params": {}
},
{
"type": "Multiply",
"params": { "times": 100 }
},
{ "type": "EthUint256" },
{ "type": "EthTx" }
]

}/<code>

initiators 用來設置觸發 chainlink 節點的合約地址, tasks 定義了該任務的 作業流水線。

3.5 在預言機合約中註冊 Chainlink 節點

新的 Chainlink 節點必須要在之前部署的預言機合約中註冊,這樣它才能接受 請求並執行任務。

可以在 Chainlink 節點的配置頁面找到新的 chainlink 節點的賬戶地址:

Chainlink去中心化預言機橋接區塊鏈與現實世界

我們使用一個 JavaScript 文件來註冊該 Chainlink 節點:

<code>const Web3 = require('web3')
const web3 = new Web3(new Web3.providers.HttpProvider('https://kovan.infura.io/'))
const h = require("chainlink-test-helpers");
const scale = 1e18;

contract("Oracle", (accounts) => {

const Oracle = artifacts.require("Oracle.sol");
const chainlinkNode ='0x79B80f3b6B06FD5516146af22E10df26dfDc5455';
let oracle;

beforeEach(async () => {
oracle = await Oracle.at("0x698EFB00F79E858724633e297d5188705512e506");
});

describe("should register chainlink node", () => {
it("register chainlink node", async () => {

await oracle.setFulfillmentPermission(chainlinkNode, true)
let status = await oracle.getAuthorizationStatus(chainlinkNode)
console.log("Chainlink node's status is := " + status)
});
});
});/<code>

使用 truffle 運行上述腳本:

<code>$ truffle test test/Oracle.Test.js --network kovan
Using network 'kovan' Contract: Oracle
should register chainlink node
Chainlink node's status is := true/<code>

狀態為 true 表示 chainlink 節點已經註冊成功,並被授予執行任務的權限。

3.6 創建請求器合約以提交請求

為了向外部適配器提交請求,我們創建一個請求器合約

<code>
function createRequest(
bytes32 _jobId,
string _coin,
string _market
)

public
onlyOwner
returns (bytes32 requestId)

{

// create request instance
Chainlink.Request memory req = newRequest(_jobId, this, this.fulfill.selector);
// fill in the pass-in parameters
req.add("endpoint", "price");
req.add("fsym", _coin);
req.add("tsyms", _market);
req.add("copyPath", _market);
// send request & payment to Chainlink oracle (Requester Contract sends the payment)
requestId = chainlinkRequestTo(getOracle(), req, ORACLE_PAYMENT);
// emit event message

emit requestCreated(msg.sender, _jobId, requestId);
}
/<code>

同樣部署到 Kovan 測試鏈:

<code>
4_requestGCP_migration.js

=========================
Replacing 'requestGCP'

----------------------

> transaction hash: 0x978974b43d843606c42ce15c87fcc560a5c625497bf074f5ec0f337347438fdf
> Blocks: 0 Seconds: 16
> contract address: 0x6f73E784253aD72F0BA4164101860992dFC17Fe1
> account: 0x0E364EB0Ad6EB5a4fC30FC3D2C2aE8EBe75F245c
> balance: 2.248942845853937168
> gas used: 1396504
> gas price: 10 gwei
> value sent: 0 ETH
> total cost: 0.01396504 ETH
> Saving artifacts
------------------------------------
> Total cost: 0.01396504 ETH
/<code>

利用 faucet 充值一些 link 代幣:https://kovan.chain.link/

現在,我們可以請求外部適配器來訪問鏈下數據。可以使用如下腳本:

<code>
const Web3 = require('web3')

const web3 = new Web3(new Web3.providers.WebsocketProvider('ws://eth:8546'))

const h = require("chainlink-test-helpers");
const scale = 1e18;

contract("requestGCP", (accounts) => {
const LinkToken = artifacts.require("LinkToken.sol");
const RequestGCP = artifacts.require("requestGCP.sol");
const jobId = web3.utils.toHex("80c7e6908e714bf4a73170c287b9a18c");
const coin = "ETH"
const market = "USD";

const defaultAccount =0x0e364eb0ad6eb5a4fc30fc3d2c2ae8ebe75f245c;
let link, ocean;


beforeEach(async () => {
link = await LinkToken.at("0xa36085F69e2889c224210F603D836748e7dC0088");
ocean = await RequestGCP.at("0x6f73E784253aD72F0BA4164101860992dFC17Fe1");
});

describe("should request data and receive callback", () => {
let request; ...
it("create a request and send to Chainlink", async () => {
let tx = await ocean.createRequest(jobId, coin, market);
request = h.decodeRunRequest(tx.receipt.rawLogs[3]);
console.log("request has been sent. request id :=" + request.id)

let data = 0
let timer = 0
while(data == 0){
data = await ocean.getRequestResult(request.id)
if(data != 0) {
console.log("Request is fulfilled. data := " + data)
}
wait(1000)
timer = timer + 1
console.log("waiting for " + timer + " second")
}
});
});
});
/<code>

用 truffle 運行該腳本:

<code>truffle test test/requestGCP.Test.js --network kovan/<code>

運行了大約 10 秒鐘,外部適配器完成該任務:

Chainlink去中心化預言機橋接區塊鏈與現實世界

可以在 chainlink 節點的交易歷史中找到該交易:

Chainlink去中心化預言機橋接區塊鏈與現實世界

也可以在 Google cloud functions 的儀表盤中找到該交易:

Chainlink去中心化預言機橋接區塊鏈與現實世界

4、結語

Chainlink 是一個重要的橋接區塊鏈與現實世界的去中心化預言機網絡。許多 區塊鏈應用可能都需要通過 chainlink 網絡來訪問現實世界中的數據流。

References

[1] 這裡: https://github.com/oceanprotocol/Nautilus/tree/master/4-chainlink

[2] 這裡: https://chainlinkadapters.com/

[3] 指南: https://chainlinkadapters.com/guides


分享到:


相關文章: