rippled
Loading...
Searching...
No Matches
CashCheck.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2017 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/ledger/Ledger.h>
21#include <xrpld/app/paths/Flow.h>
22#include <xrpld/app/tx/detail/CashCheck.h>
23
24#include <xrpl/basics/Log.h>
25#include <xrpl/basics/scope.h>
26#include <xrpl/protocol/Feature.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFlags.h>
30
31#include <algorithm>
32
33namespace ripple {
34
37{
38 // Exactly one of Amount or DeliverMin must be present.
39 auto const optAmount = ctx.tx[~sfAmount];
40 auto const optDeliverMin = ctx.tx[~sfDeliverMin];
41
42 if (static_cast<bool>(optAmount) == static_cast<bool>(optDeliverMin))
43 {
44 JLOG(ctx.j.warn())
45 << "Malformed transaction: "
46 "does not specify exactly one of Amount and DeliverMin.";
47 return temMALFORMED;
48 }
49
50 // Make sure the amount is valid.
51 STAmount const value{optAmount ? *optAmount : *optDeliverMin};
52 if (!isLegalNet(value) || value.signum() <= 0)
53 {
54 JLOG(ctx.j.warn()) << "Malformed transaction: bad amount: "
55 << value.getFullText();
56 return temBAD_AMOUNT;
57 }
58
59 if (badCurrency() == value.getCurrency())
60 {
61 JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency.";
62 return temBAD_CURRENCY;
63 }
64
65 return tesSUCCESS;
66}
67
68TER
70{
71 auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
72 if (!sleCheck)
73 {
74 JLOG(ctx.j.warn()) << "Check does not exist.";
75 return tecNO_ENTRY;
76 }
77
78 // Only cash a check with this account as the destination.
79 AccountID const dstId = sleCheck->at(sfDestination);
80 if (ctx.tx[sfAccount] != dstId)
81 {
82 JLOG(ctx.j.warn()) << "Cashing a check with wrong Destination.";
83 return tecNO_PERMISSION;
84 }
85 AccountID const srcId = sleCheck->at(sfAccount);
86 if (srcId == dstId)
87 {
88 // They wrote a check to themselves. This should be caught when
89 // the check is created, but better late than never.
90 // LCOV_EXCL_START
91 JLOG(ctx.j.error()) << "Malformed transaction: Cashing check to self.";
92 return tecINTERNAL;
93 // LCOV_EXCL_STOP
94 }
95 {
96 auto const sleSrc = ctx.view.read(keylet::account(srcId));
97 auto const sleDst = ctx.view.read(keylet::account(dstId));
98 if (!sleSrc || !sleDst)
99 {
100 // If the check exists this should never occur.
101 JLOG(ctx.j.warn())
102 << "Malformed transaction: source or destination not in ledger";
103 return tecNO_ENTRY;
104 }
105
106 if ((sleDst->getFlags() & lsfRequireDestTag) &&
107 !sleCheck->isFieldPresent(sfDestinationTag))
108 {
109 // The tag is basically account-specific information we don't
110 // understand, but we can require someone to fill it in.
111 JLOG(ctx.j.warn())
112 << "Malformed transaction: DestinationTag required in check.";
113 return tecDST_TAG_NEEDED;
114 }
115 }
116
117 if (hasExpired(ctx.view, sleCheck->at(~sfExpiration)))
118 {
119 JLOG(ctx.j.warn()) << "Cashing a check that has already expired.";
120 return tecEXPIRED;
121 }
122
123 {
124 // Preflight verified exactly one of Amount or DeliverMin is present.
125 // Make sure the requested amount is reasonable.
126 STAmount const value{[](STTx const& tx) {
127 auto const optAmount = tx[~sfAmount];
128 return optAmount ? *optAmount : tx[sfDeliverMin];
129 }(ctx.tx)};
130
131 STAmount const sendMax = sleCheck->at(sfSendMax);
132 Currency const currency{value.getCurrency()};
133 if (currency != sendMax.getCurrency())
134 {
135 JLOG(ctx.j.warn()) << "Check cash does not match check currency.";
136 return temMALFORMED;
137 }
138 AccountID const issuerId{value.getIssuer()};
139 if (issuerId != sendMax.getIssuer())
140 {
141 JLOG(ctx.j.warn()) << "Check cash does not match check issuer.";
142 return temMALFORMED;
143 }
144 if (value > sendMax)
145 {
146 JLOG(ctx.j.warn()) << "Check cashed for more than check sendMax.";
147 return tecPATH_PARTIAL;
148 }
149
150 // Make sure the check owner holds at least value. If they have
151 // less than value the check cannot be cashed.
152 {
153 STAmount availableFunds{accountFunds(
154 ctx.view,
155 sleCheck->at(sfAccount),
156 value,
158 ctx.j)};
159
160 // Note that src will have one reserve's worth of additional XRP
161 // once the check is cashed, since the check's reserve will no
162 // longer be required. So, if we're dealing in XRP, we add one
163 // reserve's worth to the available funds.
164 if (value.native())
165 availableFunds += XRPAmount{ctx.view.fees().increment};
166
167 if (value > availableFunds)
168 {
169 JLOG(ctx.j.warn())
170 << "Check cashed for more than owner's balance.";
171 return tecPATH_PARTIAL;
172 }
173 }
174
175 // An issuer can always accept their own currency.
176 if (!value.native() && (value.getIssuer() != dstId))
177 {
178 auto const sleTrustLine =
179 ctx.view.read(keylet::line(dstId, issuerId, currency));
180
181 if (!sleTrustLine &&
182 !ctx.view.rules().enabled(featureCheckCashMakesTrustLine))
183 {
184 JLOG(ctx.j.warn())
185 << "Cannot cash check for IOU without trustline.";
186 return tecNO_LINE;
187 }
188
189 auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
190 if (!sleIssuer)
191 {
192 JLOG(ctx.j.warn())
193 << "Can't receive IOUs from non-existent issuer: "
194 << to_string(issuerId);
195 return tecNO_ISSUER;
196 }
197
198 if (sleIssuer->at(sfFlags) & lsfRequireAuth)
199 {
200 if (!sleTrustLine)
201 {
202 // We can only create a trust line if the issuer does not
203 // have lsfRequireAuth set.
204 return tecNO_AUTH;
205 }
206
207 // Entries have a canonical representation, determined by a
208 // lexicographical "greater than" comparison employing strict
209 // weak ordering. Determine which entry we need to access.
210 bool const canonical_gt(dstId > issuerId);
211
212 bool const is_authorized(
213 sleTrustLine->at(sfFlags) &
214 (canonical_gt ? lsfLowAuth : lsfHighAuth));
215
216 if (!is_authorized)
217 {
218 JLOG(ctx.j.warn())
219 << "Can't receive IOUs from issuer without auth.";
220 return tecNO_AUTH;
221 }
222 }
223
224 // The trustline from source to issuer does not need to
225 // be checked for freezing, since we already verified that the
226 // source has sufficient non-frozen funds available.
227
228 // However, the trustline from destination to issuer may not
229 // be frozen.
230 if (isFrozen(ctx.view, dstId, currency, issuerId))
231 {
232 JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
233 return tecFROZEN;
234 }
235 }
236 }
237 return tesSUCCESS;
238}
239
240TER
242{
243 // Flow requires that we operate on a PaymentSandbox, rather than
244 // directly on a View.
245 PaymentSandbox psb(&ctx_.view());
246
247 auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
248 if (!sleCheck)
249 {
250 // LCOV_EXCL_START
251 JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
253 // LCOV_EXCL_STOP
254 }
255
256 AccountID const srcId{sleCheck->getAccountID(sfAccount)};
257 if (!psb.exists(keylet::account(srcId)) ||
259 {
260 // LCOV_EXCL_START
261 JLOG(ctx_.journal.fatal())
262 << "Precheck did not verify source or destination's existence.";
264 // LCOV_EXCL_STOP
265 }
266
267 // Preclaim already checked that source has at least the requested
268 // funds.
269 //
270 // Therefore, if this is a check written to self, (and it shouldn't be)
271 // we know they have sufficient funds to pay the check. Since they are
272 // taking the funds from their own pocket and putting it back in their
273 // pocket no balance will change.
274 //
275 // If it is not a check to self (as should be the case), then there's
276 // work to do...
277 auto viewJ = ctx_.app.journal("View");
278 auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
279
280 if (srcId != account_)
281 {
282 STAmount const sendMax = sleCheck->at(sfSendMax);
283
284 // Flow() doesn't do XRP to XRP transfers.
285 if (sendMax.native())
286 {
287 // Here we need to calculate the amount of XRP src can send.
288 // The amount they have available is their balance minus their
289 // reserve.
290 //
291 // Since (if we're successful) we're about to remove an entry
292 // from src's directory, we allow them to send that additional
293 // incremental reserve amount in the transfer. Hence the -1
294 // argument.
295 STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
296
297 // Now, how much do they need in order to be successful?
298 STAmount const xrpDeliver{
299 optDeliverMin
300 ? std::max(*optDeliverMin, std::min(sendMax, srcLiquid))
301 : ctx_.tx.getFieldAmount(sfAmount)};
302
303 if (srcLiquid < xrpDeliver)
304 {
305 // Vote no. However the transaction might succeed if applied
306 // in a different order.
307 JLOG(j_.trace()) << "Cash Check: Insufficient XRP: "
308 << srcLiquid.getFullText() << " < "
309 << xrpDeliver.getFullText();
310 return tecUNFUNDED_PAYMENT;
311 }
312
313 if (optDeliverMin)
314 // Set the DeliveredAmount metadata.
315 ctx_.deliver(xrpDeliver);
316
317 // The source account has enough XRP so make the ledger change.
318 if (TER const ter{
319 transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
320 ter != tesSUCCESS)
321 {
322 // The transfer failed. Return the error code.
323 return ter;
324 }
325 }
326 else
327 {
328 // Note that for DeliverMin we don't know exactly how much
329 // currency we want flow to deliver. We can't ask for the
330 // maximum possible currency because there might be a gateway
331 // transfer rate to account for. Since the transfer rate cannot
332 // exceed 200%, we use 1/2 maxValue as our limit.
333 STAmount const flowDeliver{
334 optDeliverMin ? STAmount(
335 optDeliverMin->issue(),
338 : ctx_.tx.getFieldAmount(sfAmount)};
339
340 // If a trust line does not exist yet create one.
341 Issue const& trustLineIssue = flowDeliver.issue();
342 AccountID const issuer = flowDeliver.getIssuer();
343 AccountID const truster = issuer == account_ ? srcId : account_;
344 Keylet const trustLineKey = keylet::line(truster, trustLineIssue);
345 bool const destLow = issuer > account_;
346
347 bool const checkCashMakesTrustLine =
348 psb.rules().enabled(featureCheckCashMakesTrustLine);
349
350 if (checkCashMakesTrustLine && !psb.exists(trustLineKey))
351 {
352 // 1. Can the check casher meet the reserve for the trust line?
353 // 2. Create trust line between destination (this) account
354 // and the issuer.
355 // 3. Apply correct noRipple settings on trust line. Use...
356 // a. this (destination) account and
357 // b. issuing account (not sending account).
358
359 auto const sleDst = psb.peek(keylet::account(account_));
360
361 // Can the account cover the trust line's reserve?
362 if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
363 mPriorBalance < psb.fees().accountReserve(ownerCount + 1))
364 {
365 JLOG(j_.trace()) << "Trust line does not exist. "
366 "Insufficent reserve to create line.";
367
369 }
370
371 Currency const currency = flowDeliver.getCurrency();
372 STAmount initialBalance(flowDeliver.issue());
373 initialBalance.setIssuer(noAccount());
374
375 // clang-format off
376 if (TER const ter = trustCreate(
377 psb, // payment sandbox
378 destLow, // is dest low?
379 issuer, // source
380 account_, // destination
381 trustLineKey.key, // ledger index
382 sleDst, // Account to add to
383 false, // authorize account
384 (sleDst->getFlags() & lsfDefaultRipple) == 0,
385 false, // freeze trust line
386 false, // deep freeze trust line
387 initialBalance, // zero initial balance
388 Issue(currency, account_), // limit of zero
389 0, // quality in
390 0, // quality out
391 viewJ); // journal
392 !isTesSuccess(ter))
393 {
394 return ter;
395 }
396 // clang-format on
397
398 psb.update(sleDst);
399
400 // Note that we _don't_ need to be careful about destroying
401 // the trust line if the check cashing fails. The transaction
402 // machinery will automatically clean it up.
403 }
404
405 // Since the destination is signing the check, they clearly want
406 // the funds even if their new total funds would exceed the limit
407 // on their trust line. So we tweak the trust line limits before
408 // calling flow and then restore the trust line limits afterwards.
409 auto const sleTrustLine = psb.peek(trustLineKey);
410 if (!sleTrustLine)
411 return tecNO_LINE;
412
413 SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit;
414 STAmount const savedLimit = sleTrustLine->at(tweakedLimit);
415
416 // Make sure the tweaked limits are restored when we leave scope.
417 scope_exit fixup(
418 [&psb, &trustLineKey, &tweakedLimit, &savedLimit]() {
419 if (auto const sleTrustLine = psb.peek(trustLineKey))
420 sleTrustLine->at(tweakedLimit) = savedLimit;
421 });
422
423 if (checkCashMakesTrustLine)
424 {
425 // Set the trust line limit to the highest possible value
426 // while flow runs.
427 STAmount const bigAmount(
429 sleTrustLine->at(tweakedLimit) = bigAmount;
430 }
431
432 // Let flow() do the heavy lifting on a check for an IOU.
433 auto const result = flow(
434 psb,
435 flowDeliver,
436 srcId,
437 account_,
438 STPathSet{},
439 true, // default path
440 static_cast<bool>(optDeliverMin), // partial payment
441 true, // owner pays transfer fee
444 sleCheck->getFieldAmount(sfSendMax),
445 std::nullopt, // check does not support domain
446 viewJ);
447
448 if (result.result() != tesSUCCESS)
449 {
450 JLOG(ctx_.journal.warn()) << "flow failed when cashing check.";
451 return result.result();
452 }
453
454 // Make sure that deliverMin was satisfied.
455 if (optDeliverMin)
456 {
457 if (result.actualAmountOut < *optDeliverMin)
458 {
459 JLOG(ctx_.journal.warn())
460 << "flow did not produce DeliverMin.";
461 return tecPATH_PARTIAL;
462 }
463 if (!checkCashMakesTrustLine)
464 // Set the delivered_amount metadata.
465 ctx_.deliver(result.actualAmountOut);
466 }
467
468 // Set the delivered amount metadata in all cases, not just
469 // for DeliverMin.
470 if (checkCashMakesTrustLine)
471 ctx_.deliver(result.actualAmountOut);
472
473 sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
474 }
475 }
476
477 // Check was cashed. If not a self send (and it shouldn't be), remove
478 // check link from destination directory.
479 if (srcId != account_ &&
480 !psb.dirRemove(
482 sleCheck->at(sfDestinationNode),
483 sleCheck->key(),
484 true))
485 {
486 // LCOV_EXCL_START
487 JLOG(j_.fatal()) << "Unable to delete check from destination.";
488 return tefBAD_LEDGER;
489 // LCOV_EXCL_STOP
490 }
491
492 // Remove check from check owner's directory.
493 if (!psb.dirRemove(
494 keylet::ownerDir(srcId),
495 sleCheck->at(sfOwnerNode),
496 sleCheck->key(),
497 true))
498 {
499 // LCOV_EXCL_START
500 JLOG(j_.fatal()) << "Unable to delete check from owner.";
501 return tefBAD_LEDGER;
502 // LCOV_EXCL_STOP
503 }
504
505 // If we succeeded, update the check owner's reserve.
506 adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
507
508 // Remove check from ledger.
509 psb.erase(sleCheck);
510
511 psb.apply(ctx_.rawView());
512 return tesSUCCESS;
513}
514
515} // namespace ripple
Stream fatal() const
Definition Journal.h:352
Stream error() const
Definition Journal.h:346
Stream trace() const
Severity stream access functions.
Definition Journal.h:322
Stream warn() const
Definition Journal.h:340
virtual beast::Journal journal(std::string const &name)=0
ApplyView & view()
Application & app
beast::Journal const journal
void deliver(STAmount const &amount)
Sets the DeliveredAmount field in the metadata.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
static TER preclaim(PreclaimContext const &ctx)
Definition CashCheck.cpp:69
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
Definition CashCheck.cpp:36
A currency issued by an account.
Definition Issue.h:33
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:130
void setIssuer(AccountID const &uIssuer)
Definition STAmount.h:588
static int const cMaxOffset
Definition STAmount.h:66
Currency const & getCurrency() const
Definition STAmount.h:502
static std::uint64_t const cMaxValue
Definition STAmount.h:70
AccountID const & getIssuer() const
Definition STAmount.h:508
bool native() const noexcept
Definition STAmount.h:458
STAmount const & getFieldAmount(SField const &field) const
Definition STObject.cpp:671
AccountID const account_
Definition Transactor.h:147
beast::Journal const j_
Definition Transactor.h:145
XRPAmount mPriorBalance
Definition Transactor.h:148
ApplyContext & ctx_
Definition Transactor.h:143
Fees const & fees() const override
Returns the fees for the base ledger.
void erase(std::shared_ptr< SLE > const &sle) override
Remove a peeked SLE.
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
bool exists(Keylet const &k) const override
Determine if a state item exists.
Rules const & rules() const override
Returns the tx processing rules.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T is_same_v
T max(T... args)
T min(T... args)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:244
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:336
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
AccountID const & noAccount()
A placeholder for empty accounts.
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition View.cpp:554
@ fhZERO_IF_FROZEN
Definition View.h:77
bool isLegalNet(STAmount const &value)
Definition STAmount.h:600
@ lsfRequireDestTag
@ lsfDefaultRipple
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1032
TER transferXRP(ApplyView &view, AccountID const &from, AccountID const &to, STAmount const &amount, beast::Journal j)
Definition View.cpp:2440
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:105
bool isFrozen(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer)
Definition View.cpp:247
@ tefBAD_LEDGER
Definition TER.h:170
@ no
Definition Steps.h:45
bool hasExpired(ReadView const &view, std::optional< std::uint32_t > const &exp)
Determines whether the given expiration time has passed.
Definition View.cpp:173
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:292
@ tecNO_ISSUER
Definition TER.h:299
@ tecFROZEN
Definition TER.h:303
@ tecINTERNAL
Definition TER.h:310
@ tecNO_PERMISSION
Definition TER.h:305
@ tecDST_TAG_NEEDED
Definition TER.h:309
@ tecPATH_PARTIAL
Definition TER.h:282
@ tecNO_LINE
Definition TER.h:301
@ tecUNFUNDED_PAYMENT
Definition TER.h:285
@ tecFAILED_PROCESSING
Definition TER.h:286
@ tecEXPIRED
Definition TER.h:314
@ tecNO_AUTH
Definition TER.h:300
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
TER trustCreate(ApplyView &view, bool const bSrcHigh, AccountID const &uSrcAccountID, AccountID const &uDstAccountID, uint256 const &uIndex, SLE::ref sleAccount, bool const bAuth, bool const bNoRipple, bool const bFreeze, bool bDeepFreeze, STAmount const &saBalance, STAmount const &saLimit, std::uint32_t uSrcQualityIn, std::uint32_t uSrcQualityOut, beast::Journal j)
Create a trust line.
Definition View.cpp:1398
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
XRPAmount xrpLiquid(ReadView const &view, AccountID const &id, std::int32_t ownerCountAdj, beast::Journal j)
Definition View.cpp:618
@ temBAD_AMOUNT
Definition TER.h:89
@ temBAD_CURRENCY
Definition TER.h:90
@ temMALFORMED
Definition TER.h:87
XRPAmount increment
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
beast::Journal const j
Definition Transactor.h:88
State information when preflighting a tx.
Definition Transactor.h:35
beast::Journal const j
Definition Transactor.h:42
A field with a type known at compile time.
Definition SField.h:320