Neo Oracle 服务
预言机(Oracle)的出现解决了区块链无法向外部网络获取链外信息的问题,作为智能合约与外部世界通信的网关,Oracle 为区块链打开了一扇通往外部世界的窗户。对于链外请求到的数据,Oracle 会通过多方验证来保证结果的准确性,并将结果以交易的形式上链供合约访问。
Neo Oracle Service 是 Neo N3 内置的链外数据访问服务,它允许用户在智能合约中构建对外部数据源的访问请求,并由委员会指定的可信Oracle节点获取数据后,将其传入回调函数中继续执行相关智能合约逻辑。
#
关键机制#
提交-揭露机制提交-揭露机制是一个顺序协议,可以避免多个Oracle节点间的数据抄袭问题。
流程
Oracle节点向其他Oracle节点提交一份有关于数据的密文信息(哈希、签名等)并收集其他Oracle提交的密文信息。
Neo Oracle Service 使用的是Response交易的多签签名。
收集到足够的密文信息后,Oracle节点将数据向其他Oracle节点揭露用以验证数据。
Neo Oracle Service 揭露的是Response交易
由于想要抄袭的Oracle节点无法提前预知数据,所以无法提交密文信息,从而避免了抄袭问题。
#
请求/响应模式Neo Oracle Service采用的是请求/响应模式的处理机制,这是一种异步模型。
流程
用户通过自定义智能合约调用Oracle合约的Request方法构建Request请求。
每个成功创建的Request请求会被分配一个请求编号RequestID,并缓存在Request请求缓存列表中。
Oracle节点实时监听Request缓存列表中的访问请求,并根据请求访问相关数据源来获取数据。
用指定的过滤器对获取的数据进行过滤处理,并将处理后的数据封装成一笔Response交易(包含请求编号RequestID、数据、固定调用脚本、多签地址等)。
Response交易的 TransactionAttribute 部分会附加Request结果数据。交易中的固定调用脚本用于调用 Oracle 合约的
finish
方法,从而执行回调函数CallbackMethod
。Oracle节点通过提交-揭露机制,与其他Oracle节点一起对Response交易进行多签。
完成多签的Response交易会被上链,并执行相关回调函数的逻辑。
#
协议支持- https://
- neofs:
#
收费和激励收费模型
用户在使用Neo Oracle Service时需按照Request请求次数进行付费,每个请求的收费单价默认是 0.5 GAS。同时用户还需要支付一笔费用作为回调函数的预付款。这些款项在构建Request就会被扣除。
激励模型
用户请求Request时支付的费用会在区块后置持久化
PostPersist
时依次分发给Oracle节点。分发顺序=RequestID%Oracle个数
#
合约示例以下是一个调用Oracle 服务的合约示例:
using Neo.SmartContract;using Neo.SmartContract.Framework;using Neo.SmartContract.Framework.Native;using Neo.SmartContract.Framework.Services;using System.ComponentModel;
namespace demo{ [DisplayName("Oracle Demo")] [ManifestExtra("Author", "Neo")] [ManifestExtra("Email", "dev@neo.org")] [ManifestExtra("Description", "This is a Oracle using template")] public class OracleDemo : SmartContract { static readonly string PreData = "RequstData";
public static string GetRequstData() { return Storage.Get(Storage.CurrentContext, PreData); }
public static void CreateRequest(string url, string filter, string callback, byte[] userData, long gasForResponse) { Oracle.Request(url, filter, callback, userData, gasForResponse); }
public static void Callback(string url, byte[] userData, int code, byte[] result) { if (Runtime.CallingScriptHash != Oracle.Hash) throw new Exception("Unauthorized!"); Storage.Put(Storage.CurrentContext, PreData, result.ToByteString()); } }}
如上例所示,该合约包含两个关键函数:
- Request创建函数:用于创建 Oracle Request 请求数据
- Callback回调函数:用于Oracle节点获取数据后执行合约逻辑
#
Oracle RequestOracle Request 请求中需要指定以下字段:
字段 | 字节数 | 描述 |
---|---|---|
Url | string | 访问资源路径。最大长度为256字节 |
Filter | string | 过滤器,用于在从数据源返回的结果中过滤出有用信息,其中 Filter 字段为 JSONPath 表达式,最大长度为128字节。Oracle 支持的 Filter 规则请参见后文解释。 |
CallbackMethod | string | 回调函数方法名。最大长度为32字节,且不能以“_”开头 |
UserData | var bytes | 用户自定义数据 |
GasForResponse | long | 回调函数执行预付款,用于支付Response交易执行所需的费用。预付款应不小于 0.1 GAS,否则无法发起 Oracle 请求。预付款会在Request构建时自动扣除。 |
#
UrlURL请求需提供JSON格式的数据,对于HTTP请求,服务器必须以 “Content-Type: application/ JSON” header 回答请求才能成功。
#
NeoFS URLsNeoFS URLs 格式如下:
Copyneofs://<Container-ID>/<Object-ID/<Command>/<Params>
其中 Container-ID
和 Object-ID
是强制参数,Command
和 Params
为可选。
如果没有任何命令,oracle子系统将获得一个对象并返回其负载,例如:
neofs://C3swfg8MiMJ9bXbeFG6dWJTCoHp9hAEZkHezvbSwK1Cc/3nQH1L8u3eM9jt2mZCs6MyjzdjerdSzBkXCYYj4M4Znk
使用命令 range
可以获取对象的部分有效载荷,其参数为 "offset|length",其中 "offset "是指从有效载荷开始跳过的字节数,"length "是指返回给调用者的字节数。
请求示例:
neofs://C3swfg8MiMJ9bXbeFG6dWJTCoHp9hAEZkHezvbSwK1Cc/3nQH1L8u3eM9jt2mZCs6MyjzdjerdSzBkXCYYj4M4Znk/range/42|128
使用命令 header
可以获取对象的 header。该命令没有参数,如下所示:
neofs://C3swfg8MiMJ9bXbeFG6dWJTCoHp9hAEZkHezvbSwK1Cc/3nQH1L8u3eM9jt2mZCs6MyjzdjerdSzBkXCYYj4M4Znk/header
使用命令 hash
可以获取对象的 SHA256 hash 或部分。该命令有一个可选的范围参数,其语法与 range
命令相同。如下所示:
neofs://C3swfg8MiMJ9bXbeFG6dWJTCoHp9hAEZkHezvbSwK1Cc/3nQH1L8u3eM9jt2mZCs6MyjzdjerdSzBkXCYYj4M4Znk/hash
#
Filter这里通过如下 Json 示例来说明 Oracle 支持的 Filter 规则。
{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10, "data": null}
Filter | 返回结果 |
---|---|
$.store.book[*].author | 所有图书的作者 |
$..author | 所有作者 |
$.store.* | 所有信息,包括书和自行车 |
$.store..price | 所有东西的价格 |
$..book[2] | 第三本书 |
$..book[-2] | 倒数第二本书 |
$..book[0,1] | 前两本书 |
$..book[:2] | 从索引0(包含)到索引2(不包含)的所有图书 |
$..book[1:2] | 从索引1(包含)到索引2(不包含)的所有图书 |
$..book[-2:] | 最后两本书 |
$..book[2:] | 从尾部开始的第二本书 |
$..book[?(@.isbn)] | 无效 Filter |
$.store.book[?(@.price < 10)] | 无效 Filter |
$..book[?(@.price <= $['expensive'])] | 无效 Filter |
$..book[?(@.author =~ /.*REES/i)] | 无效 Filter |
$..book[(@.length-1)] | 无效 Filter |
$..* | 无效 Filter |
$.. | 无效 Filter |
$.* | 所有 store 数据和 expensive 数据 |
empty string | 源json数据 |
返回结果可在 https://github.com/json-path/JsonPath 查询获得。
#
Callback 回调函数回调函数的形参的参数类型和顺序被严格限定,必须遵守以下规则顺序:
字段 | 字节数 | 描述 |
---|---|---|
Url | string | 请求的 Url |
UserData | var bytes | 用户自定义数据 |
Code | byte | Oracle 响应状态编码,详情请参见Code表格。 |
Result | var bytes | 结果数据 |
#
CodeCode 字段定义了Oracle 响应的状态编码,包括以下几种类型:
值 | 名称 | 说明 | 类型 |
---|---|---|---|
0x00 | Success | 执行成功 | byte |
0x10 | ProtocolNotSupported | 不支持的协议 | byte |
0x12 | ConsensusUnreachable | 结果共识未达成 | byte |
0x14 | NotFound | 请求的信息不存在 | byte |
0x16 | Timeout | 请求超时 | byte |
0x18 | Forbidden | 请求禁止 | byte |
0x1a | ResponseTooLarge | 结果数据大小超限 | byte |
0x1c | InsufficientFunds | 执行费用不足 | byte |
0xff | Error | 执行错误 | byte |