Substrateでファイルの存在証明をする


概要

Substrateでファイルの存在証明および改竄されていないかの確認ができるものを作ります。
NEMのアポスティーユのようなものです。
アポスティーユの詳細はこちら

Substrateとは

ブロックチェーンのフレームワークです。詳しくは以下を参照してください。
What is Substrate | Parity Technologies |

セットアップ

実行環境はmacです。
まずnpmとnodeがインストールされていることが必須です。
以下のコマンドでSubstrateが実行できる環境を作ります。
Rustなどがインストールされます。

$ curl https://getsubstrate.io -sSf | bash

コーヒーを飲みながら待ちます。

完了したらSubstrateノードのテンプレートを作りましょう。

$ substrate-node-new receiptchain junki
$ cd receiptchain

receiptchainはプロジェクト名、junkiは自分の名前をいれます。
またコーヒーを飲みましょう。

ファイルの編集

runtime/src以下にreceiptchain.rsを作成します。
receiptchain.rsを以下のように編集します。

receiptchain.rs
use support::{decl_storage, decl_module, StorageValue, StorageMap, dispatch::Result, ensure, decl_event};
use system::ensure_signed;
use parity_codec::{Encode, Decode};
use rstd::vec::Vec;

const BYTEARRAY_LIMIT: usize = 3000000;

#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Receipt<AccountId, Moment> {
    id: u64,
    owner: AccountId,
    timestamp: Moment
}

pub trait Trait: timestamp::Trait + balances::Trait {
    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}

decl_event!(
    pub enum Event<T> where
        <T as system::Trait>::AccountId,
        <T as timestamp::Trait>::Moment,
    {
        ReceiptCreated(AccountId, Moment),
    }
);

decl_storage! {
    trait Store for Module<T: Trait> as ReceiptStorage {
        Receipts get(receipt): map Vec<u8> => Receipt<T::AccountId, T::Moment>;
        Nonce: u64;
    }
}

decl_module! {
    pub struct Module<T: Trait> for enum Call where origin: T::Origin  {
        fn deposit_event<T>() = default;

        fn create_receipt(origin, hash: Vec<u8>) -> Result {
            ensure!(hash.len() <= BYTEARRAY_LIMIT, "Bytearray is too large");

            let sender = ensure_signed(origin)?;
            let nonce = <Nonce<T>>::get();
            ensure!(!<Receipts<T>>::exists(&hash), "This receipt has already been registered");

            let time = <timestamp::Module<T>>::now();

            let new_receipt = Receipt {
                id: nonce,
                owner: sender.clone(),
                timestamp: time.clone()
            };

            <Receipts<T>>::insert(hash, new_receipt);

            <Nonce<T>>::mutate(|n| *n += 1);
            Self::deposit_event(RawEvent::ReceiptCreated(sender, time));

            Ok(())
        }
    }
}

SubstrateはStringがサポートされていません。
チェーンにデータをのせる時はバイト列にしてのせましょう。
ちなみにここで使っている型Vec<u8>はバイト列です。

runtimeをアップデートする

  • mod receiptchain;を入れる
lib.rs
...
pub type Hash = primitives::H256;

/// Index of a block number in the chain.
pub type BlockNumber = u64;

/// Index of an account's extrinsic in the chain.
pub type Nonce = u64;

mod receiptchain;
...
  • impl receiptchain::Trait for Runtime { type Event = Event; }を入れる
lib.rs
...
impl sudo::Trait for Runtime {
    /// The uniquitous event type.
    type Event = Event;
    type Proposal = Call;
}

impl receiptchain::Trait for Runtime {
    type Event = Event;
}
...
  • construct_runtime!の中にmoduleを宣言する。
lib.rs
construct_runtime!(
    pub enum Runtime with Log(InternalLog: DigestItem<Hash, AuthorityId, AuthoritySignature>) where
        Block = Block,
        NodeBlock = opaque::Block,
        UncheckedExtrinsic = UncheckedExtrinsic
    {
        System: system::{default, Log(ChangesTrieRoot)},
        Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent},
        Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent},
        Aura: aura::{Module},
        Indices: indices,
        Balances: balances,
        Sudo: sudo,

        Receiptchain: receiptchain::{Module, Call, Storage, Event<T>},
    }
);


動かす

$ ./scripts/build.sh        //build wasm
$ cargo build --release       //build binary
$ ./target/release/receiptchain --dev       //start node

チェーンのデータ全て削除してからブロック生成を始めるには次のコマンドを実行します。

./target/release/receiptchain purge-chain --dev

こんな感じでブロック生成が始まります。

PolkadotJS-UIで挙動を確認する

PolkadotJS-UIを開きます。

ではSettingのremote nodeをLocal Node (127.0.0.1:9944)にしてSave&reloadします。

次にSettingDeveloperセクションでページが正しくデコードするために構造体を設定します。

{
  "Receipt": {
    "id": "u64",
    "owner": "H256",
    "timestamp": "u64"
  }
}

次にExtrinsicsに移動して関数を実行します。

Aliceがトークンを持っているはずなのでAliceで実行します。
hash: Bytesの横のドキュメントマークをクリックしてお好きなファイルを選択してください。
そしてSign&Submitします。

次にChain Stateに移動してreceitStorage/receipt(Bytes):Receiptを選択します。
そして、先ほど選んだファイルを再び選んで+マークをクリックします。

すると登録した情報が表示されます。
この時登録の時に選んだファイルとは違うファイルを選択した場合、またはファイルの内容が変更されていた場合はこのように返ってきます。

最後に

Substrateはバージョンの変更が多いので上のコードが動かないことがあるのでご注意ください。