mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-30 08:35:50 +00:00
[JA] Move files to a dir compatible with Redocly
The new folder will be compatible with both Dactyl and Redocly when it comes to loading the Japanese translation for a given page. Snippets are not moved for now because importing them would require more complex changes to the actual Markdown pages, and wouldn't be cross-compatible anyway (Redocly uses slightly different 'partial' rather than 'include' syntax.)
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
---
|
||||
html: contribute-code.html
|
||||
parent: resources.html
|
||||
blurb: XRP Ledgerプロトコルにどのように機能を実装するかを学びます。
|
||||
labels:
|
||||
- ブロックチェーン
|
||||
---
|
||||
# コードへの貢献
|
||||
|
||||
XRP Ledgerを動かすソフトウェアはオープンソースです。誰でもダウンロードし、変更し、拡張し、調査することができます。もしあなたがコードに貢献したいのであれば、コミュニティと協力してあなたの変更の仕様を定義し、XRP Ledgerのプロトコルとブロックチェーンの一部になる前にコードをテストすることが重要です。
|
||||
|
||||
# コアサーバのソースコード
|
||||
|
||||
XRP Ledgerを動かすソフトウェアはオープンソースです。コミュニティが参加することで、より良いものが生まれます。[ドキュメント](docs.html)内の"[Source]"リンクから関連するソースコードに直接ジャンプしたり、GitHubでソースコードを閲覧することができます:
|
||||
|
||||
| XRP Ledger ソースコード | |
|
||||
|:-----------------------|:----------------------------------------------------|
|
||||
| リポジトリ | <https://github.com/XRPLF/rippled> |
|
||||
| ライセンス | [Multiple; ISC (permissive)](https://github.com/XRPLF/rippled/blob/develop/LICENSE.md) |
|
||||
| プログラム言語 | C++ |
|
||||
|
||||
何から始めたらいいか分からないという方のために、Dev Null Productionsは、XRP Ledgerサーバー(`rippled`)のコア実装の仕組みや機能を説明した、詳細かつ充実した[**ソースコード・ガイド**](https://xrpintel.com/source)を提供しています。
|
||||
|
||||
|
||||
## XRP Ledgerの規格
|
||||
|
||||
`rippled`に対する変更はXRP Ledger Standard (XLS)、つまり変更の仕様を特定し詳細に記述した文書によって管理されます。開発にコミットする前に、[XRPL-Standardsリポジトリ](https://github.com/XRPLF/XRPL-Standards/discussions)で議論を始める必要があります。これにより、コミュニティはあなたの変更に関して議論し、フィードバックを提供する機会を得ることができます。
|
||||
|
||||
**注記:*** バグ修正にはXLSは必要ありませんが、Amendmentが必要になる場合があります。
|
||||
|
||||
XLSの作成には独自のプロセスがありますが、簡単にまとめると次のようになります:
|
||||
|
||||
1. ディスカッションを開始し、フィードバックを集めます。
|
||||
2. StandardリポジトリにXLSドラフトを作成します。
|
||||
3. XLSドラフトを仕様候補として公開します。
|
||||
|
||||
詳細については、[XLS貢献ガイド](https://github.com/XRPLF/XRPL-Standards/blob/master/CONTRIBUTING.md) をご覧ください。
|
||||
|
||||
|
||||
## Amendmentの実装
|
||||
|
||||
XLSドラフトを作成した後、その変更にAmendmentが必要かどうかを判断する必要があります。特に次のような**トランザクション処理**に影響する変更にはAmendment が必要です。
|
||||
|
||||
- レジャールールを変更し、異なる結果をもたらすもの。
|
||||
- トランザクションの追加または削除。
|
||||
- コンセンサスへの影響がある変更。
|
||||
|
||||
**注記:** 変更にAmendmentが必要ない場合、そのままコーディングとデプロイに進むことができます。
|
||||
|
||||
コードをAmendmentとして実装するには、次のファイルにAmendment情報を追加する必要があります。
|
||||
|
||||
- **Feature.cpp**:
|
||||
|
||||
開発が完了するまで、`Supported`パラメータは`no`に設定してください。
|
||||
|
||||
バグの修正の場合、`DefaultVote`パラメータを`yes`に設定する必要があります。
|
||||
|
||||
- **Feature.h**: `numFeatures` カウンタを増やし、`extern uint256 const` 変数を宣言します。
|
||||
|
||||
|
||||
## コーディングとデプロイ
|
||||
|
||||
一般的な開発プロセスは以下の通りです。
|
||||
|
||||
1. コードを開発するためにはまず、[`rippled` リポジトリ](https://github.com/XRPLF/rippled) をフォークまたはブランチを作成します。
|
||||
|
||||
**ヒント:** 何から始めたらいいかわからない場合は、_Dev Null Productions_ が詳細かつ充実した [`rippled` ソースコードガイド](https://xrpintel.com/source) を提供しています。
|
||||
|
||||
2. 単体テストと統合テストを実行します。独立した環境で作業をテストするにはスタンドアロンモードでサーバを実行するのが良いでしょう。
|
||||
|
||||
3. `XRPLF:develop`にプルリクエストを作成します。
|
||||
|
||||
**Amendment向けの注記:** **Feature.cpp**の`Supported`パラメータを`yes`に更新します。
|
||||
|
||||
4. プルリクエストがXRP Ledgerのメンテナによって承認されると、あなたのコードは`develop`にマージされ、Devnet上で追加のテストを行うことができます。
|
||||
|
||||
**Amendment向けの注記:**
|
||||
- `DefaultVote`パラメータはロックされます。
|
||||
- もしAmendmentに問題が見つかれば、Amendmentの修正と新しいPRの提出を再度行う必要があります。新しいPRでは`DefaultVote`を変更することができます。
|
||||
|
||||
年に4回、`develop`で承認されたPRからリリース候補がビルドされます。このパッケージはTestnetとMainnet上のいくつかのノードにデプロイされます。リリース候補に問題がなければ、コードは`master`にマージされ、メインネット上のノードはこのビルドにアップグレードできます。
|
||||
|
||||
6. 新しいAmendmentは合意形成プロセスを経て、バリデーターがそのAmendmentを有効にするかどうかを投票します。
|
||||
|
||||
|
||||
## コードのフローチャート
|
||||
|
||||

|
||||
|
||||
|
||||
## 関連項目
|
||||
|
||||
- **コンセプト:**
|
||||
- [Amendment](amendments.html)
|
||||
@@ -1,389 +0,0 @@
|
||||
---
|
||||
html: create-custom-transactors.html
|
||||
parent: contribute-code.html
|
||||
blurb: XRP Ledgerとやり取りするためのカスタムトランザクタを作成します。
|
||||
labels:
|
||||
- 開発
|
||||
- ブロックチェーン
|
||||
---
|
||||
# カスタムトランザクタの作成
|
||||
|
||||
_トランザクタ_ はトランザクションを処理し、XRP Ledgerを変更するコードです。カスタムトランザクタを作成することで、`rippled`に新しい機能を追加することができます。このチュートリアルではトランザクタのコーディングについて説明しますが、それをXRPLに追加するにはAmendmentプロセスを経る必要があります。 [XRP Ledgerのコードへの貢献](contribute-code-flow.html)をご覧ください。
|
||||
|
||||
トランザクタは 基本的な処理順序に則って処理されます。
|
||||
|
||||
1. シリアライズ型レジャーエントリ(SLE/serialized type ledger entry)の _view_ へアクセスします。
|
||||
2. _view_ 内の値を更新、削除、挿入します。
|
||||
3. 確定した変更を _view_ からレジャーに適用します。
|
||||
|
||||
**注記:** _view_ はレジャーのサンドボックスです。トランザクタは必要なエラーチェックと変更のすべてをサンドボックス内で行い、レジャーでは直接行いません。値が確定した後、変更はレジャーにアトミックに適用されます。
|
||||
|
||||
このチュートリアルでは、既存の`CreateCheck`トランザクションを例として使用します。ソースファイルはここで確認できます。
|
||||
|
||||
- [ヘッダファイル](https://github.com/XRPLF/rippled/blob/master/src/ripple/app/tx/impl/CreateCheck.h)
|
||||
- [CPPファイル](https://github.com/XRPLF/rippled/blob/master/src/ripple/app/tx/impl/CreateCheck.cpp)
|
||||
|
||||
|
||||
## ヘッダファイル
|
||||
|
||||
次の形式でヘッダーファイルを作成します。
|
||||
|
||||
```c++
|
||||
namespace ripple {
|
||||
|
||||
class CreateCheck : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CreateCheck(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
```
|
||||
|
||||
`ApplyContext`でトランザクタを初期化すると、トランザクタは以下にアクセスできます:
|
||||
|
||||
- トランザクタをトリガーしたトランザクション。
|
||||
- SLEのビュー。
|
||||
- エラーを記録するためのジャーナル。
|
||||
|
||||
|
||||
## CPPファイル
|
||||
|
||||
### 1. `preflight`関数の追加
|
||||
|
||||
`preflight`関数はレジャーにアクセスする前にトランザクション自体にエラーがないかチェックします。無効なトランザクションや正しく設定されていないトランザクションは拒否されなければなりません。
|
||||
|
||||
- `PreflightContext`はレジャーのビューを持っていません。
|
||||
- レジャーやトランザクションからフィールドを取得するには、次のようにブラケット記法を使用します。
|
||||
|
||||
auto const curExpiration = (*sle*)[~sfExpiration];
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
|
||||
|
||||
**注記:** `~`記号は optional型を返します。
|
||||
|
||||
- レジャーとトランザクションのスキーマはこちらから確認できます。
|
||||
- [`LedgerFormats.cpp`](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/LedgerFormats.cpp)
|
||||
- [`TxFormats.cpp`](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp)
|
||||
|
||||
-` rippled` はトランザクションの結果を結果コードで表します。[トランザクションの結果](transaction-results.html)をご覧ください。
|
||||
|
||||
```c++
|
||||
CreateCheck::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
// Check if this amendment functionality is enabled on the network.
|
||||
if (!ctx.rules.enabled(featureChecks))
|
||||
return temDISABLED;
|
||||
|
||||
NotTEC const ret{preflight1(ctx)};
|
||||
if (!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
{
|
||||
// There are no flags (other than universal) for CreateCheck yet.
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: Invalid flags set.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
{
|
||||
// They wrote a check to themselves.
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: Check to self.";
|
||||
return temREDUNDANT;
|
||||
}
|
||||
|
||||
{
|
||||
STAmount const sendMax{ctx.tx.getFieldAmount(sfSendMax)};
|
||||
if (!isLegalNet(sendMax) || sendMax.signum() <= 0)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: bad sendMax amount: "
|
||||
<< sendMax.getFullText();
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
if (badCurrency() == sendMax.getCurrency())
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
|
||||
return temBAD_CURRENCY;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const optExpiry = ctx.tx[~sfExpiration])
|
||||
{
|
||||
if (*optExpiry == 0)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: bad expiration";
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 2. `preclaim`関数の追加
|
||||
|
||||
`preclaim`関数は、現在のレジャーの情報を見る必要があるエラーをチェックします。
|
||||
|
||||
- このステップが結果コード`tesSUCCESS`または`tec`を返した場合、トランザクションはキューに入れられ、ピアに送信されます。
|
||||
|
||||
```c++
|
||||
CreateCheck::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const dstId{ctx.tx[sfDestination]};
|
||||
|
||||
// Use the `keylet` function to get the key of the SLE. Views have either `read` or `peek` access.
|
||||
// `peek` access allows the developer to modify the SLE returned.
|
||||
auto const sleDst = ctx.view.read(keylet::account(dstId));
|
||||
if (!sleDst)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Destination account does not exist.";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
auto const flags = sleDst->getFlags();
|
||||
|
||||
// Check if the destination has disallowed incoming checks
|
||||
if (ctx.view.rules().enabled(featureDisallowIncoming) &&
|
||||
(flags & lsfDisallowIncomingCheck))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if ((flags & lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: DestinationTag required.";
|
||||
return tecDST_TAG_NEEDED;
|
||||
}
|
||||
|
||||
{
|
||||
STAmount const sendMax{ctx.tx[sfSendMax]};
|
||||
if (!sendMax.native())
|
||||
{
|
||||
// The currency may not be globally frozen
|
||||
AccountID const& issuerId{sendMax.getIssuer()};
|
||||
if (isGlobalFrozen(ctx.view, issuerId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen asset";
|
||||
return tecFROZEN;
|
||||
}
|
||||
// If this account has a trustline for the currency, that
|
||||
// trustline may not be frozen.
|
||||
//
|
||||
// Note that we DO allow create check for a currency that the
|
||||
// account does not yet have a trustline to.
|
||||
AccountID const srcId{ctx.tx.getAccountID(sfAccount)};
|
||||
if (issuerId != srcId)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sleTrust = ctx.view.read(
|
||||
keylet::line(srcId, issuerId, sendMax.getCurrency()));
|
||||
if (sleTrust &&
|
||||
sleTrust->isFlag(
|
||||
(issuerId > srcId) ? lsfHighFreeze : lsfLowFreeze))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Creating a check for frozen trustline.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
}
|
||||
if (issuerId != dstId)
|
||||
{
|
||||
// Check if dst froze the line.
|
||||
auto const sleTrust = ctx.view.read(
|
||||
keylet::line(issuerId, dstId, sendMax.getCurrency()));
|
||||
if (sleTrust &&
|
||||
sleTrust->isFlag(
|
||||
(dstId > issuerId) ? lsfHighFreeze : lsfLowFreeze))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Creating a check for destination frozen trustline.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check that has already expired.";
|
||||
return tecEXPIRED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 3. Add a `doApply()` function.
|
||||
|
||||
The `doApply()` function has read/write access, enabling you to modify the ledger.
|
||||
|
||||
```c++
|
||||
CreateCheck::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL;
|
||||
|
||||
// A check counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqProxy().value();
|
||||
Keylet const checkKeylet = keylet::check(account_, seq);
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
|
||||
sleCheck->setAccountID(sfAccount, account_);
|
||||
AccountID const dstAccountId = ctx_.tx[sfDestination];
|
||||
sleCheck->setAccountID(sfDestination, dstAccountId);
|
||||
sleCheck->setFieldU32(sfSequence, seq);
|
||||
sleCheck->setFieldAmount(sfSendMax, ctx_.tx[sfSendMax]);
|
||||
if (auto const srcTag = ctx_.tx[~sfSourceTag])
|
||||
sleCheck->setFieldU32(sfSourceTag, *srcTag);
|
||||
if (auto const dstTag = ctx_.tx[~sfDestinationTag])
|
||||
sleCheck->setFieldU32(sfDestinationTag, *dstTag);
|
||||
if (auto const invoiceId = ctx_.tx[~sfInvoiceID])
|
||||
sleCheck->setFieldH256(sfInvoiceID, *invoiceId);
|
||||
if (auto const expiry = ctx_.tx[~sfExpiration])
|
||||
sleCheck->setFieldU32(sfExpiration, *expiry);
|
||||
|
||||
view().insert(sleCheck);
|
||||
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
// If it's not a self-send (and it shouldn't be), add Check to the
|
||||
// destination's owner directory.
|
||||
if (dstAccountId != account_)
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(dstAccountId),
|
||||
checkKeylet,
|
||||
describeOwnerDir(dstAccountId));
|
||||
|
||||
JLOG(j_.trace()) << "Adding Check to destination directory "
|
||||
<< to_string(checkKeylet.key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
|
||||
sleCheck->setFieldU64(sfDestinationNode, *page);
|
||||
}
|
||||
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(account_),
|
||||
checkKeylet,
|
||||
describeOwnerDir(account_));
|
||||
|
||||
JLOG(j_.trace()) << "Adding Check to owner directory "
|
||||
<< to_string(checkKeylet.key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
|
||||
sleCheck->setFieldU64(sfOwnerNode, *page);
|
||||
}
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sle, 1, viewJ);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 追加の関数
|
||||
|
||||
必要に応じて、カスタムトランザクタにヘルパー関数を追加することができます。特殊な場合に役立つ特別な関数がいくつかあります。
|
||||
|
||||
|
||||
### `calculateBaseFee`
|
||||
|
||||
ほとんどのトランザクションはデフォルトの[Referenceトランザクションコスト](transaction-cost.html)をそのまま引き継ぎます。しかし、トランザクションで通常以外のトランザクションコストを定義する必要がある場合、トランザクションの`calculateBaseFee`メソッドをカスタムメソッドに置き換えることができます。
|
||||
|
||||
次の例では、`EscrowFinish`ランザクションが条件付きエスクローに対して、フルフィルメントの大きさに応じて追加コストを請求する方法を示しています。
|
||||
|
||||
```c++
|
||||
XRPAmount
|
||||
EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount extraFee{0};
|
||||
|
||||
if (auto const fb = tx[~sfFulfillment])
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `makeTxConsequences`
|
||||
|
||||
`rippled`は[`TxConsequences`](https://github.com/XRPLF/rippled/blob/master/src/ripple/app/tx/applySteps.h#L41-L44)クラスを使用して、トランザクション適用時のアカウントへの結果を記述します。このクラスは手数料、使用可能な最大XRP、トランザクションによって消費されたシーケンス番号の数を追跡します。結果には次の3つのタイプがあります。
|
||||
|
||||
- **ノーマル:**トランザクションは署名に影響を与えず、XRP手数料を消費するのみです。手数料を超えてXRPを消費するトランザクションは正常とはみなされません。
|
||||
- **ブロッカー:**トランザクションの署名に影響を与え、有効なトランザクションがその後ろにキューイングされるのを防ぎます。
|
||||
- **カスタム:**トランザクタは結果を決定するために追加の作業を行う必要があります。
|
||||
|
||||
`makeTxConsequences`関数を使うと、以下のような状況に対してカスタム結果を作成することができます:
|
||||
|
||||
- XRPを送信する支払い。
|
||||
- 複数のシーケンス番号を消費するチケット。
|
||||
- 設定されたフラグやフィールドによって、正常またはブロッカーとなるトランザクション。
|
||||
|
||||
**注記:** `TxConsequences`は[トランザクションキュー](transaction-queue.html)にのみ影響します。トランザクションがレジャーに適用されたときに手数料を請求する可能性が高い場合、それはピアに送信されます。手数料を請求する可能性がない場合、またはそれが判断できない場合は、送信されません。
|
||||
|
||||
|
||||
```c++
|
||||
SetAccount::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
// The SetAccount may be a blocker, but only if it sets or clears
|
||||
// specific account flags.
|
||||
auto getTxConsequencesCategory = [](STTx const& tx) {
|
||||
if (std::uint32_t const uTxFlags = tx.getFlags();
|
||||
uTxFlags & (tfRequireAuth | tfOptionalAuth))
|
||||
return TxConsequences::blocker;
|
||||
|
||||
if (auto const uSetFlag = tx[~sfSetFlag]; uSetFlag &&
|
||||
(*uSetFlag == asfRequireAuth || *uSetFlag == asfDisableMaster ||
|
||||
*uSetFlag == asfAccountTxnID))
|
||||
return TxConsequences::blocker;
|
||||
|
||||
if (auto const uClearFlag = tx[~sfClearFlag]; uClearFlag &&
|
||||
(*uClearFlag == asfRequireAuth || *uClearFlag == asfDisableMaster ||
|
||||
*uClearFlag == asfAccountTxnID))
|
||||
return TxConsequences::blocker;
|
||||
|
||||
return TxConsequences::normal;
|
||||
};
|
||||
|
||||
return TxConsequences{ctx.tx, getTxConsequencesCategory(ctx.tx)};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 次のステップ
|
||||
|
||||
新しいトランザクタでサーバを再コンパイルし、[スタンドアロンモード](use-stand-alone-mode.html)でテストしてください。もしAmendmentの後ろにトランザクタをコーディングした場合、設定ファイルを使ってその機能を[強制的に有効にする](test-amendments.html)ことができます。
|
||||
Reference in New Issue
Block a user