1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

超シンプルなブロックチェーンを実装する

Last updated at Posted at 2024-11-26

はじめに

以下の記事を元に超シンプルなブロックチェーンを実装します。
https://blog.logrocket.com/how-to-build-a-blockchain-in-rust/

実装は元記事同様Rustですが、細かいところがわからなくても読めるように書いたので、最後まで読んでいただけると嬉しいです。

実装

まず、データが格納されるブロックとブロックをつなげたチェーンの2つの構造を定義します。

pub struct Blockchain {
    pub chain: Vec<Block>,
}

#[derive(Debug, Clone)]
pub struct Block {
    pub index: u64,
    pub hash: String,
    pub previous_hash: String,
    pub timestamp: u128,
    pub data: String,
}

BlockchainはBlockを単純につなげるベクトルとします。

Blockの各フィールドは以下のように利用します。

  • index: Blockの番号。今回は0, 1, 2, ...と連番で利用
  • hash: Block全体をsha256でハッシュした値
  • previous_hash: 1つ前のBlock全体をsha256でハッシュした値
  • timestamp: Blockを作成した時刻
  • data: Blockに格納されているデータ

Block::new()メソッド

データ構造を定義したら、Blockを生成するためのメソッドを定義します。

impl Block {
    pub fn new(index: u64, previous_hash: String, data: String) -> Self {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("The time is something wrong")
            .as_millis();

        // ハッシュ値の計算
        let hash = Self::calculate_hash(index, &previous_hash, &data, timestamp);

        Block {
            index,
            hash,
            previous_hash,
            timestamp,
            data,
        }
    }

    fn calculate_hash(index: u64, previous_hash: &str, data: &str, timestamp: u128) -> String {
        let input = format!("{index}{previous_hash}{data}{timestamp}");
        let hash = format!("{:x}", Sha256::digest(input.as_bytes()));
        hash
    }
}

newメソッドの内容はシンプルで、現在のtimestampを取得し、{index}{previous_hash}{data}{timestamp}の4つをハッシュしているだけです。

Blockchain::new()メソッド

Blockchain側でも初回Blockを追加できるようにnewメソッドを作成します。

impl Blockchain {
    pub fn new() -> Self {
        let genesis_block = Block::new(0, String::new(), "Genesis Block".to_string());
        Blockchain {
            chain: vec![genesis_block],
        }
    }
}

Block::newメソッドを使ってVec型データの最初の値にしているだけです。最初のBlockであるため、previous_hashは空文字列とします。ちなみにブロックチェーンではよく最初のブロックをGenesis Blockと呼びます。ここでも最初のブロックのdata値にGenesis Blockと入れることにします。

Blockchain::add_block()メソッド

Blockchainに初回のBlockを作成するメソッド(new()メソッド)を作成したら、2つ目以降のBlockを追加できるようにメソッドを定義します。

impl Blockchain {
    ...

    pub fn add_block(&mut self, data: String) {
        let previous_block = self.chain.last().unwrap();
        let new_block = Block::new(previous_block.index + 1, previous_block.hash.clone(), data);

        if new_block.is_valid(previous_block) {
            self.chain.push(new_block);
        } else {
            panic!("Failed to add block: Block is invalid.");
        }
    }

    pub fn is_valid(&self, previous_block: &Block) -> bool {
        self.previous_hash == previous_block.hash
            && self.hash
                == Self::calculate_hash(self.index, &self.previous_hash, &self.data, self.timestamp)
    }
}

新しいBlockの作成には、Block::new()メソッドを使用します。このメソッドの重要なポイントは、直前のBlockのハッシュ値をprevious_hashとして利用している点です。この仕組みにより、ブロック同士がチェーンのように繋がった構造をになり、データの改ざんを難しくします。
また、実際にBlockChainに追加する前に追加するBlockが正しいことを確認(Block::is_validメソッド)します。

Blockchain::is_chain_valid()メソッド

最後にBlockChain全体が正しいかどうかを確認するメソッドを作成します。
"Genesis Block"から順にハッシュ値を検証し、ブロックチェーンに問題がないことを確認します。

impl Blockchain {
    ...
    pub fn is_chain_valid(&self) -> bool {
        for i in 1..self.chain.len() {
            let current_block = &self.chain[i];
            let previous_block = &self.chain[i - 1];

            if !current_block.is_valid(previous_block) {
                return false;
            }
        }
        true
    }
}

以上で実装したいデータ構造、メソッドが完成しました。

main()関数

実際に作成したコードを利用してみます。
今まで実装してきたコードを整理すると以下のようになります。

block.rs
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone)]
pub struct Block {
    pub index: u64,
    pub hash: String,
    pub previous_hash: String,
    pub timestamp: u128,
    pub data: String,
}

impl Block {
    pub fn new(index: u64, previous_hash: String, data: String) -> Self {
        let timestamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("The time is something wrong")
            .as_millis();

        let hash = Self::calculate_hash(index, &previous_hash, &data, timestamp);

        Block {
            index,
            hash,
            previous_hash,
            timestamp,
            data,
        }
    }

    fn calculate_hash(index: u64, previous_hash: &str, data: &str, timestamp: u128) -> String {
        let input = format!("{index}{previous_hash}{data}{timestamp}");
        let hash = format!("{:x}", Sha256::digest(input.as_bytes()));
        hash
    }

    pub fn is_valid(&self, previous_block: &Block) -> bool {
        self.previous_hash == previous_block.hash
            && self.hash
                == Self::calculate_hash(self.index, &self.previous_hash, &self.data, self.timestamp)
    }
}

blockchain.rs
use crate::block::Block;

pub struct Blockchain {
    pub chain: Vec<Block>,
}

impl Blockchain {
    pub fn new() -> Self {
        let genesis_block = Block::new(0, String::new(), "Genesis Block".to_string());
        Blockchain {
            chain: vec![genesis_block],
        }
    }

    pub fn add_block(&mut self, data: String) {
        let previous_block = self.chain.last().unwrap();
        let new_block = Block::new(previous_block.index + 1, previous_block.hash.clone(), data);

        if new_block.is_valid(previous_block) {
            self.chain.push(new_block);
        } else {
            panic!("Failed to add block: Block is invalid.");
        }
    }

    pub fn is_chain_valid(&self) -> bool {
        for i in 1..self.chain.len() {
            let current_block = &self.chain[i];
            let previous_block = &self.chain[i - 1];

            if !current_block.is_valid(previous_block) {
                return false;
            }
        }
        true
    }
}
main.rs
mod block;
mod blockchain;

use blockchain::Blockchain;

fn main() {
    pretty_env_logger::init();

    // create a new blockchain
    let mut blockchain = Blockchain::new();
    log::info!("{:?}", blockchain.chain[0]);

    // Add blocks
    blockchain.add_block("First block".to_string());
    blockchain.add_block("Second block".to_string());
    blockchain.add_block("Third block".to_string());

    // print the blockchain
    for block in &blockchain.chain {
        println!("{:?}", block);
    }

    // Validate the blockchain
    println!(
        "Check if the blockchain is valid: {}",
        blockchain.is_chain_valid()
    );
}

実行結果

cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/rust_simple_blockchain`
Block { index: 0, hash: "ab793e5f08ef2d0c3200999a7a9e92789c131451152603552e5050f8b9df93d2", previous_hash: "", timestamp: 1732669772313, data: "Genesis Block" }
Block { index: 1, hash: "8266fcc02a8428e30fb98d5f127c1157088305650801df0b2e38349d32ce665b", previous_hash: "ab793e5f08ef2d0c3200999a7a9e92789c131451152603552e5050f8b9df93d2", timestamp: 1732669772315, data: "First block" }
Block { index: 2, hash: "f896bdbd7011d8147103f704979e6c201de7b37aa7987ee6e26779d8b9df125b", previous_hash: "8266fcc02a8428e30fb98d5f127c1157088305650801df0b2e38349d32ce665b", timestamp: 1732669772316, data: "Second block" }
Block { index: 3, hash: "043062a264ffec3a78d166de40525b2d2d6e0dd05d2b8dd3cb521953bc110b60", previous_hash: "f896bdbd7011d8147103f704979e6c201de7b37aa7987ee6e26779d8b9df125b", timestamp: 1732669772316, data: "Third block" }
Check if the blockchain is valid: true

さいごに

上記の実装は以下リポジトリに置いています。
https://github.com/hsmto25519/rust-simple-blockchain

最後までお読みいただきありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?