rippled
Loading...
Searching...
No Matches
PaymentSandbox.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/paths/detail/AmountSpec.h>
21#include <xrpld/ledger/PaymentSandbox.h>
22#include <xrpld/ledger/View.h>
23#include <xrpl/beast/utility/instrumentation.h>
24#include <xrpl/protocol/SField.h>
25
26namespace ripple {
27
28namespace detail {
29
30auto
32 AccountID const& a1,
33 AccountID const& a2,
34 Currency const& c) -> Key
35{
36 if (a1 < a2)
37 return std::make_tuple(a1, a2, c);
38 else
39 return std::make_tuple(a2, a1, c);
40}
41
42void
44 AccountID const& sender,
45 AccountID const& receiver,
46 STAmount const& amount,
47 STAmount const& preCreditSenderBalance)
48{
49 XRPL_ASSERT(
50 sender != receiver,
51 "ripple::detail::DeferredCredits::credit : sender is not receiver");
52 XRPL_ASSERT(
53 !amount.negative(),
54 "ripple::detail::DeferredCredits::credit : positive amount");
55
56 auto const k = makeKey(sender, receiver, amount.getCurrency());
57 auto i = credits_.find(k);
58 if (i == credits_.end())
59 {
60 Value v;
61
62 if (sender < receiver)
63 {
64 v.highAcctCredits = amount;
65 v.lowAcctCredits = amount.zeroed();
66 v.lowAcctOrigBalance = preCreditSenderBalance;
67 }
68 else
69 {
70 v.highAcctCredits = amount.zeroed();
71 v.lowAcctCredits = amount;
72 v.lowAcctOrigBalance = -preCreditSenderBalance;
73 }
74
75 credits_[k] = v;
76 }
77 else
78 {
79 // only record the balance the first time, do not record it here
80 auto& v = i->second;
81 if (sender < receiver)
82 v.highAcctCredits += amount;
83 else
84 v.lowAcctCredits += amount;
85 }
86}
87
88void
90 AccountID const& id,
91 std::uint32_t cur,
92 std::uint32_t next)
93{
94 auto const v = std::max(cur, next);
95 auto r = ownerCounts_.emplace(std::make_pair(id, v));
96 if (!r.second)
97 {
98 auto& mapVal = r.first->second;
99 mapVal = std::max(v, mapVal);
100 }
101}
102
105{
106 auto i = ownerCounts_.find(id);
107 if (i != ownerCounts_.end())
108 return i->second;
109 return std::nullopt;
110}
111
112// Get the adjustments for the balance between main and other.
113auto
115 AccountID const& main,
116 AccountID const& other,
117 Currency const& currency) const -> std::optional<Adjustment>
118{
120
121 Key const k = makeKey(main, other, currency);
122 auto i = credits_.find(k);
123 if (i == credits_.end())
124 return result;
125
126 auto const& v = i->second;
127
128 if (main < other)
129 {
130 result.emplace(
131 v.highAcctCredits, v.lowAcctCredits, v.lowAcctOrigBalance);
132 return result;
133 }
134 else
135 {
136 result.emplace(
137 v.lowAcctCredits, v.highAcctCredits, -v.lowAcctOrigBalance);
138 return result;
139 }
140}
141
142void
144{
145 for (auto const& i : credits_)
146 {
147 auto r = to.credits_.emplace(i);
148 if (!r.second)
149 {
150 auto& toVal = r.first->second;
151 auto const& fromVal = i.second;
152 toVal.lowAcctCredits += fromVal.lowAcctCredits;
153 toVal.highAcctCredits += fromVal.highAcctCredits;
154 // Do not update the orig balance, it's already correct
155 }
156 }
157
158 for (auto const& i : ownerCounts_)
159 {
160 auto r = to.ownerCounts_.emplace(i);
161 if (!r.second)
162 {
163 auto& toVal = r.first->second;
164 auto const& fromVal = i.second;
165 toVal = std::max(toVal, fromVal);
166 }
167 }
168}
169
170} // namespace detail
171
174 AccountID const& account,
175 AccountID const& issuer,
176 STAmount const& amount) const
177{
178 /*
179 There are two algorithms here. The pre-switchover algorithm takes the
180 current amount and subtracts the recorded credits. The post-switchover
181 algorithm remembers the original balance, and subtracts the debits. The
182 post-switchover algorithm should be more numerically stable. Consider a
183 large credit with a small initial balance. The pre-switchover algorithm
184 computes (B+C)-C (where B+C will the amount passed in). The
185 post-switchover algorithm returns B. When B and C differ by large
186 magnitudes, (B+C)-C may not equal B.
187 */
188
189 auto const currency = amount.getCurrency();
190
191 auto delta = amount.zeroed();
192 auto lastBal = amount;
193 auto minBal = amount;
194 for (auto curSB = this; curSB; curSB = curSB->ps_)
195 {
196 if (auto adj = curSB->tab_.adjustments(account, issuer, currency))
197 {
198 delta += adj->debits;
199 lastBal = adj->origBalance;
200 if (lastBal < minBal)
201 minBal = lastBal;
202 }
203 }
204
205 // The adjusted amount should never be larger than the balance. In
206 // some circumstances, it is possible for the deferred credits table
207 // to compute usable balance just slightly above what the ledger
208 // calculates (but always less than the actual balance).
209 auto adjustedAmt = std::min({amount, lastBal - delta, minBal});
210 adjustedAmt.setIssuer(amount.getIssuer());
211
212 if (isXRP(issuer) && adjustedAmt < beast::zero)
213 // A calculated negative XRP balance is not an error case. Consider a
214 // payment snippet that credits a large XRP amount and then debits the
215 // same amount. The credit can't be used but we subtract the debit and
216 // calculate a negative value. It's not an error case.
217 adjustedAmt.clear();
218
219 return adjustedAmt;
220}
221
224 const
225{
226 std::uint32_t result = count;
227 for (auto curSB = this; curSB; curSB = curSB->ps_)
228 {
229 if (auto adj = curSB->tab_.ownerCount(account))
230 result = std::max(result, *adj);
231 }
232 return result;
233}
234
235void
237 AccountID const& from,
238 AccountID const& to,
239 STAmount const& amount,
240 STAmount const& preCreditBalance)
241{
242 tab_.credit(from, to, amount, preCreditBalance);
243}
244
245void
247 AccountID const& account,
248 std::uint32_t cur,
249 std::uint32_t next)
250{
251 tab_.ownerCount(account, cur, next);
252}
253
254void
256{
257 XRPL_ASSERT(!ps_, "ripple::PaymentSandbox::apply : non-null sandbox");
258 items_.apply(to);
259}
260
261void
263{
264 XRPL_ASSERT(ps_ == &to, "ripple::PaymentSandbox::apply : matching sandbox");
265 items_.apply(to);
266 tab_.apply(to.tab_);
267}
268
271{
273 // Map of delta trust lines. As a special case, when both ends of the trust
274 // line are the same currency, then it's delta currency for that issuer. To
275 // get the change in XRP balance, Account == root, issuer == root, currency
276 // == XRP
278
279 // populate a dictionary with low/high/currency/delta. This can be
280 // compared with the other versions payment code.
281 auto each = [&result](
282 uint256 const& key,
283 bool isDelete,
284 std::shared_ptr<SLE const> const& before,
286 STAmount oldBalance;
287 STAmount newBalance;
288 AccountID lowID;
289 AccountID highID;
290
291 // before is read from prev view
292 if (isDelete)
293 {
294 if (!before)
295 return;
296
297 auto const bt = before->getType();
298 switch (bt)
299 {
300 case ltACCOUNT_ROOT:
301 lowID = xrpAccount();
302 highID = (*before)[sfAccount];
303 oldBalance = (*before)[sfBalance];
304 newBalance = oldBalance.zeroed();
305 break;
306 case ltRIPPLE_STATE:
307 lowID = (*before)[sfLowLimit].getIssuer();
308 highID = (*before)[sfHighLimit].getIssuer();
309 oldBalance = (*before)[sfBalance];
310 newBalance = oldBalance.zeroed();
311 break;
312 case ltOFFER:
313 // TBD
314 break;
315 default:
316 break;
317 }
318 }
319 else if (!before)
320 {
321 // insert
322 auto const at = after->getType();
323 switch (at)
324 {
325 case ltACCOUNT_ROOT:
326 lowID = xrpAccount();
327 highID = (*after)[sfAccount];
328 newBalance = (*after)[sfBalance];
329 oldBalance = newBalance.zeroed();
330 break;
331 case ltRIPPLE_STATE:
332 lowID = (*after)[sfLowLimit].getIssuer();
333 highID = (*after)[sfHighLimit].getIssuer();
334 newBalance = (*after)[sfBalance];
335 oldBalance = newBalance.zeroed();
336 break;
337 case ltOFFER:
338 // TBD
339 break;
340 default:
341 break;
342 }
343 }
344 else
345 {
346 // modify
347 auto const at = after->getType();
348 XRPL_ASSERT(
349 at == before->getType(),
350 "ripple::PaymentSandbox::balanceChanges : after and before "
351 "types matching");
352 switch (at)
353 {
354 case ltACCOUNT_ROOT:
355 lowID = xrpAccount();
356 highID = (*after)[sfAccount];
357 oldBalance = (*before)[sfBalance];
358 newBalance = (*after)[sfBalance];
359 break;
360 case ltRIPPLE_STATE:
361 lowID = (*after)[sfLowLimit].getIssuer();
362 highID = (*after)[sfHighLimit].getIssuer();
363 oldBalance = (*before)[sfBalance];
364 newBalance = (*after)[sfBalance];
365 break;
366 case ltOFFER:
367 // TBD
368 break;
369 default:
370 break;
371 }
372 }
373 // The following are now set, put them in the map
374 auto delta = newBalance - oldBalance;
375 auto const cur = newBalance.getCurrency();
376 result[std::make_tuple(lowID, highID, cur)] = delta;
377 auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta);
378 if (r.second)
379 {
380 r.first->second += delta;
381 }
382
383 delta.negate();
384 r = result.emplace(std::make_tuple(highID, highID, cur), delta);
385 if (r.second)
386 {
387 r.first->second += delta;
388 }
389 };
390 items_.visit(view, each);
391 return result;
392}
393
396{
397 return items_.dropsDestroyed();
398}
399
400} // 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:34
A view into a ledger.
Definition: ReadView.h:51
Currency const & getCurrency() const
Definition: STAmount.h:493
bool negative() const noexcept
Definition: STAmount.h:462
AccountID const & getIssuer() const
Definition: STAmount.h:499
STAmount zeroed() const
Returns a zero value with the same issuer and currency.
Definition: STAmount.h:511
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 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:26
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:178
static bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition: Escrow.cpp:85