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