Chainlink是一個去中心化的預言機網絡,它可以讓區塊鍊中的智能合約安全地通路外部世界的資料。在這個教程中,我們将探索chainlink預言機網絡的搭建,并學習如何使用預置或自定義的擴充卡實作智能合約與外部世界資料的橋接。
以太坊教程連結: Dapp入門 | 電商Dapp實戰 ERC721實戰 Php對接 Java對接 Python對接 C#對接 Dart對接
智能合約被鎖定在區塊鍊裡,與外部世界隔離開來。然而在許多應用中,智能合約的 運作需要依賴于外部真實世界的資訊。
以Ocean協定為例:隻有當提供的資料被證明是可以使用時,資料提供商才可以得到代币獎勵。是以一個可以橋接區塊鍊和現實世界的語言機(Oracle)網絡就非常必要了。
Chainlink是一個去中心化的oracle網絡,它可以讓區塊鍊中的智能合約安全地通路外部世界的資料:

在這個教程中,我們将探索chainlink網絡的搭建以及其擴充卡的使用方法,我們在Kovan測試鍊搭建了一個用于概念驗證的示範環境,所有的代碼可以從
這裡下載下傳。我們使用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
值得指出的是,每個來自請求器合約的請求都必須包含一個任務ID,用來唯一的辨別一個特定的工作流水線。Chainlink節點依賴于任務ID來識别與資料源互動所需的擴充卡以及處理資料所需的工作流。
2、使用Chainlink内置的擴充卡
在這一部分,我們使用Chainlinkg預置的擴充卡來展示如何內建Chainlink并向其送出請求。
2.1 安裝Chainlink包
在項目根目錄,執行如下指令安裝chainlink包:
$ npm install github:smartcontractkit/chainlink --save
另外,Chainlink官方最近增加了一個新的NPM包用于Chainlink合約,可以如下指令安裝:
$ npm install chainlink.js — save
2.2 在Kovan測試鍊部署請求器合約
要通路Chainlink的預言機合約,需要構造一個用于發送Link代币并送出請求的請求器合約。
我們建立了一個請求器合約示例,可以在這裡下載下傳。
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);
}
請求器合約中的關鍵函數是
createRequest
函數,它建立請求并設定必要的參數:
- Job Id:特定作業流水線的唯一辨別符。可以在 檢視内置擴充卡的完整清單:
- URL:可以傳回JSON資料的web api的通路端結點
- path:JSON資料字段選擇路徑,用來聲明使用資料中的哪一部分
- times:資料倍乘系數。該操作将浮點數轉換為整數,因為solidity智能合約僅接受整數
2.4 在Kovan測試鍊部署請求器合約
執行如下指令在以太坊Kovan測試鍊部署請求器合約:
$ 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
2.6 向請求器合約存入LINK代币
Chainlink官方提供了一些代币faucet。在Kovan測試鍊上可以通路
擷取一些測試用的LINK代币。
隻需要輸入合約位址或錢包位址,Chainlink的faucet就會轉100個LINK代币進去:
2.7 從合約請求資料
我們建立了一個JavaScript腳本來與請求器合約互動,以便建立并送出請求給Chainlink網絡。可以在這裡下載下傳JavaScript腳本。
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)
...
});
});
});
上面的代碼中,關鍵參數已經加粗顯式。任務ID“2c6578f488c843588954be403aba2deb”辨別了用于從URL提取JSON資料、拷貝指定字段值并轉換化為SOlidity支援的uint256類型的Chainlink擴充卡。
例如,傳回的JSON格式資料看起來像這樣:
{USD":142.33,"EUR":126.69,"JPY":15765.39}
path
參數設定為
USD
表示該字段的值需要提供給請求器合約。
我們可以運作該腳本像Chainlinkg網絡送出請求并從指定的URL提取資料。Chainlinkg節點大概需要2秒鐘來執行該請求,其中包含區塊确認的時間。
3、使用自定義的Chainlink擴充卡
前面的部分看起來幹淨簡潔。但是,Chainlink内置的擴充卡很有限,不能滿足各種區塊鍊應用的要求。是以,需要為不同的應用場景建立定制的擴充卡。
在這一部分,我們學習如何開發自己的擴充卡,并學習如何将其嵌入Chainlink體系中。可以在
找到一些外部擴充卡的參考實作,或者檢視這裡的
指南。
下圖展示了包含外部擴充卡的Chainlink網絡架構:
區塊鍊開發者需要完成以下工作:
- 将預言機合約部署到區塊鍊網絡
- 開發定制擴充卡并部署到AWS lambda或GCP functions,提供用于互動的URL端結點
- 運作一個新的CHainlink節點并在該節點的配置中注冊定制的擴充卡的URL端結點
- 在Chainlink節點中為該任務建立一個任務描述,以便其監聽預言機合約并觸發正确 的工作流水線
- 在鍊上預言機合約中注冊新的CHainlink節點
- 建立一個新的請求器合約來送出請求
下面我們逐漸來實作。
3.1 在Kovan測試鍊部署預言機合約
在我們的概念驗證系統中,需要一個預言機合約與Chainlinkg節點互動。為此,我們在Kovan測試鍊上部署這個合約:
https://github.com/oceanprotocol/Nautilus/blob/master/4-chainlink3_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
3.2 建立一個新的外部擴充卡
在這個概念驗證系統中,我們使用一個由OracleFinder開發的外部擴充卡CryptoCompareExternalAdapter。對應更一般性的應用,Thomas Hodges建立了一個用NodeJS開發的外部擴充卡模闆:
https://github.com/thodges-gh/CL-EA-NodeJS-Template$ git clone https://github.com/OracleFinder/CryptoCompareExternalAdapter.git
$ cd CryptoCompareExternalAdapter/
$ npm install
$ zip -r chainlink-cloud-adapter.zip .
壓縮檔案chainlink-cloud-adapter.zip建立後就可以部署了。作為示例,我們将這個外部擴充卡部署到Google Cloud Functions。在登入之後,參考下圖建立一個新的函數并上傳chainlink-cloud-adapter.zip:
為這個外部擴充卡生成的URL通路端結點需要提供給chainlink節點:
https://us-central1-macro-mercury-234919.cloudfunctions.net/coinmarketcap-adapter
現在使用Google Cloud Functions的控制台,我們可以測試擴充卡以確定它可以正常運作:
現在,外部擴充卡已經在Google Cloud平台運作起來,它等待執行來自Chainlink節點的請求。
3.3 在新的chainlink節點中注冊擴充卡url
我們需要運作一個新的chainlink節點,以便可以通路外部擴充卡,步驟如下:
- 安裝Parity并接入Kovan網絡:
$ 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/
- 建立Chainlink節點的管理賬号
$ 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")
- 建立Chainlink節點的配置資訊
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
- 運作chainlink節點
$ docker run --link eth -p 6689:6689 -v ~/.chainlink-kovan:/chainlink -it --env-file=.env smartcontract/chainlink n
通路
https://localhost:6689打開Chainlink節點的GUI配置界面,使用前面建立的管理賬号登入,儀表盤看起來像這樣:
- 在chainlink節點中注冊外部擴充卡
在Bridges頁籤,我們需要建立一個新的橋接器并填寫橋接url:
結果看起來是這樣:
3.4 為外部擴充卡建立任務描述
在chainlink節點上,很重要的一個步驟是建立一個新的任務描述,參考
有了任務描述,Chainlink節點可以監聽來自預言機合約的事件消息并觸發在任務描述中定義的流水線。我們的任務描述看起來是這樣:
{
"initiators": [
{
"type": "RunLog",
"params": { "address": "0x698efb00f79e858724633e297d5188705512e506" }
}
],
"tasks": [
{
"type": "coinmarketcap",
"confirmations": 0,
"params": {}
},
{
"type": "Copy",
"params": {}
},
{
"type": "Multiply",
"params": { "times": 100 }
},
{ "type": "EthUint256" },
{ "type": "EthTx" }
]
}
initiators
用來設定觸發chainlink節點的合約位址,
tasks
定義了該任務的作業流水線。
3.5 在預言機合約中注冊Chainlink節點
新的Chainlink節點必須要在之前部署的預言機合約中注冊,這樣它才能接受請求并執行任務。
可以在Chainlink節點的配置頁面找到新的chainlink節點的賬戶位址:
我們使用一個JavaScript檔案來注冊該Chainlink節點:
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)
});
});
});
使用truffle運作上述腳本:
$ truffle test test/Oracle.Test.js --network kovan
Using network 'kovan' Contract: Oracle
should register chainlink node
Chainlink node's status is := true
狀态為
true
表示chainlink節點已經注冊成功,并被授予執行任務的權限。
3.6 建立請求器合約以送出請求
為了向外部擴充卡送出請求,我們建立一個請求器合約contracts/requestGCP.sol來測試整個工作流。
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);
}
同樣部署到Kovan測試鍊:
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
利用faucet充值一些link代币:
https://kovan.chain.link/現在,我們可以請求外部擴充卡來通路鍊下資料。可以使用如下腳本:
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")
}
});
});
});
用truffle運作該腳本:
$ truffle test test/requestGCP.Test.js --network kovan
運作了大約10秒鐘,外部擴充卡完成該任務:
可以在chainlink節點的交易曆史中找到該交易:
也可以在Google cloud functions的儀表盤中找到該交易:
4、結語
Chainlink是一個重要的橋接區塊鍊與現實世界的去中心化預言機網絡。許多區塊鍊應用可能都需要通過chainlink網絡來通路現實世界中的資料流。
原文連結:
Chainlink去中心化預言機網絡搭建與定制教程 — 彙智網