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

コロナによる需要変化 コロナパンデミックの影響で、人々は外に出られなくなり、自宅で過ごす時間が増えました。 この自粛ムードの中、下記のようなビジネスの需要変化が引き起こされています。 【利用者減少】 ...

2

米国レストランの決済時に毎日お世話になっていた「Square」のビジネスモデルについて本日はふれていきたいと思います。 「Square」とは、ネットにつながったモバイル端末と専用のカードリーダーを用意 ...

3

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

4

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

5

ナイキのSNKRSが、なぜこれほどまでに人気なのか?調べてみました。 きっかけは米国での友達との会話。彼は自分のシューズをみせて、「これ20万円もしたんだぜ。」と語ってくれました。 あまり靴に興味がな ...

-Blockchain, code, Metaverse, VR
-, ,

Copyright© BUSINESS HACKER , 2022 All Rights Reserved Powered by AFFINGER5.