rippled
Loading...
Searching...
No Matches
PaymentSandbox.cpp
1#include <xrpl/beast/utility/instrumentation.h>
2#include <xrpl/ledger/PaymentSandbox.h>
3#include <xrpl/ledger/View.h>
4#include <xrpl/protocol/SField.h>
5
6namespace xrpl {
7
8namespace detail {
9
10auto
11DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency const& c) -> Key
12{
13 if (a1 < a2)
14 {
15 return std::make_tuple(a1, a2, c);
16 }
17
18 return std::make_tuple(a2, a1, c);
19}
20
21void
23 AccountID const& sender,
24 AccountID const& receiver,
25 STAmount const& amount,
26 STAmount const& preCreditSenderBalance)
27{
28 XRPL_ASSERT(
29 sender != receiver, "xrpl::detail::DeferredCredits::credit : sender is not receiver");
30 XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::credit : positive amount");
31
32 auto const k = makeKey(sender, receiver, amount.getCurrency());
33 auto i = credits_.find(k);
34 if (i == credits_.end())
35 {
36 Value v;
37
38 if (sender < receiver)
39 {
40 v.highAcctCredits = amount;
41 v.lowAcctCredits = amount.zeroed();
42 v.lowAcctOrigBalance = preCreditSenderBalance;
43 }
44 else
45 {
46 v.highAcctCredits = amount.zeroed();
47 v.lowAcctCredits = amount;
48 v.lowAcctOrigBalance = -preCreditSenderBalance;
49 }
50
51 credits_[k] = v;
52 }
53 else
54 {
55 // only record the balance the first time, do not record it here
56 auto& v = i->second;
57 if (sender < receiver)
58 {
59 v.highAcctCredits += amount;
60 }
61 else
62 {
63 v.lowAcctCredits += amount;
64 }
65 }
66}
67
68void
70{
71 auto const v = std::max(cur, next);
72 auto r = ownerCounts_.emplace(std::make_pair(id, v));
73 if (!r.second)
74 {
75 auto& mapVal = r.first->second;
76 mapVal = std::max(v, mapVal);
77 }
78}
79
82{
83 auto i = ownerCounts_.find(id);
84 if (i != ownerCounts_.end())
85 return i->second;
86 return std::nullopt;
87}
88
89// Get the adjustments for the balance between main and other.
90auto
92 AccountID const& main,
93 AccountID const& other,
94 Currency const& currency) const -> std::optional<Adjustment>
95{
97
98 Key const k = makeKey(main, other, currency);
99 auto i = credits_.find(k);
100 if (i == credits_.end())
101 return result;
102
103 auto const& v = i->second;
104
105 if (main < other)
106 {
107 result.emplace(v.highAcctCredits, v.lowAcctCredits, v.lowAcctOrigBalance);
108 return result;
109 }
110
111 result.emplace(v.lowAcctCredits, v.highAcctCredits, -v.lowAcctOrigBalance);
112 return result;
113}
114
115void
117{
118 for (auto const& i : credits_)
119 {
120 auto r = to.credits_.emplace(i);
121 if (!r.second)
122 {
123 auto& toVal = r.first->second;
124 auto const& fromVal = i.second;
125 toVal.lowAcctCredits += fromVal.lowAcctCredits;
126 toVal.highAcctCredits += fromVal.highAcctCredits;
127 // Do not update the orig balance, it's already correct
128 }
129 }
130
131 for (auto const& i : ownerCounts_)
132 {
133 auto r = to.ownerCounts_.emplace(i);
134 if (!r.second)
135 {
136 auto& toVal = r.first->second;
137 auto const& fromVal = i.second;
138 toVal = std::max(toVal, fromVal);
139 }
140 }
141}
142
143} // namespace detail
144
147 AccountID const& account,
148 AccountID const& issuer,
149 STAmount const& amount) const
150{
151 /*
152 There are two algorithms here. The pre-switchover algorithm takes the
153 current amount and subtracts the recorded credits. The post-switchover
154 algorithm remembers the original balance, and subtracts the debits. The
155 post-switchover algorithm should be more numerically stable. Consider a
156 large credit with a small initial balance. The pre-switchover algorithm
157 computes (B+C)-C (where B+C will the amount passed in). The
158 post-switchover algorithm returns B. When B and C differ by large
159 magnitudes, (B+C)-C may not equal B.
160 */
161
162 auto const currency = amount.getCurrency();
163
164 auto delta = amount.zeroed();
165 auto lastBal = amount;
166 auto minBal = amount;
167 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
168 {
169 if (auto adj = curSB->tab_.adjustments(account, issuer, currency))
170 {
171 delta += adj->debits;
172 lastBal = adj->origBalance;
173 if (lastBal < minBal)
174 minBal = lastBal;
175 }
176 }
177
178 // The adjusted amount should never be larger than the balance. In
179 // some circumstances, it is possible for the deferred credits table
180 // to compute usable balance just slightly above what the ledger
181 // calculates (but always less than the actual balance).
182 auto adjustedAmt = std::min({amount, lastBal - delta, minBal});
183 adjustedAmt.setIssuer(amount.getIssuer());
184
185 if (isXRP(issuer) && adjustedAmt < beast::zero)
186 {
187 // A calculated negative XRP balance is not an error case. Consider a
188 // payment snippet that credits a large XRP amount and then debits the
189 // same amount. The credit can't be used but we subtract the debit and
190 // calculate a negative value. It's not an error case.
191 adjustedAmt.clear();
192 }
193
194 return adjustedAmt;
195}
196
199{
200 std::uint32_t result = count;
201 for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
202 {
203 if (auto adj = curSB->tab_.ownerCount(account))
204 result = std::max(result, *adj);
205 }
206 return result;
207}
208
209void
211 AccountID const& from,
212 AccountID const& to,
213 STAmount const& amount,
214 STAmount const& preCreditBalance)
215{
216 tab_.credit(from, to, amount, preCreditBalance);
217}
218
219void
221 AccountID const& account,
222 std::uint32_t cur,
223 std::uint32_t next)
224{
225 tab_.ownerCount(account, cur, next);
226}
227
228void
230{
231 XRPL_ASSERT(!ps_, "xrpl::PaymentSandbox::apply : non-null sandbox");
232 items_.apply(to);
233}
234
235void
237{
238 XRPL_ASSERT(ps_ == &to, "xrpl::PaymentSandbox::apply : matching sandbox");
239 items_.apply(to);
240 tab_.apply(to.tab_);
241}
242
245{
247 // Map of delta trust lines. As a special case, when both ends of the trust
248 // line are the same currency, then it's delta currency for that issuer. To
249 // get the change in XRP balance, Account == root, issuer == root, currency
250 // == XRP
252
253 // populate a dictionary with low/high/currency/delta. This can be
254 // compared with the other versions payment code.
255 auto each = [&result](
256 uint256 const& key,
257 bool isDelete,
258 std::shared_ptr<SLE const> const& before,
260 STAmount oldBalance;
261 STAmount newBalance;
262 AccountID lowID;
263 AccountID highID;
264
265 // before is read from prev view
266 if (isDelete)
267 {
268 if (!before)
269 return;
270
271 auto const bt = before->getType();
272 switch (bt)
273 {
274 case ltACCOUNT_ROOT:
275 lowID = xrpAccount();
276 highID = (*before)[sfAccount];
277 oldBalance = (*before)[sfBalance];
278 newBalance = oldBalance.zeroed();
279 break;
280 case ltRIPPLE_STATE:
281 lowID = (*before)[sfLowLimit].getIssuer();
282 highID = (*before)[sfHighLimit].getIssuer();
283 oldBalance = (*before)[sfBalance];
284 newBalance = oldBalance.zeroed();
285 break;
286 case ltOFFER:
287 // TBD
288 break;
289 default:
290 break;
291 }
292 }
293 else if (!before)
294 {
295 // insert
296 auto const at = after->getType();
297 switch (at)
298 {
299 case ltACCOUNT_ROOT:
300 lowID = xrpAccount();
301 highID = (*after)[sfAccount];
302 newBalance = (*after)[sfBalance];
303 oldBalance = newBalance.zeroed();
304 break;
305 case ltRIPPLE_STATE:
306 lowID = (*after)[sfLowLimit].getIssuer();
307 highID = (*after)[sfHighLimit].getIssuer();
308 newBalance = (*after)[sfBalance];
309 oldBalance = newBalance.zeroed();
310 break;
311 case ltOFFER:
312 // TBD
313 break;
314 default:
315 break;
316 }
317 }
318 else
319 {
320 // modify
321 auto const at = after->getType();
322 XRPL_ASSERT(
323 at == before->getType(),
324 "xrpl::PaymentSandbox::balanceChanges : after and before "
325 "types matching");
326 switch (at)
327 {
328 case ltACCOUNT_ROOT:
329 lowID = xrpAccount();
330 highID = (*after)[sfAccount];
331 oldBalance = (*before)[sfBalance];
332 newBalance = (*after)[sfBalance];
333 break;
334 case ltRIPPLE_STATE:
335 lowID = (*after)[sfLowLimit].getIssuer();
336 highID = (*after)[sfHighLimit].getIssuer();
337 oldBalance = (*before)[sfBalance];
338 newBalance = (*after)[sfBalance];
339 break;
340 case ltOFFER:
341 // TBD
342 break;
343 default:
344 break;
345 }
346 }
347 // The following are now set, put them in the map
348 auto delta = newBalance - oldBalance;
349 auto const cur = newBalance.getCurrency();
350 result[std::make_tuple(lowID, highID, cur)] = delta;
351 auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
352 if (r.second)
353 {
354 r.first->second += delta;
355 }
356
357 delta.negate();
358 r = result.emplace(std::make_tuple(highID, highID, cur), delta);
359 if (r.second)
360 {
361 r.first->second += delta;
362 }
363 };
364 items_.visit(view, each);
365 return result;
366}
367
370{
371 return items_.dropsDestroyed();
372}
373
374} // namespace xrpl
A wrapper which makes credits unavailable to balances.
STAmount balanceHook(AccountID const &account, AccountID const &issuer, STAmount const &amount) const override
void adjustOwnerCountHook(AccountID const &account, std::uint32_t cur, std::uint32_t next) override
PaymentSandbox const * ps_
void apply(RawView &to)
Apply changes to base view.
detail::DeferredCredits tab_
std::uint32_t ownerCountHook(AccountID const &account, std::uint32_t count) const override
void creditHook(AccountID const &from, AccountID const &to, STAmount const &amount, STAmount const &preCreditBalance) override
XRPAmount xrpDestroyed() const
std::map< std::tuple< AccountID, AccountID, Currency >, STAmount > balanceChanges(ReadView const &view) const
Interface for ledger entry changes.
Definition RawView.h:14
A view into a ledger.
Definition ReadView.h:31
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition STAmount.h:494
Currency const & getCurrency() const
Definition STAmount.h:476
void visit(ReadView const &base, std::function< void(uint256 const &key, bool isDelete, std::shared_ptr< SLE const > const &before, std::shared_ptr< SLE const > const &after)> const &func) const
XRPAmount const & dropsDestroyed() const
void apply(RawView &to) const
detail::ApplyStateTable items_
void apply(DeferredCredits &to)
std::optional< Adjustment > adjustments(AccountID const &main, AccountID const &other, Currency const &currency) const
std::map< AccountID, std::uint32_t > ownerCounts_
void credit(AccountID const &sender, AccountID const &receiver, STAmount const &amount, STAmount const &preCreditSenderBalance)
std::map< Key, Value > credits_
void ownerCount(AccountID const &id, std::uint32_t cur, std::uint32_t next)
static Key makeKey(AccountID const &a1, AccountID const &a2, Currency const &c)
T emplace(T... args)
T is_same_v
T make_pair(T... args)
T make_tuple(T... args)
T max(T... args)
T min(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool isXRP(AccountID const &c)
Definition AccountID.h:70
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:523
AccountID const & xrpAccount()
Compute AccountID from public key.