# 预言机服务技术方案 ## 概述 `长安链·预言机`v1.0.0_alpha版本是一种中心化的可分布式部署的预言机,为长安链链上、链下数据的交互提供了一个桥梁。用户可以通过智能合约调用来获取链下的接口数据、指定的数据库数据、VRF(可验证随机数数据)以及借助智能合约做跨链数据的查询,同时也提供了注册查询topic/按照topic查询的功能以简化用户使用。 ## 设计 ### 整体流程 - 用户在智能合约中调用`预言机智能合约`中的取数据方法,`预言机智能合约`校验相应参数,发送相应的链事件 - `预言机服务`监听到链上取数据事件,根据合约中指定的取数据配置,进行数据的获取 - `预言机服务`将取到数据通过调用`预言机智能合约`方法来将数据回传,`预言机智能合约`通过回调用户智能合约告知用户数据取到,用户合约做相应业务处理即可 ![预言机整体流程](../images/oracle-flow.png)
预言机整体流程
### 预言机智能合约设计 - 预言机功能由`预言机智能合约`提供接口的方式来实现。`用户智能合约`通过调用接口来获取数据。为方便用户使用,系统提供了复杂查询的订制化功能,可以由`管理员`来进行查询的订制,简化普通用户的查询。 ![预言机智能合约交互](../images/oracle-contract-interact.png)
预言机智能合约交互
`管理员`即为是预言机合约安装人员,如下图为预言机智能合约的鉴权方案 ![预言机智能合约的鉴权方案](../images/oracle-contract-rbac.png)
预言机智能合约的鉴权方案
### 预言机服务设计 `预言机服务`整体可以分为四个模块。由监听到的链上`预言机智能合约`事件进行驱动,各个模块配合运行完成取数据功能。服务将运行中所需数据保存到本地的`mysql`数据库中。 - 预言机状态流转模块 - 取数据模块 - 预言机接口模块 - 预言机告警模块 ![预言机Server结构](../images/oracle-server-arch.png)
预言机Server结构
#### 预言机表设计 `预言机服务`将服务运行所需数据存储到了`mysql`数据库中。 - `合约表(oracle_contracts)`主要记录通过接口安装的预言机合约信息。合约的安装、升级都会写入到表中。`预言机服务`监听链上的事件需要提供序号,因此在`预言机服务`处理完相关事件后,也会将事件序号的变化更新到表中 ```sql CREATE TABLE if NOT exists `oracle_contracts`( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id', `chain_alias` varchar(128) NOT NULL COMMENT '链别名', `chain_id` varchar(128) NOT NULL COMMENT '链id', `contract_name` varchar(128) NOT NULL COMMENT '合约名称', `contract_version` int NOT NULL DEFAULT '1' COMMENT '合约版本', `deployed` tinyint NOT NULL DEFAULT '0' COMMENT '是否部署过,默认为0未部署,1为已经部署', `runtime_type` tinyint NOT NULL DEFAULT '6' COMMENT '1 为系统合约;2 为rust; 3 为c++; 4为tinygo; 5为solidity; 6为go', `topic_query_start` bigint unsigned NOT NULL COMMENT '订阅“查询事件”的起始位置', `topic_finish_start` bigint unsigned NOT NULL COMMENT '订阅“结束事件”的起始位置', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_c_c` (`chain_alias`,`contract_name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='合约情况表格'; ``` - `事件状态表(event_state)`记录了从链上监听事件到整个事件流转完整的各个阶段 ```sql CREATE TABLE if NOT exists `event_state`( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id', `chain_alias` varchar(128) NOT NULL, `request_id` varchar(256) NOT NULL COMMENT '请求id', `tx_id` varchar(128) NOT NULL COMMENT '交易id', `contract_name` varchar(128) NOT NULL COMMENT '合约名称', `state` tinyint NOT NULL DEFAULT '0' COMMENT '0:StateInit ;1:StateFetchDataFailed ; 2: StateFetchDataSucess ; 3: StateCallbackFailed ; 4:StateCallbackSuccess ; 5:StateResultOnChainSuccess(终止态) ;6:StateCallbackFailedFibnoacci(多次调链失败终止态)', `query_type` tinyint NOT NULL DEFAULT '1' COMMENT '查询类型枚举 1:use_http;2:use_db ', `query_body` mediumblob NOT NULL COMMENT '具体的查询方案,暂定为序列化后的json字符串', `next_fibonacci` bigint unsigned NOT NULL DEFAULT '0' COMMENT '上链失败时候feibonacci重试记录', `block_height` bigint unsigned NOT NULL DEFAULT '1' COMMENT '当前事件的区块高度', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uniq_s_c_r` (`request_id`) COMMENT '''请求id唯一索引''', KEY `updated_state` (`updated_at`,`state`), KEY `created_state` (`created_at`,`state`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='事件状态表格' ; ``` - `预言机取数据返回值存储表(event_result)`存储了预言机取到的数据结果 ```sql CREATE TABLE if NOT exists `event_result` ( `request_id` varchar(256) NOT NULL COMMENT '请求id,唯一索引', `event_result` mediumblob NOT NULL COMMENT '取到的数据', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '链id', PRIMARY KEY (`request_id`), KEY `created_at` (`created_at`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='预言机取数据返回值存储表' ; ``` - `事件锁表(event_lock)`用于协调多个`预言机服务实例`进行工作 ```sql CREATE TABLE if NOT exists `event_lock` ( `request_id` varchar(256) NOT NULL COMMENT '请求id', `locked_version` int NOT NULL DEFAULT '0' COMMENT '锁', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时记录', PRIMARY KEY (`request_id`) COMMENT '请求id唯一索引', KEY `updated_at` (`updated_at`), KEY `request_updated` (`request_id`,`updated_at`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='事件锁' ; ``` - `vrf表格(vrfs)`记录了vrf的公钥和私钥信息,其中srv_name为唯一索引(系统写的)。多个实例如果连接到同一个mysql实例,那么他们共享同一个vrf公私钥,各个实例生成vrf是一致的 ```sql CREATE TABLE if NOT exists `vrfs`( `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `privatek` blob NOT NULL COMMENT '私钥', `publick` blob NOT NULL COMMENT '公钥', `srv_name` varchar(128) NOT NULL COMMENT '服务名称,唯一索引', PRIMARY KEY (`id`), UNIQUE KEY `uniq-s` (`srv_name`) COMMENT '利用服务名称作唯一索引' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='vrf表格'; ``` #### 预言机状态流转模块 `预言机服务`监听事件,取数据,回传数据到链上整个流程抽象成为一个有限状态机来进行实现,状态迁移如下图 ![预言机内部状态流转](../images/oracle-fsm.png)
预言机内部状态流转
#### 取数据模块 `预言机服务`可以支持四种取数据模式。通过解析指定的查询需求,实例化相应的接口实现类取数据 - 支持通过http接口取xml,json,html类型数据 - 支持通过mysql数据源取数据 - 支持跨链(长安链)调用链上合约查询数据 - 支持vrf数据取/验 ![取数据模块](../images/oracle-fetch-data.png)
取数据模块
#### 预言机接口模块 `预言机服务`的接口模块提供两种类型的接口。两类接口分别都有`token`验证(token为服务配置中的对应项) - 安装、升级预言机合约接口`token配置项目为smart_oracle.yml文件中httpServer对象的admin_token` - 辅助开发接口,用于查看通过预言机可以取的数据样式(不借助智能合约,方便开发测试)`token配置项目为smart_oracle.yml文件中httpServer对象的assist_token` ![api接口模块](../images/oracle-apis.png)
api接口模块
#### 预言机告警模块 `预言机服务`在将数据通过调用`预言机合约`回调链的时候可能会发生错误,我们依据fibonacci重试的策略进行重试,当重试次数超过用户配置的最大值(配置项为fibonacci_limits)时候,会根据用户配置(配置项为alarmer)的报警方式进行报警 ![告警模块](../images/oracle-alarmer.png)
告警模块
## 代码整体目录结构 ```bash . ├── alarms # 告警模块的代码包 ├── config # 读取配置模块的代码包 ├── config_files # 配置文件 │ ├── crypto-config # 使用chainmaker-cryptogen 工具生成的证书文件 │ ├── sdk-config # 链sdk配置 │ │ ├── sdk_config_chain1.yml # 链1的sdk │ │ └── sdk_config_chain2.yml # 链2的sdk │ └── smart_oracle.yml # 预言机服务的配置文件 ├── contract_process # 预言机服务核心业务逻辑的代码包 ├── custom.cnf # 可以参考这个配置来配置docker中的mysql数据库(5.7 及以后) ├── databases # 连接数据库模块的代码包 ├── docker-compose.yml # docker-compose 模板文件 ├── Dockerfile # docker 镜像文件 ├── fetch_data # 取数据模块代码包 ├── http_server_process # 预言机http服务接口代码包 ├── install-upgrade-oracle-contract.sh #预言机合约安装、升级脚本 ├── logger # 日志模块代码包 ├── main.go # 服务入口 ├── Makefile ├── models # 预言机数据模型包 ├── mysql-data-volumes # 默认的docker-compose启动mysql数据库的数据挂载点 │ └── data ├── readme.md ├── scripts # mysql建库脚本 │ └── init.sql ├── standard_oracle_contract # 系统自带的合约文件及代码 │ ├── oracle_contract_file │ │ └── oracle_contract_v1 # 编译过的预言机合约可执行文件 │ ├── oracle_contract_src.zip # 预言机合约源代码 │ └── use_demo_src.zip # 用户使用预言机合约的示例源代码 │ └── use_demo.7z # 编译压缩后的示例智能合约文件 └── ut_cover.sh # 单元测试脚本 ```