本文将通过上、中、下三篇文章带领大家一步步开发实现一个中心化的Oracle服务,并通过一个抽奖合约演示如何使用我们的Oracle服务。文章内容安排如下: 在上篇中,我们实现了一个通用的Oracle合约,其主要有一个接收用户请求的Query方法;回调用户合约的Response方法和一个供Oracle后端服务订阅的QueryInfo事件。 本篇是中篇,主要使用go语言开发实现Oracle的后端服务。 本文作者六天,文中的Oracle服务完整代码地址:https://github.com/six-days/ethereum-oracle-service Oracle后端服务整体包含事件订阅模块、查询模块和回调模块,架构如下图所示。 服务开启后,首先会通过以太坊ws协议的jsonrpc,在区块链上注册事件订阅,订阅成功后开启一个for循环,接收并处理事件消息。 代码如下所示。 事件订阅必须使用ws协议的jsonrpc,http协议的jsonprc无法订阅事件。 事件订阅的核心是通过ethclient的 代码如下所示。 事件日志解析我们用go-ethereum的abi模块的 代码如下所示。 查询请求比较简单,就是根据用户提供的url发送请求。代码如下所示。 查询可能失败,这里需要增加失败重试机制,代码比较简单,就不写出来了。 这里使用 回调模块相对比较简单,首先将Oracle合约实例化了一个 在 回调模块代码如下所示。 回调也可能失败,服务对 编译完成后查看帮助信息 配置信息如下: 至此,我们的V1版的Oracle服务已开发完成,服务已能满足基本需求,但还有一些方面需要进一步优化,我这里列出了三点。 在回调模块中,调用合约时,我们并没有指定发起交易账号的Nonce值,而是由 在高并发的情况下,肯定会出现多笔交易Nonce值相同的情况,后发起交易覆盖前交易,造成前交易失败。 针对这种情况,我的思路是对Nonce进行托管: 这段时间以太坊网络比较拥堵,导致手续费居高不下。对于我们Oracle服务来说,节省Gas是很重要的一个优化方向。 这里我的思路是可以从以下几个方面优化: 有的网络节点没有开启ws服务,而使用http协议的网络jsonrpc又无法直接订阅事件。这时可以采取迂回策略,模拟事件订阅,具体思路如下: 大家对于优化有其他思路或疑问,欢迎留言探讨。 下篇中,我将以一个抽奖合约为示例,介绍如何使用我们开发的Oracle服务来对抽奖合约提供一个随机数。
一、文章结构
二、服务架构
// start monitor oracle contract event func (e *EventWatch) Start() {  if err := e.subscribeEvent(); err != nil {   return  }  e.dealEvent() }  func (e *EventWatch) dealEvent() {  for {   select {   case err := <-e.Subscription.Err():    logs.Error("[dealEvent] Subscription err: ", err)    e.subscribeEvent()   case vLog := <-e.EventChan:    // 处理查询请求并回调    go e.dealQuery(vLog)   }  } } 三、事件订阅
SubscribeFilterLogs方法,其中query参数是订阅的过滤条件。其中
Addresses是Oracle合约地址;Topics参数是过滤主题,是一个二维数组,这里我们的主题只指定了事件的名称。func (e *EventWatch) subscribeEvent() error {  query := ethereum.FilterQuery{   Addresses: []common.Address{    common.HexToAddress(e.Config.OracleContractAddress),   },   Topics: [][]common.Hash{    {e.OracleABI.Events[OracelEventName].ID()},   },  }  events := make(chan types.Log)  sub, err := e.Client.SubscribeFilterLogs(context.Background(), query, events)  if err != nil {   logs.Error("[SubscribeEvent]fail to subscribe event:", err)   return err  }  e.EventChan = events  e.Subscription = sub  return nil } 四、查询模块
1、日志解析
Unpack方法,将日志解析为我们定义好的结构体。type OracleQueryInfo struct {  QueryId      [32]byte  Requester    common.Address  Fee          *big.Int  CallbackAddr common.Address  CallbackFUN  string  QueryData    []byte  Raw          types.Log // Blockchain specific contextual infos }  type QueryRequest struct {  URL            string   `json:"url,omitempty"`  ResponseParams []string `json:"responseParams,omitempty"` }  func (e *EventWatch) dealQuery(vLog types.Log) error {         queryInfo := &OracleQueryInfo{}  err := e.OracleABI.Unpack(queryInfo, OracelEventName, vLog.Data)  if err != nil {   return fmt.Errorf("[dealQuery] unpack event log failed:%v", err)  }  reqData := &QueryRequest{}  if err = json.Unmarshal(queryInfo.QueryData, reqData); err != nil {   return fmt.Errorf("[dealQuery] unmarshal query data failed:%v", err)  } } 2、查询请求
// sendQueryRequest 根据客户端指定的查询地址发送请求 func (e *EventWatch) sendQueryRequest(reqData *QueryRequest, resParamType string) (interface{}, error) {  req, err := http.NewRequest("GET", reqData.URL, nil)  if err != nil {   return nil, fmt.Errorf("[sendQueryRequest] NewRequest failed: %v", err)  }  res, err := http.DefaultClient.Do(req)  if err != nil {   return nil, fmt.Errorf("[sendQueryRequest] http get request failed: %v", err)  }  defer res.Body.Close()  body, err := ioutil.ReadAll(res.Body)  if err != nil {   return nil, fmt.Errorf("[sendQueryRequest] read response data failed: %v", err)  }  logs.Trace("[sendQueryRequest] get ", reqData.URL, " response is: ", string(body))  queryRes, err := ParseResponeData(body, reqData.ResponseParams, resParamType)  if err != nil {   return nil, err  }  return queryRes, nil } 3、结果解析
go-simplejson库将查询结果进行json解析,并且提取用户指定所需要的字段,将字段转换为用户合约中回调方法接收的数据类型。// ParseResponeData 解析链下获取到的数据,提取用户所需要的字段,并转换为对应的数据类型 func ParseResponeData(repData []byte, keys []string, resParamType string) (interface{}, error) {  resData, err := simplejson.NewJson(repData)  if err != nil {   return nil, fmt.Errorf("[ParseResponeData] unmarshal response data failed:%v", err)  }   for _, paramName := range keys {   resData = resData.Get(paramName)  }  if resData == nil {   return nil, fmt.Errorf("[ParseResponeData] response data not exist request key:%v", keys)  }  var resValue interface{}  var coverErr error  switch resParamType {  case "uint256":   resUint64Value, coverErr := resData.Uint64()   if coverErr == nil {    resValue = big.NewInt(int64(resUint64Value))   }    case "bytes":   resValue, coverErr = resData.Bytes()  default:   return nil, fmt.Errorf("[ParseResponeData] unsupport response data type %s", resParamType)  }  if coverErr != nil {   return nil, fmt.Errorf("[ParseResponeData] response data type %s error:%v", resParamType, err)  }   return resValue, nil } 五、回调模块
BoundContract对象,然后调用Transact方法发送交易。其中第一个参数是使用私钥实例化的一个TransactOpts对象。TransactOpts对象中可以配置nonce、gasLimit、gasPrice等值,如果不指定,Transact方法会自己补充上。除此之外,Transact方法也会调用TransactOpts对象的Signer方法对消息进行签名。Transact方法源码详见:https://github.com/six-days/go-ethereum/blob/master/accounts/abi/bind/base.go// sendQueryResponse 将查询到的结果发送给客户端合约指定方法 func (e *EventWatch) sendQueryResponse(res interface{}, stateCode uint64, queryInfo *OracleQueryInfo, resParamType string) error {  in := []interface{}{   queryInfo.QueryId,   queryInfo.CallbackAddr,   queryInfo.CallbackFUN,   stateCode,   res,  }  var responseName string  switch resParamType {  case "bytes":   responseName = OracelResponseBytesName  case "uint256":   responseName = OracelResponseUint256Name  default:   return fmt.Errorf("[SendQueryResponse] unsupport response data type")  }  transaction, err := e.BoundContract.Transact(e.TransactOpts, responseName, in...)  if err != nil {   return fmt.Errorf("[SendQueryResponse] Transact failed: %v", err)  }  logs.Trace("[SendQueryResponse] call back tx:", transaction.Hash().Hex())  return nil } sendQueryResponse方法的调用也增加了失败重试机制。六、编译与运行
1、编译
go build ./oracle-service -h oracle_service version: 1.0.0 Usage: oracle_service [-h help] [-v version] [-c config path] [-l log path] 2、配置
# 合约地址 OracleContractAddress = "" # 网络ws地址 NetworkWS = "ws://" # 调用合约的私钥 PrivateKey = "" 3、运行
./oracle-service -c ./conf/app.conf -l logs/ 七、可以优化的地方
1、Nonce托管
Transact方法在每次发起交易时,动态计算。这就会限制我们交易的并发。
2、Gas优化
3、支持http协议jsonrpc
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算
 官方软件产品操作指南 (170)
官方软件产品操作指南 (170)