はじめに
前回の記事でブロックチェーンの構造を定義しました。その実装にマイニングを追加します。Rustで実装していますが、Rustがわからなくても問題ないようにプログラムを書くようにしていますので、よければ最後まで見てみてください。
https://qiita.com/hsmto25519/items/1f867918784b85c944e7
前回同様、以下の記事を参考にしています。
https://blog.logrocket.com/how-to-build-a-blockchain-in-rust/
実装
マイニングはある特殊な値を探す作業になります。
その作業の中で重要になるのがnonceとtargetと呼ばれる2つになります。
nonce追加
マイニングを実装するにはBlockにnonceを追加します。
#[derive(Debug, Clone)]
pub struct Block {
pub index: u64,
pub hash: String,
pub previous_hash: String,
pub timestamp: u128,
pub data: String,
pub nonce: u64, // 追加
}
nonceをハッシュ値の計算に取り入れることにより値が変わるようになります。timestamp値によってもハッシュ値を変えることはできますが、その場合は時間に依存する実装となってしまうため都合がよくありません。
targetとは
次にtargetと呼ばれる値を導入します。
targetは端的にいうとマイニングの難易度です。今回のシンプルなブロックチェーンではtargetを見つけることをマイニングと呼ぶことにします。また、今回はtargetではなくシンプルにDIFFICULTYと呼びます(こちらのほうが直感的であるため)。
今回は0x0000で始まるハッシュ値を見つけることを目的にします。
/// This is the mining difficulty.
pub const DIFFICULTY_PREFIX: &str = "0000";
(※今回は0000で始まるハッシュ値を探していますが、bitcoinではtargetと呼ばれる値よりも小さいハッシュ値を見つけます。また、今回の実装ではDIFFICULTYを固定してますが、bitcoinでは動的に更新されます。)
Block::mine_block()メソッド
impl Block {
...
fn mine_block(index: u64, previous_hash: &str, timestamp: u128, data: &str) -> (u64, String) {
info!("mining block...");
// find a nonce that makes the hash start with the DIFFICULTY_PREFIX
let mut nonce = 0;
loop {
if nonce % 100000 == 0 {
info!("nonce: {}", nonce);
}
let hash = Self::calculate_hash(index, previous_hash, timestamp, data, nonce);
if hash.starts_with(DIFFICULTY_PREFIX) {
info!("mined! nonce: {}, hash: {}", nonce, &hash,);
return (nonce, hash);
}
nonce += 1;
}
Block::mine_block()メソッドは前回実装したBlock::calculate_hash()メソッドを拡張したようなメソッドになっています。
calculate_hashに各データを渡してハッシュ値を計算し、そのハッシュ値が条件にマッチしているかどうかを確認します(今回は0000で始まるハッシュ値)。条件に合えばマイニングが成功し、nonce値とhash値を返します。条件に合わなければnonceを更新して条件に合うハッシュが出るまで続けます。
Block::is_block_valid()メソッド
マイニング作業を導入したため、Blockの検証プロセスを以下のように変更します。
細かいところはいくつか変わっていますが、重要なのはハッシュ値が0000で始まる要求を満たしているかどうかを確認するプロセスを追加している部分になります。この検証によりBlockを大量に作成するのを防止できます。(今回の例では時間がかかりませんが、DIFFICULTYを調整することにより計算コストが増えて時間がかかるようになります)
impl Block {
...
pub fn is_block_valid(&self, previous_block: &Block) -> bool {
// check if the previous hash is correct
if !(self.previous_hash == previous_block.hash) {
return false;
}
if !self.is_valid_hash() {
return false;
}
// check if the hash meets the difficulty requirement
if !self.hash.starts_with(DIFFICULTY_PREFIX) {
return false;
}
// check if the block is sequential
if !self.index == previous_block.index + 1 {
return false;
}
true
}
動作確認
RUST_LOG=info cargo run
...
INFO rust_simple_blockchain::block > mining block...
INFO rust_simple_blockchain::block > nonce: 0
INFO rust_simple_blockchain::block > mined! nonce: 47814, hash: 0000ae2c86921c1cc47eb97b75790b4abee1b8d3c063237a51ccd04723531a6b
INFO rust_simple_blockchain > Block { index: 0, hash: "0000ae2c86921c1cc47eb97b75790b4abee1b8d3c063237a51ccd04723531a6b", previous_hash: "", timestamp: 1733017636382, data: "Genesis Block", nonce: 47814 }
INFO rust_simple_blockchain::block > mining block...
INFO rust_simple_blockchain::block > nonce: 0
INFO rust_simple_blockchain::block > mined! nonce: 51821, hash: 0000da986b2cb8510a76adfe2d067ec7c2303cdd68f70575c6f7d3dfb7d967a4
INFO rust_simple_blockchain::block > mining block...
INFO rust_simple_blockchain::block > nonce: 0
INFO rust_simple_blockchain::block > nonce: 100000
INFO rust_simple_blockchain::block > mined! nonce: 132136, hash: 000044fc5eadb470e833fcbec25a753542f43ad3b987be6ad381e353bec551e6
INFO rust_simple_blockchain::block > mining block...
INFO rust_simple_blockchain::block > nonce: 0
INFO rust_simple_blockchain::block > mined! nonce: 34266, hash: 0000b257d52ee9230e2be0e7b095e5977ad0a7c63335a36c15f18764175a61eb
Block { index: 0, hash: "0000ae2c86921c1cc47eb97b75790b4abee1b8d3c063237a51ccd04723531a6b", previous_hash: "", timestamp: 1733017636382, data: "Genesis Block", nonce: 47814 }
Block { index: 1, hash: "0000da986b2cb8510a76adfe2d067ec7c2303cdd68f70575c6f7d3dfb7d967a4", previous_hash: "0000ae2c86921c1cc47eb97b75790b4abee1b8d3c063237a51ccd04723531a6b", timestamp: 1733017636587, data: "First block", nonce: 51821 }
Block { index: 2, hash: "000044fc5eadb470e833fcbec25a753542f43ad3b987be6ad381e353bec551e6", previous_hash: "0000da986b2cb8510a76adfe2d067ec7c2303cdd68f70575c6f7d3dfb7d967a4", timestamp: 1733017636878, data: "Second block", nonce: 132136 }
Block { index: 3, hash: "0000b257d52ee9230e2be0e7b095e5977ad0a7c63335a36c15f18764175a61eb", previous_hash: "000044fc5eadb470e833fcbec25a753542f43ad3b987be6ad381e353bec551e6", timestamp: 1733017637611, data: "Third block", nonce: 34266 }
Check if the blockchain is valid: true
ハッシュ値の先頭が0000のものだけが格納されるようになってますね!
実装は以上になります。
その他、細かい変更がいくつかあるため、具体的な実装は以下githubを参照ください。
https://github.com/hsmto25519/rust-blockchain
さいごに
マイニングを導入したことでよりブロックチェーンぽくなったかと思います。
最後までお読みいただきありがとうございました。