热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ChainlinkAnyAPI代码解析

智能合约对链下数据的兼容会大大增加开发复杂度,Chainlink 通过 AnyAPI 使开发者的智能合约可以通过去中心化预言机网络(Decentralized Oracle Network:DON)从

智能合约对链下数据的兼容会大大增加开发复杂度,Chainlink 通过 AnyAPI 使开发者的智能合约可以通过去中心化预言机网络(Decentralized Oracle Network:DON)从外部获取数据。这样在使用 Chainlink AnyAPI 的时候,开发人员可以投入最少的开发资源,获得最大的灵活度,因此可以更加专注在智能合约的功能性上,而非怎么样去获取数据上。


虽然 Chainlink Data Feed 可以给链上智能合约提供由 DON 聚合以后的通证价格,但是在很多场景下,尤其是非 DeFi 应用中,dApp 除了价格以外还需要多种多样的数据来实现自己的业务逻辑。比如在保险领域,智能合约需要天气数据来计算参保方的赔付金额,在合成资产协议中,外部股票市场的数据是必不可少的,除此以外,随着 web3 的场景越来越丰富,会越来越多地依赖于链下数据,比如说链下的交通运输,房地产,身份信息等等多种多样的数据。


如果你的智能合约需要依赖于这些数据,Chainlink AnyAPI 都可以作为一个工具让你从指定的外部数据源获取到特定数据。接下来,就让我们看看 Chainlink AnyAPI 的工作原理是什么。


使用 Chainlink AnyAPI 服务


发送 Chainlink 请求


在从 Chainlink 预言机节点获得数据之前,我们首先需要创建一个用户合约,然后在用户合约中给 Chainlink 预言机节点发送一个请求。下面的代码将展示如何通过用户合约给预言机节点发送请求:


function requestVolumeData() public returns (bytes32 requestId) { Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector); req.add('get', 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD');
req.add('path', 'RAW,ETH,USD,VOLUME24HOUR');
int256 timesAmount = 10**18; req.addInt('times', timesAmount);
return sendChainlinkRequest(req, fee);}


Chainlink AnyAPI 获取的数据的方式一般是通过预言机节点给外部数据源发送 RESTful 请求,所以节点在发送请求之前需要知道要请求的数据的 API 和参数。为了给 Any API 提供必要的数据,在智能合约的 `ChainlinkRequest` 中,我们需要通过函数 `buildChainlinkRequest` 加入这些相关信息。`buildChainlinkRequest` 这个函数定义在 `ChainlinkClient.sol`,代码展示如下:


function buildChainlinkRequest( bytes32 specId, address callbackAddr, bytes4 callbackFunctionSignature) internal pure returns (Chainlink.Request memory) { Chainlink.Request memory req; return req.initialize(specId, callbackAddr, callbackFunctionSignature);}


在 `buildChainlinkRequest` 函数中,所有与请求相关的信息都会加入到 Request 这个结构体中,并且调用函数 `initialize` 来完成初始化。


Struct Request 定义在 Chainlink.sol 文件中,代码如下:


struct Request { bytes32 id; address callbackAddress; bytes4 callbackFunctionId; uint256 nonce; BufferChainlink.buffer buf;}


函数 `initialize` 也定义在 Chainlink.sol 文件中,代码如下:


function initialize( Request memory self, bytes32 jobId, address callbackAddr, bytes4 callbackFunc) internal pure returns (Chainlink.Request memory) { BufferChainlink.init(self.buf, defaultBufferSize); self.id = jobId; self.callbackAddress = callbackAddr; self.callbackFunctiOnId= callbackFunc; return self;}


在函数 `buildChainlinkRequest` 中,会接受 3 个参数:

  • jobId: 预言机节点需要执行的 job 的 ID。

  • callbackAddr:用户合约地址,这个参数是预言机节点将会将返回数据的合约地址。

  • callbackFunc:这个是预言机节点需要回调的函数签名。


在这三个参数设置好以后,还需要加入 URL 和数据路径,因为我们需要告诉预言机节点通过哪个 API 获取数据,并且在获取数据以后,如何在返回的数据中找到我们所需要的有效数据。


req.add('get', 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD');
req.add('path', 'RAW,ETH,USD,VOLUME24HOUR');


这里的 `path` 参数很重要,因为预言机通常会通过用户提供的 URL 获得庞大而杂乱的数据,下面的 JSON 数据就是一个例子。


{"RAW": {"ETH": {"USD": {"TYPE":"5", "MARKET":"CCCAGG", "FROMSYMBOL":"ETH", "TOSYMBOL":"USD", "FLAGS":"2049", "PRICE":1083.43, "LASTUPDATE":1655472805, "MEDIAN":1083.49, "LASTVOLUME":0.01152796, "LASTVOLUMETO":12.488815466, "LASTTRADEID":"298687546", "VOLUMEDAY":279647.314121829, "VOLUMEDAYTO":304661021.9293905, "VOLUME24HOUR":617393.32461219, ....... ....... "TOTALTOPTIERVOLUME24HTO":"$ 5.47B", "IMAGEURL":"/media/37746238/eth.png"} } } }


通过在 request 中的 path 数据,预言机节点才可以获取到我们所想要的数据。所以在 URL 和路径被设置好以后,这个 `chainlinkRequest` 就完成并且可以被发送了。


return sendChainlinkRequest(req, fee);


这里的调用顺序是:


  1. 终端用户会在用户合约中调用函数 `requestVolumeData`,然后用户合约会调用 ChainlinkClient.sol 中的函数 `sendChainlinkRequest`。

  2. 然后 `sendChainlinkRequest` 会调用函数 `sendChainlinkRequestTo`,这个函数会接受的参数是预言机的地址,函数签名和其他相关信息,然后 encode 所有的信息,转化为 bytes 数据。

  3. 接下来,`_rawRequest` 会调用 Link 通证合约的中的 `transferAndCall` 函数,`transferAndCall` 是 ERC-677 标准中所定义的函数。`transferAndCall`会把要执行的代码(上一步中的 encode 数据)发送给预言机合约,然后要求该合约执行 代码逻辑。

  4. 最后,预言机合约 `OperatorInterface.sol`中的函数 `operatorRequest` 会被上一步中的 `transferAndCall`调用,然后该函数会将函数签名,requestId 等信息写到 event 中,以便链下预言机发现。


让我们看一个具体的例子,Chainlink 官方在测试网 Kovan 中所部署了一个预言机合约。这个合约的代码可以在这里看到:

https://kovan.etherscan.io/address/0x74EcC8Bdeb76F2C6760eD2dc8A46ca5e581fA656#code


function oracleRequest( address sender, uint256 payment, bytes32 specId, address callbackAddress, bytes4 callbackFunctionId, uint256 nonce, uint256 dataVersion, bytes calldata data ) external override validateFromLINK { (bytes32 requestId, uint256 expiration) = _verifyAndProcessOracleRequest( sender, payment, callbackAddress, callbackFunctionId, nonce, dataVersion ); emit OracleRequest(specId, sender, requestId, payment, sender, callbackFunctionId, expiration, dataVersion, data); }


在代码中,可以很容易看到,这个函数就是把所有的信息写到了 event log 中,然后等待链下的预言机节点检测。


返回请求数据


返回数据的函数 `fulfillOracleRequest2` 也被定义在 `OperatorInterface.sol` 文件中。


function fulfillOracleRequest2(bytes32 requestId,uint256 payment,address callbackAddress,bytes4 callbackFunctionId,uint256 expiration,bytes calldata data) external returns (bool);


让我们看看刚才的合约中,这个函数中的逻辑是什么样的:


function fulfillOracleRequest2( bytes32 requestId, uint256 payment, address callbackAddress, bytes4 callbackFunctionId, uint256 expiration, bytes calldata data) external override validateAuthorizedSender validateRequestId(requestId) validateCallbackAddress(callbackAddress) validateMultiWordResponseId(requestId, data) returns (bool){ _verifyOracleRequestAndProcessPayment(requestId, payment, callbackAddress, callbackFunctionId, expiration, 2); emit OracleResponse(requestId); require(gasleft() >= MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas");
(bool success, ) = callbackAddress.call(abi.encodePacked(callbackFunctionId, data)); return success; }


通过上面的代码,我们看到这个函数调用了 `callbackAddress` 地址合约中的一个函数,这个地址就是之前传给预言机合约的用户合约地址。用户合约中 `fullfill` 函数被调用了:


function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId) { emit RequestVolume(_requestId, _volume); volume = _volume; }


这个展示合约中的 fulfill 函数非常简单,就是将 requestId 和 volume 的数据写入 event log,然后将 volume 写入到本地变量 _volume 中。


运行 Chainlink 节点提供 Any API 服务


在上一章节,我们了解了如何在用户合约中使用 Chainlink AnyAPI 服务,以获取到多种多样的数据。接下来,我们来看看如何去运行 Chainlink AnyAPI 的“后端”,来看看如何运行一个预言机节点来帮助链上合约获取它们所需要的各种数据。


新建 Chainlink 节点


新建节点有很多种方式,如果你是一个运维人员并且自身有足够的硬件资源,可以根据官方文档中的教程新建一个节点。如果你不想要自己运维节点,那么可以选择使用 naas.link(node as a service),只需要点几个按钮就可以新建一个节点,并且是免费的。另外,Chainlink 官方开发者关系团队也在不同的链上维护了一些 Chainlink 节点,可以在这里查看关于这些节点的 JobId,合约地址和其他相关信息:

https://docs.chain.link/docs/decentralized-oracles-ethereum-mainnet/


如果你需要一些特殊的数据,比如说天气数据,股票数据,体育比赛数据等等,可以在 Chainlink 提供的数据市场 market.link 中搜索。


如果你对于 Chainlink 节点所提供的数据有更个性化的要求,可以登陆 Chainlink 的 Discord,Chainlink 团队会帮你在社区中联系节点运营商,以满足你的需求。华语开发者也可以直接联系 Chainlink 中国团队,获得更快的响应。


在上一章节,我们了解了如何去写一个用户合约来使用 Chainlink AnyAPI ,从而获取到多种多样的数据。接下来,我们可以学习一下如何去运行 AnyAPI 的“后端”,来看看如何运行一个预言机节点来帮助链上合约获取它们所需要的各种数据。


预言机节点的 job 和 task pipeline (TOML)


TOML(Tom's Obviously Minimal Language)是一种配置文件的格式,因为 sematics 比较清晰,所以更容易阅读,被很多项目所使用。Chainlink 节点就是使用 TOML 来定义节点所提供的 API 服务所对应的 job 的详细信息。


在 Chainlink 节点中,每一个 jobId 都会代表一个在节点中运行的 job。比如说在 API 样例代码中,jobId 代表的 job 是获取 BTC 昨天的市场数据的。Chainlink 节点使用 TOML 来定义怎么样从 API 中获取数据,并且将这个数据进行标准化,使其可以被用户合约使用。


Chainlink 节点做的任何操作都会依赖于 job,现在支持以下 6 种 job:

  1. Cron:根据一个时间表而非外部触发来执行一个 job。

  2. Direct request job:根据一个用户所发出的请求的 receipt 来执行一个 job。预言机合约会在被 emit 的 log 中发现用户的请求。这个方式和以前 ethlog/runlog 执行 job 的方式类似。

  3. Flux monitor job:根据不同的预言机节点所返回的数据来更新 data feed 中的数据。更新会在波动率足够大,或者是 heartbeat 超出时间限制的时候触发。

  4. Keeper job:根据链上合约中的状态进行判断,判断成功以后会执行智能合约中的函数,可以非定期地调用合约中的函数。

  5. Off-chain reporting job:Off-chain reporting(OCR)和 Flux monitor job 非常类似,OCR job 会根据多个 Chainlink 预言机节点聚合以后的数据更新 data feed。OCR 和 Flux monitor job 的区别是 OCR 使用了由密码学保证的链下协议,可以让单个节点在一个 round 中将所有其他节点中的数据提交上来。通过这个方式,可以节省大量的 gas。

  6. Webhook job: Webhook 可以由 HTTP 请求所触发,HTTP 请求可以由用户或者其他外部触发器所触发。


在 job 中,需要定义以下变量:

  1. name: 在 Chainlink 节点 UI 中所现实的 job 的名字。

  2. type: job 的类型,可以是上述 job 类型中的任何一个。

  3. schemaVersion: 现在都需要设置为 1,这个设置是为了让 job 的格式向前兼容。

  4. observationSource: 这个参数定义 job 具体要做的操作。

  5. maxTaskDuration: 任何一个任务能够运行的最长事件默认值。如果一个任务达到了最长时间,这个任务就会报错。

  6. externalJobID: 提供了一种可选方法,用户可以通过这个参数直接定义 job 的。


除了上述参数以外,你还需要定义 job 中的任务(task),让我们看看一个 job 的 TOML 文件的例子:


type = "directrequest"schemaVersion = 1evmChainID = 1name = "example eth request event spec"cOntractAddress= "0x613a38AC1659769640aaE063C651F48E0250454C"

observatiOnSource= """ ds [type="http" method=GET url="http://example.com"] ds_parse [type="jsonparse" path="USD"] ds_multiply [type="multiply" times=100]
ds -> ds_parse -> ds_multiply


`ds`, `ds_parse`, `ds_multiply` 是 job 要执行的 3 个任务,执行顺序通过 `ds -> ds_parse -> ds_multiply` 这一行定义,语法非常简单,即先给 “http://exmpale.com” 发送 GET 请求,然后使用路径 “USD” 来找到用户在这个 JSON 文件中需要的值。这个 JSON  文件如下:


{ usd: number}


最后,这个 job 会根据 `ds_multiply` 这个任务将结果乘以 100。


总结


除了可以通过 Chainlink data feed 获取通证价格以外,开发还可以通过 Chainlink AnyAPI 获得任何自己想要的数据。文章讲解了如何新建自己的 Chainlink 节点,并且在合约中使用 Chainlink 节点 AnyAPI 服务获得个性化数据。


您可以关注 Chainlink 预言机并且私信加入开发者社区,有大量关于智能合约的学习资料以及关于区块链的话题!


END


获取 Chainlink 官方最新资讯

Chainlink 欢迎优秀小伙伴的加入▼

《招人啦 | 欢迎你加入 Chainlink 中国团队!》

加入 Chainlink 官方渠道▼


Chainlink 官方渠道
微博:  https://weibo.com/chainlinkofficial
知乎:https://www.zhihu.com/people/chainlink
中文 Twitter: https://twitter.com/ChainlinkChina
Twitter:  https://twitter.com/chainlink
中文爱好者电报群:https://t.me/chainlinkfans
Telegram:  https://t.me/chainlinkofficial
Discord:   https://discord.gg/aSK4zew
GitHub:  https://github.com/smartcontractkit/chainlink
SegmentFault:https://segmentfault.com/u/chainlink
QQ 群: 6135525
合作联系:  china@smartcontract.com

点击“阅读原文” 进入 Chainlink 中文官网


推荐阅读
  • 本指南介绍了如何在ASP.NET Web应用程序中利用C#和JavaScript实现基于指纹识别的登录系统。通过集成指纹识别技术,用户无需输入传统的登录ID即可完成身份验证,从而提升用户体验和安全性。我们将详细探讨如何配置和部署这一功能,确保系统的稳定性和可靠性。 ... [详细]
  • 如何使用 `org.eclipse.rdf4j.query.impl.MapBindingSet.getValue()` 方法及其代码示例详解 ... [详细]
  • 本文介绍了如何利用Shell脚本高效地部署MHA(MySQL High Availability)高可用集群。通过详细的脚本编写和配置示例,展示了自动化部署过程中的关键步骤和注意事项。该方法不仅简化了集群的部署流程,还提高了系统的稳定性和可用性。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • 如何在PHP中准确获取服务器IP地址?
    如何在PHP中准确获取服务器IP地址? ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • MATLAB字典学习工具箱SPAMS:稀疏与字典学习的详细介绍、配置及应用实例
    SPAMS(Sparse Modeling Software)是一个强大的开源优化工具箱,专为解决多种稀疏估计问题而设计。该工具箱基于MATLAB,提供了丰富的算法和函数,适用于字典学习、信号处理和机器学习等领域。本文将详细介绍SPAMS的配置方法、核心功能及其在实际应用中的典型案例,帮助用户更好地理解和使用这一工具箱。 ... [详细]
  • 在使用 Qt 进行 YUV420 图像渲染时,由于 Qt 本身不支持直接绘制 YUV 数据,因此需要借助 QOpenGLWidget 和 OpenGL 技术来实现。通过继承 QOpenGLWidget 类并重写其绘图方法,可以利用 GPU 的高效渲染能力,实现高质量的 YUV420 图像显示。此外,这种方法还能显著提高图像处理的性能和流畅性。 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • 深入探索HTTP协议的学习与实践
    在初次访问某个网站时,由于本地没有缓存,服务器会返回一个200状态码的响应,并在响应头中设置Etag和Last-Modified等缓存控制字段。这些字段用于后续请求时验证资源是否已更新,从而提高页面加载速度和减少带宽消耗。本文将深入探讨HTTP缓存机制及其在实际应用中的优化策略,帮助读者更好地理解和运用HTTP协议。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 针对MySQL Undo空间满载及Oracle Undo表空间溢出的问题,本文详细探讨了其原因与解决策略。首先,通过启动SQL*Plus并以SYS用户身份登录数据库,查询当前数据库的UNDO表空间名称,确认当前状态。接着,分析导致Undo空间满载的常见原因,如长时间运行的事务、频繁的更新操作等,并提出相应的解决方案,包括调整Undo表空间大小、优化事务管理、定期清理历史数据等。最后,结合实际案例,提供具体的实施步骤和注意事项,帮助DBA有效应对这些问题。 ... [详细]
  • Java能否直接通过HTTP将字节流绕过HEAP写入SD卡? ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
author-avatar
饿狼传说少校_584_869_541
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有