mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-04 20:05:51 +00:00
fix: mpt_issuance_id not in tx for MPTIssuanceCreate (#2630)
fixes: #2332
This commit is contained in:
@@ -210,6 +210,7 @@ TransactionFeed::pub(
|
||||
|
||||
auto& txnPubobj = pubObj[txKey].as_object();
|
||||
rpc::insertDeliverMaxAlias(txnPubobj, version);
|
||||
rpc::insertMPTIssuanceID(txnPubobj, meta);
|
||||
|
||||
Json::Value nftJson;
|
||||
ripple::RPC::insertNFTSyntheticInJson(nftJson, tx, *meta);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "web/Context.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/format/format_fwd.hpp>
|
||||
#include <boost/format/free_funcs.hpp>
|
||||
@@ -173,10 +174,7 @@ canHaveDeliveredAmount(
|
||||
if (tt != ripple::ttPAYMENT && tt != ripple::ttCHECK_CASH && tt != ripple::ttACCOUNT_DELETE)
|
||||
return false;
|
||||
|
||||
if (meta->getResultTER() != ripple::tesSUCCESS)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return meta->getResultTER() == ripple::tesSUCCESS;
|
||||
}
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
@@ -260,6 +258,7 @@ toExpandedJson(
|
||||
auto metaJson = toJson(*meta);
|
||||
insertDeliveredAmount(metaJson, txn, meta, blobs.date);
|
||||
insertDeliverMaxAlias(txnJson, apiVersion);
|
||||
insertMPTIssuanceID(txnJson, meta);
|
||||
|
||||
if (nftEnabled == NFTokenjson::ENABLE) {
|
||||
Json::Value nftJson;
|
||||
@@ -318,6 +317,66 @@ insertDeliveredAmount(
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the delivered amount
|
||||
*
|
||||
* @param meta The metadata
|
||||
* @return The mpt_issuance_id or std::nullopt if not available
|
||||
*/
|
||||
static std::optional<ripple::uint192>
|
||||
getMPTIssuanceID(std::shared_ptr<ripple::TxMeta const> const& meta)
|
||||
{
|
||||
ripple::TxMeta const& transactionMeta = *meta;
|
||||
|
||||
for (ripple::STObject const& node : transactionMeta.getNodes()) {
|
||||
if (node.getFieldU16(ripple::sfLedgerEntryType) != ripple::ltMPTOKEN_ISSUANCE ||
|
||||
node.getFName() != ripple::sfCreatedNode)
|
||||
continue;
|
||||
|
||||
auto const& mptNode = node.peekAtField(ripple::sfNewFields).downcast<ripple::STObject>();
|
||||
return ripple::makeMptID(mptNode[ripple::sfSequence], mptNode[ripple::sfIssuer]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if transaction has a new MPToken created
|
||||
*
|
||||
* @param txnJson The transaction Json
|
||||
* @param meta The metadata
|
||||
* @return true if the transaction can have a mpt_issuance_id
|
||||
*/
|
||||
static bool
|
||||
canHaveMPTIssuanceID(boost::json::object const& txnJson, std::shared_ptr<ripple::TxMeta const> const& meta)
|
||||
{
|
||||
if (txnJson.at(JS(TransactionType)).is_string() and
|
||||
not boost::iequals(txnJson.at(JS(TransactionType)).as_string(), JS(MPTokenIssuanceCreate)))
|
||||
return false;
|
||||
|
||||
if (meta->getResultTER() != ripple::tesSUCCESS)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
insertMPTIssuanceID(boost::json::object& txnJson, std::shared_ptr<ripple::TxMeta const> const& meta)
|
||||
{
|
||||
if (!canHaveMPTIssuanceID(txnJson, meta))
|
||||
return false;
|
||||
|
||||
if (txnJson.contains(JS(TransactionType)) && txnJson.at(JS(TransactionType)).is_string() and
|
||||
txnJson.at(JS(TransactionType)).as_string() == JS(MPTokenIssuanceCreate))
|
||||
return false;
|
||||
|
||||
auto const id = getMPTIssuanceID(meta);
|
||||
ASSERT(id.has_value(), "MPTIssuanceID must have value");
|
||||
txnJson[JS(mpt_issuance_id)] = ripple::to_string(*id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
insertDeliverMaxAlias(boost::json::object& txJson, std::uint32_t const apiVersion)
|
||||
{
|
||||
|
||||
@@ -199,6 +199,18 @@ insertDeliveredAmount(
|
||||
uint32_t date
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Add "mpt_issuance_id" into various MPTToken transaction json.
|
||||
* @note We exclude "mpt_issuance_id" for MPTokenIssuanceCreate only. The reason is because the mpt_issuance_id
|
||||
* is generated only after one submits MPTokenIssuanceCreate, so there’s no way to know what the id is. (rippled)
|
||||
*
|
||||
* @param txnJson The transaction Json object
|
||||
* @param meta The metadata object
|
||||
* @return true if the "mpt_issuance_id" is added to the txnJson JSON object
|
||||
*/
|
||||
bool
|
||||
insertMPTIssuanceID(boost::json::object& txnJson, std::shared_ptr<ripple::TxMeta const> const& meta);
|
||||
|
||||
/**
|
||||
* @brief Convert STBase object to JSON
|
||||
*
|
||||
|
||||
@@ -1528,6 +1528,66 @@ createMPTIssuanceCreateTxWithMetadata(std::string_view accountId, uint32_t fee,
|
||||
return ret;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
createMPTokenAuthorizeTx(
|
||||
std::string_view accountId,
|
||||
ripple::uint192 const& mptIssuanceID,
|
||||
uint32_t fee,
|
||||
uint32_t seq,
|
||||
std::optional<std::string_view> holder,
|
||||
std::optional<std::uint32_t> flags
|
||||
)
|
||||
{
|
||||
ripple::STObject tx(ripple::sfTransaction);
|
||||
tx.setFieldU16(ripple::sfTransactionType, ripple::ttMPTOKEN_AUTHORIZE);
|
||||
tx.setAccountID(ripple::sfAccount, getAccountIdWithString(accountId));
|
||||
tx[ripple::sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
tx.setFieldAmount(ripple::sfFee, ripple::STAmount(fee, false));
|
||||
tx.setFieldU32(ripple::sfSequence, seq);
|
||||
tx.setFieldVL(ripple::sfSigningPubKey, kSLICE);
|
||||
|
||||
if (holder)
|
||||
tx.setAccountID(ripple::sfHolder, getAccountIdWithString(*holder));
|
||||
if (flags)
|
||||
tx.setFieldU32(ripple::sfFlags, *flags);
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
data::TransactionAndMetadata
|
||||
createMPTokenAuthorizeTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
ripple::uint192 const& mptIssuanceID,
|
||||
uint32_t fee,
|
||||
uint32_t seq
|
||||
)
|
||||
{
|
||||
ripple::STObject const tx = createMPTokenAuthorizeTx(accountId, mptIssuanceID, fee, seq);
|
||||
|
||||
ripple::STObject metaObj(ripple::sfTransactionMetaData);
|
||||
metaObj.setFieldU8(ripple::sfTransactionResult, ripple::tesSUCCESS);
|
||||
metaObj.setFieldU32(ripple::sfTransactionIndex, 0);
|
||||
|
||||
ripple::STObject finalFields(ripple::sfFinalFields);
|
||||
finalFields.setFieldU16(ripple::sfLedgerEntryType, ripple::ltMPTOKEN);
|
||||
finalFields[ripple::sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
finalFields.setFieldU64(ripple::sfMPTAmount, 0);
|
||||
|
||||
ripple::STObject modifiedNode(ripple::sfModifiedNode);
|
||||
modifiedNode.setFieldU16(ripple::sfLedgerEntryType, ripple::ltMPTOKEN);
|
||||
modifiedNode.setFieldH256(ripple::sfLedgerIndex, ripple::uint256{});
|
||||
modifiedNode.emplace_back(std::move(finalFields));
|
||||
|
||||
ripple::STArray affectedNodes(ripple::sfAffectedNodes);
|
||||
affectedNodes.push_back(std::move(modifiedNode));
|
||||
metaObj.setFieldArray(ripple::sfAffectedNodes, affectedNodes);
|
||||
|
||||
data::TransactionAndMetadata ret;
|
||||
ret.transaction = tx.getSerializer().peekData();
|
||||
ret.metadata = metaObj.getSerializer().peekData();
|
||||
return ret;
|
||||
}
|
||||
|
||||
ripple::STObject
|
||||
createPermissionedDomainObject(
|
||||
std::string_view accountId,
|
||||
|
||||
@@ -462,6 +462,26 @@ createMPTIssuanceCreateTx(std::string_view accountId, uint32_t fee, uint32_t seq
|
||||
[[nodiscard]] data::TransactionAndMetadata
|
||||
createMPTIssuanceCreateTxWithMetadata(std::string_view accountId, uint32_t fee, uint32_t seq);
|
||||
|
||||
[[nodiscard]]
|
||||
ripple::STObject
|
||||
createMPTokenAuthorizeTx(
|
||||
std::string_view accountId,
|
||||
ripple::uint192 const& mptIssuanceID,
|
||||
uint32_t fee,
|
||||
uint32_t seq,
|
||||
std::optional<std::string_view> holder = std::nullopt,
|
||||
std::optional<std::uint32_t> flags = std::nullopt
|
||||
);
|
||||
|
||||
[[nodiscard]]
|
||||
data::TransactionAndMetadata
|
||||
createMPTokenAuthorizeTxWithMetadata(
|
||||
std::string_view accountId,
|
||||
ripple::uint192 const& mptIssuanceID,
|
||||
uint32_t fee,
|
||||
uint32_t seq
|
||||
);
|
||||
|
||||
[[nodiscard]] ripple::STObject
|
||||
createPermissionedDomainObject(
|
||||
std::string_view accountId,
|
||||
|
||||
@@ -1247,6 +1247,133 @@ TEST_F(FeedTransactionTest, PubTransactionWithOwnerFundFrozenLPToken)
|
||||
testFeedPtr->pub(trans1, ledgerHeader, backend_, mockAmendmentCenterPtr_, kNETWORK_ID);
|
||||
}
|
||||
|
||||
TEST_F(FeedTransactionTest, PublishesMPTokenIssuanceCreateTx)
|
||||
{
|
||||
constexpr auto kMPTOKEN_ISSUANCE_CREATE_TRAN_V1 =
|
||||
R"JSON({
|
||||
"transaction": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "12",
|
||||
"Sequence": 1,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "MPTokenIssuanceCreate",
|
||||
"hash": "B565E9E541E9C4615C920807AC8104D26F961424A06F3BB25A083DD47680EF45",
|
||||
"date": 0
|
||||
},
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
{
|
||||
"CreatedNode": {
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"LedgerIndex": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"NewFields": {
|
||||
"Flags": 0,
|
||||
"Issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "746573742D6D657461",
|
||||
"MaximumAmount": "0",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"PreviousTxnLgrSeq": 0,
|
||||
"Sequence": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
},
|
||||
"ctid": "C000002100000000",
|
||||
"type": "transaction",
|
||||
"validated": true,
|
||||
"status": "closed",
|
||||
"ledger_index": 33,
|
||||
"close_time_iso": "2000-01-01T00:00:00Z",
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"engine_result_code": 0,
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger."
|
||||
})JSON";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 1);
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, 33);
|
||||
auto const trans = createMPTIssuanceCreateTxWithMetadata(kACCOUNT1, 12, 1);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(sharedStringJsonEq(kMPTOKEN_ISSUANCE_CREATE_TRAN_V1)));
|
||||
|
||||
testFeedPtr->pub(trans, ledgerHeader, backend_, mockAmendmentCenterPtr_, kNETWORK_ID);
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
EXPECT_EQ(testFeedPtr->transactionSubCount(), 0);
|
||||
|
||||
testFeedPtr->pub(trans, ledgerHeader, backend_, mockAmendmentCenterPtr_, kNETWORK_ID);
|
||||
}
|
||||
|
||||
TEST_F(FeedTransactionTest, PublishesMPTokenAuthorizeTx)
|
||||
{
|
||||
constexpr auto kMPTOKEN_AUTHORIZE_TRAN_V1 =
|
||||
R"JSON({
|
||||
"transaction": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Fee": "15",
|
||||
"MPTokenIssuanceID": "000000014B4E9C06F24296074F7BC48F92A97916C6DC5EA9",
|
||||
"Sequence": 5,
|
||||
"SigningPubKey": "74657374",
|
||||
"TransactionType": "MPTokenAuthorize",
|
||||
"hash": "94ACAB5D571C4A2B8D76979B76E8A82FA91915AEB3FD0A9917223308D5EAE331",
|
||||
"date": 0
|
||||
},
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"LedgerEntryType": "MPToken",
|
||||
"MPTAmount": "0",
|
||||
"MPTokenIssuanceID": "000000014B4E9C06F24296074F7BC48F92A97916C6DC5EA9"
|
||||
},
|
||||
"LedgerEntryType": "MPToken",
|
||||
"LedgerIndex": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
},
|
||||
"ctid": "C000002100000000",
|
||||
"type": "transaction",
|
||||
"validated": true,
|
||||
"status": "closed",
|
||||
"ledger_index": 33,
|
||||
"close_time_iso": "2000-01-01T00:00:00Z",
|
||||
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"engine_result_code": 0,
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger."
|
||||
})JSON";
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, onDisconnect);
|
||||
testFeedPtr->sub(sessionPtr);
|
||||
|
||||
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, 33);
|
||||
// The issuance ID that this transaction is authorizing
|
||||
auto const mptIssuanceID = ripple::makeMptID(1, getAccountIdWithString(kACCOUNT1));
|
||||
auto const trans = createMPTokenAuthorizeTxWithMetadata(kACCOUNT1, mptIssuanceID, 15, 5);
|
||||
|
||||
EXPECT_CALL(*mockSessionPtr, apiSubversion).WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(*mockSessionPtr, send(sharedStringJsonEq(kMPTOKEN_AUTHORIZE_TRAN_V1)));
|
||||
|
||||
testFeedPtr->pub(trans, ledgerHeader, backend_, mockAmendmentCenterPtr_, kNETWORK_ID);
|
||||
|
||||
testFeedPtr->unsub(sessionPtr);
|
||||
testFeedPtr->pub(trans, ledgerHeader, backend_, mockAmendmentCenterPtr_, kNETWORK_ID);
|
||||
}
|
||||
|
||||
struct TransactionFeedMockPrometheusTest : WithMockPrometheus, SyncExecutionCtxFixture {
|
||||
protected:
|
||||
web::SubscriptionContextPtr sessionPtr_ = std::make_shared<MockSession>();
|
||||
|
||||
Reference in New Issue
Block a user