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