NEM2カタパルトで双方向ペイメントチャネルするには その３ ペイロードの送信と罰則

# はじめに

前回の記事

https://qiita.com/planethouki/items/53415a14b34bebd450fc

前回は、トランザクションを作ることにのみ専念した。

トランザクションを作り、送信し、連署し、承認を確認した。

しかし、まだ検証していないものが２つある。

## 署名済みトランザクションを相手に渡す

ペイメントチャネルでは、署名済みトランザクションを送信せずに相手に渡す必要がある。

そこで今回は、シリアライズされたトランザクションを送信し、連署し、承認されることを確認する。

## 裏切りの代償

前回の記事では、ペイメントチャネルによる取引がスムーズにいった場合のみを対象とした。

そこで今回は、裏切りのケースを想定し、古いコミットメントトランザクションを送信し、もう一方が罰を発動できるかを確認する。

# 作ってみる

ペイロードを送信するサンプル。その後、連署しているのだが、これは通常の流れ。

```javascript
function sendAndCosignAggregateTransaction(payloads, address1, account2, addressG) {
    return new Promise((resolve, reject) => {
        const calcurateTransactionHash = function(payload) {
            const hashInputPayload =
                payload.substr(4*2,32*2) +
                payload.substr((4+64)*2,32*2) +
                payload.substr((4+64+32)*2);
            const hasher = sha3_256.create();
            return hasher.update(Buffer.from(hashInputPayload, 'hex')).hex().toUpperCase();
        }
        const cosignAggregateBondedTransaction = function(transaction, account) {
            const cosignatureTransaction = CosignatureTransaction.create(transaction);
            const signedTransaction = account.signCosignatureTransaction(cosignatureTransaction);
            return signedTransaction;
        }
        const payload = payloads[0];
        const hash = calcurateTransactionHash(payload);
        const lockFundsTxPayload = payloads[1];
        const lockFundsTxHash = calcurateTransactionHash(lockFundsTxPayload);
        const listener = new Listener(ENDPOINT);
        const transactionHttp = new TransactionHttp(ENDPOINT);
        listener.open().then(() => {
            return new Promise((resolveReq, rejectReq) => {
                console.log(lockFundsTxHash);
                request({
                    url: `${ENDPOINT}/transaction`,
                    method: 'PUT',
                    headers: {
                        'Content-Type':'application/json'
                    },
                    json: {"payload": lockFundsTxPayload}
                }, (error, response, body) => {
                    console.log(body);
                    error ? rejectReq(error) : resolveReq();
                });
            });
        }).then(() => {
            listener.confirmed(addressG).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === lockFundsTxHash),
                flatMap(ignored => rx.from(new Promise((resolveReq, rejectReq) => {
                    console.log(hash);
                    request({
                        url: `${ENDPOINT}/transaction/partial`,
                        method: 'PUT',
                        headers: {
                            'Content-Type':'application/json'
                        },
                        json: {payload}
                    }, (error, response, body) => {
                        error ? rejectReq(error) : resolveReq(body);
                    });
                })))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.aggregateBondedAdded(account2.address).pipe(
                filter((_) => !_.signedByAccount(account2.publicAccount)),
                map(transaction => cosignAggregateBondedTransaction(transaction, account2)),
                flatMap(cosignatureSignedTransaction => transactionHttp.announceAggregateBondedCosignature(cosignatureSignedTransaction))
            ).subscribe(
                x => console.log(x),
                err => console.error(err)
            );
            listener.confirmed(address1).pipe(
                filter((transaction) => transaction.transactionInfo !== undefined
                && transaction.transactionInfo.hash === hash)
            ).subscribe(
                ignore => {
                    listener.close();
                    resolve();
                },
                err => console.error(err)
            );
        }).catch((error) => {
            console.error(error);
        });
    });
}
```

裏切りのパターンは、普通のSecret Proofなので割愛する。

# 送ってみる

ソースはこちら

https://github.com/planethouki/bidirectional-nem2-payment-channel-sample/blob/master/paymentch9c.js

順番に以下の処理を行う。

* マルチシグ、Alice、Bobの秘密鍵を生成する
* AliceとBobにXEMを送る
* マルチシグ用アカウントをマルチシグ化する
* 残高を表示する
* Opening Transactionのペイロードを作成する
* Commitment Transaction 1のペイロードを作成する
* Opening Transactionを送信する
* 残高を表示する
* Commitment Transaction 2のペイロードを作成する
* BobがCommitment Transaction 1を送信する
* 残高を表示する
* Aliceが裏切りの代償を発動する
* 残高を表示する

```
$ node paymentch9c.js 
=== send XEM first ===
EB4B38E0166CC7F14E0148B0B1CA9FEC0D83EBC89B2D8DB009A6465AE84F46D3
connection open
TransactionAnnounceResponse {
  message: 'packet 9 was pushed to the network via /transaction' }
=== create multisig account ===
C97C074389684061FD9A5876CB01E5343F5E84FBF17DB7B9B3F0660D5229E520
connection open
TransactionAnnounceResponse {
  message: 'packet 9 was pushed to the network via /transaction' }
=== show balance [Multisig, Alice, Bob] ===
[ 0, 5000000, 5000000 ]
=== create opning transaction payload ===
=== create commitment 1 transaction payload ===
=== send opening transaction ===
connection open
9254669288957604223ED99703D8A7759CAD4E73E1C54FBF2CBDDC481E7C186F
{ message: 'packet 9 was pushed to the network via /transaction' }
B0CAA690C5B19F371CDDB1FEF1E9E3DC3BB03C7E3018AB1AF0755857D333EC6A
{ message:
   'packet 500 was pushed to the network via /transaction/partial' }
TransactionAnnounceResponse {
  message:
   'packet 501 was pushed to the network via /transaction/cosignature' }
=== show balance [Multisig, Alice, Bob] ===
[ 10000000, 0, 0 ]
=== create commitment 2 transaction payload ===
=== cheat (send old commitment transaction) ===
connection open
9D3CC19B769B5F0282F297CDE0545757929E4D75B1F28BAAE964F171FE7B8524
{ message: 'packet 9 was pushed to the network via /transaction' }
E1880F5B30D430F3A28B18CDCB88623783A2BBCD22AA3CF30BBC32208C18914A
{ message:
   'packet 500 was pushed to the network via /transaction/partial' }
TransactionAnnounceResponse {
  message:
   'packet 501 was pushed to the network via /transaction/cosignature' }
=== show balance [Multisig, Alice, Bob] ===
[ 0, 4000000, 0 ]
=== penalty (collect all XEM) ===
D9DEDBB21B4883EB37F7688AC940EAC5FD4E201F0824E857E50AB4D5F07D5484
connection open
TransactionAnnounceResponse {
  message: 'packet 9 was pushed to the network via /transaction' }
=== show balance [Multisig, Alice, Bob] ===
[ 0, 10000000, 0 ]
```

## Opening Transaction

https://gist.github.com/planethouki/949d0280cd61870431f75470ea6798ac#file-paymentch9c-openingtx-json

## Commitment Transaction 1 (Cheat)

https://gist.github.com/planethouki/949d0280cd61870431f75470ea6798ac#file-paymentch9c-commitmenttx1-json

## Secret Proof Transaction (Penalty)

https://gist.github.com/planethouki/949d0280cd61870431f75470ea6798ac#file-paymentch9c-penaltytx-json

# まとめ

作ることができる

# 関連

NEM2カタパルトで双方向ペイメントチャネルするには その１ 概要
https://qiita.com/planethouki/items/c22a21836d913418de82

NEM2カタパルトで双方向ペイメントチャネルするには その２ トランザクションは作れるのか
https://qiita.com/planethouki/items/53415a14b34bebd450fc

NEM2カタパルトで双方向ペイメントチャネルするには その３ ペイロードの送信と罰則
https://qiita.com/planethouki/items/945239fe04d2af1fb203