xrpld
Toggle main menu visibility
Loading...
Searching...
No Matches
libxrpl
tx
invariants
NFTInvariant.cpp
1
#include <xrpl/tx/invariants/NFTInvariant.h>
2
3
#include <xrpl/basics/Log.h>
4
#include <xrpl/basics/base_uint.h>
5
#include <xrpl/beast/utility/Journal.h>
6
#include <xrpl/beast/utility/Zero.h>
7
#include <xrpl/ledger/ReadView.h>
8
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
9
#include <xrpl/protocol/Feature.h>
10
#include <xrpl/protocol/LedgerFormats.h>
11
#include <xrpl/protocol/Protocol.h>
12
#include <xrpl/protocol/SField.h>
13
#include <xrpl/protocol/STArray.h>
14
#include <xrpl/protocol/STLedgerEntry.h>
15
#include <xrpl/protocol/STTx.h>
16
#include <xrpl/protocol/TER.h>
17
#include <xrpl/protocol/TxFormats.h>
18
#include <xrpl/protocol/XRPAmount.h>
19
#include <xrpl/protocol/nftPageMask.h>
20
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
21
22
#include <
cstddef
>
23
#include <
optional
>
24
25
namespace
xrpl
{
26
27
void
28
ValidNFTokenPage::visitEntry
(
bool
isDelete,
SLE::ConstRef
before,
SLE::ConstRef
after
)
29
{
30
static
constexpr
UInt256
const
& kPageBits =
nft::kPageMask
;
31
static
constexpr
UInt256
kAccountBits = ~kPageBits;
32
33
if
((before && before->getType() != ltNFTOKEN_PAGE) ||
34
(
after
&&
after
->getType() != ltNFTOKEN_PAGE))
35
return
;
36
37
auto
check = [
this
, isDelete](
SLE::ConstRef
sle) {
38
UInt256
const
account = sle->
key
() & kAccountBits;
39
UInt256
const
hiLimit = sle->key() & kPageBits;
40
std::optional<UInt256>
const
prev = (*sle)[~sfPreviousPageMin];
41
42
// Make sure that any page links...
43
// 1. Are properly associated with the owning account and
44
// 2. The page is correctly ordered between links.
45
if
(prev)
46
{
47
if
(account != (*prev & kAccountBits))
48
badLink_
=
true
;
49
50
if
(hiLimit <= (*prev & kPageBits))
51
badLink_
=
true
;
52
}
53
54
if
(
auto
const
next = (*sle)[~sfNextPageMin])
55
{
56
if
(account != (*next & kAccountBits))
57
badLink_
=
true
;
58
59
if
(hiLimit >= (*next & kPageBits))
60
badLink_
=
true
;
61
}
62
63
{
64
auto
const
& nftokens = sle->getFieldArray(sfNFTokens);
65
66
// An NFTokenPage should never contain too many tokens or be empty.
67
if
(
std::size_t
const
nftokenCount = nftokens.size();
68
(!isDelete && nftokenCount == 0) || nftokenCount >
kDirMaxTokensPerPage
)
69
invalidSize_
=
true
;
70
71
// If prev is valid, use it to establish a lower bound for
72
// page entries. If prev is not valid the lower bound is zero.
73
UInt256
const
loLimit = prev ? *prev & kPageBits :
UInt256
(
beast::kZero
);
74
75
// Also verify that all NFTokenIDs in the page are sorted.
76
UInt256
loCmp = loLimit;
77
for
(
auto
const
& obj : nftokens)
78
{
79
UInt256
const
tokenID = obj[sfNFTokenID];
80
if
(!
nft::compareTokens
(loCmp, tokenID))
81
badSort_
=
true
;
82
loCmp = tokenID;
83
84
// None of the NFTs on this page should belong on lower or
85
// higher pages.
86
if
(
UInt256
const
tokenPageBits = tokenID & kPageBits;
87
tokenPageBits < loLimit || tokenPageBits >= hiLimit)
88
badEntry_
=
true
;
89
90
if
(
auto
uri = obj[~sfURI]; uri && uri->empty())
91
badURI_
=
true
;
92
}
93
}
94
};
95
96
if
(before)
97
{
98
check(before);
99
100
// While an account's NFToken directory contains any NFTokens, the last
101
// NFTokenPage (with 96 bits of 1 in the low part of the index) should
102
// never be deleted.
103
if
(isDelete && (before->key() &
nft::kPageMask
) ==
nft::kPageMask
&&
104
before->isFieldPresent(sfPreviousPageMin))
105
{
106
deletedFinalPage_
=
true
;
107
}
108
}
109
110
if
(
after
)
111
check(
after
);
112
113
if
(!isDelete && before &&
after
)
114
{
115
// If the NFTokenPage
116
// 1. Has a NextMinPage field in before, but loses it in after, and
117
// 2. This is not the last page in the directory
118
// Then we have identified a corruption in the links between the
119
// NFToken pages in the NFToken directory.
120
if
((before->key() &
nft::kPageMask
) !=
nft::kPageMask
&&
121
before->isFieldPresent(sfNextPageMin) && !
after
->isFieldPresent(sfNextPageMin))
122
{
123
deletedLink_
=
true
;
124
}
125
}
126
}
127
128
bool
129
ValidNFTokenPage::finalize
(
130
STTx
const
& tx,
131
TER
const
result,
132
XRPAmount
const
,
133
ReadView
const
& view,
134
beast::Journal
const
& j)
const
135
{
136
if
(
badLink_
)
137
{
138
JLOG(j.
fatal
()) <<
"Invariant failed: NFT page is improperly linked."
;
139
return
false
;
140
}
141
142
if
(
badEntry_
)
143
{
144
JLOG(j.
fatal
()) <<
"Invariant failed: NFT found in incorrect page."
;
145
return
false
;
146
}
147
148
if
(
badSort_
)
149
{
150
JLOG(j.
fatal
()) <<
"Invariant failed: NFTs on page are not sorted."
;
151
return
false
;
152
}
153
154
if
(
badURI_
)
155
{
156
JLOG(j.
fatal
()) <<
"Invariant failed: NFT contains empty URI."
;
157
return
false
;
158
}
159
160
if
(
invalidSize_
)
161
{
162
JLOG(j.
fatal
()) <<
"Invariant failed: NFT page has invalid size."
;
163
return
false
;
164
}
165
166
if
(view.
rules
().
enabled
(fixNFTokenPageLinks))
167
{
168
if
(
deletedFinalPage_
)
169
{
170
JLOG(j.
fatal
()) <<
"Invariant failed: Last NFT page deleted with "
171
"non-empty directory."
;
172
return
false
;
173
}
174
if
(
deletedLink_
)
175
{
176
JLOG(j.
fatal
()) <<
"Invariant failed: Lost NextMinPage link."
;
177
return
false
;
178
}
179
}
180
181
return
true
;
182
}
183
184
//------------------------------------------------------------------------------
185
void
186
NFTokenCountTracking::visitEntry
(
bool
,
SLE::ConstRef
before,
SLE::ConstRef
after
)
187
{
188
if
(before && before->getType() == ltACCOUNT_ROOT)
189
{
190
beforeMintedTotal_
+= (*before)[~sfMintedNFTokens].value_or(0);
191
beforeBurnedTotal_
+= (*before)[~sfBurnedNFTokens].value_or(0);
192
}
193
194
if
(
after
&&
after
->getType() == ltACCOUNT_ROOT)
195
{
196
afterMintedTotal_
+= (*after)[~sfMintedNFTokens].value_or(0);
197
afterBurnedTotal_
+= (*after)[~sfBurnedNFTokens].value_or(0);
198
}
199
}
200
201
bool
202
NFTokenCountTracking::finalize
(
203
STTx
const
& tx,
204
TER
const
result,
205
XRPAmount
const
,
206
ReadView
const
& view,
207
beast::Journal
const
& j)
const
208
{
209
if
(!
hasPrivilege
(tx,
Privilege::ChangeNftCounts
))
210
{
211
if
(
beforeMintedTotal_
!=
afterMintedTotal_
)
212
{
213
JLOG(j.
fatal
()) <<
"Invariant failed: the number of minted tokens "
214
"changed without a mint transaction!"
;
215
return
false
;
216
}
217
218
if
(
beforeBurnedTotal_
!=
afterBurnedTotal_
)
219
{
220
JLOG(j.
fatal
()) <<
"Invariant failed: the number of burned tokens "
221
"changed without a burn transaction!"
;
222
return
false
;
223
}
224
225
return
true
;
226
}
227
228
if
(tx.
getTxnType
() == ttNFTOKEN_MINT)
229
{
230
if
(
isTesSuccess
(result) &&
beforeMintedTotal_
>=
afterMintedTotal_
)
231
{
232
JLOG(j.
fatal
()) <<
"Invariant failed: successful minting didn't increase "
233
"the number of minted tokens."
;
234
return
false
;
235
}
236
237
if
(!
isTesSuccess
(result) &&
beforeMintedTotal_
!=
afterMintedTotal_
)
238
{
239
JLOG(j.
fatal
()) <<
"Invariant failed: failed minting changed the "
240
"number of minted tokens."
;
241
return
false
;
242
}
243
244
if
(
beforeBurnedTotal_
!=
afterBurnedTotal_
)
245
{
246
JLOG(j.
fatal
()) <<
"Invariant failed: minting changed the number of "
247
"burned tokens."
;
248
return
false
;
249
}
250
}
251
252
if
(tx.
getTxnType
() == ttNFTOKEN_BURN)
253
{
254
if
(
isTesSuccess
(result))
255
{
256
if
(
beforeBurnedTotal_
>=
afterBurnedTotal_
)
257
{
258
JLOG(j.
fatal
()) <<
"Invariant failed: successful burning didn't increase "
259
"the number of burned tokens."
;
260
return
false
;
261
}
262
}
263
264
if
(!
isTesSuccess
(result) &&
beforeBurnedTotal_
!=
afterBurnedTotal_
)
265
{
266
JLOG(j.
fatal
()) <<
"Invariant failed: failed burning changed the "
267
"number of burned tokens."
;
268
return
false
;
269
}
270
271
if
(
beforeMintedTotal_
!=
afterMintedTotal_
)
272
{
273
JLOG(j.
fatal
()) <<
"Invariant failed: burning changed the number of "
274
"minted tokens."
;
275
return
false
;
276
}
277
}
278
279
return
true
;
280
}
281
282
}
// namespace xrpl
beast::Journal
A generic endpoint for log messages.
Definition
Journal.h:44
beast::Journal::fatal
Stream fatal() const
Definition
Journal.h:368
xrpl::NFTokenCountTracking::afterMintedTotal_
std::uint32_t afterMintedTotal_
Definition
NFTInvariant.h:60
xrpl::NFTokenCountTracking::visitEntry
void visitEntry(bool, SLE::ConstRef, SLE::ConstRef)
Definition
NFTInvariant.cpp:186
xrpl::NFTokenCountTracking::beforeBurnedTotal_
std::uint32_t beforeBurnedTotal_
Definition
NFTInvariant.h:59
xrpl::NFTokenCountTracking::finalize
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
Definition
NFTInvariant.cpp:202
xrpl::NFTokenCountTracking::beforeMintedTotal_
std::uint32_t beforeMintedTotal_
Definition
NFTInvariant.h:58
xrpl::NFTokenCountTracking::afterBurnedTotal_
std::uint32_t afterBurnedTotal_
Definition
NFTInvariant.h:61
xrpl::ReadView
A view into a ledger.
Definition
ReadView.h:41
xrpl::ReadView::rules
virtual Rules const & rules() const =0
Returns the tx processing rules.
xrpl::Rules::enabled
bool enabled(UInt256 const &feature) const
Returns true if a feature is enabled.
Definition
Rules.cpp:182
xrpl::STLedgerEntry::ConstRef
std::shared_ptr< STLedgerEntry const > const & ConstRef
Definition
STLedgerEntry.h:37
xrpl::STTx
Definition
STTx.h:43
xrpl::STTx::getTxnType
TxType getTxnType() const
Definition
STTx.h:250
xrpl::ValidNFTokenPage::deletedFinalPage_
bool deletedFinalPage_
Definition
NFTInvariant.h:32
xrpl::ValidNFTokenPage::deletedLink_
bool deletedLink_
Definition
NFTInvariant.h:33
xrpl::ValidNFTokenPage::badURI_
bool badURI_
Definition
NFTInvariant.h:30
xrpl::ValidNFTokenPage::visitEntry
void visitEntry(bool, SLE::ConstRef, SLE::ConstRef)
Definition
NFTInvariant.cpp:28
xrpl::ValidNFTokenPage::badSort_
bool badSort_
Definition
NFTInvariant.h:29
xrpl::ValidNFTokenPage::badLink_
bool badLink_
Definition
NFTInvariant.h:28
xrpl::ValidNFTokenPage::invalidSize_
bool invalidSize_
Definition
NFTInvariant.h:31
xrpl::ValidNFTokenPage::badEntry_
bool badEntry_
Definition
NFTInvariant.h:27
xrpl::ValidNFTokenPage::finalize
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
Definition
NFTInvariant.cpp:129
xrpl::XRPAmount
Definition
XRPAmount.h:28
cstddef
beast::kZero
constexpr Zero kZero
Definition
Zero.h:30
xrpl::nft::compareTokens
bool compareTokens(UInt256 const &a, UInt256 const &b)
Definition
NFTokenHelpers.cpp:215
xrpl::nft::kPageMask
constexpr UInt256 kPageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
xrpl
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition
algorithm.h:5
xrpl::Privilege::ChangeNftCounts
@ ChangeNftCounts
Definition
TxSettings.h:28
xrpl::UInt256
BaseUInt< 256 > UInt256
Definition
base_uint.h:580
xrpl::hasPrivilege
bool hasPrivilege(STTx const &tx, Privilege priv)
Definition
InvariantCheck.cpp:56
xrpl::after
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition
View.cpp:644
xrpl::kDirMaxTokensPerPage
constexpr std::size_t kDirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition
Protocol.h:70
xrpl::isTesSuccess
bool isTesSuccess(TER x) noexcept
Definition
TER.h:683
xrpl::TER
TERSubset< CanCvtToTER > TER
Definition
TER.h:654
optional
std::size_t
xrpl::Keylet::key
UInt256 key
Definition
Keylet.h:21
Generated by
1.17.0