LoginSignup
7

More than 5 years have passed since last update.

nem2のサプライチェーンマネジメントケースのワークショップ用プロジェクトをやってみた

Last updated at Posted at 2018-07-28

NEM blockchain applied to supply chain

nemのサプライチェーンマネジメントケースのワークショップ用プロジェクトが公開されていたので、一通りやってみました。

(現在公開されているものが当時より更新されていますので、こちらのアーカイブ版を実施した内容となります)

なお、記事中やキャプチャ中の秘密・公開鍵、アドレスなどはご自身で作ったものに置き換えてください。
(やり直したり、作り直したため、チグハグになってしまいました。ご了承ください。)

要求環境

環境のセットアップ

追記: 以下で立ち上がるノードを公開しました。
エンドポイントを読み替えれば、ローカルマシンにノード立ち上げは不要です。
ただし、ネームスペースなどが誰かに取られている状態になっているかもしれないので、
そのあたりも適宜読み替えてご利用ください。

エンドポイント: http://catapult-test.44uk.net:3000
蛇口: http://test-nem2-faucet.44uk.net

catapultノードの立ち上げ

git clone https://github.com/tech-bureau/catapult-service-bootstrap
cd catapult-service-bootstrap
docker-compose up

docker-compose-with-explorer.ymlを指定することでノードエクスプローラも動くので、見たい人はこちらで立ち上げてください。

docker-compose -f docker-compose-with-explorer.yml up

しばらくするとブロックチェーンが稼働し始めます。
curlでもブラウザでも良いですが、localhost:3000/block/1にアクセスすると初期ブロックの情報が確認できます。

{"meta":{"hash":"324E7B52A02734696B9D41BBECDB9B5EF5D0FDE6B792C40616C353AA48F8D936","generationHash":"0FD18FA8F06EA62DCCCC6C12337049ADAAD6EFB0EB9C10B9D58250338936FB7E","totalFee":[0,0],"numTransactions":25,"merkleTree":["u/P8za376OE6EOGjSKVF+ylH6x8YumwzE0JUd/jX5aE=","pH6SPI99soNY6G8AOijVwENxEDOqCJTtBazOv9MaNGc=","VoHtlXJ7yJClbgjDb0FhDlLFNV8Dhk9PPBoWxf+ABuM=","iGqkJ3sRR32Tt/su9chFCQYMrm60z+j/6CnJEKvgr7Q=","W+lb6gZVneiEZAFz0IpTrt1oU3MXgmtj6ZWPNvK7yzc=","jlj1sNghH/OOAj3lhCE1f2i7536i8rpENYD6g21OED8=","VNSY5v1qVVAoTRSAdhAkZExkhk0C3OMqVJ4tr+QZkAU=","mGsIY5fQMyW4/LMg0QrVIsEWwGaD8z8918dsqt52DII=","7yt90KUsOZWcW5jGIWG13Q6kIZU+Qi2PGHbL6Fa/X1k=","nL7b6P/QNgx0ylWO7P0K3NsoOAJhmvqx/aOYh3LXA7g=","uDhagZ8zC78Yc6tezpF5J/mRtWXB25+c49+WCD1RDx4=","4aFztGlIA6IlMluhZCBSWZzefalvpnhvWoQppSmq7Lc=","L9WX150j/qekL1C5sK2keeZTJt1W6TecLKR8ZZG3zCk=","u+yhrlrHwkZ37FZ+Ss9nck3u9IiKgQ4bZFGsnamYxDw=","4XSfFniE1CYe3UKiFebKjzJzIpN/Hv4ys3FWutIlcCU=","0eytEL/sZf1ADBQeKENs6XsY73rPKAC52+iHvb9cn+0=","DUWR3BwJsmhrFUuTfeMQU4OdA0vKIFjGLom+NiLyZQs=","aY2MS2hXKRcXWDhNvU6g4z3WrmbbmG4En4mI/RtDGkM=","32aM23Y0YwzOFhXc1q3ZgXl42V1UYbjNd2/WrTK7s/I=","jEFcI8ViyF0qoLaoqe2caZ2bGQMbtIQxdGzFR26k0h8=","XPddDcsWC7duTBJ5gq9RPzRHpPOkCpyCitscOGDRHfY=","u/ufTFxzWtZ4mMCCmCHLUZSo/sBYhhttb4Wt6KkWxGM=","lY7d4yIi6Dr5BHRTlitUGAjtCBuck4jjOuqh9JV2Or4=","nSLGYfLnwLt4jYKxVF4R5mim1WDnORDNZHULgnFLU58=","4c+WcabKYoGHCzEDeB9Nx19iWsGe1v3ZhLobJIXAhsQ=","4c+WcabKYoGHCzEDeB9Nx19iWsGe1v3ZhLobJIXAhsQ=","uB/RbgFpREM83U6HOhlr21nW68FGg9Z54a0+n6U2rn8=","W+8OXVmf8W6qXpyQMlo+4xwsUzyeQ0HymkEWTW3XazY=","hI4QgRG2m8Yj4V6FaiFj4CL9CzG6359LFY9fk3GrSfI=","W9QSRlk6dQyIGCNWyNn5wx9ANSN1mhpzQuAw5QvpgIE=","X99wXMVIO3X9xXsuOowLxC2FlTP7+mM47c1ITN96HJ4=","zyZLyUazVJfH3uECgBQJfz8W+ZCHiiutZUBI2a2X8gQ=","7HGUqJ/B1DYlKB8oHsOzUOHWCS8UVa7B6oxLQ8JNGzw=","YISZR+rUBpHrtOpUZok5g8mp6Rp/+T0X/Y1QlIRybeU=","v7rEsGKlbddAolhiVm0OHgeDp9YBnF63OEZ+fuQwKPQ=","8lN981cgusU2TK1zclTOdM+YLcDKTIVwSI1EFMVHVMw=","kBRQJGQffBfvbkoohYEWLiuqeyGMXFimJGVcGGThX1w=","yiqkETJ6dOi+bsL/0QPdBVnY7fRMP2KoIdDtbUS6nNM=","q9n2xRp1RIFabyaK05djcb0YkQ2iNNhoMHnB6CujtRI=","q9n2xRp1RIFabyaK05djcb0YkQ2iNNhoMHnB6CujtRI=","p0UpURd0EG1ZMLa4uh1lIYSMTRXo/BNESBaRlAGjjGs=","MBAm2ejVwrqykTNE6F0lksz26lkzhDO1vzwIDU+ibMM=","Ej2YLkx4Qu0cDQVwZIV8SGGyRKgh3UZU7WbVUkKqjmg=","pM+dzfWmGhVSJp/+6lhlhYrzUrihlrxX9lX30SKEIEc=","UuwC9uEoBvLpE1LdBQwVDDcC7S0ffS5dL1MgBmcf8OQ=","avoT3ZSkMgCujJFPtzwFBS5zS1uphw3d37+GOdf509Y=","uffkYJ5pVGEovNsHmM55t0+POc+Z3flZ56wgaBYWKyE=","uffkYJ5pVGEovNsHmM55t0+POc+Z3flZ56wgaBYWKyE=","wlNMcW2jqUgarP2vIUXnZ6sCm4iKHOwWT/Yvl3YqARw=","hsOwVdEp0EB3vk/pqN66wOLv0NaeV1RmYyi4tjeSzbw=","+5ZtfVgneG98wuMmKkL+ENi3DmJZgOG8sSClkzwuLVA=","3POOPgyeQqNTCX+VlWVyw3Qwehr3PADHgxq4rs3AplM=","X6jsXpwwm0nxyrdk2eTT8nCvazJeZ51VhU6hIe7taIQ=","s0WVIv6VZ7U40qBb0aR6/tbv1k8qKqNSmKuIUwVYvsE=","BtxhY8tak0HIiaOJ3oCY/L+GoZIaPqeFhOVKcJqECRA="]},"block":{"signature":"B9953BF70D42681C5E907CB07C4B3B0476C0DC92690A2F41BA897E97733C5DDF7C15CECE9FF74235A8DB7017369AD1011FA98E648D549734E7B70E988295FE09","signer":"760555011D500A00633BA5E7B142DE58BE523BC4D0FE5C0221F0A05E8441E045","version":36867,"type":32835,"height":[1,0],"timestamp":[0,0],"difficulty":[276447232,23283],"previousBlockHash":"0000000000000000000000000000000000000000000000000000000000000000","blockTransactionsHash":"06DC6163CB5A9341C889A389DE8098FCBF86A1921A3EA78584E54A709A840910"}}

エクスプローラを起動させた場合はhttp://localhost:8000/でアクセスすることができます。

explorer-1.png

ワークショッププロジェクトのダウンロード

このリポジトリをcloneするかアーカイブをダウンロードして展開してください。

project以下のフォルダに、dashboardserverというアプリケーションがあるので、それぞれでパッケージインストールをして、アプリケーションを起動します。

cd project/server
npm install
npm start
cd project/dasbhoard
npm install
npm start

Dashboardアプリケーションにはhttp://localhost:4200 でアクセスできます。

dashboard-1.png

nem2-cli のインストール

アカウントを作成したり、トランザクションを発行できるコマンドラインツールです。

$> npm i -g nem2-cli

なおnem2-cliコマンドは多様します。
bash用ですがコマンド補完を作りましたのでよかったらどうぞ。

サプライチェーンマネジメントの概要

use-case-nem.png

今回のケースでは、製品(PRODUCT)にアドレスを割り当て、検査作業者(OPERATOR)によるチェックに問題がなかったら、モザイクをアドレスへ転送します。
このモザイクを持っていることが、その製品が安全であることを示します。

製品

製品にアドレスを割り当てます。
このアドレスのトランザクション履歴を見ることで、要所要所で行われた作業のトレースが確認できるようにします。
製品アカウントはトランザクションを発信する必要がなく、受信するだけでよいので以下の方法で公開鍵を作成します。

publicKey = sha256(company_identifier + product_identifier)

データベースが失われたとしても、company_identifierproduct_identifierから、公開鍵を復元できるので、製品についてのトレースを再開することができます。

検査作業者(Operator)

製品を検査し、問題がなければ、製品アカウントへ検査済みシールを送るアカウントです。

検査済みシール(company.safety:seal)

製品が検査済みであることを証明するためのモザイクです。
モザイクは転送不可の設定をすることができますが、このケースの場合は製品アカウントの秘密鍵を取り扱わないため、一度製品に送られたモザイクがどこかへ転送される可能性がありません。

また、一見すると検査作業者アカウントがネームスペースやモザイク定義を保有するように見えますが、作業者は離職することも考えられますので、会社アカウントが保有することとします。

会社アカウントから検査作業者アカウントへモザイクが配布され、検査作業者は製品へ1 company.safety:sealモザイクを転送します。

アカウントのセットアップ

会社アカウントと検査作業者アカウントを準備します。

Operatorアカウントの作成

検査作業者アカウントを作ります。


$> nem2-cli generate
Introduce network type (MIJIN_TEST, MIJIN, MAIN_NET, TEST_NET): MIJIN_TEST
Do you want to save it? [y/n]: y
Introduce NEM 2 Node URL. (Example: http://localhost:3000): http://localhost:3000
Insert profile name (blank means default and it could overwrite the previous profile): operator
New Account:    SD3JRL-JNHRLJ-HANQR4-LTAZSE-RMAQER-FHMCLD-3ZFS
Public Key:     030C85D3CA2B4B2E2CE28919AA5EC296D6AB354EE23BB846867D9D8DD41996A4
Private Key:    77F943572DCAD7E710A19BDA908CF8CACAC6AEEA4191A5A01A017AB6346548B1

秘密鍵をメモしなくても、2つ目の質問でyと回答していれば、コマンドで確認できるので大丈夫です。

Companyアカウントの設定

Companyアカウントには初期配布のXEMを持っている初期アドレスを利用します。

$> cd build/generated-addresses/
$> cat addresses.yaml

addresses.yamlに初期アカウントが記述されているので、nemesis_addressesのうちからどれでも1つ選んでください。


nemesis_addresses:
- private: B90BEFE75105C1D3EA8BC9783F6873B314F271AF0B37D3E97186643C65983E88
  public: 4CC296EADED0078CD1872CF37C5836113C6F1BFDB4C3C4F6650F8693D5F61A44
  address: SA4ZEMF73SL4DLMVNXRRILVGU74DK55C3444HJHF

今度はprofile createオプションでプロファイルを作成します。
これは秘密鍵からプロファイルを作るもので、選んだアカウントの秘密鍵を入力します。

$> nem2-cli profile create
Introduce network type (MIJIN_TEST, MIJIN, MAIN_NET, TEST_NET): MIJIN_TEST
Introduce your private key: B90BEFE75105C1D3EA8BC9783F6873B314F271AF0B37D3E97186643C65983E88
Introduce NEM 2 Node URL. (Example: http://localhost:3000): http://localhost:3000
Insert profile name (blank means default and it could overwrite the previous profile): company

プロファイルを確認してみる

operatorcompanyで名前付きプロファイルとして保存されたものを確認してみます。

$> nem2-cli profile list
operator->
        Network:        MIJIN_TEST
        Url:            http://localhost:3000
        Address:        SD3JRLJNHRLJHANQR4LTAZSERMAQERFHMCLD3ZFS
        PublicKey:      030C85D3CA2B4B2E2CE28919AA5EC296D6AB354EE23BB846867D9D8DD41996A4
        PrivateKey:     77F943572DCAD7E710A19BDA908CF8CACAC6AEEA4191A5A01A017AB6346548B1

company->
        Network:        MIJIN_TEST
        Url:            http://localhost:3000
        Address:        SA4ZEMF73SL4DLMVNXRRILVGU74DK55C3444HJHF
        PublicKey:      4CC296EADED0078CD1872CF37C5836113C6F1BFDB4C3C4F6650F8693D5F61A44
        PrivateKey:     B90BEFE75105C1D3EA8BC9783F6873B314F271AF0B37D3E97186643C65983E88

間違えてしまったらもう一度同じ名前で上書きすればよいです。
また力技ですが、この設定は~/.nem2rc.jsonに保存されるので直接書き換えることもできます。

$> cat ~/.nem2rc.json
{"operator":{"privateKey":"77F943572DCAD7E710A19BDA908CF8CACAC6AEEA4191A5A01A017AB6346548B1","networkType":144,"url":"http://localhost:3000"},"company":{"privateKey":"B90BEFE75105C1D3EA8BC9783F6873B314F271AF0B37D3E97186643C65983E88","networkType":144,"url":"http://localhost:3000"}}

companyのネームスペースを取得する

ネームスペースを取得します。
トランザクションは非同期で実行されるので、その結果を確認するためには監視が必要です。
ターミナルをもう2つ用意してアカウントの監視を始めます。

トランザクションの状態を監視

$> nem2-cli monitor status --profile company
Monitoring SA4ZEM-F73SL4-DLMVNX-RRILVG-U74DK5-5C3444-HJHF using http://localhost:3000
connection open

承認済みの監視

$> nem2-cli monitor confirmed --profile company
Monitoring SA4ZEM-F73SL4-DLMVNX-RRILVG-U74DK5-5C3444-HJHF using http://localhost:3000
connection open

ネームスペースの取得

監視を待機させたらネームスペースの取得トランザクションを実行します。
ここではルートネームスペースとしてcompany90000ブロック(およそ15日間)の期間取得するトランザクションを発行します。

$> nem2-cli transaction namespace --name company --rootnamespace --duration 90000 --profile company
Transaction announced correctly
Hash:    912ADD9C83D2253E622DB2833407548A5CB6358C0DFD0D3667ADC2C0D149243E
Signer:  4CC296EADED0078CD1872CF37C5836113C6F1BFDB4C3C4F6650F8693D5F61A44

しばらくすると、confirmedを監視していたコンソールに承認されたトランザクションが現れます。

RegisterNamespaceTransaction: NamespaceName:company NamespaceType:RootNamespace Duration:90000 Signer:SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR Deadline:2018-07-26 Hash:018D4DF04E686E6E195BBC85A41E9AC193B357C02A5AA30F89D7E95025660DFF

確認してみましょう。

$ nem2-cli namespace info -n company --profile company
Namespace: company
------------------

hexadecimal:    8f6d56dafc485be7
uint:       [ 4232600551, 2406307546 ]
type:       Root namespace
owner:      SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR
startHeight:    306
endHeight:  90306

サブネームスペースの取得

続いてcompany.safetyのサブネームスペースを取得します。

$> nem2-cli  transaction namespace --name safety --subnamespace  --parentname company --profile company
Do you want to create a root namespace? [y/n]: n
Transaction announced correctly
Hash:    6842A4D33CF83044D923A986741E55C84DFF9D7813FC1A91E5AE27C146366F2E
Signer:  82B8D0CEAB58EABE13F9EE4467E5E6A55F585636E8F0FC83C6D17F48C0577CFF

同様に確認してみましょう。

$ nem2-cli namespace info -n company.safety --profile company
Namespace: company.safety
-------------------------

hexadecimal:    f294890eaf227d65
uint:       [ 2938273125, 4069820686 ]
type:       Sub namespace
owner:      SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR
startHeight:    306
endHeight:  90306

Parent Id: company.safety
-------------------------

hexadecimal:    8f6d56dafc485be7
uint:       [ 4232600551, 2406307546 ]

モザイクの作成

サブネームスペース company.safetyseal モザイクを作成します。
総量は1000000とし、転送は可能、可分性は0、期間は90000ブロックで設定しています。

$> nem2-cli transaction mosaic --mosaicname seal --namespacename company.safety --amount 1000000 --transferable --supplymutable --divisibility 0 --duration 90000 --profile company
Do you want mosaic to have levy mutable? [y/n]: n
Transaction announced correctly
Hash:    BAED85EFEBF8A86223DFE30EB512BCEC1D4F286D3FDAA218F3F555B7E6A46575
Signer:  82B8D0CEAB58EABE13F9EE4467E5E6A55F585636E8F0FC83C6D17F48C0577CFF

同様に確認してみましょう。

$ nem2-cli mosaic info -n company.safety:seal --profile company
Mosaic: safety:seal
-------------------

hexadecimal:    e4ee1b491bb373cd
uint:       [ 464745421, 3840809801 ]

divisibility:   0
transferable:   true
supply mutable: true
active:     true
block height:   504
duration:   90000
owner:      SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR
supply:     1000000
namespaceId hex: f294890eaf227d65
namespaceId uint: [ 2938273125, 4069820686 ]

これで検査合格の際に、製品へ検査済みとして送るシール(モザイク)の準備ができました。

モザイクの配布

このモザイクを検査作業者のアカウントへ送ります。

$> nem2-cli transaction transfer --profile company
Introduce the recipient address: SAED4E4DXVQIOLK52XNGIRL6OZ7BRQRLMFAKO2BZ
Introduce the mosaics in the format namespaceName:mosaicName::absoluteAmount, add multiple mosaics splitting them with a comma:
> company.safety:seal::1000
Transaction announced correctly
Hash:    A405AA6D798EE5111D99293DD6A344B7B1094B9E9BCF6ADC5D9A5426BDDF2EC0
Signer:  82B8D0CEAB58EABE13F9EE4467E5E6A55F585636E8F0FC83C6D17F48C0577CFF

確認してみましょう。

$ nem2-cli account info --profile operator
Account:        SAED4E-4DXVQI-OLK52X-NGIRL6-OZ7BRQ-RLMFAK-O2BZ
-------------------------------------------------------

Address:        SAED4E-4DXVQI-OLK52X-NGIRL6-OZ7BRQ-RLMFAK-O2BZ
at height:      520

PublicKey:      0000000000000000000000000000000000000000000000000000000000000000
at height:      0

Importance:     0
at height:      0

Mosaics
safety:seal:    1000

届いているようですね。

Serverアプリケーションにcompanyの秘密鍵を設定する

Serverアプリケーションがトランザクションを発信できるように、COMPANY_PRIVATE_KEYに秘密鍵を設定してください。

# server/.env
SENSOR_PRIVATE_KEY=''
COMPANY_PRIVATE_KEY='9FBB2CF3AD1D3E8FD810122CC12C7DC452EBC630C3FFD8E9E6D505A042061DAD'

保存したら、Serverを Ctrl+c で停止させ、もう一度 npm start して起動しなおしてください。
内容が反映された状態になります。

製品をブロックチェーンへ登録する

Dashboard(http://localhost:4200)を開いて、Create a new productボタンを押してください。
アドレスと公開鍵が作成されるので、#1のリンクをクリックして詳細を見てみます。

dashboard-2.png

新しい製品がデータベースに登録されましたが、まだチェーンへ登録する処理が実装されていないので、実装していきます。

チェーンへ登録する実装

server/src/controller/product/product.controller.tsを開き、createProductメソッドを修正します。

// ---- 上の方に追記
import {Account, NetworkType} from 'nem2-sdk'

// ----

export let createProduct: ExpressSignature = (request, response, next) => {

    const productService = new ProductService();

    // ---- 追記ここから
    const companyPrivateKey = process.env.COMPANY_PRIVATE_KEY as string;
    const companyAccount = Account
        .createFromPrivateKey(companyPrivateKey, NetworkType.MIJIN_TEST);
    // ---- ここまで

    // Save product in the database and return product created
    return productService.createProduct()
        // ---- 追記ここから
        .flatMap(product => {
            return productService.registerProductInBlockchain(companyAccount, product.id)
                .map(ignored => product);
        })
        // ---- ここまで
        .subscribe(product => response.status(200).send(product.toMessage()),
            err => response.status(400).send(err));
};

companyアカウントインスタンスを作り、registerProductInBlockchainメソッドでチェーンへ登録します。

次に、server/src/domain/product/product.service.tsを開き、registerProductBlockchainメソッドを完成させます。


    public registerProductInBlockchain(account: Account, productId: number) : Observable<TransactionAnnounceResponse>{

        // Create deterministic public key for product
        const product = Asset.create(account.publicAccount,
            'company',
            productId.toString(),
            {});

        // Create transaction
        const publishableProduct = AssetService.publish(product);

        // Sign transfer transaction with company account
        const signedTransaction = account.sign(publishableProduct);

        return this.transactionHttp.announce(signedTransaction);
    }

渡された製品のIDとcompanyアカウントインスタンスでトランザクションに署名をして、発信するようにします。

実装したら、再度Create a new productボタンを押してください。
今度は監視しているコンソールにトランザクションが流れるのを確認でき、数秒後には画面に登録された製品情報が表示されるようになります。

AggregateTransaction:  InnerTransactions: [ TransferTransaction: Recipient:SBLIUQ-TVZSJE-KDGSEM-XBZKOD-AQHBNV-AZIHGP-WNLC Message:"asset(1):company,3" Signer:SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR Deadline:2018-07-27 TransferTransaction: Recipient:SBLIUQ-TVZSJE-KDGSEM-XBZKOD-AQHBNV-AZIHGP-WNLC Message:"metadata(1):" Signer:SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR Deadline:2018-07-27 ] Signer:SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR Deadline:2018-07-27 Hash:6E86D5540F20D43CDA7D8AD896A7928E74CF9E7E91CD818AF9AC4189B1C68D6E

dashboard-3.png

検査済みシールを送る

製品が登録されたので、検査作業者がこの製品を検査し、合格したとして、検査済みシールを添付しましょう。

transferSafetySealメソッドの実装

その前に、まだシールを送信する実装が無いので、実装します。
dashboard/src/app/services/safetySeal.service.tsを開いて、メソッドを完成させます。


// 実装に必要なモジュールのimportを追加してください。
import {EmptyMessage, Mosaic, MosaicId, TransferTransaction, ...

// ----

  transferSafetySeal(productAddress: Address, operatorAccount: Account) : SignedTransaction {

    // Create transfer transaction
    const transferTransaction = TransferTransaction.create(
      Deadline.create(),
      productAddress,
      [new Mosaic(new MosaicId('company.safety:seal'), UInt64.fromUint(1))],
      EmptyMessage,
      NetworkType.MIJIN_TEST
    );

    // Sign transfer transaction with operator account
    return operatorAccount.sign(transferTransaction);
  }

ヘッダリンクからSend Safety Sealへ移動してください。
#2の製品を選択肢、operatorの秘密鍵を入力して、Send Safety Sealをクリックしてください。

すぐ下にトランザクションの情報が表示されるので、confirmedになったら、一覧の#2のリンクから詳細を開いてみます。

モザイクsafety:sealが添付されています。
これでこの製品は、検査作業者によって検査に通過して、安全であることが証明されました。

ノードのAPIからも確認してみましょう。

{"meta":{},"account":{"address":"90F17BCA5FA18815AAC614323C0AFFE7AC6D39D7030A80CDB0","addressHeight":[2326,0],"publicKey":"0000000000000000000000000000000000000000000000000000000000000000","publicKeyHeight":[0,0],"mosaics":[{"id":[464745421,3840809801],"amount":[1,0]}],"importance":[0,0],"importanceHeight":[0,0]}}

JSONレスポンスではわかりにくいですが、{"id":[464745421,3840809801],"amount":[1,0]}company:safety:sealモザイクです。
いちおう定義をnem2-cliで確認しましょう。

$> nem2-cli mosaic info -u [464745421,3840809801] --profile operator
Mosaic: safety:seal
-------------------

hexadecimal:    e4ee1b491bb373cd
uint:           [ 464745421, 3840809801 ]

divisibility:   0
transferable:   true
supply mutable: true
active:         true
block height:   504
duration:       90000
owner:          SAM3XW-LHBTLI-IOFM6W-LZRSGP-QSEDSQ-W7TRIQ-BMRR
supply:         1000000
namespaceId hex: f294890eaf227d65
namespaceId uint: [ 2938273125, 4069820686 ]

Dashboardアプリケーション経由でなくても、チェーンから直接情報を得ることができました。

デジタルセンサによる検査

use-case-nem-adding-a-sensor.png

ヒューマンエラーを回避するために、センサによる検査にもパスしたら検査済みシールを送るというケースに対応してみます。

アグリゲートトランザクションの利用

ここでは、アグリゲートトランザクションを使って、検査作業者からのモザイク送信とセンサからの検査パスメッセージの送信をひとまとめにします。
センサによる検査に不合格となった場合は署名をせずに、検査失敗メッセージを送信することとします。

センサアカウントの作成

まずセンサのアカウントを作成します。
今回は対話式ではなく、パラメタを直接渡して作ります。

$> nem2-cli account generate --network MIJIN_TEST --url http://localhost:3000 --profile sensor --save
New Account:    SD2Q47-DBAFHA-WROET7-FU4XPK-6NDJQN-QBXAOD-MDSY
Public Key:     A43FE23D1688C7689A85C0CE72924858B732B7F51EEB25DA9564591B52512939
Private Key:    78F4F772AFEB1F837DAECF2F4CB12B80389F8A83D4D841BDEFAB2D07543F057C

Stored sensor profile

確認してみましょう。

$> nem2-cli profile list
sensor->
        Network:        MIJIN_TEST
        Url:            http://localhost:3000
        Address:        SD2Q47DBAFHAWROET7FU4XPK6NDJQNQBXAODMDSY
        PublicKey:      A43FE23D1688C7689A85C0CE72924858B732B7F51EEB25DA9564591B52512939
        PrivateKey:     78F4F772AFEB1F837DAECF2F4CB12B80389F8A83D4D841BDEFAB2D07543F057C

transferSafetySeal メソッドの修正

  1. 製品に検査済みシールを送信する
  2. 製品に検査パスメッセージを送信する

このケースでは、この2つのトランザクションをアグリゲートトランザクションによって、両方のトランザクションが同時に成功するか失敗するようにします。

(1)のトランザクションは検査作業者が署名しますが、(2)のトランザクションにはセンサの署名を必要とします。
このようなトランザクションを「アグリゲートボンドトランザクション」と呼ばれます。

アグリゲートボンドトランザクションの場合、検査作業者は10 XEMをロックさせる必要があります。
センサがトランザクションに署名すると、ロックされた10 XEMは返還されます。

dashboard/src/app/services/safetySeal.service.tsを開いて、メソッドを修正します。

// 実装に必要なモジュールのimportを追加してください。
import {AggregateTransaction, PublicAccount, PlainMessage, ...

// ----

  transferSafetySeal(productAddress: Address, operatorAccount: Account) : SignedTransaction {

    const sensorPublicKey = ''; // センサーの公開鍵をセットしてください。
    const sensorPublicAccount = PublicAccount.createFromPublicKey(sensorPublicKey, NetworkType.MIJIN_TEST);

    const operatorToProductTransaction = TransferTransaction.create(
      Deadline.create(),
      productAddress,
      [new Mosaic(new MosaicId('company.safety:seal'), UInt64.fromUint(1))],
      EmptyMessage,
      NetworkType.MIJIN_TEST
    );

    const sensorToProductTransaction = TransferTransaction.create(
      Deadline.create(),
      productAddress,
      [],
      PlainMessage.create("Inspection passed"),
      NetworkType.MIJIN_TEST
    );

    const aggregateTransaction = AggregateTransaction.createBonded(
      Deadline.create(),
      [
        operatorToProductTransaction.toAggregate(operatorAccount.publicAccount),
        sensorToProductTransaction.toAggregate(sensorPublicAccount)
      ],
      NetworkType.MIJIN_TEST);

    return operatorAccount.sign(aggregateTransaction);

  }

ロック用のXEMを検査作業者アカウントへ送る

適当にいくらか送っておきましょう。

# 初期配布を持っている company から XEM を送る
nem2-cli transaction transfer -r SD2Q47DBAFHAWROET7FU4XPK6NDJQNQBXAODMDSY -c nem:xem::100000000  --profile company

アグリゲートトランザクションの処理

dashboard/src/app/components/sendsafetySeal.component.tsを開いて、132行目からのアグリゲートボンドトランザクションの扱いを確認してみます。


    const lockFundsTransactionSigned = this.safetySealService.createAndSignLockFundsTransaction(signedTransaction, operatorAccount);

    this.transactionHttp
      .announce(lockFundsTransactionSigned)
      .subscribe(x => console.log(x), err => console.error(err));

    this.listener
      .confirmed(operatorAccount.address)
      .filter((transaction) => transaction.transactionInfo !== undefined
        && transaction.transactionInfo.hash === lockFundsTransactionSigned.hash)
      .flatMap(ignored => this.transactionHttp.announceAggregateBonded(signedTransaction))
      .subscribe(announcedAggregateBonded => console.log(announcedAggregateBonded),
        err => console.error(err));
  1. 資産ロックトランザクションをアナウンスします。
  2. 資産ロックトランザクションの承認の監視を始めます。
  3. 承認されたらアグリゲートボンドトランザクションをアナウンスします。

センサの挙動をシミュレーション

物理的なセンサの代わりに、センサを模倣するコードをserverに実装します。
server/.envにセンサの秘密鍵を追記します。

# server/.env
SENSOR_PRIVATE_KEY='78F4F772AFEB1F837DAECF2F4CB12B80389F8A83D4D841BDEFAB2D07543F057C'
COMPANY_PRIVATE_KEY='9FBB2CF3AD1D3E8FD810122CC12C7DC452EBC630C3FFD8E9E6D505A042061DAD'

追記したらserverアプリケーションを再起動してください。

project/server/src/service/sensor.service.tsを開いてセンサの挙動を確認してみましょう。

    startListening() {

        const sensorPrivateKey = process.env.SENSOR_PRIVATE_KEY as string;
        const sensorAccount = Account.createFromPrivateKey(sensorPrivateKey, NetworkType.MIJIN_TEST);

        // 署名が必要なアグリゲートボンドを監視して、センサによる検査を実施します。
        this.listener.open().then(_ => {

            const address = process.env.SAFETY_DEPARTMENT_ADDRESS ?
                Address.createFromRawAddress(process.env.SAFETY_DEPARTMENT_ADDRESS) : sensorAccount.address;

            this.listener
                .aggregateBondedAdded(address)
                .filter((_) => !_.signedByAccount(sensorAccount.publicAccount))
                .flatMap(transaction => this.digitalInspection(transaction, sensorAccount))
                .subscribe(announcedTransaction => console.log(announcedTransaction),
                    err => console.log(err));
        });
    }

    // 検査結果を返します。
    // この実装はダミーのため、実行時に50%の確率で成功するようになっています。
    private inspect() {
        const inspection = Math.floor(Math.random() * 6);
        return (inspection < 2.5);
    }

    // 検査に合格した場合に署名を要求されているトランザクションへの署名トランザクションの発行
    private announceCosignatureTransaction(transaction: AggregateTransaction, sensorAccount: Account): Observable<TransactionAnnounceResponse> {
        const cosignatureTransaction = CosignatureTransaction.create(transaction);
        const cosignatureSignedTransaction = sensorAccount.signCosignatureTransaction(cosignatureTransaction);
        return this.transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction);
    }

    // 検査に合格しなかった場合の検査失敗メッセージトランザクションの発行
    private announceErrorTransaction(recipient: Address, sensorAccount: Account): Observable<TransactionAnnounceResponse> {
        const transferTransaction = TransferTransaction.create(
            Deadline.create(),
            recipient,
            [],
            PlainMessage.create('Invalid inspection'),
            NetworkType.MIJIN_TEST
        );

        const signedTransaction = sensorAccount.sign(transferTransaction);
        return this.transactionHttp.announce(signedTransaction);
    }

    // センサによる検査メソッド。
    // 検査パスまたは検査失敗した場合でそれぞれトランザクションを発行する
    private digitalInspection(transaction: AggregateTransaction, sensorAccount: Account): Observable<TransactionAnnounceResponse> {
        // Assuming all inner transactions send to this account are inner transactions
        const product: TransferTransaction = <TransferTransaction> transaction.innerTransactions[0];
        if (this.inspect()) return this.announceErrorTransaction(product.recipient, sensorAccount);
        else return this.announceCosignatureTransaction(transaction, sensorAccount);
    }

startListeningServerアプリケーションの初期化時に呼ばれて監視とトランザクションへの署名を待機します。

動作

ここまでできたら動作させてみましょう。
手順は同様、製品を登録して、operatorの秘密鍵でシールの送信を行います。
余裕があればターミナルを開いて、operatorsensorアカウントも監視しておくとわかりやすいでしょう。少し待って検査が終わると結果が取得できるようになります。

検査に合格した場合

dashboard-8.png

モザイクを受信していて、Inspection passedというメッセージを受け取っていることが確認できます。

検査に失敗した場合

dashboard-7.png

モザイクは受信しておらず、Invalid inspectionというメッセージを受け取っていることが確認できます。

検査作業者を追加する

use-case-nem-adding-operator.png

複数の検査作業者を追加して検査作業の効率化を図りましょう。
このケースでは、在庫管理部アカウントが検査作業者を、品質管理部アカウントから検査済みシールを製品へ送ることで検査を管理するようにします。

複数の検品作業者のいずれかからのモザイク転送要求、センサとの組み合わせ等複雑なケースに対応するため、マルチレベルマルチシグの構築もします。

検査作業者2アカウントの作成

新しい検査作業者アカウントを作成します。

$> nem2-cli account generate --network MIJIN_TEST --url http://localhost:3000 --profile operator2 --save 
New Account:    SAXAZY-RUF6DE-FI2GTP-6LEHUT-ZTYVYE-Z53D3M-XCBX
Public Key:     0C0CBF09DC7CF34A50A3B1EDF3B9E2A1DCC4355C42B76E4C8E872CF80CA500E6
Private Key:    B0B452767EA176EB3919B7282463F3868D6C1184A487AF1901D502A7E10A6174
# 初期配布を持っている company から XEM を送る
nem2-cli transaction transfer -r SAXAZYRUF6DEFI2GTP6LEHUTZTYVYEZ53D3MXCBX -c nem:xem::100000000  --profile company

在庫管理部アカウントと品質管理部アカウントの作成

$> nem2-cli account generate --network MIJIN_TEST --url http://localhost:3000 --profile warehouse --save 
New Account:    SCSUZD-YNJCWH-KBDSEI-KA3PLA-M6EFOR-XTMQUQ-64ZI
Public Key:     7F600B8B367F8DCF5DF3D76A06EF6F84D52267A9F912FD3436F8C7EF97CCC13B
Private Key:    97AAF2CD95021D3CDD78888198E09E27E9CDD485F5880FCC2DDF2B56BFC171C0

$> nem2-cli account generate --network MIJIN_TEST --url http://localhost:3000 --profile safetydept --save 
New Account:    SBEZUJ-2NCWCV-GUFDJG-QWUTVG-Q54CTL-4LM246-ODEE
Public Key:     531D305BE398DA3647935FF43D1865EEA588E9763B2F988800B695A1EABAC132
Private Key:    B8242D2A761F0AFF54D5F84287C197D213957D0BFC364FF2BB3B26E2CC1DF46C

マルチシグ化ツールの実装

dashboard/src/app/services/multisig.service.tsを開いてcreateMultisigを修正します。

// 必要なモジュールの追記
import {NetworkType, Deadline, MultisigCosignatoryModification, MultisigCosignatoryModificationType, ModifyMultisigAccountTransaction...

// ----

  createMultisig(account: Account, minApproval: number, cosignatories: PublicAccount[]) : SignedTransaction {

    const modifications = cosignatories.map(cosignatory => {
      return new MultisigCosignatoryModification(
        MultisigCosignatoryModificationType.Add,
        cosignatory,
      );
    });

    const createMultisigTransaction = ModifyMultisigAccountTransaction.create(
      Deadline.create(),
      minApproval,
      1, // 連署者の追加
      modifications,
      NetworkType.MIJIN_TEST);

    return account.sign(createMultisigTransaction);
  }

在庫管理部アカウントと品質管理部アカウントのマルチシグ化

右上のMultisig Serviceをクリックして、マルチシグ化ツールを開きます。
マルチシグにしたいアカウントの秘密鍵を入力して、連署者として追加したいアカウントの公開鍵を入力します。
Min Approval:は最低連署者数です。
次の構成でマルチシグ化のトランザクションを発信します。

在庫管理部のマルチシグ構成

最低連署者数を1operatoroperatorを連署者として設定する。

dashboard-9.png

トランザクションが承認されたら確認してみましょう。

品質管理部のマルチシグ構成

最低連署者数を2warehousesensorを連署者として設定する。

dashboard-10.png

トランザクションが承認されたら確認してみましょう。

マルチシグの階層を確認してみる

multisig/graph にアクセスすると階層的な情報を得られます。

品質管理部アカウントへモザイクを送る

companyアカウントからsafetydeptアカウントへcompany.safety:sealモザイクを転送します。

nem2-cli transaction transfer -r SBEZUJ2NCWCVGUFDJGQWUTVGQ54CTL4LM246ODEE -c company.safety:seal::1000  --profile company

これでアカウントの下準備は完了です。

transferSafetySealメソッドの修正

  transferSafetySeal(productAddress: Address, operatorAccount: Account) : SignedTransaction | null{
    const safetyDeparmentAccountPublicKey = '531D305BE398DA3647935FF43D1865EEA588E9763B2F988800B695A1EABAC132'; // 品質管理部の公開鍵を設定する
    const safetyDeparmentPublicAccount = PublicAccount.createFromPublicKey(safetyDeparmentAccountPublicKey, NetworkType.MIJIN_TEST);

    const safetyDepartmentToProductTransaction = TransferTransaction.create(
      Deadline.create(),
      productAddress,
      [new Mosaic(new MosaicId('company.safety:seal'), UInt64.fromUint(1))],
      EmptyMessage,
      NetworkType.MIJIN_TEST
    );

    const aggregateTransaction = AggregateTransaction.createBonded(
      Deadline.create(),
      [safetyDepartmentToProductTransaction.toAggregate(safetyDeparmentPublicAccount)],
      NetworkType.MIJIN_TEST);

    return operatorAccount.sign(aggregateTransaction);
  }

品質管理部アカウントが製品にモザイクを送るよう変更します。

serverアプリケーションにsafetydeptのアドレスを設定する

# server/.env
SAFETY_DEPARTMENT_ADDRESS='SBEZUJ2NCWCVGUFDJGQWUTVGQ54CTL4LM246ODEE'

サーバを再起動して、準備は完了です。

動作させてみる

  1. 検査作業者のどちらかが製品を検査します。
  2. 検査が合格となったら、品質管理部へ製品に検査済みシールを送るよう、トランザクションを発行します。
  3. 製品管理部は検査作業者のいずれかの署名があれば承認となります。
  4. その後、センサによる検査が行われ、パスしたならば署名が行われ、製品管理部とセンサによって承認となります。
  5. 署名が揃うと品質管理部から製品へ検査済みシールのモザイクが転送されます。

操作は前述と同じです。製品を作って、検査作業者の秘密鍵で署名します。
どちらのアカウントの秘密鍵でもよいですし、両方やってみてください。

dashboard-11.png

さらなる拡張の余地

multilevel-multisig-account.png

ワークショップについてはここまでですが、図のように、温度管理の証明、製造年月日の証明なども組み合わせていくことができます。

まとめ

プログラミングや実装についてわからない方だと体感しにくいかもしれませんが、serverdashboardも大した実装はされていません。
ざっくりいうと製品を登録する、シールを送る、署名をする部分しかないです。
モザイクの管理や署名が揃ったときにモザイクが転送されたりする部分についてはすべてnemの機能によるものです。

また、マルチレベルマルチシグの構成へと変更していく間のソース修正も、トランザクションの送り方を少し修正した程度です。

アグリゲートトランザクションによる原子性なトランザクションとマルチレベルマルチシグによるコントラクトはのような仕組みを作ろうとすると、それなりの労力(開発や動作テスト)がかかるのですが、このように既存のWebサービスを組み込むように、実装することができます。

詳細は不明ですが、かなり近いことを実現していると思われます。
こちらは(Catapultではない)mijin v1なので、アグリゲートトランザクションなどは使用できませんが、
原子性を別の方法で実現したり、複数のモザイクによって管理するなどをしているのかもしれません。
Catapultを用いれば、それらももっと簡単に実装しなおせるのかもしれませんね。

今更なんですが、秘密鍵の存在に囚われていたので、冒頭の公開鍵を独自ルールで作成し、そこからアドレスを生成するという方法に驚きました。
確かにアドレスがNEMネットワーク上で有効なものであればなんでもよいわけです。
製品の秘密鍵が存在しないことで製品の状態をイミュータブルとすることもできています。
(本当にモザイクを持っていないのか、剥がされたりしていないか、等をトランザクション履歴から確認する必要がない)

nem2-assets-identifierがモザイクの扱いを簡単にしているような気がします。
このあたり何をどうしているのかもうちょっと調べてみたいですね。

資料や情報を見ればなんとなく実現できそうだったり、わかった気になりますが、実際動くものを触ってみるとより一層可能性を感じます。

非技術者の方も、基本的にはガイドに沿うだけで進められますし、この記事では原文をだいぶ補完したつもりですので、
やってみてはいかがでしょうか。

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
7