アグリゲートトランザクションでパブリックチェーンの参加者を増やす(手数料代払いの話)


はじめに

パブリックなブロックチェーン(以下、パブリックチェーン)を利用するに当たり、その障壁となる課題の一つに手数料があります。
パブリックチェーンで送金などの処理を行う際は、原則としてその処理を行うアカウント自身が手数料を負担する必要があります。

この手数料は円やドルといった法定通貨ではなく、パブリックチェーン上の基軸通貨(例:NENであればXEM、イーサリアムであればETHなど)で支払う必要があります。
これらの通貨は、誰かから譲ってもらうなり、交換所・取引所を利用するなどなんらかの手段で調達する必要があります。

なぜ手数料があるのか

この手数料はネットワークの秩序維持のために必要となります。
仮に手数料がかからなかった場合、悪意を持った人が闇雲にトランザクションを送り続け、ネットワークを容易にパンクさせ混乱に陥れることができてしまいます。
誰でも参加することができるパブリックチェーンにおいては、手数料を導入することによりスパムトランザクションを抑制し、ネットワークの秩序が保たれるようにしている側面があります。

また、手数料はネットワークの参加者への報酬としても使われており、NEMにおいては、生成されたブロックに含まれているトランザクション手数料がブロックを作成する権利を得た人(ハーベスター)に支払われます。

このように、パブリックチェーンを維持する上で、手数料は必要不可欠なものではありますが、一方でこれによりパブリックチェーンを利用するハードルが高くなっている側面もあります。

その手数料はそもそも自分が負担すべきか

例えば、以下の図の様にゲーム内通貨とアイテムがブロックチェーン上で作られたトークンによって取引されるとします。

この際、この取引を行うAliceとBobはそれぞれゲーム内通貨やアイテムのトークンの他にトランザクション手数料を支払うためにネットワークの基軸通貨(XEMやETHなど)を用意し、負担する必要があります。

しかしながら、この取引はゲーム内のプラットフォーム内で行われてるとすると、それぞれのユーザがネットワークの基軸通貨を用意しないといけないのは、ユーザにとっては負担が大きく、プラットフォームの活性化を阻害する要因にもなります。
AliceとBobはあくまでもゲーム内の通貨とアイテムを交換したいだけです。

手数料代払い

仮に、ゲームプラットフォームを運営してるゲーム会社がこのトランザクション手数料を負担することができれば、AliceとBobはネットワークの基軸通貨を用意しなくともゲーム内の通貨とアイテムを交換することができます。
これにより、AliceとBobはブロックチェーンを使っている事を意識することなく、しかしその裏で信頼の高い取引が行われています。

このように、手数料代払いができると全ての利用者がネットワークの基軸通貨を保有していなくても、ブロックチェーンの恩恵を受ける事ができるようになります。

NEM2(Catapult)における手数料代払い

NEMの次期コアエンジンCatapultには、アグリゲートトランザクションという機能が追加されました。
近々パブリックチェーンでリリースされるNEM2(Catapult)においては、このアグリゲートトランザクションを利用することで手数料の代払いを実現させることができます。

アグリゲートトランザクションとは

アグリゲートトランザクションは複数のトランザクションをひとまとめにして処理する機能です。
先ほどのゲーム内通貨とアイテムの取引の例では、現行のNEMでは、BobがAliceにゲーム内通貨を支払う処理と、AliceがBobにアイテムを渡す処理を別々に行う必要がありました。別々に処理を行った場合、BoBが代金を支払ったのに、Aliceからアイテムが渡ってこず、お金だけが持ち逃げされる可能性があります。

アグリゲートトランザクションを用いると、AliceとBobの署名が揃って始めて、ゲーム内通貨とアイテムの交換が実行されます。これによりお金やアイテムの持ち逃げを回避でき安全な取引を行うことが可能となります。

アグリゲートトランザクションを使った手数料代払い

結論から先に書いてしますと、アグリゲートトランザクションの場合、その手数料は、トランザクションの作成者が負担することになります。

上記の例においては、ゲーム会社がトランザクションの作成者となり、手数料を負担することによって、AliceとBobは、このトランザクションを承認さえすれば、手数料を負担することなく安全に取引をすることができます。

手数料代払いを行うためのコード

以下は、Fushicho3対応のnem2-sdk(0.16.0)で上記の取引を行うためのコードです。

import * as dotenv from 'dotenv';
import { TransactionService, Listener, Account, PublicAccount, Mosaic, UInt64,
 TransferTransaction, Deadline, EmptyMessage, AggregateTransaction,
 HashLockTransaction, NetworkCurrencyMosaic, TransactionHttp,
 NamespaceId } from 'nem2-sdk';
import { filter, mergeMap } from 'rxjs/operators';

dotenv.config();

const transactionService = new TransactionService(process.env.API_ENDPOINT);
const listener = new Listener(process.env.API_ENDPOINT);
const networkType = Number(process.env.NETWORK_TYPE);

const gameCompanyAccount = Account.createFromPrivateKey('GAME_COMPANY_PRIVATE_KEY', networkType);
const alicePubAccount = PublicAccount.createFromPublicKey('ALICE_PUBLIC_KEY', networkType);
const bobPubAccount  = PublicAccount.createFromPublicKey('BOB_PUBLIC_KEY', networkType);

const gameCurrency = new Mosaic(new NamespaceId('game_company.currency'), UInt64.fromUint(100));
const gameItem = new Mosaic(new NamespaceId('game_company.item'), UInt64.fromUint(1));

const tx1 = TransferTransaction.create(
  Deadline.create(),
  bobPubAccount.address,
  [gameItem],
  EmptyMessage,
  networkType,
);

const tx2 = TransferTransaction.create(
  Deadline.create(),
  alicePubAccount.address,
  [gameCurrency],
  EmptyMessage,
  networkType
);

const dummyTx = TransferTransaction.create(
  Deadline.create(),
  gameCompanyAccount.address,
  [],
  EmptyMessage,
  networkType
);

const aggregateTx = AggregateTransaction.createBonded(
  Deadline.create(),
  [
    tx1.toAggregate(alicePubAccount),
    tx2.toAggregate(bobPubAccount),
    dummyTx.toAggregate(gameCompanyAccount.publicAccount),
  ],
  networkType,
  [],
  UInt64.fromUint(200000)
);

const signedTx = gameCompanyAccount.sign(aggregateTx, process.env.GENERATION_HASH);

console.log(`txHash: ${signedTx.hash}`);

const hashLockTx = HashLockTransaction.create(
  Deadline.create(),
  NetworkCurrencyMosaic.createRelative(10),
  UInt64.fromUint(480),
  signedTx,
  networkType,
  UInt64.fromUint(18400)
);

const hashLockTxSigned = gameCompanyAccount.sign(hashLockTx, process.env.GENERATION_HASH);

listener.open().then(() => {
  transactionService.announceHashLockAggregateBonded(hashLockTxSigned, signedTx, listener)
  .subscribe((x) => {
    console.log(x);
    listener.close()
  }, (err) => {
    console.error(err);
    listener.close();
  });
});

ポイント

  • 手数料代払いといいながら、実際にはゲーム会社のアカウントgameCompanyAccountでaliceとbobのゲーム内通貨とアイテムの取引のトランザクションを作っているだけです。
  • とはいいながら、内部トランザクションにgameCompanyAccountが作成するトランザクションが1つでも含まれていないと、最終的にトランザクションが無効になってしまうため、gameCompanyAccount自身に空のトランザクションを送るダミーのトランザクションの入れています。
  • トランザクション手数料はあらかじめ十分な量を積んでおかないと、トランザクションが無効になってしまいます。

まとめ

パブリックチェーンを利用する際のボトルネックとして、ユーザがそのチェーンの基軸通貨で手数料を払わないといけないという課題がありますが、NEMの次期コアエンジンCatapultのアグリゲートトランザクションを利用することによって、トランザクション手数料の代払いを行うことができるようになります。
手数料代払いは、Catapult上で作成されるどんなモザイク(トークン)にも適用することが可能で、比較的容易に手数料代払いの仕組みを実現することが可能です。

これにより、パブリックチェーンを利用する人が必ずしも手数料を負担する必要がなくなり、今までより多くの人がブロックチェーンの恩恵を受けやすくなると思います。