Dry Goods | Basic Principles of Chainlink Predictor

In this article, we will briefly describe the basic principles of Chainlink technically. If you explain what Chainlink is in the shortest sentence, you can say that Chainlink is a decentralized oracle project, so in order to understand the working principle of Chainlink, we must first understand what a oracle is.

Oracle

Oracle in English is the same name as Oracle (Oracle), a famous database service provider, but the two do not have any relationship other than the same name. What does the word Oracle mean? Here is the meaning of Oracle I found on vocabulary.com :

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.

The approximate meaning of Chinese is: In ancient times, oracle was a person who made suggestions or predictions, and his suggestions or predictions were thought to come directly from God. In modern usage, any good source of information can be called oracle .

It is not difficult to understand that Oracle conveyed the will of God, which is omnipotent and omnipotent, and Oracle was originally used as a record of divination, and was also considered a deity at that time, conveying the meaning of God. That is to say, whether it is "the oracle" or "Oracle", it means "information source".

The word oracle in the computer field was first proposed by Turing. Turing added a black box called Oracle on the basis of Turing Machine to form Oracle Machine. The so-called prophet is an entity that can answer a specific set of questions. That is, it can input information into the Turing machine system to help the Turing machine complete operations. Ethereum's smart contract is "Turing Complete". In a sense, it can be regarded as a Turing machine. Therefore, the designers of Ethereum draw on this concept, The psychic input information is also called oracle. Therefore, the name "predictor" is not an original concept in the field of blockchain technology. It originates from very early computer abstract designs. It also has similar concepts in fields such as cryptography.

In the field of blockchain, the oracle is considered to be a system that can provide external data sources for smart contracts. From the perspective of traditional technology architecture, the oracle is a middleware that connects smart contracts with the outside world of the blockchain. It is an important infrastructure of the blockchain. Its role is to provide smart contracts Contract) to provide data information.

As defined by Ethereum, the blockchain is a transaction-based state machine, and what it can do is very simple, that is, by submitting transactions / transactions to the blockchain, Blockchain transitions from one state to another. In order to maintain consensus, the execution process of EVM must be completely determined and based only on the shared context of Ethereum status and signed transactions. This has two particularly important consequences: one is that EVM and smart contracts have no inherent source of randomness; the other is that external data can only be introduced as a data load for transactions. In layman's terms, the blockchain does not have the ability to actively obtain data, it can only use the blockchain's own data. The lack of data has led to a very limited scope of application of smart contracts. At present, most applications are developed around tokens.

The certainty of the blockchain means that at any node, as long as it is connected to the distributed network of the blockchain, it can synchronize all historical blocks and play back an identical set of ledgers. In other words: without an internet connection, given a complete block, a node must be able to recreate the final state of the blockchain from scratch. If the ledger depends on the results of an external API call during the formation of the ledger, the results will be different when played back at different times and in different environments. This situation is not allowed by the blockchain, so there is no network call at the beginning of the design of the blockchain.

So what should we do to provide data to the blockchain? The blockchain can only leave the ledger, while the blockchain can only enter transactions. We start from these two aspects.

Almost every contract system will have an event logging function, such as the EventLog function in Ethereum.

Let us introduce an example to introduce the basic principles of the oracle. We establish a user contract on the Ethereum chain, which needs to obtain temperature data for a certain city. Of course, the smart contract itself cannot obtain the data information that occurs in the real world off-chain, and it needs to be achieved by means of a predictor. The smart contract writes the city that needs to obtain the weather temperature into the EventLog. We will start a process off the chain, listen to and subscribe to this event log. After obtaining the smart contract request, the temperature of the specified city will be submitted by the transaction Call the backfill method in the contract and submit it to the smart contract.

Disclaimer: The following code is only used to demonstrate the principle of the oracle. It does not perform parameter detection and error handling. Please do not use it in a production environment.

Consumer contract:

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } } 

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } }

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } }

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } }

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } }

func SubscribeEventLog() { topic := crypto.Keccak256([]byte("RequestTemperature(bytes)")) query := ethereum.FilterQuery{ Topics: [][]common.Hash{ { common.BytesToHash(topic), }, }, } // 订阅相关主题的日志事件 events := make(chan types.Log) sub, err := EthClient.SubscribeFilterLogs(ctx, query, events) // 加载合约的ABI文件 ta, err := abi.JSON(strings.NewReader(AbiJsonStr)) // 监听事件订阅 for { select { case err := <-sub.Err(): log.Error(err) break case ev := <-events: // 获取到订阅的消息 ej, _ := ev.MarshalJSON() log.Info(string(ej)) // 解析数据 var sampleEvent struct { City []byte } err = ta.Unpack(&sampleEvent, "RequestTemperature", ev.Data) log.Info(string(sampleEvent.City)) // 构建交易提交结果,需要提供私钥用于签署交易 CallContract("b7b502b...164b42c") } } }

  func CallContract(keyStr string) { addr := PrivateKeyToAddress(keyStr) nonce, err := EthClient.PendingNonceAt(ctx, addr) 

gasPrice, err: = EthClient.SuggestGasPrice (ctx)

privateKey, err: = crypto.HexToECDSA (keyStr)

auth: = bind.NewKeyedTransactor (privateKey)
auth.Nonce = big.NewInt (int64 (nonce))
auth.Value = big.NewInt (0)
auth.GasLimit = uint64 (300000)
auth.GasPrice = gasPrice

instance, err: = event.NewEvent (common.HexToAddress ("0x8A421906e9562AA1c71e5a32De1cf75161C5A463"), EthClient)

// Call the updateWeather method in the contract and backfill data "29"
tx, err: = instance.UpdateWeather (auth, big.NewInt (29))

log.Info (tx.Hash (). Hex ())
} Use a diagram to illustrate this process:

Chainlink

Chainlink is a decentralized oracle machine project.Its role is to provide the blockchain with data generated in the real world in the most secure way. Chainlink builds on a basic oracle machine principle and builds a virtuous circle ecosystem around LINK token through economic incentives. The Chainlink oracle needs to be triggered through the transfer of LINK tokens.

LINK is an ERC677 contract on the Ethereum network. For the difference between various ERC tokens, please refer to this article .

In the book "Mastering Ethereum", three design patterns for oracles are proposed, namely

  • Immediate-read
  • Publish–subscribe
  • Request / response

The oracle function based on the LINK ERC677 token belongs to the request / response mode. This is a more complex pattern, shown in the figure above is a simple request / corresponding process without the aggregation process.

We take the requestEthereumPrice method in the TestnetConsumer contract provided by Chainlink as an example to briefly explain the process of requesting and responding. This function is defined as follows:

  function requestEthereumPrice(address _oracle, string _jobId) public onlyOwner { Chainlink.Request memory req = buildChainlinkRequest(stringToBytes32(_jobId), this, this.fulfillEthereumPrice.selector); req.add("get", "https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD"); req.add("path", "USD"); req.addInt("times", 100); sendChainlinkRequestTo(_oracle, req, ORACLE_PAYMENT); } 

The function it implements is to obtain the transaction price of ETH / USD from the specified API (cryptocompare). The parameters passed to the function are the specified oracle address and jobId. After grouping a series of request parameters, the sendChainlinkRequestTo method is called to send the request. sendChainlinkRequestTo is an interface method defined in the library provided by Chainlink, and is defined as follows:

  /** * @notice 向指定的oracle地址创建一个请求* @dev 创建并存储一个请求ID, 增加本地的nonce值, 并使用`transferAndCall` 方法发送LINK, * 创建到目标oracle合约地址的请求* 发出ChainlinkRequested 事件. * @param _oracle 发送请求至的oracle地址* @param _req 完成初始化的Chainlink请求* @param _payment 请求发送的LINK数量* @return 请求ID */ function sendChainlinkRequestTo(address _oracle, Chainlink.Request memory _req, uint256 _payment) internal returns (bytes32 requestId) { requestId = keccak256(abi.encodePacked(this, requests)); _req.nonce = requests; pendingRequests[requestId] = _oracle; emit ChainlinkRequested(requestId); require(link.transferAndCall(_oracle, _payment, encodeRequest(_req)), "unable to transferAndCall to oracle"); requests += 1; 

return requestId;} The link.transferAndCall method is the token transfer method defined by ERC677. Compared with the ERC20 transfer method, it has a data field that can carry data while transferring. Here the previously packed request data is placed in the data field and sent to the oracle contract along with the transfer. The transferAndCall method is defined as follows:

  /** * @dev 将token和额外数据一起转移给一个合约地址* @param _to 转移到的目的地址* @param _value 转移数量* @param _data 传递给接收合约的额外数据*/ function transferAndCall(address _to, uint _value, bytes _data) public returns (bool success) { super.transfer(_to, _value); Transfer(msg.sender, _to, _value, _data); if (isContract(_to)) { contractFallback(_to, _value, _data); } return true; } 

Among them Transfer (msg.sender, _to, _value, _data); is issued an event log:

  event Transfer(address indexed from, address indexed to, uint value, bytes data); 

Record the details of the transfer (sender, receiver, amount, data) in the log.

After the Oracle contract receives the transfer, the onTokenTransfer method will be triggered. This method will check the validity of the transfer and record more detailed data information by issuing the OracleRequest event:

  event OracleRequest( bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data ); 

This log will be found in the log of the oracle contract, as shown below. The off-chain node will subscribe to the log of this topic. After obtaining the recorded log information, the node will parse out the specific information of the request and obtain the requested result through the network API call. Later, by submitting a transaction, the fulfillOracleRequest method in the Oracle contract is called to submit the data to the chain. fulfillOracleRequest is defined as follows:

  /** * @notice 由Chainlink节点调用来完成请求* @dev 提交的参数必须是`oracleRequest`方法所记录的哈希参数 * 将会调用回调地址的回调函数,`require`检查时不会报错,以便节点可以获得报酬* @param _requestId 请求ID必须与请求者所匹配* @param _payment 为Oracle发放付款金额(以wei为单位) * @param _callbackAddress 完成方法的回调地址* @param _callbackFunctionId 完成方法的回调函数* @param _expiration 请求者可以取消之前节点应响应的到期时间* @param _data 返回给消费者合约的数据* @return 外部调用成功的状态值*/ function fulfillOracleRequest( bytes32 _requestId, uint256 _payment, address _callbackAddress, bytes4 _callbackFunctionId, uint256 _expiration, bytes32 _data ) external onlyAuthorizedNode isValidRequest(_requestId) returns (bool) { bytes32 paramsHash = keccak256( abi.encodePacked( _payment, _callbackAddress, _callbackFunctionId, _expiration ) ); require(commitments[_requestId] == paramsHash, "Params do not match request ID"); withdrawableTokens = withdrawableTokens.add(_payment); delete commitments[_requestId]; require(gasleft() >= MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas"); // All updates to the oracle's fulfillment should come before calling the // callback(addr+functionId) as it is untrusted. // See: https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern return _callbackAddress.call(_callbackFunctionId, _requestId, _data); // solhint-disable-line avoid-low-level-calls } 

After performing a series of tests, this method will pass the result to the consumer contract through the previously recorded callback address and callback function:

  _callbackAddress.call(_callbackFunctionId, _requestId, _data); 

This completes one request.

to sum up

This article starts with the concept of the oracle machine, through a simple example of obtaining the ETH price, explains the basic process of the Chainlink oracle machine in the request / response mode, and hopes to help you understand the operation of the oracle machine and Chainlink.

Author: Head

We will continue to update Blocking; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more