概要
こちらをご覧ください
ざっくりいうと暗号学的にまず証明することができない秘密鍵の破棄を、プラグイントランザクションで代用しようとするものです
プラグインに必要なもの
以前、TransferTransactionのコピーは作った経験があったのですが、これはトランザクションタイプ以外すべてコピーで作ったため本来プラグイントランザクションを作るために必要な工程を殆ど飛ばしていました。本来トランザクションごとに全く別の機能があるのですから、その機能などを定義する必要があります。
必要なものについて調べるためこちらの記事をみてみましょう
トランザクションプラグインの中に次の物が内包されていることが分かります
- Observers
- Validators
- Transactions Definitions
前回のコピートランザクションではTransactions Definitions
だけ定義し、他は流用していそうだなと分かります。今回はこれらを設計していきましょう
Transactions Definitions
今回はアカウントの無効化ということで、account_restriction
の新トランザクションとして作成します。restrictionのプラグンとして作成するため、フラグの定義などをそのまま流用することができます。
まずEntityType, Flagに手を加えます
+ /// Operation account deactivation restriction transaction.
+ DEFINE_TRANSACTION_TYPE(RestrictionAccount, Account_Deactivation_Restriction, 0x4);
/// Restriction type sentinel.
Sentinel = 0x0008,
+ /// Restriction type Deactivation.
+ Deactivation = 0x0010,
/// Restriction is interpreted as outgoing.
Outgoing = 0x4000,
/// Operation account restriction transaction.
DEFINE_TRANSACTION_TYPE(RestrictionAccount, Account_Operation_Restriction, 0x3);
+ /// Operation account deactivation restriction transaction.
+ DEFINE_TRANSACTION_TYPE(RestrictionAccount, Account_Deactivation_Restriction, 0x4);
#ifndef CUSTOM_ENTITY_TYPE_DEFINITION
今回作成するトランザクションのモデルを定義します(といっても他のプラグインと殆ど構造はおなじです)
#pragma once
#include "AccountRestrictionSharedTransaction.h"
namespace catapult { namespace model {
#pragma pack(push, 1)
/// Binary layout for an account operation restriction transaction body.
DEFINE_ACCOUNT_RESTRICTION_TRANSACTION(Deactivation, Deactivation, EntityType)
#pragma pack(pop)
}}
モデルをstateに追加します
addRestriction(model::AccountRestrictionFlags::TransactionType | Outgoing, sizeof(model::EntityType));
+ addRestriction(model::AccountRestrictionFlags::Deactivation , sizeof(model::EntityType));
}
Observers
Ovserversはプラグインのnotificationを構成します。notificationでは対象に設定したトランザクションを受信した際に通知を発報し、トランザクションのValidationを実行する役割があります
今回作成するトランザクションはアカウント無効化、つまり無効化フラグがたったアドレスの署名をすべて拒否する必要があります。そこで全てのトランザクションに対して発火するObserverを作成します
便利なことに、これはCatapultでTransactionNotification
として既に実装されているためこれだけで作成できます。状態を持つのでSTATEFUL_VALIDATORとして宣言します
DECLARE_STATEFUL_VALIDATOR(AccountOperationRestrictionNoSelfBlocking, model::ModifyAccountOperationRestrictionValueNotification)();
+ DECLARE_STATEFUL_VALIDATOR(AccountDeactivation, model::TransactionNotification)();
}}
他のrestrictionTransactionでは値を変更する必要があるため、他のObserverを作成したり独自に定義したりするのですが、今回は一度アナウンスすると2度とアカウントが使えなくなるトランザクションなのでこれ1つだけでOKです。
Validators
Validatorはトランザクションを検証する役割を持ちます。つまりここでフラグの有無を判定してトランザクションを拒否するコードを書けばよいのです。
modelで定義したフラグがあれば拒否するValidatorを作ってあげます
また、判別結果を吐き出せるようにログを仕込んでおきます
#include "Validators.h"
#include "AccountRestrictionView.h"
#include "src/cache/AccountRestrictionCache.h"
#include "catapult/model/Address.h"
#include "catapult/validators/ValidatorContext.h"
namespace catapult { namespace validators {
using Notification = model::TransactionNotification;
DEFINE_STATEFUL_VALIDATOR(AccountDeactivation, [](const Notification& notification,const ValidatorContext& context) {
constexpr auto Restriction_Flags = model::AccountRestrictionFlags::Deactivation;
AccountRestrictionView view(context.Cache);
CATAPULT_LOG(info) << "Deactivation validation....";
if (!view.initialize(notification.Sender))
return ValidationResult::Success;
auto isDeactivated = HasFlag(model::AccountRestrictionFlags::Deactivation, view.get(Restriction_Flags).descriptor().raw());
if (!isDeactivated)
CATAPULT_LOG(info) << "validation success";
if (isDeactivated)
CATAPULT_LOG(info) << "DeactivatedAddress....";
return isDeactivated ? Failure_RestrictionAccount_Account_Deactivated : ValidationResult::Success;
})
}}
追加したFlagを他のValidatorにも追加しておきます
case model::AccountRestrictionFlags::MosaicId:
return HasSingleFlag(directionalRestrictionFlags);
case model::AccountRestrictionFlags::TransactionType:
return HasFlag(model::AccountRestrictionFlags::Outgoing, directionalRestrictionFlags);
+ case model::AccountRestrictionFlags::Deactivation:
+ return true;
default:
return false;
}
+ #include "src/model/AccountDeactivationRestrictionTransaction.h"
namespace {
constexpr auto Relevant_Entity_Type = model::AccountOperationRestrictionTransaction::Entity_Type;
+ constexpr auto Relevant_Entity_Type_Deactivation = model::AccountDeactivationRestrictionTransaction::Entity_Type;
+ constexpr auto Restriction_Flags = model::AccountRestrictionFlags::TransactionType | model::AccountRestrictionFlags::Outgoing | model::AccountRestrictionFlags::Deactivation;
bool Validate(const Notification& notification, const ValidatorContext& context) {
AccountRestrictionView view(context.Cache);
auto isRelevantEntityType = Relevant_Entity_Type == notification.RestrictionValue;
+ auto isRelevantEntityTypeDeactivation = Relevant_Entity_Type_Deactivation == notification.RestrictionValue;
auto isAllow = state::AccountRestrictionOperationType::Allow == notification.AccountRestrictionDescriptor.operationType();
// cannot delete relevant entity type for operation type Allow
if (model::AccountRestrictionModificationAction::Del == notification.Action)
return !(isAllow && isRelevantEntityType);
size_t numRestrictionValues = 0;
if (view.initialize(notification.Address)) {
const auto& restriction = view.get(Restriction_Flags);
numRestrictionValues = restriction.values().size();
}
// adding a value to an account restrictions should only be allowed when
// - operation type Allow: if it is the relevant entity type or the relevant entity type is already contained
// - operation type Block: if it is not the relevant entity type
+ // isAllowAndForbidden... isAllow==true, isRelevantEntityType==false, numRestrictionValues==0
auto isAllowAndForbidden = isAllow && !isRelevantEntityType && isRelevantEntityTypeDeactivation && 0 == numRestrictionValues;
auto isBlockAndForbidden = !isAllow && isRelevantEntityType;
if (isAllowAndForbidden)
CATAPULT_LOG(info) << "Failure_RestrictionAccount_Invalid_Modification(noSelfBlocking, isAllowAndForbidden)";
if (isBlockAndForbidden)
CATAPULT_LOG(info) << "Failure_RestrictionAccount_Invalid_Modification(noSelfBlocking, isBlockAndForbidden)";
return !(isAllowAndForbidden || isBlockAndForbidden);
}
エラーコードを作ってあげればvalidatorは完成です
/// Validation failed because the operation type is not allowed to be initiated by the signer.
DEFINE_RESTRICTION_ACCOUNT_RESULT(Operation_Type_Prohibited, 13);
+ /// Validation failed beause the account was deactivated
+ DEFINE_RESTRICTION_ACCOUNT_RESULT(Account_Deactivated, 14);
#ifndef CUSTOM_RESULT_DEFINITION
完成
これで完成です。勿論開発には結構な時間を要しているのですが流用が結構効くのでソースコードは非常に少ないことが分かると思います。
こちらの指示に従ってdockerビルドしてください
なおビルドの際、/jenkins_cache/ccache/release
を作成し、パーミッションを設定して上げる必要があります(今後修正されるかもしれません)
catbuffer
作成した新トランザクションをCatbufferで定義して投げます
今回はaccount_restrictionの4番目のトランザクションとして作成しました。
- トランザクションの最上位ビット:
4
- タイプ:
4
- RestrictionAccountのファシリティコード:
0x50
よって今回作成したトランザクションタイプは0x4450(17488)
となります
また、OperationRestrictionの構造をそのまま利用したのでrestriction_additions
およびrestriction_deletions
にはTransactionType
を配列形式で作成してやればよいです。最終的な構造は次のようになります
import "restriction_account/restriction_account_types.cats"
import "transaction.cats"
# Shared content between AccountDeactivateTransaction and EmbeddedAccountDeactivateTransaction.
inline struct AccountDeactivateTransactionBody
# Type of restriction being applied to the listed transaction types.
restriction_flags = AccountRestrictionFlags
# Number of transaction types being added.
restriction_additions_count = uint8
# Number of transaction types being removed.
restriction_deletions_count = uint8
# Reserved padding to align restriction_additions to an 8-byte boundary.
account_restriction_transaction_body_reserved_1 = make_reserved(uint32, 0)
# Array of transaction types being added to the restricted list.
restriction_additions = array(TransactionType, restriction_additions_count)
# Array of transaction types being rtemoved from the restricted list.
restriction_deletions = array(TransactionType, restriction_deletions_count)
# Allow or block outgoing transactions depending on their transaction type (V1, latest).
struct AccountDeactivateTransactionV1
TRANSACTION_VERSION = make_const(uint8, 1)
TRANSACTION_TYPE = make_const(TransactionType, ACCOUNT_DEACTIVATE)
inline Transaction
inline AccountDeactivateTransactionBody
# Embedded version of AccountDeactivateTransaction (V1, latest).
struct EmbeddedAccountDeactivateTransactionV1
TRANSACTION_VERSION = make_const(uint8, 1)
TRANSACTION_TYPE = make_const(TransactionType, ACCOUNT_DEACTIVATE)
inline EmbeddedTransaction
inline AccountDeactivateTransactionBody
作り方についてはこちらをご覧ください
実践
カスタムプリセットを作ります。モザイクを配布するのが面倒なので手数料0ノードを立てましょう。また、起動イメージはビルドしたものを指定します
symbolServerImage: symbolplatform/symbol-server-private:gcc-latest-main-9273d6c5
minFeeMultiplier: 0
起動します
symbol-bootstrap start -d -p bootstrap -a dual -c custom.yml
これに対してCatbufferで作ったトランザクションを投げます。2022年11月30日現在、sdkのjavascriptにwasmが導入されて立ち上げが若干大変になっているので個人的にはPythonSDKがおすすめです
無効化前
TransferTransactionを投げると受理されます
無効化TX
受理されます。フラグを建てる必要があるため、additionに何かTransactionTypeを入れてください
無効化後
通常のTransferTransactionを投げると次のようなレスポンスが帰ってきます
{
"hash": "ADE2C516A7BB6F023FD092799326C699753208BE0D4FD3453CE3E322F63A99E0",
"code": "unknown status 0x8050000E",
"deadline": "96365378578",
"group": "failed"
}
見たことのないエラーコード(0x8050000E)が帰ってきていることがわかります。
これはRestにエラーコードが登録されていないことが原因です。Restに定義してやれば問題なく動作します
正常に拒否できているか確認するため、Validatorに仕込んでおいたログを確認してみましょう
2022-11-30 06:53:09.588390 0x00007fb765ff3640: <info> (validators::AccountDeactivateValidator.cpp@37) Deactivate validation....
2022-11-30 06:53:09.588465 0x00007fb765ff3640: <info> (validators::AccountDeactivateValidator.cpp@58) DeactivatedAddress....
無事、無効化に成功していることがわかります
ソースコード
こちらをご覧ください
プラグインの可能性
プラグインであれば暗号学的に難しいことを補うトランザクションなど、様々なものを作ることができます。
送金すると相手の残高を奪えるようなカオスなネットワークだって作ることができます。これは送金時に発生させるBalanceTransferNotificationの送金先、送金元を入れ替えるだけで実現できます
const auto* pMosaics = transaction.MosaicsPtr();
for (auto i = 0u; i < transaction.MosaicsCount; ++i) {
sub.notify(BalanceTransferNotification(
transaction.RecipientAddress,//context.SignerAddress,逆転
context.SignerAddress,//transaction.RecipientAddress,
pMosaics[i].MosaicId,
pMosaics[i].Amount));
}
そして公式がフォークを認めるかは別としてDEXの実装も可能です。 C++に強い方、是非DEXを実装してCatapultを強化してみてください
その他
C++はまともに勉強したことがなく、Catapult内の他のPluginからコピペして試行錯誤しながら作ったのでバグや不適切な書き方が確実にあります。C++に強い方、もしよければ改善点やバグを教えて下さい
参考