rippled
NFTokenID.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2023 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/rpc/NFTokenID.h>
21 
22 #include <ripple/app/ledger/LedgerMaster.h>
23 #include <ripple/app/ledger/OpenLedger.h>
24 #include <ripple/app/misc/Transaction.h>
25 #include <ripple/ledger/View.h>
26 #include <ripple/net/RPCErr.h>
27 #include <ripple/protocol/AccountID.h>
28 #include <ripple/protocol/Feature.h>
29 #include <ripple/rpc/Context.h>
30 #include <ripple/rpc/impl/RPCHelpers.h>
31 #include <boost/algorithm/string/case_conv.hpp>
32 
33 namespace ripple {
34 namespace RPC {
35 
36 bool
38  std::shared_ptr<STTx const> const& serializedTx,
39  TxMeta const& transactionMeta)
40 {
41  if (!serializedTx)
42  return false;
43 
44  TxType const tt = serializedTx->getTxnType();
45  if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER &&
47  return false;
48 
49  // if the transaction failed nothing could have been delivered.
50  if (transactionMeta.getResultTER() != tesSUCCESS)
51  return false;
52 
53  return true;
54 }
55 
57 getNFTokenIDFromPage(TxMeta const& transactionMeta)
58 {
59  // The metadata does not make it obvious which NFT was added. To figure
60  // that out we gather up all of the previous NFT IDs and all of the final
61  // NFT IDs and compare them to find what changed.
62  std::vector<uint256> prevIDs;
63  std::vector<uint256> finalIDs;
64 
65  for (STObject const& node : transactionMeta.getNodes())
66  {
67  if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE)
68  continue;
69 
70  SField const& fName = node.getFName();
71  if (fName == sfCreatedNode)
72  {
73  STArray const& toAddPrevNFTs = node.peekAtField(sfNewFields)
74  .downcast<STObject>()
75  .getFieldArray(sfNFTokens);
77  toAddPrevNFTs.begin(),
78  toAddPrevNFTs.end(),
79  std::back_inserter(finalIDs),
80  [](STObject const& nft) {
81  return nft.getFieldH256(sfNFTokenID);
82  });
83  }
84  else if (fName == sfModifiedNode)
85  {
86  // When a mint results in splitting an existing page,
87  // it results in a created page and a modified node. Sometimes,
88  // the created node needs to be linked to a third page, resulting
89  // in modifying that third page's PreviousPageMin or NextPageMin
90  // field changing, but no NFTs within that page changing. In this
91  // case, there will be no previous NFTs and we need to skip.
92  // However, there will always be NFTs listed in the final fields,
93  // as rippled outputs all fields in final fields even if they were
94  // not changed.
95  STObject const& previousFields =
97  if (!previousFields.isFieldPresent(sfNFTokens))
98  continue;
99 
100  STArray const& toAddPrevNFTs =
101  previousFields.getFieldArray(sfNFTokens);
103  toAddPrevNFTs.begin(),
104  toAddPrevNFTs.end(),
105  std::back_inserter(prevIDs),
106  [](STObject const& nft) {
107  return nft.getFieldH256(sfNFTokenID);
108  });
109 
110  STArray const& toAddFinalNFTs = node.peekAtField(sfFinalFields)
111  .downcast<STObject>()
112  .getFieldArray(sfNFTokens);
114  toAddFinalNFTs.begin(),
115  toAddFinalNFTs.end(),
116  std::back_inserter(finalIDs),
117  [](STObject const& nft) {
118  return nft.getFieldH256(sfNFTokenID);
119  });
120  }
121  }
122 
123  // We expect NFTs to be added one at a time. So finalIDs should be one
124  // longer than prevIDs. If that's not the case something is messed up.
125  if (finalIDs.size() != prevIDs.size() + 1)
126  return std::nullopt;
127 
128  // Find the first NFT ID that doesn't match. We're looking for an
129  // added NFT, so the one we want will be the mismatch in finalIDs.
130  auto const diff = std::mismatch(
131  finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
132 
133  // There should always be a difference so the returned finalIDs
134  // iterator should never be end(). But better safe than sorry.
135  if (diff.first == finalIDs.end())
136  return std::nullopt;
137 
138  return *diff.first;
139 }
140 
142 getNFTokenIDFromDeletedOffer(TxMeta const& transactionMeta)
143 {
144  std::vector<uint256> tokenIDResult;
145  for (STObject const& node : transactionMeta.getNodes())
146  {
147  if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER ||
148  node.getFName() != sfDeletedNode)
149  continue;
150 
151  auto const& toAddNFT = node.peekAtField(sfFinalFields)
152  .downcast<STObject>()
153  .getFieldH256(sfNFTokenID);
154  tokenIDResult.push_back(toAddNFT);
155  }
156 
157  // Deduplicate the NFT IDs because multiple offers could affect the same NFT
158  // and hence we would get duplicate NFT IDs
159  sort(tokenIDResult.begin(), tokenIDResult.end());
160  tokenIDResult.erase(
161  unique(tokenIDResult.begin(), tokenIDResult.end()),
162  tokenIDResult.end());
163  return tokenIDResult;
164 }
165 
166 void
168  Json::Value& response,
169  std::shared_ptr<STTx const> const& transaction,
170  TxMeta const& transactionMeta)
171 {
172  if (!canHaveNFTokenID(transaction, transactionMeta))
173  return;
174 
175  // We extract the NFTokenID from metadata by comparing affected nodes
176  if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
177  {
178  std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
179  if (result.has_value())
180  response[jss::nftoken_id] = to_string(result.value());
181  }
182  else if (type == ttNFTOKEN_ACCEPT_OFFER)
183  {
184  std::vector<uint256> result =
185  getNFTokenIDFromDeletedOffer(transactionMeta);
186 
187  if (result.size() > 0)
188  response[jss::nftoken_id] = to_string(result.front());
189  }
190  else if (type == ttNFTOKEN_CANCEL_OFFER)
191  {
192  std::vector<uint256> result =
193  getNFTokenIDFromDeletedOffer(transactionMeta);
194 
195  response[jss::nftoken_ids] = Json::Value(Json::arrayValue);
196  for (auto const& nftID : result)
197  response[jss::nftoken_ids].append(to_string(nftID));
198  }
199 }
200 
201 } // namespace RPC
202 } // namespace ripple
ripple::STObject::peekAtField
const STBase & peekAtField(SField const &field) const
Definition: STObject.cpp:373
std::optional::has_value
T has_value(T... args)
ripple::STObject::getFieldArray
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:624
std::shared_ptr
STL class.
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
std::vector
STL class.
std::vector::size
T size(T... args)
std::back_inserter
T back_inserter(T... args)
ripple::TxType
TxType
Transaction type identifiers.
Definition: TxFormats.h:56
ripple::sfFinalFields
const SField sfFinalFields
ripple::ttNFTOKEN_ACCEPT_OFFER
@ ttNFTOKEN_ACCEPT_OFFER
This transaction accepts an existing offer to buy or sell an existing NFT.
Definition: TxFormats.h:140
ripple::sfDeletedNode
const SField sfDeletedNode
std::vector::front
T front(T... args)
ripple::RPC::getNFTokenIDFromDeletedOffer
std::vector< uint256 > getNFTokenIDFromDeletedOffer(TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:142
std::vector::push_back
T push_back(T... args)
ripple::TxMeta
Definition: TxMeta.h:32
std::mismatch
T mismatch(T... args)
ripple::RPC::getNFTokenIDFromPage
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:57
ripple::TxMeta::getResultTER
TER getResultTER() const
Definition: TxMeta.h:68
ripple::RPC::insertNFTokenID
void insertNFTokenID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:167
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::sfNewFields
const SField sfNewFields
ripple::ltNFTOKEN_OFFER
@ ltNFTOKEN_OFFER
A ledger object which identifies an offer to buy or sell an NFT.
Definition: LedgerFormats.h:162
ripple::ttNFTOKEN_MINT
@ ttNFTOKEN_MINT
This transaction mints a new NFT.
Definition: TxFormats.h:128
ripple::STArray
Definition: STArray.h:28
ripple::sfModifiedNode
const SField sfModifiedNode
std::vector::erase
T erase(T... args)
std::transform
T transform(T... args)
ripple::ttNFTOKEN_CANCEL_OFFER
@ ttNFTOKEN_CANCEL_OFFER
This transaction cancels an existing offer to buy or sell an existing NFT.
Definition: TxFormats.h:137
ripple::RPC::canHaveNFTokenID
bool canHaveNFTokenID(std::shared_ptr< STTx const > const &serializedTx, TxMeta const &transactionMeta)
Add a nftoken_ids field to the meta output parameter.
Definition: NFTokenID.cpp:37
ripple::sfPreviousFields
const SField sfPreviousFields
ripple::STArray::begin
iterator begin()
Definition: STArray.h:224
ripple::sfNFTokens
const SField sfNFTokens
std::optional::value
T value(T... args)
ripple::STObject
Definition: STObject.h:51
ripple::ltNFTOKEN_PAGE
@ ltNFTOKEN_PAGE
A ledger object which contains a list of NFTs.
Definition: LedgerFormats.h:156
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::TxMeta::getNodes
STArray & getNodes()
Definition: TxMeta.h:100
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
ripple::SField
Identifies fields.
Definition: SField.h:112
std::vector::begin
T begin(T... args)
ripple::STObject::isFieldPresent
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:428
ripple::sfCreatedNode
const SField sfCreatedNode
std::optional
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
std::vector::end
T end(T... args)
ripple::STBase::downcast
D & downcast()
Definition: STBase.h:145
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:222
ripple::STArray::end
iterator end()
Definition: STArray.h:230
Json::Value
Represents a JSON value.
Definition: json_value.h:145