xrpld
Loading...
Searching...
No Matches
FreezeInvariant.cpp
1#include <xrpl/tx/invariants/FreezeInvariant.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/ledger/ReadView.h>
7#include <xrpl/protocol/AccountID.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/Indexes.h>
10#include <xrpl/protocol/Issue.h>
11#include <xrpl/protocol/LedgerFormats.h>
12#include <xrpl/protocol/SField.h>
13#include <xrpl/protocol/STLedgerEntry.h>
14#include <xrpl/protocol/STTx.h>
15#include <xrpl/protocol/TER.h>
16#include <xrpl/protocol/XRPAmount.h>
17#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
18
19#include <utility>
20
21namespace xrpl {
22
23void
25{
26 /*
27 * A trust line freeze state alone doesn't determine if a transfer is
28 * frozen. The transfer must be examined "end-to-end" because both sides of
29 * the transfer may have different freeze states and freeze impact depends
30 * on the transfer direction. This is why first we need to track the
31 * transfers using IssuerChanges senders/receivers.
32 *
33 * Only in validateIssuerChanges, after we collected all changes can we
34 * determine if the transfer is valid.
35 */
36 if (!isValidEntry(before, after))
37 {
38 return;
39 }
40
41 auto const balanceChange = calculateBalanceChange(before, after, isDelete);
42 if (balanceChange.signum() == 0)
43 {
44 return;
45 }
46
47 recordBalanceChanges(after, balanceChange);
48}
49
50bool
52 STTx const& tx,
53 TER const ter,
54 XRPAmount const fee,
55 ReadView const& view,
56 beast::Journal const& j)
57{
58 /*
59 * We check this invariant regardless of deep freeze amendment status,
60 * allowing for detection and logging of potential issues even when the
61 * amendment is disabled.
62 *
63 * If an exploit that allows moving frozen assets is discovered,
64 * we can alert operators who monitor fatal messages and trigger assert in
65 * debug builds for an early warning.
66 *
67 * In an unlikely event that an exploit is found, this early detection
68 * enables encouraging the UNL to expedite deep freeze amendment activation
69 * or deploy hotfixes via new amendments. In case of a new amendment, we'd
70 * only have to change this line setting 'enforce' variable.
71 * enforce = view.rules().enabled(featureDeepFreeze) ||
72 * view.rules().enabled(fixFreezeExploit);
73 */
74 [[maybe_unused]] bool const enforce = view.rules().enabled(featureDeepFreeze);
75
76 for (auto const& [issue, changes] : balanceChanges_)
77 {
78 auto const issuerSle = findIssuer(issue.account, view);
79 // It should be impossible for the issuer to not be found, but check
80 // just in case so xrpld doesn't crash in release.
81 if (!issuerSle)
82 {
83 // The comment above starting with "assert(enforce)" explains this
84 // assert.
85 XRPL_ASSERT(
86 enforce,
87 "xrpl::TransfersNotFrozen::finalize : enforce "
88 "invariant.");
89 if (enforce)
90 {
91 return false;
92 }
93 continue;
94 }
95
96 if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
97 {
98 return false;
99 }
100 }
101
102 return true;
103}
104
105bool
107{
108 // `after` can never be null, even if the trust line is deleted.
109 XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after.");
110 if (!after)
111 {
112 return false;
113 }
114
115 if (after->getType() == ltACCOUNT_ROOT)
116 {
117 possibleIssuers_.emplace(after->at(sfAccount), after);
118 return false;
119 }
120
121 /* While LedgerEntryTypesMatch invariant also checks types, all invariants
122 * are processed regardless of previous failures.
123 *
124 * This type check is still necessary here because it prevents potential
125 * issues in subsequent processing.
126 */
127 return after->getType() == ltRIPPLE_STATE && (!before || before->getType() == ltRIPPLE_STATE);
128}
129
132 SLE::const_ref before,
134 bool isDelete)
135{
136 auto const getBalance = [](auto const& line, auto const& other, bool zero) {
137 STAmount const amt = line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
138 return zero ? amt.zeroed() : amt;
139 };
140
141 /* Trust lines can be created dynamically by other transactions such as
142 * Payment and OfferCreate that cross offers. Such trust line won't be
143 * created frozen, but the sender might be, so the starting balance must be
144 * treated as zero.
145 */
146 auto const balanceBefore = getBalance(before, after, false);
147
148 /* Same as above, trust lines can be dynamically deleted, and for frozen
149 * trust lines, payments not involving the issuer must be blocked. This is
150 * achieved by treating the final balance as zero when isDelete=true to
151 * ensure frozen line restrictions are enforced even during deletion.
152 */
153 auto const balanceAfter = getBalance(after, before, isDelete);
154
155 return balanceAfter - balanceBefore;
156}
157
158void
160{
161 XRPL_ASSERT(
162 change.balanceChangeSign,
163 "xrpl::TransfersNotFrozen::recordBalance : valid trustline "
164 "balance sign.");
165 auto& changes = balanceChanges_[issue];
166 if (change.balanceChangeSign < 0)
167 {
168 changes.senders.emplace_back(std::move(change));
169 }
170 else
171 {
172 changes.receivers.emplace_back(std::move(change));
173 }
174}
175
176void
178{
179 auto const balanceChangeSign = balanceChange.signum();
180 auto const currency = after->at(sfBalance).get<Issue>().currency;
181
182 // Change from low account's perspective, which is trust line default
184 {currency, after->at(sfHighLimit).getIssuer()},
185 {.line = after, .balanceChangeSign = balanceChangeSign});
186
187 // Change from high account's perspective, which reverses the sign.
189 {currency, after->at(sfLowLimit).getIssuer()},
190 {.line = after, .balanceChangeSign = -balanceChangeSign});
191}
192
195{
196 if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
197 {
198 return it->second;
199 }
200
201 return view.read(keylet::account(issuerID));
202}
203
204bool
206 SLE::const_ref issuer,
207 IssuerChanges const& changes,
208 STTx const& tx,
209 beast::Journal const& j,
210 bool enforce)
211{
212 if (!issuer)
213 {
214 return false;
215 }
216
217 bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
218 if (changes.receivers.empty() || changes.senders.empty())
219 {
220 /* If there are no receivers, then the holder(s) are returning
221 * their tokens to the issuer. Likewise, if there are no
222 * senders, then the issuer is issuing tokens to the holder(s).
223 * This is allowed regardless of the issuer's freeze flags. (The
224 * holder may have contradicting freeze flags, but that will be
225 * checked when the holder is treated as issuer.)
226 */
227 return true;
228 }
229
230 for (auto const& actors : {changes.senders, changes.receivers})
231 {
232 for (auto const& change : actors)
233 {
234 bool const high = change.line->at(sfLowLimit).getIssuer() == issuer->at(sfAccount);
235
236 if (!validateFrozenState(change, high, tx, j, enforce, globalFreeze))
237 {
238 return false;
239 }
240 }
241 }
242 return true;
243}
244
245bool
247 BalanceChange const& change,
248 bool high,
249 STTx const& tx,
250 beast::Journal const& j,
251 bool enforce,
252 bool globalFreeze)
253{
254 bool const freeze =
255 change.balanceChangeSign < 0 && change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
256 bool const deepFreeze = change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
257 bool const frozen = globalFreeze || deepFreeze || freeze;
258
259 bool const isAMMLine = change.line->isFlag(lsfAMMNode);
260
261 if (!frozen)
262 {
263 return true;
264 }
265
266 // AMMClawbacks are allowed to override some freeze rules
267 if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, OverrideFreeze))
268 {
269 JLOG(j.debug()) << "Invariant check allowing funds to be moved "
270 << (change.balanceChangeSign > 0 ? "to" : "from")
271 << " a frozen trustline for AMMClawback " << tx.getTransactionID();
272 return true;
273 }
274
275 JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
276 << tx.getTransactionID();
277 // The comment above starting with "assert(enforce)" explains this assert.
278 XRPL_ASSERT(
279 enforce,
280 "xrpl::TransfersNotFrozen::validateFrozenState : enforce "
281 "invariant.");
282
283 return !enforce;
284}
285
286} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream debug() const
Definition Journal.h:297
A currency issued by an account.
Definition Issue.h:13
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
int signum() const noexcept
Definition STAmount.h:504
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:512
std::shared_ptr< STLedgerEntry const > const & const_ref
std::shared_ptr< STLedgerEntry const > const_pointer
uint256 getTransactionID() const
Definition STTx.h:200
void recordBalance(Issue const &issue, BalanceChange change)
std::map< AccountID, SLE::const_pointer const > possibleIssuers_
static STAmount calculateBalanceChange(SLE::const_ref before, SLE::const_ref after, bool isDelete)
void recordBalanceChanges(SLE::const_ref after, STAmount const &balanceChange)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
static bool validateFrozenState(BalanceChange const &change, bool high, STTx const &tx, beast::Journal const &j, bool enforce, bool globalFreeze)
bool isValidEntry(SLE::const_ref before, SLE::const_ref after)
static bool validateIssuerChanges(SLE::const_ref issuer, IssuerChanges const &changes, STTx const &tx, beast::Journal const &j, bool enforce)
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
SLE::const_pointer findIssuer(AccountID const &issuerID, ReadView const &view)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
TERSubset< CanCvtToTER > TER
Definition TER.h:634
std::vector< BalanceChange > senders
std::vector< BalanceChange > receivers