愛記システムの基本設計:フェデレーションモデルのブロックチェーン 要求分析④ | 続・ティール組織 研究会のブログ

続・ティール組織 研究会のブログ

ティール組織が話題になっているが、具現化するにはどうしたらよいか?
その研究を続けるにあたり、さらに次の形態である、続・ティール組織なるものまで視野に入れ、具体的な施策・行動内容を研究・支援する会。

先までは、"愛記"についての記載で、どのようにブロックチェーンSNSに組み込んで実装していけばよいのか、概念的なところからアプローチ方法を記載していった。概念設計としてはひとまず終えた。次は、フェデレーションモデル全体の基本設計といえるところまで、基本設計書に着手できるようなところまで、概念を具体化していきたい。

 

フェデレーションモデルのブロックチェーンの基本設計を進めるには、以下の手順に従って詳細に設計していくことが重要である。これにはシステムの機能、アーキテクチャ、データフロー、技術スタック、および具体的な実装方法の設計が含まれる。

基本設計のステップ

  1. 要求分析

  2. システムアーキテクチャの設計

  3. データフローの設計

  4. 技術スタックの選定

  5. データモデルの設計

  6. API設計

  7. セキュリティ設計

  8. エラーハンドリングとログ設計

基本設計の各ステップを順番に進めることで、フェデレーションモデルのブロックチェーンの詳細な設計が可能になる。各ステップでは、関係者との協議やレビューを通じて設計内容を確定していくことが重要である。

1.要求分析

まず、基本設計の最初のステップである要求分析をしていきたい。どのような機能が必要か、どのような問題を解決するのかを洗い出したい。要求分析はシステム設計の最初の重要なステップであり、システムが解決するべき問題と、必要な機能を明確に定義するプロセスである。以下に、メインチェーンとフェデレーションモデルでつながる市町村のブロックチェーンのプログラムに必要な機能と解決すべき問題を列挙してみよう。

解決すべき問題

  1. データの一貫性と整合性

  2. トランザクションの信頼性とセキュリティ

  3. スケーラビリティとパフォーマンス

  4. ガバナンスとコンセンサス

  5. システムの拡張性

  6. トランザクションのトレーサビリティ

必要な機能

  1. トランザクション生成

  2. トランザクションの検証

  3. トランザクション承認と統合

  4. ブロック生成

  5. 自治体の登録と管理

  6. 履歴証明(Proof of History)

  7. APIゲートウェイ

  8. ガバナンス機能

  9. セキュリティ機能

  10. スケーラビリティとパフォーマンス最適化
     

インフラストラクチャーの設計

前回記載した、インフラストラクチャーの設計におけるネットワークアーキテクチャの設計はとても重要であるので、今一度記載する。

  • ネットワークアーキテクチャの設計

    • ネットワークトポロジー(ノードの配置、接続方法、負荷分散など)
    • ネットワークセキュリティ(ファイアウォール、VPN、セキュア通信プロトコルなど)
    • サブネットの設計(パブリックサブネット、プライベートサブネット)
    • MongoDBクラスターを設置し、レプリカセットを使用してデータの冗長性と可用性を確保する。
    • ネットワークセキュリティを強化するため、VPC内にMongoDBサーバーを配置し、アクセス制御リスト(ACL)を設定する。

フェデレーションモデルにおける「 ネットワークトポロジーの設計 」は、システムの信頼性、パフォーマンス、セキュリティを確保するために非常に重要である。以下に、フェデレーションモデルにおけるネットワークトポロジーの設計手順を詳しく見ていこう。

1. ノードの種類と役割

まず、ネットワーク内で使用されるノードの種類とそれぞれの役割を定義する。

  • バリデータノード:トランザクションの検証とブロックの生成を担当。
  • フルノード:完全なブロックチェーンデータを保持し、トランザクションの検証を行う。
  • ライトノード:簡略化されたデータを保持し、トランザクションの送信や検証を行う。
  • APIゲートウェイ:クライアントからのリクエストを受け取り、バックエンドノードに転送する。

・ バリデータノード

バリデータノードは、トランザクションの検証とブロックの生成を担当。

struct ValidatorNode {
    id: String,
    blockchain: Arc<Mutex<Blockchain>>,
}

impl ValidatorNode {
    fn new(id: String, blockchain: Arc<Mutex<Blockchain>>) -> Self {
        ValidatorNode { id, blockchain }
    }

    fn validate_transaction(&self, transaction: &Transaction) -> bool {
        transaction.verify_signature()
    }

    fn generate_block(&self) {
        let mut blockchain = self.blockchain.lock().unwrap();
        let pending_transactions = blockchain.get_pending_transactions();
        let new_block = Block::new(blockchain.get_latest_block().index + 1, blockchain.get_latest_block().hash.clone(), pending_transactions);
        blockchain.add_block(new_block);
    }
}

・ フルノード

フルノードは完全なブロックチェーンデータを保持し、トランザクションの検証を行う。

struct FullNode {
    id: String,
    blockchain: Arc<Mutex<Blockchain>>,
}

impl FullNode {
    fn new(id: String, blockchain: Arc<Mutex<Blockchain>>) -> Self {
        FullNode { id, blockchain }
    }

    fn validate_transaction(&self, transaction: &Transaction) -> bool {
        transaction.verify_signature()
    }

    fn sync_with_network(&self, other_nodes: Vec<FullNode>) {
        // ネットワーク内の他のノードとブロックチェーンデータを同期するロジックを実装
    }
}

・ライトノード

ライトノードは簡略化されたデータを保持し、トランザクションの送信や検証を行う。

struct LightNode {
    id: String,
    blockchain: Arc<Mutex<Blockchain>>,
}

impl LightNode {
    fn new(id: String, blockchain: Arc<Mutex<Blockchain>>) -> Self {
        LightNode { id, blockchain }
    }

    fn send_transaction(&self, transaction: Transaction) {
        let mut blockchain = self.blockchain.lock().unwrap();
        blockchain.add_pending_transaction(transaction);
    }

    fn validate_transaction(&self, transaction: &Transaction) -> bool {
        transaction.verify_signature()
    }
}

・ APIゲートウェイ

APIゲートウェイはクライアントからのリクエストを受け取り、バックエンドノードに転送する。

use warp::Filter;

struct APIGateway {
    blockchain: Arc<Mutex<Blockchain>>,
}

impl APIGateway {
    fn new(blockchain: Arc<Mutex<Blockchain>>) -> Self {
        APIGateway { blockchain }
    }

    async fn handle_request(self: Arc<Self>, transaction: Transaction) {
        let mut blockchain = self.blockchain.lock().unwrap();
        blockchain.add_pending_transaction(transaction);
    }

    fn start(self: Arc<Self>) {
        let blockchain = self.blockchain.clone();
        let api = warp::path("transaction")
            .and(warp::post())
            .and(warp::body::json())
            .and_then(move |transaction: Transaction| {
                let api_clone = self.clone();
                async move {
                    api_clone.handle_request(transaction).await;
                    warp::reply::json(&"Transaction added")
                }
            });

        warp::serve(api).run(([127, 0, 0, 1], 3030));
    }
}

 

これらのノードが協力して動作することで、分散型のブロックチェーンネットワークが形成され、データの一貫性と整合性を保ちながら、トランザクションの処理とブロックの生成が行われる。
 

2. ノードの配置

次に、ノードの物理的および論理的な配置を計画する。

  • 地理的配置:ネットワークの分散度を高めるために、ノードを異なる地理的リージョンに配置します。これにより、システムの信頼性と可用性が向上します。
  • 論理的配置:各自治体ごとに独自のノードグループを配置し、それらがメインチェーンに接続されるようにします。

・地理的配置

地理的リージョンの定義:各ノードを配置する地理的リージョンを定義する。例えば、以下のようにノードを設定してみる。
struct Node {
    id: String,
    region: String,
    node_type: NodeType,
    blockchain: Arc<Mutex<Blockchain>>,
}

enum NodeType {
    Validator,
    Full,
    Light,
}

impl Node {
    fn new(id: String, region: String, node_type: NodeType, blockchain: Arc<Mutex<Blockchain>>) -> Self {
        Node {
            id,
            region,
            node_type,
            blockchain,
        }
    }

    fn run(&self) {
        match self.node_type {
            NodeType::Validator => {
                // バリデータノードとしての処理を実装
            }
            NodeType::Full => {
                // フルノードとしての処理を実装
            }
            NodeType::Light => {
                // ライトノードとしての処理を実装
            }
        }
    }
}

・論理的配置

自治体ごとのノードグループの定義:各自治体に対応するノードグループを定義する。

struct Municipality {
    name: String,
    nodes: Vec<Node>,
}

impl Municipality {
    fn new(name: String, nodes: Vec<Node>) -> Self {
        Municipality { name, nodes }
    }

    fn add_node(&mut self, node: Node) {
        self.nodes.push(node);
    }
}

・メインチェーンと自治体ノードの接続

各自治体のノードをメインチェーンに接続する。

struct MainChain {
    municipalities: Vec<Municipality>,
    blockchain: Arc<Mutex<Blockchain>>,
}

impl MainChain {
    fn new(blockchain: Arc<Mutex<Blockchain>>) -> Self {
        MainChain {
            municipalities: Vec::new(),
            blockchain,
        }
    }

    fn register_municipality(&mut self, municipality: Municipality) {
        self.municipalities.push(municipality);
    }

    fn sync_with_municipalities(&self) {
        for municipality in &self.municipalities {
            for node in &municipality.nodes {
                node.run();
                // メインチェーンとノードの同期処理を実装
            }
        }
    }
}

・プログラムの実行例

各ノードが異なる地理的リージョンに配置され、各自治体が独自のノードグループを持ち、それらがメインチェーンに接続される例を示す。

fn main() {
    let blockchain = Arc::new(Mutex::new(Blockchain::new()));

    // 各ノードを異なる地理的リージョンに配置
    let node1 = Node::new("Node1".to_string(), "Asia".to_string(), NodeType::Validator, blockchain.clone());
    let node2 = Node::new("Node2".to_string(), "Europe".to_string(), NodeType::Full, blockchain.clone());
    let node3 = Node::new("Node3".to_string(), "North America".to_string(), NodeType::Light, blockchain.clone());

    // 自治体ごとのノードグループを定義
    let mut municipality_a = Municipality::new("MunicipalityA".to_string(), vec![node1.clone()]);
    let mut municipality_b = Municipality::new("MunicipalityB".to_string(), vec![node2.clone()]);
    let mut municipality_c = Municipality::new("MunicipalityC".to_string(), vec![node3.clone()]);

    // ノードを追加
    municipality_a.add_node(node2.clone());
    municipality_b.add_node(node3.clone());

    // メインチェーンに自治体を登録
    let mut main_chain = MainChain::new(blockchain.clone());
    main_chain.register_municipality(municipality_a);
    main_chain.register_municipality(municipality_b);
    main_chain.register_municipality(municipality_c);

    // メインチェーンと自治体ノードの同期
    main_chain.sync_with_municipalities();

    // トランザクションの生成と検証
    let transaction = Transaction::new(
        "txn123".to_string(),
        "MunicipalityA".to_string(),
        (35.6895, 139.6917),
        8,
        100.0,
        "Helping others".to_string(),
        "SenderPublicKey".to_string(),
        "ReceiverPublicKey".to_string(),
    );

    let validator = node1;
    if validator.validate_transaction(&transaction) {
        println!("Transaction is valid");
    } else {
        println!("Transaction is invalid");
    }

    // ブロック生成
    validator.generate_block();
}

 

これで、ノードの物理的および論理的な配置を計画し、それをRustプログラムに実装する具体例を示した。ノードを異なる地理的リージョンに配置し、各自治体ごとにノードグループを定義してメインチェーンに接続することで、システムの信頼性と可用性を高めることができる。これにより、各ノードが協力してトランザクションの処理とブロックの生成を行い、分散型ブロックチェーンネットワークの一貫性と整合性を保つことができる。

 

3. 接続方法

ノード間の接続方法を設計する。

  • P2Pネットワーク:バリデータノードとフルノード間はピアツーピア(P2P)ネットワークで接続し、直接的な通信を行います。
  • APIゲートウェイ接続:クライアントからのリクエストはAPIゲートウェイを通じて受け取り、内部のバリデータノードまたはフルノードに転送されます。
  • セキュアな通信:すべてのノード間の通信はTLS(Transport Layer Security)で暗号化します。

・ P2Pネットワークの設計

P2Pネットワークのために、Rustの tokio や libp2p クレートを利用してノード間の通信を実装する。

use libp2p::{PeerId, Swarm, identity, mdns, NetworkBehaviour, swarm::SwarmBuilder};
use libp2p::tcp::TcpConfig;
use libp2p::core::transport::upgrade;
use libp2p::plaintext::PlainText2Config;
use libp2p::yamux::YamuxConfig;
use libp2p::core::multiaddr::Multiaddr;
use futures::StreamExt;

#[derive(NetworkBehaviour)]
struct MyBehaviour {
    mdns: mdns::Mdns,
}

impl MyBehaviour {
    pub async fn new() -> Self {
        let mdns = mdns::Mdns::new(mdns::MdnsConfig::default()).await.unwrap();
        Self { mdns }
    }
}

async fn run_p2p_network() {
    let local_key = identity::Keypair::generate_ed25519();
    let local_peer_id = PeerId::from(local_key.public());
    println!("Local peer id: {:?}", local_peer_id);

    let transport = TcpConfig::new()
        .upgrade(upgrade::Version::V1)
        .authenticate(PlainText2Config { local_public_key: local_key.public() })
        .multiplex(YamuxConfig::default())
        .boxed();

    let behaviour = MyBehaviour::new().await;

    let mut swarm = SwarmBuilder::new(transport, behaviour, local_peer_id)
        .executor(Box::new(|fut| {
            tokio::spawn(fut);
        }))
        .build();

    loop {
        let event = swarm.next().await;
        match event {
            Some(event) => println!("Event: {:?}", event),
            None => println!("No event"),
        }
    }
}

#[tokio::main]
async fn main() {
    run_p2p_network().await;
}

・APIゲートウェイ接続の設計

APIゲートウェイを通じてクライアントからのリクエストを受け取り、内部のバリデータノードまたはフルノードに転送する方法を示す。

use warp::Filter;

#[tokio::main]
async fn main() {
    let validate = warp::path!("validate" / String)
        .map(|data: String| {
            // バリデータノードにトランザクションを転送する処理
            format!("Validating transaction: {}", data)
        });

    let routes = validate;

    warp::serve(routes)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

・セキュアな通信の設計

セキュアな通信を確保するために、TLSを用いた接続を設定する。まず、TLS証明書を生成し、クライアントとサーバーでそれを使用する。

use tokio_rustls::rustls::{ServerConfig, NoClientAuth, Certificate, PrivateKey};
use tokio_rustls::TlsAcceptor;
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::sync::Arc;
use std::fs;

fn load_certs(path: &str) -> Vec<Certificate> {
    let certfile = fs::File::open(path).expect("Cannot open certificate file");
    let mut reader = std::io::BufReader::new(certfile);
    rustls::internal::pemfile::certs(&mut reader).unwrap()
}

fn load_private_key(path: &str) -> PrivateKey {
    let keyfile = fs::File::open(path).expect("Cannot open private key file");
    let mut reader = std::io::BufReader::new(keyfile);
    rustls::internal::pemfile::pkcs8_private_keys(&mut reader).unwrap()[0].clone()
}

#[tokio::main]
async fn main() {
    let certs = load_certs("server.crt");
    let key = load_private_key("server.key");

    let mut config = ServerConfig::new(NoClientAuth::new());
    config.set_single_cert(certs, key).expect("bad certificate/key");

    let acceptor = TlsAcceptor::from(Arc::new(config));
    let listener = TcpListener::bind("127.0.0.1:8080").await.unwrap();

    loop {
        let (stream, _) = listener.accept().await.unwrap();
        let acceptor = acceptor.clone();
        
        tokio::spawn(async move {
            let mut stream = acceptor.accept(stream).await.unwrap();
            let mut buf = vec![0; 1024];
            let n = stream.read(&mut buf).await.unwrap();
            stream.write_all(&buf[0..n]).await.unwrap();
        });
    }
}

・メインチェーンと市町村ブロックチェーンの接続

メインチェーンと市町村ブロックチェーン間の通信もP2PネットワークとAPIゲートウェイ、TLSを用いてセキュアに接続する。

// メインチェーンのRustプログラム
struct MainChain {
    municipalities: Vec<Municipality>,
    blockchain: Arc<Mutex<Blockchain>>,
}

impl MainChain {
    fn new(blockchain: Arc<Mutex<Blockchain>>) -> Self {
        MainChain {
            municipalities: Vec::new(),
            blockchain,
        }
    }

    fn register_municipality(&mut self, municipality: Municipality) {
        self.municipalities.push(municipality);
    }

    fn sync_with_municipalities(&self) {
        for municipality in &self.municipalities {
            for node in &municipality.nodes {
                node.run();
                // メインチェーンとノードの同期処理を実装
            }
        }
    }
}

fn main() {
    let blockchain = Arc::new(Mutex::new(Blockchain::new()));

    // 各ノードを異なる地理的リージョンに配置
    let node1 = Node::new("Node1".to_string(), "Asia".to_string(), NodeType::Validator, blockchain.clone());
    let node2 = Node::new("Node2".to_string(), "Europe".to_string(), NodeType::Full, blockchain.clone());
    let node3 = Node::new("Node3".to_string(), "North America".to_string(), NodeType::Light, blockchain.clone());

    // 自治体ごとのノードグループを定義
    let mut municipality_a = Municipality::new("MunicipalityA".to_string(), vec![node1.clone()]);
    let mut municipality_b = Municipality::new("MunicipalityB".to_string(), vec![node2.clone()]);
    let mut municipality_c = Municipality::new("MunicipalityC".to_string(), vec![node3.clone()]);

    // ノードを追加
    municipality_a.add_node(node2.clone());
    municipality_b.add_node(node3.clone());

    // メインチェーンに自治体を登録
    let mut main_chain = MainChain::new(blockchain.clone());
    main_chain.register_municipality(municipality_a);
    main_chain.register_municipality(municipality_b);
    main_chain.register_municipality(municipality_c);

    // メインチェーンと自治体ノードの同期
    main_chain.sync_with_municipalities();
}

 

これで、ノード間の接続方法を設計し、Rustプログラムで具体的に表現した。各ノードはP2Pネットワークで接続され、APIゲートウェイを通じてクライアントからのリクエストを受け取る。すべての通信はTLSで暗号化され、セキュアな通信を確保する。メインチェーンと市町村ブロックチェーン間の接続も同様の方法でセキュアに行われ、一貫性と整合性を保ちながら、分散型ブロックチェーンネットワークを構築する。

 

4. 負荷分散

システムのパフォーマンスを最適化するために、負荷分散を設計する。

  • ロードバランサーの使用:複数のAPIゲートウェイやバリデータノードに対してリクエストを均等に分配するために、ロードバランサーを使用します。
  • オートスケーリング:AWSのAuto Scaling機能を使用して、トラフィックの増加に応じてノードの数を自動的に調整します。

○ロードバランサーの使用

AWSでは、Application Load Balancer (ALB) を使用して、HTTP/HTTPSトラフィックを均等に分配する。ロードバランサー設定例(AWS CLI)を以下に示す。

  • ロードバランサーの作成
    aws elbv2 create-load-balancer --name my-load-balancer --subnets subnet-12345abcde --security-groups sg-12345abcde
     
  • ターゲットグループの作成
    aws elbv2 create-target-group --name my-targets --protocol HTTP --port 80 --vpc-id vpc-12345abcde
     
  • ターゲットグループにターゲットを登録
    aws elbv2 register-targets --target-group-arn arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-targets/12345abcde --targets Id=i-1234567890abcdef0
     
  • リスナーの作成
    aws elbv2 create-listener --load-balancer-arn arn:aws:elasticloadbalancing:region:account-id:loadbalancer/app/my-load-balancer/12345abcde --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:region:account-id:targetgroup/my-targets/12345abcde

○オートスケーリング

AWSのAuto Scaling機能を使用して、トラフィックの増加に応じてノードの数を自動的に調整する。オートスケーリング設定例(AWS CLI)を以下に示す。

  • 起動テンプレートの作成
    aws ec2 create-launch-template --launch-template-name my-template --version-description my-version --launch-template-data '{"ImageId":"ami-12345abcde","InstanceType":"t2.micro","KeyName":"my-key-pair","SecurityGroupIds":["sg-12345abcde"]}'
     
  • オートスケーリンググループの作成
    aws autoscaling create-auto-scaling-group --auto-scaling-group-name my-asg --launch-template LaunchTemplateName=my-template,Version=1 --min-size 1 --max-size 10 --desired-capacity 2 --vpc-zone-identifier subnet-12345abcde
     
  • スケーリングポリシーの設定
    aws autoscaling put-scaling-policy --auto-scaling-group-name my-asg --policy-name my-scale-out-policy --scaling-adjustment 1 --adjustment-type ChangeInCapacity

○ブロックチェーンプログラムでのロードバランサーとオートスケーリングの設計

実際のブロックチェーンプログラムでは、ノード間の通信やAPIゲートウェイの処理をロードバランサーを通じて行うように設定する。

use warp::Filter;
use tokio::sync::mpsc;
use tokio::task;
use std::sync::Arc;
use std::sync::Mutex;

// バリデータノードの定義
#[derive(Clone)]
struct ValidatorNode {
    id: String,
}

impl ValidatorNode {
    async fn handle_request(&self, request: String) -> String {
        // リクエストを処理するロジック
        format!("ValidatorNode {} handled request: {}", self.id, request)
    }
}

// APIゲートウェイの実装
async fn api_gateway(node_pool: Arc<Mutex<Vec<ValidatorNode>>>, request: String) -> String {
    let nodes = node_pool.lock().unwrap();
    let node = &nodes[0]; // シンプルなラウンドロビンの例

    node.clone().handle_request(request).await
}

#[tokio::main]
async fn main() {
    // バリデータノードプールの初期化
    let node_pool = Arc::new(Mutex::new(vec![
        ValidatorNode { id: "node1".to_string() },
        ValidatorNode { id: "node2".to_string() },
        ValidatorNode { id: "node3".to_string() },
    ]));

    let pool = node_pool.clone();
    let api = warp::path!("api" / String)
        .map(move |request: String| {
            let pool = pool.clone();
            warp::reply::json(&task::block_in_place(move || api_gateway(pool, request).await))
        });

    warp::serve(api).run(([0, 0, 0, 0], 3030)).await;
}
 

このプログラムでは、APIゲートウェイがリクエストを受け取り、バリデータノードプールからノードを選択してリクエストを処理する。この例ではシンプルなラウンドロビン方式でノードを選択しているが、より高度な負荷分散戦略も実装可能である。

 

負荷分散とオートスケーリングの設定は、システムのパフォーマンスとスケーラビリティを最適化するために重要である。AWSのロードバランサーとAuto Scaling機能を活用し、ブロックチェーンプログラム内でこれらの設定を適用することで、効率的で高可用性なシステムを構築できる。

 

 

いかがであろうか、インフラストラクチャーの設計はまだまだ続く。次回、続きを記載したい。