Skip to main content

Neo Oracle 服务

预言机(Oracle)的出现解决了区块链无法向外部网络获取链外信息的问题,作为智能合约与外部世界通信的网关,Oracle 为区块链打开了一扇通往外部世界的窗户。对于链外请求到的数据,Oracle 会通过多方验证来保证结果的准确性,并将结果以交易的形式上链供合约访问。

Neo Oracle Service 是 Neo N3 内置的链外数据访问服务,它允许用户在智能合约中构建对外部数据源的访问请求,并由委员会指定的可信Oracle节点获取数据后,将其传入回调函数中继续执行相关智能合约逻辑。

img

关键机制#

提交-揭露机制#

提交-揭露机制是一个顺序协议,可以避免多个Oracle节点间的数据抄袭问题。

流程

  1. Oracle节点向其他Oracle节点提交一份有关于数据的密文信息(哈希、签名等)并收集其他Oracle提交的密文信息。

    Neo Oracle Service 使用的是Response交易的多签签名。

  2. 收集到足够的密文信息后,Oracle节点将数据向其他Oracle节点揭露用以验证数据。

    Neo Oracle Service 揭露的是Response交易

由于想要抄袭的Oracle节点无法提前预知数据,所以无法提交密文信息,从而避免了抄袭问题。

请求/响应模式#

Neo Oracle Service采用的是请求/响应模式的处理机制,这是一种异步模型。

流程

  1. 用户通过自定义智能合约调用Oracle合约的Request方法构建Request请求。

    每个成功创建的Request请求会被分配一个请求编号RequestID,并缓存在Request请求缓存列表中。

  2. Oracle节点实时监听Request缓存列表中的访问请求,并根据请求访问相关数据源来获取数据。

  3. 用指定的过滤器对获取的数据进行过滤处理,并将处理后的数据封装成一笔Response交易(包含请求编号RequestID、数据、固定调用脚本、多签地址等)。

    Response交易的 TransactionAttribute 部分会附加Request结果数据。交易中的固定调用脚本用于调用 Oracle 合约的 finish 方法,从而执行回调函数 CallbackMethod

  4. Oracle节点通过提交-揭露机制,与其他Oracle节点一起对Response交易进行多签。

  5. 完成多签的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 请求中需要指定以下字段:

字段字节数描述
Urlstring访问资源路径。最大长度为256字节
Filterstring过滤器,用于在从数据源返回的结果中过滤出有用信息,其中 Filter 字段为 JSONPath 表达式,最大长度为128字节。Oracle 支持的 Filter 规则请参见后文解释。
CallbackMethodstring回调函数方法名。最大长度为32字节,且不能以“_”开头
UserDatavar bytes用户自定义数据
GasForResponselong回调函数执行预付款,用于支付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-IDObject-ID 是强制参数,CommandParams 为可选。

如果没有任何命令,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 回调函数#

回调函数的形参的参数类型和顺序被严格限定,必须遵守以下规则顺序:

字段字节数描述
Urlstring请求的 Url
UserDatavar bytes用户自定义数据
CodebyteOracle 响应状态编码,详情请参见Code表格。
Resultvar bytes结果数据

Code#

Code 字段定义了Oracle 响应的状态编码,包括以下几种类型:

名称说明类型
0x00Success执行成功byte
0x10ProtocolNotSupported不支持的协议byte
0x12ConsensusUnreachable结果共识未达成byte
0x14NotFound请求的信息不存在byte
0x16Timeout请求超时byte
0x18Forbidden请求禁止byte
0x1aResponseTooLarge结果数据大小超限byte
0x1cInsufficientFunds执行费用不足byte
0xffError执行错误byte