XRPLのトランザクションコード
rippledとは
rippledは、XRP Ledgerのサーバーソフトウェアです。XRP Ledgerの各バリデータやノードの実体は、「rippled」であり、XRP Ledgerがネイティブなトークン機能、DEX機能、NFT機能等を備えているのは、rippledにこれらの機能が実装されているためです。
この記事ではそのトランザクションのコードについて簡単に解説します。
トランザクションコード
ここではCheckCreate
トランザクションを例として解説し、CreateCheck.cpp
ファイルのコードのみを取り扱います。
CreateCheck.cpp
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/tx/impl/CreateCheck.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
NotTEC
CreateCheck::preflight(PreflightContext const& ctx)
{
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);
}
TER
CreateCheck::preclaim(PreclaimContext const& ctx)
{
AccountID const dstId{ctx.tx[sfDestination]};
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;
// AMM can not cash the check
if (sleDst->isFieldPresent(sfAMMID))
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;
}
TER
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;
}
} // namespace ripple
トランザクションコードは主にpreflight
やpreclaim
、doApply
の3つの関数から構成されています。
preflight
現在のステート情報を必要としないチェック処理を行います。
これはトランザクションの送信時に送信先ノードで処理され、エラーが発生した場合は他のノードへの伝播は行われません。
つまりこの処理でチェックを通過した場合は、他のノードへの伝播が行われ、コンセンサス処理の対象となります。
preclaim
ステートの書き込み(追加・更新・削除)を必要としないチェック処理を行います。
doApply
いくつかの正常性チェックとトランザクションの反映・登録処理を行います。
他にもトランザクションコスト(手数料)に関連するcalculateBaseFee
やその他makeTxConsequences
などありますが、ここでは割愛します。
コードをブロックに分けて確認してみましょう。
preflight
preflightではトランザクションのフォーマットチェックを行います。
この処理はオープンレジャーに入る前、つまりトランザクションの送信直後に行われます。
この処理でエラーになった場合、他のノードへの伝播は行われず、トランザクションは即座にエラーとなります。
NotTEC
CreateCheck::preflight(PreflightContext const& ctx)
{
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);
}
Amendmentの有効化チェック
if (!ctx.rules.enabled(featureChecks))
return temDISABLED;
Amendmentの有効化のチェックを行なっています。
Amendmentが有効でない場合、トランザクションはエラーとなります。
共通フィールドのフォーマットチェック
NotTEC const ret{preflight1(ctx)};
if (!isTesSuccess(ret))
return ret;
全てのトランザクションの共通フィールドであるアカウントアドレス(Account
)や手数料(Fee
)などのチェックを行うpreflight1
関数を呼び出しています。
preflight1
NotTEC
preflight1(PreflightContext const& ctx)
{
// This is inappropriate in preflight0, because only Change transactions
// skip this function, and those do not allow an sfTicketSequence field.
if (ctx.tx.isFieldPresent(sfTicketSequence) &&
!ctx.rules.enabled(featureTicketBatch))
{
return temMALFORMED;
}
auto const ret = preflight0(ctx);
if (!isTesSuccess(ret))
return ret;
auto const id = ctx.tx.getAccountID(sfAccount);
if (id == beast::zero)
{
JLOG(ctx.j.warn()) << "preflight1: bad account id";
return temBAD_SRC_ACCOUNT;
}
// No point in going any further if the transaction fee is malformed.
auto const fee = ctx.tx.getFieldAmount(sfFee);
if (!fee.native() || fee.negative() || !isLegalAmount(fee.xrp()))
{
JLOG(ctx.j.debug()) << "preflight1: invalid fee";
return temBAD_FEE;
}
auto const spk = ctx.tx.getSigningPubKey();
if (!spk.empty() && !publicKeyType(makeSlice(spk)))
{
JLOG(ctx.j.debug()) << "preflight1: invalid signing key";
return temBAD_SIGNATURE;
}
// An AccountTxnID field constrains transaction ordering more than the
// Sequence field. Tickets, on the other hand, reduce ordering
// constraints. Because Tickets and AccountTxnID work against one
// another the combination is unsupported and treated as malformed.
//
// We return temINVALID for such transactions.
if (ctx.tx.getSeqProxy().isTicket() &&
ctx.tx.isFieldPresent(sfAccountTxnID))
return temINVALID;
return tesSUCCESS;
}
フラグのフォーマットチェック
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;
}
トランザクションのフラグ(Flags
)のチェックを行なっています。
CreateCheckトランザクションでは専用のフラグがないため、共通のフラグのみをチェックしています。
宛先アドレスのフォーマットチェック
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
{
// They wrote a check to themselves.
JLOG(ctx.j.warn()) << "Malformed transaction: Check to self.";
return temREDUNDANT;
}
Checkの宛先アドレス(Destination
)のチェックを行なっています。
Checkの宛先アドレスはトランザクション送信者自身のアドレスにすることはできません。
送信額のフォーマットチェック
{
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;
}
}
Checkの送信額(SendMax
)のチェックを行なっています。
指定したトークンのフォーマットや送信額が0以上であるかなどのチェックを行なっています。
有効期限のフォーマットチェック
if (auto const optExpiry = ctx.tx[~sfExpiration])
{
if (*optExpiry == 0)
{
JLOG(ctx.j.warn()) << "Malformed transaction: bad expiration";
return temBAD_EXPIRATION;
}
}
Checkの有効期限(Expiration
)のチェックを行なっています。
署名情報のチェック
return preflight2(ctx);
トランザクションの署名情報が正しいかチェックを行うpreflight2
関数を呼び出しています。
preflight2
NotTEC
preflight2(PreflightContext const& ctx)
{
auto const sigValid = checkValidity(
ctx.app.getHashRouter(), ctx.tx, ctx.rules, ctx.app.config());
if (sigValid.first == Validity::SigBad)
{
JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
return temINVALID;
}
return tesSUCCESS;
}
preclaim
TER
CreateCheck::preclaim(PreclaimContext const& ctx)
{
AccountID const dstId{ctx.tx[sfDestination]};
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;
// AMM can not cash the check
if (sleDst->isFieldPresent(sfAMMID))
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;
}
宛先アドレスの存在チェック
AccountID const dstId{ctx.tx[sfDestination]};
auto const sleDst = ctx.view.read(keylet::account(dstId));
if (!sleDst)
{
JLOG(ctx.j.warn()) << "Destination account does not exist.";
return tecNO_DST;
}
宛先アドレスがレジャーに存在する(該当アドレスのAccountRoot
エントリが存在する)かチェックを行なっています。
DisallowIncomingのチェック
auto const flags = sleDst->getFlags();
// Check if the destination has disallowed incoming checks
if (ctx.view.rules().enabled(featureDisallowIncoming) &&
(flags & lsfDisallowIncomingCheck))
return tecNO_PERMISSION;
宛先アカウントがCheckの受け取りを拒否している(DisallowIncomingCheck
が有効)かチェックを行なっています。
拒否している場合にはエラーとなります。
これはfeatureDisallowIncoming
Amendmentが有効な場合のみチェックされます。
AMMチェック
// AMM can not cash the check
if (sleDst->isFieldPresent(sfAMMID))
return tecNO_PERMISSION;
宛先アドレスがAMMのアドレスであるかチェックを行なっています。
AMMのアドレスの場合にはエラーとなります。
宛先アドレスに対する宛先タグの必須チェック
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;
}
}
}
}
送金額(SendMax
)について次のようなチェックを行なっています。
- グローバルフリーズ(凍結)されていないこと
- 送金元や送金先アカウントがトークンの発行者でない場合、そのトラストラインが凍結されていないこと
有効期限切れチェック
if (hasExpired(ctx.view, ctx.tx[~sfExpiration]))
{
JLOG(ctx.j.warn()) << "Creating a check that has already expired.";
return tecEXPIRED;
}
指定した有効期限が過ぎていないかチェックを行なっています。
doApply
TER
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;
}
準備金チェック
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;
}
送信先アカウントの現在のオブジェクト数+1に相当する準備金を保持していない場合にはエラーとなります。
Checkエントリの作成と登録
// 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);
次の処理を行っています。
- アカウントのシーケンス番号を取得
- Checkエントリのキー情報を取得
- Checkエントリの情報を作成
- レジャーへ登録
宛先アカウントの所有者ディレクトリへの登録
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);
}
宛先アカウントの所有者ディレクトリへCheckエントリのキー情報を登録しています。
また、登録した宛先アカウントのディレクトリ情報をCheckエントリへ追加しています。
送信元アカウントの所有者ディレクトリへの登録
{
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);
}
送信元アカウントの所有者ディレクトリへCheckエントリのキー情報を登録しています。
また、登録した送信元アカウントのディレクトリ情報をCheckエントリへ追加しています。
送信元アカウントの準備金の更新
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sle, 1, viewJ);
return tesSUCCESS;
Checkエントリが作成されたため、送信元アカウントの準備金を更新(+1)しています。
まとめ
他の多くのチェーンとは異なり、XRP Ledgerのトランザクションコードはコア実装に含まれています。
これは全てのトランザクションコードがコア開発者によってレビューされ、コードが公開されていることを意味しておりXRPLの安全性に大きくつながる特徴でもあります。
興味を持たれた方はXRP Ledger開発者のDiscordチャンネルへ是非お越しください!
日本語チャンネルもありますので、英語ができなくても大丈夫です!
Discussion