LoginSignup
3
2

More than 5 years have passed since last update.

vDiceでみるブロックチェーン上のスマートコントラクトへのアクセスについて

Last updated at Posted at 2017-08-26

Ethereumは、ブロックチェーン上のコントラクトを誰もが参照できる点に魅力がある。

ここではvDiceのサービスを例にブロックチェーン上にデプロイされたコントラクトメソッドを呼び出してみる。

このサービス自体はギャンブルなので、利用自体を推奨するわけではないので注意してほしい。

vDice:
https://www.vdice.io/
0.png

vDiceの特徴

-vDiceのコントラクトは0~9999までのランダムな数字を出力する。
-プレイヤーは出力される0~9999までの値で、コントラクトが出力する値を予測する。
-たとえば、出力される値が500以下と予測するのであればコントラクトAに、9000以下と予測するのであればコントラクトBのアドレスにETHを送付する。
-予想が的中すればオッズ分のETHが、自身のウォレットに上乗せされて返金される。また、予測される範囲の値は狭ければ狭いほど、的中したときにリターンとして得られる倍率は高くなる。(≦500のとき19.6倍)

やったこと

  • これまでvDiceのサービスとして実行された結果を確認する。
  • 過去vDiceのコントラクトが出力した値がまんべんなく0~9999の間に納まっているか確認する。 (今回9000以下と予測するコントラクトアドレスを対象に、これまでの勝敗の結果を出力してみる。 0~9999の間で9000以下と予測するため、結果自体も90%以内に収まっているか確認をする。)

 確認対象のコントラクトアドレス
 ・0x7DA90089A73edD14c75B0C827cb54f4248D47eCc (≦9000)

本家Webページを見ると、直近10件の取引履歴しか表示されていないが
ブロックチェーンの特性として、実行されたコントラクト結果は確認できるはず。

Step1.コントラクトコードを読む

まず、ブロックチェーンに組み込まれているvDiceのコントラクトを見てみよう。

   function bet()
        payable
        onlyIfNotStopped {

        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        if (oraclizeFee >= msg.value) throw;
        uint betValue = msg.value - oraclizeFee;
        if ((((betValue * ((10000 - edge) - pwin)) / pwin ) <= (maxWin * getBankroll()) / 10000) && (betValue >= minBet)) {
            LOG_NewBet(msg.sender, betValue);
            bytes32 myid =
                oraclize_query(
                    "nested",
                    "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random.data.0', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateSignedIntegers\",\"params\":{\"apiKey\":${[decrypt] BIYkzb1GQzRZFNsTzF7fh+n8VmT8GEyW3mHYlrU8It5O6/bam6/LVVxqkury8YZDJPjm0mWQeqQGebGAVSFrFw16/VHJ65QMFBfIHN2frhav/d10ARqECjoOvse5v4/DIT3LQUHPEx0Z/5UdtqYTQydW/pbC5BM=},\"n\":1,\"min\":1,\"max\":10000${[identity] \"}\"},\"id\":1${[identity] \"}\"}']",
                    ORACLIZE_GAS_LIMIT + safeGas
                );
            bets[myid] = Bet(msg.sender, betValue, 0);
            betsKeys.push(myid);
        }
        else {
            throw;
        }
    }

vDiceのコントラクト自体、下記のurlからAPIにより乱数を取得していることがわかる。
https://api.random.org

次にgetBet関数、numBets関数を見てみる。

    function getBet(uint id)
        constant
        returns(address, uint, uint) {

        if (id < betsKeys.length) {
            bytes32 betKey = betsKeys[id];
            return (bets[betKey].playerAddress, bets[betKey].amountBet, bets[betKey].numberRolled);
        }
    }

    function numBets()
        constant
        returns(uint) {

        return betsKeys.length;
    }

getBet関数は引数にbetsKeys.length > idとなる値を入力することでプレイヤーのアドレス、Bet額、結果(numRoll)を返す。

下段にあるnumBets関数はbetsKeys.lengthを返すことがわかる

つまり、過去の結果をすべて出力するためには次のことさえできれば良い。

1.numBets関数でbetskeys配列の要素数を取得する。
2.getBet関数をbetskeys配列の要素数分実行する。

Step2.コントラクト結果を参照してみる

ためしにEtherscan上から、結果を確認する。

getBet関数にid=7600を入力し、Queryボタンを押す。

https://etherscan.io/address/0x7DA90089A73edD14c75B0C827cb54f4248D47eCc#readContract
9.png

出力されるのは、上から次のとおり
・プレイヤーアドレス
・ベット額(ETHの単位にするには10^18で割る)
・結果

Step3.環境の準備

 Ethereumのライブネットと同期した自ノードの環境を準備する。
 http://qiita.com/umidachi/items/44a50ce86e94ff0df8f2

Step4.コントラクトメソッドをABI形式にする。

次のコントラクトメソッドについて、ABI形式のファンクションパラメータを作る

  • numBets関数
gethコンソール
> web3.sha3("numBets()").substring(0,10)

結果:
"0xdf06f906"

numBets関数は引数がない。そのためファンクションパラメータは16進4バイトデータだけですむ。

  • getBets関数
gethコンソール
> web3.sha3("getBet(uint256)").substring(0,10)

結果:
"0x061e494f"

一方、getBet関数の引数はuint256。
そのため、abiのメソッドパラメータは16進4バイトデータ+
32バイトデータの構成になる。

pythonでabiパラメータを作るとしたら、次の記述だと楽に作れる。


b="0x061e494f" #getBet(uint256)
d="{0:064x}".format((bet_id))
getBet_abi=b+d

Step5.pythonで実装する。

サービス当初のベット結果から直近の結果を得るまで、pythonの記載は次のようになる。

vDice.py
import requests
import json

#Json-rpcでeth_callを呼び出す
def eth_call(url,to,data):
    headers = {'content-type': 'application/json'}
    payload = {
        "jsonrpc": "2.0",
        "method": "eth_call",
        "params":[{"to":to,
                   "data":data},
                   "latest"],
        "id": 0
    }

    response = requests.post(url, data=json.dumps(payload), headers=headers).json()
    #print(response)
    return response['result']

def numBet():
    contract_address={"vdice":"0x7DA90089A73edD14c75B0C827cb54f4248D47eCc"}
    numBets_abi="0xdf06f906"
    result=eth_call(url,contract_address['vdice'],numBets_abi)
    i=int(result,16)
    return i

def getBet(bet_id):
    contract_address={"vdice":"0x7DA90089A73edD14c75B0C827cb54f4248D47eCc"}
    b="0x061e494f" #getBet(uint256)
    d="{0:064x}".format((bet_id))
    getBet_abi=b+d

    result=eth_call(url,contract_address['vdice'],getBet_abi)

    d=result[2:194]    #先頭0xの表記を省略
    #print(d)
    print("PlayerAddress:",d[24:64])
    print("AmountBet:",int(d[64:128],16)/pow(10,18))
    print("numRoll:",int(d[129:193],16))


if __name__ == "__main__":
    url = "http://localhost:8545"
    betnum=numBet()

    print("---betnum:",betnum)
    for i in range(1,betnum):
        print("---num:",i)
        getBet(i-1)

今回、betNumは7642が最大であるため、0~7642回分のgetBet関数の結果を出力する。

出力結果

---betnum: 7642
---num: 1
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.22
numRoll: 7821
---num: 2
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.2104874128878282
numRoll: 3903
---num: 3
PlayerAddress: 3f69019bc9d6e62432900caa46021e16c1f376a6
AmountBet: 0.2104874128878282
numRoll: 7746
---num: 4
PlayerAddress: 3f69019bc9d6e62432900caa46021e16c1f376a6
AmountBet: 0.2104874128878282
numRoll: 353
---num: 5
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.2104874128878282

........
........

補足:
getBet関数の生のレスポンスは例として次のようになる

getBetのレスポンス
{'jsonrpc': '2.0', 'id': 0, 'result': '0x0000000000000000000000006bd933140ff6b40
630ef12928979eb22d17ddc7100000000000000000000000000000000000000000000000004174e8
b1d32337e0000000000000000000000000000000000000000000000000000000000000056'}

'result'に結果が格納されるが内容は次のとおりとなっている。

・プレイヤーアドレス [32byte]
・ベット額(ETHの単位にするには10^18を割る)[32byte]
・結果(0~9999)[32byte]

先頭0xを含めると194文字が返却されるため
受け入れ結果をわかりやすくするため次の記載をしている。

参考
    d=result[2:194]    #先頭0xの表記を省略
    print("PlayerAddress:",d[24:64])
    print("AmountBet:",int(d[64:128],16)/pow(10,18))
    print("numRoll:",int(d[129:193],16))

確認結果

取得結果をCSVに出力するコードに書き換え、下図のとおり散布図にしてみた。

chart.png

横軸がベット回数。右に行くほど、直近の結果になる。
縦軸はベット結果(0~9999)を示す。

図を見る限り満遍なくプロットされているように見える。

結果として
- 総ベット数:7642回
- 出力結果が9001以上になった回数(つまり予想が外れた回数):762回

つまり
9000以下が出力される確率: 1 - ( 762/7642 ) =0.9003 ≒ 90 %
 
上記結果から、次のvDiceのコントラクトは統計的に90%相当の確率で的中することがわかる。
 ・0x7DA90089A73edD14c75B0C827cb54f4248D47eCc (≦9000)

ブロックチェーンは同期さえしていれば、不特定多数の人間がコントラクトの結果を見ることができる、興味深い仕組みだ。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2