从零开始写预言机(二)

本博客整理于 cryptozombies.io/zh/lesson
查看原教程请参考如上链接,本博客做个人记录!

从零开始写预言机(二)

1、概览

在前面我们已经完成了EthPriceOracle.sol、CallerContract的合约的编写,接下来我们就要在链下调用合约对ETH价格的请求
前面我们已经配置好了相关的环境,除此之外,我们还要在oracleDemo目录下再新建Client.js,EthpriceOracle.js,utils/common.js
Client定义用户相关的函数,EthpriceOracle定义与预言机相关的函数,common定义读取账户等初始化的操作

文件目录大致如下

.
├── caller
│   ├── build
│   ├── caller_private_key
│   ├── contracts
│   ├── migrations
│   ├── test
│   └── truffle-config.js
├── Client.js
├── EthPriceOracle.js
├── oracle
│   ├── build
│   ├── contracts
│   ├── migrations
│   ├── oracle_private_key.js
│   ├── test
│   └── truffle-config.js
├── package.json
├── package-lock.json
└── utils
    └── common.js

2、 EthPriceOracle.js

const axios = require('axios')
const BN = require('bn.js')
const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 2000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './oracle/oracle_private_key'
const CHUNK_SIZE = process.env.CHUNK_SIZE || 3
const MAX_RETRIES = process.env.MAX_RETRIES || 5
var OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')
var pendingRequests = []

// Start here
async function getOracleContract(web3js) {
    
    
    var networkId = await web3js.eth.net.getId()
    console.log(networkId)
    return new web3js.eth.Contract(OracleJSON.abi, OracleJSON.networks[networkId].address)
}

async function retrieveLatestEthPrice () {
    
    
    const resp = await axios({
    
    
      url: "https://api.nomics.com/v1/currencies/ticker?key=dc5568f283d8744cbb3eef663762d14095265cca&ids=ETH&interval=1h&per-page=100&page=1",
      params: {
    
    
        id: 'ETH'
      },
      method: 'get'
    })
    return resp.data[0].price
  }

async function filterEvents(oracleContract, web3js) {
    
    
    oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
    
    
        if (err) {
    
    
          console.error('Error on event', err)
          return
        }
        await addRequestToQueue(event)
    })
    oracleContract.events.SetLatestEthPriceEvent(async (err, event) => {
    
    
        if (err) console.error('Error on event', err)
    })
}

async function addRequestToQueue(event) {
    
    
    const callerAddress = event.returnValues.callerAddress
    const id = event.returnValues.id
    pendingRequests.push({
    
    callerAddress,id})
    console.log("接收到一个请求, id = ",event.returnValues.id)
}

async function processQueue(oracleContract,ownerAddress) {
    
    
    let processedRequests = 0
    while(pendingRequests.length > 0 && processedRequests < CHUNK_SIZE) {
    
    
        const req = pendingRequests.shift()
        console.log("开始处理 id = " + req.id)
        await processRequest(oracleContract,ownerAddress,req.id,req.callerAddress)
        processedRequests++
    }
}

async function processRequest(oracleContract,ownerAddress,id,callerAddress) {
    
    
    let retries = 0
    while(retries < MAX_RETRIES) {
    
    
        try {
    
    
            const ethPrice = await retrieveLatestEthPrice()
            console.log("已成功获取数据 ethPrice = " + ethPrice)
            await setLatestEthPrice(oracleContract,callerAddress,ownerAddress,ethPrice,id)
            return
        } catch (error) {
    
    
            //console.log(error)
            if (retries === MAX_RETRIES - 1) {
    
    
                await setLatestEthPrice(oracleContract,callerAddress,ownerAddress,'0',id)
                return
            }
            retries++
        }
    }
}

async function setLatestEthPrice(oracleContract,callerAddress,ownerAddress,ethPrice,id) {
    
    
    // Start here
    ethPrice = ethPrice.replace('.','')
    const multiplier = new BN(10**10,10)
    const ethPriceInt = (new BN(parseInt(ethPrice), 10)).mul(multiplier)
    const idInt = new BN(parseInt(id))
    try {
    
    
        await oracleContract.methods.setLatestEthPrice(ethPriceInt.toString(), callerAddress, idInt.toString()).send({
    
     from: ownerAddress })
    } catch (error) {
    
    
        console.log('Error encountered while calling setLatestEthPrice.')
        console.log(error);
        // Do some error handling
    }
}

async function init() {
    
    
    const {
    
     accounts, web3js, client } = await common.loadAccount(PRIVATE_KEY_FILE_NAME)
    const ownerAddress = accounts[0]
    console.log("Oracle_Address = " + ownerAddress)
    const oracleContract = await getOracleContract(web3js)
    filterEvents(oracleContract,web3js)
    console.log("start to listen!")
    return {
    
    oracleContract,ownerAddress,client}
}

(async () => {
    
    
    const {
    
     oracleContract, ownerAddress, client } = await init()
    process.on( 'SIGINT', () => {
    
    
      console.log('Calling client.disconnect()')
      // 1. Execute client.disconnect
    //   client.disconnect()
      process.exit( )
    })
    setInterval(async () => {
    
    
      // 2. Run processQueue
      await processQueue(oracleContract,ownerAddress)
    }, SLEEP_INTERVAL)
  })()
  
  

2.1 web3.js如何监听事件?

  oracleContract.events.GetLatestEthPriceEvent(async (err, event) => {
    
    
        if (err) {
    
    
          console.error('Error on event', err)
          return
        }
        await addRequestToQueue(event)
    })

上述代码监听GetLatestEthPriceEvent事件,并在监听到事件之后调用addRequestToQueue函数

2.2 如何处理访问API时网络不稳定导致的访问失败?

// 处理请求
async function processRequest(oracleContract,ownerAddress,id,callerAddress) {
    
    
    let retries = 0
    while(retries < MAX_RETRIES) {
    
    
        try {
    
    
            const ethPrice = await retrieveLatestEthPrice()
            console.log("已成功获取数据 ethPrice = " + ethPrice)
            await setLatestEthPrice(oracleContract,callerAddress,ownerAddress,ethPrice,id)
            return
        } catch (error) {
    
    
            //console.log(error)
            // 如果已经达到最大查询次数,那么就返回0
            if (retries === MAX_RETRIES - 1) {
    
    
                await setLatestEthPrice(oracleContract,callerAddress,ownerAddress,'0',id)
                return
            }
            retries++
        }
    }
}

上述代码记录了一个retries值,没访问失败一次就加一,直到达到最大的失败次数,返回“0”为eth价格

2.3 如何循环处理pendingRequest队列?

    setInterval(async () => {
    
    
      // 2. Run processQueue
      await processQueue(oracleContract,ownerAddress)
    }, SLEEP_INTERVAL)

利用setInterval函数

3、Client.js

const common = require('./utils/common.js')
const SLEEP_INTERVAL = process.env.SLEEP_INTERVAL || 20000
const PRIVATE_KEY_FILE_NAME = process.env.PRIVATE_KEY_FILE || './caller/caller_private_key'
const CallerJSON = require('./caller/build/contracts/CallerContract.json')
const OracleJSON = require('./oracle/build/contracts/EthPriceOracle.json')

async function getCallerContract (web3js) {
    
    
  const networkId = await web3js.eth.net.getId()
  return new web3js.eth.Contract(CallerJSON.abi, CallerJSON.networks[networkId].address)
}

async function retrieveLatestEthPrice () {
    
    
  const resp = await axios({
    
    
    url: 'https://api.binance.com/api/v3/ticker/price',
    params: {
    
    
      symbol: 'ETHUSDT'
    },
    method: 'get'
  })
  return resp.data.price
}

async function filterEvents (callerContract) {
    
    
  callerContract.events.PriceUpdatedEvent({
    
     filter: {
    
     } }, async (err, event) => {
    
    
    if (err) console.error('Error on event', err)
    console.log('* New PriceUpdated event. ethPrice: ' + event.returnValues.ethPrice)
  })
  callerContract.events.ReceivedNewRequestIdEvent({
    
     filter: {
    
     } }, async (err, event) => {
    
    
    if (err) console.error('Error on event', err)
    console.log("请求已成功创建, id = " + event.returnValues.id)
  })
}

async function init () {
    
    
  const {
    
     accounts, web3js, client } = await common.loadAccount(PRIVATE_KEY_FILE_NAME)
  const ownerAddress = accounts[0]
  console.log("Caller_Address = " + ownerAddress)
  const callerContract = await getCallerContract(web3js)
  filterEvents(callerContract)
  return {
    
     callerContract, ownerAddress, client, web3js }
}

(async () => {
    
    
  const {
    
     callerContract, ownerAddress, client, web3js } = await init()
  process.on( 'SIGINT', () => {
    
    
    console.log('Calling client.disconnect()')
    // client.disconnect();
    process.exit();
  })
  const networkId = await web3js.eth.net.getId()
  const oracleAddress =  OracleJSON.networks[networkId].address
  await callerContract.methods.setOracleInstanceAddress(oracleAddress).send({
    
     from: ownerAddress })
  setInterval( async () => {
    
    
    // Start here
    await callerContract.methods.updateEthPrice().send({
    
    from:ownerAddress})
    //console.log("Have send a request!");
  }, SLEEP_INTERVAL);
})()

这里的基本逻辑和EthPriceOracle.js中的基本相同
不过在setInterval 函数中会调用callerContract.methods.updateEthPrice().send函数发起请求

4、utils/commo.js

const fs = require('fs')
const Web3 = require('web3')
const ganache = require('ganache-cli')
const {
    
     type } = require('os')
//const { Client, NonceTxMiddleware, SignedTxMiddleware, LocalAddress, CryptoUtils, LoomProvider } = require('loom-js')

async function loadAccount (privateKeyFileName) {
    
    
    const provider = new Web3.providers.WebsocketProvider("ws://<YOUR_IP>:8546")
    const web3js = new Web3(provider);
    //console.log(web3js)
    const objAccounts = await web3js.eth.getAccounts()
    var accounts = []
    for (var i in objAccounts) {
    
    
      accounts.push(objAccounts[i]);
    }
    const client = 0;
    return {
    
    
    accounts,
    web3js: web3js,
    client
    }
}

module.exports = {
    
    
  loadAccount,
};

common.js主要是初始化owneraddress与web3对象,在2.3小结中在init中调用。

因为原来的教程里面的loom-js死活安装不上,所以就放弃了,换用了在geth的私有链进行部署,为什么不用ganache呢?因为http的provider已经被web3给弃用了,我没有找到ganache如何开启ws的方法,所以采用了geth,如果知道怎么用ganache进行测试,能告诉我一下嘛哈哈

这里与cryptozombies中有一些不同

  1. 没有用到私钥文件,传入common.js的文件路径实际没用
  2. client实际上没用
  3. 这里部署caller,oracle合约的用户其实是同一个,有需要可以才truffle中的配置文件中更改

5、部署

5.1 启动geth

接下来我们的代码文件都已经做好了,现在让我们启动我们的私有链吧!

geth --datadir ./ --networkid <YOUR_NETWORK_ID> --http --http.port 7545 --http.addr "0.0.0.0" --ws --ws.addr "0.0.0.0" --allow-insecure-unlock console

5.2 部署合约

编译合约

truffle compile

修改truffle-config.json中的networks

更改你的geth的ip、端口就可以啦

     geth: {
    
    
      host: "",     
      port: 7545,            
      network_id: "*",      
     },

大致可能是这样子的

部署合约

truffle migrate --network <YOUR_NETWORK_NAME>

6、运行!

分别运行

node Client.js
node EthPriceOracle.js

7、结果!

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45469783/article/details/123350430
今日推荐