沉浸在以太坊的开发中。 第5部分:Oraclize

访问大型文件和各种外部动态数据通常是分散式应用程序中非常重要的部分。 同时,以太坊本身并没有提供转向外部的机制-智能合约只能在区块链本身内读写。 在本文中,我们将考虑Oraclize,它可以通过查询几乎所有Internet资源来与外界交互。 一个相关的主题是IPFS,并简要提及它。



IPFS


IPFS是具有内容寻址功能的分布式文件系统。 这意味着对于其中添加的任何文件的内容,将考虑唯一的哈希。 然后使用相同的哈希从网络搜索和检索此内容。
本文和其他几篇文章已经描述了基本信息,因此我们没有理由重复。

为什么将IPFS与以太坊结合使用?


在区块链上保存任何卷内容太昂贵且对网络有害。 因此,最好的选择是保存到链下存储中文件的某种链接,而不必是IPFS。 但是IPFS具有许多优点:

  • 文件链接是文件特定内容所独有的哈希,因此,如果将此哈希放在区块链上,我们可以确保从它收到的文件是最初添加的文件,因此无法替换该文件
  • 分布式系统可确保特定服务器不可用(由于阻塞或其他原因)
  • 指向文件的链接和哈希确认合并在一行中,这意味着您可以减少对区块链的写操作并节省费用

在缺点中,可以提到的是,由于没有中央服务器,因此为了文件的可访问性,必须至少“分发”一个文件。 但是,如果您有一个特定的文件,则连接到分发服务器很容易-启动ipfs守护程序并通过ipfs add添加文件。

该技术非常适合于权力下放的意识形态,因此,考虑到Oraclize,我们经常会在不同的Oracle机制中使用IPFS。

Oraclize


为了执行几乎所有有用的工作,智能合约需要接收新数据。 但是,没有内置功能可以满足从区块链到外界的请求。 当然,您可以手动添加事务所需的所有内容,但是无法验证此数据的来源及其可靠性。 另外,您可能需要组织其他基础架构来快速更新动态数据,例如汇率。 并且以固定间隔进行更新将导致天然气超支。

因此, Oraclize提供的服务非常方便:在智能合约中,您可以向Internet上几乎所有的API或资源发送请求,请确保从指定资源接收的数据不变,并在同一智能合约中使用结果。

Oraclize不仅是一个以太坊服务,其他区块链也提供了类似的功能,但我们只会描述以太坊的捆绑包。

开始使用


开始所需要做的就是将存储库中的oraclizeAPI文件之一添加到项目中。 您只需要选择一个适合您的编译器版本(solc)的版本:oraclizeAPI_0.5.sol(从0.4.18开始的版本),oraclizeAPI_0.4.sol(从0.4.1开始的版本),oraclizeAPI_pre0.4.sol(对于所有较旧的版本),都支持。此版本已经停产。 如果您使用松露,请不要忘记将文件重命名为usingOraclize-它要求文件名和合同匹配。

通过在项目中包含适当的文件,可以从usingOraclize继承合同。 您可以开始使用Oracle,这主要归结为两点:使用oraclize_query帮助器发送请求,然后在__callback函数中处理结果。 最简单的智能合约(以美元获取当前通话时间价格)可能如下所示:

 pragma solidity 0.4.23; import "./usingOraclize.sol"; contract ExampleContract is usingOraclize { string public ETHUSD; event updatedPrice(string price); event newOraclizeQuery(string description); function ExampleContract() payable { updatePrice(); } function __callback(bytes32 myid, string result) { require (msg.sender == oraclize_cbAddress()); ETHUSD = result; updatedPrice(result); } function updatePrice() payable { if (oraclize_getPrice("URL") > this.balance) { newOraclizeQuery("Oraclize query was NOT sent, please add some ETH to cover for the query fee"); } else { newOraclizeQuery("Oraclize query was sent, standing by for the answer.."); oraclize_query("URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd"); } } } 

发送请求的函数是updatePrice 。 您可以看到,首先检查到oraclize_getPrice(“URL”)大于合同的当前余额。 这是因为必须支付oraclize_query调用,价格的计算方式为调用回调的固定佣金和汽油费用之和。 “URL”是数据源类型之一的名称,在这种情况下,它是通过https进行的简单请求,然后我们将考虑其他选项。 可以将请求中的答案预先解析为json(如示例中所示)和其他几种方式(我们将进一步考虑)。 在__callback返回__callback行。 从一开始,就验证了从oraclize可信任地址传递的呼叫

使用oraclize的所有选项都是根据一种方案构建的,只有数据源和向__callback添加身份验证的能力__callback 。 因此,在以后的示例中,我们仅引用重大差异。

使用价格


如前所述,多余的以太币是为oraclize请求支付的,它从合同的余额中删除,而不是从主叫地址中删除。 只有每个新合同的第一个请求都是例外,它是免费提供的。 同样有趣的是,测试网络中保留了相同的机制,但是通过广播相应的网络来付费,也就是说,在测试网中,请求实际上是免费的。

已经提到过,请求价格由两个值组成:固定的佣金和汽油回拨的费用。 固定佣金以美元定义,以太币的数量是根据当前汇率计算的。 该委员会取决于数据源和其他支持机制,我们将在后面详细介绍。 当前的价格表如下所示:


如您所见,每个URL请求的价格为几美分。 是很多还是一点? 为此,让我们考虑一下第二部分的成本-回调气费。
这是按照以下方案进行的:按照合同的要求,预先转移以固定价格支付固定量天然气所需的乙醚量。 该金额应足以进行回调,并且价格应适合市场,否则交易将不会进行或将挂起很长时间。 同时,很明显并非总是能够预先知道气体量,因此,板子必须有边距(不退回边距)。 默认值为20 gwei价格下的20万天然气的限制。 对于具有多个条目和某种逻辑的普通回调而言,这已足够。 而20 gwei的价格,虽然目前看来似乎太大了(在撰写本文时,平均值为4 gwei),但是在交易涌入时,市场价格可能会突然跳升甚至更高,因此通常这些值接近实际使用的价格。 因此,按照这样的价格和500美元左右的空气价格,天然气支付将接近2美元,因此可以说固定佣金只占一小部分。

如果您知道自己在做什么,则可以选择更改天然气的限额和价格,从而大大节省要求。

可以通过单独的函数oraclize_setCustomGasPrice(< wei>)来设置汽油价格。 通话后,价格将保存并用于所有后续请求中。
可以在oraclize_query查询oraclize_query设置该限制,并使用最后一个参数进行指定,例如:

 oraclize_query("URL", "<>", 50000); 

如果您在__callback使用复杂的逻辑并且消耗的天然气超过200k,那么您肯定需要设置一个限制,以涵盖最坏的天然气消耗情况。 否则,如果超出限制,则__callback简单地回滚。

顺便说一句,最近oraclize获得了您可以为区块链外部的请求付费的信息,这将使您不必花费全部限额或退还余额(并且付款不来自合同)。 我们尚未使用此功能,但是如果此选项很有趣,则oraclize可以通过info@oraclize.it与他们联系。 因此,请记住。

如何运作


为什么从常规的智能合约继承下来后,我们得到的功能最初并未受到区块链机制的支持? 实际上,oracle服务不仅包含具有辅助功能的合同。 获取数据的主要工作由外部服务完成。 智能合约构成了用于访问外部数据的应用程序,并将其放置在区块链上。 外部服务-监视区块链的新块,如果检测到应用程序,则执行该应用程序。 可以用以下方式表示:


资料来源


除了所考虑的URL ,oraclize还提供了4个选项(在定价部分中可以看到): WolframAlphaIPFSrandomIPFS 。 让我们考虑它们中的每一个。

1.网址


已经讨论过的示例使用此数据源。 这是对各种API的HTTP请求的来源。 示例如下:

 oraclize_query("URL", "json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd"); 

这正在获取以太币的价格,并且由于api提供了带有数据集的json字符串,因此请求被包装在json解析器中,并且仅返回我们需要的字段。 在这种情况下,它是GET,但是源URL也支持POST请求。 请求的类型由附加参数自动确定。 如果存在有效的json,例如以下示例:

 oraclize_query("URL", "json(https://shapeshift.io/sendamount).success.deposit", '{"pair":"eth_btc","amount":"1","withdrawal":"1AAcCo21EUc1jbocjssSQDzLna9Vem2UN5"}') 

然后将请求作为POST处理(如果感兴趣,请在此处描述所用的api)

2. WolframAlpha


此数据源使您可以访问WolframAlpha服务,该服务可以提供对各种事实或计算请求的答案,例如

 oraclize_query(“WolframAlpha”, “president of Russia”) 

将返回Vladimir Putin ,并要求

 oraclize_query(“WolframAlpha”, “solve x^2-4”) 

将返回x = 2
如您所见,由于±符号丢失,结果不完整。 因此,在使用此源之前,您需要检查特定请求的值是否可以在智能合约中使用。 此外,响应不支持身份验证,因此,oraclize建议将此源仅用于测试。

3. IPFS


您可能会猜到,它允许您使用多重哈希在IPFS中检索文件的内容。 接收内容的超时为20秒。

 oraclize_query(“IPFS”, “QmTL5xNq9PPmwvM1RhxuhiYqoTJcmnaztMz6PQpGxmALkP”) 

将返回Hello, Habr! (如果具有该内容的文件仍然可用)

4.随机


随机数生成的工作方式与其他来源相同,但是如果使用oraclize_query ,则需要花费大量时间来准备参数。 为了避免这种情况,可以使用oraclize_newRandomDSQuery(delay, nbytes, customGasLimit)帮助oraclize_newRandomDSQuery(delay, nbytes, customGasLimit) ,仅设置执行延迟(以秒为单位),生成的字节数以及调用__callback的气体限制。
使用random有两点要记住:

  • 为了确认该数字实际上是随机的,使用了一种特殊的验证方式-Ledger,它可以在区块链上执行(与其他所有人不同,但以后会更多)。 这意味着在智能合约的构造函数中,您需要通过以下函数设置此验证方法:

     oraclize_setProof(proofType_Ledger); 

    在回调的开始,应该进行检查:

      function __callback(bytes32 _queryId, string _result, bytes _proof) { require (oraclize_randomDS_proofVerify__returnCode(_queryId, _result, _proof) == 0) ); <...> 

    此检查需要真实的网络,并且在ganache上不起作用,因此对于本地测试,您可以暂时删除此行。 顺便说一下, __callback的第三个参数是可选的_proof参数。 当使用一种确认类型时,始终需要它。
  • 例如,如果在关键时刻使用随机数来确定彩票的中奖者,请在发送newRandomDSQuery之前捕获用户输入。 否则,可能会发生这种情况:oraclize调用_callback,该事务对于待处理列表中的每个人都是可见的。 与此相关的是,随机数本身也是可见的。 如果用户粗略地说可以继续下注,那么他们将能够指示更高的汽油价格并在执行_callback之前提高其价格,从而提前知道它将获胜。


5.计算


这是最灵活的来源。 它允许您编写自己的脚本并将其用作数据源。 计算在AWS上进行。 为了执行,您需要描述Dockerfile并将其与其他任意文件放到zip存档中,然后将存档下载到IPFS中。 实现必须满足以下条件:

  • 在stdout的最后一行写下您要返回的答案
  • 答案不得超过2500个字符
  • 初始化和执行总共不应超过5分钟

对于如何完成此操作的示例,我们将考虑如何对传输的行执行最简单的合并并返回结果。

Dockerfile:

 FROM ubuntu:16.04 MAINTAINER "info@rubyruby.ru" CMD echo "$ARG0 $ARG1 $ARG2 $ARG3" 

环境变量ARG0ARG1等 -这些是与请求一起传递的参数。
将dockerfile添加到存档中,启动ipfs服务器,然后在此添加存档

 $ zip concatenation.zip Dockerfile $ ipfs daemon & $ ipfs add concatenation.zip QmWbnw4BBFDsh7yTXhZaTGQnPVCNY9ZDuPBoSwB9A4JNJD 

我们使用生成的哈希值通过智能合约中的oraclize_query发送请求:

 oraclize_query("computation", ["QmVAS9TNKGqV49WTEWv55aMCTNyfd4qcGFFfgyz7BYHLdD", "s1", "s2", "s3", "s4"]); 

数组用作参数,其中第一个元素是存档多哈希,其余所有都是属于环境变量的参数。

如果您等待请求完成,则__callback__callback结果s1 s2 s3 s4

解析器帮助器和子查询


从任何来源返回的响应中,您可以使用许多帮助程序仅预选所需的信息,例如:

1. JSON解析器


您在第一个示例中看到了此方法,在该示例中,coinmarketcap返回的结果仅返回了价格:

 json(https://api.coinmarketcap.com/v1/ticker/ethereum/?convert=USD).0.price_usd 

用例非常明显,例如返回:

 [ { "id": "ethereum", "name": "Ethereum", "symbol": "ETH", "rank": "2", "price_usd": "462.857", "price_btc": "0.0621573", "24h_volume_usd": "1993200000.0", "market_cap_usd": "46656433775.0", "available_supply": "100800968.0", "total_supply": "100800968.0", "max_supply": null, "percent_change_1h": "-0.5", "percent_change_24h": "-3.02", "percent_change_7d": "5.93", "last_updated": "1532064934" } ] 

由于这是一个数组,因此我们取元素0 ,并从中取price_usd字段

2. XML


用法类似于JSON,例如:

 xml(https://informer.kovalut.ru/webmaster/getxml.php?kod=7701).Exchange_Rates.Central_Bank_RF.USD.New.Exch_Rate 

3. HTML


您可以使用XPath解析XHTML。 例如,使用etherscan获得市值:

 html(https://etherscan.io/).xpath(string(//*[contains(@href, '/stat/supply')]/font)) 

MARKET CAP OF $46.148 BillionB

4.二进制助手


允许您使用切片(偏移量,长度)功能从原始数据中切割片段。 也就是说,例如,我们有一个文件,其内容为“ abc”:

 echo "abc" > example.bin 

放在IPFS上:

 $ ipfs add example.bin added Qme4u9HfFqYUhH4i34ZFBKi1ZsW7z4MYHtLxScQGndhgKE 

现在从中间切下1个字符:

 binary(Qme4u9HfFqYUhH4i34ZFBKi1ZsW7z4MYHtLxScQGndhgKE).slice(1, 1) 

答案是b

您可能已经注意到,对于二进制帮助程序,不是使用IP源,而是IPFS。 实际上,解析器可以应用于任何来源,比方说,不必将JSON应用于返回URL的内容,您可以将此类内容添加到文件中:

 { "one":"1", "two":"2" } 

将其添加到IPFS:

 $ ipfs add test.json added QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp 

然后像这样反汇编:

 json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).one 

我们得到1

一个特别有趣的用例是在一个请求中组合任何数据源和任何解析器。 使用单独的nested数据源可以做到这一点。 我们使用刚刚在更复杂的请求中创建的文件(在两个字段中添加值):

 [WolframAlpha] add ${[IPFS] json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).one} to ${[IPFS] json(QmZinLwAq5fy4imz8ZNgupWeNFTneUqHjPiTPX9tuR7Vxp).two} 

我们得到3
该请求的格式如下:指定nested数据源,然后为每个请求在方括号中添加源名称,并在${..}添加所有子查询的框架。

测试中


Oraclize无需使用智能合约即可提供有用的查询验证服务 。 只需进入,选择一个数据源,一种验证方法,您就会发现,如果您发送相应的请求,它将返回__callback

对于与智能合约结合的本地验证,可以使用支持oraclize请求的特殊版本的Remix IDE

要使用ganache在本地进行检查,您将需要以太坊桥该桥将oraclize智能合约部署到您的测试网。 为了进行测试,首先将以下行添加到合同的构造函数中:

 OAR = OraclizeAddrResolverI(0x6f485C8BF6fc43eA212E93BBF8ce046C7f1cb475); 

运行

 ganache-cli 

然后

 node bridge --dev 

等待合同失效,您可以进行测试。 在node bridge的输出中node bridge您可以看到发送的请求和收到的响应。

不仅在测试期间而且在实际使用中的另一个帮助是能够在此处监视请求。 如果在公共网络上进行请求,则可以使用执行请求的事务的哈希值。 如果您使用的身份验证,请记住,他们只能保证在mainnet发送,对于其他网络可以得出0。如果请求是本地网络中,可以使用id查询,返回oraclize_query 。 顺便提一下,建议始终保留此ID,例如,在类似的映射中:

 mapping(bytes32=>bool) validIds; 

在请求时,将发送的id标记为true

 bytes32 queryId = oraclize_query(<...>); validIds[queryId] = true; 

然后在__callback检查具有该ID的请求尚未处理:

 function __callback(bytes32 myid, string result) { require(validIds[myid] != bytes32(0)); require(msg.sender == oraclize_cbAddress()); validIds[myid] = bytes32(0); <...> 

这是必要的,因为由于Oraclize机制的特殊性,对一个请求的__callback可以被多次调用。

认证方式


在带有来源的表格中,您会看到不同的来源可以支持不同类型的确认,并且可能收取不同的费用。 这是oraclize的非常重要的部分,但是对这些机制的详细描述是一个单独的主题。

至少我们最常使用的机制是TLSNotary ,其存储在IPFS中。 IPFS中的存储效率更高,因为__callback不会返回证据本身(可能在4-5 KB范围内),而是返回更小的__callback值。 要指定此类型,请在构造函数中添加一行:

 oraclize_setProof(proofType_TLSNotary | proofStorage_IPFS); 

我们只能说,从粗略地说,这种类型可以保护我们免受从Oraclize接收的数据的不准确性。 但是Oraclize使用充当审核员的Amazon服务器,因此它们只需要信任。

在这里阅读更多。

结论


Oraclize提供的工具可显着增加智能合约以及IPFS的用例数量,这可以在多个版本的Oracle查询中看到。 主要问题是,我们再次使用外部数据,这些数据受到区块链应避免的威胁:集中化,阻止功能,代码更改,欺骗。 但这虽然是不可避免的,并且获取数据的选项非常有用且可行,但您只需要了解为什么将区块链的使用引入了项目,以及使用外部不可靠的来源是否会将收益降低为零。

如果您对以太坊上尚未在这些文章中披露的一些开发主题感兴趣,请在评论中写下这些内容,也许我们将在下面进行介绍。

沉浸在以太坊开发中:
第1部分:简介
第2部分:Web3.js和gas
第3部分:用户应用程序
第4部分:在松露,Ganache和Infura中进行部署和调试

Source: https://habr.com/ru/post/zh-CN417681/


All Articles