Chainlink预言机基本原理

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本文从预言机的概念开始,通过一个简单的获取 ETH 价格的例子,讲解了请求/响应模式的 Chainlink 预言机的基本过程,希望对你理解预言机与 Chainlink 的运行原理有所帮助。

Chainlink 基本原理
本文我们来从技术上简述一下 Chainlink 的基本原理。如果用最短的一句话解释什么是 Chainlink,可以说 Chainlink 一个去中心化的预言机项目,所以为了理解 Chainlink 的工作原理,我们首先要明白什么是预言机。

预言机

预言机的英文为 Oracle,和著名的数据库服务提供商 Oracle(甲骨文)重名,但是两者除了名字相同以为并没有任何关系。Oracle 这个单词是什么意思,下面是我在 vocabulary.com 上查到的 Oracle 的含义:

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.

中文的大概意思是:在古代,Oracle 是一个提出建议或预言的人,他的建议或预言被认为是直接来自于神。在现代用法中,任何好的信息来源都可以称为 Oracle。

这样就不难理解了,Oracle 传达了万能全知的神的旨意,而甲骨文最初就是用来占卜吉凶时的记录,也在当时也被认为是神谕,传达了神的意思。说以不管是“预言机”还是“甲骨文”都表达了“信息源”的意思。

计算机领域内的预言机一词,最早是图灵提出的。图灵在图灵机(Turing Machine)的基础上,加入了一个称为预言者(Oracle)的黑盒,组成了预言机(Oracle Machine)。所谓预言者,是一个可以回答特定问题集合的实体。即它可以向图灵机系统内部输入信息,帮助图灵机完成运算。以太坊的智能合约是“图灵完备(Turing Complete)”的,某种意义上可以看做一个图灵机,所以以太坊的设计者借鉴这个概念,把向“图灵完备的智能合约”这个图灵机输入信息的也被称为预言机 Oracle。所以说“预言机”这个名字并不是区块链技术领域内的独创概念,它来源于非常早期的计算机抽象设计,在密码学等领域内也都有类似的概念。

而在区块链领域,预言机被认为是可以为智能合约提供外部数据源的系统。从传统技术架构方面来看,预言机是连接智能合约与区块链外部世界的中间件(middleware),是区块链重要的基础设施,它的作用是为区块链上的智能合约(Smart Contract)提供数据信息的。

正如以太坊的定义,区块链是一个交易驱动的状态机(a transaction-based state machine),它能做的事情非常简单,就是通过向区块链提交事务/交易(transaction),来将区块链从一个状态转变成另一个状态。为了保持共识,EVM 的执行过程必须完全确定,并且仅基于以太坊状态和签名交易的共享上下文。这产生了两个特别重要的后果:一个是 EVM 和智能合约没有内在的随机性来源;另一个是外部数据只能作为交易的数据载荷引入。用通俗的话讲,区块链没有主动获取数据的能力,它能用的只有区块链自己本身的数据。数据的缺失导致智能合约的应用范围非常少,目前大部分的应用都是围绕着 token 来展开的。

区块链的确定性的意思是,在任何节点上,只要连入到区块链的分布式网络中,它就可以同步所有的历史区块,回放出一套完全相同的账本。换句话说:在没有互联网连接的情况下,给定完整的块,节点必须能够从头开始重新创建区块链的最终状态。如果账本在形成过程中,依赖于某个外部的 API 调用结果,那在不同时间不同环境下回放的结果就会不一样。这种情况是区块链所不允许的,所以区块链在设计之初就没有网络调用。

那么要实现向区块链提供数据,应该怎么做呢?区块链能留下的只有账本,而区块链所能输入的只有交易。我们就从这两个方面入手。

几乎每一个合约系统,都会有事件记录的功能,比如以太坊中的 EventLog 功能。

下面我们通过一个例子,来介绍一下预言机的基本原理。我们在以太坊链上建立一个用户合约,它需要获取到某个城市的气温数据。当然,智能合约自己是无法获取到这个发生于链下真实世界中的数据信息的,需要借助预言机来实现。智能合约将需要获取天气温度的的城市写入到 EventLog 中,链下我们会启动一个进程,监听并订阅这个事件日志,获取到智能合约的请求之后,将指定城市的温度,通过提交 transaction 的方式,调用合约中的回填方法,提交到智能合约中。

声明:以下代码仅供演示预言机原理,没有做参数检测和错误处理,请不要在生产环境中使用。

消费者合约:

contract WeatherOracle {
  // 用户存储预言机提交的天气数值
  uint256 public temperature;

  // 定义事件
  event RequestTemperature (bytes city);

  // 发出获取请求,即发出一个事件日志
  function requestTemperature (string memory _city) public {
    emit RequestTemperature(bytes(_city));
  }

  // 预言机回调方法,预言机获取到数据后通过这个方法将数据提交到链上
  function updateWeather (uint256 _temperature) public {
    temperature = _temperature;
  }
}

上面的代码非常简单,定义了一个变量用来存储结果,一个方法用于发出请求,一个方法用于接收结果。

链下,我们启动一个进程,以订阅 topic 的方式获取日志信息,之后通过构建一个 transaction,提交一个结果到合约中。

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)

  // 调用合约中的updateWeather方法,回填数据"29"
  tx, err := instance.UpdateWeather(auth, big.NewInt(29))

  log.Info(tx.Hash().Hex())
}

用一个图来展示这个过程:

Chainlink

Chainlink 是一个去中心化的预言机项目,它的作用就是以最安全的方式向区块链提供现实世界中产生的数据。Chainlink 在基本的预言机原理的实现方式之上,围绕 LINK token 通过经济激励建立了一个良性循环的生态系统。Chainlink 预言机需要通过 LINK token 的转账来实现触发。

LINK 是以太坊网络上的 ERC677 合约,关于各类 ERC token 的区别,请参考这篇文章。

在《精通以太坊(Matering Ethereum)》一书中,提出了三种预言机的设计模式,分别是

  • 立即读取(immediate-read)
  • 发布/订阅(publish–subscribe)
  • 请求/响应(request–response)

而基于 LINK ERC677 token完成的预言机功能,就属于其中的请求/响应模式。这是一种较为复杂的模式,上图中展示的是一个不含有聚合过程的简单请求/相应流程。

我们以 Chainlink 提供的 TestnetConsumer 合约中的一个 requestEthereumPrice 方法为例来简单讲一下请求响应的流程。这个函数定义如下:

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);
}

它所实现的功能就是从指定的 API(cryptocompare)获取 ETH/USD 的交易价格。函数传入的参数是指定的 Oracle 地址和 jobId。将一些列的请求参数组好后,调用 sendChainlinkRequestTo 方法将请求发出。sendChainlinkRequestTo 是定义在 Chainlink 提供的库中的一个接口方法,定义如下:

/**
  * @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;
}

其中 link.transferAndCall 方法即是 ERC677 定义的 token 转账方法,与 ERC20 的 transfer 方法相比,它多了一个 data 字段,可以在转账的同时携带数据。这里就将之前打包好的请求数据放在了 data 字段,跟随转账一起发送到了 Oracle 合约。transferAndCall 方法定义如下:

/**
  * @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;
  }

其中的 Transfer(msg.sender, _to, _value, _data); 是发出一个事件日志:

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

将这次转账的详细信息(发送方、接收方、金额、数据)记录到日志中。

Oracle 合约在收到转账之后,会触发 onTokenTransfer 方法,该方法会检查转账的有效性,并通过发出 OracleRequest 事件记录更为详细的数据信息:

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

这个日志会在 Oracle 合约的日志中找到,如图中下方所示。链下的节点会订阅该主题的日志,在获取到记录的日志信息之后,节点会解析出请求的具体信息,通过网络的 API 调用,获取到请求的结果。之后通过提交事务的方式,调用 Oracle 合约中的 fulfillOracleRequest 方法,将数据提交到链上。fulfillOracleRequest 定义如下:

/**
  * @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
}

这个方法会在进行一系列的检验之后,会将结果通过之前记录的回调地址与回调函数,返回给消费者合约:

_callbackAddress.call(_callbackFunctionId, _requestId, _data);

这样一次请求就全部完成了。

总结

本文从预言机的概念开始,通过一个简单的获取 ETH 价格的例子,讲解了请求/响应模式的 Chainlink 预言机的基本过程,希望对你理解预言机与 Chainlink 的运行原理有所帮助。

参考:
https://medium.com/@liyunlong518/%E5%88%9B%E5%BB%BA%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA%E4%BB%A5%E5%A4%AA%E5%9D%8A%E9%A2%84%E8%A8%80%E6%9C%BA-df53e50cc2d5

欢迎加入 Chainlink 开发者社区

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
5月前
|
机器学习/深度学习 人工智能 自然语言处理
数字化转型需要的技术:生成式AI时代的全栈能力图谱
本文探讨生成式AI推动下的数字化转型技术需求转变,从技术本质、实施路径、伦理规制三方面解构核心要素。技术本质从工具理性进化到能力体系,需建立模型思维、多模态交互和自主进化能力。实施路径分为认知重构、实验验证与迭代优化三个阶段。同时,文章介绍生成式人工智能认证(GAI认证)的战略价值,强调其在能力基准建立、技术合作及创新生态接入中的作用。最后,文章分析组织能力进化与未来技术前沿,如认知智能、具身智能和群体智能的演进方向,为企业提供全面的技术赋能与战略转型指导。
|
JavaScript Ubuntu Shell
Ubuntu上安装任意版本nodejs方法
通过以上步骤,您可以在Ubuntu系统上灵活地安装和管理不同版本的Node.js。这种方法让开发者能够根据不同项目的需要选择合适的Node.js版本,同时也使版本切换变得非常方便。而且,nvm能够为每个项目独立管理依赖,从而确保不同项目之间的环境隔离,增强了开发环境的稳定性和可维护性。
3189 2
|
7月前
|
机器学习/深度学习 人工智能 缓存
云上玩转DeepSeek系列之五:实测优化16%, 体验FlashMLA加速DeepSeek-V2-Lite推理
DeepSeek-AI 开源的 FlashMLA 是一个优化多层注意力机制的解码内核,显著提升大语言模型的长序列处理和推理效率。本文介绍了如何在 PAI 平台上安装并使用 FlashMLA 部署 DeepSeek-V2-Lite-Chat 模型。通过优化后的 FlashMLA,实现了约 16% 的性能提升。
|
数据采集 SQL DataWorks
【颠覆想象的数据巨匠】DataWorks——远超Excel的全能数据集成与管理平台:一场电商数据蜕变之旅的大揭秘!
【8月更文挑战第7天】随着大数据技术的发展,企业对数据处理的需求日益增长。DataWorks作为阿里云提供的数据集成与管理平台,为企业提供从数据采集、清洗、加工到应用的一站式解决方案。不同于桌面级工具如Excel,DataWorks具备强大的数据处理能力和丰富的功能集,支持大规模数据处理任务。本文通过电商平台案例,展示了如何使用DataWorks构建数据处理流程,包括多源数据接入、SQL任务实现数据采集、数据清洗加工以提高质量,以及利用分析工具挖掘数据价值的过程。这不仅凸显了DataWorks在大数据处理中的核心功能与优势,还展示了其相较于传统工具的高扩展性和灵活性。
328 0
|
Shell Go
go 编辑yaml 文件
在Go语言中编辑YAML文件通常涉及以下步骤: 1. 读取YAML文件内容到字符串。 2. 使用YAML解析库(如`gopkg.in/yaml.v2`)将字符串解析为Go数据结构(如`map[string]interface{}`或自定义的结构体)。 3. 修改数据结构中的值以更新YAML内容。 4. 将修改后的数据结构编码回YAML格式的字符串。 5. 将字符串写回到YAML文件。 以下是一个简单的例子,展示了如何使用`gopkg.in/yaml.v2`库来编辑YAML文件: 首先,确保你已经安装了`gopkg.in/yaml.v2`包: ```bash go get gopkg.i
600 0
|
弹性计算 虚拟化 异构计算
阿里云GPU服务器价格表(Nvidia M40/P100/P4/V100)
阿里云GPU服务器价格表(Nvidia M40/P100/P4/V100)
1357 0
|
Shell 开发工具 git
使用git命令对gitee存放项目到仓库、切换分支以及合并分支教程
使用git命令对gitee存放项目到仓库、切换分支以及合并分支教程
573 0
|
算法 区块链
数字藏品NFT开发交易平台系统(快速搭建)
现下,数字藏品已成功“出圈”,成为数字文创领域近年来的热门话题。通过独有的版权追踪与所有权归属证明,数字藏品可为数字文创内容的构建、确权、流转全流程提供切实可行的解决方案,为构建可信透明且有约束力的文化产业运营机制提供了强大的保障。 数字藏品源于NFT,二者都是元宇宙概念的产物。数字藏品,通过区块链技术进行唯一性标识的链上资产,其具备可溯源性,不可篡改性以及唯√一性等技术特点。由于NFT自带的金融与可虚拟货币交易的属性,鉴于我国的合规政策,在剥离其交易属性后,数字藏品应运而生。
|
SQL 存储 数据挖掘
区块链数据探索:Bitcoin公链数据ETL
Bitcoin 公链可以理解为是一个公共的数据库,里面存储的是Bitcoin发布至今的所有转账记录,并且任何人只要接入到其网络中都可以获取,并不需要任何交易、挖矿、持币等相关操作。 本文主要主题的是将原始的Bitcoin公链数据进行清洗规整,写入到阿里云SLS,然后做一些有趣的数据处理,比如实现简洁的区块链浏览器、数据分析、交易链路追踪等。
1426 0
区块链数据探索:Bitcoin公链数据ETL