2. 预言机工具

2.1. 安装及配置

2.1.1. 服务依赖

2.1.1.1. 操作系统

预言机合约运行在Docker VM中,当前仅支持部署在Linux系统

2.1.1.2. docker方式

  • git

  • docker 18+

  • docker-compose 1.29.2

2.1.1.3. 源码方式

  • Mysql 5.7+

  • golang 1.16+

2.1.2. 源码下载

  • 下载代码

$ git clone -b v1.0.0_beta  --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-oracle

2.1.3. 服务配置

2.1.3.1. 整体配置目录结构如下:

├── config_files # 配置文件   ├── crypto-config # 使用chainmaker-cryptogen 工具生成的证书文件   ├── sdk-config # 链sdk配置      ├── sdk_config_chain1.yml # 链1的sdk      └── sdk_config_chain2.yml # 链2的sdk   └── smart_oracle.yml # 预言机服务的配置文件

2.1.3.2. 预言机配置结构(smart_oracle.yml):

用户需要关注如下配置,详情参考完整的配置文件

# 链证书配置相关
sdks: 

# 预言机数据库配置
oracle_mysql:

# 预言机提供的mysql数据源
datasource_mysql:
  - datasource: local
    user_name: test
    password: 123456@
    db_address: oracle_db:3306 
    database: chain_oracle
    max_open: 2
    max_idle: 1
    max_idle_seconds: 180
    max_life_seconds: 1800

2.1.4. 快速启动一条docker-vm引擎的链(已有链则跳过)

$ git clone -b v2.3.1 --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-go.git
$ git clone -b v2.2.0  --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-cryptogen.git
$ cd chainmaker-cryptogen && make
$ cd ../chainmaker-go/tools/ && ln -s ../../chainmaker-cryptogen/ .
# 修改下链的grpc配置中消息大小,链的chainmaker.yml文件
$ cd ../config/config_tpl
$ sed -i 's/max_send_msg_size: 10/max_send_msg_size: 200/g' chainmaker.tpl
$ sed -i 's/max_recv_msg_size: 10/max_recv_msg_size: 200/g' chainmaker.tpl
$ cd ../../scripts
$ ./prepare.sh 4 2
	1
	INFO
	YES
$ ./build_release.sh
$ ./cluster_quick_start.sh normal

2.1.5. 运行服务方式一:使用docker-compose启动

  • 若需自定义则修改下方配置信息

    • 修改文件chainmaker-oralce/config_files/smart_oracle.yml

    • 修改文件chainmaker-oralce/config_files/sdk-config/sdk_config_chain1.yml

    • 添加证书chainmaker-oralce/config_files/crypto-config

  • 执行docker-compose up即可启动服务

# 设置证书
$ cd chainmaker-oracle 
$ rm -rf config_files/crypto-config/ && cp -rf ../chainmaker-go/build/crypto-config/ config_files/
# 修改链节点地址为局域网IP
$ vim config_files/sdk-config/sdk_config_chain1.yml
$ vim config_files/sdk-config/sdk_config_chain2.yml

# 启动服务
$ docker-compose up 

# 看到如下日志表示成功启动:
  http service start , port :10123

# 查看服务健康状态
$ curl -X GET localhost:10123/v1/health
$ curl -X GET localhost:10124/v1/health
# 查看日志  
$ docker logs -f --tail 100 chainmaker-oracle-1
$ docker logs -f --tail 100 chainmaker-oracle-2
  • 停止服务

$ docker-compse down 
# 删除mysql中的数据
$ rm -rf ./mysql-data-volumes/data/

2.1.6. 运行服务方式二:使用源码启动

  • 编译:

$ cd chainmaker-oracle && go build -o chainmaker-oracle
  • 初始化数据库: 数据库上执行代码的scripts/init.sql脚本文件

$ mysql -uroot -p < scripts/init.sql 
  • 修改配置信息

    • 修改文件chainmaker-oralce/config_files/smart_oracle.yml

    • 修改文件chainmaker-oralce/config_files/sdk-config/sdk_config_chain1.yml

    • 添加证书chainmaker-oralce/config_files/crypto-config

  • 启动服务

# 直接启动
$ ./chainmaker-oracle 
# 或指定配置文件启动
$ ./chainmaker-oracle -i config_files/smart_oracle.yml 

# 看到如下日志表示成功启动:
  http service start , port :10123
  
# 查看服务健康状态
$ curl -X GET localhost:10123/v1/health

# 查看日志  
$ tail -100 log/system.log 

# 停止服务
$ kill ${pid}

2.1.7. 预言机合约安装

  • 预言机合约的安装是通过http接口的形式给出,可以直接调用接口

# admin_token 为配置中的admin_token
# address 为预言机监听,接收http请求地址
# chain_alias 为配置文件smart_oracle.yml中的chain_alias
# oracle-contract 为预言机合约二进制压缩文件(默认为.7z)
$ curl -H "admin_auth_token: ${admin_token}" -X POST ${address}/v1/install_contract -F "chain_alias=${chain_alias}" -F "contract_version=1" -F "runtime=DOCKER_GO" -F "contract_file=@${oracle-contract}"
# 示例:
$ cd chainmaker-oracle 
$ 7z a oracle_contract_v1.7z standard_oracle_contract/oracle_contract_file/oracle_contract_v1
# 给chain1安装oracle合约
$ curl -H 'admin_auth_token: si!*dfji@12mnku' -X POST localhost:10123/v1/install_contract -F "chain_alias=chain1_alias" -F "contract_version=1" -F "runtime=DOCKER_GO" -F "contract_file=@./oracle_contract_v1.7z"
# 给chain2安装oracle合约
$ curl -H 'admin_auth_token: si!*dfji@12mnku' -X POST localhost:10123/v1/install_contract -F "chain_alias=chain2_alias" -F "contract_version=1" -F "runtime=DOCKER_GO" -F "contract_file=@./oracle_contract_v1.7z"
  • 为了方便使用,提供了install-upgrade-oracle-contract.sh安装脚本,可以按照命令提示也可以安装

$ ./install-upgrade-oracle-contract.sh 

2.1.8. 使用用户合约取数据(以cmc工具为例)

2.1.8.1. 编译CMC

$ git clone --depth=1  hps://git.chainmaker.org.cn/chainmaker/chainmaker-go.git
$ cd chainmaker-go/
$ make cmc
$ cp bin/cmc ../chainmaker-oracle/ && cd ../chainmaker-oracle/

2.1.8.2. 使用cmc工具安装示例用户合约文件

# 链1上安装用户合约
./cmc client contract user create --contract-name=use_demo --runtime-type=DOCKER_GO --byte-code-path=./standard_oracle_contract/use_demo.7z --version=1 --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml  --admin-key-file-paths=./config_files/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.key --admin-crt-file-paths=./config_files/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.crt --sync-result=true --params="{\"chainId\":\"chain1\",\"version\":\"1\"}" --chain-id=chain1
# 链2上安装用户合约
./cmc client contract user create --contract-name=use_demo --runtime-type=DOCKER_GO --byte-code-path=./standard_oracle_contract/use_demo.7z --version=1 --sdk-conf-path=./config_files/sdk-config/sdk_config_chain2.yml  --admin-key-file-paths=./config_files/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.key,./config_files/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.key --admin-crt-file-paths=./config_files/crypto-config/wx-org1.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org2.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org3.chainmaker.org/user/admin1/admin1.tls.crt,./config_files/crypto-config/wx-org4.chainmaker.org/user/admin1/admin1.tls.crt --sync-result=true --params="{\"chainId\":\"chain2\",\"version\":\"1\"}" --chain-id=chain2

2.1.8.3. 使用管理员身份(也即为安装预言机合约相同的签名)注册查询topic,使用普通用户身份根据topic查询

# 注册topic[event_state_query],查询数据源local的表event_state
./cmc client contract user invoke --contract-name=oracle_contract_v1 --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"registerQueryTopic\",\"topic\":\"event_state_query\",\"method_type\":\"queryMysql\",\"query_body\": \"{\\\"sql_sentence\\\":\\\"select * from event_state \\\",\\\"data_source\\\":\\\"local\\\",\\\"sql_type\\\":\\\"select\\\"}\" }" --sync-result=true  --chain-id=chain1   

# 使用topic[event_state_query]查询
./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml  --params="{\"method\":\"query_topic\",\"topic\":\"event_state_query\"}" --sync-result=true  --chain-id=chain1 

2.1.8.4. 取json类型数据

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_http\",\"url\":\"https://v0.yiketianqi.com/api?unescape=1&version=v61&appid=88684831&appsecret=OsSX6jwW\",\"fetch_data_type\":\"json\",\"fetch_data_formular\":\"\/aqi\/no2_desc\",\"max_retry\":\"3\",\"connection_timeout\":\"20\",\"max_fetch_timeout\":\"20\"}" --sync-result=true  --chain-id=chain1

结果:INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”status:200 payload:”{\”result\”:null,\”code\”:\”202\”,\”message\”:\”success\”,\”response_hash\”:\”b1ee8be6f122612f1bfeac335ba1f2f3\”}” “ message:”Success” gas_used:18510 contract_event:<topic:”oracle_query” tx_id:”16fc7e533eabd3d7ca52fdfc0721826594cce7e90d084fc890053e15aa8b479f” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”1” event_data:”chain1_alias” event_data:”b1ee8be6f122612f1bfeac335ba1f2f3” event_data:”16fc7e533eabd3d7ca52fdfc0721826594cce7e90d084fc890053e15aa8b479f” event_data:”7” event_data:”{”method”:”GET”,”url”:”https://v0.yiketianqi.com/api?unescape=1\u0026version=v61\u0026appid=88684831\u0026appsecret=OsSX6jwW”,”http_header”:null,”http_body”:null,”connection_timeout”:30,”max_fetch_timeout”:30,”fetch_data_type”:”json”,”fetch_data_formula”:”/aqi/no2_desc”}” > ]/[txId:16fc7e533eabd3d7ca52fdfc0721826594cce7e90d084fc890053e15aa8b479f]

2.1.8.5. 取html数据

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_http\",\"url\":\"https://www.baidu.com\",\"http_body\":null,\"connection_timeout\":\"5\",\"max_retry\":\"3\",\"max_fetch_timeout\":\"10\",\"fetch_data_type\":\"html\",\"fetch_data_formular\":\"\/\/title\"}" --sync-result=true  --chain-id=chain1

结果:INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”status:200 payload:”{\”result\”:null,\”code\”:\”202\”,\”message\”:\”success\”,\”response_hash\”:\”30a004cdc8b2f4e80876dbed381fd3e7\”}” “ message:”Success” gas_used:19115 contract_event:<topic:”oracle_query” tx_id:”16fc7e5c927ec90fca52fdfc07218265daa02eb765794c77bb2deceebd4a4b05” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”1” event_data:”chain1_alias” event_data:”30a004cdc8b2f4e80876dbed381fd3e7” event_data:”16fc7e5c927ec90fca52fdfc07218265daa02eb765794c77bb2deceebd4a4b05” event_data:”9” event_data:”{”method”:”GET”,”url”:”https://www.baidu.com”,”http_header”:{”User-Agent”:”Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3776.0 Safari/537.36”},”http_body”:null,”connection_timeout”:30,”max_fetch_timeout”:30,”fetch_data_type”:”html”,”fetch_data_formula”:”//title”}” > ]/[txId:16fc7e5c927ec90fca52fdfc07218265daa02eb765794c77bb2deceebd4a4b05]

2.1.8.6. 取xml数据

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_http\",\"url\":\"https://www.w3school.com.cn/example/xmle/note.xml\",\"http_body\":null,\"connection_timeout\":\"5\",\"max_retry\":\"3\",\"max_fetch_timeout\":\"10\",\"fetch_data_type\":\"xml\",\"fetch_data_formular\":\"\/\/note\"}" --sync-result=true  --chain-id=chain1

结果: INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”status:200 payload:”{\”result\”:null,\”code\”:\”202\”,\”message\”:\”success\”,\”response_hash\”:\”4ca4a8cb3f2907eb5dac3f6f673fec0e\”}” “ message:”Success” gas_used:17611 contract_event:<topic:”oracle_query” tx_id:”16fc7e62554560a5ca52fdfc072182656b74385f041e4da8aadf68e78727e06a” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”1” event_data:”chain1_alias” event_data:”4ca4a8cb3f2907eb5dac3f6f673fec0e” event_data:”16fc7e62554560a5ca52fdfc072182656b74385f041e4da8aadf68e78727e06a” event_data:”11” event_data:”{”method”:”GET”,”url”:”https://www.w3school.com.cn/example/xmle/note.xml”,”http_header”:null,”http_body”:null,”connection_timeout”:30,”max_fetch_timeout”:30,”fetch_data_type”:”xml”,”fetch_data_formula”:”//note”}” > ]/[txId:16fc7e62554560a5ca52fdfc072182656b74385f041e4da8aadf68e78727e06a]

2.1.8.7. 取VRF数据

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"get_vrf\",\"alpha\":\"f12vvio\"}" --sync-result=true  --chain-id=chain1

结果:INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”status:200 payload:”{\”result\”:null,\”code\”:\”202\”,\”message\”:\”success\”,\”response_hash\”:\”dce88bc54c62200bfbdc24f755535d54\”}” “ message:”Success” gas_used:14431 contract_event:<topic:”oracle_query” tx_id:”16fc7e662a606536ca52fdfc07218265bf42b16723af4d7db03dec55a11dea46” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”3” event_data:”chain1_alias” event_data:”dce88bc54c62200bfbdc24f755535d54” event_data:”16fc7e662a606536ca52fdfc07218265bf42b16723af4d7db03dec55a11dea46” event_data:”13” event_data:”f12vvio” > ]/[txId:16fc7e662a606536ca52fdfc07218265bf42b16723af4d7db03dec55a11dea46]

2.1.8.8. 取mysql数据

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_mysql\",\"sql_type\":\"select\",\"page_num\":\"1\",\"page_size\":\"10\",\"sql_sentence\":\"select * from event_result \",\"data_source\":\"mysql\"}" --sync-result=true  --chain-id=chain1

INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”{”result”:null,”code”:”202”,”message”:”success”,”response_hash”:”3896ca045ed94ff9c3d5add90f206b0b”}” message:”Success” gas_used:15706 contract_event:<topic:”oracle_query” tx_id:”16fc7e6902f78cafca52fdfc07218265dc4d346d146b4b08a69f088f842d5585” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”2” event_data:”chain1_alias” event_data:”3896ca045ed94ff9c3d5add90f206b0b” event_data:”16fc7e6902f78cafca52fdfc07218265dc4d346d146b4b08a69f088f842d5585” event_data:”15” event_data:”{”sql_type”:”select”,”sql_sentence”:”select * from event_result “,”data_source”:”mysql”}” > ]/[txId:16fc7e6902f78cafca52fdfc07218265dc4d346d146b4b08a69f088f842d5585]

2.1.8.9. 使用hash查询数据(注意hash为预言机合约返回给用户合约的hash值)

从上方结果中任意取一个response_hash填充到query_hash中。

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_hash\",\"query_hash\":\"3896ca045ed94ff9c3d5add90f206b0b\"}" --sync-result=true  --chain-id=chain1

结果:INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”{”result”:null,”code”:”201”,”message”:”query success, get no result”,”response_hash”:””}” message:”Success” gas_used:10259 ]/[txId:16fc7e6e0343ce7fca52fdfc07218265e1778052f320478c939d3c86a8c57cad]

2.1.8.10. 跨链查询数据(注意hash为预言机合约返回给用户合约的hash值)

示例中从上方结果中任意取一个response_hash填充到query_hash中(此为用户合约行为,可自定义)。

./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_hash\",\"query_hash\":\"3896ca045ed94ff9c3d5add90f206b0b\"}" --sync-result=true  --chain-id=chain1
# 注意query_hash 需要为查询返回的值
./cmc client contract user invoke --contract-name=use_demo --method=invoke_contract --sdk-conf-path=./config_files/sdk-config/sdk_config_chain1.yml --params="{\"method\":\"query_cross\",\"chain_alias\":\"chain1_alias\",\"contract_name\":\"use_demo\",\"method_name\":\"query_hash\", \"query_hash\":\"conf\"}" --sync-result=true  --chain-id=chain2

结果:INVOKE contract resp, [code:0]/[msg:]/[contractResult:result:”QueryCross ok” message:”Success” gas_used:16445 contract_event:<topic:”oracle_query” tx_id:”16fc7e8add879fd8ca52fdfc0721826578760f8515c049599c29e3cadc3a382e” contract_name:”oracle_contract_v1” contract_version:”1” event_data:”5” event_data:”chain2_alias” event_data:”55193d721576a16e21b2a110d7f11bd3” event_data:”16fc7e8add879fd8ca52fdfc0721826578760f8515c049599c29e3cadc3a382e” event_data:”6” event_data:”{”chain_alias”:”chain1_alias”,”contract_name”:”use_demo”,”method_name”:”query_hash”,”params”:[{”key”:”query_hash”,”value”:”Y29uZg==”}]}” > ]/[txId:16fc7e8add879fd8ca52fdfc0721826578760f8515c049599c29e3cadc3a382e]

2.1.9. 代码整体目录结构

.
├── 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 # 单元测试脚本

2.2. 使用及示例

2.2.1. 通过开发智能合约来操作预言机取数据

预言机通过在链上部署预言机智能合约oracle_contract,来给用户提供取数据功能。用户可以通过调用oracle_contract相应接口即可。由于取数据是一个不确定运行时长的过程,因此我们采用的是调用->事件->回调的方式。用户合约告知预言机需要取的数据类型,预言机取到数据通过用户合约注册的回调函数来告知用户合约

2.2.1.1. 用户合约的安装,安装时候需要查询一下预言机合约的公钥签名,以便后续校验数据

func (o *UseDemo) getOralceContractPk() (string, error) {
	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryOracleContractPK")
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryOracleContractPK", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("getOralceContractPk error," + resp.String())
		return "", errors.New(resp.String())
	}
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("getOralceContractPk unmarshal error")
		return "", errors.New("getOralceContractPk unmarshal error" + uerr.Error())
	}
	return string(responeT.ResultB), nil
}

func (o *UseDemo) InitContract() protogo.Response {
	// 首先获取一下预言机合约的公钥签名,缓存一下,后续做callback的时候可以校验
	oraclePk, oraclePkErr := o.getOralceContractPk()
	if oraclePkErr != nil {
		return sdk.Error(oraclePkErr.Error())
	}
	storeErr := sdk.Instance.PutStateFromKey(gOracleContractPkStr, oraclePk)
	if storeErr != nil {
		sdk.Instance.Errorf("InitContract store oracle_contract_pk error")
		return sdk.Error(storeErr.Error())
	}

	version := string(sdk.Instance.GetArgs()["version"])
	if strings.TrimSpace(version) == "" {
		version = "1"
	}
	err := sdk.Instance.PutStateFromKey("version", version)
	if err != nil {
		sdk.Instance.Errorf("InitContract fail to save version")
		return sdk.Error("fail to save version")
	}
	sdk.Instance.Infof("InitContract version(%s) ", version)
	return sdk.Success([]byte("Init Success"))
}

func (o *UseDemo) UpgradeContract() protogo.Response {
	// 首先获取一下预言机合约的公钥签名,缓存一下,后续做callback的时候可以校验
	oraclePk, oraclePkErr := o.getOralceContractPk()
	if oraclePkErr != nil {
		return sdk.Error(oraclePkErr.Error())
	}
	storeErr := sdk.Instance.PutStateFromKey(gOracleContractPkStr, oraclePk)
	if storeErr != nil {
		sdk.Instance.Errorf("UpgradeContract store oracle_contract_pk error")
		return sdk.Error(storeErr.Error())
	}

	version := string(sdk.Instance.GetArgs()["version"])
	if strings.TrimSpace(version) == "" {
		version = "1"
	}
	err := sdk.Instance.PutStateFromKey("version", version)
	if err != nil {
		sdk.Instance.Errorf("UpgradeContract fail to save version")
		return sdk.Error("fail to save version")
	}
	sdk.Instance.Infof("UpgradeContract version(%s) ", version)
	return sdk.Success([]byte("UpgradeContract Success"))
}

2.2.1.2. 取VRF功能开发

调用参数

	parameters := make(map[string][]byte)
	parameters["alpha"] = []byte(alpha) //用户输入信息,来计算随机数使用
	parameters["method"] = []byte("get_vrf") //调用预言机的vrf功能
	parameters["original_contract_name"] = []byte("use_demo") //用户自己合约的名称
	parameters["original_contract_version"] = []byte(version) //用户自己合约的版本

返回参数:

type OracleQueryResult struct {
	ResultB      []byte `json:"result"`        //查询结果的json序列化
	Code         string `json:"code"`          //错误码
	Message      string `json:"message"`       //错误信息
	ResponseHash string `json:"response_hash"` //返回唯一hash值
}

取数据回调:

	args["result"]              //调用结果数据
	args["response_hash"]  //调用的唯一索引值,与上文的返回参数唯一索引值一样
	args["code"]           //错误码
	args["message"]    //错误信息

其中result中即为所取到的VRF信息,其为如下结构体的json序类化字符串

type Random struct {
	RandData string  `json:"rand_data"` //32 byte的bigint
	Pi       []byte  `json:"pi"` //证据
	Ratio    float64 `json:"ratio"` //[0,1]之间的随机数
}

2.2.1.3. 取接口数据功能开发(xpath语法)

调用参数

type ModelHttp struct {
	Method     string            `json:"method"` //GET
	URL        string            `json:"url"`
	HttpHeader map[string]string `json:"http_header"`
	HttpBody   []byte            `json:"http_body"`
	ConnectionTimeout int `json:"connection_timeout"` //最大连接超时时长,默认30-60s,单位秒级别
	MaxFetchTimeout   int `json:"max_fetch_timeout"`  //最大读取超时时长,默认10s,可以选择为10-60s
	FetchDataType    string `json:"fetch_data_type"`    //json,xml,html
	FetchDataFormula string `json:"fetch_data_formula"` //默认为空,为空则不解析,原样返回。如果非空,则按照xpath来分隔取出来结构化的数据
}


	parameters["http_query"] = httpBs // ModelHttp 序列化后的字符串
	parameters["method"] = []byte("queryHttp") //调用预言机的接口数据功能
	parameters["original_contract_name"] = []byte("use_demo") //用户自己合约的名称
	parameters["original_contract_version"] = []byte(version) //用户自己合约的版本
	parameters["use_chaindata"] = []byte(useCache) //如果链上已经有相同的查询参数做的查询,是否使用链上已有数据返回
	parameters["is_persistence"] = []byte(isPersist) //查询结果是否保存到链上,便于后面可以根据查询参数做查询

2.2.1.4. 取mysql数据功能开发

调用参数:

type ModelSql struct {
	SqlType     string `json:"sql_type"` //select
	SqlSentence string `json:"sql_sentence"`// sql语句
	DataSource  string `json:"data_source"` //数据源
}
	parameters["mysql_query"] = sqlBs // ModelSql 序列化后的字符串
	parameters["method"] = []byte("queryMysql") //调用预言机的查询mysql数据功能
	parameters["original_contract_name"] = []byte("use_demo") //用户自己合约的名称
	parameters["original_contract_version"] = []byte(version) //用户自己合约的版本
	parameters["use_chaindata"] = []byte(useCache) //如果链上已经有相同的查询参数做的查询,是否使用链上已有数据返回
	parameters["is_persistence"] = []byte(isPersist) //查询结果是否保存到链上,便于后面可以根据查询参数做查询	

2.2.1.5. 根据hash来查询数据功能开发(这个是个同步方法,可以直接调用预言机合约直接返回)

调用参数:

	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryResultByHash")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["query_hash"] = []byte(qHash) //hash值	

2.2.1.6. 跨链功能的开发

调用参数:

type CrossChainQuery struct {
	ChainAlias   string         `json:"chain_alias"`   //调用的链id
	ContractName string         `json:"contract_name"` //链上合约名称
	MethodName   string         `json:"method_name"`   //合约的方法
	Params       []KeyValuePair `json:"params"`        //合约所需要参数
}

	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryCrossChain")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["cross_query"] = queryMBS //queryMBS为json序列化后的结果
	parameters["use_chaindata"] = []byte(useCache) //如果链上已经有相同的查询参数做的查询,是否使用链上已有数据返回
	parameters["is_persistence"] = []byte(isPersist) //查询结果是否保存到链上,便于后面可以根据查询参数做查询	

2.2.1.7. 根据指定topic查询数据的开发

调用参数:

	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryDataByTopic")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["topic"] = topic
	parameters["use_chaindata"] = []byte(useCache) //如果链上已经有相同的查询参数做的查询,是否使用链上已有数据返回
	parameters["is_persistence"] = []byte(isPersist) //查询结果是否保存到链上,便于后面可以根据查询参数做查询	

2.2.2. 一个完整的demo合约代码参考

/*
Copyright (C) BABEC. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"encoding/json"
	"errors"
	"strings"

	"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sandbox"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sdk"
)

const (
	gOracleContractPkStr = "oracle_contract_pk"
)

type Random struct {
	RandData string  `json:"rand_data"`
	Pi       []byte  `json:"pi"`
	Ratio    float64 `json:"ratio"`
}

type ModelHttp struct {
	Method     string            `json:"method"` //GET,POST
	URL        string            `json:"url"`
	HttpHeader map[string]string `json:"http_header"`
	HttpBody   []byte            `json:"http_body"`

	ConnectionTimeout int `json:"connection_timeout"` //最大连接超时时长,默认30-60s,单位秒级别
	MaxFetchTimeout   int `json:"max_fetch_timeout"`  //最大读取超时时长,默认10s,可以选择为10-60s

	FetchDataType    string `json:"fetch_data_type"`    //json,xml,html
	FetchDataFormula string `json:"fetch_data_formula"` //默认为空,为空则不解析,原样返回。如果非空,则按照. 来分隔取出来结构化的数据,https://goessner.net/articles/JsonPath/(json)
}

type ModelSql struct {
	SqlType     string `json:"sql_type"` //select ,update , insert ,delete
	SqlSentence string `json:"sql_sentence"`
	DataSource  string `json:"data_source"` //数据源
}

type CrossChainQuery struct {
	ChainAlias   string         `json:"chain_alias"`   //调用的链id
	ContractName string         `json:"contract_name"` //链上合约名称
	MethodName   string         `json:"method_name"`   //合约的方法
	Params       []KeyValuePair `json:"params"`        //合约所需要参数
}

type KeyValuePair struct {
	Key   string `json:"key"`
	Value []byte `json:"value"`
}
type UseDemo struct {
}

type OracleQueryResult struct {
	ResultB      []byte `json:"result"`        //查询结果的json序列化
	Code         string `json:"code"`          //错误码
	Message      string `json:"message"`       //错误信息
	ResponseHash string `json:"response_hash"` //返回唯一hash值
}

func (o *UseDemo) Name() protogo.Response {
	return sdk.Success([]byte("use_demo"))
}

func (o *UseDemo) Version() protogo.Response {
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	return sdk.Success([]byte(version))
}

func (o *UseDemo) getOralceContractPk() (string, error) {
	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryOracleContractPK")
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryOracleContractPK", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("getOralceContractPk error," + resp.String())
		return "", errors.New(resp.String())
	}
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("getOralceContractPk unmarshal error")
		return "", errors.New("getOralceContractPk unmarshal error" + uerr.Error())
	}
	return string(responeT.ResultB), nil
}

func (o *UseDemo) InitContract() protogo.Response {
	// 首先获取一下预言机合约的公钥签名,缓存一下,后续做callback的时候可以校验
	oraclePk, oraclePkErr := o.getOralceContractPk()
	if oraclePkErr != nil {
		return sdk.Error(oraclePkErr.Error())
	}
	storeErr := sdk.Instance.PutStateFromKey(gOracleContractPkStr, oraclePk)
	if storeErr != nil {
		sdk.Instance.Errorf("InitContract store oracle_contract_pk error")
		return sdk.Error(storeErr.Error())
	}

	version := string(sdk.Instance.GetArgs()["version"])
	if strings.TrimSpace(version) == "" {
		version = "1"
	}
	err := sdk.Instance.PutStateFromKey("version", version)
	if err != nil {
		sdk.Instance.Errorf("InitContract fail to save version")
		return sdk.Error("fail to save version")
	}
	sdk.Instance.Infof("InitContract version(%s) ", version)
	return sdk.Success([]byte("Init Success"))
}

func (o *UseDemo) UpgradeContract() protogo.Response {
	// 首先获取一下预言机合约的公钥签名,缓存一下,后续做callback的时候可以校验
	oraclePk, oraclePkErr := o.getOralceContractPk()
	if oraclePkErr != nil {
		return sdk.Error(oraclePkErr.Error())
	}
	storeErr := sdk.Instance.PutStateFromKey(gOracleContractPkStr, oraclePk)
	if storeErr != nil {
		sdk.Instance.Errorf("UpgradeContract store oracle_contract_pk error")
		return sdk.Error(storeErr.Error())
	}

	version := string(sdk.Instance.GetArgs()["version"])
	if strings.TrimSpace(version) == "" {
		version = "1"
	}
	err := sdk.Instance.PutStateFromKey("version", version)
	if err != nil {
		sdk.Instance.Errorf("UpgradeContract fail to save version")
		return sdk.Error("fail to save version")
	}
	sdk.Instance.Infof("UpgradeContract version(%s) ", version)
	return sdk.Success([]byte("UpgradeContract Success"))
}

func (o *UseDemo) InvokeContract(method string) protogo.Response {

	sdk.Instance.Infof("custom " + method)
	switch method {
	case "query_http":
		return o.QueryHttp()
	case "query_mysql":
		return o.QueryMysql()
	case "get_vrf":
		return o.GetVRF()
	case "oracle_callback":
		return o.OracleCallBack()
	case "name":
		return o.Name()
	case "version":
		return o.Version()
	case "query_hash":
		return o.QueryUseHash()
	case "query_cross":
		return o.QueryCross()
	// case "register_topic":
	// 	return o.RegisterTopic(stub)
	case "query_topic":
		return o.QueryTopic()
	default:
		sdk.Instance.Errorf("InvokeContract invalid method, method is " + method)
		return sdk.Error("invalid method")
	}

}

func (o *UseDemo) QueryTopic() protogo.Response {
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	args := sdk.Instance.GetArgs()
	topic := args["topic"]
	useCache := string(args["use_chaindata"])
	isPersist := string(args["is_persist"])

	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryDataByTopic")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["use_chaindata"] = []byte(useCache)
	parameters["is_persistence"] = []byte(isPersist)
	parameters["topic"] = topic
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryDataByTopic", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("QueryTopic error," + resp.String())
		return sdk.Error(resp.String())
	}

	sdk.Instance.Infof("QueryTopic response," + resp.String())
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("QueryTopic unmarshal error")
	}
	if responeT.Code == "201" {
		//同步调用
		sdk.Instance.Errorf("QueryTopic sync," + string(responeT.ResultB))
		return sdk.Success(responeT.ResultB)
	}
	sdk.Instance.Infof("QueryTopic " + string(responeT.ResponseHash))
	sdk.Instance.PutStateFromKey(string(responeT.ResponseHash), string(topic))
	//stub.PutStateByte(string(responeT.ResponseHash), "parameter", sqlBs) //存根一下
	return sdk.Success([]byte("QueryTopic ok"))
}


func (o *UseDemo) GetVRF() protogo.Response {
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}

	args := sdk.Instance.GetArgs()
	alpha := string(args["alpha"])

	parameters := make(map[string][]byte)

	parameters["alpha"] = []byte(alpha)
	parameters["method"] = []byte("getVrf")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)

	//stub.EmitEvent("http_query", []string{url, "aac"})
	resp := sdk.Instance.CallContract("oracle_contract_v1", "getVrf", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("GetVRF error," + resp.String())
		return sdk.Error(resp.String())
	}
	sdk.Instance.Infof("GetVRF resp" + resp.String())
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("GetVRF unmarshal error")
	}
	sdk.Instance.Infof("get_vrf_responset " + string(responeT.ResponseHash))
	sdk.Instance.PutStateFromKey(string(responeT.ResponseHash), string(alpha))
	//stub.PutStateByte(string(responeT.ResponseHash), "parameter", []byte(alpha)) //存根一下
	return sdk.Success([]byte(resp.String()))
}

func (o *UseDemo) QueryHttp() protogo.Response {
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	args := sdk.Instance.GetArgs()
	useCache := string(args["use_chaindata"])
	isPersist := string(args["is_persist"])

	url := string(args["url"])
	fetchType := string(args["fetch_data_type"])
	fetchFormular := string(args["fetch_data_formular"])
	parameters := make(map[string][]byte)
	httpQuerys := ModelHttp{
		Method:            "GET",
		URL:               url, //"https://www.baidu.com/baidu?wd=chainmaker",,
		ConnectionTimeout: 30,
		MaxFetchTimeout:   30,
		FetchDataType:     fetchType,
		FetchDataFormula:  fetchFormular,
	}
	if fetchType == "html" {
		httpQuerys.HttpHeader = map[string]string{
			"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3776.0 Safari/537.36",
		}
	}
	httpBs, _ := json.Marshal(httpQuerys)

	parameters["http_query"] = httpBs
	parameters["method"] = []byte("queryHttp")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["use_chaindata"] = []byte(useCache)
	parameters["is_persistence"] = []byte(isPersist)
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryHttp", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("query_http error," + resp.String())
		return sdk.Error(resp.String())
	}
	sdk.Instance.Infof("query_http resp," + resp.String())
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("GetVRF unmarshal error")
	}
	if responeT.Code == "201" {
		//同步调用结果
		sdk.Instance.Errorf("QueryHttp sync," + string(responeT.ResultB))
		return sdk.Success(responeT.ResultB)
	}
	sdk.Instance.Infof("query_http_responset " + string(responeT.ResponseHash))
	sdk.Instance.PutStateFromKey(string(responeT.ResponseHash), string(httpBs))
	//stub.PutStateByte(string(responeT.ResponseHash), "parameter", httpBs) //存根一下
	return sdk.Success([]byte(resp.String()))

}

func (o *UseDemo) QueryCross() protogo.Response {
	//订制化查询hash函数
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	args := sdk.Instance.GetArgs()
	chainA := string(args["chain_alias"])
	name := string(args["contract_name"])
	mName := string(args["method_name"])
	useCache := string(args["use_chaindata"])
	isPersist := string(args["is_persist"])
	qHash := string(args["query_hash"])
	var kvp []KeyValuePair
	kvp = append(kvp, KeyValuePair{
		Key:   "query_hash",
		Value: []byte(qHash),
	})
	var queryM CrossChainQuery
	queryM.ChainAlias = chainA
	queryM.ContractName = name
	queryM.MethodName = mName
	queryM.Params = kvp
	queryMBS, _ := json.Marshal(queryM)
	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryCrossChain")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["use_chaindata"] = []byte(useCache)
	parameters["is_persistence"] = []byte(isPersist)
	parameters["cross_query"] = queryMBS
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryCrossChain", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("QueryCross error," + resp.String())
		return sdk.Error(resp.String())
	}

	sdk.Instance.Infof("QueryCross response," + resp.String())
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("QueryCross unmarshal error")
	}
	if responeT.Code == "201" {
		sdk.Instance.Errorf("QueryCross sync," + string(responeT.ResultB))
		return sdk.Success(responeT.ResultB)
	}
	sdk.Instance.Infof("QueryCross " + string(responeT.ResponseHash))
	sdk.Instance.PutStateFromKey(string(responeT.ResponseHash), string(queryMBS))
	//stub.PutStateByte(string(responeT.ResponseHash), "parameter", sqlBs) //存根一下
	return sdk.Success([]byte("QueryCross ok"))

}

func (o *UseDemo) QueryUseHash() protogo.Response {
	args := sdk.Instance.GetArgs()
	qHash := string(args["query_hash"])
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	parameters := make(map[string][]byte)
	parameters["method"] = []byte("queryResultByHash")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["query_hash"] = []byte(qHash)
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryResultByHash", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("QueryUseHash error," + resp.String())
		return sdk.Error(resp.String())
	}
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("QueryCross unmarshal error")
	}
	if responeT.Code == "201" && responeT.Message == "success" {
		sdk.Instance.Errorf("QueryUseHash sync," + string(responeT.ResultB))
		return sdk.Success(responeT.ResultB)
	} else {
		return sdk.Success(resp.Payload)
	}

}

func (o *UseDemo) QueryMysql() protogo.Response {
	version, versionErr := sdk.Instance.GetStateFromKey("version")
	if versionErr != nil {
		sdk.Instance.Errorf("Version failed")
		return sdk.Error(versionErr.Error())
	}
	args := sdk.Instance.GetArgs()
	sqlType := string(args["sql_type"])
	sqlSentence := string(args["sql_sentence"])
	dataSource := string(args["data_source"])
	useCache := string(args["use_chaindata"])
	isPersist := string(args["is_persist"])
	parameters := make(map[string][]byte)
	sqlQuerys := ModelSql{
		SqlType:     sqlType,
		SqlSentence: sqlSentence,
		DataSource:  dataSource,
	}
	sqlBs, _ := json.Marshal(sqlQuerys)
	parameters["mysql_query"] = sqlBs
	parameters["method"] = []byte("queryMysql")
	parameters["original_contract_name"] = []byte("use_demo")
	parameters["original_contract_version"] = []byte(version)
	parameters["use_chaindata"] = []byte(useCache)
	parameters["is_persistence"] = []byte(isPersist)
	resp := sdk.Instance.CallContract("oracle_contract_v1", "queryMysql", parameters)
	if resp.Status != 0 {
		sdk.Instance.Errorf("QueryMysql error," + resp.String())
		return sdk.Error(resp.String())
	}

	sdk.Instance.Infof("QueryMysql response," + resp.String())
	var responeT OracleQueryResult
	uerr := json.Unmarshal(resp.Payload, &responeT)
	if uerr != nil {
		sdk.Instance.Errorf("QueryMysql unmarshal error")
	}
	if responeT.Code == "201" { //同步返回结果
		sdk.Instance.Errorf("QueryMysql sync," + string(responeT.ResultB))
		return sdk.Success(responeT.ResultB)
	}
	sdk.Instance.Infof("query_mysql_responset " + string(responeT.ResponseHash))
	sdk.Instance.PutStateFromKey(string(responeT.ResponseHash), string(sqlBs))
	//stub.PutStateByte(string(responeT.ResponseHash), "parameter", sqlBs) //存根一下
	return sdk.Success(resp.Payload)
}

func (o *UseDemo) OracleCallBack() protogo.Response {
	//首先需要做一下校验,校验一下调用这个函数的交易发起者pk是否是预言机合约;不是的话,不允许调用
	senderPk, senderPkErr := sdk.Instance.GetSenderPk()
	if senderPkErr != nil {
		sdk.Instance.Errorf("OracleCallBack getSenderPk error , " + senderPkErr.Error())
		return sdk.Error(senderPkErr.Error())
	}
	storePk, storePkErr := sdk.Instance.GetStateFromKey(gOracleContractPkStr)
	if storePkErr != nil {
		sdk.Instance.Errorf("OracleCallBack getStateFromKey error , " + storePkErr.Error())
		return sdk.Error(storePkErr.Error())
	}
	if storePk != senderPk {
		return sdk.Error("OracleCallBack failed , tx senderpk not match")
	}
	//
	args := sdk.Instance.GetArgs()
	results := string(args["result"])
	originalMethod := string(args["original_method"])
	responseHash := args["response_hash"]
	code := string(args["code"])
	message := string(args["message"])
	value, valueErr := sdk.Instance.GetStateFromKey(string(responseHash))
	if valueErr != nil {
		sdk.Instance.Errorf("callback got key error ")
	}
	//parameters, _ := stub.GetStateByte(string(responseHash), "parameter") //寻找对应关系
	sdk.Instance.Infof("originalMethod(%s) , callback result (%s) ,code(%s), message(%s), parameter(%s) , responseHash(%s) \n", originalMethod, results,
		code, message, value, string(responseHash))
	if originalMethod == "get_vrf" {
		var vrfR Random
		json.Unmarshal(args["result"], &vrfR)
		parameters := make(map[string][]byte)
		parameters["pi"] = vrfR.Pi
		parameters["alpha"] = []byte(value)
		parameters["method"] = []byte("verifyVrf")
		verifyResp := sdk.Instance.CallContract("oracle_contract_v1", "verifyVrf", parameters)
		if verifyResp.Status != 0 {
			sdk.Instance.Errorf("verify vrf error, " + verifyResp.String())
			return sdk.Error("verify vrf error")
		}
		var verifyT OracleQueryResult
		json.Unmarshal(verifyResp.Payload, &verifyT)
		if string(verifyT.ResultB) != "true" {
			sdk.Instance.Errorf("verify vrf false")
			return sdk.Error("verify vrf false")
		}
	}
	return sdk.Success([]byte("callback done"))
}

func main() {

	err := sandbox.Start(new(UseDemo))
	if err != nil {
		panic(err)
	}
}

2.2.3. 通过接口对预言机进行管理和简化开发

2.2.3.1. 安装预言机合约接口

/v1/install_contract 
方法: POST
格式: Form表单
header: admin_auth_token
参数:
    1. chain_alias 链id
    2. contract_name 合约名称
    3. contract_version 合约版本(整形数字,从1开始)
    4. contract_file 合约文件(7z文件)
    5. runtime 合约格式(暂时只支持DOCKER_GO)
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息

2.2.3.2. 查询已安装合约接口

/v1/list_contracts 
方法: GET
格式: query参数
header: assist_auth_token
参数:
    1. chain_alias 链id
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息列表

2.2.3.3. 升级合约接口

/v1/upgrade_contract 
方法: POST
header: admin_auth_token
格式: Form表单
参数:
    1. chain_alias 链id
    2. contract_name 合约名称
    3. contract_version 合约版本(整形数字,从1开始)
    4. contract_file 合约文件(7z文件)
    5. runtime 合约格式(暂时只支持DOCKER_GO)
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息

2.2.3.4. 生成VRF接口

/v1/vrf/get_random
方法:POST
header: assist_auth_token
格式:Form表单
参数:
    1. alpha 用户输入随机数字
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回VRF信息  
    

2.2.3.5. 验证VRF接口

/v1/vrf/verify
方法:POST
header: assist_auth_token
格式:json
参数:
    1. alpha 用户输入随机数字
    2. pi 证据
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 是否验证通过(true/false)  
    

2.2.3.6. 查询预言机公钥接口

/v1/vrf/get_public_key
方法:GET
header: assist_auth_token
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回公钥

2.2.3.7. 服务是否可触达接口

/v1/health 
方法: GET
格式: query参数
header: assist_auth_token
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)

2.2.3.8. 查询mysql数据接口

/v1/query/query_mysql 
方法: POST
格式: Json
header: assist_auth_token
参数:
type ModelSql struct {
	SqlType     string `json:"sql_type"` //select
	SqlSentence string `json:"sql_sentence"`
	DataSource  string `json:"data_source"` //数据源
}的序列化json数据
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息

2.2.3.9. 查询http接口数据接口

/v1/query/query_http 
方法: POST
格式: Json
header: assist_auth_token
参数:
type ModelHttp struct {
	URL               string            `json:"url"`
	HttpHeader        map[string]string `json:"http_header"`
	HttpBody          []byte            `json:"http_body"`
	ConnectionTimeout int               `json:"connection_timeout"` //最大连接超时时长,默认30s,单位秒级别
	MaxRetry          int               `json:"max_retry"`          //最大连接试错次数,默认3次
	MaxFetchTimeout   int               `json:"max_fetch_timeout"`  //最大读取超时时长,默认10s,可以选择为10-60s
	FetchDataType     string            `json:"fetch_data_type"`    //json,xml,html
	FetchDataFormula  string            `json:"fetch_data_formula"` //默认为空,为空则不解析,原样返回。如果非空,则按照. 来分隔取出来结构化的数据
}的序列化json数据
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息

2.2.3.10. 跨链查询数据接口

/v1/query/query_cross 
方法: POST
格式: Json
header: assist_auth_token
参数:
# CrossChainQuery 合约跨链查询模型
type CrossChainQuery struct {
	ChainAlias   string         `json:"chain_alias"` //调用的链id
	ContractName string         `json:"contract_name"`
	MethodName   string         `json:"method_name"`
	Params       []KeyValuePair `json:"params"`
}

# KeyValuePair 合约传参数模型
type KeyValuePair struct {
	Key   string `json:"key"`
	Value []byte `json:"value"`
}的序列化json数据
返回参数:
    格式: json
    code: 错误码(0 为成功)
    message: 错误信息(默认为success)
    data: 返回合约信息