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: 返回合约信息