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 Request#
Oracle 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构建时自动扣除。 |
Url#
URL请求需提供JSON格式的数据,对于HTTP请求,服务器必须以 “Content-Type: application/ JSON” header 回答请求才能成功。
NeoFS URLs#
NeoFS 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/hashFilter#
这里通过如下 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 | 结果数据 |
Code#
Code 字段定义了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 |