jpa query oracle 参数int为空_撸一个预言机(Oracle)服务,真香!—中篇
本文作者:六天
一、文章結構
本文將通過上、中、下三篇文章帶領大家一步步開發實現一個中心化的 Oracle 服務,并通過一個抽獎合約演示如何使用我們的 Oracle 服務。文章內容安排如下:
- 上篇:Oracle 簡介及合約實現
- 中篇:使用 go 語言開發 Oracle 服務
- 下篇:抽獎合約調用 Oracle 服務示例
在上篇中,我們實現了一個通用的 Oracle 合約,其主要有一個接收用戶請求的 Query 方法;回調用戶合約的 Response 方法和一個供 Oracle 后端服務訂閱的 QueryInfo 事件。
本篇是中篇,主要使用 go 語言開發實現 Oracle 的后端服務。
本文作者六天,文中的 Oracle 服務完整代碼地址:https://github.com/six-days/ethereum-oracle-service二、服務架構
Oracle 后端服務整體包含事件訂閱模塊、查詢模塊和回調模塊,架構如下圖所示。
服務開啟后,首先會通過以太坊 ws 協議的 jsonrpc,在區塊鏈上注冊事件訂閱,訂閱成功后開啟一個 for 循環,接收并處理事件消息。
代碼如下所示。
// 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)}} }三、事件訂閱
事件訂閱必須使用 ws 協議的 jsonrpc,http 協議的 jsonprc 無法訂閱事件。
事件訂閱的核心是通過 ethclient 的 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 = eventse.Subscription = subreturn nil }四、查詢模塊
1、日志解析
事件日志解析我們用 go-ethereum 的 abi 模塊的 Unpack 方法,將日志解析為我們定義好的結構體。
代碼如下所示。
type OracleQueryInfo struct {QueryId [32]byteRequester common.AddressFee *big.IntCallbackAddr common.AddressCallbackFUN stringQueryData []byteRaw 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、查詢請求
查詢請求比較簡單,就是根據用戶提供的 url 發送請求。代碼如下所示。
// 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 errorswitch 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 }五、回調模塊
回調模塊相對比較簡單,首先將 Oracle 合約實例化了一個 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 stringswitch resParamType {case "bytes":responseName = OracelResponseBytesNamecase "uint256":responseName = OracelResponseUint256Namedefault: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/七、可以優化的地方
至此,我們的 V1 版的 Oracle 服務已開發完成,服務已能滿足基本需求,但還有一些方面需要進一步優化,我這里列出了三點。
1、Nonce 托管
在回調模塊中,調用合約時,我們并沒有指定發起交易賬號的 Nonce 值,而是由 Transact 方法在每次發起交易時,動態計算。這就會限制我們交易的并發。
在高并發的情況下,肯定會出現多筆交易 Nonce 值相同的情況,后發起交易覆蓋前交易,造成前交易失敗。
針對這種情況,我的思路是對 Nonce 進行托管:
- 在緩存(內存或 Redis 等)中維護賬號對應的 Nonce
- 每次發起交易時,從緩存中獲取,每獲取一次,緩存中的 Nonce 累加 1
- 緩存中的 Nonce 定期和鏈上進行校對和同步
- 對于可能出現的空洞情況,使用空交易填補
2、Gas 優化
這段時間以太坊網絡比較擁堵,導致手續費居高不下。對于我們 Oracle 服務來說,節省 Gas 是很重要的一個優化方向。
這里我的思路是可以從以下幾個方面優化:
- 引入動態 GasPrice,可以從 https://ethgasstation.info 網站中獲取實時的 GasPrice
- 指定 GasLimit,防止由于合約問題消耗過多 Gas
- 余額檢查,防止由于余額不足造成交易失敗,浪費了手續費
- 接收回調數據的用戶合約方法盡量簡單,分離業務邏輯
3、支持 http 協議 jsonrpc
有的網絡節點沒有開啟 ws 服務,而使用 http 協議的網絡 jsonrpc 又無法直接訂閱事件。這時可以采取迂回策略,模擬事件訂閱,具體思路如下:
- 開啟網絡區塊監控
- 監控到有新區塊產生,查詢區塊中的日志
- 如果有我們 Oracle 合約產生的查詢日志,則進入后續的查詢和回調流程
大家對于優化有其他思路或疑問,歡迎留言探討。
下篇中,我將以一個抽獎合約為示例,介紹如何使用我們開發的 Oracle 服務來對抽獎合約提供一個隨機數。
原文鏈接: 擼一個預言機(Oracle)服務,真香!—中篇 | 登鏈社區 | 深入淺出區塊鏈技術
作者主頁: 六天 的個人主頁 - 登鏈社區 | 深入淺出區塊鏈技術,歡迎閱讀作者更多好文
登鏈社區 - 區塊鏈技術愛好者的家園
總結
以上是生活随笔為你收集整理的jpa query oracle 参数int为空_撸一个预言机(Oracle)服务,真香!—中篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 买保险到底有什么好处
- 下一篇: 不用手机实名制的贷款