关于智能合约:通过预言机获取任意链下数据-Chainlink-Any-API-代码解析

0次阅读

共计 7625 个字符,预计需要花费 20 分钟才能阅读完成。

智能合约对链下数据的兼容会大大增加开发复杂度,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 is defined in Chainlink.sol as below:
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);
这里的调用程序是:
终端用户会在用户合约中调用函数 requestVolumeData,而后用户合约会调用 ChainlinkClient.sol 中的函数 sendChainlinkRequest
而后 sendChainlinkRequest 会调用函数 sendChainlinkRequestTo,这个函数会承受的参数是预言机的地址,函数签名和其余相干信息,而后 encode 所有的信息,转化为 bytes 数据。
接下来,_rawRequest 会调用 Link 通证合约的中的 transferAndCall 函数,transferAndCall 是 ERC-677 规范中所定义的函数。transferAndCall 会把要执行的代码(上一步中的 encode 数据)发送给预言机合约,而后要求该合约执行 代码逻辑。
最初,预言机合约 OperatorInterface.sol 中的函数 operatorRequest 会被上一步中的 transferAndCall 调用,而后该函数会将函数签名,requestId 等信息写到 event 中,以便链下预言机发现。
让咱们看一个具体的例子,Chainlink 官网在测试网 Kovan 中所部署了一个预言机合约。这个合约的代码能够在这里看到。

 function operatorRequest(
    address sender,
    uint256 payment,
    bytes32 specId,
    bytes4 callbackFunctionId,
    uint256 nonce,
    uint256 dataVersion,
    bytes calldata data
  ) external override validateFromLINK {(bytes32 requestId, uint256 expiration) = _verifyAndProcessOracleRequest(
      sender,
      payment,
      sender,
      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,合约地址和其余相干信息。
如果你须要一些非凡的数据,比如说天气数据,股票数据,体育比赛数据等等,能够在 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       = 1
evmChainID          = 1
name                = "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 Any API 取得任何个性化数据。文章解说了如何新建本人的 Chainlink 节点,并且在合约中应用 Chainlink 节点 AnyAPI 服务取得个性化数据。
您能够关注 Chainlink 预言机并且私信退出开发者社区,有大量对于智能合约的学习材料以及对于区块链的话题!

正文完
 0