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