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