ブロックチェーンSNS概念設計:システム構築の品質評価のポイント9:効率性② | 続・ティール組織 研究会のブログ

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

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

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

愛記システムのシステム評価について

システム評価とは、つまりは、このシステムを導入して成功だったか失敗だったかという効果検証という意味だ。概念設計をする上で必ず抑えておくべきポイントということだ。それには各項目があり、それぞれの項目を見ていくことで、その結果が得られる。そのシステム評価項目を1つずつ見ていきたい。

システム構築の品質評価のポイント1:理解可能性(Understandability)

システム構築の品質評価のポイント2:完全性(Completeness)

システム構築の品質評価のポイント3:簡潔性(Conciseness)

システム構築の品質評価のポイント4:移植性(Portability)

システム構築の品質評価のポイント5:一貫性(Consistency)と構造化の度合い

システム構築の品質評価のポイント6:保守性(Maintainability)

システム構築の品質評価のポイント7:試験性(Testability)

システム構築の品質評価のポイント8:ユーザビリティ(Usability)

システム構築の品質評価のポイント9:効率性(Efficiency)

システム構築の品質評価のポイント10:セキュリティ(Security)

システム構築の品質評価のポイント9:効率性②(Efficiency)

システム構築の品質評価における効率性とは、システムが動いているときにリソースがどれだけ無駄になっていないかである。業務システム全体の効率性はこちらの効率性ではなく一貫性や理解可能性に含まれているのだろう。こちらのポイントは与えられたリソースで適切な能力を発揮できているかどうかとなる。

 

ブロックチェーンSNSを一から構築する際に、システム評価の効率性を確保するためにはいくつかの具体的な注意点がある。以下にそれらを詳しく説明する。

  1. プロトタイピングとユーザーテスト

  2. スケーラビリティの確認:

    • システムが将来的な利用者増加に耐えられるかどうかを確認する。スケーラビリティの評価は、トランザクションの処理速度やネットワークの負荷などを含む。ベンチマークテストを通じてシステムの性能を評価する。

      先にブロックチェーン側のシステムについて性能の評価項目を見ていった。次に、Dapps側の評価も見ていきたい。一般的なDappsの開発の話から見ていこう。データベースやAPIを用意して、それらを利用して JavaScript でクライアントサイドの処理を行うのが一般的なアプローチであろう。
    • ブロックチェーンとDapps側のやり取りの橋渡しをするのがスマートコントラクトであるが、スマートコントラクトは一度デプロイされると、基本的には不変のままである。変更が必要な場合、新しいバージョンのコントラクトをデプロイする必要がある。これは、スマートコントラクトの信頼性とセキュリティを維持するための仕組みだが、変更が頻繁に必要な場合はその点を考慮する必要がある。フロントエンドのコードやデータベースのスキーマなど、変更が容易な部分を別途管理することで、柔軟性を確保できる。ある程度分析のパターンを決めて、スマートコントラクトで書く部分は、シンプルにしたい。できるだけJavascriptの方で処理したいと思うだろう。なお、これは一般的な話であるが、セキュリティ面では懸念が残る。

    • データベースとAPIの用意:

      • データベース: 例えば、MongoDBやFirebaseなどを利用して行動履歴やトークンの種類などを保存するデータベースを用意する。
      • API: データベースにアクセスするためのAPIを作成する。Express.jsやFastAPIなどのフレームワークを使用してAPIを構築する。
    • JavaScriptでクライアントサイド処理:

      • フロントエンドにおいて、JavaScript(またはTypeScript)を使用して必要なデータをAPIから取得する。
      • 取得したデータをもとに行動履歴の分析やトークンの種類ごとの処理を行う。
         
    • 一方、Solanaは、スマートコントラクトを使用することで高いスループットと低いトランザクションコストを提供している。一般的に、Solana上のDAppsはスマートコントラクトを利用してブロックチェーン上で実行されることが一般的である。SolanaのスマートコントラクトはプログラムとしてRustで記述され、その高いパフォーマンスが特徴である。これにより、Solanaのネットワーク上で効率的に処理が行える。ただし、データの永続化や特定のデータ処理のためには、外部のデータベースやAPIと連携することも一般的である。これにより、必要なデータを取得し、スマートコントラクト内で処理することが可能である。ただし、このようなアプローチはプロジェクトの要件により異なる。簡単に言えば、Solana上のDAppsは主にスマートコントラクトを利用しているが、必要に応じて外部データやデータベースと連携しているということだ。


      では、トークンの分析や送受信履歴も、Solanaのスマートコントラクト内で処理されることがある。Solanaのスマートコントラクトはプログラム性が高く、複雑な処理も可能である。以下は、簡単な例として、Solana上でのトークンの送受信履歴を管理するスマートコントラクトの一部を示す(実際のプロジェクトには合わせた変更が必要である)。
      use solana_program::{
          account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
          program_error::ProgramError, system_instruction, sysvar,
      };
      use solana_sdk::{
          program_pack::IsInitialized, pubkey::Pubkey, system_instruction::create_account,
          sysvar::rent,
      };
      use spl_token::state::Account as TokenAccount;

      // トークン送受信履歴を管理するスマートコントラクトの例

      // 送受信履歴のエントリ
      pub struct TransactionEntry {
          pub sender: Pubkey,
          pub recipient: Pubkey,
          pub amount: u64,
          // 他に必要な情報を追加
      }

      // スマートコントラクトのエントリポイント
      #[entrypoint]
      fn process_transaction(
          program_id: &Pubkey,
          accounts: &[AccountInfo],
          amount: u64,
      ) -> ProgramResult {
          let accounts_iter = &mut accounts.iter();

          // 送信者のアカウント情報を取得
          let sender_account = next_account_info(accounts_iter)?;

          // 受信者のアカウント情報を取得
          let recipient_account = next_account_info(accounts_iter)?;

          // トークン送受信履歴のアカウント情報を取得
          let history_account = next_account_info(accounts_iter)?;

          // 送信者のトークン残高を確認
          let sender_token_account = TokenAccount::unpack(&sender_account.data.borrow())?;
          if sender_token_account.amount < amount {
              msg!("Insufficient balance");
              return Err(ProgramError::InsufficientFunds);
          }

          // 送信者のトークン残高を減少
          sender_token_account.amount -= amount;
          TokenAccount::pack(sender_token_account, &mut sender_account.data.borrow_mut())?;

          // 受信者のトークン残高を増加
          let recipient_token_account = TokenAccount::unpack(&recipient_account.data.borrow())?;
          recipient_token_account.amount += amount;
          TokenAccount::pack(recipient_token_account, &mut recipient_account.data.borrow_mut())?;

          // 送受信履歴にエントリを追加
          let mut history_entries = HistoryEntries::load(history_account)?;
          history_entries.add_entry(
              TransactionEntry {
                  sender: *sender_account.key,
                  recipient: *recipient_account.key,
                  amount,
              },
              &rent,
          )?;
          history_entries.save(history_account)?;

          msg!("Transaction processed successfully");

          Ok(())
      }

      // 送受信履歴管理用のデータ構造
      struct HistoryEntries {
          entries: Vec<TransactionEntry>,
      }

      impl HistoryEntries {
          fn load(account: &AccountInfo) -> Result<Self, ProgramError> {
              let data = account.try_borrow_data()?;
              Ok(Self {
                  entries: Vec::deserialize(&data)?,
              })
          }

          fn save(&self, account: &AccountInfo) -> Result<(), ProgramError> {
              let mut data = account.try_borrow_mut_data()?;
              self.entries.serialize(&mut data)?;
              Ok(())
          }

          fn add_entry(&mut self, entry: TransactionEntry, rent: &Rent) -> Result<(), ProgramError> {
              // 必要に応じてエントリの追加処理を行う
              // 例えば、データの整理や容量制限の確認など
              self.entries.push(entry);
              Ok(())
          }
      }
       
    • また、solanaのSDKを利用し開発されたDApps側のプログラムも見てみよう。ボタンなどのUI要素を用意し、それをユーザーがクリックすることでJavascriptが対応する指令をスマートコントラクトに送り、スマートコントラクトはその指令に基づいてデータを取得し、取得したデータをJavascriptに返し、最終的にフロントエンドで表示させる、といった流れである。このような仕組みを実現することで、ユーザーは必要な情報を取得する際に、その都度スマートコントラクトが発動し、自分のデータを取り扱うことができる。これにより、データのセキュリティと所有権がユーザーによって管理される形になる。ただし、具体的な実装には多くの要素が絡むため、以下はシンプルな例である。

      ▪Rustのスマートコントラクト部分:
      use solana_program::{
          account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey,
      };

      #[derive(Debug)]
      pub struct LoveData {
          pub sender: Pubkey,
          pub recipient: Pubkey,
          pub amount: u64,
      }

      #[no_mangle]
      pub extern "C" fn process_love_transaction(
          sender: *const u8,
          recipient: *const u8,
          amount: u64,
      ) -> *const LoveData {
          let sender_bytes = unsafe { std::slice::from_raw_parts(sender, 32) };
          let recipient_bytes = unsafe { std::slice::from_raw_parts(recipient, 32) };

          let sender_pubkey = Pubkey::new_from_array(*sender_bytes);
          let recipient_pubkey = Pubkey::new_from_array(*recipient_bytes);

          let love_data = LoveData {
              sender: sender_pubkey,
              recipient: recipient_pubkey,
              amount,
          };

          Box::into_raw(Box::new(love_data))
      }

      #[entrypoint]
      fn entry(_program_id: &Pubkey, _accounts: &[AccountInfo], _input: u64) -> ProgramResult {
          // スマートコントラクトのロジック
          msg!("Smart contract executed successfully!");
          Ok(())
      }

      solana_program::declare_entrypoint!(entry);

      ▪Javascriptのフロントエンド部分:
      // ユーザーがボタンをクリックしたときの処理
      document.getElementById("getLoveDataButton").addEventListener("click", async () => {
          // JavascriptからRustにデータを送信
          const sender = new Uint8Array([1, 2, 3, /* ... */]);
          const recipient = new Uint8Array([4, 5, 6, /* ... */]);
          const amount = 100;

          const loveData = process_love_transaction(sender, recipient, amount);

          // Rustから返ってきたデータを表示
          console.log("Love Data:", JSON.parse(UTF8ToString(loveData)));
      });

      // RustからのデータをUTF-8文字列に変換する関数
      function UTF8ToString(ptr) {
          let length = 0;
          while (Module.HEAPU8[(ptr + length) >> 0]) {
              ++length;
          }
          const str = new Array(length);
          for (let i = 0; i < length; ++i) {
              str[i] = String.fromCharCode(Module.HEAPU8[(ptr + i) >> 0]);
          }
          return str.join("");
      }

      この例では、process_love_transaction関数がRust側のスマートコントラクトで、ボタンがクリックされるとこの関数が呼ばれ、Javascriptから送られたデータを元にLoveData構造体を作成し、それをJavascriptに返している。ただし、これは非常に基本的な例であり、実際のアプリケーションでは以下の点に注意する必要がある:

    • セキュリティ: スマートコントラクトやデータのやりとりにおいて、セキュリティは非常に重要である。この例ではセキュリティ対策が不足しており、実際のアプリケーションでは慎重なセキュリティ設計が必要である。

    • エラーハンドリング: 現在の例ではエラーハンドリングが不十分であり、実際のアプリケーションではエラー処理が適切に行われるようにする必要がある。

    • ユーザビリティ: ユーザビリティやエクスペリエンスの向上も考慮する必要がある。例えば、データの送受信が非同期で行われる場合、ユーザに対して適切な通知や進捗表示が必要である。

    • データのプライバシー: ブロックチェーン上で扱うデータのプライバシーにも留意する必要がある。敏感な情報を適切に保護し、公開するべきでないデータはブロックチェーン上に載せないようにする必要がある。

      これらのポイントを考慮し、実際の利用状況や要件に基づいて適切な実装を行うことが重要である。このような複雑なアプリケーションの開発には、十分な調査と検討、そして適切なサポートを受けることが重要であろう。
       

    • DAppsでは、ユーザーが使いやすいフロントエンドを提供する必要がある。このため、JavaScriptや他のウェブ技術を用いてユーザーインターフェースを構築し、SolanaのAPIを介してブロックチェーンと対話することも一般的にはある。

    • フロントエンドでデータの分析を行うことも一部可能だが、注意が必要である。ブロックチェーンのデータは一般的に大きく、直接フロントエンドに取得して処理することは、パフォーマンスの観点から望ましくはない。ただし、軽量なデータをフロントエンドに取得し、基本的な分析や表示を行うことは可能である。例えば、ウォレットの残高や最近のトランザクションなどのデータを表示する際に、JavaScriptで簡単な処理を行うことができる。しかし、複雑な分析や大量のデータ処理が必要な場合、それらの処理をバックエンドに委任し、処理済みの結果をフロントエンドに提供することが一般的である。これにより、ユーザーエクスペリエンスを向上させつつ、効率的なデータ処理が可能となる。SolanaはそれをAPIを介して外部とやり取りするのではなく、スマートコントラクト上で、rustでプログラミングすることが多いということだ。

 

当方も、ブロックチェーンのアルゴリズムにPoPとPoHを組み合わせて行う関係上、SolanaのSDKを利用するということは難しい。プロトタイプをPythonで構築していき、パフォーマンスが悪い部分をrustなどの言語に変更して記述していくことで、パフォーマンスを上げていくことを考える必要があるのだろう。

例:市町村のブロックチェーンをメインチェーンであるフェデレーションが承認するために、以下のような構造をpythonで考えてみた。

from datetime import datetime
from hashlib import sha256

class Block:
    def __init__(self, index, previous_hash, timestamp, data, proof_of_place):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.data = data
        self.proof_of_place = proof_of_place
        self.hash = self.calculate_hash()

    def calculate_hash(self):
        hash_data = (
            str(self.index) +
            str(self.previous_hash) +
            str(self.timestamp) +
            str(self.data) +
            str(self.proof_of_place)
        )
        return sha256(hash_data.encode()).hexdigest()

class Blockchain:
    def __init__(self):
        self.chain = [self.create_genesis_block()]

    def create_genesis_block(self):
        return Block(0, "0", datetime.now(), "Genesis Block", "Proof of Place")

    def get_latest_block(self):
        return self.chain[-1]

    def add_block(self, data, proof_of_place):
        index = len(self.chain)
        previous_block = self.get_latest_block()
        new_block = Block(index, previous_block.hash, datetime.now(), data, proof_of_place)
        self.chain.append(new_block)

# ブロックチェーンのインスタンスを作成
my_blockchain = Blockchain()

# ブロックを追加
my_blockchain.add_block("Transaction Data 1", "Proof of Place A")
my_blockchain.add_block("Transaction Data 2", "Proof of Place B")

# ブロックチェーンを表示
for block in my_blockchain.chain:
    print(f"Block #{block.index} - Hash: {block.hash}")
 

この例では、ブロックチェーンの各ブロックが前のブロックのハッシュ値を保持している。ブロックを追加するたびに、そのブロックのハッシュが計算され、連鎖が作られる。 Proof of Place は、位置情報などの証拠を表すものとして考えている。

 

Pythonで構築されたプロトタイプをrustに書き換える際、主にパフォーマンスが必要とされる部分やブロックチェーンの中核的な処理を対象にする。以下は、例として一部のコードをrustに書き換えたものである:

use chrono::Utc;
use crypto_hash::{hex_digest, Algorithm};

struct Block {
    index: u32,
    previous_hash: String,
    timestamp: String,
    data: String,
    proof_of_place: String,
    hash: String,
}

impl Block {
    fn calculate_hash(&self) -> String {
        let hash_data = format!(
            "{}{}{}{}{}",
            self.index, self.previous_hash, self.timestamp, self.data, self.proof_of_place
        );
        hex_digest(Algorithm::SHA256, hash_data.as_bytes())
    }
}

struct Blockchain {
    chain: Vec<Block>,
}

impl Blockchain {
    fn create_genesis_block() -> Block {
        Block {
            index: 0,
            previous_hash: String::from("0"),
            timestamp: Utc::now().to_rfc3339(),
            data: String::from("Genesis Block"),
            proof_of_place: String::from("Proof of Place"),
            hash: String::new(),
        }
    }

    fn get_latest_block(&self) -> &Block {
        self.chain.last().unwrap()
    }

    fn add_block(&mut self, data: &str, proof_of_place: &str) {
        let index = self.chain.len() as u32;
        let previous_block = self.get_latest_block();
        let new_block = Block {
            index,
            previous_hash: previous_block.hash.clone(),
            timestamp: Utc::now().to_rfc3339(),
            data: String::from(data),
            proof_of_place: String::from(proof_of_place),
            hash: String::new(), // To be calculated later
        };
        self.chain.push(new_block);
    }
}

fn main() {
    let mut my_blockchain = Blockchain {
        chain: vec![Blockchain::create_genesis_block()],
    };

    my_blockchain.add_block("Transaction Data 1", "Proof of Place A");
    my_blockchain.add_block("Transaction Data 2", "Proof of Place B");

    for block in my_blockchain.chain.iter() {
        println!("Block #{} - Hash: {}", block.index, block.hash);
    }
}


この例では、 chrono ライブラリを使用して日時のフォーマットを行い、 crypto_hash ライブラリを使用してSHA256ハッシュを計算している。これにより、Pythonよりも高速で効率的な実行が期待される。ただし、実際のアプリケーションにおいては、利用するライブラリやrustの機能により最適な実装を選定する必要がある。

 

上記のように、ブロックチェーンの実装において、パフォーマンスのボトルネックとなる可能性があるいくつかの要因がある。例えば、ブロック生成、トランザクション処理、コンセンサスアルゴリズム、データベースの処理などが挙げられる。これらの部分が大量のトランザクションやデータに対して効率的でない場合、パフォーマンスが低下する可能性がある。Pythonは高水準かつ柔軟な言語であり、開発速度が速い一方で、低レベルのメモリ制御が難しいため、性能が劣ることがある。特に、計算集約的な処理や低レベルのメモリ操作が多い場合、Rustのようなメモリセーフで効率的な言語に切り替えることが望ましい。

 

PythonとRustの組み合わせを考える場合、いくつかの方法がある。例えば、PyO3などのツールを使用して、Rustで書かれたコードをPythonから呼び出すことができる。これにより、性能が必要な部分をRustで実装し、それをPythonから利用することが可能となる。ただし、PythonとRustは言語の特性が異なるため、直接の整合性が難しいこともありえる。整合性を保ちながらRustに変更していく場合、APIの設計やデータ構造の変更に注意が必要であろう。また、PythonとRustとで使用されるデータのシリアライゼーションや通信プロトコルなども検討する必要がある。

 

とはいえ、全く新しいPoP(proof of Place)などのアルゴリズムや、愛貨トークン(お金ではない)の仕組みを導入するので、世界中の人々を巻き込むにはプロトタイプをPythonで開発するのがベストなのであろう。そして、スマートコントラクトはSolidtyで記述すると、Ethereumとは異なる独自のアルゴリズムなので、上手く機能するかどうかが疑問だ。それならば、開発当初からrustでスマートコントラクトを記述することで、DApps側とやり取りさせる仕組みにしたいと考えている。様々な分析をしたいので、rustでスマートコントラクトを記述するほうが都合がよい。それをDApps側の愛記システムにJavascriptでデータを受け渡し、表示させるという仕組みとしたい。

 

 

いかがであろうか、これらの注意点を考慮することで、DApps側のシステム評価をより効果的かつ包括的に行うことが可能となる。APIを利用してブロックチェーンのデータを外部データベースへ移行させ分析するようなDAppsであると、セキュリティ面が懸念される。Solanaのように、スマートコントラクト上で、rustを用いて様々な記述ができることは魅力的である。このような効率性も追求していきたい。