rippled
Loading...
Searching...
No Matches
XChainBridge.cpp
1#include <xrpld/app/paths/Flow.h>
2#include <xrpld/app/tx/detail/SignerEntries.h>
3#include <xrpld/app/tx/detail/Transactor.h>
4#include <xrpld/app/tx/detail/XChainBridge.h>
5
6#include <xrpl/basics/Log.h>
7#include <xrpl/basics/Number.h>
8#include <xrpl/basics/chrono.h>
9#include <xrpl/beast/utility/Journal.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/ledger/ApplyView.h>
12#include <xrpl/ledger/PaymentSandbox.h>
13#include <xrpl/ledger/View.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/PublicKey.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STObject.h>
21#include <xrpl/protocol/STXChainBridge.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/TxFlags.h>
24#include <xrpl/protocol/XChainAttestations.h>
25#include <xrpl/protocol/XRPAmount.h>
26
27#include <unordered_map>
28#include <unordered_set>
29
30namespace ripple {
31
32/*
33 Bridges connect two independent ledgers: a "locking chain" and an "issuing
34 chain". An asset can be moved from the locking chain to the issuing chain by
35 putting it into trust on the locking chain, and issuing a "wrapped asset"
36 that represents the locked asset on the issuing chain.
37
38 Note that a bridge is not an exchange. There is no exchange rate: one wrapped
39 asset on the issuing chain always represents one asset in trust on the
40 locking chain. The bridge also does not exchange an asset on the locking
41 chain for an asset on the issuing chain.
42
43 A good model for thinking about bridges is a box that contains an infinite
44 number of "wrapped tokens". When a token from the locking chain
45 (locking-chain-token) is put into the box, a wrapped token is taken out of
46 the box and put onto the issuing chain (issuing-chain-token). No one can use
47 the locking-chain-token while it remains in the box. When an
48 issuing-chain-token is returned to the box, one locking-chain-token is taken
49 out of the box and put back onto the locking chain.
50
51 This requires a way to put assets into trust on one chain (put a
52 locking-chain-token into the box). A regular XRP account is used for this.
53 This account is called a "door account". Much in the same way that a door is
54 used to go from one room to another, a door account is used to move from one
55 chain to another. This account will be jointly controlled by a set of witness
56 servers by using the ledger's multi-signature support. The master key will be
57 disabled. These witness servers are trusted in the sense that if a quorum of
58 them collude, they can steal the funds put into trust.
59
60 This also requires a way to prove that assets were put into the box - either
61 a locking-chain-token on the locking chain or returning an
62 issuing-chain-token on the issuing chain. A set of servers called "witness
63 servers" fill this role. These servers watch the ledger for these
64 transactions, and attest that the given events happened on the different
65 chains by signing messages with the event information.
66
67 There needs to be a way to prevent the attestations from the witness
68 servers from being used more than once. "Claim ids" fill this role. A claim
69 id must be acquired on the destination chain before the asset is "put into
70 the box" on the source chain. This claim id has a unique id, and once it is
71 destroyed it can never exist again (it's a simple counter). The attestations
72 reference this claim id, and are accumulated on the claim id. Once a quorum
73 is reached, funds can move. Once the funds move, the claim id is destroyed.
74
75 Finally, a claim id requires that the sender has an account on the
76 destination chain. For some chains, this can be a problem - especially if
77 the wrapped asset represents XRP, and XRP is needed to create an account.
78 There's a bootstrap problem. To address this, there is a special transaction
79 used to create accounts. This transaction does not require a claim id.
80
81 See the document "docs/bridge/spec.md" for a full description of how
82 bridges and their transactions work.
83*/
84
85namespace {
86
87// Check that the public key is allowed to sign for the given account. If the
88// account does not exist on the ledger, then the public key must be the master
89// key for the given account if it existed. Otherwise the key must be an enabled
90// master key or a regular key for the existing account.
91TER
92checkAttestationPublicKey(
93 ReadView const& view,
95 AccountID const& attestationSignerAccount,
96 PublicKey const& pk,
98{
99 if (!signersList.contains(attestationSignerAccount))
100 {
101 return tecNO_PERMISSION;
102 }
103
104 AccountID const accountFromPK = calcAccountID(pk);
105
106 if (auto const sleAttestationSigningAccount =
107 view.read(keylet::account(attestationSignerAccount)))
108 {
109 if (accountFromPK == attestationSignerAccount)
110 {
111 // master key
112 if (sleAttestationSigningAccount->getFieldU32(sfFlags) &
114 {
115 JLOG(j.trace()) << "Attempt to add an attestation with "
116 "disabled master key.";
118 }
119 }
120 else
121 {
122 // regular key
123 if (std::optional<AccountID> regularKey =
124 (*sleAttestationSigningAccount)[~sfRegularKey];
125 regularKey != accountFromPK)
126 {
127 if (!regularKey)
128 {
129 JLOG(j.trace())
130 << "Attempt to add an attestation with "
131 "account present and non-present regular key.";
132 }
133 else
134 {
135 JLOG(j.trace()) << "Attempt to add an attestation with "
136 "account present and mismatched "
137 "regular key/public key.";
138 }
140 }
141 }
142 }
143 else
144 {
145 // account does not exist.
146 if (calcAccountID(pk) != attestationSignerAccount)
147 {
148 JLOG(j.trace())
149 << "Attempt to add an attestation with non-existant account "
150 "and mismatched pk/account pair.";
152 }
153 }
154
155 return tesSUCCESS;
156}
157
158// If there is a quorum of attestations for the given parameters, then
159// return the reward accounts, otherwise return TER for the error.
160// Also removes attestations that are no longer part of the signers list.
161//
162// Note: the dst parameter is what the attestations are attesting to, which
163// is not always used (it is used when automatically triggering a transfer
164// from an `addAttestation` transaction, it is not used in a `claim`
165// transaction). If the `checkDst` parameter is `check`, the attestations
166// must attest to this destination, if it is `ignore` then the `dst` of the
167// attestations are not checked (as for a `claim` transaction)
168
169enum class CheckDst { check, ignore };
170template <class TAttestation>
171Expected<std::vector<AccountID>, TER>
172claimHelper(
173 XChainAttestationsBase<TAttestation>& attestations,
174 ReadView const& view,
175 typename TAttestation::MatchFields const& toMatch,
176 CheckDst checkDst,
177 std::uint32_t quorum,
180{
181 // Remove attestations that are not valid signers. They may be no longer
182 // part of the signers list, or their master key may have been disabled,
183 // or their regular key may have changed
184 attestations.erase_if([&](auto const& a) {
185 return checkAttestationPublicKey(
186 view, signersList, a.keyAccount, a.publicKey, j) !=
188 });
189
190 // Check if we have quorum for the amount specified on the new claimAtt
191 std::vector<AccountID> rewardAccounts;
192 rewardAccounts.reserve(attestations.size());
193 std::uint32_t weight = 0;
194 for (auto const& a : attestations)
195 {
196 auto const matchR = a.match(toMatch);
197 // The dest must match if claimHelper is being run as a result of an add
198 // attestation transaction. The dst does not need to match if the
199 // claimHelper is being run using an explicit claim transaction.
200 using enum AttestationMatch;
201 if (matchR == nonDstMismatch ||
202 (checkDst == CheckDst::check && matchR != match))
203 continue;
204 auto i = signersList.find(a.keyAccount);
205 if (i == signersList.end())
206 {
207 // LCOV_EXCL_START
208 UNREACHABLE(
209 "ripple::claimHelper : invalid inputs"); // should have already
210 // been checked
211 continue;
212 // LCOV_EXCL_STOP
213 }
214 weight += i->second;
215 rewardAccounts.push_back(a.rewardAccount);
216 }
217
218 if (weight >= quorum)
219 return rewardAccounts;
220
222}
223
255struct OnNewAttestationResult
256{
258 // `changed` is true if the attestation collection changed in any way
259 // (added/removed/changed)
260 bool changed{false};
261};
262
263template <class TAttestation>
264[[nodiscard]] OnNewAttestationResult
265onNewAttestations(
266 XChainAttestationsBase<TAttestation>& attestations,
267 ReadView const& view,
268 typename TAttestation::TSignedAttestation const* attBegin,
269 typename TAttestation::TSignedAttestation const* attEnd,
270 std::uint32_t quorum,
273{
274 bool changed = false;
275 for (auto att = attBegin; att != attEnd; ++att)
276 {
277 if (checkAttestationPublicKey(
278 view,
279 signersList,
280 att->attestationSignerAccount,
281 att->publicKey,
282 j) != tesSUCCESS)
283 {
284 // The checkAttestationPublicKey is not strictly necessary here (it
285 // should be checked in a preclaim step), but it would be bad to let
286 // this slip through if that changes, and the check is relatively
287 // cheap, so we check again
288 continue;
289 }
290
291 auto const& claimSigningAccount = att->attestationSignerAccount;
292 if (auto i = std::find_if(
293 attestations.begin(),
294 attestations.end(),
295 [&](auto const& a) {
296 return a.keyAccount == claimSigningAccount;
297 });
298 i != attestations.end())
299 {
300 // existing attestation
301 // replace old attestation with new attestation
302 *i = TAttestation{*att};
303 changed = true;
304 }
305 else
306 {
307 attestations.emplace_back(*att);
308 changed = true;
309 }
310 }
311
312 auto r = claimHelper(
313 attestations,
314 view,
315 typename TAttestation::MatchFields{*attBegin},
316 CheckDst::check,
317 quorum,
318 signersList,
319 j);
320
321 if (!r.has_value())
322 return {std::nullopt, changed};
323
324 return {std::move(r.value()), changed};
325};
326
327// Check if there is a quorurm of attestations for the given amount and
328// chain. If so return the reward accounts, if not return the tec code (most
329// likely tecXCHAIN_CLAIM_NO_QUORUM)
330Expected<std::vector<AccountID>, TER>
331onClaim(
332 XChainClaimAttestations& attestations,
333 ReadView const& view,
334 STAmount const& sendingAmount,
335 bool wasLockingChainSend,
336 std::uint32_t quorum,
339{
340 XChainClaimAttestation::MatchFields toMatch{
341 sendingAmount, wasLockingChainSend, std::nullopt};
342 return claimHelper(
343 attestations, view, toMatch, CheckDst::ignore, quorum, signersList, j);
344}
345
346enum class CanCreateDstPolicy { no, yes };
347
348enum class DepositAuthPolicy { normal, dstCanBypass };
349
350// Allow the fee to dip into the reserve. To support this, information about the
351// submitting account needs to be fed to the transfer helper.
352struct TransferHelperSubmittingAccountInfo
353{
354 AccountID account;
355 STAmount preFeeBalance;
356 STAmount postFeeBalance;
357};
358
381TER
382transferHelper(
383 PaymentSandbox& psb,
384 AccountID const& src,
385 AccountID const& dst,
386 std::optional<std::uint32_t> const& dstTag,
387 std::optional<AccountID> const& claimOwner,
388 STAmount const& amt,
389 CanCreateDstPolicy canCreate,
390 DepositAuthPolicy depositAuthPolicy,
392 submittingAccountInfo,
394{
395 if (dst == src)
396 return tesSUCCESS;
397
398 auto const dstK = keylet::account(dst);
399 if (auto sleDst = psb.read(dstK))
400 {
401 // Check dst tag and deposit auth
402
403 if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag)
404 return tecDST_TAG_NEEDED;
405
406 // If the destination is the claim owner, and this is a claim
407 // transaction, that's the dst account sending funds to itself. It
408 // can bypass deposit auth.
409 bool const canBypassDepositAuth = dst == claimOwner &&
410 depositAuthPolicy == DepositAuthPolicy::dstCanBypass;
411
412 if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) &&
413 !psb.exists(keylet::depositPreauth(dst, src)))
414 {
415 return tecNO_PERMISSION;
416 }
417 }
418 else if (!amt.native() || canCreate == CanCreateDstPolicy::no)
419 {
420 return tecNO_DST;
421 }
422
423 if (amt.native())
424 {
425 auto const sleSrc = psb.peek(keylet::account(src));
426 XRPL_ASSERT(sleSrc, "ripple::transferHelper : non-null source account");
427 if (!sleSrc)
428 return tecINTERNAL; // LCOV_EXCL_LINE
429
430 {
431 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
432 auto const reserve = psb.fees().accountReserve(ownerCount);
433
434 auto const availableBalance = [&]() -> STAmount {
435 STAmount const curBal = (*sleSrc)[sfBalance];
436 // Checking that account == src and postFeeBalance == curBal is
437 // not strictly nessisary, but helps protect against future
438 // changes
439 if (!submittingAccountInfo ||
440 submittingAccountInfo->account != src ||
441 submittingAccountInfo->postFeeBalance != curBal)
442 return curBal;
443 return submittingAccountInfo->preFeeBalance;
444 }();
445
446 if (availableBalance < amt + reserve)
447 {
448 return tecUNFUNDED_PAYMENT;
449 }
450 }
451
452 auto sleDst = psb.peek(dstK);
453 if (!sleDst)
454 {
455 if (canCreate == CanCreateDstPolicy::no)
456 {
457 // Already checked, but OK to check again
458 return tecNO_DST;
459 }
460 if (amt < psb.fees().reserve)
461 {
462 JLOG(j.trace()) << "Insufficient payment to create account.";
463 return tecNO_DST_INSUF_XRP;
464 }
465
466 // Create the account.
467 sleDst = std::make_shared<SLE>(dstK);
468 sleDst->setAccountID(sfAccount, dst);
469 sleDst->setFieldU32(sfSequence, psb.seq());
470
471 psb.insert(sleDst);
472 }
473
474 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
475 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
476 psb.update(sleSrc);
477 psb.update(sleDst);
478
479 return tesSUCCESS;
480 }
481
482 auto const result = flow(
483 psb,
484 amt,
485 src,
486 dst,
487 STPathSet{},
488 /*default path*/ true,
489 /*partial payment*/ false,
490 /*owner pays transfer fee*/ true,
491 /*offer crossing*/ OfferCrossing::no,
492 /*limit quality*/ std::nullopt,
493 /*sendmax*/ std::nullopt,
494 /*domain id*/ std::nullopt,
495 j);
496
497 if (auto const r = result.result();
498 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
499 return r;
501}
502
508enum class OnTransferFail {
510 removeClaim,
512 keepClaim
513};
514
515struct FinalizeClaimHelperResult
516{
518 std::optional<TER> mainFundsTer;
519 // TER for transfering the reward funds
520 std::optional<TER> rewardTer;
521 // TER for removing the sle (if is sle is to be removed)
522 std::optional<TER> rmSleTer;
523
524 // Helper to check for overall success. If there wasn't overall success the
525 // individual ters can be used to decide what needs to be done.
526 bool
527 isTesSuccess() const
528 {
529 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
530 (!rmSleTer || *rmSleTer == tesSUCCESS);
531 }
532
533 TER
534 ter() const
535 {
536 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
537 (!rewardTer || *rewardTer == tesSUCCESS) &&
538 (!rmSleTer || *rmSleTer == tesSUCCESS))
539 return tesSUCCESS;
540
541 // if any phase return a tecINTERNAL or a tef, prefer returning those
542 // codes
543 if (mainFundsTer &&
544 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
545 return *mainFundsTer;
546 if (rewardTer &&
547 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
548 return *rewardTer;
549 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
550 return *rmSleTer;
551
552 // Only after the tecINTERNAL and tef are checked, return the first
553 // non-success error code.
554 if (mainFundsTer && mainFundsTer != tesSUCCESS)
555 return *mainFundsTer;
556 if (rewardTer && rewardTer != tesSUCCESS)
557 return *rewardTer;
558 if (rmSleTer && rmSleTer != tesSUCCESS)
559 return *rmSleTer;
560 return tesSUCCESS;
561 }
562};
563
592FinalizeClaimHelperResult
593finalizeClaimHelper(
594 PaymentSandbox& outerSb,
595 STXChainBridge const& bridgeSpec,
596 AccountID const& dst,
597 std::optional<std::uint32_t> const& dstTag,
598 AccountID const& claimOwner,
599 STAmount const& sendingAmount,
600 AccountID const& rewardPoolSrc,
601 STAmount const& rewardPool,
602 std::vector<AccountID> const& rewardAccounts,
603 STXChainBridge::ChainType const srcChain,
604 Keylet const& claimIDKeylet,
605 OnTransferFail onTransferFail,
606 DepositAuthPolicy depositAuthPolicy,
608{
609 FinalizeClaimHelperResult result;
610
611 STXChainBridge::ChainType const dstChain =
613 STAmount const thisChainAmount = [&] {
614 STAmount r = sendingAmount;
615 r.setIssue(bridgeSpec.issue(dstChain));
616 return r;
617 }();
618 auto const& thisDoor = bridgeSpec.door(dstChain);
619
620 {
621 PaymentSandbox innerSb{&outerSb};
622 // If distributing the reward pool fails, the mainFunds transfer should
623 // be rolled back
624 //
625 // If the claimid is removed, the rewards should be distributed
626 // even if the mainFunds fails.
627 //
628 // If OnTransferFail::removeClaim, the claim should be removed even if
629 // the rewards cannot be distributed.
630
631 // transfer funds to the dst
632 result.mainFundsTer = transferHelper(
633 innerSb,
634 thisDoor,
635 dst,
636 dstTag,
637 claimOwner,
638 thisChainAmount,
639 CanCreateDstPolicy::yes,
640 depositAuthPolicy,
642 j);
643
644 if (!isTesSuccess(*result.mainFundsTer) &&
645 onTransferFail == OnTransferFail::keepClaim)
646 {
647 return result;
648 }
649
650 // handle the reward pool
651 result.rewardTer = [&]() -> TER {
652 if (rewardAccounts.empty())
653 return tesSUCCESS;
654
655 // distribute the reward pool
656 // if the transfer failed, distribute the pool for "OnTransferFail"
657 // cases (the attesters did their job)
658 STAmount const share = [&] {
659 auto const round_mode =
660 innerSb.rules().enabled(fixXChainRewardRounding)
663 saveNumberRoundMode _{Number::setround(round_mode)};
664
665 STAmount const den{rewardAccounts.size()};
666 return divide(rewardPool, den, rewardPool.issue());
667 }();
668 STAmount distributed = rewardPool.zeroed();
669 for (auto const& rewardAccount : rewardAccounts)
670 {
671 auto const thTer = transferHelper(
672 innerSb,
673 rewardPoolSrc,
674 rewardAccount,
675 /*dstTag*/ std::nullopt,
676 // claim owner is not relevant to distributing rewards
677 /*claimOwner*/ std::nullopt,
678 share,
679 CanCreateDstPolicy::no,
680 DepositAuthPolicy::normal,
682 j);
683
684 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
685 return thTer;
686
687 if (isTesSuccess(thTer))
688 distributed += share;
689
690 // let txn succeed if error distributing rewards (other than
691 // inability to pay)
692 }
693
694 if (distributed > rewardPool)
695 return tecINTERNAL; // LCOV_EXCL_LINE
696
697 return tesSUCCESS;
698 }();
699
700 if (!isTesSuccess(*result.rewardTer) &&
701 (onTransferFail == OnTransferFail::keepClaim ||
702 *result.rewardTer == tecINTERNAL))
703 {
704 return result;
705 }
706
707 if (!isTesSuccess(*result.mainFundsTer) ||
708 isTesSuccess(*result.rewardTer))
709 {
710 // Note: if the mainFunds transfer succeeds and the result transfer
711 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
712 // rolled back)
713 innerSb.apply(outerSb);
714 }
715 }
716
717 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
718 {
719 auto const cidOwner = (*sleClaimID)[sfAccount];
720 {
721 // Remove the claim id
722 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
723 auto const page = (*sleClaimID)[sfOwnerNode];
724 if (!outerSb.dirRemove(
725 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
726 {
727 JLOG(j.fatal())
728 << "Unable to delete xchain seq number from owner.";
729 result.rmSleTer = tefBAD_LEDGER;
730 return result;
731 }
732
733 // Remove the claim id from the ledger
734 outerSb.erase(sleClaimID);
735
736 adjustOwnerCount(outerSb, sleOwner, -1, j);
737 }
738 }
739
740 return result;
741}
742
753getSignersListAndQuorum(
754 ReadView const& view,
755 SLE const& sleBridge,
757{
760
761 AccountID const thisDoor = sleBridge[sfAccount];
762 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
763
764 if (!sleDoor)
765 {
766 return {r, q, tecINTERNAL};
767 }
768
769 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
770 if (!sleS)
771 {
772 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
773 }
774 q = (*sleS)[sfSignerQuorum];
775
776 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
777
778 if (!accountSigners)
779 {
780 return {r, q, tecINTERNAL};
781 }
782
783 for (auto const& as : *accountSigners)
784 {
785 r[as.account] = as.weight;
786 }
787
788 return {std::move(r), q, tesSUCCESS};
789};
790
791template <class R, class F>
793readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
794{
795 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
796 if (auto r = getter(bridgeSpec, ct))
797 {
798 if ((*r)[sfXChainBridge] == bridgeSpec)
799 return r;
800 }
801 return nullptr;
802 };
803 if (auto r = tryGet(STXChainBridge::ChainType::locking))
804 return r;
806}
807
809peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
810{
811 return readOrpeekBridge<SLE>(
812 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
813 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
814 bridgeSpec);
815}
816
818readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
819{
820 return readOrpeekBridge<SLE const>(
821 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
823 return v.read(keylet::bridge(b, ct));
824 },
825 bridgeSpec);
826}
827
828// Precondition: all the claims in the range are consistent. They must sign for
829// the same event (amount, sending account, claim id, etc).
830template <class TIter>
831TER
832applyClaimAttestations(
833 ApplyView& view,
834 RawView& rawView,
835 TIter attBegin,
836 TIter attEnd,
837 STXChainBridge const& bridgeSpec,
838 STXChainBridge::ChainType const srcChain,
840 std::uint32_t quorum,
842{
843 if (attBegin == attEnd)
844 return tesSUCCESS;
845
846 PaymentSandbox psb(&view);
847
848 auto const claimIDKeylet =
849 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
850
851 struct ScopeResult
852 {
853 OnNewAttestationResult newAttResult;
854 STAmount rewardAmount;
855 AccountID cidOwner;
856 };
857
858 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
859 // This lambda is ugly - admittedly. The purpose of this lambda is to
860 // limit the scope of sles so they don't overlap with
861 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
862 // views, it's important that the sle's lifetime doesn't overlap.
863 auto const sleClaimID = psb.peek(claimIDKeylet);
864 if (!sleClaimID)
866
867 // Add claims that are part of the signer's list to the "claims" vector
869 atts.reserve(std::distance(attBegin, attEnd));
870 for (auto att = attBegin; att != attEnd; ++att)
871 {
872 if (!signersList.contains(att->attestationSignerAccount))
873 continue;
874 atts.push_back(*att);
875 }
876
877 if (atts.empty())
878 {
880 }
881
882 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
883 if (attBegin->sendingAccount != otherChainSource)
884 {
886 }
887
888 {
889 STXChainBridge::ChainType const dstChain =
891
892 STXChainBridge::ChainType const attDstChain =
893 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
894
895 if (attDstChain != dstChain)
896 {
898 }
899 }
900
901 XChainClaimAttestations curAtts{
902 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
903
904 auto const newAttResult = onNewAttestations(
905 curAtts,
906 view,
907 &atts[0],
908 &atts[0] + atts.size(),
909 quorum,
910 signersList,
911 j);
912
913 // update the claim id
914 sleClaimID->setFieldArray(
915 sfXChainClaimAttestations, curAtts.toSTArray());
916 psb.update(sleClaimID);
917
918 return ScopeResult{
919 newAttResult,
920 (*sleClaimID)[sfSignatureReward],
921 (*sleClaimID)[sfAccount]};
922 }();
923
924 if (!scopeResult.has_value())
925 return scopeResult.error();
926
927 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
928 auto const& [rewardAccounts, attListChanged] = newAttResult;
929 if (rewardAccounts && attBegin->dst)
930 {
931 auto const r = finalizeClaimHelper(
932 psb,
933 bridgeSpec,
934 *attBegin->dst,
935 /*dstTag*/ std::nullopt,
936 cidOwner,
937 attBegin->sendingAmount,
938 cidOwner,
939 rewardAmount,
940 *rewardAccounts,
941 srcChain,
942 claimIDKeylet,
943 OnTransferFail::keepClaim,
944 DepositAuthPolicy::normal,
945 j);
946
947 auto const rTer = r.ter();
948
949 if (!isTesSuccess(rTer) &&
950 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
951 return rTer;
952 }
953
954 psb.apply(rawView);
955
956 return tesSUCCESS;
957}
958
959template <class TIter>
960TER
961applyCreateAccountAttestations(
962 ApplyView& view,
963 RawView& rawView,
964 TIter attBegin,
965 TIter attEnd,
966 AccountID const& doorAccount,
967 Keylet const& doorK,
968 STXChainBridge const& bridgeSpec,
969 Keylet const& bridgeK,
970 STXChainBridge::ChainType const srcChain,
972 std::uint32_t quorum,
974{
975 if (attBegin == attEnd)
976 return tesSUCCESS;
977
978 PaymentSandbox psb(&view);
979
980 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
981 auto const sleBridge = psb.peek(bridgeK);
982 if (!sleBridge)
983 return Unexpected(tecINTERNAL);
984
985 return (*sleBridge)[sfXChainAccountClaimCount];
986 }();
987
988 if (!claimCountResult.has_value())
989 return claimCountResult.error();
990
991 std::uint64_t const claimCount = claimCountResult.value();
992
993 if (attBegin->createCount <= claimCount)
994 {
996 }
997 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
998 {
999 // Limit the number of claims on the account
1001 }
1002
1003 {
1004 STXChainBridge::ChainType const dstChain =
1006
1007 STXChainBridge::ChainType const attDstChain =
1008 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
1009
1010 if (attDstChain != dstChain)
1011 {
1012 return tecXCHAIN_WRONG_CHAIN;
1013 }
1014 }
1015
1016 auto const claimIDKeylet =
1017 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1018
1019 struct ScopeResult
1020 {
1021 OnNewAttestationResult newAttResult;
1022 bool createCID;
1023 XChainCreateAccountAttestations curAtts;
1024 };
1025
1026 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1027 // This lambda is ugly - admittedly. The purpose of this lambda is to
1028 // limit the scope of sles so they don't overlap with
1029 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1030 // views, it's important that the sle's lifetime doesn't overlap.
1031
1032 // sleClaimID may be null. If it's null it isn't created until the end
1033 // of this function (if needed)
1034 auto const sleClaimID = psb.peek(claimIDKeylet);
1035 bool createCID = false;
1036 if (!sleClaimID)
1037 {
1038 createCID = true;
1039
1040 auto const sleDoor = psb.peek(doorK);
1041 if (!sleDoor)
1042 return Unexpected(tecINTERNAL);
1043
1044 // Check reserve
1045 auto const balance = (*sleDoor)[sfBalance];
1046 auto const reserve =
1047 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
1048
1049 if (balance < reserve)
1051 }
1052
1054 atts.reserve(std::distance(attBegin, attEnd));
1055 for (auto att = attBegin; att != attEnd; ++att)
1056 {
1057 if (!signersList.contains(att->attestationSignerAccount))
1058 continue;
1059 atts.push_back(*att);
1060 }
1061 if (atts.empty())
1062 {
1064 }
1065
1066 XChainCreateAccountAttestations curAtts = [&] {
1067 if (sleClaimID)
1068 return XChainCreateAccountAttestations{
1069 sleClaimID->getFieldArray(
1070 sfXChainCreateAccountAttestations)};
1071 return XChainCreateAccountAttestations{};
1072 }();
1073
1074 auto const newAttResult = onNewAttestations(
1075 curAtts,
1076 view,
1077 &atts[0],
1078 &atts[0] + atts.size(),
1079 quorum,
1080 signersList,
1081 j);
1082
1083 if (!createCID)
1084 {
1085 // Modify the object before it's potentially deleted, so the meta
1086 // data will include the new attestations
1087 if (!sleClaimID)
1088 return Unexpected(tecINTERNAL);
1089 sleClaimID->setFieldArray(
1090 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1091 psb.update(sleClaimID);
1092 }
1093 return ScopeResult{newAttResult, createCID, curAtts};
1094 }();
1095
1096 if (!scopeResult.has_value())
1097 return scopeResult.error();
1098
1099 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1100 auto const& [rewardAccounts, attListChanged] = attResult;
1101
1102 // Account create transactions must happen in order
1103 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1104 {
1105 auto const r = finalizeClaimHelper(
1106 psb,
1107 bridgeSpec,
1108 attBegin->toCreate,
1109 /*dstTag*/ std::nullopt,
1110 doorAccount,
1111 attBegin->sendingAmount,
1112 /*rewardPoolSrc*/ doorAccount,
1113 attBegin->rewardAmount,
1114 *rewardAccounts,
1115 srcChain,
1116 claimIDKeylet,
1117 OnTransferFail::removeClaim,
1118 DepositAuthPolicy::normal,
1119 j);
1120
1121 auto const rTer = r.ter();
1122
1123 if (!isTesSuccess(rTer))
1124 {
1125 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
1126 isTefFailure(rTer))
1127 return rTer;
1128 }
1129 // Move past this claim id even if it fails, so it doesn't block
1130 // subsequent claim ids
1131 auto const sleBridge = psb.peek(bridgeK);
1132 if (!sleBridge)
1133 return tecINTERNAL; // LCOV_EXCL_LINE
1134 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1135 psb.update(sleBridge);
1136 }
1137 else if (createCID)
1138 {
1139 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1140 (*createdSleClaimID)[sfAccount] = doorAccount;
1141 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1142 (*createdSleClaimID)[sfXChainAccountCreateCount] =
1143 attBegin->createCount;
1144 createdSleClaimID->setFieldArray(
1145 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1146
1147 // Add to owner directory of the door account
1148 auto const page = psb.dirInsert(
1149 keylet::ownerDir(doorAccount),
1150 claimIDKeylet,
1151 describeOwnerDir(doorAccount));
1152 if (!page)
1153 return tecDIR_FULL; // LCOV_EXCL_LINE
1154 (*createdSleClaimID)[sfOwnerNode] = *page;
1155
1156 auto const sleDoor = psb.peek(doorK);
1157 if (!sleDoor)
1158 return tecINTERNAL; // LCOV_EXCL_LINE
1159
1160 // Reserve was already checked
1161 adjustOwnerCount(psb, sleDoor, 1, j);
1162 psb.insert(createdSleClaimID);
1163 psb.update(sleDoor);
1164 }
1165
1166 psb.apply(rawView);
1167
1168 return tesSUCCESS;
1169}
1170
1171template <class TAttestation>
1173toClaim(STTx const& tx)
1174{
1175 static_assert(
1178
1179 try
1180 {
1181 STObject o{tx};
1182 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1183 return TAttestation(o);
1184 }
1185 catch (...)
1186 {
1187 }
1188 return std::nullopt;
1189}
1190
1191template <class TAttestation>
1192NotTEC
1193attestationpreflight(PreflightContext const& ctx)
1194{
1195 if (!publicKeyType(ctx.tx[sfPublicKey]))
1196 return temMALFORMED;
1197
1198 auto const att = toClaim<TAttestation>(ctx.tx);
1199 if (!att)
1200 return temMALFORMED;
1201
1202 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1203 if (!att->verify(bridgeSpec))
1204 return temXCHAIN_BAD_PROOF;
1205 if (!att->validAmounts())
1206 return temXCHAIN_BAD_PROOF;
1207
1208 if (att->sendingAmount.signum() <= 0)
1209 return temXCHAIN_BAD_PROOF;
1210 auto const expectedIssue =
1211 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1212 if (att->sendingAmount.issue() != expectedIssue)
1213 return temXCHAIN_BAD_PROOF;
1214
1215 return tesSUCCESS;
1216}
1217
1218template <class TAttestation>
1219TER
1220attestationPreclaim(PreclaimContext const& ctx)
1221{
1222 auto const att = toClaim<TAttestation>(ctx.tx);
1223 // checked in preflight
1224 if (!att)
1225 return tecINTERNAL; // LCOV_EXCL_LINE
1226
1227 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1228 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1229 if (!sleBridge)
1230 {
1231 return tecNO_ENTRY;
1232 }
1233
1234 AccountID const attestationSignerAccount{
1235 ctx.tx[sfAttestationSignerAccount]};
1236 PublicKey const pk{ctx.tx[sfPublicKey]};
1237
1238 // signersList is a map from account id to weights
1239 auto const [signersList, quorum, slTer] =
1240 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1241
1242 if (!isTesSuccess(slTer))
1243 return slTer;
1244
1245 return checkAttestationPublicKey(
1246 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1247}
1248
1249template <class TAttestation>
1250TER
1251attestationDoApply(ApplyContext& ctx)
1252{
1253 auto const att = toClaim<TAttestation>(ctx.tx);
1254 if (!att)
1255 // Should already be checked in preflight
1256 return tecINTERNAL; // LCOV_EXCL_LINE
1257
1258 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1259
1260 struct ScopeResult
1261 {
1262 STXChainBridge::ChainType srcChain;
1264 std::uint32_t quorum;
1265 AccountID thisDoor;
1266 Keylet bridgeK;
1267 };
1268
1269 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1270 // This lambda is ugly - admittedly. The purpose of this lambda is to
1271 // limit the scope of sles so they don't overlap with
1272 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1273 // views, it's important that the sle's lifetime doesn't overlap.
1274 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1275 if (!sleBridge)
1276 {
1277 return Unexpected(tecNO_ENTRY);
1278 }
1279 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1280 AccountID const thisDoor = (*sleBridge)[sfAccount];
1281
1283 {
1284 if (thisDoor == bridgeSpec.lockingChainDoor())
1286 else if (thisDoor == bridgeSpec.issuingChainDoor())
1288 else
1289 return Unexpected(tecINTERNAL);
1290 }
1291 STXChainBridge::ChainType const srcChain =
1293
1294 // signersList is a map from account id to weights
1295 auto [signersList, quorum, slTer] =
1296 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1297
1298 if (!isTesSuccess(slTer))
1299 return Unexpected(slTer);
1300
1301 return ScopeResult{
1302 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1303 }();
1304
1305 if (!scopeResult.has_value())
1306 return scopeResult.error();
1307
1308 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
1309 scopeResult.value();
1310
1311 static_assert(
1314
1316 {
1317 return applyClaimAttestations(
1318 ctx.view(),
1319 ctx.rawView(),
1320 &*att,
1321 &*att + 1,
1322 bridgeSpec,
1323 srcChain,
1324 signersList,
1325 quorum,
1326 ctx.journal);
1327 }
1328 else if constexpr (std::is_same_v<
1329 TAttestation,
1330 Attestations::AttestationCreateAccount>)
1331 {
1332 return applyCreateAccountAttestations(
1333 ctx.view(),
1334 ctx.rawView(),
1335 &*att,
1336 &*att + 1,
1337 thisDoor,
1338 keylet::account(thisDoor),
1339 bridgeSpec,
1340 bridgeK,
1341 srcChain,
1342 signersList,
1343 quorum,
1344 ctx.journal);
1345 }
1346}
1347
1348} // namespace
1349//------------------------------------------------------------------------------
1350
1351NotTEC
1353{
1354 auto const account = ctx.tx[sfAccount];
1355 auto const reward = ctx.tx[sfSignatureReward];
1356 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1357 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1358 // Doors must be distinct to help prevent transaction replay attacks
1359 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1360 {
1362 }
1363
1364 if (bridgeSpec.lockingChainDoor() != account &&
1365 bridgeSpec.issuingChainDoor() != account)
1366 {
1368 }
1369
1370 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1371 isXRP(bridgeSpec.issuingChainIssue()))
1372 {
1373 // Because ious and xrp have different numeric ranges, both the src and
1374 // dst issues must be both XRP or both IOU.
1376 }
1377
1378 if (!isXRP(reward) || reward.signum() < 0)
1379 {
1381 }
1382
1383 if (minAccountCreate &&
1384 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1385 !isXRP(bridgeSpec.lockingChainIssue()) ||
1386 !isXRP(bridgeSpec.issuingChainIssue())))
1387 {
1389 }
1390
1391 if (isXRP(bridgeSpec.issuingChainIssue()))
1392 {
1393 // Issuing account must be the root account for XRP (which presumably
1394 // owns all the XRP). This is done so the issuing account can't "run
1395 // out" of wrapped tokens.
1396 static auto const rootAccount = calcAccountID(
1398 KeyType::secp256k1, generateSeed("masterpassphrase"))
1399 .first);
1400 if (bridgeSpec.issuingChainDoor() != rootAccount)
1401 {
1403 }
1404 }
1405 else
1406 {
1407 // Issuing account must be the issuer for non-XRP. This is done so the
1408 // issuing account can't "run out" of wrapped tokens.
1409 if (bridgeSpec.issuingChainDoor() !=
1410 bridgeSpec.issuingChainIssue().account)
1411 {
1413 }
1414 }
1415
1416 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1417 {
1418 // If the locking chain door is locking their own asset, in some sense
1419 // nothing is being locked. Disallow this.
1421 }
1422
1423 return tesSUCCESS;
1424}
1425
1426TER
1428{
1429 auto const account = ctx.tx[sfAccount];
1430 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1431 STXChainBridge::ChainType const chainType =
1432 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1433
1434 {
1435 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1436 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1437 };
1438
1439 if (hasBridge(STXChainBridge::ChainType::issuing) ||
1441 {
1442 return tecDUPLICATE;
1443 }
1444 }
1445
1446 if (!isXRP(bridgeSpec.issue(chainType)))
1447 {
1448 auto const sleIssuer =
1449 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1450
1451 if (!sleIssuer)
1452 return tecNO_ISSUER;
1453
1454 // Allowing clawing back funds would break the bridge's invariant that
1455 // wrapped funds are always backed by locked funds
1456 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1457 return tecNO_PERMISSION;
1458 }
1459
1460 {
1461 // Check reserve
1462 auto const sleAcc = ctx.view.read(keylet::account(account));
1463 if (!sleAcc)
1464 return terNO_ACCOUNT;
1465
1466 auto const balance = (*sleAcc)[sfBalance];
1467 auto const reserve =
1468 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1469
1470 if (balance < reserve)
1472 }
1473
1474 return tesSUCCESS;
1475}
1476
1477TER
1479{
1480 auto const account = ctx_.tx[sfAccount];
1481 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1482 auto const reward = ctx_.tx[sfSignatureReward];
1483 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1484
1485 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1486 if (!sleAcct)
1487 return tecINTERNAL; // LCOV_EXCL_LINE
1488
1489 STXChainBridge::ChainType const chainType =
1490 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1491
1492 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1493 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1494
1495 (*sleBridge)[sfAccount] = account;
1496 (*sleBridge)[sfSignatureReward] = reward;
1497 if (minAccountCreate)
1498 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1499 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1500 (*sleBridge)[sfXChainClaimID] = 0;
1501 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1502 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1503
1504 // Add to owner directory
1505 {
1506 auto const page = ctx_.view().dirInsert(
1507 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1508 if (!page)
1509 return tecDIR_FULL; // LCOV_EXCL_LINE
1510 (*sleBridge)[sfOwnerNode] = *page;
1511 }
1512
1513 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1514
1515 ctx_.view().insert(sleBridge);
1516 ctx_.view().update(sleAcct);
1517
1518 return tesSUCCESS;
1519}
1520
1521//------------------------------------------------------------------------------
1522
1528
1529NotTEC
1531{
1532 auto const account = ctx.tx[sfAccount];
1533 auto const reward = ctx.tx[~sfSignatureReward];
1534 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1535 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1536 bool const clearAccountCreate =
1538
1539 if (!reward && !minAccountCreate && !clearAccountCreate)
1540 {
1541 // Must change something
1542 return temMALFORMED;
1543 }
1544
1545 if (minAccountCreate && clearAccountCreate)
1546 {
1547 // Can't both clear and set account create in the same txn
1548 return temMALFORMED;
1549 }
1550
1551 if (bridgeSpec.lockingChainDoor() != account &&
1552 bridgeSpec.issuingChainDoor() != account)
1553 {
1555 }
1556
1557 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1558 {
1560 }
1561
1562 if (minAccountCreate &&
1563 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1564 !isXRP(bridgeSpec.lockingChainIssue()) ||
1565 !isXRP(bridgeSpec.issuingChainIssue())))
1566 {
1568 }
1569
1570 return tesSUCCESS;
1571}
1572
1573TER
1575{
1576 auto const account = ctx.tx[sfAccount];
1577 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1578
1579 STXChainBridge::ChainType const chainType =
1580 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1581
1582 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1583 {
1584 return tecNO_ENTRY;
1585 }
1586
1587 return tesSUCCESS;
1588}
1589
1590TER
1592{
1593 auto const account = ctx_.tx[sfAccount];
1594 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1595 auto const reward = ctx_.tx[~sfSignatureReward];
1596 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1597 bool const clearAccountCreate =
1599
1600 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1601 if (!sleAcct)
1602 return tecINTERNAL; // LCOV_EXCL_LINE
1603
1604 STXChainBridge::ChainType const chainType =
1605 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1606
1607 auto const sleBridge =
1608 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1609 if (!sleBridge)
1610 return tecINTERNAL; // LCOV_EXCL_LINE
1611
1612 if (reward)
1613 (*sleBridge)[sfSignatureReward] = *reward;
1614 if (minAccountCreate)
1615 {
1616 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1617 }
1618 if (clearAccountCreate &&
1619 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1620 {
1621 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1622 }
1623 ctx_.view().update(sleBridge);
1624
1625 return tesSUCCESS;
1626}
1627
1628//------------------------------------------------------------------------------
1629
1630NotTEC
1632{
1633 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1634 auto const amount = ctx.tx[sfAmount];
1635
1636 if (amount.signum() <= 0 ||
1637 (amount.issue() != bridgeSpec.lockingChainIssue() &&
1638 amount.issue() != bridgeSpec.issuingChainIssue()))
1639 {
1640 return temBAD_AMOUNT;
1641 }
1642
1643 return tesSUCCESS;
1644}
1645
1646TER
1648{
1649 AccountID const account = ctx.tx[sfAccount];
1650 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1651 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1652 auto const claimID = ctx.tx[sfXChainClaimID];
1653
1654 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1655 if (!sleBridge)
1656 {
1657 return tecNO_ENTRY;
1658 }
1659
1660 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1661 {
1662 return tecNO_DST;
1663 }
1664
1665 auto const thisDoor = (*sleBridge)[sfAccount];
1666 bool isLockingChain = false;
1667 {
1668 if (thisDoor == bridgeSpec.lockingChainDoor())
1669 isLockingChain = true;
1670 else if (thisDoor == bridgeSpec.issuingChainDoor())
1671 isLockingChain = false;
1672 else
1673 return tecINTERNAL; // LCOV_EXCL_LINE
1674 }
1675
1676 {
1677 // Check that the amount specified matches the expected issue
1678
1679 if (isLockingChain)
1680 {
1681 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1683 }
1684 else
1685 {
1686 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1688 }
1689 }
1690
1691 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1692 isXRP(bridgeSpec.issuingChainIssue()))
1693 {
1694 // Should have been caught when creating the bridge
1695 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1696 // and the numeric issues that need to be addressed with that.
1697 return tecINTERNAL; // LCOV_EXCL_LINE
1698 }
1699
1700 auto const otherChainAmount = [&]() -> STAmount {
1701 STAmount r(thisChainAmount);
1702 if (isLockingChain)
1703 r.setIssue(bridgeSpec.issuingChainIssue());
1704 else
1705 r.setIssue(bridgeSpec.lockingChainIssue());
1706 return r;
1707 }();
1708
1709 auto const sleClaimID =
1710 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1711 {
1712 // Check that the sequence number is owned by the sender of this
1713 // transaction
1714 if (!sleClaimID)
1715 {
1716 return tecXCHAIN_NO_CLAIM_ID;
1717 }
1718
1719 if ((*sleClaimID)[sfAccount] != account)
1720 {
1721 // Sequence number isn't owned by the sender of this transaction
1723 }
1724 }
1725
1726 // quorum is checked in `doApply`
1727 return tesSUCCESS;
1728}
1729
1730TER
1732{
1733 PaymentSandbox psb(&ctx_.view());
1734
1735 AccountID const account = ctx_.tx[sfAccount];
1736 auto const dst = ctx_.tx[sfDestination];
1737 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1738 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1739 auto const claimID = ctx_.tx[sfXChainClaimID];
1740 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1741
1742 struct ScopeResult
1743 {
1744 std::vector<AccountID> rewardAccounts;
1745 AccountID rewardPoolSrc;
1746 STAmount sendingAmount;
1748 STAmount signatureReward;
1749 };
1750
1751 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1752 // This lambda is ugly - admittedly. The purpose of this lambda is to
1753 // limit the scope of sles so they don't overlap with
1754 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1755 // views, it's important that the sle's lifetime doesn't overlap.
1756
1757 auto const sleAcct = psb.peek(keylet::account(account));
1758 auto const sleBridge = peekBridge(psb, bridgeSpec);
1759 auto const sleClaimID = psb.peek(claimIDKeylet);
1760
1761 if (!(sleBridge && sleClaimID && sleAcct))
1762 return Unexpected(tecINTERNAL);
1763
1764 AccountID const thisDoor = (*sleBridge)[sfAccount];
1765
1767 {
1768 if (thisDoor == bridgeSpec.lockingChainDoor())
1770 else if (thisDoor == bridgeSpec.issuingChainDoor())
1772 else
1773 return Unexpected(tecINTERNAL);
1774 }
1775 STXChainBridge::ChainType const srcChain =
1777
1778 auto const sendingAmount = [&]() -> STAmount {
1779 STAmount r(thisChainAmount);
1780 r.setIssue(bridgeSpec.issue(srcChain));
1781 return r;
1782 }();
1783
1784 auto const [signersList, quorum, slTer] =
1785 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1786
1787 if (!isTesSuccess(slTer))
1788 return Unexpected(slTer);
1789
1791 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1792
1793 auto const claimR = onClaim(
1794 curAtts,
1795 psb,
1796 sendingAmount,
1797 /*wasLockingChainSend*/ srcChain ==
1799 quorum,
1800 signersList,
1801 ctx_.journal);
1802 if (!claimR.has_value())
1803 return Unexpected(claimR.error());
1804
1805 return ScopeResult{
1806 claimR.value(),
1807 (*sleClaimID)[sfAccount],
1808 sendingAmount,
1809 srcChain,
1810 (*sleClaimID)[sfSignatureReward],
1811 };
1812 }();
1813
1814 if (!scopeResult.has_value())
1815 return scopeResult.error();
1816
1817 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1818 scopeResult.value();
1819 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1820
1821 auto const r = finalizeClaimHelper(
1822 psb,
1823 bridgeSpec,
1824 dst,
1825 dstTag,
1826 /*claimOwner*/ account,
1827 sendingAmount,
1828 rewardPoolSrc,
1829 signatureReward,
1830 rewardAccounts,
1831 srcChain,
1832 claimIDKeylet,
1833 OnTransferFail::keepClaim,
1834 DepositAuthPolicy::dstCanBypass,
1835 ctx_.journal);
1836 if (!r.isTesSuccess())
1837 return r.ter();
1838
1839 psb.apply(ctx_.rawView());
1840
1841 return tesSUCCESS;
1842}
1843
1844//------------------------------------------------------------------------------
1845
1848{
1849 auto const maxSpend = [&] {
1850 auto const amount = ctx.tx[sfAmount];
1851 if (amount.native() && amount.signum() > 0)
1852 return amount.xrp();
1853 return XRPAmount{beast::zero};
1854 }();
1855
1856 return TxConsequences{ctx.tx, maxSpend};
1857}
1858
1859NotTEC
1861{
1862 auto const amount = ctx.tx[sfAmount];
1863 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1864
1865 if (amount.signum() <= 0 || !isLegalNet(amount))
1866 return temBAD_AMOUNT;
1867
1868 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
1869 amount.issue() != bridgeSpec.issuingChainIssue())
1870 return temBAD_ISSUER;
1871
1872 return tesSUCCESS;
1873}
1874
1875TER
1877{
1878 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1879 auto const amount = ctx.tx[sfAmount];
1880
1881 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1882 if (!sleBridge)
1883 {
1884 return tecNO_ENTRY;
1885 }
1886
1887 AccountID const thisDoor = (*sleBridge)[sfAccount];
1888 AccountID const account = ctx.tx[sfAccount];
1889
1890 if (thisDoor == account)
1891 {
1892 // Door account can't lock funds onto itself
1893 return tecXCHAIN_SELF_COMMIT;
1894 }
1895
1896 bool isLockingChain = false;
1897 {
1898 if (thisDoor == bridgeSpec.lockingChainDoor())
1899 isLockingChain = true;
1900 else if (thisDoor == bridgeSpec.issuingChainDoor())
1901 isLockingChain = false;
1902 else
1903 return tecINTERNAL; // LCOV_EXCL_LINE
1904 }
1905
1906 if (isLockingChain)
1907 {
1908 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1910 }
1911 else
1912 {
1913 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1915 }
1916
1917 return tesSUCCESS;
1918}
1919
1920TER
1922{
1923 PaymentSandbox psb(&ctx_.view());
1924
1925 auto const account = ctx_.tx[sfAccount];
1926 auto const amount = ctx_.tx[sfAmount];
1927 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1928
1929 if (!psb.read(keylet::account(account)))
1930 return tecINTERNAL; // LCOV_EXCL_LINE
1931
1932 auto const sleBridge = readBridge(psb, bridgeSpec);
1933 if (!sleBridge)
1934 return tecINTERNAL; // LCOV_EXCL_LINE
1935
1936 auto const dst = (*sleBridge)[sfAccount];
1937
1938 // Support dipping into reserves to pay the fee
1939 TransferHelperSubmittingAccountInfo submittingAccountInfo{
1941
1942 auto const thTer = transferHelper(
1943 psb,
1944 account,
1945 dst,
1946 /*dstTag*/ std::nullopt,
1947 /*claimOwner*/ std::nullopt,
1948 amount,
1949 CanCreateDstPolicy::no,
1950 DepositAuthPolicy::normal,
1951 submittingAccountInfo,
1952 ctx_.journal);
1953
1954 if (!isTesSuccess(thTer))
1955 return thTer;
1956
1957 psb.apply(ctx_.rawView());
1958
1959 return tesSUCCESS;
1960}
1961
1962//------------------------------------------------------------------------------
1963
1964NotTEC
1966{
1967 auto const reward = ctx.tx[sfSignatureReward];
1968
1969 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
1971
1972 return tesSUCCESS;
1973}
1974
1975TER
1977{
1978 auto const account = ctx.tx[sfAccount];
1979 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1980 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1981
1982 if (!sleBridge)
1983 {
1984 return tecNO_ENTRY;
1985 }
1986
1987 // Check that the reward matches
1988 auto const reward = ctx.tx[sfSignatureReward];
1989
1990 if (reward != (*sleBridge)[sfSignatureReward])
1991 {
1993 }
1994
1995 {
1996 // Check reserve
1997 auto const sleAcc = ctx.view.read(keylet::account(account));
1998 if (!sleAcc)
1999 return terNO_ACCOUNT;
2000
2001 auto const balance = (*sleAcc)[sfBalance];
2002 auto const reserve =
2003 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
2004
2005 if (balance < reserve)
2007 }
2008
2009 return tesSUCCESS;
2010}
2011
2012TER
2014{
2015 auto const account = ctx_.tx[sfAccount];
2016 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2017 auto const reward = ctx_.tx[sfSignatureReward];
2018 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2019
2020 auto const sleAcct = ctx_.view().peek(keylet::account(account));
2021 if (!sleAcct)
2022 return tecINTERNAL; // LCOV_EXCL_LINE
2023
2024 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2025 if (!sleBridge)
2026 return tecINTERNAL; // LCOV_EXCL_LINE
2027
2028 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2029 if (claimID == 0)
2030 {
2031 // overflow
2032 return tecINTERNAL; // LCOV_EXCL_LINE
2033 }
2034
2035 (*sleBridge)[sfXChainClaimID] = claimID;
2036
2037 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2038 if (ctx_.view().exists(claimIDKeylet))
2039 {
2040 // already checked out!?!
2041 return tecINTERNAL; // LCOV_EXCL_LINE
2042 }
2043
2044 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2045
2046 (*sleClaimID)[sfAccount] = account;
2047 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2048 (*sleClaimID)[sfXChainClaimID] = claimID;
2049 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2050 (*sleClaimID)[sfSignatureReward] = reward;
2051 sleClaimID->setFieldArray(
2052 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
2053
2054 // Add to owner directory
2055 {
2056 auto const page = ctx_.view().dirInsert(
2057 keylet::ownerDir(account),
2058 claimIDKeylet,
2059 describeOwnerDir(account));
2060 if (!page)
2061 return tecDIR_FULL; // LCOV_EXCL_LINE
2062 (*sleClaimID)[sfOwnerNode] = *page;
2063 }
2064
2065 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2066
2067 ctx_.view().insert(sleClaimID);
2068 ctx_.view().update(sleBridge);
2069 ctx_.view().update(sleAcct);
2070
2071 return tesSUCCESS;
2072}
2073
2074//------------------------------------------------------------------------------
2075
2076NotTEC
2078{
2079 return attestationpreflight<Attestations::AttestationClaim>(ctx);
2080}
2081
2082TER
2084{
2085 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2086}
2087
2088TER
2090{
2091 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2092}
2093
2094//------------------------------------------------------------------------------
2095
2096NotTEC
2098{
2099 return attestationpreflight<Attestations::AttestationCreateAccount>(ctx);
2100}
2101
2102TER
2104{
2105 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2106}
2107
2108TER
2110{
2111 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2112}
2113
2114//------------------------------------------------------------------------------
2115
2116NotTEC
2118{
2119 auto const amount = ctx.tx[sfAmount];
2120
2121 if (amount.signum() <= 0 || !amount.native())
2122 return temBAD_AMOUNT;
2123
2124 auto const reward = ctx.tx[sfSignatureReward];
2125 if (reward.signum() < 0 || !reward.native())
2126 return temBAD_AMOUNT;
2127
2128 if (reward.issue() != amount.issue())
2129 return temBAD_AMOUNT;
2130
2131 return tesSUCCESS;
2132}
2133
2134TER
2136{
2137 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2138 STAmount const amount = ctx.tx[sfAmount];
2139 STAmount const reward = ctx.tx[sfSignatureReward];
2140
2141 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2142 if (!sleBridge)
2143 {
2144 return tecNO_ENTRY;
2145 }
2146
2147 if (reward != (*sleBridge)[sfSignatureReward])
2148 {
2150 }
2151
2152 std::optional<STAmount> const minCreateAmount =
2153 (*sleBridge)[~sfMinAccountCreateAmount];
2154
2155 if (!minCreateAmount)
2157
2158 if (amount < *minCreateAmount)
2160
2161 if (minCreateAmount->issue() != amount.issue())
2163
2164 AccountID const thisDoor = (*sleBridge)[sfAccount];
2165 AccountID const account = ctx.tx[sfAccount];
2166 if (thisDoor == account)
2167 {
2168 // Door account can't lock funds onto itself
2169 return tecXCHAIN_SELF_COMMIT;
2170 }
2171
2173 {
2174 if (thisDoor == bridgeSpec.lockingChainDoor())
2176 else if (thisDoor == bridgeSpec.issuingChainDoor())
2178 else
2179 return tecINTERNAL; // LCOV_EXCL_LINE
2180 }
2181 STXChainBridge::ChainType const dstChain =
2183
2184 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2186
2187 if (!isXRP(bridgeSpec.issue(dstChain)))
2189
2190 return tesSUCCESS;
2191}
2192
2193TER
2195{
2196 PaymentSandbox psb(&ctx_.view());
2197
2198 AccountID const account = ctx_.tx[sfAccount];
2199 STAmount const amount = ctx_.tx[sfAmount];
2200 STAmount const reward = ctx_.tx[sfSignatureReward];
2201 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2202
2203 auto const sle = psb.peek(keylet::account(account));
2204 if (!sle)
2205 return tecINTERNAL; // LCOV_EXCL_LINE
2206
2207 auto const sleBridge = peekBridge(psb, bridge);
2208 if (!sleBridge)
2209 return tecINTERNAL; // LCOV_EXCL_LINE
2210
2211 auto const dst = (*sleBridge)[sfAccount];
2212
2213 // Support dipping into reserves to pay the fee
2214 TransferHelperSubmittingAccountInfo submittingAccountInfo{
2216 STAmount const toTransfer = amount + reward;
2217 auto const thTer = transferHelper(
2218 psb,
2219 account,
2220 dst,
2221 /*dstTag*/ std::nullopt,
2222 /*claimOwner*/ std::nullopt,
2223 toTransfer,
2224 CanCreateDstPolicy::yes,
2225 DepositAuthPolicy::normal,
2226 submittingAccountInfo,
2227 ctx_.journal);
2228
2229 if (!isTesSuccess(thTer))
2230 return thTer;
2231
2232 (*sleBridge)[sfXChainAccountCreateCount] =
2233 (*sleBridge)[sfXChainAccountCreateCount] + 1;
2234 psb.update(sleBridge);
2235
2236 psb.apply(ctx_.rawView());
2237
2238 return tesSUCCESS;
2239}
2240
2241} // namespace ripple
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:333
Stream trace() const
Severity stream access functions.
Definition Journal.h:303
ApplyView & view()
beast::Journal const journal
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:300
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static rounding_mode getround()
Definition Number.cpp:28
static rounding_mode setround(rounding_mode mode)
Definition Number.cpp:34
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 bool exists(Keylet const &k) const =0
Determine if a state item exists.
void setIssue(Asset const &asset)
Set the Issue for this amount.
Definition STAmount.cpp:429
Issue const & issue() const
Definition STAmount.h:477
std::uint32_t getFlags() const
Definition STObject.cpp:518
static ChainType dstChain(bool wasLockingChainSend)
static ChainType srcChain(bool wasLockingChainSend)
AccountID const & issuingChainDoor() const
Issue const & issuingChainIssue() const
Issue const & lockingChainIssue() const
static ChainType otherChain(ChainType ct)
AccountID const & lockingChainDoor() const
Issue const & issue(ChainType ct) const
static Expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
AccountID const account_
Definition Transactor.h:128
XRPAmount mPriorBalance
Definition Transactor.h:129
XRPAmount mSourceBalance
Definition Transactor.h:130
ApplyContext & ctx_
Definition Transactor.h:124
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:39
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T contains(T... args)
T distance(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T is_same_v
T max(T... args)
void check(bool condition, std::string const &message)
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:467
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:361
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:454
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:355
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:311
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:481
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:323
std::uint32_t ownerCount(Env const &env, Account const &account)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:74
constexpr std::uint32_t tfBridgeModifyMask
Definition TxFlags.h:248
bool isXRP(AccountID const &c)
Definition AccountID.h:71
constexpr size_t xbridgeMaxAccountCreateClaims
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
bool isLegalNet(STAmount const &value)
Definition STAmount.h:581
@ lsfRequireDestTag
@ lsfAllowTrustLineClawback
@ lsfDisableMaster
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:1013
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1031
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:86
AccountID calcAccountID(PublicKey const &pk)
@ tefBAD_LEDGER
Definition TER.h:151
bool isTefFailure(TER x) noexcept
Definition TER.h:647
@ no
Definition Steps.h:26
@ yes
Definition Steps.h:26
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
constexpr std::uint32_t tfClearAccountCreateAmount
Definition TxFlags.h:247
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_DST
Definition TER.h:272
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:332
@ tecXCHAIN_WRONG_CHAIN
Definition TER.h:324
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:328
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:334
@ tecNO_ISSUER
Definition TER.h:281
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:319
@ tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE
Definition TER.h:323
@ tecDIR_FULL
Definition TER.h:269
@ tecXCHAIN_PAYMENT_FAILED
Definition TER.h:331
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:321
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:320
@ tecXCHAIN_PROOF_UNKNOWN_KEY
Definition TER.h:322
@ tecDUPLICATE
Definition TER.h:297
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:329
@ tecINTERNAL
Definition TER.h:292
@ tecXCHAIN_BAD_TRANSFER_ISSUE
Definition TER.h:318
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:325
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition TER.h:327
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:333
@ tecUNFUNDED_PAYMENT
Definition TER.h:267
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:326
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition TER.h:330
@ tecNO_DST_INSUF_XRP
Definition TER.h:273
bool isTerRetry(TER x) noexcept
Definition TER.h:653
@ tesSUCCESS
Definition TER.h:226
bool isTesSuccess(TER x) noexcept
Definition TER.h:659
STLedgerEntry SLE
Unexpected(E(&)[N]) -> Unexpected< E const * >
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57
@ terNO_ACCOUNT
Definition TER.h:198
TERSubset< CanCvtToTER > TER
Definition TER.h:630
bool isTecClaim(TER x) noexcept
Definition TER.h:666
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:590
@ temBAD_ISSUER
Definition TER.h:74
@ temBAD_AMOUNT
Definition TER.h:70
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:116
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition TER.h:117
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition TER.h:112
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition TER.h:115
@ temMALFORMED
Definition TER.h:68
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:114
@ temXCHAIN_BAD_PROOF
Definition TER.h:113
T push_back(T... args)
T reserve(T... args)
T size(T... args)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
State information when preflighting a tx.
Definition Transactor.h:16