新しいトランザクションをつくりたい
nem Advent Calendar 2021にてSymbol/Nemコアデブのjaguarさんが次の記事の書いていました.
この記事にはSymbolハードフォークの際に作成されたRevaocableのトランザクションの作り方についてかかれていました.作成にはcatbufferとC++の知識を必要とするようだったので気になってはいたものの試していませんでした.しかしやはり興味が抑えきれなかったのでやってみました.
基本的な手順等はjaguarさんの記事に従って行います.また,この記事ではcatbufferで新トランザクションの構造を定義し,javascriptのSDKの作成してトランザクションを生成するところまで実施します.
ノードで新トランザクションを取り込む部分は別の記事にて書きます
今回作るもの
新しいトランザクションを作るとはいっても,この分野に関する記事や知見は殆どなかったのでTransferTransactionのTypeを変更したものを作成することにしました. トランザクション名はTransfervTransaction
とします
構造を定義する
まず,githubからcatbufferをダウンロードしましょう.
このレポジトリにはcatapultのclient,rest,client,そしてcatbufferなどノードに関するものが大体全部入っています.
git clone https://github.com/symbol/symbol.git
ダウンロードが完了したらcatbuffer>schemas>symbolに移動しましょう.
ディレクトリにはSymbol以外にnemも存在するのでnemのトランザクション作成も同じような手順でやれそうな気はします.今回はスルーです.
TransferTransactionのタイプを変えたものを作成するのでTransferに移動しましょう
ディレクトリにはTransferTransaction.catsがあるのでこれをコピーし,TransfervTransaction.catsを作成します.またトランザクション名をTransfervに変えていきます.
import "transaction.cats"
# Test PluginTransaction(copy of transfer)
# Shared content between TransfervTransaction and EmbeddedTransfervTransaction.
- inline struct TransferTransactionBody
+ inline struct TransfervTransactionBody
# recipient address
recipient_address = UnresolvedAddress
# size of attached message
message_size = uint16
# number of attached mosaics
mosaics_count = uint8
# reserved padding to align mosaics on 8-byte boundary
- transfer_transaction_body_reserved_1 = make_reserved(uint8, 0)
+ transferv_transaction_body_reserved_1 = make_reserved(uint8, 0)
# reserved padding to align mosaics on 8-byte boundary
- transfer_transaction_body_reserved_2 = make_reserved(uint32, 0)
+ transferv_transaction_body_reserved_2 = make_reserved(uint32, 0)
# attached mosaics
@sort_key(mosaic_id)
mosaics = array(UnresolvedMosaic, mosaics_count)
# attached message
message = array(uint8, message_size)
# Send mosaics and messages between two accounts.
-struct TransferTransaction
+struct TransfervTransaction
TRANSACTION_VERSION = make_const(uint8, 1)
- TRANSACTION_TYPE = make_const(TransactionType, TRANSFER)
+ TRANSACTION_TYPE = make_const(TransactionType, TRANSFERV)
inline Transaction
- inline TransferTransactionBody
+ inline TransfervTransactionBody
# Embedded version of TransfervTransaction.
-struct EmbeddedTransferTransaction
+struct EmbeddedTransfervTransaction
TRANSACTION_VERSION = make_const(uint8, 1)
- TRANSACTION_TYPE = make_const(TransactionType, TRANSFER)
+ TRANSACTION_TYPE = make_const(TransactionType, TRANSFERV)
inline EmbeddedTransaction
- inline TransferTransactionBody
+ inline TransfervTransactionBody
all_transaction.catsに作成したトランザクションを追加してcatbufferに認識させます
import "account_link/account_key_link.cats"
import "account_link/node_key_link.cats"
import "aggregate/aggregate.cats"
import "coresystem/voting_key_link.cats"
import "coresystem/vrf_key_link.cats"
import "lock_hash/hash_lock.cats"
import "lock_secret/secret_lock.cats"
import "lock_secret/secret_proof.cats"
import "metadata/account_metadata.cats"
import "metadata/mosaic_metadata.cats"
import "metadata/namespace_metadata.cats"
import "mosaic/mosaic_definition.cats"
import "mosaic/mosaic_supply_change.cats"
import "mosaic/mosaic_supply_revocation.cats"
import "multisig/multisig_account_modification.cats"
import "namespace/address_alias.cats"
import "namespace/mosaic_alias.cats"
import "namespace/namespace_registration.cats"
import "restriction_account/account_address_restriction.cats"
import "restriction_account/account_mosaic_restriction.cats"
import "restriction_account/account_operation_restriction.cats"
import "restriction_mosaic/mosaic_address_restriction.cats"
import "restriction_mosaic/mosaic_global_restriction.cats"
import "transfer/transfer.cats"
+import "transfer/transferv.cats"
トランザクションタイプを定義します.TransfervTransaction.catでTransactionTypeの変数名をTRANSFERVとして定義したので,TRANSFERVとして定義します.
ここで重要になるのがトランザクションタイプの番号です.今回のTransfervTransactionはTransferTransactionのタイプ違いのものとして作成します.このため,TransferTransactionのタイプ違いのコードになるように定義してあげる必要があります(つまり好き勝手にきめられません)
私もとりあえず動かしたい精神で調べたので厳密には違う可能性があるのですがどうもこのトランザクションTypeの末尾2術はCatapultのFacilityCodeを示しているようです.
今回追加するTransferは0x54
となっているので末尾は54となります.全てのトランザクションタイプの1術目についている4が何なのかまでは調べきれてないのですが,2術目はTransferのタイプ(番号)を示しているようです.TransferにはTransferTransactionのみ存在し,このトランザクションタイプは1なので,追加されるTransfervTransactionのトランザクションタイプは2となります.よってTRANSFERV=0x4254
を指定します.
# Enumeration of Transaction types
enum TransactionType : uint16
# AccountKeyLinkTransaction
ACCOUNT_KEY_LINK = 0x414C
# NodeKeyLinkTransaction
NODE_KEY_LINK = 0x424C
# AggregateCompleteTransaction
AGGREGATE_COMPLETE = 0x4141
# AggregateBondedTransaction
AGGREGATE_BONDED = 0x4241
# VotingKeyLinkTransaction
VOTING_KEY_LINK = 0x4143
# VrfKeyLinkTransaction
VRF_KEY_LINK = 0x4243
# HashLockTransaction
HASH_LOCK = 0x4148
# SecretLockTransaction
SECRET_LOCK = 0x4152
# SecretProofTransaction
SECRET_PROOF = 0x4252
# AccountMetadataTransaction
ACCOUNT_METADATA = 0x4144
# MosaicMetadataTransaction
MOSAIC_METADATA = 0x4244
# NamespaceMetadataTransaction
NAMESPACE_METADATA = 0x4344
# MosaicDefinitionTransaction
MOSAIC_DEFINITION = 0x414D
# MosaicSupplyChangeTransaction
MOSAIC_SUPPLY_CHANGE = 0x424D
# MosaicSupplyRevocationTransaction
MOSAIC_SUPPLY_REVOCATION = 0x434D
# MultisigAccountModificationTransaction
MULTISIG_ACCOUNT_MODIFICATION = 0x4155
# AddressAliasTransaction
ADDRESS_ALIAS = 0x424E
# MosaicAliasTransaction
MOSAIC_ALIAS = 0x434E
# NamespaceRegistrationTransaction
NAMESPACE_REGISTRATION = 0x414E
# AccountAddressRestrictionTransaction
ACCOUNT_ADDRESS_RESTRICTION = 0x4150
# AccountMosaicRestrictionTransaction
ACCOUNT_MOSAIC_RESTRICTION = 0x4250
# AccountOperationRestrictionTransaction
ACCOUNT_OPERATION_RESTRICTION = 0x4350
# MosaicAddressRestrictionTransaction
MOSAIC_ADDRESS_RESTRICTION = 0x4251
# MosaicGlobalRestrictionTransaction
MOSAIC_GLOBAL_RESTRICTION = 0x4151
# TransferTransaction
TRANSFER = 0x4154
+ # CopyOfTransferTransaction
+ TRANSFERV = 0x4254
ちなみに私は適当にトランザクションタイプを設定した結果,ノードに拒絶されてしまう痛い思いをしました.みなさんの参考になってくれると嬉しいです.
これでcatbufferの準備は完了です
SDKを作成する
sdk>javascriptに移動し,次のコードを実行します
npm i
npm i request js-sha3
./scripts/run_catbuffer_generator.sh
すると,これだけで先ほど作成したcatbufferから新トランザクションを含んだSDKが生成されます.
catbufferはあくまで構造を定義するだけ(ノードに送信するペイロードの書式を定義するだけ?)なのでこういったことができるのかもしれません.
新しいトランザクションを作成したときにSDK作成の労力をなくす画期的な仕組みだと思います.
実行するとsrc/symbol/model.js
が新しいトランザクションを含んだSDKに更新されます.
新トランザクションを生成する
あとは新トランザクションを作成してみましょう.src内のREADMEにも情報がありますのでそちらもご覧ください.catbufferで生成されるSDKはpaylaodの生成に特化しているらしく,送信機能は別途実装する必要があります(私が見落としているかもです)
そこでノードの/transactionsにPUTで送信する機能を追加します
function anounceTX(signed){
console.log(node+"/transactions")
var options = {
uri: node+"/transactions",
headers: {
"Content-type": "application/json",
},
json: {
"payload": signed
}
};
request.put(options, function(error, response, body){
console.log(body);
});
}
Deadlineも自力で計算してあげます
const now = Date.now();
const eadj = 1637848847;
const deadline = BigInt(now - eadj*1000 + 60*60*5*1000);//deadlineを導出
トランザクションのステータスを知るために,生成したpayloadからトランザクションハッシュを導出します
const payload = stringToUint8Array(JSON.parse(jsonPayload).payload);
const sig = payload.slice(8,8+64);
const pub = payload.slice(8+64,8+64+32)
const gene = stringToUint8Array("7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836");
const tx = payload.slice(8 + 64 + 32 + 4);
const hasher = sha3_256.create();
hasher.update(sig);
hasher.update(pub);
hasher.update(gene);
hasher.update(tx);
const hash = new Uint8Array(hasher.arrayBuffer())
console.log(node +"/transactionStatus/"+uint8ToString(hash))
このように,生成されるSDKは本当に最小限の機能のみを提供するような形になっています.(逆に最小限だからこそcatbufferから生成できるのだと思います)
実際に開発者が使うにはこのSDKをラップして使い勝手が良いものに改造した方が良さそうです.
Transferv_transactionを送信するまでの全文です
const newXym = require("./src/index.js");
const { sha3_256 } = require('js-sha3');
const request = require('request');
const node = "http://154.12.242.37:3000"
// const node = "http://sym-test-01.opening-line.jp:3000"
const facade = new newXym.facade.SymbolFacade("testnet");
const key = new newXym.CryptoTypes.PrivateKey("**********")
const now = Date.now();
const eadj = 1637848847;
const deadline = BigInt(now - eadj*1000 + 60*60*5*1000);//deadlineを導出
const transaction = facade.transactionFactory.create({
type: 'transferv_transaction',
signerPublicKey: 'CBCBFCD07813A55368AE02E36A8C8D016D50D8DEE5C34BC4D805E8873EC93073',//signerの公開鍵
fee: 1000000n,
deadline: deadline,
recipientAddress: 'TAEVDDC5TXMJ5ICMRZ6A52DT6NYCQ7U3MBODEWA',
});
const signature = facade.signTransaction(new newXym.facade.SymbolFacade.KeyPair(key), transaction);
const jsonPayload = facade.transactionFactory.constructor.attachSignature(transaction, signature);
console.log(jsonPayload);
anounceTX(JSON.parse(jsonPayload).payload);
const payload = stringToUint8Array(JSON.parse(jsonPayload).payload);
const sig = payload.slice(8,8+64);
const pub = payload.slice(8+64,8+64+32)
const gene = stringToUint8Array("7FCCD304802016BEBBCD342A332F91FF1F3BB5E902988B352697BE245F48E836");
const tx = payload.slice(8 + 64 + 32 + 4);
const hasher = sha3_256.create();
hasher.update(sig);
hasher.update(pub);
hasher.update(gene);
hasher.update(tx);
const hash = new Uint8Array(hasher.arrayBuffer())
console.log(node +"/transactionStatus/"+uint8ToString(hash))
function stringToUint8Array(str){
const buf = Buffer.from(str,"hex");
return bufferToUint8Array(buf);
}
function bufferToUint8Array(buf) {
const view = new Uint8Array(buf.length);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return view;
}
function uint8ToString(uint8arr){
return Buffer.from(uint8arr).toString("hex").toUpperCase();
}
function anounceTX(signed){
console.log(node+"/transactions")
var options = {
uri: node+"/transactions",
headers: {
"Content-type": "application/json",
},
json: {
"payload": signed
}
};
request.put(options, function(error, response, body){
console.log(body);
});
}
完成したらテストネットの適当なノードに投げてみましょう.PluginTransactionはどこのノードも実装していない(はず)ので拒絶されたら成功です!
Catapult改造編につづく...