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