rippled
AccountTxPaging.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/ledger/LedgerMaster.h>
21 #include <ripple/app/ledger/LedgerToJson.h>
22 #include <ripple/app/main/Application.h>
23 #include <ripple/app/misc/Transaction.h>
24 #include <ripple/app/misc/impl/AccountTxPaging.h>
25 #include <ripple/protocol/Serializer.h>
26 #include <ripple/protocol/UintTypes.h>
27 #include <boost/format.hpp>
28 #include <memory>
29 
30 namespace ripple {
31 
32 void
35  std::uint32_t ledger_index,
36  std::string const& status,
37  Blob const& rawTxn,
38  Blob const& rawMeta,
39  Application& app)
40 {
41  SerialIter it(makeSlice(rawTxn));
42  auto txn = std::make_shared<STTx const>(it);
43  std::string reason;
44 
45  auto tr = std::make_shared<Transaction>(txn, reason, app);
46 
47  tr->setStatus(Transaction::sqlTransactionStatus(status));
48  tr->setLedger(ledger_index);
49 
50  auto metaset =
51  std::make_shared<TxMeta>(tr->getID(), tr->getLedger(), rawMeta);
52 
53  to.emplace_back(std::move(tr), metaset);
54 };
55 
56 void
58 {
59  if (auto l = app.getLedgerMaster().getLedgerBySeq(seq))
60  pendSaveValidated(app, l, false, false);
61 }
62 
63 void
65  DatabaseCon& connection,
66  AccountIDCache const& idCache,
67  std::function<void(std::uint32_t)> const& onUnsavedLedger,
69  void(std::uint32_t, std::string const&, Blob&&, Blob&&)> const&
70  onTransaction,
71  AccountID const& account,
72  std::int32_t minLedger,
73  std::int32_t maxLedger,
74  bool forward,
76  int limit,
77  bool bAdmin,
78  std::uint32_t page_length)
79 {
80  bool lookingForMarker = marker.has_value();
81 
82  std::uint32_t numberOfResults;
83 
84  if (limit <= 0 || (limit > page_length && !bAdmin))
85  numberOfResults = page_length;
86  else
87  numberOfResults = limit;
88 
89  // As an account can have many thousands of transactions, there is a limit
90  // placed on the amount of transactions returned. If the limit is reached
91  // before the result set has been exhausted (we always query for one more
92  // than the limit), then we return an opaque marker that can be supplied in
93  // a subsequent query.
94  std::uint32_t queryLimit = numberOfResults + 1;
95  std::uint32_t findLedger = 0, findSeq = 0;
96 
97  if (lookingForMarker)
98  {
99  findLedger = marker->ledgerSeq;
100  findSeq = marker->txnSeq;
101  }
102 
103  // marker is also an output parameter, so need to reset
104  marker.reset();
105 
106  static std::string const prefix(
107  R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
108  Status,RawTxn,TxnMeta
109  FROM AccountTransactions INNER JOIN Transactions
110  ON Transactions.TransID = AccountTransactions.TransID
111  AND AccountTransactions.Account = '%s' WHERE
112  )");
113 
114  std::string sql;
115 
116  // SQL's BETWEEN uses a closed interval ([a,b])
117 
118  if (forward && (findLedger == 0))
119  {
120  sql = boost::str(
121  boost::format(
122  prefix + (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u'
123  ORDER BY AccountTransactions.LedgerSeq ASC,
124  AccountTransactions.TxnSeq ASC
125  LIMIT %u;)")) %
126  idCache.toBase58(account) % minLedger % maxLedger % queryLimit);
127  }
128  else if (forward && (findLedger != 0))
129  {
130  auto b58acct = idCache.toBase58(account);
131  sql = boost::str(
132  boost::format((
133  R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
134  Status,RawTxn,TxnMeta
135  FROM AccountTransactions, Transactions WHERE
136  (AccountTransactions.TransID = Transactions.TransID AND
137  AccountTransactions.Account = '%s' AND
138  AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u')
139  OR
140  (AccountTransactions.TransID = Transactions.TransID AND
141  AccountTransactions.Account = '%s' AND
142  AccountTransactions.LedgerSeq = '%u' AND
143  AccountTransactions.TxnSeq >= '%u')
144  ORDER BY AccountTransactions.LedgerSeq ASC,
145  AccountTransactions.TxnSeq ASC
146  LIMIT %u;
147  )")) %
148  b58acct % (findLedger + 1) % maxLedger % b58acct % findLedger %
149  findSeq % queryLimit);
150  }
151  else if (!forward && (findLedger == 0))
152  {
153  sql = boost::str(
154  boost::format(
155  prefix + (R"(AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u'
156  ORDER BY AccountTransactions.LedgerSeq DESC,
157  AccountTransactions.TxnSeq DESC
158  LIMIT %u;)")) %
159  idCache.toBase58(account) % minLedger % maxLedger % queryLimit);
160  }
161  else if (!forward && (findLedger != 0))
162  {
163  auto b58acct = idCache.toBase58(account);
164  sql = boost::str(
165  boost::format((
166  R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq,
167  Status,RawTxn,TxnMeta
168  FROM AccountTransactions, Transactions WHERE
169  (AccountTransactions.TransID = Transactions.TransID AND
170  AccountTransactions.Account = '%s' AND
171  AccountTransactions.LedgerSeq BETWEEN '%u' AND '%u')
172  OR
173  (AccountTransactions.TransID = Transactions.TransID AND
174  AccountTransactions.Account = '%s' AND
175  AccountTransactions.LedgerSeq = '%u' AND
176  AccountTransactions.TxnSeq <= '%u')
177  ORDER BY AccountTransactions.LedgerSeq DESC,
178  AccountTransactions.TxnSeq DESC
179  LIMIT %u;
180  )")) %
181  b58acct % minLedger % (findLedger - 1) % b58acct % findLedger %
182  findSeq % queryLimit);
183  }
184  else
185  {
186  assert(false);
187  // sql is empty
188  return;
189  }
190 
191  {
192  auto db(connection.checkoutDb());
193 
194  Blob rawData;
195  Blob rawMeta;
196 
197  boost::optional<std::uint64_t> ledgerSeq;
198  boost::optional<std::uint32_t> txnSeq;
199  boost::optional<std::string> status;
200  soci::blob txnData(*db);
201  soci::blob txnMeta(*db);
202  soci::indicator dataPresent, metaPresent;
203 
204  soci::statement st =
205  (db->prepare << sql,
206  soci::into(ledgerSeq),
207  soci::into(txnSeq),
208  soci::into(status),
209  soci::into(txnData, dataPresent),
210  soci::into(txnMeta, metaPresent));
211 
212  st.execute();
213 
214  while (st.fetch())
215  {
216  if (lookingForMarker)
217  {
218  if (findLedger == ledgerSeq.value_or(0) &&
219  findSeq == txnSeq.value_or(0))
220  {
221  lookingForMarker = false;
222  }
223  }
224  else if (numberOfResults == 0)
225  {
226  marker = {
227  rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
228  txnSeq.value_or(0)};
229  break;
230  }
231 
232  if (!lookingForMarker)
233  {
234  if (dataPresent == soci::i_ok)
235  convert(txnData, rawData);
236  else
237  rawData.clear();
238 
239  if (metaPresent == soci::i_ok)
240  convert(txnMeta, rawMeta);
241  else
242  rawMeta.clear();
243 
244  // Work around a bug that could leave the metadata missing
245  if (rawMeta.size() == 0)
246  onUnsavedLedger(ledgerSeq.value_or(0));
247 
248  // `rawData` and `rawMeta` will be used after they are moved.
249  // That's OK.
250  onTransaction(
251  rangeCheckedCast<std::uint32_t>(ledgerSeq.value_or(0)),
252  *status,
253  std::move(rawData),
254  std::move(rawMeta));
255  // Note some callbacks will move the data, some will not. Clear
256  // them so code doesn't depend on if the data was actually moved
257  // or not. The code will be more efficient if `rawData` and
258  // `rawMeta` don't have to allocate in `convert`, so don't
259  // refactor my moving these variables into loop scope.
260  rawData.clear();
261  rawMeta.clear();
262 
263  --numberOfResults;
264  }
265  }
266  }
267 
268  return;
269 }
270 } // namespace ripple
ripple::Transaction::sqlTransactionStatus
static TransStatus sqlTransactionStatus(boost::optional< std::string > const &status)
Definition: Transaction.cpp:66
ripple::Application
Definition: Application.h:97
std::optional::has_value
T has_value(T... args)
ripple::makeSlice
std::enable_if_t< std::is_same< T, char >::value||std::is_same< T, unsigned char >::value, Slice > makeSlice(std::array< T, N > const &a)
Definition: Slice.h:240
ripple::accountTxPage
void accountTxPage(DatabaseCon &connection, AccountIDCache const &idCache, std::function< void(std::uint32_t)> const &onUnsavedLedger, std::function< void(std::uint32_t, std::string const &, Blob &&, Blob &&)> const &onTransaction, AccountID const &account, std::int32_t minLedger, std::int32_t maxLedger, bool forward, std::optional< NetworkOPs::AccountTxMarker > &marker, int limit, bool bAdmin, std::uint32_t page_length)
Definition: AccountTxPaging.cpp:64
std::string
STL class.
ripple::convert
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition: SociDB.cpp:156
std::vector
STL class.
std::vector::size
T size(T... args)
std::optional::value_or
T value_or(T... args)
std::function
ripple::convertBlobsToTxResult
void convertBlobsToTxResult(NetworkOPs::AccountTxs &to, std::uint32_t ledger_index, std::string const &status, Blob const &rawTxn, Blob const &rawMeta, Application &app)
Definition: AccountTxPaging.cpp:33
std::optional::reset
T reset(T... args)
ripple::AccountIDCache
Caches the base58 representations of AccountIDs.
Definition: AccountID.h:118
std::vector::clear
T clear(T... args)
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:73
ripple::DatabaseCon::checkoutDb
LockedSociSession checkoutDb()
Definition: DatabaseCon.h:176
ripple::Application::getLedgerMaster
virtual LedgerMaster & getLedgerMaster()=0
ripple::saveLedgerAsync
void saveLedgerAsync(Application &app, std::uint32_t seq)
Definition: AccountTxPaging.cpp:57
ripple::LedgerMaster::getLedgerBySeq
std::shared_ptr< Ledger const > getLedgerBySeq(std::uint32_t index)
Definition: LedgerMaster.cpp:1668
ripple::SerialIter
Definition: Serializer.h:308
std::uint32_t
memory
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::DatabaseCon
Definition: DatabaseCon.h:81
std::optional
ripple::pendSaveValidated
bool pendSaveValidated(Application &app, std::shared_ptr< Ledger const > const &ledger, bool isSynchronous, bool isCurrent)
Save, or arrange to save, a fully-validated ledger Returns false on error.
Definition: Ledger.cpp:1061
ripple::AccountIDCache::toBase58
std::string toBase58(AccountID const &) const
Return ripple::toBase58 for the AccountID.
Definition: AccountID.cpp:134