速習Symbolブロックチェーンの11章、制限編です。

URLとしてはこちら。

アカウントに対する制限とモザイクのグローバル制限についての方法とのことです。

アカウントに対する制限は、アカウントに対しトランザクション/モザイク/アドレスの送受信可否を設定できるようです。

モザイクのグローバル制限については特定のモザイクについて、所持できる数量と所持できるアカウントの制限ができるようです。
事前に認証したアカウントのみが保持できることで、流通を制限できるようです。

さて、早速やっていきます。

実践編

11.1 アカウント制限

指定アドレスからの受信制限・指定アドレスへの送信制限

制限の関係から、carolXというアカウントを新規で作ることとしました。

// carolXのアカウントを作成する。
var carolX = await Account.createFromPrivateKey(
  await PrivateKey.create(carolXPrivateKey), NetworkTypeEnum.testnet);

// bobのアカウントを作成する。
var bob = await Account.createFromPrivateKey(
  await PrivateKey.create(bobPrivateKey), NetworkTypeEnum.testnet);

print("carolX address: ${carolX.address}");
print("bob address: ${bob.address}");

var tx = AccountRestrictionTxInfo.createAddressRestriction(
  carolX.publicAccount, 
  AccountRestrictionFlagsEnum.blockIncomingFromAddress,   // アドレスからの受信を制限する。
  [bob.address],                                          // 設定アドレス。
  [],                                                     // 解除アドレス。
  );

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carolX, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

await txSender.sendTransaction(transSetting, tx);

結果。ちゃんと制限できているのかはわからないですが、restrictionFlagsが32769ということで、受信制限のフラグが立っていると思われます。

このrestrictionFlagsの部分を変更することで制限を変更することができます。

なおこのrestrictionFlagsのところは下記の値を組み合わせて決定するようです。

アドレス対象0x0001
モザイクID対象0x0002
トランザクションタイプ対象0x0004
受信ホワイトリスト。(指定したもののみ受信可)0x0000
送信ホワイトリスト。(指定したもののみ送信可)0x4000
受信ブラックリスト。(指定したもののみ受信不可)0x8000
送信ブラックリスト。(指定したもののみ送信不可)0xC000

よってアドレス対象で受信不可となると0x8001となる感じですね。


指定モザイクの受信制限

指定のモザイクを受け入れないようにします。

// carolXのアカウントを作成する。
var carolX = await Account.createFromPrivateKey(
  await PrivateKey.create(carolXPrivateKey), NetworkTypeE

print("carolX address: ${carolX.address}");

var tx = AccountRestrictionTxInfo.createMosaicRestriction
  carolX.publicAccount, 
  AccountRestrictionFlagsEnum.blockIncomingContainMosaicI
  [MosaicId("72C0212E67A08BCE")],                        
  [],                                                    
  );

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carolX, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。        

var txSender = TransactionSender();

await txSender.sendTransaction(transSetting, tx);

指定モザイクを含む受信をブロックするようにしました。

なお、速習Symbolによるとモザイクの送信制限はないようです。


指定トランザクションの送信制限

指定したトランザクションを送信できないようにすることができます。


// carolXのアカウントを作成する。
var carolX = await Account.createFromPrivateKey(
  await PrivateKey.create(carolXPrivateKey), NetworkTypeEnum.testnet);

print("carolX address: ${carolX.address}");

var tx = AccountRestrictionTxInfo.createOperationRestriction(
  carolX.publicAccount, 
  AccountRestrictionFlagsEnum.allowOutgoingWithTxType,   // 指定トランザクションの送信のみ許可。
  [TransactionTypeEnum.accountOperationRestriction],     // 設定Txタイプ。アカウント制限のみ許可する。
  [],                                                    // 解除Txタイプ。
  );

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carolX, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

await txSender.sendTransaction(transSetting, tx);

通ったようです。これで実行可能なトランザクションは(たぶん)指定したトランザクション以外は使用できなくなりました。

今回、”トランザクションの送信制限”トランザクション以外は制限をかけたため、これでcarolXは”トランザクションの送信制限”トランザクション以外は使用不可となっていると思います。

なお、この”トランザクションの送信制限”をするトランザクション自体を制限することはできないようです。
まぁ、アカウントがGOX状態になるからでしょうね。制限解除できなくなるので。つまり、セルフバーン的な。


確認

制限状態の確認。

restrictionsにrestrictionsFlagsが32769、32770、16388とあります。

32769=0x8001であり、指定アドレスからの受信トランザクションをブロックすることがわかります。valuesに対象のアドレスが配列で設定されています。

32770=0x8002であり、指定されたモザイク識別子を含む受信トランザクションをブロックします。valuesに対象のモザイクIdが指定されています。

16388=0x4004であり、指定されたトランザクションタイプを持つ発信トランザクションのみ許可します。valuesに17232とありますが、0x4350を示し、これがトランザクションの送信制限トランザクションを示します。

制限されていることが確認できますね。


11.2 グローバルモザイク制限

モザイクに対して送信可能な条件を設定するようです。

  1. モザイクに対して送信可能な条件を設定
  2. 各アカウントに対して、グローバルモザイク制限専用のメタデータ付与
  3. 送受信するアカウントがともに条件を満たす場合のみ、モザイクを送付可能

らしいです。決まった範囲に絞り、流通を制限することができるという感じのようですね。

例ではKYCをしたアカウント間で取引ができる、という感じのようです。

グローバル制限機能つきモザイクの作成

carolが新しい制限付きモザイクを作成します。
なお、11.1で使用していたcarolXはトランザクションに制限がかかったため、解除しないかぎり使えないようになっています。(実行してしまった。

モザイク定義トランザクション、モザイク供給量変更トランザクションを作成します。
以前の通り? であれば、この2つをアグリゲートトランザクションとすることでモザイクの定義ができました。
ここにグローバルモザイク制限用のトランザクションを追加し、3トランザクションをアグリゲートトランザクションとします。

  • モザイク自体の定義
  • モザイクの供給量定義
  • グローバルモザイク制限の設定定義

の3点をまとめて「このモザイクはどういうものであるか?」を示すって感じでしょうか。

// carolのアカウントを作成する。
var carol = await Account.createFromPrivateKey(
  await PrivateKey.create(carol1PrivateKey), NetworkTypeEnum.testnet);

print("carol address: ${carol.address}");

var nonce = MosaicNonce.createRandom();

var newMosaicId = MosaicId.createNew(nonce, carol.address);

print("created mosaic id: ${newMosaicId.value}");

// 次で使うため保存しておく。
_createdMosaicId = newMosaicId;

// モザイクフラグの設定。
var mosaicFlags = MosaicFlagsEnum.restrictable        // グローバル制限設定の可否
  + MosaicFlagsEnum.supplyMutable                     // 供給量変更の可否
  + MosaicFlagsEnum.transferable                      // 第三者への譲渡可否
  + MosaicFlagsEnum.revocable;                        // 発行者からの回収可否

// モザイク作成Tx。
var mosaicDefTx = MosaicDefinitionTxInfo.create(
  carol.publicAccount, 
  newMosaicId, 
  BlockDuration(0),
  nonce, 
  mosaicFlags,
  Int8(0));

// モザイク供給量変更Tx。
var mosaicChangeTx = MosaicSupplyChangeTxInfo.create(
  carol.publicAccount, 
  mosaicDefTx.mosaicId, 
  Amount(1000000),
  MosaicSupplyChangeActionEnum.increase);

// グローバルモザイク制限Txを実行するには、本来前回の値を取得する必要があるが、今回は存在しない。(新規生成しているため)
// グローバルモザイク制限Tx。

var restrictionKey = RestrictionKey.createFromKeyName("KYC");

print("Restriction key: ${restrictionKey.value}");

var mosaicGlobalResTx = MosaicGlobalRestrictionTxInfo.create(
  carol.publicAccount, 
  mosaicDefTx.mosaicId, 
  null,                                           // 参照先とは?
  RestrictionKey.createFromKeyName("KYC"),        // 自動的に生成してやっていいね。
  RestrictionValue(0),                            // 過去の制限値を設定してやる必要がある。
  RestrictionValue(1),
  MosaicRestrictionTypeEnum.uninitializedValue,   // 過去は未設定となっている。
  MosaicRestrictionTypeEnum.equal,                // イコールを設定。
  );

// ここまでのTxをまとめる。
var aggTx = [
  mosaicDefTx,
  mosaicChangeTx,
  mosaicGlobalResTx,
];

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carol, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

// 必要なのはCarolXの署名のみのため、コンプリートで送信する。連署不要。
await txSender.sendAggregateComplete(transSetting, aggTx, []);

実行結果はこちら。

こんな感じです。

新規設定した制限タイプ(newRestrictionType)は1、値(newRestrictionValue)は1となっています。ここで設定した値1は何の効果があるのだろう?


アカウントへのモザイク制限適用

先ほど、モザイクに制限をかけました。あれは「このモザイクは1アカウント1つしか持てないよ」という制限です。
次に、「どのアカウントが保持することができるのか?」を決定(適格情報の付与)をするようです。

速習Symbolにおいて、送信・受信にかかる制限のため、既に所有しているモザイクについて制限はない、とのこと。
削除はできないということですね。
※設定されてないと削除となるのであれば、モザイクの定義とRestrictionType、適格情報の付与までをアグリゲートでまとめて発行しないと、だめな気もしますもんね。

きっと、「あいつは不適格である」ってなるのであれば、別途リボーカブルで没収しておく必要があるのかもですね。
さて。

なお、アカウントへの適格情報付与については、モザイク作成者の秘密鍵があればどのアカウントに対しても勝手に付与できるようです。
わざわざ連署求めてたら運用上厳しいし、特に意味がないからでしょうか。

さて。

コードではKYCを行ったという意味で、RestrictionKeyにKYCを設定しているようです。
要するにKYCを行ったアカウント同士で

// carolのアカウントを作成する。
var carol = await Account.createFromPrivateKey(
  await PrivateKey.create(carol1PrivateKey), NetworkTypeEnum.testnet);

// bobのアカウントを作成する。
var bob = await Account.createFromPrivateKey(
  await PrivateKey.create(bobPrivateKey), NetworkTypeEnum.testnet);

print("carol address: ${carol.address}");

// 先ほど作成したモザイクID。
var useMosaicId = MosaicId('610B9D7BB91FEBA7');
print('useMosaicId: ${useMosaicId.value}');

// carolXに適用。
var carolXMosaicAddrResTx = MosaicAddressRestrictionTxInfo.create(
  carol.publicAccount,
  useMosaicId,
  RestrictionKey.createFromKeyName('KYC'),
  RestrictionValue('FFFFFFFFFFFFFFFF'),       // 以前の制限状態。制限なし=MAX=最大値?
  RestrictionValue(1),                        // 新制限値
  carol.address,                             // 対象者アドレス。
);

// bobに適用。
var bobMosaicAddrResTx = MosaicAddressRestrictionTxInfo.create(
  carol.publicAccount,
  useMosaicId,
  RestrictionKey.createFromKeyName('KYC'),
  RestrictionValue('FFFFFFFFFFFFFFFF'),       // 以前の制限状態。制限なし=MAX=最大値?
  RestrictionValue(1),                        // 新制限値
  bob.address,                                // 対象者アドレス。
);

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carol, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

var sendItem = [
  carolXMosaicAddrResTx,
  bobMosaicAddrResTx,
];

print('carol -> bobの順でトランザクションの送信を行う。');

for(var element in sendItem){
  await txSender.sendTransaction(transSetting, element);
}

そんなわけで実行します。

そんなわけで2Txともに承認され、carol、bobのアカウントが適格情報を保有したんだと思います。たぶん。

※なんか2連続でTx飛ばしたら、2回目でNotFoundを吐かれてやり直す羽目になりました。。。


制限状態確認

制限状態を確認できます。REST APIを普通に叩くようです。

// 問い合わせ先を取得する。
var networkHostInfo = await TransactionSender.getNetworkHost();

var resMosaicHttp = await RestrictionMosaicRoutesHttp(networkHostInfo.networkHost);

var criteria = SearchMosaicRestrictionsCriteria();
criteria.mosaicId = MosaicId('610B9D7BB91FEBA7');          // 先ほど作成したモザイクID。

var resultPage = await resMosaicHttp.search(criteria);

print(resultPage.baseMap);

さて。

こんな感じで取得できました。

{
	data: [
		{
			mosaicRestrictionEntry: {
				version: 1,
				 compositeHash: 519D2E815C8CED736EE44FFEBD0923CE79A8C6A272422264FD64685E8F8C109A,
				 entryType: 1,
				 mosaicId: 610B9D7BB91FEBA7,
				 restrictions: [
					{
						key: 8637195520858854017,
						 restriction: {
							referenceMosaicId: 0000000000000000,
							 restrictionValue: 1,
							 restrictionType: 1
						}
					}
				]
			},
			 id: 6574176000C38A269940BB86
		},
		 {
			mosaicRestrictionEntry: {
				version: 1,
				 compositeHash: EB099B1625E671C9332F5FC1DB734CC1B4B0B2A086B85991029EDA985FE58323,
				 entryType: 0,
				 mosaicId: 610B9D7BB91FEBA7,
				 targetAddress: 9812AC7F5DC96504B2A4909D29BF94A50B067C594BFFF485,
				 restrictions: [
					{
						key: 8637195520858854017,
						 value: 1
					}
				]
			},
			 id: 657417F500C38A269940BCF8
		},
		 {
			mosaicRestrictionEntry: {
				version: 1,
				 compositeHash: 78CC30B4D962376AAA6CEF19F159C8A2DE0722A53B3B367F38ECD250734FEB38,
				 entryType: 0,
				 mosaicId: 610B9D7BB91FEBA7,
				 targetAddress: 9873F010CF581FAC0AAF94BC24C75F255D0FB380BD160147,
				 restrictions: [
					{
						key: 8637195520858854017,
						 value: 1
					}
				]
			},
			 id: 6574180F00C38A269940BD8F
		}
	],
	 pagination: {
		pageNumber: 1,
		 pageSize: 20
	}
}

うーん、よくわからないですね。。

内容としてですが、mosaicRestrictionEntryが3件入っています。
これらは内部のデータの違いで何を指しているかが違います。

構造の違いで追ってもいいのですが、(おそらく)正しいやり方であれば、mosaicRestrictionEntryの子にあるentryTypeで何を示しているか判別することができるようです。

このentryTypeの定義は以下の通りです。

自作のコードから引っ張ってきたのでちょっとあれですが、0か1のみで構成されています。

0の場合はMosaicAddressRestriction。アカウントへの制限で、適格情報を示します。ほら、targetAddressとか書いてあるしね。

1についてはMosaicGlobalRestriction。グローバル制限機能付きモザイクの定義を示します。

なお、制限の種類については以上のようです。


送信確認

実際にモザイクを送信するようです。

成功と失敗でコードを分けました。

成功パターン(carolからbobに送信)

print("送信確認(成功)");
print("※carolからbobに送信");

// carolのアカウントを作成する。
var carol = await Account.createFromPrivateKey(
  await PrivateKey.create(carol1PrivateKey), NetworkTypeEnum.testnet);

// bobのアカウントを作成する。
var bob = await Account.createFromPrivateKey(
  await PrivateKey.create(bobPrivateKey), NetworkTypeEnum.testnet);

var tx = TransferTxInfo.create(
  carol.publicAccount,
  bob.address, 
  [Mosaic(amount: Amount(1), id: MosaicId('610B9D7BB91FEBA7'))]);

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carol, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

await txSender.sendTransaction(transSetting, tx);

失敗パターン(carolからdave(誰?)に送信)

print("送信確認(失敗)");
print("※carolからdaveに送信");

// carolのアカウントを作成する。
var carol = await Account.createFromPrivateKey(
  await PrivateKey.create(carol1PrivateKey), NetworkTypeEnum.testnet);

// daveのアカウントを作成する。
var dave = await Account.createFromPrivateKey(
  await PrivateKey.create(davePrivateKey), NetworkTypeEnum.testnet);

var tx = TransferTxInfo.create(
  carol.publicAccount,
  dave.address, 
  [Mosaic(amount: Amount(1), id: MosaicId('610B9D7BB91FEBA7'))]);

// Transaction設定を行う。
var transSetting = TransactionSetting(
  signer: carol, 
  generationHash: generationHashSeed, 
  networkType: NetworkTypeEnum.testnet, 
  deadline: Deadline.create(epochAdjustment),
  fee:FeeMultiplier(100) );     // 手数料は100%。          

var txSender = TransactionSender();

await txSender.sendTransaction(transSetting, tx);

やっていきます。

成功パターン。

こんな感じで、bobに送られました。成功ですね。

次に失敗パターン。

payloadに間違いがなければ、Transactionoの結果を取得した際にエラーコードの文字列を取得することができます。

Failure_RestrictionMosaic_Account_Unauthorizedということで、「モザイク制限のアカウントが認証されていませんので失敗しました」という感じでしょうか。

KYCなされていないdaveには失敗、ということが確認できましたね。


11.3 現場で使えるヒント

割愛します。もうここに書いてあることだけで、なんらかのシステムができそうですね。社内での評価ポイントみたいなやつとか。


感想編

なかなか強力な部分かなぁと思いました。

オープンなブロックチェーンにおいて、取引を一定の範囲内に狭めることができるという感じでしょうか。
WANの中にLANを構築するような(なにいってんだ?)ことが可能なイメージがわきました。ある一定範囲内(例えば組織内部のような)でのトークンのやり取りを、外部からも検証できる。ただし、操作できるのは内部だけ、的な。うーむ、難しい。

現場で使えるヒントのモザイクロックによって、ゲームのトロフィー的なこともできそうですね。

以上です。お疲れさまでした。

投稿者 和泉