モバイルルータ+MVNO SIMな感じを用意しようかな

理由は2つ。

ひとつは、家族でシェアしている通信容量を気にしながら通信しているのが微妙にしんどい事。

もうひとつは、久々にモバイルルータを使ってみたい事。

2012年頃、今は亡き(?)EMOBILEのLTE回線を契約した。当時はドコモでSC-02Cを使っていたのだが、この端末はLTE通信に対応しないため、新しいLTEとかいうのに触れてみたいと思いつつも、機種変更するお金も無かった折に(分割購入なので、当時まだ払い切っていない)、外側だけLTEにして内側は無線LANの速度になれば、3G端末を使っていてもLTEが体感できるのではないか?と思って、ドコモのデータ通信を絞って、EMOBILEと契約したのだ。

当時は長く使っていくつもりだったので、ドコモドメインのメールでやっていた諸々を、EMOBILEのメールサービスに移行したし、次世代モデルのモバイルルータが出ればそれらしい時期に機種変更するつもりだった。

しかし、丁度2年が経って契約更新?となっていた頃には、その熱はもうすっかり冷めてしまっていた。

(SC-02Cの支払いが終了したタイミングでスッとL-04Eを購入していたため)LTE対応端末が普通に手に入っていた事、それから、EMOBILEと契約した時に入手していたモバイルルータGL04Pが普通に使いにくかった事が理由だ。

音声通話は3大キャリア、データ通信はEMOBILEやWiMAX等のモバイルルータで、みたいな環境にしていた人がこぞって言うのは「電源の管理が面倒」という事。

確かに、スマホ(或いはタブレット)単体で運用するよりも、1台多くの端末電源を管理しないといけないので、労力は増える。とは言え、モバイルバッテリー(当時は大容量モノが少なかったので、5000mAh程度のものを複数持ち歩いていた)があれば特に問題なく充電できていた。

不便を感じたのは、熱処理。いつもモバイルルータ本体は鞄の中に入れて持ち歩いていたのだけど、本体がめっちゃ熱を持ってしまって、まともに通信できる状態じゃなくなってしまうのだ。しかもその状態になると充電すらまともにできない。とにかく冷やす必要があるが、屋外で冷やす道具なんて持ってやしない。モバイルバッテリーを組み合わせて、アクティブクーラーを自作しようかと思ったこともあるけど、結局やらなかった(工作するよりもゲームに時間を費やしていたかった)。

で、冷えるまでは通信ができないので、スマホそのものが使いにくい感じになってしまう。

という訳で、データ通信を外側に出しておく運用は、EMOBILEの2年契約が満了を迎えるタイミングで、やめてしまった。

さて、現在は、家族で10GBの通信容量をシェアして(我が家は全員ドコモなのだ)使っているが、殆どの通信は自分1人が食いつぶしている状態。とは言え、通信制限にかかるほど大胆に通信することは殆ど無い。稀に、新幹線の中で映画が見たいと思い立って、急に数GBの通信をするとか、そういう事はしないでもないけど。

要は自分がほんの少しだけ気をつけていれば、それで問題なく運用できる状態であった。

ところが、ここ最近どうも様子が変わっていて、これまで殆ど通信していなかった家族が、月によって多くなる事がちょいちょいある。それを察知して、自分が調整して容量を残すようにしていたけど、何回か通信制限を食らってしまう事があった。

それならいっそ、自分のデータ通信だけ外側にまた追いやってしまって、家族の契約はもう少し小容量…と言っても5GBプランしか存在しないが、その程度にしても良いのではないかと思うようになった。

つまり、家族みんなはみんなで好きにやってくれ、自分だけ抜けさせてもらう、みたいな感じだ。ドコモドメインメールだけは残そうと思ってるので、データだけ逃がす。

幸い、今はMVNOの選択肢も多く、割安で使いやすい。

で、問題は端末をどうするかという話。基本線の利用はドコモで良いと思う。普段の利用からそうたくさん通信する訳ではないからだ。使うときは一極集中だし、それかテザリングとかなので、メインのスマホで使うことを強制する必要はない。

LTEが使えるノートPCというのも魅力的だが、1年ほど前にDellのXPS9360を購入したので買い替えは流石に時期尚早。

そんで、2012年当時よりもモバイルルータの省電力性能も向上しているだろうし(?)、またそういう環境にしてみても良いのではなかろかと思った次第だ。

考えているのは、回線がエキサイトモバイルの最適料金プランで、端末はNETGEARのAC785という組み合わせ。

使わないときは多分とことん使わないので、使用量に応じて料金が変わっていってもらえると管理も楽ちんで良い。容量増えた時のコストだけ言えば勿論最安ではないけど、管理コストも含めればコスパはめっちゃ良いはず。

端末選択は結構てきとうで、安物過ぎないこと、聞いたことあるメーカーであること、ドコモ系MVNOが利用できバンド的にも問題ないこと、くらいしかこだわってない。

Huawei製品が沢山目につくが、2012年頃に使っていたものがHuawei製だったのでなんとなく敬遠。

NECのAterm MR05LNがデュアルSIMいけてまあまあ使いやすくていいよ、みたいなのをよく聞くんだけど、そういえばNETGEARって名前よく聞くけど使ったことないなっていう、NECに何のいわれもない理由で排除してしまった(自宅の固定回線ルータにずっと使ってたのよね、NEC)

というわけで、何を選ぶのか決めてしまっているので、すぐにでも契約できるのだが…

ここ最近出費がかさんでいること、それから、もう少し通信容量をウォッチしてから決めようという気持ちでいる。

特に後者は、これまでは月末近辺になって「そろそろだぜ」メールが来て、マジ!?って感じで確認して、あ〜確かにという気持ちになってそこから調整するという流れだったので、状態の分析を全然してないのだ。

半分以上は好奇心で通信環境変えるって話なので実益はぶっちゃけ薄くとも良いけれど、一応ね。

契約したらまた書きます。

コンパクトでカスタマイズできるメカニカルキーボードKUMO

Kickstarterで資金調達中のプロジェクト。

キーの配置が気分で気軽に交換できる楽しそうなやつ。

よくある自分で組み立て系のキーボードは、半田ごて片手に、気合を入れて毎スイッチについてソルダリングしないといけないものが多いようだけど、こちらはぐいっと押し込むだけっぽい。

 

また、レイアウトはWeb上で自由にカスタマイズできる。どうやってキーボードに反映させるかはようわからんかった。

レイヤー機能を持ち、レイヤーをトグルさせてキーの振る舞いを変更させながら使うみたいだ。ITmediaの記事によると、レイヤーは15枚まで使えるようだが、そんなにたくさん管理し切る自信が無い。Steam Controllerにも似たような機能があったが、2枚までで記憶が限界。

そして見て分かる通り、44キーのコンパクトな製品で、HHKBみたいなのを彷彿とさせるけど(スイッチが全然違うけど)、試しにレイアウト作っても、気に入らなければ変えてしまえば良いのだ。

キーボードのキーが少ないということは面積も少ないという事で、つまりは手が動く範囲が小さくなるため、手首への負担もかなり少ないはず。自宅でメインで使っているのがフルサイズのリアフォってあたり、説得力無いけど。

 

デフォルトではKailh製のCherry MX互換スイッチが付属してくる。スイッチ種別は3種から選べるようだ。が、これは自分で用意しても良い。気分でスイッチを変えたって良いのだ。

 

ハードウェアのみならず、ソフトウェア(レイアウト)まで含めて、自分で育て上げる事と、手軽さの共存したバランスの良い製品という印象。

 

ところで、上記見ればわかるとおり、資金調達は未達成なのである…

そしてこのプロジェクトはAll or Nothingのため、2週間と少し後の期限たる8/18(土)の1:48までに残りの25%程度が集まり切らなければ、無かったことに……

残りのプレッジは150USDのプランしか無いので、とすると更に145人くらい集まらないといけないのか。

最終日付近になったら駆け込みがあるだろうけど、そんなに沢山集まるかな~~~どうだろう

印刷禁止サイトを印刷する

下書きにおいてあったけど公開してなかったシリーズ。

 

日本電気技術者協会の解説講座、学生時代は結構お世話になったんだけど、研究で何度も見に行くうちに、あーこれ印刷して机にでも貼り付けておきたいなと思うようになった。

でもここ印刷禁止なのよね。

 

当時は大学でやってる研究が目的で印刷したかったんだけど、著作権法を眺めてると「授業の過程における使用に供することを目的とする場合には、必要と認められる限度において、公表された著作物を複製することができる」って記述があるんだよね。

研究も、履修登録を要する仕組みなので、「授業」の一環だよね!と解釈すれば、複製可能なはず。あくまで個人の勝手な解釈です。あと当該サイトに印刷に関する記述がぱっと見つけられないのも怖い。

 

という訳で、てきとうにソースコード眺めてると、window.onbeforeprintとかいうものが。

これ、最初はerase関数を実行するようになっているので、これを別ので再定義してしまえば良いのでは?と発想。

CSSでbodyにdisplay:noneを設定しているみたいなので、これをblockにしてしまえ

というわけで

window.onbeforeprint=function(){
document.body.style.display='block';
};

これであとは印刷し放題

 

ソース読めばそれとなくわかるし、大して難しい事もしてないけど、本当におkなのかは知りません。JSが読み書きできると、Webに関するいろんな事ができるよ、の一例でした。

kindle paperwhite買ってみた

別段すごく安くなってたって印象ではないけど、こないだのプライムセールで多少安くなってたので、買ってみた。

Wi-Fiモデル、キャンペーン情報無し。

もともと、買うならキャンペーン情報は無しだなと思ってたし、3Gモデル楽しそうだなとは思ってたけど案外価格差あるしで、取り敢えずエントリーしてみるのに丁度よいモデルを選んだつもり。

一応画面保護フィルムと、ぱたぱたのカバーも用意(Amazon純正)。スクリーンに対する防御が2重になっちゃってやり過ぎ感もあったけど。充電器はスルー。USB microBで充電できるので、わざわざ専用に用意する意味がない。

 

 

使ってみた感じ、やっぱ画面はきれいだ。画素密度もきちんとあるので。それから、ぱたぱたのカバーはスマホも含めて一切使ったことが無いんだけど(持つ手を制限される構造が気に入らない)、でもカバーの開閉でkindleのスリープが自動で切り替わるのはなかなか快適。

一週間ほど使ってみて、思ったより電池が持たない。別に画面輝度最大にしてるって訳じゃない。必要な時以外は輝度最低にしてるし、必要なときも半分以下くらいしか光らせてない。それでも、日に2時間程度の利用で、電池残量表示が目減りしていく印象がある。印象なので、事実からは大きく離れているかもしれないけどね。

 

 

さて、kindle使って外で気軽に読もうと思ってたのは、物欲を刺激されない(取り敢えず読めればいい)ラノベやコミック、あとてきとうな読み物、それから、できたらお勉強の本も読めたらなーとか。

 

いざ買ってみて思ったのは、結論から言うと、元々が文庫本サイズじゃないとかなり厳しいということ。

加えて、(これは前からずっと思ってるけど)画像として配信される書籍はもっと厳しい。

 

まず読んだのはラノベ。そもそもサイズが近いという事もあり、また文字データとして配信されているから字もきれいで、大変読みやすい。惜しむらくは、表紙やカラーページも白黒で見るしかないということ。まぁその辺は他のデバイスで見ればいいんだし、そこはぶっちゃけ中身に大きな影響を与えない。挿絵はそもそも白黒で印刷されているし。

ちなみに妹ちゃん推しです。

 

次に読んだのはコミック。長く続いているからモノを集めるのはしんどいけど気楽に読みたくなるもの、という事でNARUTOをチョイス。

大判コミックじゃないのはわざと。丁度いいサイズを狙った結果。物欲刺激されないが読みたいというタイトルがぱっと見つからなかったのもある。

これもかなり読みやすかったが、見開きページがつらい。一応画面下の方に「全体のうちのこの辺がうつってますよー」という表示はあるのだが、これがあるからと言って見えていない範囲が見えるようになる訳じゃない。

 

次はこちら。

結城浩著、暗号技術入門。もとはA5判らしく、そんなに大きくないのでいけるかなと思い。

しかしこれが一番キツい。文字がめちゃくちゃ小さい上に、凄く薄い。画像として配信されているので、調整しようもない。

薄くて何も見えない

 

正直かなり目を凝らして読む状況で、全然無理。

電子版で買ってしまったので、どうやって読もうかめっちゃ悩んでいるところである。

 

きちんとものを選べばかなり快適に読める事がわかったのは、大変収穫だった。つまりは、画像としてではなくテキストで配信されているコンテンツは、大変快適。

 

今は、ラノベとして

これを読みつつ、読み物として、じわじわと

これを読んでいる。

まぁなんていうか、予想できていた事だけど、めっちゃ気軽に迂闊に本を買っちゃうので、Wi-Fiモデルでよかったなって思うよ。無限に購入しちゃうもん。

改行コードを変換する

nkfでokだけど、メモ書き。ふと必要になった時は忘れているのだ…

 

Linux環境で吐き出した計算結果ファイルをWindowsに持ってきてそこで処理しようとすると(Linux環境で開発した自前ツールで計算した結果をWindows環境にインストールしたMATLABでグラフ化したかった)、改行コードの違いでクソ面倒な事に…

 

改行コードはCarriage Return(CR)とLine Feed(LF)の組み合わせが存在して、MacはCR、unix系(Linux)はLF、WindowsではCR+LFと完全にバラバラな状態となっている。どれかに統一すりゃいいじゃんという気持ちでいっぱい。

 

nkfでは、-L[uwm]オプションで改行コードの変換ができる。

-Lu→unix(LF)

-Lw→Windows(CR+LF)

-Lm→Mac(CR)

 

今回はWindows(CR+LF)の形に変換したかったので、

nkf -Lw –overwrite hoge.txt

みたいな感じで。(オプションで–overwriteをつけると上書き保存される)

 

Kumanの3.5インチモニタを回転させる

これ

今回はRaspberry pi 3 model Bに挿してつかう。

付属のCDにそのまま使えるRaspbianのイメージが入っているが、これを使うのは何されるのかわからんな?という声もよく見る。しかしまぁ、どのネットワークにも接続しないで使う予定だったので、ドライバ等の自前導入はせず、付属のイメージを使うことにした。

で、用途上はモニタを90度回転させると丁度良い感じなので、回転させる。

 

類似の商品が多いのでてきとうに調べていたら、2種類の情報があって、/boot/config.txtに

・display_rotate=90

・lcd_rotate=90

とかを書くとかいうのを得た。

ただし、前者だと画面は回転するがタッチスクリーンが回らなくて操作しんどい、後者だといい感じ!みたいな論調ばっかり。この口ぶりから分かる通り、うまくいきませんでした。lcd_rotateだとそもそも画面が回転しなかった。(要は別製品の情報なのかね)

 

Kumanのこの製品はWaveshareの3.5inch RPi LCD (A), 320×480 – waveshareのコピー製品、という情報を得たので(下の方にAnti-PiracyとかThis product is protected by patentとか書いてあるけど気にしない…)、Waveshareのそれっぽいサイトを眺めることにする。

5inch HDMI LCD -Waveshare Wiki

Screen orientation settings及びTouch screen calibrationの項で全てが解決した。キャリブレーションはメニューにも選択肢が最初からあったのでそっちを使っても良いかも。

 

きちんと一次情報を当たろうねとか、自前できちんと環境構築しようねとか、思いました(いつもの事では?)

Raspberry Pi 3 Model BでBrother HL-L2300を使う

環境はRaspbian(久々のDebian系だ)

 

一先ずCUPSを入れておく

$ sudo apt-get install cups

brotherから公式に提供されているドライバはi386なのでRaspberry piでは使えない…

という訳でぐぐってみたところ、それっぽいものをgithubで発見。

pdewacht/brlaser – github

対応プリンタ一覧にHL-L2300は無いが、HL-L2300Dがある。同一モデルのロケール違いっぽいのでそのまま突っ込む。

ビルドに必要なものをインストールしてから、REAMD.mdの通りに操作

$ sudo apt-get install build-essential
$ sudo apt-get install cmake libcups2-dev libcupsimage2-dev
$ git clone https://github.com/pdewacht/brlaser.git && cd brlaser
$ cmake .
$ make
$ sudo make install
$ sudo service cups restart

プリンターを接続し、CUPSのWebインターフェイスからプリンターを追加。

brlaserをインストールすると、モデル一覧にBrother HL-L2300D, using brlaser v4が出現するので、ここから追加すればおk

 

このままlprコマンドから印刷しようとすると怒られる。

$ lpr -o fit-to-page ./test.pdf
lpr: エラー - 利用可能なデフォルトの宛先がありません。

登録してやれば良い。一覧をlpstatで表示して、これをlpoptionsで指定する。

$ lpstat -p
プリンター Brother_HL-L2300_series は待機中です。2018年03月02日 00時55分09秒 以来有効です
$ lpoptions -d Brother_HL-L2300_series
copies=1 device-uri=usb://Brother/HL-L2300………(以下略)

あとはlprコマンドで普通に印刷できる。

PhantomJSで遊ぶ

Javascriptでいじれる、webkitベースのヘッドレスブラウザであるPhantomJSでスクレイピングを行う。

だいぶ前に、ファミマTなクレジットカードのログイン画面がクソ認証を導入したのでログインにマウス操作が必要になってしまった。どうせ見に行く情報も限られているし、この際スクレイピングしてしまおう。

クソな画像認証

 

 

まずはログイン画面の仕組みを探す。いじってみると、正解の記号(ここでは星)のみ掴めるようになっていて、他はただ置いてあるだけ。droppable関係を見てみれば良いのかな?とか思いながらソースコードやインスペクタとにらめっこしていると、「画像認証に必要なJS」とかいういかにもなコメントの下にjsファイルが3つくっついていた。

これを見てみる(HTMLにもJSにも丁寧にコメントが書いてあるので解釈が捗る)と、droppableの設定の下にdropした時の挙動が書いてあり、その辺の動作を真似てしまえば画像認証を突破できるのでは?という気持ちに。…突破も何も、ただソース読めば誰にだってわかる内容である、難読の工夫は見られないし、ご丁寧にコメントで可読性もバシバシ向上。

 

という訳でPhantomJSのお話。

てきとうなjsファイルを作成して、それをphantomjsに食わせると動く。

$ sudo pacman -S phantomjs
$ cat hello.js
console.log('hello phantom');
phantom.exit();
$ phantomjs hello.js
hello phantom

 

 

取り敢えずログイン画面を表示してスクショをとってみるのが以下。

var page = require('webpage').create();
page.open('https://wis.pocketcard.co.jp/netservice/login?type=ft', function(status) {
 console.log("Status: " + status);
 if(status === "success") {
  page.render('screen_shot.png');
 }
 phantom.exit();
});

 

実際にはページ遷移を挟むので参考URLを見ながらそれらしいものを書いたのが以下。今月の請求額をぴょろっと吐き出して終了する。

var page = require('webpage').create();

page.onInitialized = function() 
 page.evaluate(function() {
  document.addEventListener('DOMContentLoaded', function() {
   window.callPhantom('DOMContentLoaded');
  }, false);
 });
};

var funcs = function(funcs) {
 this.funcs = funcs;
 this.init();
};
funcs.prototype = {
 init: function() {
  var self = this;
  page.onCallback = function(data) {
   if(data === 'DOMContentLoaded') self.next();
  }
 },
 next: function() {
  var func = this.funcs.shift();
  if(func !== undefined) {
   func();
  } else {
   page.onCallback = function(){};
  }
 }
};

new funcs([
 function() {
  page.open('https://wis.pocketcard.co.jp/netservice/login?type=ft');
 },
 function() {
  page.evaluate(function() {
   $('#username').val('USERNAME');
   $('#password').val('PASSWORD');
   $('#imgCertf').val('1');
   $('#command').submit();
  });
 },
 function() {
  page.render('out.png');
  var seikyu = page.evaluate(function() {
   return $.trim($('div.table-cell-width501:first div span.bold').text());
  });
  console.log("seikyu:" + seikyu);
  phantom.exit();
 }
]).next();

 

 

 

ページ内でJSな処理を行うには、page.evaluate(function(){処理});みたいな感じでやれば良く、データを取り出すには、その処理からreturnしてあげれば良い。単純だ。

 

スクレイピングと言えば、コニカミノルタの強そうな複合機の消耗品残量を見れるWebページから情報を取り出してチャットツールに放り込むスクリプトを書いた事があるけど、その時はScrapyを使った。Python何もわからんところからスタートしてやっていくのとてもとてもしんどかったし、Scrapyなんだかんだ複雑だった…(しかもそのWebページはJavascriptで情報更新だったのでただ見に行くだけでは情報は得られず、ScrapinghubのSplashを使って情報を取得したのだった)

Javascriptは遊んだ経験が多少あったので、違和感なく簡単にコードが書けてよかった。

ただ、ES6の構文(アロー関数とか)を何も言わずに使うと処理も進まず思考停止みたいな感じになってしまってつらいことがあった。しょうがないのでES6の構文は使わずに上記コードを書いていたけど、github(ariya/phantomjs)では

Hi. Answering to your questions:

  1. When we solve all issues listed here #14458
  2. Yes. 2.5 will have full support for ES2015.

とある。pacmanで降ってきたのは2.1.1だったし、gihubの最新Releaseも2.1.3ということで、まだのようだ。

それからエラーを何も吐いてくれない感じだったので(ここでは閉じカッコの対応を間違えるというただのSyntax errorだったけど)参考URLの通り、nodeに読み込ませるとスッと教えてくれた。なんなんだろ。

 

 

参考URL:

PhantomJS でログインが必要なページでも自由自在にスクレイピング – 凹みTips

CasperJS/PhantomJSでシンタックスエラー行を取得する方法 – yohgaki’s blog

 

 

蛇足:

研究室では今までMajestouch NINJA(茶軸)を使っていたんだけど、追加で自分用にリアフォ(104UB-S)を買った。本当は少しお値段ケチって静音モデルでない方(104UB)が欲しかったんだけど、どこにも在庫が無くてネット販売でも取り寄せだったので、諦めて静音の方。

打ち心地は勿論最高に良いです。ずっと触っていたくなる…という訳でこのキーボードは研究が落ち着くまで研究室に置きっぱなしにしておく()

んで、このリアフォで書く最初の(趣味の)プログラムがこれ、というわけ。最近ずっと研究関係のものしか書いてなかったので時間があいてしまった。これからよろしくね♡

GPD WINとArchLinuxInstallBattle

今更な話な上に大した内容でもなかった感。(この記事、実は何ヶ月も前に書いてあったんだけど、どういう訳か下書きのまま放置されてたんだよね、それを今回公開しただけという…)

 

本体ストレージをいじらずにいきたいので、小型のUSBメモリを用意して、そこにインストールする事にする。

BIOSでなくUEFIな環境にインストールする事を念頭に作業を進めれば特に問題は無い。

 

以下、引っかかったところとかをピックアップ。

 

・Live環境の起動時にUSB error -110を吐いて死ぬ

最初の見た目の状況としてはUSBメモリのラベル名が正しく設定されてない時に近かったが、USBメモリを見失って起動できなくなっているという状況の方が正しかった。

よくよく見てみるとUSB error -110を吐いて死んでるっぽかった。

usb – device descriptor read/64, error -110 – Stack Overflow

によると、要は電力が足りてないらしい。

USB3.0ポートにUSB3.0メモリを刺してるんだから動いて当然っしょと思っていたけど、何らかの理由で、電力供給が不安定になっていたみたい。しょうがないのでUSB2.0の延長ケーブルを通して接続してみたら、問題無く認識するようになった。

延長ケーブルを使うのは持ち歩きに邪魔なので、USB2.0メモリを使う事にした。

 

・画面向きがおかしいやつ

ArchWikiの通りに。

インストール後の環境にも適用するために、てきとうに設定しておく。今回はブートローダにgrubを使ったので、/etc/default/grubのGRUB_CMDLINE_LINUXにi915.fastboot=1 fbcon=rotate:1を追加しておく。

設定の適用はgrub-mkconfig -o /boot/grub/grub.cfgみたいな感じで。

 

・インストールしてからwifi-menuがdialogとかwpa_supplicantを要求するのでwifi-menuが使えない

これはGPD WINつーかただ自分がアホなだけ

Live環境からてきとうに入れておきましょう

 

GPD WINはおもちゃとして面白いと思うんだけど、外でホイホイっと使うには、入力系統がしんどくてあんまり気が乗らないんですよね。そういう意味ではGPD Pocketの方が優秀なんだけど、両方なんて買うお金は無いし、GPD WINの頃にGPD Pocketなんて知らなかったし…という

php-mposのPROP計算を追いかける

初のAdvent Calendar参加です。1日遅れですが、暗号通貨Advent Calendar 5日目に登録しました。

お茶漬けと申します。普段は都内で学生やってます。研究進まない。

昨日4日目は@zinntikumugai氏の仮想通貨を変換した時の金額を求めるTwitterBOT作ったお話でした。JPYやBTCを挟まずに、目的の通貨へのレートを一発で教えてもらえるTwitterBOTだそうです。中間に基準通貨のようなものを挟まずともレートを教えてくれるというのは、便利で良さそうです。新たに対応通貨を増やす等、精力的に活動しているようで、今後が楽しみですね。

 

さて、プールマイニングを行った場合に、報酬はShareの比率に従って分配されるとかなんとか言いますが、その比率とは何を元に算出されるのか、きちんと考えたことが無かったので、具体的に理解する事が目標です。報酬の分配方式としては、代表的なものにPPLNSがあると思いますが、ここではPROPのみに絞ります(単純に私の技量と気力が足りなかったので)。また、実装を見させてもらうのは、MPOS/php-mposです。

基本的にはgithubのソースを眺めて追いかけていきますが、随時データベースの中身も利用します(採掘経験のある、mposデータベースが手元にあるので)。

ちなみに私は、きちんとPHPを触ったことがない上に、人のコードを読む経験が非常に少ないので、かなり手探りになります。ご了承ください。また、間違い等あったらご指摘頂けると大変嬉しく思います。

 

TL;DR

比率の計算は各ユーザの提出したShare達のDifficultyの総和を元に行う。おしまい。

 

以下はphp初心者が手探りで読んでいった痕跡です。

 

眺める起点

  • /cronjobs/run-crons.shをcronで回すので、それを見てみる
~略~
# List of cruns to execute
CRONS="findblock.php proportional_payout.php pplns_payout.php pps_payout.php blockupdate.php payouts.php tickerupdate.php notifications.php statistics.php tables_cleanup.php"
~略~
for cron in $CRONS; do
 [[ $VERBOSE == 1 ]] && echo "Running $cron, check logfile for details"
 $PHP_BIN $cron $PHP_OPTS
done
  • $CRONSの中にproportional_payout.phpとかいうドンピシャくさいものがある

 

proportional_payout.phpを読んでみる

  • creditで検索してみると、122行目にAdd new credit transactionという文言がある

  • そこの周辺はこんな感じ

// Add new credit transaction
if (!$transaction->addTransaction($aData['id'], $aData['payout'], 'Credit', $aBlock['id']))
 $log->logFatal('Failed to insert new Credit transaction to database for ' . $aData['username'] . ': ' . $transaction->getCronError());
  • $transaction->addTransaction()に失敗したらログに出力するよ」みたいに見える

  • addTransaction()の引数にはid, payout, Creditとかそれっぽいキーワードが並ぶ

  • addTransaction()の定義はどこに?$transactionの定義を探せば良い?

  • $transactionはこの行が初出なのでこのファイルには無さそう

  • transaction->addTransactionでリポジトリ検索してみると、include/classes/transaction.class.phpが見つかる

  • 5行目に$table = 'transactions'があって、クエリにはINERT INTO $this->table...とか書いてあるので、transactionsテーブルをいじるクラスだと想像される

  • 18行目、addTransactionの引数リストを見ると、account_id, amount, type, …と並んでいるので、上記の$aData['payout']は払い出しの分量っぽいことがわかる

  • では$aData['payout']どうやって決定される?

  • proportional_payout.phpの95行目付近にそれらしいものが見つかる

$aData['percentage'] = ( 100 / $iRoundShares ) * $aData['valid'];
$aData['payout'] = ( $aData['percentage'] / 100 ) * $dReward;
  • 100 / $iRoundShares$aData['valid']を掛け算して出てきたpercentagedRewardの積がpayoutになる、といったところ

  • 不明な変数は3つ、$iRoundShares, $aData['valid'], $dReward

 

$dRewardの記述を探す

  • proportional_payout.phpの71行目に以下の記述
$config['reward_type'] == 'block' ? $dReward = $aBlock['amount'] : $dReward = $config['reward'];

普通のifで書き直したのはこちら

if(config['reward_type'] == 'block'){
$dReward = $aBlock['amount']
}else{
$dReward = $config['reward'];
}
  • config['reward_type']include/config/global.inc.dist.php(実稼働環境ではglobal.inc.phpになるだろうが)の244行目に$config['reward_type'] = 'block'の記述あり

  • つまり$dRewardには$aBlock['amount']が入る

  • さて$aBlock['amount']はどこで決まる?→49行目のforeachで$aBlockが出て来る、その元は$aAllBlocks→35行目で$block->getAllUnaccounted('ASC');で得られている

 

block->getAllUnaccounted()を読む

  • その定義はinclude/classes/block.class.phpの96行目~

  • 97行目のクエリは「blocksテーブルからaccountedが0のレコードを取得してくる」もの

  • blocksテーブルに並んでいるのはプールで発見したブロックたちのように見える

  • データベースにてdesc blocksすると、accountedカラムのDefault値が0なことがわかる→値を設定せずINSERTされれば0になる

  • block.class.php内のINSERTを探すとaddBlock($block)が見つかる

  • クエリを眺めるといかにもブロック発見時の動作に見えるが、利用シーンを探して検証する

 

addBlock()が呼ばれているところを探してみる

  • リポジトリ内検索をするとcronjobs/findblock.phpに1つあるのみ(82行目で$aDataを引数に呼ばれている)

  • $aDataはどこで得られるか?→53行目のforeachで出て来る、その元は$aTransactions['transactions']

  • $aTransactionsは41行目の$bitcoin->listsinceblock($strLastBlockHash)で得られている

  • listsinceblockは置いておいて、$strLastBlockHashを先に見てみる

  • $strLastBlockHashは30行目で$aLastBlock['blockhash']が代入されている

  • $aLastBlockは29行目で$block->getLastValid()が代入されている

  • include/classes/block.class.phpの24行目~getLastValid()の定義がある

  • そこにあるクエリは「blocksテーブルからconfirmationsが-1よりも大きいレコードでheightが最も高いものを1つ取得してくる」もの(つまり直近に見つかったブロックを取得する)

  • …つまり$bitcoin->listsinceblock($strLastBlockHash)は直近に発見したブロックのblockhashを引数に使う事になる

  • listsinceblock()をリポジトリ内検索してみても、ヒットは無い

  • PHPには、存在しないメソッドを呼び出した場合の動作を決めるマジックメソッド__callがあるので、それを使っているのでは?

  • block.class.php内には__callは見当たらないので、requireしてるinclude/lib/jsonRPCClient.phpを見てみると83行目からそれらしい記述がある

  • coindに喋りかけてそうな感じに見えるが、それも確認してみる

  • パラメータの流れを見ると$urlあたりから全部来てる→__construct$urlが代入されている

  • $urlはコンストラクタの引数なので、それを利用するシーンを探す

  • bitcoin.class.phpの241行目からjsonRPCClientクラスを継承したBitcoinClientクラスの定義で、基底クラスのコンストラクタは278行目で実行される→その引数$uriは277行目で定義されるが、そのデータはBitcoinClientのコンストラクタの引数から与えられる

  • BitcoinClientをリポジトリ内検索すると、include/classes/bitcoinwrapper.class.phpで定義されるBitcoinWrapperが見つかる(これはBitcoinClientを継承するクラス)

  • bitcoinwrapper.class.phpの112行目でインスタンスの生成が行われており、そのパラメータ$config['wallet']['username']$config['wallet']['password']等はinclude/config/global.inc.dis.phpの70行目付近で設定されている

  • このパラメータはウォレットにアクセスする情報なので、やはりinclude/lib/jsonRPCClient.phpの83行目~はcoindに喋りかけているようだ

  • coindのコマンドとしてのlistsinceblockは「指定ブロック以降の(ウォレットに影響を与えた)トランザクションを取得」

  • つまり$aTransactionsは「直近に見つかったブロック以降の、ウォレットに影響を与えるトランザクション全て」になる

  • 要は、addBlockの引数は「直近に発見したブロック以降ウォレットに影響を与えたトランザクション」であって、blocksテーブルは発見したブロック群であると思われる

 

結局$dRewardは何が入るか

  • 発見したブロック群からUnaccountedなレコードのみを取り出してきたのが$aAllBlocksである(UnaccountedはMPOSが未だ処理していないという意味と思われる)

  • $aAllBlocksのうちの1つ…のamountが$dRewardに代入される

 

一旦休憩、まとめ……

  • そろそろ忘れてるのでまとめると、
$aData['percentage'] = ( 100 / $iRoundShares ) * $aData['valid']
$aData['payout'] = ( $aData['percentage'] / 100 ) * $dReward

 

  • で報酬が決められているのでは?と思う

  • $dRewardは「直近に発見したブロック以降で、ウォレットに影響を与えたトランザクションたち」のうちの1つ、のamountが入る

  • つまり$dRewardは「発見したブロックで得られた報酬の量」である

  • 残りの不明な変数は$iRoundShares, $aData['valid']の2つ

 

$aData['valid']には何が入っている?

  • $aDatacronjobs/proportional_payout.phpの79行目で$aAccountSharesから得られる事がわかる

  • その次のところにSkip users with only invalidsとあるので、恐らく各ユーザのValidなShareの量である

  • $aAccountSharesは69行目で$share->getSharesForAccounts($iPreviousShareId, $aBlock['share_id']);が代入されている

  • include/classes/share.class.phpの96行目~getSharesForAccountsの定義がある

  • そこにあるクエリは、

SELECT
 a.id,
 SUBSTRING_INDEX( s.username , '.', 1 ) as username,
 a.no_fees AS no_fees,
 IFNULL(SUM(IF(our_result='Y', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS valid,
 IFNULL(SUM(IF(our_result='N', IF(s.difficulty=0, POW(2, (" . $this->config['difficulty'] . " - 16)), s.difficulty), 0)), 0) AS invalid
 FROM $this->table AS s
 LEFT JOIN " . $this->user->getTableName() . " AS a
 ON a.username = SUBSTRING_INDEX( s.username , '.', 1 )
 WHERE s.id > ? AND s.id <= ? AND a.is_locked != 2
 GROUP BY username DESC

 

 

  • ややこしいのでFROM句から見ていく。それぞれ変数やメソッドから出力されたものに書き直すと以下のような状態
FROM shares AS s
LEFT JOIN accounts AS a
ON a.username = SUBSTRING_INDEX( s.username, '.', 1 )

 

 

  • sharesテーブルはユーザが提出したShareの記録、accountsテーブルはユーザ一覧

  • sharesにおけるusernameはユーザ名.ワーカ名であって、accountsにおけるusernameはユーザ名であることに注意すれば、accountsに存在するユーザが提出したShareたちを取得しようとしている事がわかる

  • WHERE句はs.id > $previous_upstream AND s.id <= $current_upstream AND a.is_locked !=2という事らしい

  • 最後はロックされているかのチェックぽいので良いとして、もう一つは(まとめて書くと)$previous_upstream < s.id <= $current_upstreamであるが、その変数は呼び出し元ではどう扱われているか?

  • proportional_payout.phpの67行目に$iPreviousShareId = @$aAllBlocks[$iIndex - 1]['share_id'] ? $aAllBlocks[$iIndex - 1]['share_id'] : 0;の記述がある

  • $iIndex$aAllBlocksをforeachしたときのインデックスである(49行目)

  • 発見したブロック群のうち、Unaccountedなものである$aAllBlocksのうち、1つ前のshare_idが前述の不等式において$previous_upstreamで使われる(share_idがfalseの場合には0が使われる)

  • $current_upstream$aBlock['share_id']が使われる(今処理しているブロックのshare_idが使われる)

  • つまり「1つ前のブロック発見~今のブロックまでに提出された範囲内に絞る」という意味になるようだ

  • 最後に、SELECTするカラムのうち3つは、a.id, SUBSTRING_INDEX( s.username, '.', 1 ) as username, a.no_fees AS no_feesで、大変簡単で、それぞれアカウントID(ユーザ名でなくデータベース上のID)、ユーザ名、feeの状況。

  • valid, invalidについては、適宜並べてみると、以下のようになる

IFNULL(
 SUM(
  IF(
   our_result='Y',
   IF(
    s.difficulty=0,
    POW(2, (" . $this->config['difficulty'] . " - 16)),
    s.difficulty
   ),
   0
  )
 ),
 0
)
 AS valid

 

  • まずSUMから読んでいくと、our_result='Y'の時に最深のIF()を、our_result='N'の時0を足し算していく

  • 最深のIF()は、各Shareレコードのdifficultyが0だったら2^($this->config['difficulty'] - 16)を、0でなかったらdificultyをそのまま返す($this->config['difficulty']include/config/global.inc.dist.phpの237行目で定義されている)

  • SUMがNULLだった場合には、0を返す

  • invalidではour_resultのY, Nが逆になっている

  • 最後にGROUP BY usernameがあるので、各ユーザごとにまとまるようだ

  • つまりは、各ユーザが提出したShareのDifficultyのそれぞれの総和を返すものと思えば良い

  • そしてshares.difficultyはワーカに割り振られたdifficultyの値らしい(SELECT difficulty FROM shares WHERE username='てきとうなワーカ名' ORDER BY id DESC LIMIT 1;で得られた値と、マイナー側に示されているDiffが一致しているので)

 

$iRoundSharesには何が入っている?

  • cronjobs/proportional_payout.phpの70行目で$share->getRoundShares($iPreviousShareId, $aBlock['share_id']);が代入されている

  • getRoundSharesという名前から察せられるが、当該ラウンドのShare量が返されているのであろう

  • クエリのgetSharesForAccountsとの差は、GROUP BY句が無い事、our_result='Y'以外は無視する事

 

不明だった変数をまとめると

  • $iRoundShares:当該ラウンドにおいて全ユーザが提出したSharesのDifficultyの総和

  • $aData['valid']:当該ラウンドにおいて各ユーザが提出したSharesのDifficultyの総和

  • $dReward:当該ブロックにてcoindが得た報酬

 

これらを踏まえて最初の式を見ると

$aData['percentage'] = ( 100 / $iRoundShares ) * $aData['valid']
$aData['payout'] = ( $aData['percentage'] / 100 ) * $dReward

 

  • てきとうに代入して書き換えてみると、
$aData['payout'] = ( $aData['valid'] / $iRoundShares ) * $dReward

 

  • つまり、ブロック報酬$dRewardをラウンド内のSharesの割合で分配する、その割合の計算元は、SharesのDifficultyの総和である