PX-Q3PE4で録画機の新調

某店でPX-Q3PE4が安くなってたので、衝動的に新調してしまった。新調したのは9月くらいなんだけど、下書きしておいたのを今公開ってやつ。

構成は以下の感じで。

CPU:Intel Core i3 8100

M/B:ASUS TUF B360M-E GAMING

RAM:てきとうに8GBx1

SSD:Samsung 970EVO 250GB

PSU:Corsair CX450M

ケースはIN WINの301が良い感じだったのでチョイス。

カードリーダーは事前に購入していたSCR3310を利用。

組み立てはまぁ良いとして、環境構築がうまくいくかが問題。

Ubuntuを使って、Mirakurun + Rivarun + Chinachu gammaみたいな感じの構成(あんまはっきりわかってない)を作る。PX-Q3PE4はPLEX公式からUbuntu向けドライバが出ているので、これを使う。

というわけで実際の手順。

1.取り敢えずUbuntu18.04 LTSをインストールして、アップデートしておく

$ sudo apt-get update
$ sudo apt-get upgrade

2.時刻合わせしてほしいのでそのへんの設定しておく

$ sudo apt-get install ntp
$ sudo vi /etc/ntp.conf
$ sudo systemctl restart ntp
$ ntpq -p

3.カードリーダーに必要なパッケージのインストール

$ sudo apt-get install pcscd pcsc-tools libpcsclite-dev
$ pcsc_scan
Japanese Chijou Digital B-CAS Card (pay TV)

4.ビルドに必要なパッケージのインストール

$ sudo apt-get install autoconf build-essential cmake curl git-core libssl-dev libtool libboost-all-dev pkg-config yasm

5.ARIB25ライブラリのインストール

$ git clone https://github.com/stz2012/libarib25.git
$ cd ./libarib25
$ cmake.
$ make
$ sudo make install
$ sudo /sbin/ldconfig

6.PX-Q3PE4ドライバ(カーネルバージョンがあっているかどうか確認しておく)

$ uname -r
4.15.0-23
$ wget http://plex-net.co.jp/plex/linux/Ubuntu18.04_64bit_kernel4.15.0-23.zip
$ unzip Ubuntu18.04_64bit_kernel4.15.0-23.zip
$ cd !$
$ sudo insmod tty_Virtual.ko
$ sudo insmod usb-px4.ko
$ lsmod | grep px4
usb_px4 454656 0
tty_Virtual 24576 1 usb_px4
$ ls /dev/px4*
/dev/px4-DTV0 /dev/px4-DTV2 /dev/px4-DTV4 /dev/px4-DTV6
/dev/px4-DTV1 /dev/px4-DTV3 /dev/px4-DTV5 /dev/px4-DTV7

7.ドライバが正しくロードできているので、インストール

$ sudo cp -p *.ko /lib/modules/<code>uname -r</code>/kernel/drivers/video
$ sudo depmod

8.root以外からもアクセスできるようにruleを作成(デフォルトはroot:rootなのでroot:videoに変更するのと、600から666にする)

$ sudo cat /etc/udev/rules.d/99-px4.rules
KERNEL=="px4*", GROUP="video", MODE="0666"

9.録画コマンドを用意する(実行権限なくて怒られるので適宜追加しておく)

$ wget http://plex-net.co.jp/download/linux/Linux_Driver.zip
$ unzip Linux_Driver.zip
$ cd ./Linux_Driver/MyRecpt1/MyRecpt1/recpt1
$ make clean
$ ./autogen.sh
$ ./configure --enable-b25
$ make
$ sudo make install

10.recpt1が正しくできていることを確認する

$ recpt1 --b25 --strip 27 10 ~/test.ts

11.Mirakurunが要求するNode.jsを導入(v8.9.4くらいを要求するみたいなのでそれを入れる)

$ sudo apt-get install nodejs npm
$ sudo npm cache clean
$ sudo npm install n -g
$ n ls
$ sudo n 8.9.4
$ sudo apt-get purge nodejs npm
$ sudo apt-get autoremove
$ node -v
v8.9.4

12.Mirakurunの導入

$ sudo npm install pm2 -g
$ sudo npm install mirakurun -g --unsafe --production
Version: pm2@3.0.0 [NG] Expected: &gt;=2.4.0 &lt;3.0.0

pm2のバージョンで怒られてしまったので入れ直す

$ sudo npm uninstall pm2 -g
$ sudo npm install pm2@2.4.0 -g
$ sudo npm install mirakurun -g --unsafe --production
$ sudo npm install rivarun -g
$ sudo npm install arib-b25-stream-test -g --unsafe

13.Mirakurunのstatusを見る

$ sudo pm2 status
Name mode status restart cpu memory
mirakurun-server fork online 0 0% 47.3MB

14.チューナー設定

$ sudo mirakurun config tuners

15.チャンネル設定

$ sudo mirakurun config channels

16.ログローテーションの設定

$ sudo pm2 install pm2-logrotate
$ sudo cat /etc/logrotate.d/mirakurun
/usr/local/var/log/mirakurun.stdout.log
/usr/local/var/log/mirakurun.stderr.log
/{
daily
compress
rotate 7
missingok
notifempty
}

17.rivarunから動作確認

//Unixソケット通信
$ rivarun --b25 --sid 1024 --ch GR/27 15 ~/test.ts
//TCP接続
$ rivarun --b25 --mirakurun localhost:40772 --sid 1024 --ch GR/27 15 ~/test.ts

18.chinachu gammaの導入

$ git clone -b gamma git://github.com/kanreisa/Chinachu.git ~/chinachu
$ cd chinachu
$ ./cinachu installer&nbsp; //1(Auto)を選択

19.録画予約用の空ファイル生成

$ echo "[]" &gt; rules.json

20.設定する

$ cp config.sample.json config.json
$ vi config.json
uidの値を実行ユーザへ書き換え
wuiHostをwuiOpenHostに書き換え

21.chinachuのログローテーション設定

$ sudo cat /etc/logrotate.d/chinachu
/usr/local/var/log/chinachu-operator.stderr.log
/usr/local/var/log/chinachu-operator.stdout.log
/usr/local/var/log/chinachu-wui.stderr.log
/usr/local/var/log/chinachu-wui.stdout.log
{
weekly
compress
rotate 4
missingok
notifempty
}

22.起動確認

$ ./chinachu service wui execute

23.pm2を使ってChinachu自動起動設定

$ sudo pm2 start processes.json
mirakurun-server, chinachu-operator, chinachu-wuiの3つが見えるはず
$ sudo pm2 save

24.EPG取得テスト

$ ./chinachu update

以上でおしまい。特に詰まるところもなく、割と単純に作業できてしまい、拍子抜け。

これまではPX-W3PEだったので、同時録画可能数が2倍になった。旧環境ではtvrockを使っていたので、更にリッチなUIになったし、CPUはAthlonII X2 250eを使っていたので、快適に動くようになった。衝動的ではあったけど、良い買い物でした。

お次はRyzenで組みたいって気持ちだな。

改行コードを変換する

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をつけると上書き保存される)

 

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の総和である