智能合约对链下数据的兼容会大大增加开发复杂度,Chainlink 通过 AnyAPI 使开发者的智能合约可以通过去中心化预言机网络(Decentralized Oracle Network:DON)获取外部数据。这样在使用 Chainlink AnyAPI 的时候,开发人员可以投入最少的开发资源,获得最大的自由度,因此可以更加专注在智能合约的功能性上,而非怎么样去获取数据上。
虽然 Chainlink Data Feed 可以给链上智能合约提供由 DON 聚合以后的通证价格,但是在很多场景下,尤其是非 DeFi 应用中,dApp 除了价格以外还需要多种多样的数据来实现自己的业务逻辑。比如在保险领域,智能合约需要天气数据来计算参保方的赔付金额,在合成资产协议中,外部股票市场的数据是必不可少的,除此以外,随着 web3 的场景越来越丰富,会越来越多地依赖于链下数据,比如说链下的交通运输,房地产,身份信息等等多种多样的数据。
如果你的智能合约需要依赖于这些数据,Chainlink AnyAPI 都可以作为一个工具让你从指定的外部数据源获取到特定数据。接下来,就让我们看看 Chainlink AnyAPI 的工作原理是什么。
在从 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 个参数:
在这三个参数设置好以后,还需要加入 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 AnyAPI服务,以获取到多种多样的数据。接下来,我们来看看如何去运行 Chainlink AnyAPI 的“后端”,来看看如何运行一个预言机节点来帮助链上合约获取它们所需要的各种数据。
新建节点有很多种方式,如果你是一个运维人员并且自身有足够的硬件资源,可以根据官方文档中的教程新建一个节点。如果你不想要自己运维节点,那么可以选择使用 naas.link(node as a service),只需要点几个按钮就可以新建一个节点,并且是免费的。另外,Chainlink 官方开发者关系团队也在不同的链上维护了一些 Chainlink 节点,可以在这里查看关于这些节点的 JobId,合约地址和其他相关信息。
如果你需要一些特殊的数据,比如说天气数据,股票数据,体育比赛数据等等,可以在 Chainlink 提供的数据市场 market.link 中搜索。
如果你对于Chainlink 节点所提供的数据有更个性化的要求,可以登陆 Chainlink 的 Discord,Chainlink 团队会帮你在社区中联系节点运营商,以满足你的需求。华语开发者也可以直接联系 Chainlink 中国团队,获得更快的响应。
在上一章节,我们了解了如何去写一个用户合约来使用 Chainlink AnyAPI ,从而获取到多种多样的数据。接下来,我们可以学习一下如何去运行 AnyAPI 的“后端”,来看看如何运行一个预言机节点来帮助链上合约获取它们所需要的各种数据。
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:
在 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 预言机并且私信加入开发者社区,有大量关于智能合约的学习资料以及关于区块链的话题!
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我正在使用ruby1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search