NEM blockchain applied to supply chain
nemのサプライチェーンマネジメントケースのワークショップ用プロジェクトが公開されていたので、一通りやってみました。
(現在公開されているものが当時より更新されていますので、こちらのアーカイブ版を実施した内容となります)
なお、記事中やキャプチャ中の秘密・公開鍵、アドレスなどはご自身で作ったものに置き換えてください。
(やり直したり、作り直したため、チグハグになってしまいました。ご了承ください。)
要求環境
- Git
- Docker
- Docker Compose
- Node.js 8.9.x 以上
環境のセットアップ
追記: 以下で立ち上がるノードを公開しました。
エンドポイントを読み替えれば、ローカルマシンにノード立ち上げは不要です。
ただし、ネームスペースなどが誰かに取られている状態になっているかもしれないので、
そのあたりも適宜読み替えてご利用ください。
エンドポイント: 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/
でアクセスすることができます。
ワークショッププロジェクトのダウンロード
このリポジトリをclone
するかアーカイブをダウンロードして展開してください。
project
以下のフォルダに、dashboard
とserver
というアプリケーションがあるので、それぞれでパッケージインストールをして、アプリケーションを起動します。
cd project/server
npm install
npm start
cd project/dasbhoard
npm install
npm start
Dashboardアプリケーションにはhttp://localhost:4200
でアクセスできます。
nem2-cli のインストール
アカウントを作成したり、トランザクションを発行できるコマンドラインツールです。
$> npm i -g nem2-cli
なおnem2-cli
コマンドは多様します。
bash
用ですがコマンド補完を作りましたのでよかったらどうぞ。
サプライチェーンマネジメントの概要
今回のケースでは、製品(PRODUCT)にアドレスを割り当て、検査作業者(OPERATOR)によるチェックに問題がなかったら、モザイクをアドレスへ転送します。
このモザイクを持っていることが、その製品が安全であることを示します。
製品
製品にアドレスを割り当てます。
このアドレスのトランザクション履歴を見ることで、要所要所で行われた作業のトレースが確認できるようにします。
製品アカウントはトランザクションを発信する必要がなく、受信するだけでよいので以下の方法で公開鍵を作成します。
publicKey = sha256(company_identifier + product_identifier)
データベースが失われたとしても、company_identifier
とproduct_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
プロファイルを確認してみる
operator
とcompany
で名前付きプロファイルとして保存されたものを確認してみます。
$> 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
ネームスペースの取得
監視を待機させたらネームスペースの取得トランザクションを実行します。
ここではルートネームスペースとしてcompany
、90000
ブロック(およそ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.safety
に seal
モザイクを作成します。
総量は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
のリンクをクリックして詳細を見てみます。
新しい製品がデータベースに登録されましたが、まだチェーンへ登録する処理が実装されていないので、実装していきます。
チェーンへ登録する実装
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
検査済みシールを送る
製品が登録されたので、検査作業者がこの製品を検査し、合格したとして、検査済みシールを添付しましょう。
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アプリケーション経由でなくても、チェーンから直接情報を得ることができました。
デジタルセンサによる検査
ヒューマンエラーを回避するために、センサによる検査にもパスしたら検査済みシールを送るというケースに対応してみます。
アグリゲートトランザクションの利用
ここでは、アグリゲートトランザクションを使って、検査作業者からのモザイク送信とセンサからの検査パスメッセージの送信をひとまとめにします。
センサによる検査に不合格となった場合は署名をせずに、検査失敗メッセージを送信することとします。
センサアカウントの作成
まずセンサのアカウントを作成します。
今回は対話式ではなく、パラメタを直接渡して作ります。
$> 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 メソッドの修正
- 製品に検査済みシールを送信する
- 製品に検査パスメッセージを送信する
このケースでは、この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));
- 資産ロックトランザクションをアナウンスします。
- 資産ロックトランザクションの承認の監視を始めます。
- 承認されたらアグリゲートボンドトランザクションをアナウンスします。
センサの挙動をシミュレーション
物理的なセンサの代わりに、センサを模倣するコードを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);
}
startListening
はServer
アプリケーションの初期化時に呼ばれて監視とトランザクションへの署名を待機します。
動作
ここまでできたら動作させてみましょう。
手順は同様、製品を登録して、operator
の秘密鍵でシールの送信を行います。
余裕があればターミナルを開いて、operator
とsensor
アカウントも監視しておくとわかりやすいでしょう。少し待って検査が終わると結果が取得できるようになります。
検査に合格した場合
モザイクを受信していて、Inspection passed
というメッセージを受け取っていることが確認できます。
検査に失敗した場合
モザイクは受信しておらず、Invalid inspection
というメッセージを受け取っていることが確認できます。
検査作業者を追加する
複数の検査作業者を追加して検査作業の効率化を図りましょう。
このケースでは、在庫管理部アカウントが検査作業者を、品質管理部アカウントから検査済みシールを製品へ送ることで検査を管理するようにします。
複数の検品作業者のいずれかからのモザイク転送要求、センサとの組み合わせ等複雑なケースに対応するため、マルチレベルマルチシグの構築もします。
検査作業者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:
は最低連署者数です。
次の構成でマルチシグ化のトランザクションを発信します。
在庫管理部のマルチシグ構成
最低連署者数を1
、operator
とoperator
を連署者として設定する。
トランザクションが承認されたら確認してみましょう。
品質管理部のマルチシグ構成
最低連署者数を2
、warehouse
とsensor
を連署者として設定する。
トランザクションが承認されたら確認してみましょう。
マルチシグの階層を確認してみる
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'
サーバを再起動して、準備は完了です。
動作させてみる
- 検査作業者のどちらかが製品を検査します。
- 検査が合格となったら、品質管理部へ製品に検査済みシールを送るよう、トランザクションを発行します。
- 製品管理部は検査作業者のいずれかの署名があれば承認となります。
- その後、センサによる検査が行われ、パスしたならば署名が行われ、製品管理部とセンサによって承認となります。
- 署名が揃うと品質管理部から製品へ検査済みシールのモザイクが転送されます。
操作は前述と同じです。製品を作って、検査作業者の秘密鍵で署名します。
どちらのアカウントの秘密鍵でもよいですし、両方やってみてください。
さらなる拡張の余地
ワークショップについてはここまでですが、図のように、温度管理の証明、製造年月日の証明なども組み合わせていくことができます。
まとめ
プログラミングや実装についてわからない方だと体感しにくいかもしれませんが、server
もdashboard
も大した実装はされていません。
ざっくりいうと製品を登録する、シールを送る、署名をする部分しかないです。
モザイクの管理や署名が揃ったときにモザイクが転送されたりする部分についてはすべてnem
の機能によるものです。
また、マルチレベルマルチシグの構成へと変更していく間のソース修正も、トランザクションの送り方を少し修正した程度です。
アグリゲートトランザクションによる原子性なトランザクションとマルチレベルマルチシグによるコントラクトはのような仕組みを作ろうとすると、それなりの労力(開発や動作テスト)がかかるのですが、このように既存のWebサービスを組み込むように、実装することができます。
詳細は不明ですが、かなり近いことを実現していると思われます。
こちらは(Catapultではない)mijin v1なので、アグリゲートトランザクションなどは使用できませんが、
原子性を別の方法で実現したり、複数のモザイクによって管理するなどをしているのかもしれません。
Catapultを用いれば、それらももっと簡単に実装しなおせるのかもしれませんね。
今更なんですが、秘密鍵の存在に囚われていたので、冒頭の公開鍵を独自ルールで作成し、そこからアドレスを生成するという方法に驚きました。
確かにアドレスがNEMネットワーク上で有効なものであればなんでもよいわけです。
製品の秘密鍵が存在しないことで製品の状態をイミュータブルとすることもできています。
(本当にモザイクを持っていないのか、剥がされたりしていないか、等をトランザクション履歴から確認する必要がない)
nem2-assets-identifier
がモザイクの扱いを簡単にしているような気がします。
このあたり何をどうしているのかもうちょっと調べてみたいですね。
資料や情報を見ればなんとなく実現できそうだったり、わかった気になりますが、実際動くものを触ってみるとより一層可能性を感じます。
非技術者の方も、基本的にはガイドに沿うだけで進められますし、この記事では原文をだいぶ補完したつもりですので、
やってみてはいかがでしょうか。