什麽是去中心化的預言機項目Chainlink

區塊鏈的確定性的意思是,在任何節點上,只要連入到區塊鏈的分布式網絡中,它就可以同步所有的曆史區塊,回放出一套完全相同的賬本。換句話說:在沒有互聯網連接的情況下,給定完整的塊,節點必須能夠從頭開始重新創建區塊鏈的最終狀態。

Chainlink基本原理TxK顯卡之家

本文我們來從技術上簡述一下Chainlink的基本原理。如果用最短的一句話解釋什麽是Chainlink,可以說Chainlink一個去中心化的預言機項目,所以爲了理解Chainlink的工作原理,我們首先要明白什麽是預言機。TxK顯卡之家

預言機TxK顯卡之家

預言機的英文爲Oracle,和著名的數據庫服務提供商Oracle(甲骨文)重名,但是兩者除了名字相同以爲並沒有任何關系。Oracle這個單詞是什麽意思,下面是我在vocabulary.com上查到的Oracle的含義:TxK顯卡之家

Back in ancient times, an oracle was someone who offered advice or a prophecy thought to have come directly from a divine source. In modern usage, any good source of information can be called an oracle.TxK顯卡之家

中文的大概意思是:在古代,oracle是一個提出建議或預言的人,他的建議或預言被認爲是直接來自于神。在現代用法中,任何好的信息來源都可以稱爲oracle。TxK顯卡之家

這樣就不難理解了,Oracle傳達了萬能全知的神的旨意,而甲骨文最初就是用來占蔔吉凶時的記錄,也在當時也被認爲是神谕,傳達了神的意思。所以不管是“預言機”還是“甲骨文”都表達了“信息源”的意思。TxK顯卡之家

計算機領域內的預言機一詞,最早是圖靈提出的。圖靈在圖靈機(Turing Machine)的基礎上,加入了一個稱爲預言者(oracle)的黑盒,組成了預言機(Oracle Machine)。所謂預言者,是一個可以回答特定問題集合的實體。即它可以向圖靈機系統內部輸入信息,幫助圖靈機完成運算。以太坊的智能合約是“圖靈完備(Turing Complete)”的,某種意義上可以看做一個圖靈機,所以以太坊的設計者借鑒這個概念,把向“圖靈完備的智能合約”這個圖靈機輸入信息的也被稱爲預言機oracle。所以說“預言機”這個名字並不是區塊鏈技術領域內的獨創概念,它來源于非常早期的計算機抽象設計,在密碼學等領域內也都有類似的概念。TxK顯卡之家

而在區塊鏈領域,預言機被認爲是可以爲智能合約提供外部數據源的系統。從傳統技術架構方面來看,預言機是連接智能合約與區塊鏈外部世界的中間件(middleware),是區塊鏈重要的基礎設施,它的作用是爲區塊鏈上的智能合約(Smart Contract)提供數據信息的。TxK顯卡之家

正如以太坊的定義,區塊鏈是一個交易驅動的狀態機(a transaction-based state machine),它能做的事情非常簡單,就是通過向區塊鏈提交事務/交易(transaction),來將區塊鏈從一個狀態轉變成另一個狀態。爲了保持共識,EVM的執行過程必須完全確定,並且僅基于以太坊狀態和簽名交易的共享上下文。這産生了兩個特別重要的後果:一個是EVM和智能合約沒有內在的隨機性來源;另一個是外部數據只能作爲交易的數據載荷引入。用通俗的話講,區塊鏈沒有主動獲取數據的能力,它能用的只有區塊鏈自己本身的數據。數據的缺失導致智能合約的應用範圍非常少,目前大部分的應用都是圍繞著token來展開的。TxK顯卡之家

區塊鏈的確定性的意思是,在任何節點上,只要連入到區塊鏈的分布式網絡中,它就可以同步所有的曆史區塊,回放出一套完全相同的賬本。換句話說:在沒有互聯網連接的情況下,給定完整的塊,節點必須能夠從頭開始重新創建區塊鏈的最終狀態。如果賬本在形成過程中,依賴于某個外部的API調用結果,那在不同時間不同環境下回放的結果就會不一樣。這種情況是區塊鏈所不允許的,所以區塊鏈在設計之初就沒有網絡調用。TxK顯卡之家

那麽要實現向區塊鏈提供數據,應該怎麽做呢?區塊鏈能留下的只有賬本,而區塊鏈所能輸入的只有交易。我們就從這兩個方面入手。TxK顯卡之家

什麽是去中心化的預言機項目ChainlinkTxK顯卡之家

幾乎每一個合約系統,都會有事件記錄的功能,比如以太坊中的EventLog功能。TxK顯卡之家

下面我們通過一個例子,來介紹一下預言機的基本原理。我們在以太坊鏈上建立一個用戶合約,它需要獲取到某個城市的氣溫數據。當然,智能合約自己是無法獲取到這個發生于鏈下真實世界中的數據信息的,需要借助預言機來實現。智能合約將需要獲取天氣溫度的的城市寫入到EventLog中,鏈下我們會啓動一個進程,監聽並訂閱這個事件日志,獲取到智能合約的請求之後,將指定城市的溫度,通過提交transaction的方式,調用合約中的回填方法,提交到智能合約中。TxK顯卡之家

聲明:以下代碼僅供演示預言機原理,沒有做參數檢測和錯誤處理,請不要在生産環境中使用。TxK顯卡之家

消費者合約:TxK顯卡之家

contract WeatherOracle {TxK顯卡之家

// 用戶存儲預言機提交的天氣數值TxK顯卡之家

uint256 public temperature;TxK顯卡之家

// 定義事件TxK顯卡之家

event RequestTemperature (bytes city);TxK顯卡之家

// 發出獲取請求,即發出一個事件日志TxK顯卡之家

function requestTemperature (string memory _city) public {TxK顯卡之家

emit RequestTemperature(bytes(_city));TxK顯卡之家

}TxK顯卡之家

// 預言機回調方法,預言機獲取到數據後通過這個方法將數據提交到鏈上TxK顯卡之家

function updateWeather (uint256 _temperature) public {TxK顯卡之家

temperature = _temperature;TxK顯卡之家

}TxK顯卡之家

}TxK顯卡之家

上面的代碼非常簡單,定義了一個變量用來存儲結果,一個方法用于發出請求,一個方法用于接收結果。TxK顯卡之家

鏈下,我們啓動一個進程,以訂閱topic的方式獲取日志信息,之後通過構建一個transaction,提交一個結果到合約中。TxK顯卡之家

func SubscribeEventLog() {TxK顯卡之家

topic := crypto.Keccak256([]byte(“RequestTemperature(bytes)”))TxK顯卡之家

query := ethereum.FilterQuery{TxK顯卡之家

Topics: [][]common.Hash{TxK顯卡之家

{TxK顯卡之家

common.BytesToHash(topic),TxK顯卡之家

},TxK顯卡之家

},TxK顯卡之家

}TxK顯卡之家

// 訂閱相關主題的日志事件TxK顯卡之家

events := make(chan types.Log)TxK顯卡之家

sub, err := EthClient.SubscribeFilterLogs(ctx, query, events)TxK顯卡之家

// 加載合約的ABI文件TxK顯卡之家

ta, err := abi.JSON(strings.NewReader(AbiJsonStr))TxK顯卡之家

// 監聽事件訂閱TxK顯卡之家

for {TxK顯卡之家

select {TxK顯卡之家

case err := 《-sub.Err():TxK顯卡之家

log.Error(err)TxK顯卡之家

breakTxK顯卡之家

case ev := 《-events:TxK顯卡之家

// 獲取到訂閱的消息TxK顯卡之家

ej, _ := ev.MarshalJSON()TxK顯卡之家

log.Info(string(ej))TxK顯卡之家

// 解析數據TxK顯卡之家

var sampleEvent struct {TxK顯卡之家

City []byteTxK顯卡之家

}TxK顯卡之家

err = ta.Unpack(&sampleEvent, “RequestTemperature”, ev.Data)TxK顯卡之家

log.Info(string(sampleEvent.City))TxK顯卡之家

// 構建交易提交結果,需要提供私鑰用于簽署交易TxK顯卡之家

CallContract(“b7b502b.。.164b42c”)TxK顯卡之家

}TxK顯卡之家

}TxK顯卡之家

}TxK顯卡之家

func CallContract(keyStr string) {TxK顯卡之家

addr := PrivateKeyToAddress(keyStr)TxK顯卡之家

nonce, err := EthClient.PendingNonceAt(ctx, addr)TxK顯卡之家

gasPrice, err := EthClient.SuggestGasPrice(ctx)TxK顯卡之家

privateKey, err := crypto.HexToECDSA(keyStr)TxK顯卡之家

auth := bind.NewKeyedTransactor(privateKey)TxK顯卡之家

auth.Nonce = big.NewInt(int64(nonce))TxK顯卡之家

auth.Value = big.NewInt(0)TxK顯卡之家

auth.GasLimit = uint64(300000)TxK顯卡之家

auth.GasPrice = gasPriceTxK顯卡之家

instance, err := event.NewEvent(common.HexToAddress(“0x8A421906e9562AA1c71e5a32De1cf75161C5A463”), EthClient)TxK顯卡之家

// 調用合約中的updateWeather方法,回填數據“29”TxK顯卡之家

tx, err := instance.UpdateWeather(auth, big.NewInt(29))TxK顯卡之家

log.Info(tx.Hash().Hex())TxK顯卡之家

}TxK顯卡之家

用一個圖來展示這個過程:TxK顯卡之家

什麽是去中心化的預言機項目ChainlinkTxK顯卡之家

ChainlinkTxK顯卡之家

Chainlink是一個去中心化的預言機項目,它的作用就是以最安全的方式向區塊鏈提供現實世界中産生的數據。Chainlink在基本的預言機原理的實現方式之上,圍繞LINK token通過經濟激勵建立了一個良性循環的生態系統。Chainlink預言機需要通過LINK token的轉賬來實現觸發。TxK顯卡之家

LINK是以太坊網絡上的ERC677合約,關于各類ERC token的區別,請參考這篇文章。TxK顯卡之家

在《精通以太坊(Matering Ethereum)》一書中,提出了三種預言機的設計模式,分別是TxK顯卡之家

· 立即讀取(immediate-read)TxK顯卡之家

· 發布/訂閱(publish–subscribe)TxK顯卡之家

· 請求/響應(request–response)TxK顯卡之家

而基于LINK ERC677 token完成的預言機功能,就屬于其中的請求/響應模式。這是一種較爲複雜的模式,上圖中展示的是一個不含有聚合過程的簡單請求/相應流程。TxK顯卡之家

什麽是去中心化的預言機項目ChainlinkTxK顯卡之家

我們以Chainlink提供的TestnetConsumer合約中的一個requestEthereumPrice 方法爲例來簡單講一下請求響應的流程。這個函數定義如下:TxK顯卡之家

function requestEthereumPrice(address _oracle, string _jobId)TxK顯卡之家

publicTxK顯卡之家

onlyOwnerTxK顯卡之家

{TxK顯卡之家

Chainlink.Request memory req = buildChainlinkRequest(stringToBytes32(_jobId), this, this.fulfillEthereumPrice.selector);TxK顯卡之家

req.add(“get”, “http://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD”);TxK顯卡之家

req.add(“path”, “USD”);TxK顯卡之家

req.addInt(“times”, 100);TxK顯卡之家

sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT);TxK顯卡之家

}TxK顯卡之家

它所實現的功能就是從指定的API(cryptocompare)獲取ETH/USD的交易價格。函數傳入的參數是指定的oracle地址和jobId。TxK顯卡之家

將一些列的請求參數組好後,調用sendChainlinkRequestTo 方法將請求發出。sendChainlinkRequestTo是定義在Chainlink提供的庫中的一個接口方法,定義如下:TxK顯卡之家

/**TxK顯卡之家

* @notice 向指定的oracle地址創建一個請求TxK顯卡之家

* @dev 創建並存儲一個請求ID, 增加本地的nonce值, 並使用`transferAndCall` 方法發送LINK,TxK顯卡之家

* 創建到目標oracle合約地址的請求TxK顯卡之家

* 發出 ChainlinkRequested 事件。TxK顯卡之家

* @param _oracle 發送請求至的oracle地址TxK顯卡之家

* @param _req 完成初始化的Chainlink請求TxK顯卡之家

* @param _payment 請求發送的LINK數量TxK顯卡之家

* @return 請求 IDTxK顯卡之家

*/TxK顯卡之家

function sendChainlinkRequestTo(address _oracle, Chainlink.Request memory _req, uint256 _payment)TxK顯卡之家

internalTxK顯卡之家

returns (bytes32 requestId)TxK顯卡之家

{TxK顯卡之家

requestId = keccak256(abi.encodePacked(this, requests));TxK顯卡之家

_req.nonce = requests;TxK顯卡之家

pendingRequests[requestId] = _oracle;TxK顯卡之家

emit ChainlinkRequested(requestId);TxK顯卡之家

require(link.transferAndCall(_oracle, _payment, encodeRequest(_req)), “unable to transferAndCall to oracle”);TxK顯卡之家

requests += 1;TxK顯卡之家

return requestId;TxK顯卡之家

}TxK顯卡之家

其中link.transferAndCall方法即是ERC677定義的token轉賬方法,與ERC20的transfer方法相比,它多了一個data字段,可以在轉賬的同時攜帶數據。這裏就將之前打包好的請求數據放在了data字段,跟隨轉賬一起發送到了oracle合約。transferAndCall 方法定義如下:TxK顯卡之家

/**TxK顯卡之家

* @dev 將token和額外數據一起轉移給一個合約地址TxK顯卡之家

* @param _to 轉移到的目的地址TxK顯卡之家

* @param _value 轉移數量TxK顯卡之家

* @param _data 傳遞給接收合約的額外數據TxK顯卡之家

*/TxK顯卡之家

function transferAndCall(address _to, uint _value, bytes _data)TxK顯卡之家

publicTxK顯卡之家

returns (bool success)TxK顯卡之家

{TxK顯卡之家

super.transfer(_to, _value);TxK顯卡之家

Transfer(msg.sender, _to, _value, _data);TxK顯卡之家

if (isContract(_to)) {TxK顯卡之家

contractFallback(_to, _value, _data);TxK顯卡之家

}TxK顯卡之家

return true;TxK顯卡之家

}TxK顯卡之家

其中的Transfer(msg.sender, _to, _value, _data);是發出一個事件日志:TxK顯卡之家

event Transfer(address indexed from, address indexed to, uint value, bytes data);TxK顯卡之家

將這次轉賬的詳細信息(發送方、接收方、金額、數據)記錄到日志中。TxK顯卡之家

Oracle合約在收到轉賬之後,會觸發onTokenTransfer方法,該方法會檢查轉賬的有效性,並通過發出OracleRequest事件記錄更爲詳細的數據信息:TxK顯卡之家

event OracleRequest(TxK顯卡之家

bytes32 indexed specId,TxK顯卡之家

address requester,TxK顯卡之家

bytes32 requestId,TxK顯卡之家

uint256 payment,TxK顯卡之家

address callbackAddr,TxK顯卡之家

bytes4 callbackFunctionId,TxK顯卡之家

uint256 cancelExpiration,TxK顯卡之家

uint256 dataVersion,TxK顯卡之家

bytes dataTxK顯卡之家

);TxK顯卡之家

這個日志會在oracle合約的日志中找到,如圖中下方所示。鏈下的節點會訂閱該主題的日志,在獲取到記錄的日志信息之後,節點會解析出請求的具體信息,通過網絡的API調用,獲取到請求的結果。之後通過提交事務的方式,調用Oracle合約中的fulfillOracleRequest方法,將數據提交到鏈上。fulfillOracleRequest定義如下:TxK顯卡之家

/**TxK顯卡之家

* @notice 由Chainlink節點調用來完成請求TxK顯卡之家

* @dev 提交的參數必須是`oracleRequest`方法所記錄的哈希參數TxK顯卡之家

* 將會調用回調地址的回調函數,`require`檢查時不會報錯,以便節點可以獲得報酬TxK顯卡之家

* @param _requestId 請求ID必須與請求者所匹配TxK顯卡之家

* @param _payment 爲Oracle發放付款金額 (以wei爲單位)TxK顯卡之家

* @param _callbackAddress 完成方法的回調地址TxK顯卡之家

* @param _callbackFunctionId 完成方法的回調函數TxK顯卡之家

* @param _expiration 請求者可以取消之前節點應響應的到期時間TxK顯卡之家

* @param _data 返回給消費者合約的數據TxK顯卡之家

* @return 外部調用成功的狀態值TxK顯卡之家

*/TxK顯卡之家

function fulfillOracleRequest(TxK顯卡之家

bytes32 _requestId,TxK顯卡之家

uint256 _payment,TxK顯卡之家

address _callbackAddress,TxK顯卡之家

bytes4 _callbackFunctionId,TxK顯卡之家

uint256 _expiration,TxK顯卡之家

bytes32 _dataTxK顯卡之家

TxK顯卡之家

externalTxK顯卡之家

onlyAuthorizedNodeTxK顯卡之家

isValidRequest(_requestId)TxK顯卡之家

returns (bool)TxK顯卡之家

{TxK顯卡之家

bytes32 paramsHash = keccak256(TxK顯卡之家

abi.encodePacked(TxK顯卡之家

_payment,TxK顯卡之家

_callbackAddress,TxK顯卡之家

_callbackFunctionId,TxK顯卡之家

_expirationTxK顯卡之家

TxK顯卡之家

);TxK顯卡之家

require(commitments[_requestId] == paramsHash, “Params do not match request ID”);TxK顯卡之家

withdrawableTokens = withdrawableTokens.add(_payment);TxK顯卡之家

delete commitments[_requestId];TxK顯卡之家

require(gasleft() 》= MINIMUM_CONSUMER_GAS_LIMIT, “Must provide consumer enough gas”);TxK顯卡之家

// All updates to the oracle‘s fulfillment should come before calling theTxK顯卡之家

// callback(addr+functionId) as it is untrusted.TxK顯卡之家

// See: http://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-patternTxK顯卡之家

return _callbackAddress.call(_callbackFunctionId, _requestId, _data); // solhint-disable-line avoid-low-level-callsTxK顯卡之家

}TxK顯卡之家

這個方法會在進行一系列的檢驗之後,會將結果通過之前記錄的回調地址與回調函數,返回給消費者合約:TxK顯卡之家

_callbackAddress.call(_callbackFunctionId, _requestId, _data);TxK顯卡之家

這樣一次請求就全部完成了。TxK顯卡之家

總結TxK顯卡之家

本文從預言機的概念開始,通過一個簡單的獲取ETH價格的例子,講解了請求/響應模式的Chainlink預言機的基本過程,希望對你理解預言機與Chainlink的運行原理有所幫助。TxK顯卡之家
責任編輯;zlTxK顯卡之家

相關推薦

X-POWER-BY FNC V1.0.0 FROM 自制32