Blockchain code Metaverse VR

SmartContract×VR×Crypto(615)

スポンサーリンク

//tips

//smart contract

先のバイナンスからの値の取得関数 retrieveLatestEthPriceはprocessRequestのtryの中で実行される形になっている。その上でsetLatestEthPriceに値が格納されている。

async function processRequest (oracleContract, ownerAddress, id, callerAddress) {
let retries = 0
while (retries < MAX_RETRIES) {
try {
const ethPrice = await retrieveLatestEthPrice()
await setLatestEthPrice(oracleContract, callerAddress, ownerAddress, ethPrice, id)

setLatestEthPriceでは小数点部分をBNで対応可能にし、oracleコントラクトの方に情報を送っている。

try {
await oracleContract.methods.setLatestEthPrice(ethPriceInt.toString(), callerAddress, idInt.toString()).send({ from: ownerAddress })
}

Axiosはまだ使われるのかfetchになるのかは今後確認していく必要がありそう。やっているapiによる価格取得はかなりシンプル。

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
}

Jsでの返り値で複数の値は返せないので一度オブジェクトに格納し、その後に渡し先で分解して取り出す形になる。

あとはoracleを起動する際に毎回必要になるチェーンネットワークへの接続、oracleコントラクトの初期化、イベントの感知の開始をoracleに準備させるinit関数を組み込む。

・connect to Extdev TestNet by calling the common.loadAccount function
→client (an object the app uses to interact with the Extedev Testnet),

・instantiate the oracle contract
→An instance of the oracle contract, and

・start listening for events
→ownerAddress (used in the setLatestEthPrice to specify the address that sends the transaction).

async function init () {
const { ownerAddress, web3js, client } = common.loadAccount(PRIVATE_KEY_FILE_NAME)
const oracleContract = await getOracleContract(web3js)
filterEvents(oracleContract, web3js)
return { oracleContract, ownerAddress, client }
}

Javascriptの通常運用では非同期に対応していないので、ここではqueueを使用して、非同期の仕組みに変えて一本道の処理部分にスタック倉庫を補完している。next.jsなどのフレームワークを使った場合には、この問題に対応する必要はなさそうだが、背景知識として理解しておく。

Remember that, due to JavaScript's single-threaded nature, we're processing the queue in batches and our thread will just sleep for SLEEP_INTERVAL milliseconds between each iteration. For this, we'll use the setInterval function.

https://dev.to/bbarbour/if-javascript-is-single-threaded-how-is-it-asynchronous-56gd

setInterval(async () => {
doSomething()
}, SLEEP_INTERVAL)

一方でユーザーがoracleを終了する機能はこちらとする。

process.on( 'SIGINT', () => {
// Gracefully shut down the oracle
})

これを先の関数につなげると下記のようになるとのこと。オブジェクトを分解して取得し、終了時には終了させ、インターバルで随時processQueueを呼ぶ。

async function init () {
const { ownerAddress, web3js, client } = common.loadAccount(PRIVATE_KEY_FILE_NAME)
const oracleContract = await getOracleContract(web3js)
filterEvents(oracleContract, web3js)
return { oracleContract, ownerAddress, client }
}

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

そして、ここで作成したoracle.jsをもとにClient.jsを生成する。中身はほぼ同じで随時eth価格のupdateのコードのみを追加している。このoracle.jsとClient.jsの使い分けはどうなるのか。

await callerContract.methods.setOracleInstanceAddress(oracleAddress).send({ from: ownerAddress })
setInterval( async () => {
await callerContract.methods.updateEthPrice().send({ from: ownerAddress })
}, SLEEP_INTERVAL);
})()

コントラクトのdeployをする前にcaller contractとthe oracleのprivatekeyを作成。

node scripts/gen-key.js oracle/oracle_private_key

node scripts/gen-key.js caller/caller_private_key

const { CryptoUtils } = require('loom-js')
const fs = require('fs')

if (process.argv.length <= 2) {
console.log("Usage: " + __filename + " <filename>.")
process.exit(1);
}

const privateKey = CryptoUtils.generatePrivateKey()
const privateKeyString = CryptoUtils.Uint8ArrayToB64(privateKey)

let path = process.argv[2]
fs.writeFileSync(path, privateKeyString)

次にtruffleにネットワークへのdeploy方法を伝える。ここではloomへのdeploy。

const LoomTruffleProvider = require('loom-truffle-provider')

const path = require('path')
const fs = require('fs')

module.exports = {
networks: {
extdev: {
provider: function () {
const privateKey = fs.readFileSync(path.join(__dirname, 'oracle_private_key'), 'utf-8')
const chainId = 'extdev-plasma-us1'
const writeUrl = 'wss://extdev-plasma-us1.dappchains.com/websocket'
const readUrl = 'wss://extdev-plasma-us1.dappchains.com/queryws'
return new LoomTruffleProvider(chainId, writeUrl, readUrl, privateKey)
},
network_id: '9545242630824'
}
},
compilers: {
solc: {
version: '0.5.0'
}
}
}

node Client.jsをコマンドに入れるとpriceupdatedのevent通知がターミナルに表示された。これできちんとoracle機能が稼働することが確認できた。

ここからさらにoracleを分散化する流れを見ていく。ownerとoracleの役割に分解する。

The owner should be able to add and remove oracles. In turn, an oracle must be allowed to update the ETH price by calling the setLatestEthPrice function.

ここでの分散化とは、チェーンに提供される情報を単一oracleの情報元だけに頼らず、複数oracleから収集し、データの正当性や正確性を判断するというもの。

openzeppelinではこの役割分割をする仕組みをライブラリに用意してくれているのでそちらを使う。

import "openzeppelin-solidity/contracts/access/Roles.sol";

pragma solidity ^0.5.0;

/**
* @title Roles
* @dev Library for managing addresses assigned to a Role.
*/
library Roles {
struct Role {
mapping (address => bool) bearer;
}

/**
* @dev Give an account access to this role.
*/
function add(Role storage role, address account) internal {
require(!has(role, account), "Roles: account already has role");
role.bearer[account] = true;
}

/**
* @dev Remove an account's access to this role.
*/
function remove(Role storage role, address account) internal {
require(has(role, account), "Roles: account does not have role");
role.bearer[account] = false;
}

/**
* @dev Check if an account has this role.
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), "Roles: account is the zero address");
return role.bearer[account];
}
}

その上でusing Roles for Roles.Role;を使うことで、下記のaddなどの機能が使うことができる。これはmathをinterfaceで導入した仕組みと同じ流れ。対象となるのが数字ではなく個別oracleになるという違いがある。

oracles.add(_oracle); // Adds `_oracle` to the list of oracles
oracles.remove(_oracle); // Removes `_oracle` from the list of oracles
oracles.has(msg.sender); // Returns `true` if `msg.sender` is an `oracle`

下記のように設定する。

pragma solidity 0.5.0;
import "openzeppelin-solidity/contracts/access/Roles.sol";
import "./CallerContractInterface.sol";
contract EthPriceOracle {
using Roles for Roles.Role;
Roles.Role private owners;
Roles.Role private oracles;

上記にてownableを外したので、constructorの形で組み込む。コントラクトのdeploy時に一度だけ実行されるもの。先のownersにconstructorの引数にとったアドレスを追加する。

contract EthPriceOracle {
using Roles for Roles.Role;
Roles.Role private owners;
Roles.Role private oracles;

uint private randNonce = 0;
uint private modulus = 1000;

mapping(uint256=>bool) pendingRequests;
event GetLatestEthPriceEvent(address callerAddress, uint id);
event SetLatestEthPriceEvent(uint256 ethPrice, address callerAddress);

constructor (address _owner) public {
owners.add(_owner);
}

この流れで新たなoracleの追加もしていく。

・Verify that the caller is the owner of the contract.
・Make sure that the address is not already an oracle.
・Notify the front-end that a new oracle has been added by firing an event at the end of the function.

これはシンプル。

function addOracle (address _oracle) public {
require(owners.has(msg.sender), "Not an owner!");
require(!oracles.has(_oracle), "Already an oracle!");
oracles.add(_oracle);
emit AddOracleEvent(_oracle);
}

 

人気の記事

1

皆さん、ついに、エアラインでも、サブスクリプションが始まったのはご存じですか? まだ実験段階ですが、ANAが、定額全国住み放題サービスを提供する「ADDress」と組んで、国内線を4回まで定額利用可能 ...

2

無料でネットショップを開けるアプリとして多くの人に驚きを与えたBASE株式会社が、2019年10月25日東証マザーズに上場しました。2020年2月時点で90万店を超えるショップを抱えるまでに成長してい ...

3

2011年にサービスを開始してから圧倒的な成長率を誇るインテリア通販サイト 【FLYMEe/フライミー】を皆さんご存じでしょうか。 「自分のイメージするインテリア、本当に欲しいインテリアがどこにあるの ...

4

ついに、noteの月間アクティブユーザー数が4400万人(2020年3月時点)に到達しました。 そもそも、「note」とは、クリエイターが、文章やマンガ、写真、音声を投稿することができ、ユーザーはその ...

5

ボードゲームカフェが1日2回転で儲かるという記事をみつけたので興味を持ち、調べてみました。 まずは、需要がどれくらいあるのか、市場のようすからみていきましょう。 世界最大のボードゲーム市場はドイツで、 ...

-Blockchain, code, Metaverse, VR
-, ,

Copyright© BUSINESS HUNTER , 2023 All Rights Reserved Powered by AFFINGER5.