「XP Coinを効率良く鋳造するために取引を一つにまとめる方法とは?」みたいな記事をいくつか見かけ、本当にまとめたほうが得なのかが気になってソースコードを読んだ。「このコインはこういう使い方をします」とか「どこそこへの上場を目指します」だけではなくて、アルゴリズムの詳細もホワイトペーパーに書いてほしい。
- https://github.com/peercoin/peercoin/
- https://github.com/eXperiencePoints/XPCoin/
- https://github.com/NEETCOINProject/NEETCOIN/
PeercoinはBitcoinからフォークしていて、GitHubに明示はされていないけれど、XP CoinとNEETCOINはPeercoinが元らしい。
XP Coinは40万 XPくらいを数か月持っていて、この前1度鋳造に成功したが、ブロックチェーンに入らなかった。NEETCOINは初期に採掘した分と、プロジェクトにビットコインを寄付して返ってきたぶんで1,000 NEETくらい持っていて、1日1回くらい鋳造している。Peercoinは持っていない。
Proof of Stakeとは
Aさんが暗号通貨をBさんに送金するとき、Aさんが自分の所有する暗号通貨をBさんに送金する意思があることは、ブロックチェーンにもProof of何とかにも頼らずに、自分の秘密鍵で取引に電子署名することで証明できる。ただ、単に電子署名をするだけではAさんが後から送金済みの暗号通貨をCさんにも送金すること(二重送金)を防げない。そこで、この取引をブロックチェーンに格納し、簡単にはブロックチェーンを書き換えられないようにすることで、二重送金を防いでいる。Bさんは、自分宛の送金がブロックチェーンに格納され、書き換えられる可能性が充分小さくなった(ブロックチェーンの長さが充分伸びた)ことをもって、自分宛の送金が完了したと見なせる。この状態ならば、AさんがCさんにも送金しようとしたところで、既存のブロックチェーンに矛盾するので弾かれる。
ブロックチェーンを書き換えられないようにする仕組みとして、一般的なのがProof of Work(PoW)。ブロックチェーンに繋ぐためには莫大な計算量が必要な仕様にすることで、AさんがBさんに送った取引が格納されたブロックチェーン以外のチェーンを作ることが(莫大な計算量無しには)不可能になる。計算量を得るためには電力が必要だということで問題になっている。
ビットコインマイニングの消費電力が世界159ヶ国の各消費量より多い現状
電力が必要な計算量ではなく、コインを所持していることでチェーンを伸ばせるようにしようというのがProof of Stake(PoS)。PoWでブロックを生成することをmining(採掘)と言うのに対して、PoSはminting(鋳造)と言うらしい。大量にコインを持っている人ならばブロックチェーンを好きに書き換えることができてしまうけれど、そんなことをするとその通貨の価値が下がるから、そんなことはしないという話になっている。上手くできている。まあ、PoSはPoSで、ブロックチェーンが分岐したときに、ネットワーク参加者は分岐した両方のチェーンを承認するのが最適戦略になってしまう(Nothing at Stake)など問題はあるらしい。
PoSのもう1個の問題点として、コインを溜め込んでいれば増えるのでコインが流通しなくなるということが言われている。私はこの見方には懐疑的で、例えばコインの総量が10%増えたのならば、1コインあたりの価値が10/11になり、所持しているコインの価値は変わらないのではないかと思っている。むしろ、鋳造のためにはネットワークに接続している必要があり、大量のコインをネットワークに繋がった状態にしておくとセキュリティ的なリスクがあるので繋ぎたくないと考えると、コインを持っていればいるほど不利になるのではないだろうか。
ただし、これは鋳造できるコインの枚数の期待値が、所持しているコインの枚数に比例しているならばの話。あるいは、大量のコインを持っている人が大半の時間はネットワークに接続せずたまに接続して、常に接続していた場合とほぼ変わらない枚数のコインを鋳造することはできるだろうか? ということで、報酬がどのように計算されているかが気になる。
PoSの報酬の計算方法
PoW
BitcoinのPoWでは、過去の難易度とどの程度の頻度でブロックが採掘されたかで難易度を決める値が定まり、その値以下のハッシュ値を得ることで、チェーンにブロックを繋げられる。PoWでもPoSでも何らかのインセンティブが無いとブロックを採掘(鋳造)する人がいなくなるので、ブロックを採掘(鋳造)した人には報酬が与えられる。ブロックの最初の取引だけは、入力無しに好きなアドレスに出力できる。PoWでは報酬の額は4年(21万ブロック)ごとに半減する固定値。2018年現在は12.5 BTC。これにブロックに含めた取引の手数料が加わる。
鋳造確率
PeercoinなどのPoSで、ブロックを繋げられるかどうかの判定はCheckStakeKernelHash
として実装されている。
// Now check if proof-of-stake hash meets target protocol
if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay)
return false;
hashProofOfStake
は、これまでのブロックの内容、Stakeの元となる出金、現在時刻から求まるハッシュ値。なお、Bitcoinなどの暗号通貨では、アドレスが保有するコインの量はチェーンに記録されておらず、取引だけが記録されている。あるアドレスが保有するコインとは、そのアドレス宛の出金でまだ使用されていないものである(UTXO, Unspent Transaction Output)。あるアドレス宛に複数の出金があった場合、この処理では別に扱われる。ハッシュ値には現在時刻が含まれているので、毎秒ごとに値が変わる。1秒に1回ガチャを引くイメージ。
bnCoinDayWeight
は、Stakeの元となる出金の金額×出金からの日数。ただし、この日数には、MinAge
とMaxAge
というパラメタがあって、MinAge
を引いた値が使われ、MaxAge
以上にはならない。このパラメタは各通貨で変更されている。
bnTargetPerCoinDay
は、PoWと同様にこれまでの難易度と鋳造頻度から決まる値。
出金の時刻が同じならば、Stakeの元となる出金の金額に比例して、鋳造できる確率が上がることが分かる。
鋳造金額
GetProofOfStakeReward
に実装されている。通貨ごとに、ブロック高さで金額が変わるなどの処理はあるものの、基本的にはnCoinAge
×定数。
nCoinAge
を計算しているのは、CTransaction::GetCoinAge
。Stakeの元となる出金の金額×出金からの日数。鋳造確率と異なり、MaxAge
などのパラメタは無い。日数が経過すればするほど鋳造できる金額は上がる。ただ、鋳造も普通の取引と同じ扱いなので、一度鋳造したら日数は0に戻る。
鋳造の取引に含まれる全ての入力のCoinAgeの和となっている。一方、鋳造確率では最初の入力だけが使われる。公式のウォレットでは、鋳造の取引を作成するときに同じアドレスに対する出金が他に存在すれば全て入力に加えるようになっている。XP Coinだけは、後述する出金を分割処理が行われなかった場合のみ。他のアドレスに対する出金が含まれていても、弾くような処理は見当たらなかったので、鋳造が成功したときは自分が持っている全てのコインを入力に含めるようにすれば、鋳造金額が増えて得かもしれない。
鋳造金額も(そのアドレスに対する出金が1個だけならば)Stakeの元となる出金の金額に比例している。
各通貨の現在のパラメタ
Peercoin | XP Coin | NEETCOIN | |
---|---|---|---|
目標PoSブロック生成間隔 | 10分 | 1分30秒 | 1分 |
MinAge | 30日 | 1日 | 4時間 |
MaxAge | 90日 | 3日 | ∞ |
単位通貨×年あたりのPoS報酬 | 0.01 | 0.4 | 0.21※1 |
目標PoWブロック生成間隔 | 2時間?※2 | 1分 | 1分 |
PoW報酬 | 40くらい?※3 | 0 | 2.1 |
2018年4月9日現在の総発行量 | 24,699,053 | 257,904,000,000 | 2,669,384 |
- ※1: 5万ブロックごとに2,500ブロックは2.1
- ※2: max(2時間, 10分×(直近のPoWブロック数+1))
- https://github.com/peercoin/peercoin/blob/fd1d96ed6e7d73fa96f4d656031057fcaf9004e5/src/main.cpp#L1245
- PoWブロックが減れば減るほど、PoWブロックの生成間隔が延びるという処理で意図が分からない
- 実際のブロックを見るとほとんどがPoS
- ※3: 計算が複雑で分からん。報酬の計算に難易度が入っていて良いのか?
通貨によって大きくパラメタが変わっていて面白い。
これらの通貨はPoSだけではなく、PoWによってもブロックが生成できるのでPoWのパラメタも記載している。全てをPoSで生成すると全てのコインが初期開発者由来になってしまい、それはズルくない?という話になるからだと思う。XP Coinも過去にはPoWで報酬が得られた。
PoS報酬の期待値
報酬が得られる確率も金額も所持金額に比例するので、報酬の期待値は所持金額の2乗に比例しそうだが、ここにコインを受け取ったり鋳造してからの日数が絡んでくるし、さらに鋳造確率はこの日数に上限があるので、分からなくなってくる。シミュレートしてみよう。
import random
n = 100
rewardCoinYear = 0.1
probCoinYear = 0.001
amount = 100
reward = 0
count = 0
for _ in range(n):
r = 0
c = 0
lastMintTime = 0
for time in range(24*365):
coinYear = amount * (time-lastMintTime) / (24.*365)
if random.random() < coinYear * probCoinYear:
r += coinYear * rewardCoinYear
c += 1
lastMintTime = time
reward += r
count += c
print "amount:", amount
print "reward:", reward/n
print "count:", float(count)/n
1秒に1回ではシミュレーションが終わらないので、1時間ごとにした。probCoinYear
の値は実際には鋳造に参加しているコインの枚数に依存するので適当。鋳造期待回数がそれっぽくなっていれば良いだろう。複利は考慮していない。実行結果は、
amount: 100
reward: 9.69434931507
count: 23.49
ほぼ、amount*rewardCoinYear
になる。amount
の値を変えると、
amount: 1
reward: 0.0759385844749
count: 2.05
amount: 10
reward: 0.91321347032
count: 7.01
amount: 100
reward: 9.69434931507
count: 23.49
amount: 1000
reward: 99.195890411
count: 74.11
初期枚数が少ないと不利に見えるが、これは1年の終わりのほうのコインが成長(?)したけれど鋳造できなかった分があるから、シミュレーション期間を10年にすると
amount: 1
reward: 0.973428424658
count: 23.02
となる。
ここで気が付いたが、話は簡単で、鋳造に成功したときに得られる報酬は、最後に鋳造してからの経過日数に比例するので、あるタイミングで鋳造できなかったとしたら、それはそのまま持ち越される。長い時間を考えればいつかは鋳造に成功して、そのときに経過日数に比例した報酬が得られる。
コインをずっと持ち続けるのならば、量に関わらず、1年当たりrewardCoinYear
の報酬が得られる。まとめてもまとめなくても変わらない。ただ、コインはいつかは誰かに送るので、そのときに成長していた分は無駄になる。鋳造できる確率はコインが多いほど高くなるので、その分はまとめていないと損になる。損するのは、送金する時点での鋳造期間の期待値×rewardCoinYear
。1年に1回しか鋳造ができない程度の量だと、だいぶ損。
ウォレットを立ち上げていなくて鋳造に参加していない期間があっても、複利とブロック鋳造時の手数料収入を除けば、損にはならない。暗号通貨をPoS方式にする利点として、報酬欲しさにウォレットを立ち上げ続ける人が増えてネットワークが安定するということがあると思っていたが、そんなことは無いらしい。
コインの分割
一定時間内に鋳造された(それだけコインの総量が大きい)コインは鋳造時に2個に分割される。Peercoinは120日、XP Coinは4日、NEETCOINは1日4時間(GetWeight
の返り値と比較しているのでMinAge
の分を足す)。
if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) < nStakeSplitAge)
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
読み切れていないが、少数の大きな取引だけから鋳造されるのは、ネットワークのセキュリティ的に良くないらしい。何となく分かる気はする。
この分割がされていないからといってネットワークに弾かれることは無いようなので、ユーザーとしてはこの処理を潰すとちょっと得なのだろうか?
考察
鋳造によって得られる報酬の期待値をコインの所持枚数に比例させたいのならば、鋳造確率を枚数に比例させ、ブロック報酬を一定にすれば良いと思っていた。そのほうがコインの発行総数も分かりやすくなるし、ユーザーが常にウォレットを立ち上げているインセンティブにもなる。しかし、現在の方式でも報酬の期待値はほぼコインの所持枚数に比例する。鋳造に参加しているコインの総数に依存して報酬が変化することもない。どちらが良いのだろう?