公式ドキュメント
Writing your first application
Creating an escrow with aggregate bonded transaction
Signing announced aggregate bonded transaction
概要
モザイクの送金手数料を肩代わり
Aggregate Transactionは、複数のトランザクションをまとめることができるトランザクションです。これを使って、モザイクを送る際に発生するXEM手数料を、誰かが肩代わりできるそうです。
ですが、MIJIN_TESTでのCatapult Serverは、手数料が無料のため、これが検証できません。
「無いものを誰かが肩代わりする」という点に着目し、別の方法を試してみようと思います。
無いXEMを送る
AliceとCarolとBobが登場人物です。
Aliceは十分な量のXEMを持っており、Carolは10XEMもっています。Bobは、受取人なので、残高はわからなくても大丈夫。
ここで、CarolがBobに100XEM送ることを考える。
すると当然、残高不足で送れないでしょう。
ここで、Aliceがそれを肩代わりします。AliceはAggregate Transactionを作成し、Carolに100XEMを送り、CarolからBobへ100XEM送るようにします。
CarolはそれにCosignすればよいだけです。Bobは何もしなくてよいです。
ここで気になるのが、Aggregate Transactionの内部トランザクションは、順番があるのかどうか。
先にCarolにXEMを送金すれば成り立つこの取引、もし逆だった場合は、当然成り立たないと想定されます。
環境
- ubuntu 18.04
- jq 1.5.1
- nodejs 8.11.2
- nem2-sdk 0.9.3
- nem2-cli
- tech-bureau/catapult-service-bootstrap
使用するアカウント
Alice:
private: 7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4
public: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
address: SBWEUWON6IBHCW5IC4EI6V6SMTVJGCJWGLF57UGK
Bob:
private: 31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E
public: 3390BF02D2BB59C8722297FF998CE89183D0906E469873284C091A5CDC22FD57
address: SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC
Carol:
private: B332E3CA7B31D0BC663232B66D7C282BC2FE1DC0C01BB0159586A2CBEADD6B2A
public: 543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174
address: SBDG7U6NY7MTNOXKVK2JFZDLK2KSCRFPXDZ7IC3Q
コード
ツール類
残高照会
Toggle
const nem2Sdk = require("nem2-sdk");
const Address = nem2Sdk.Address,
Deadline = nem2Sdk.Deadline,
Account = nem2Sdk.Account,
UInt64 = nem2Sdk.UInt64,
NetworkType = nem2Sdk.NetworkType,
PlainMessage = nem2Sdk.PlainMessage,
TransferTransaction = nem2Sdk.TransferTransaction,
Mosaic = nem2Sdk.Mosaic,
MosaicId = nem2Sdk.MosaicId,
TransactionHttp = nem2Sdk.TransactionHttp,
AccountHttp = nem2Sdk.AccountHttp,
MosaicHttp = nem2Sdk.MosaicHttp,
NamespaceHttp = nem2Sdk.NamespaceHttp,
MosaicService = nem2Sdk.MosaicService,
XEM = nem2Sdk.XEM,
AggregateTransaction = nem2Sdk.AggregateTransaction,
PublicAccount = nem2Sdk.PublicAccount,
LockFundsTransaction = nem2Sdk.LockFundsTransaction,
Listener = nem2Sdk.Listener,
CosignatureTransaction = nem2Sdk.CosignatureTransaction,
SecretLockTransaction = nem2Sdk.SecretLockTransaction,
SecretProofTransaction = nem2Sdk.SecretProofTransaction,
HashType = nem2Sdk.HashType;
const alicePrivateKeyPublic = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const aliceAccountPublic = Account.createFromPrivateKey(alicePrivateKeyPublic, NetworkType.MIJIN_TEST);
const bobPrivateKeyPublic = '31B96EEB0C7FD6F8FB6B4ED09A9EB142A42B194AFBEB9EB52F0B79889F22326E';
const bobAccountPublic = Account.createFromPrivateKey(bobPrivateKeyPublic, NetworkType.MIJIN_TEST);
const carolPrivateKeyPublic = 'B332E3CA7B31D0BC663232B66D7C282BC2FE1DC0C01BB0159586A2CBEADD6B2A';
const carolAccountPublic = Account.createFromPrivateKey(carolPrivateKeyPublic, NetworkType.MIJIN_TEST);
const urlPublic = 'http://localhost:3000';
const transactionHttpPublic = new TransactionHttp(urlPublic);
const accountHttpPublic = new AccountHttp(urlPublic);
const mosaicHttpPublic = new MosaicHttp(urlPublic);
const namespaceHttpPublic = new NamespaceHttp(urlPublic);
const mosaicServicePublic = new MosaicService(accountHttpPublic, mosaicHttpPublic, namespaceHttpPublic);
const mosaicsAmountViewFromAddress = (logPrefix, mosaicService, address) => {
mosaicService.mosaicsAmountViewFromAddress(address)
.flatMap((_) => _)
.subscribe(
mosaic => console.log(logPrefix, mosaic.relativeAmount(), mosaic.fullName()),
err => console.error(err)
);
};
const timestamp = new Date().getTime();
mosaicsAmountViewFromAddress('[' + timestamp + '] Alice(Public) have', mosaicServicePublic, aliceAccountPublic.address);
mosaicsAmountViewFromAddress('[' + timestamp + '] Bob(Public) have', mosaicServicePublic, bobAccountPublic.address);
mosaicsAmountViewFromAddress('[' + timestamp + '] Carol(Public) have', mosaicServicePublic, carolAccountPublic.address);
aggregate transactionの作成と送信
抜粋。AliceからCarolが先。
const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[
aliceTocarolTx.toAggregate(aliceAccount.publicAccount),
carolToBobTx.toAggregate(carolPublicAccount),
],
NetworkType.MIJIN_TEST);
Toggle
const nem2Sdk = require("nem2-sdk");
const Address = nem2Sdk.Address,
Deadline = nem2Sdk.Deadline,
Account = nem2Sdk.Account,
UInt64 = nem2Sdk.UInt64,
NetworkType = nem2Sdk.NetworkType,
PlainMessage = nem2Sdk.PlainMessage,
TransferTransaction = nem2Sdk.TransferTransaction,
Mosaic = nem2Sdk.Mosaic,
MosaicId = nem2Sdk.MosaicId,
TransactionHttp = nem2Sdk.TransactionHttp,
XEM = nem2Sdk.XEM,
AggregateTransaction = nem2Sdk.AggregateTransaction,
PublicAccount = nem2Sdk.PublicAccount,
LockFundsTransaction = nem2Sdk.LockFundsTransaction,
Listener = nem2Sdk.Listener;
const alicePrivateKey = '7808B5B53ECF24E40BE17B8EC3D0EB5F7C3F3D938E0D95A415F855AD4C27B2A4';
const bobRawAddress = 'SB2Y5ND4FDLBIO5KHXTKRWODDG2QHIN73DTYT2PC';
const carolPublicKey = '543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174';
const aliceAccount = Account.createFromPrivateKey(alicePrivateKey, NetworkType.MIJIN_TEST);
const bobAddress = Address.createFromRawAddress(bobRawAddress);
const carolPublicAccount = PublicAccount.createFromPublicKey(carolPublicKey, NetworkType.MIJIN_TEST);
const aliceTocarolTx = TransferTransaction.create(
Deadline.create(),
carolPublicAccount.address,
[new Mosaic( new MosaicId('nem:xem'), UInt64.fromUint(100000000))],
PlainMessage.create('send 100 nem:xem to carol'),
NetworkType.MIJIN_TEST,
);
const carolToBobTx = TransferTransaction.create(
Deadline.create(),
bobAddress,
[new Mosaic( new MosaicId('nem:xem'), UInt64.fromUint(100000000))],
PlainMessage.create('send 100 nem:xem to bob'),
NetworkType.MIJIN_TEST,
);
const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[
aliceTocarolTx.toAggregate(aliceAccount.publicAccount),
carolToBobTx.toAggregate(carolPublicAccount),
],
NetworkType.MIJIN_TEST);
const signedTransaction = aliceAccount.sign(aggregateTransaction);
// Creating the lock funds transaction
const lockFundsTransaction = LockFundsTransaction.create(
Deadline.create(),
new Mosaic( new MosaicId('nem:xem'), UInt64.fromUint(10000000)),
UInt64.fromUint(480),
signedTransaction,
NetworkType.MIJIN_TEST);
const lockFundsTransactionSigned = aliceAccount.sign(lockFundsTransaction);
const transactionHttp = new TransactionHttp('http://localhost:3000');
transactionHttp
.announce(lockFundsTransactionSigned)
.subscribe(x => console.log(x), err => console.error(err));
setTimeout(() =>
transactionHttp
.announceAggregateBonded(signedTransaction)
.subscribe(x => console.log(x), err => console.error(err))
,30000);
console.log('lockFundsTransactionSigned.hash : ' + lockFundsTransactionSigned.hash);
console.log('lockFundsTransactionSigned.signer: ' + lockFundsTransactionSigned.signer);
console.log('aggregateTransactionSigned.hash : ' + signedTransaction.hash);
console.log('aggregateTransactionSigned.signer: ' + signedTransaction.signer);
cosignature transactionの作成と送信
Toggle
const nem2Sdk = require("nem2-sdk");
const Address = nem2Sdk.Address,
Deadline = nem2Sdk.Deadline,
Account = nem2Sdk.Account,
UInt64 = nem2Sdk.UInt64,
NetworkType = nem2Sdk.NetworkType,
PlainMessage = nem2Sdk.PlainMessage,
TransferTransaction = nem2Sdk.TransferTransaction,
Mosaic = nem2Sdk.Mosaic,
MosaicId = nem2Sdk.MosaicId,
TransactionHttp = nem2Sdk.TransactionHttp,
XEM = nem2Sdk.XEM,
AggregateTransaction = nem2Sdk.AggregateTransaction,
PublicAccount = nem2Sdk.PublicAccount,
LockFundsTransaction = nem2Sdk.LockFundsTransaction,
Listener = nem2Sdk.Listener,
CosignatureTransaction = nem2Sdk.CosignatureTransaction,
AccountHttp = nem2Sdk.AccountHttp;
const cosignAggregateBondedTransaction = (transaction, account) => {
const cosignatureTransaction = CosignatureTransaction.create(transaction);
const signedTransaction = account.signCosignatureTransaction(cosignatureTransaction);
console.log('cosignatureTransaction.signer: ' + signedTransaction.signer);
return signedTransaction;
};
const privateKey = 'B332E3CA7B31D0BC663232B66D7C282BC2FE1DC0C01BB0159586A2CBEADD6B2A';
const account = Account.createFromPrivateKey(privateKey, NetworkType.MIJIN_TEST);
const accountHttp = new AccountHttp('http://localhost:3000');
const transactionHttp = new TransactionHttp('http://localhost:3000');
accountHttp.aggregateBondedTransactions(account.publicAccount)
.flatMap((_) => _)
.filter((_) => !_.signedByAccount(account.publicAccount))
.map(transaction => cosignAggregateBondedTransaction(transaction, account))
.flatMap(cosignatureSignedTransaction => transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction))
.subscribe(announcedTransaction => console.log(announcedTransaction),
err => console.error(err));
aggregate transactionの作成と送信(逆順)
この部分だけ変更する。
const aggregateTransaction = AggregateTransaction.createBonded(Deadline.create(),
[
carolToBobTx.toAggregate(carolPublicAccount),
aliceTocarolTx.toAggregate(aliceAccount.publicAccount),
],
NetworkType.MIJIN_TEST);
結果
事前の残高照会
# node disp.js
[1528289345957] Alice(Public) have 409089685 nem:xem
[1528289345957] Bob(Public) have 409090253 nem:xem
[1528289345957] Carol(Public) have 10 nem:xem
Aggregate Transactionの作成と送信
まずは、Aliceによって、Aggregate Transactionを送信します。(LockFunds Transactionも忘れずに)
# node aggregate.js
lockFundsTransactionSigned.hash : 3550B37A682E3AE1BD60106E2BBFFA62EC777220ACE50E05252C73CC34AC5A7A
lockFundsTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
aggregateTransactionSigned.hash : F0F88FE2455A3B37D37D85617DB8C812B384221C9DC05C4C8231B1E66F763C40
aggregateTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
TransactionAnnounceResponse {
message: 'packet 500 was pushed to the network via /transaction/partial' }
Carol Cosignature Monitoring
Aggregate TransactionがAliceにより送信されたら、Carolがこれを検知できるようになります。
Carolの公開鍵を使って、RESTに問い合わせると、結果が返ってきます。
Toggle
# curl http://localhost:3000/account/543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174/transactions/partial | jq .
[
{
"meta": {
"height": [
0,
0
],
"hash": "F0F88FE2455A3B37D37D85617DB8C812B384221C9DC05C4C8231B1E66F763C40",
"merkleComponentHash": "0000000000000000000000000000000000000000000000000000000000000000",
"index": 0,
"id": "5B17DAEA80FDB30001360A3A"
},
"transaction": {
"signature": "511DB1C968DA2C34022A51E7EFC386E8BCBFB8F7DF65F14323A042EEB438F2A0B2924B3C326BDEA14F040FD9FB582F44D4087A3042AE6BB361C497CA95E4E801",
"signer": "5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C",
"version": 36866,
"type": 16961,
"fee": [
0,
0
],
"deadline": [
108279139,
16
],
"cosignatures": [],
"transactions": [
{
"meta": {
"height": [
0,
0
],
"aggregateHash": "F0F88FE2455A3B37D37D85617DB8C812B384221C9DC05C4C8231B1E66F763C40",
"aggregateId": "5B17DAEA80FDB30001360A3A",
"index": 0,
"id": "5B17DAEA80FDB30001360A3B"
},
"transaction": {
"signer": "5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C",
"version": 36867,
"type": 16724,
"recipient": "90466FD3CDC7D936BAEAAAB492E46B56952144AFB8F3F40B70",
"message": {
"type": 0,
"payload": "73656E6420313030206E656D3A78656D20746F206361726F6C"
},
"mosaics": [
{
"id": [
3646934825,
3576016193
],
"amount": [
100000000,
0
]
}
]
}
},
{
"meta": {
"height": [
0,
0
],
"aggregateHash": "F0F88FE2455A3B37D37D85617DB8C812B384221C9DC05C4C8231B1E66F763C40",
"aggregateId": "5B17DAEA80FDB30001360A3A",
"index": 1,
"id": "5B17DAEA80FDB30001360A3C"
},
"transaction": {
"signer": "543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174",
"version": 36867,
"type": 16724,
"recipient": "90758EB47C28D6143BAA3DE6A8D9C319B503A1BFD8E789E9E2",
"message": {
"type": 0,
"payload": "73656E6420313030206E656D3A78656D20746F20626F62"
},
"mosaics": [
{
"id": [
3646934825,
3576016193
],
"amount": [
100000000,
0
]
}
]
}
}
]
}
}
]
Carol Cosignature
Carolによって、Cosignature Transactionを送信します。
# node cosign.js
cosignatureTransaction.signer: 543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174
TransactionAnnounceResponse {
message: 'packet 501 was pushed to the network via /transaction/cosignature' }
事後の残高照会
# node disp.js
[1528289610569] Alice(Public) have 409089585 nem:xem
[1528289610569] Bob(Public) have 409090353 nem:xem
[1528289610569] Carol(Public) have 10 nem:xem
結果まとめ
無事肩代わりできた。Carolの残高は変化せず、Aliceが100XE減り、Bobは100XEM増えた。
Balance | |
---|---|
Alice | 409089685 → 409089585 nem:xem |
Carol | 10 → 10 nem:xem |
Bob | 409090253 → 409090353 nem:xem |
内部トランザクションの順序を逆にした結果
事前の残高照会
# node disp.js
[1528290768587] Alice(Public) have 409089385 nem:xem
[1528290768587] Bob(Public) have 409090553 nem:xem
[1528290768587] Carol(Public) have 10 nem:xem
Status Monitoring Start
たぶん失敗すると思うので、Carolのアカウントでステータスのモニタリングを開始しておきます。
# nem2-cli monitor status --profile carol
Monitoring SBDG7U-6NY7MT-NOXKVK-2JFZDL-K2KSCR-FPXDZ7-IC3Q using http://localhost:3000
connection open
Aggregate Transactionの作成と送信
まずは、Aliceによって、Aggregate Transactionを送信します。(LockFunds Transactionも忘れずに)
# node aggregate_inverse.js
lockFundsTransactionSigned.hash : F05C33FA72F490EE020469F8F463859F8B4FE5AC1B92F1B3BFD604074BEC1CE2
lockFundsTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
aggregateTransactionSigned.hash : C3E1147124B34D7EF52CB3D5F1E26EA4E4546172D7F0D2504EA5EDEB770D41AC
aggregateTransactionSigned.signer: 5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
TransactionAnnounceResponse {
message: 'packet 500 was pushed to the network via /transaction/partial' }
Carol Cosignature Monitoring
Carolの公開鍵を使って、RESTに問い合わせると、結果が返ってきます。まだ失敗していません。
Toggle
[
{
"meta": {
"height": [
0,
0
],
"hash": "C3E1147124B34D7EF52CB3D5F1E26EA4E4546172D7F0D2504EA5EDEB770D41AC",
"merkleComponentHash": "0000000000000000000000000000000000000000000000000000000000000000",
"index": 0,
"id": "5B17E07E80FDB30001360ABB"
},
"transaction": {
"signature": "D627538CB0EE6C18162168CD9262B2E27CCEB634AD64287F6C094D439913CA5C8548B4F60C5B83AC042BF2C9AC6C9E08784D7C04EBE4C79A69C6360D5EAE8508",
"signer": "5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C",
"version": 36866,
"type": 16961,
"fee": [
0,
0
],
"deadline": [
109707788,
16
],
"cosignatures": [],
"transactions": [
{
"meta": {
"height": [
0,
0
],
"aggregateHash": "C3E1147124B34D7EF52CB3D5F1E26EA4E4546172D7F0D2504EA5EDEB770D41AC",
"aggregateId": "5B17E07E80FDB30001360ABB",
"index": 0,
"id": "5B17E07E80FDB30001360ABC"
},
"transaction": {
"signer": "543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174",
"version": 36867,
"type": 16724,
"recipient": "90758EB47C28D6143BAA3DE6A8D9C319B503A1BFD8E789E9E2",
"message": {
"type": 0,
"payload": "73656E6420313030206E656D3A78656D20746F20626F62"
},
"mosaics": [
{
"id": [
3646934825,
3576016193
],
"amount": [
100000000,
0
]
}
]
}
},
{
"meta": {
"height": [
0,
0
],
"aggregateHash": "C3E1147124B34D7EF52CB3D5F1E26EA4E4546172D7F0D2504EA5EDEB770D41AC",
"aggregateId": "5B17E07E80FDB30001360ABB",
"index": 1,
"id": "5B17E07E80FDB30001360ABD"
},
"transaction": {
"signer": "5D9513282B65A12A1B68DCB67DB64245721F7AE7822BE441FE813173803C512C",
"version": 36867,
"type": 16724,
"recipient": "90466FD3CDC7D936BAEAAAB492E46B56952144AFB8F3F40B70",
"message": {
"type": 0,
"payload": "73656E6420313030206E656D3A78656D20746F206361726F6C"
},
"mosaics": [
{
"id": [
3646934825,
3576016193
],
"amount": [
100000000,
0
]
}
]
}
}
]
}
}
]
Carol Cosignature
Carolによって、Cosignature Transactionを送信します。
# node cosign.js
cosignatureTransaction.hash : undefined
cosignatureTransaction.signer: 543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD682D77251E2C4EC174
TransactionAnnounceResponse {
message: 'packet 501 was pushed to the network via /transaction/cosignature' }
残高不足によりトランザクション失敗
Carolのモニタリング画面に、エラーが表示されました。
# nem2-cli monitor status --profile carol
Monitoring SBDG7U-6NY7MT-NOXKVK-2JFZDL-K2KSCR-FPXDZ7-IC3Q using http://localhost:3000
connection open
Hash: C3E1147124B34D7EF52CB3D5F1E26EA4E4546172D7F0D2504EA5EDEB770D41AC
Error code: Failure_Core_Insufficient_Balance
Deadline: 2018-06-06 15:13:04.524
Failure_Core_Insufficient_Balance
、つまり残高不足でエラーとなった。
Carol Cosignature Monitoring
もう1度RESTに問い合わせてみたが、結果は返ってこなかった。
再度Cosignatureはできず、lock fundsはもう回収できないと思う。
# curl http://localhost:3000/account/543BB01DFEEA0D9A25ADDE515DACC72F2125A8AAE85EDD
682D77251E2C4EC174/transactions/partial | jq .
[]
事後の残高照会
# node disp.js
[1528291031079] Alice(Public) have 409089375 nem:xem
[1528291031079] Bob(Public) have 409090553 nem:xem
[1528291031079] Carol(Public) have 10 nem:xem
結果のまとめ(逆順)
肩代わりはできなかった。
LockFunds Transactionを作成したAliceが10XEM失った。
Balance | |
---|---|
Alice | 409089385 → 409089375 nem:xem |
Carol | 10 nem:xem |
Bob | 409090553 nem:xem |
まとめ
Aggregate Transactionにより、足りないXEMを肩代わりすることができた。
Aggregate Transactionには実行される順番に注意しなければならない。
メインネットでモザイクの送金手数料を肩代わりするシーンを考えると、Lock Funds Transactionがいるので、肩代わり者(Service Provider的な)がAggregate Transactionを送信し、モザイク送りたい人はCosignするだけになると思う。
それか、ネットワークに送信する前に関係者の署名を全部揃えられるならば、Aggregate Completeとなり、Lock Fundsは不要になるので、それを使えないか検討する。
ネットワークタイプをPublicにして動かしたら手数料が発生するのではないだろうか。