Reality Keysを使ったWebサービス「BY MY COIN」について(2/4)
はじめに
前回はBY MY COINの使い方について書きました。今回からはどうやって実装しているかについて調べます。
主なユースケースとして以下3つあります。今回は1.契約のセットアップについて書きます。
- 契約のセットアップ
- 初期表示とセットアップ後の待ち状態での画面表示
- Claim実行
目次
知りたいこと
以下のことが分かることをゴールにします。
- RK APIのリクエスト、レスポンス仕様。サンプルデータ
- マルチシグアドレス、P2SHアドレスのJSでの作成
- トランザクションのブロードキャスト方法(特にclaim時)
- 契約データの保存方法
- 豚アイコンの意味
RK RunKeeper API
Reality Keysが提供するRunKeeper APIの仕様はこれです。
リクエスト
Send a POST request to: https://www.realitykeys.com/api/v1/runkeeper/new
パラメータ
user_id RunKeeper ID = 29908850 activity アクティビティ running または = walking measurement 到達距離タイプ トータル または 累積 = total_distance comparison 比較 ge(等しいまたは大きい) lt(小さい) = ge goal 距離(m) = 4000 settlement_date 期日 = 2014-09-23 objection_period_secs 事実認証までの時間 = 604800 accept_terms_of_service 規約のバージョン = current use_existing use_existing アップデートする場合は1 = 1
リクエスト例
wget -qO- https://www.realitykeys.com/api/v1/runkeeper/new --post-data="user_id=29908850&activity=running&measurement=total_distance&comparison=ge&goal=4000&settlement_date=2014-09-23&objection_period_secs=604800&accept_terms_of_service=current&use_existing=1"
レスポンス
{ "activity": "running", "created_datetime": "2014-09-05 04:56:45", "evaluation_method": "ge", "goal": "4000", "human_resolution_scheduled_datetime": null, "id": 376, "is_user_authenticated": true, "machine_resolution_scheduled_datetime": "2014-09-23 00:00:00", "measurement": "total_distance", "no_pubkey": "02397f0f5cefd6610a6aa722baf0462c5fa6e172b585784ad07644c29ece1fd06a", "objection_fee_satoshis_due": 1000000, "objection_fee_satoshis_paid": 0, "objection_period_secs": 604800, "settlement_date": "2014-09-23", "source": "runkeeper", "user_id": "29908850", "user_name": "edochan", "user_profile": "edochan", "value": "4000", "winner": "No", "winner_privkey": "L38R5VMnFyGmAHLY6Cx25ky5QaeNN72QugPJXuCM4DJtTBt4hTuV", "yes_pubkey": "0315796f27cc850af4ff89b39b32e571b1e40c005b9943c14ae192e25d0918c5cd" }
machine_resolution_scheduled_datetimeは自動で事実認証する日時ですね。きっと。この情報は見たかったです。
user_nameが"edochan"になってます。カワイイな。
ソースコードを読む
契約のセットアップについて流れを見ていきます。
1.「Set this goal」ボタンクリック
<button id="set-goal-submit" type="submit" class="btn btn-primary set-goal-submit">Set this goal</button>
2.clickイベントハンドラ
$('#set-goal-submit').click( function() { register_contract(); return false; });
3.register_contractメソッド
- RKのRunkeeper API実行。レスポンスはdataに入る。
- P2SHアドレス作成。dataに格納する。
- シェア用のURL作成
- dataをwindow.localStorageに保存
- 契約データ追加(blockrから残高取得してdataに保存)
- 画面下部に契約情報追加(RKとblockrから情報を取り出す)
function register_contract() { $('body').addClass('registering-fact'); var url = oracle_api_base + '/runkeeper/new'; params = { 'user_id': $('#user').val(), 'activity': $('#activity').val(), 'measurement': $('#measurement').val(), 'goal': $('#goal').val(), 'settlement_date': $('#settlement_date').val(), 'comparison': 'ge', 'objection_period_secs': (24*60*60), 'accept_terms_of_service': 'current', }; (省略) var user_pubkey = $('#public-key').text(); // 画面表示時に作成される (省略) $.ajax({ url: url, type: request_type, data: params, dataType: 'json', }).done(function(data) { data['wins_on'] = wins_on; data['yes_user_pubkey'] = user_pubkey; data['no_user_pubkey'] = charity_pubkey; data['charity_display'] = charity_display; data['is_testnet'] = is_testnet; data['address'] = p2sh_address(data); var jump_to = '#' + sharing_url(data, false); document.location.hash = jump_to; $(document).scrollTop( $("#section3").offset().top ); store_contract(data); reflect_contract_added(data); (省略)
BY MY COINの場合、自分との戦いなので、YESは常に自分のpubkeyになり、NOはチャリティのpubkeyになります。P2SHアドレスは後で書きます。
data['yes_user_pubkey'] = user_pubkey; data['no_user_pubkey'] = charity_pubkey; data['address'] = p2sh_address(data);
4.p2sh_addressメソッド (register_contractから呼ばれる)
bitcore.jsのbitcore.Address.fromScriptを使ってスクリプトを元にP2SHアドレスを作成しています。
function p2sh_address(data, include_user_keys) { if (include_user_keys == null) { include_user_keys = true; } var script = redeem_script(data); address_version = data['is_testnet'] ? 'testnet' : 'livenet'; var addr = bitcore.Address.fromScript(script, address_version); return addr.toString(); }
5.redeem_scriptメソッド
bitcore.jsのTransactionBuilder.infoForP2shを使ってredeem_scriptを作成しています。(bitcore_monkey_patches.jsも使われています。詳細不明)
function redeem_script(data) { include_user_keys = true; var yes_pubkeys = [ data['yes_user_pubkey'], data['yes_pubkey'] ]; var no_pubkeys = [ data['no_user_pubkey'], data['no_pubkey'] ]; var user_pubkeys = [ data['yes_user_pubkey'], data['no_user_pubkey'] ]; // multisig group p2sh var opts = { nreq: include_user_keys ? [2,2,2] : [2,2], pubkeys: include_user_keys ? [ yes_pubkeys, no_pubkeys, user_pubkeys ] : [ yes_pubkeys, no_pubkeys ] }; var address_version = data['is_testnet'] ? 'testnet' : 'livenet'; var info = TransactionBuilder.infoForP2sh(opts, address_version); var p2shScript = info.scriptBufHex; return p2shScript; }
6.store_contractメソッド
window.localStorageに保存。キーは'contract_store'。
function store_contract(c, is_update) { contract_store = { 'contracts': {}, 'default': null } p2sh_addr = c['address']; contract_json_str = localStorage.getItem('contract_store'); if (contract_json_str) { contract_store = JSON.parse(contract_json_str); } if (!is_update) { if (contract_store['contracts'][p2sh_addr]) { return; // Already there } } contract_store['contracts'][p2sh_addr] = c; contract_store['default'] = p2sh_addr; localStorage.setItem('contract_store', JSON.stringify(contract_store)); }
ざっくりいうと以下情報を保存しています。
contract_store['contracts'][p2sh_addr] = c; // cはAPIレスポンス、画面の入力値など多数の情報。P2SHアドレス毎に保存 contract_store['default'] = p2sh_addr; // 使用用途不明
7.登録した契約情報に対して残高チェック
blockr.ioから残高をチェックします。登録時は残高0です。
function populate_contract_and_append_to_display(c) { var addr = c['address']; var url = c.is_testnet ? 'https://tbtc.blockr.io/api/v1/address/balance/' + addr+'?confirmations=0' : 'https://btc.blockr.io/api/v1/address/balance/' + addr+'?confirmations=0'; $.ajax({ url: url, type: 'GET', dataType: 'json', success: function(data) { c['balance'] = data['data']['balance']; populate_reality_key_and_append_to_display(c); }, error: function(data) { c['load_error_funds'] = true; populate_reality_key_and_append_to_display(c); } }); }
8.RKにアクセスして情報を取得します。
function populate_reality_key_and_append_to_display(c) { var url = oracle_api_base + '/runkeeper/'+c['id']+'/'+oracle_param_string $.ajax({ url: url, type: 'GET', dataType: 'json', }).fail( function(data) { c['load_error_fact'] = true; }).always( function(data) { $.extend(c, data); append_contract_to_display(c); }); }
9.画面に表示する項目を作成
APIと画面から取得した情報(パラメータc)を画面に表示します。 詳細は次回やりますが、claimボタンの実装もここに含まれています。
function append_contract_to_display(c) { var frm = $('#claim-form'); (省略)
まとめ
realitykeysdemo.pyと異なりブロードキャストしてません。BY MY COINは自分との戦いでありカウンターパーティリスク(Aliceに対するBobという意味で)が無いため、P2SHアドレスを作るときにFundする必要がないのだと思います。もちろん実装しだいでいくらでも変更できますね。
次回も引き続きソースコードを読みます。
参考
[iOS 7] JavaScriptCore Framework を使った Objective-C と JavaScript の連携ができるようになった
TransactionBuilder: A (hopefully) easy API to generate Bitcoin transactions
疑問(今度調べよう!)
- RKはCounterpartyリスクですよね。仮の仮の話でRKが無くなった場合にどうなるか。どういう解決策があるか考えます。