Skip to content

Commit

Permalink
feat: Support Dynamic NFT (#1525)
Browse files Browse the repository at this point in the history
Fix #1471
Clio's changes for supporting DNFT
https://github.com/XRPLF/rippled/pull/5048/files
  • Loading branch information
cindyyan317 authored Jan 31, 2025
1 parent e7702e9 commit 1753c95
Show file tree
Hide file tree
Showing 16 changed files with 951 additions and 49 deletions.
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Clio(ConanFile):
'protobuf/3.21.9',
'grpc/1.50.1',
'openssl/1.1.1u',
'xrpl/2.4.0-b1',
'xrpl/2.4.0-b3',
'zlib/1.3.1',
'libbacktrace/cci.20210118'
]
Expand Down
3 changes: 3 additions & 0 deletions src/data/AmendmentCenter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ struct Amendments {
REGISTER(fixAMMv1_2);
REGISTER(AMMClawback);
REGISTER(Credentials);
REGISTER(DynamicNFT);
// TODO: Add PermissionedDomains related RPC changes
REGISTER(PermissionedDomains);

// Obsolete but supported by libxrpl
REGISTER(CryptoConditionsSuite);
Expand Down
38 changes: 22 additions & 16 deletions src/data/CassandraBackend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,6 @@ class BasicCassandraBackend : public BackendInterface {
return seq;
}
LOG(log_.debug()) << "Could not fetch ledger object sequence - no rows";

} else {
LOG(log_.error()) << "Could not fetch ledger object sequence: " << res.error();
}
Expand Down Expand Up @@ -948,28 +947,35 @@ class BasicCassandraBackend : public BackendInterface {
statements.reserve(data.size() * 3);

for (NFTsData const& record : data) {
statements.push_back(
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
);
if (!record.onlyUriChanged) {
statements.push_back(
schema_->insertNFT.bind(record.tokenID, record.ledgerSequence, record.owner, record.isBurned)
);

// If `uri` is set (and it can be set to an empty uri), we know this
// is a net-new NFT. That is, this NFT has not been seen before by
// us _OR_ it is in the extreme edge case of a re-minted NFT ID with
// the same NFT ID as an already-burned token. In this case, we need
// to record the URI and link to the issuer_nf_tokens table.
if (record.uri) {
statements.push_back(schema_->insertIssuerNFT.bind(
ripple::nft::getIssuer(record.tokenID),
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
record.tokenID
));
// If `uri` is set (and it can be set to an empty uri), we know this
// is a net-new NFT. That is, this NFT has not been seen before by
// us _OR_ it is in the extreme edge case of a re-minted NFT ID with
// the same NFT ID as an already-burned token. In this case, we need
// to record the URI and link to the issuer_nf_tokens table.
if (record.uri) {
statements.push_back(schema_->insertIssuerNFT.bind(
ripple::nft::getIssuer(record.tokenID),
static_cast<uint32_t>(ripple::nft::getTaxon(record.tokenID)),
record.tokenID
));
statements.push_back(
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
);
}
} else {
// only uri changed, we update the uri table only
statements.push_back(
schema_->insertNFTURI.bind(record.tokenID, record.ledgerSequence, record.uri.value())
);
}
}

executor_.write(std::move(statements));
executor_.writeEach(std::move(statements));
}

void
Expand Down
18 changes: 18 additions & 0 deletions src/data/DBHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ struct NFTsData {
ripple::AccountID owner;
std::optional<ripple::Blob> uri;
bool isBurned = false;
bool onlyUriChanged = false; // Whether only the URI was changed

/**
* @brief Construct a new NFTsData object
Expand Down Expand Up @@ -170,6 +171,23 @@ struct NFTsData {
: tokenID(tokenID), ledgerSequence(ledgerSequence), owner(owner), uri(uri)
{
}

/**
* @brief Construct a new NFTsData object with only the URI changed
*
* @param tokenID The token ID
* @param meta The transaction metadata
* @param uri The new URI
*
*/
NFTsData(ripple::uint256 const& tokenID, ripple::TxMeta const& meta, ripple::Blob const& uri)
: tokenID(tokenID)
, ledgerSequence(meta.getLgrSeq())
, transactionIndex(meta.getIndex())
, uri(uri)
, onlyUriChanged(true)
{
}
};

/**
Expand Down
32 changes: 31 additions & 1 deletion src/data/cassandra/impl/ExecutionStrategy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <boost/asio/spawn.hpp>
#include <boost/json/object.hpp>

#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
Expand Down Expand Up @@ -192,10 +193,24 @@ class DefaultExecutionStrategy {
template <typename... Args>
void
write(PreparedStatementType const& preparedStatement, Args&&... args)
{
auto statement = preparedStatement.bind(std::forward<Args>(args)...);
write(std::move(statement));
}

/**
* @brief Non-blocking query execution used for writing data.
*
* Retries forever with retry policy specified by @ref AsyncExecutor
*
* @param statement Statement to execute
* @throw DatabaseTimeout on timeout
*/
void
write(StatementType&& statement)
{
auto const startTime = std::chrono::steady_clock::now();

auto statement = preparedStatement.bind(std::forward<Args>(args)...);
incrementOutstandingRequestCount();

counters_->registerWriteStarted();
Expand Down Expand Up @@ -251,6 +266,21 @@ class DefaultExecutionStrategy {
});
}

/**
* @brief Non-blocking query execution used for writing data. Constrast with write, this method does not execute
* the statements in a batch.
*
* Retries forever with retry policy specified by @ref AsyncExecutor.
*
* @param statements Vector of statements to execute
* @throw DatabaseTimeout on timeout
*/
void
writeEach(std::vector<StatementType>&& statements)
{
std::ranges::for_each(std::move(statements), [this](auto& statement) { this->write(std::move(statement)); });
}

/**
* @brief Coroutine-based query execution used for reading data.
*
Expand Down
16 changes: 15 additions & 1 deletion src/etl/NFTHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@

namespace etl {

std::pair<std::vector<NFTTransactionsData>, std::optional<NFTsData>>
getNftokenModifyData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
{
auto const tokenID = sttx.getFieldH256(ripple::sfNFTokenID);
// note: sfURI is optional, if it is absent, we will update the uri as empty string
return {
{NFTTransactionsData(sttx.getFieldH256(ripple::sfNFTokenID), txMeta, sttx.getTransactionID())},
NFTsData(tokenID, txMeta, sttx.getFieldVL(ripple::sfURI))
};
}

std::pair<std::vector<NFTTransactionsData>, std::optional<NFTsData>>
getNFTokenMintData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
{
Expand Down Expand Up @@ -166,7 +177,7 @@ getNFTokenBurnData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
node.peekAtField(ripple::sfPreviousFields).downcast<ripple::STObject>();
if (previousFields.isFieldPresent(ripple::sfNFTokens))
prevNFTs = previousFields.getFieldArray(ripple::sfNFTokens);
} else if (!prevNFTs && node.getFName() == ripple::sfDeletedNode) {
} else if (node.getFName() == ripple::sfDeletedNode) {
prevNFTs =
node.peekAtField(ripple::sfFinalFields).downcast<ripple::STObject>().getFieldArray(ripple::sfNFTokens);
}
Expand Down Expand Up @@ -336,6 +347,9 @@ getNFTDataFromTx(ripple::TxMeta const& txMeta, ripple::STTx const& sttx)
case ripple::TxType::ttNFTOKEN_CREATE_OFFER:
return getNFTokenCreateOfferData(txMeta, sttx);

case ripple::TxType::ttNFTOKEN_MODIFY:
return getNftokenModifyData(txMeta, sttx);

default:
return {{}, {}};
}
Expand Down
10 changes: 10 additions & 0 deletions src/etl/NFTHelpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@

namespace etl {

/**
* @brief Get the NFT URI change data from a NFToken Modify transaction
*
* @param txMeta Transaction metadata
* @param sttx The transaction
* @return NFT URI change data as a pair of transactions and optional NFTsData
*/
std::pair<std::vector<NFTTransactionsData>, std::optional<NFTsData>>
getNftokenModifyData(ripple::TxMeta const& txMeta, ripple::STTx const& sttx);

/**
* @brief Get the NFT Token mint data from a transaction
*
Expand Down
17 changes: 15 additions & 2 deletions src/etl/impl/LedgerLoader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct FormattedTransactionsData {
std::vector<NFTTransactionsData> nfTokenTxData;
std::vector<NFTsData> nfTokensData;
std::vector<MPTHolderData> mptHoldersData;
std::vector<NFTsData> nfTokenURIChanges;
};

namespace etl::impl {
Expand Down Expand Up @@ -111,6 +112,7 @@ class LedgerLoader {
{
FormattedTransactionsData result;

std::vector<NFTsData> nfTokenURIChanges;
for (auto& txn : *(data.mutable_transactions_list()->mutable_transactions())) {
std::string* raw = txn.mutable_transaction_blob();

Expand All @@ -123,8 +125,15 @@ class LedgerLoader {

auto const [nftTxs, maybeNFT] = getNFTDataFromTx(txMeta, sttx);
result.nfTokenTxData.insert(result.nfTokenTxData.end(), nftTxs.begin(), nftTxs.end());
if (maybeNFT)
result.nfTokensData.push_back(*maybeNFT);

// We need to unique the URI changes separately, in case the URI changes are discarded
if (maybeNFT) {
if (maybeNFT->onlyUriChanged) {
nfTokenURIChanges.push_back(*maybeNFT);
} else {
result.nfTokensData.push_back(*maybeNFT);
}
}

auto const maybeMPTHolder = getMPTHolderFromTx(txMeta, sttx);
if (maybeMPTHolder)
Expand All @@ -143,6 +152,10 @@ class LedgerLoader {
}

result.nfTokensData = getUniqueNFTsDatas(result.nfTokensData);
nfTokenURIChanges = getUniqueNFTsDatas(nfTokenURIChanges);

// Put uri change at the end to ensure the uri not overwritten
result.nfTokensData.insert(result.nfTokensData.end(), nfTokenURIChanges.begin(), nfTokenURIChanges.end());
return result;
}

Expand Down
1 change: 0 additions & 1 deletion src/etlng/impl/TaskManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include "util/async/AnyStrand.hpp"
#include "util/log/Logger.hpp"


#include <chrono>
#include <cstddef>
#include <functional>
Expand Down
Loading

0 comments on commit 1753c95

Please sign in to comment.