9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

nemAdvent Calendar 2021

Day 11

catbufferでタイプ違いのTransferTransactionを定義してSDKを作成し,新トランザクションを生成する

Last updated at Posted at 2022-04-16

新しいトランザクションをつくりたい

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に変えていきます.

TransfervTransaction.cats
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に認識させます

all_transaction.cats
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を指定します.

transaction_type.cats
# 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改造編につづく...

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?