メタデータエントリの更新

存在するメタデータエントリの更新

ユースケース

Metadata トランザクションはブロックチェーンに記録されます。トランザクションがブロックに取り込まれ —十分な承認を得ると— チェーン全体が無効にならない限りは記録を変更することはできません

メタデータエントリを更新するには、次のメタデータトランザクションをアナウンスします。このアクションは変遷を不変のまま保ちながら新しいトランザクションを記録します。しかし、どうやってチェーン全体に問い合わせることなく、アセットに割り当てられた最新のメタデータ値を取得すればよいのでしょうか?Symbol はメタデータエントリに割り当てられた 最新の値 のコピーを 状態 として保持することで、これを可能にしています。

このガイドでは、アカウントに添付された メタデータエントリの更新 する方法を示します。なお、同じアプローチでネームスペースとモザイクのメタデータエントリを更新することができます。

前提条件

方法 #01: SDK を使用する

Bob — アカウントへのメタデータエクセルの割当ガイド からの公証人 — は証明書の有効期限が切れているため、アリスのアカウントの CERT メタデータエントリを削除するように要求されます。

../../_images/metadata-update.png

1. Define a new AccountMetadataTransaction setting Alice’s account as the metadata target. To indicate that the certificate has expired, Bob decides to add the new value 000000 to the metadata entry with key CERT. However, you need to pass an extra parameter that was not necessary when assigning a metadata entry for the first time.

定義により、フォークを解決するために事前に設定された特定の深さまで ブロックチェーンはロールバックされる可能性 があります。状態を元に戻す必要がある場合、メタデータエントリに割り当てられた previousValuenewValue のサイズの違いを示す必要があります。

A) Retrieve the previous metadata value and calculate the difference of size with the newest value. Then, return the AccountMetadataTransaction object.

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with bob private key
const bobPrivateKey =
  '0000000000000000000000000000000000000000000000000000000000000000';
const bobAccount = Account.createFromPrivateKey(bobPrivateKey, networkType);
// replace with alice public key
const alicePublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const alicePublicAccount = PublicAccount.createFromPublicKey(
  alicePublicKey,
  networkType,
);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const metadataHttp = repositoryFactory.createMetadataRepository();

// replace with key and new value
const key = KeyGenerator.generateUInt64Key('CERT');
const newValue = '000000';
const newValueBytes = Convert.utf8ToUint8(newValue);

const searchCriteria = {
  targetAddress: alicePublicAccount.address,
  scopedMetadataKey: key.toString(),
  sourceAddress: bobAccount.address,
};
const accountMetadataTransaction = metadataHttp.search(searchCriteria).pipe(
  mergeMap((metadata) => {
    const currentValueBytes = Convert.utf8ToUint8(
      metadata.data[0].metadataEntry.value,
    );
    return of(
      AccountMetadataTransaction.create(
        Deadline.create(epochAdjustment),
        alicePublicAccount.address,
        key,
        newValueBytes.length - currentValueBytes.length,
        Convert.decodeHex(Convert.xor(currentValueBytes, newValueBytes)),
        networkType,
      ),
    );
  }),
);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with bob private key
const bobPrivateKey =
  '0000000000000000000000000000000000000000000000000000000000000000';
const bobAccount = symbol_sdk_1.Account.createFromPrivateKey(
  bobPrivateKey,
  networkType,
);
// replace with alice public key
const alicePublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const alicePublicAccount = symbol_sdk_1.PublicAccount.createFromPublicKey(
  alicePublicKey,
  networkType,
);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const metadataHttp = repositoryFactory.createMetadataRepository();
// replace with key and new value
const key = symbol_sdk_1.KeyGenerator.generateUInt64Key('CERT');
const newValue = '000000';
const newValueBytes = symbol_sdk_1.Convert.utf8ToUint8(newValue);
const searchCriteria = {
  targetAddress: alicePublicAccount.address,
  scopedMetadataKey: key.toString(),
  sourceAddress: bobAccount.address,
};
const accountMetadataTransaction = metadataHttp.search(searchCriteria).pipe(
  operators_1.mergeMap((metadata) => {
    const currentValueBytes = symbol_sdk_1.Convert.utf8ToUint8(
      metadata.data[0].metadataEntry.value,
    );
    return rxjs_1.of(
      symbol_sdk_1.AccountMetadataTransaction.create(
        symbol_sdk_1.Deadline.create(epochAdjustment),
        alicePublicAccount.address,
        key,
        newValueBytes.length - currentValueBytes.length,
        symbol_sdk_1.Convert.decodeHex(
          symbol_sdk_1.Convert.xor(currentValueBytes, newValueBytes),
        ),
        networkType,
      ),
    );
  }),
);

B) You can achieve the same result with less effort using the MetadataService. Behind the scenes, the Symbol SDK handles the complexity of updating metadata entries.

// replace with network type
const networkType = NetworkType.TEST_NET;
// replace with bob private key
const bobPrivateKey =
  '0000000000000000000000000000000000000000000000000000000000000000';
const bobAccount = Account.createFromPrivateKey(bobPrivateKey, networkType);
// replace with alice public key
const alicePublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const alicePublicAccount = PublicAccount.createFromPublicKey(
  alicePublicKey,
  networkType,
);
// replace with node endpoint
const nodeUrl = 'http://api-01.us-east-1.testnet.symboldev.network:3000';
const metadataHttp = new MetadataHttp(nodeUrl);
const metadataService = new MetadataTransactionService(metadataHttp);

// replace with key and new value
const key = KeyGenerator.generateUInt64Key('CERT');
const newValue = '000000';

const accountMetadataTransaction = metadataService.createAccountMetadataTransaction(
  Deadline.create(epochAdjustment),
  networkType,
  alicePublicAccount.address,
  key,
  newValue,
  bobAccount.publicAccount.address,
  UInt64.fromUint(0),
);

2. To avoid spamming the account with invalid metadata, all metadata is attached only with the consent of the account owner through Aggregate Transactions. Thus, Alice will have to opt-in if she wants the metadata to be updated. Wrap the AccountMetadataTransaction inside an AggregateBondedTransaction and sign the transaction using Bob’s account.

// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash =
  '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedAggregateTransaction = accountMetadataTransaction.pipe(
  mergeMap((transaction) => {
    const aggregateTransaction = AggregateTransaction.createBonded(
      Deadline.create(epochAdjustment),
      [transaction.toAggregate(bobAccount.publicAccount)],
      networkType,
      [],
      UInt64.fromUint(2000000),
    );
    const signedTransaction = bobAccount.sign(
      aggregateTransaction,
      networkGenerationHash,
    );
    return of(signedTransaction);
  }),
);

3. Before sending an aggregate transaction to the network, Bob has to lock 10 symbol.xym. Define a new HashLockTransaction and sign it with Bob’s account, locking the amount of symbol.xym required to announce the aggregate transaction.

interface SignedAggregateHashLock {
  readonly aggregate: SignedTransaction;
  readonly hashLock: SignedTransaction;
}

// replace with symbol.xym id
const networkCurrencyMosaicId = new MosaicId('5E62990DCAC5BE8A');
// replace with network currency divisibility
const networkCurrencyDivisibility = 6;

const signedAggregateHashLock = signedAggregateTransaction.pipe(
  mergeMap((signedAggregateTransaction) => {
    const hashLockTransaction = HashLockTransaction.create(
      Deadline.create(epochAdjustment),
      new Mosaic(
        networkCurrencyMosaicId,
        UInt64.fromUint(10 * Math.pow(10, networkCurrencyDivisibility)),
      ),
      UInt64.fromUint(480),
      signedAggregateTransaction,
      networkType,
      UInt64.fromUint(2000000),
    );
    const signedTransaction = bobAccount.sign(
      hashLockTransaction,
      networkGenerationHash,
    );
    const signedAggregateHashLock: SignedAggregateHashLock = {
      aggregate: signedAggregateTransaction,
      hashLock: signedTransaction,
    };
    console.log(
      'Aggregate Transaction Hash:',
      signedAggregateTransaction.hash + '\n',
    );
    console.log('HashLock Transaction Hash:', signedTransaction.hash + '\n');
    return of(signedAggregateHashLock);
  }),
);

注釈

Alice が次の 480 ブロック中にアグリゲートに署名すると、 Bob はロックされた資産を取り戻します。

4. Announce the HashLockTransaction. Monitor the network until the transaction gets confirmed, and then announce the AggregateTransaction containing the AccountMetadataTransaction.

const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const listener = repositoryFactory.createListener();
const receiptHttp = repositoryFactory.createReceiptRepository();
const transactionHttp = repositoryFactory.createTransactionRepository();
const transactionService = new TransactionService(transactionHttp, receiptHttp);

listener.open().then(() => {
  signedAggregateHashLock
    .pipe(
      mergeMap((signedAggregateHashLock) =>
        transactionService.announceHashLockAggregateBonded(
          signedAggregateHashLock.hashLock,
          signedAggregateHashLock.aggregate,
          listener,
        ),
      ),
    )
    .subscribe(
      () => console.log('Transaction confirmed'),
      (err) => console.log(err),
      () => listener.close(),
    );
});
  1. トランザクションが承認されたら、Alice のプロファイルを使用して、3番目のステップで取得したハッシュに署名します。
symbol-cli transaction cosign --hash <transaction-hash> --profile alice
  1. 次のガイド に従って、Alice のアカウントに割り当てられたメタデータエントリを取得します。