CatapultでPluginTransactionを作成する
この記事の続きです
前回に続いてType違いのTransferTransactionを作成します.
今回はタイプ違いのトランザクションを追加してみることだけを目指していたので詳しい動作については自分自身も未だ理解できておらず,説明ができないので今回は省略させていただきます.個人的な目標としてはこれらの動作をしっかり把握して新しいトランザクションを作ってみたいです.
Transactionを追加する
TransfervTransactionの構造を定義する
TransferTransactionのタイプ違い(中身は同じ)なトランザクションを作成するのでclient/catapult/plugins/txes/transfer/src/model/TransferTransaction.h
の中身をコピーしてTransfervTransactionとして書き換えます.前回の記事で作成したものと同じ構造にします.
#pragma once
#include "TransferEntityType.h"
#include "catapult/model/Mosaic.h"
#include "catapult/model/Transaction.h"
namespace catapult { namespace model {
#pragma pack(push, 1)
/// Binary layout for a transferv transaction body.
template<typename THeader>
struct TransfervTransactionBody : public THeader {
private:
using TransactionType = TransfervTransactionBody<THeader>;
public:
DEFINE_TRANSACTION_CONSTANTS(Entity_Type_Transferv, 1)
public:
/// Recipient address.
UnresolvedAddress RecipientAddress;
/// Message size in bytes.
uint16_t MessageSize;
/// Number of mosaics.
uint8_t MosaicsCount;
/// Reserved padding to align Mosaics on 8-byte boundary.
uint32_t TransferTransactionBody_Reserved1;
uint8_t TransferTransactionBody_Reserved2;
// followed by mosaics data if MosaicsCount != 0
DEFINE_TRANSACTION_VARIABLE_DATA_ACCESSORS(Mosaics, UnresolvedMosaic)
// followed by message data if MessageSize != 0
DEFINE_TRANSACTION_VARIABLE_DATA_ACCESSORS(Message, uint8_t)
private:
template<typename T>
static auto* MosaicsPtrT(T& transaction) {
return transaction.MosaicsCount ? THeader::PayloadStart(transaction) : nullptr;
}
template<typename T>
static auto* MessagePtrT(T& transaction) {
auto* pPayloadStart = THeader::PayloadStart(transaction);
return transaction.MessageSize && pPayloadStart
? pPayloadStart + transaction.MosaicsCount * sizeof(UnresolvedMosaic)
: nullptr;
}
public:
/// Calculates the real size of transferv \a transaction.
static constexpr uint64_t CalculateRealSize(const TransactionType& transaction) noexcept {
return sizeof(TransactionType) + transaction.MessageSize + transaction.MosaicsCount * sizeof(UnresolvedMosaic);
}
};
DEFINE_EMBEDDABLE_TRANSACTION(Transferv)
#pragma pack(pop)
}}
Pluginを追加する
Transactionの動作を定義する
#include "TransfervTransactionPlugin.h"
#include "src/model/TransferNotifications.h"
#include "src/model/TransfervTransaction.h"
#include "catapult/model/NotificationSubscriber.h"
#include "catapult/model/TransactionPluginFactory.h"
using namespace catapult::model;
namespace catapult { namespace plugins {
namespace {
template<typename TTransaction>
void Publish(const TTransaction& transaction, const PublishContext& context, NotificationSubscriber& sub) {
auto padding = transaction.TransferTransactionBody_Reserved1 << 8 | transaction.TransferTransactionBody_Reserved2;
sub.notify(InternalPaddingNotification(padding));
sub.notify(AccountAddressNotification(transaction.RecipientAddress));
sub.notify(AddressInteractionNotification(context.SignerAddress, transaction.Type, { transaction.RecipientAddress }));
const auto* pMosaics = transaction.MosaicsPtr();
for (auto i = 0u; i < transaction.MosaicsCount; ++i) {
sub.notify(BalanceTransferNotification(
context.SignerAddress,
transaction.RecipientAddress,
pMosaics[i].MosaicId,
pMosaics[i].Amount));
}
if (transaction.MessageSize) {
sub.notify(TransferMessageNotification(
transaction.SignerPublicKey,
transaction.RecipientAddress,
transaction.MessageSize,
transaction.MessagePtr()));
}
if (transaction.MosaicsCount)
sub.notify(TransferMosaicsNotification(transaction.MosaicsCount, pMosaics));
}
}
DEFINE_TRANSACTION_PLUGIN_FACTORY(Transferv, Default, Publish)
}}
Pluginとして登録する
#include "TransferPlugin.h"
#include "TransferTransactionPlugin.h"
#include "src/config/TransferConfiguration.h"
#include "src/observers/Observers.h"
#include "src/validators/Validators.h"
#include "catapult/config/CatapultDataDirectory.h"
#include "catapult/config/CatapultKeys.h"
#include "catapult/crypto/OpensslKeyUtils.h"
#include "catapult/model/Address.h"
#include "catapult/plugins/PluginManager.h"
+ #include "TransfervTransactionPlugin.h"
namespace catapult { namespace plugins {
void RegisterTransferSubsystem(PluginManager& manager) {
manager.addTransactionSupport(CreateTransferTransactionPlugin());
+ manager.addTransactionSupport(CreateTransfervTransactionPlugin());
auto config = model::LoadPluginConfiguration<config::TransferConfiguration>(manager.config(), "catapult.plugins.transfer");
manager.addStatelessValidatorHook([config](auto& builder) {
builder.add(validators::CreateTransferMessageValidator(config.MaxMessageSize));
builder.add(validators::CreateTransferMosaicsValidator());
});
if (!manager.userConfig().EnableDelegatedHarvestersAutoDetection)
return;
auto encryptionPrivateKeyPemFilename = config::GetNodePrivateKeyPemFilename(manager.userConfig().CertificateDirectory);
auto encryptionPublicKey = crypto::ReadPublicKeyFromPrivateKeyPemFile(encryptionPrivateKeyPemFilename);
auto recipient = model::PublicKeyToAddress(encryptionPublicKey, manager.config().Network.Identifier);
auto dataDirectory = config::CatapultDataDirectory(manager.userConfig().DataDirectory);
manager.addObserverHook([recipient, dataDirectory](auto& builder) {
builder.add(observers::CreateTransferMessageObserver(0xE201735761802AFE, recipient, dataDirectory.dir("transfer_message")));
});
}
}}
extern "C" PLUGIN_API
void RegisterSubsystem(catapult::plugins::PluginManager& manager) {
catapult::plugins::RegisterTransferSubsystem(manager);
}
#pragma once
#include "catapult/plugins.h"
#include <memory>
namespace catapult { namespace model { class TransactionPlugin; } }
namespace catapult { namespace plugins {
/// Creates a transfer transaction plugin.
PLUGIN_API
std::unique_ptr<model::TransactionPlugin> CreateTransfervTransactionPlugin();
}}
TransactionTypeを追加する
今回はTransferTransactionのタイプ違いを作成するのでEntityTypeに新しいタイプを定義します.
#pragma once
#ifndef CUSTOM_ENTITY_TYPE_DEFINITION
#include "catapult/model/EntityType.h"
namespace catapult { namespace model {
#endif
/// Transfer transaction.
DEFINE_TRANSACTION_TYPE(Transfer, Transfer, 0x1);
+ //add Transferv
+ DEFINE_TRANSACTION_TYPE(Transfer, Transferv, 0x2);
#ifndef CUSTOM_ENTITY_TYPE_DEFINITION
}}
これらはgithubにて公開しています.もしよければ参考にしてみてください.
Dockerビルドで環境構築を省力化する
これまで,PluginTransactionの検証にはcatapultをマニュアルビルドし,起動する方法を私はとっていました.しかしこれにはmongodbのセットアップなど設定や,プロセスの管理が大変です.そこでCatapultをdockerビルドし,Symbol-bootstrapで起動することで作業を簡略化します.
作業前に,symbol-bootstrapでノードを構築する場合と同じ環境(doker,docker-compose,nodejs)をインストールしておいてください.また,dockerhubのアカウントを取得してください.
Dockerビルド
こちらの手順に従います.
まず,そのままでは動かないコードがあるのでこれを修正します(2022/05/06現在,今後修正されると思います).
今回はうにやさんの情報を元にさせていただきました.本当にありがとうございます.
dockerhubにログインします
docker login
出力ファイルの設定を行います.ファイルを次のように書き換えてください
$ vi jenkins/catapult/runDockerBuild.py
- '--source-path=/catapult-src/client/catapult',
+ '--source-path=/catapult-src',
- environment_manager.copy_tree_with_symlinks(source_path / 'client/catapult' / folder_name, folder_name)
+ environment_manager.copy_tree_with_symlinks(source_path / '' / folder_name, folder_name)
- destination_image_name = f'symbolplatform/{destination_repository}:{destination_image_label}'
+ destination_image_name = f'(dockerhubのユーザー名)/(お好みのレポジトリの名前):{destination_image_label}'
設定が終わったら次のコマンドを実行します.
python3 jenkins/catapult/runDockerBuild.py \
--compiler-configuration jenkins/catapult/configurations/gcc-10.yaml \
--build-configuration jenkins/catapult/configurations/release-private.yaml \
--operating-system ubuntu \
--user "$(id -u):$(id -g)" \
--source-path client/catapult \
--destination-image-label latest
成功するとイメージが作成されます.次のコマンドで確認ができます.
docker images
これをdockerhubに登録します
docker push (dockerhubのユーザー名)/(お好みのレポジトリの名前):latest
bootstrapの設定を書き換える
bootstrapには公式の作成したcatapultのdockerのイメージが指定されており,必要に応じてこれをDL,使用するようになっています.そこでこのイメージの参照先を公式から自分の作成したものに切り替えます.npmのグローバルインストール先を探し,symbol-bootstrapの設定を書き換えます.おそらく/usr/libがグローバルインストール先として表示されると思います.
npm list -g | head -1
cd /usr/lib/node_modules/symbol-bootstrap/presets
vi shared.yml
symbolServerImageを書き換えます
...
symbolServerImage: (dockerhubのユーザー名)/(お好みのレポジトリの名前)
...
起動する
あとはプライベートネットワークを起動するだけです.ノードを展開しても良いディレクトリで次のコマンドを実行してください.実行後,パスワードを設定すれば完了です.プリセットにはbootstrapを使用します.testnetやmainnetにした場合,PluginTransaactionのTXは承認されないままになってしまうので注意してください.
symbol-bootstrap start -d -p bootstrap
今までのようなマニュアルビルドの作業をしなくてもこれだけでdualノードが構築できてしまいます.
TXを投げる
前回作成したSDKを利用してTXを投げてみましょう.作成したノードのgenerationhash
を知るためにhttp://(URL):3000/network/properties
にアクセスし,generationhash
およびepochAdjustment
を確認します.
確認後,前回作成したSDKの/src/symbol/Network.js
を確認し次のように書き換えます
Network.TESTNET = new Network('testnet', 0x98, new Hash256('ここにgenerationhash'));
また,ネットワークのepochAdjustmentを TX送信の際に利用するようにしてください.
Tを投げてみて承認されれば成功です!
PluginTransactionを試す場合はTXを投げるための通貨が必要になると思います.Nemesisで配布された通貨が必要な場合はtarget内にaddresses.ymlがあるのでこれをsymbol-bootstarp decrypt --source addresses.yml --destination decrypt.yml
で復号すればOKです.単に送金のないTXを投げるだけであればノードのminfeemultiplierを0に変更するのもありです.
その他
- dockerビルドしたものをdockerhubに登録することで別のユーザーがイメージを取得し,ノードを構築することも当然可能になります.(既存のネットワークを拡張する場合はseed,設定値が別途必要になります)dockerビルドでPluginTransactionを搭載したノードを公開するのは結構便利な手段になると思います.
- 未だ私も勉強中の身ではあるのですが,PluginTransactionによって従来にはなかった取引を実現し,Catapultをより強力かつ便利なネットワークにすることが可能だと考えています.この記事だけではPluginTransactionを勉強するには不十分ですが,もしよろしければcatapultについて研究して是非PluginTransactionを開発してみてください!