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