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
25#include <xrpl/basics/Log.h>
26#include <xrpl/basics/Number.h>
27#include <xrpl/basics/chrono.h>
28#include <xrpl/beast/utility/Journal.h>
29#include <xrpl/beast/utility/instrumentation.h>
30#include <xrpl/ledger/ApplyView.h>
31#include <xrpl/ledger/PaymentSandbox.h>
32#include <xrpl/ledger/View.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 /*domain id*/ std::nullopt,
515 j);
516
517 if (auto const r = result.result();
518 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
519 return r;
521}
522
528enum class OnTransferFail {
530 removeClaim,
532 keepClaim
533};
534
535struct FinalizeClaimHelperResult
536{
538 std::optional<TER> mainFundsTer;
539 // TER for transfering the reward funds
540 std::optional<TER> rewardTer;
541 // TER for removing the sle (if is sle is to be removed)
542 std::optional<TER> rmSleTer;
543
544 // Helper to check for overall success. If there wasn't overall success the
545 // individual ters can be used to decide what needs to be done.
546 bool
547 isTesSuccess() const
548 {
549 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
550 (!rmSleTer || *rmSleTer == tesSUCCESS);
551 }
552
553 TER
554 ter() const
555 {
556 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
557 (!rewardTer || *rewardTer == tesSUCCESS) &&
558 (!rmSleTer || *rmSleTer == tesSUCCESS))
559 return tesSUCCESS;
560
561 // if any phase return a tecINTERNAL or a tef, prefer returning those
562 // codes
563 if (mainFundsTer &&
564 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
565 return *mainFundsTer;
566 if (rewardTer &&
567 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
568 return *rewardTer;
569 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
570 return *rmSleTer;
571
572 // Only after the tecINTERNAL and tef are checked, return the first
573 // non-success error code.
574 if (mainFundsTer && mainFundsTer != tesSUCCESS)
575 return *mainFundsTer;
576 if (rewardTer && rewardTer != tesSUCCESS)
577 return *rewardTer;
578 if (rmSleTer && rmSleTer != tesSUCCESS)
579 return *rmSleTer;
580 return tesSUCCESS;
581 }
582};
583
612FinalizeClaimHelperResult
613finalizeClaimHelper(
614 PaymentSandbox& outerSb,
615 STXChainBridge const& bridgeSpec,
616 AccountID const& dst,
617 std::optional<std::uint32_t> const& dstTag,
618 AccountID const& claimOwner,
619 STAmount const& sendingAmount,
620 AccountID const& rewardPoolSrc,
621 STAmount const& rewardPool,
622 std::vector<AccountID> const& rewardAccounts,
623 STXChainBridge::ChainType const srcChain,
624 Keylet const& claimIDKeylet,
625 OnTransferFail onTransferFail,
626 DepositAuthPolicy depositAuthPolicy,
628{
629 FinalizeClaimHelperResult result;
630
631 STXChainBridge::ChainType const dstChain =
633 STAmount const thisChainAmount = [&] {
634 STAmount r = sendingAmount;
635 r.setIssue(bridgeSpec.issue(dstChain));
636 return r;
637 }();
638 auto const& thisDoor = bridgeSpec.door(dstChain);
639
640 {
641 PaymentSandbox innerSb{&outerSb};
642 // If distributing the reward pool fails, the mainFunds transfer should
643 // be rolled back
644 //
645 // If the claimid is removed, the rewards should be distributed
646 // even if the mainFunds fails.
647 //
648 // If OnTransferFail::removeClaim, the claim should be removed even if
649 // the rewards cannot be distributed.
650
651 // transfer funds to the dst
652 result.mainFundsTer = transferHelper(
653 innerSb,
654 thisDoor,
655 dst,
656 dstTag,
657 claimOwner,
658 thisChainAmount,
659 CanCreateDstPolicy::yes,
660 depositAuthPolicy,
662 j);
663
664 if (!isTesSuccess(*result.mainFundsTer) &&
665 onTransferFail == OnTransferFail::keepClaim)
666 {
667 return result;
668 }
669
670 // handle the reward pool
671 result.rewardTer = [&]() -> TER {
672 if (rewardAccounts.empty())
673 return tesSUCCESS;
674
675 // distribute the reward pool
676 // if the transfer failed, distribute the pool for "OnTransferFail"
677 // cases (the attesters did their job)
678 STAmount const share = [&] {
679 auto const round_mode =
680 innerSb.rules().enabled(fixXChainRewardRounding)
683 saveNumberRoundMode _{Number::setround(round_mode)};
684
685 STAmount const den{rewardAccounts.size()};
686 return divide(rewardPool, den, rewardPool.issue());
687 }();
688 STAmount distributed = rewardPool.zeroed();
689 for (auto const& rewardAccount : rewardAccounts)
690 {
691 auto const thTer = transferHelper(
692 innerSb,
693 rewardPoolSrc,
694 rewardAccount,
695 /*dstTag*/ std::nullopt,
696 // claim owner is not relevant to distributing rewards
697 /*claimOwner*/ std::nullopt,
698 share,
699 CanCreateDstPolicy::no,
700 DepositAuthPolicy::normal,
702 j);
703
704 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
705 return thTer;
706
707 if (isTesSuccess(thTer))
708 distributed += share;
709
710 // let txn succeed if error distributing rewards (other than
711 // inability to pay)
712 }
713
714 if (distributed > rewardPool)
715 return tecINTERNAL;
716
717 return tesSUCCESS;
718 }();
719
720 if (!isTesSuccess(*result.rewardTer) &&
721 (onTransferFail == OnTransferFail::keepClaim ||
722 *result.rewardTer == tecINTERNAL))
723 {
724 return result;
725 }
726
727 if (!isTesSuccess(*result.mainFundsTer) ||
728 isTesSuccess(*result.rewardTer))
729 {
730 // Note: if the mainFunds transfer succeeds and the result transfer
731 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
732 // rolled back)
733 innerSb.apply(outerSb);
734 }
735 }
736
737 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
738 {
739 auto const cidOwner = (*sleClaimID)[sfAccount];
740 {
741 // Remove the claim id
742 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
743 auto const page = (*sleClaimID)[sfOwnerNode];
744 if (!outerSb.dirRemove(
745 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
746 {
747 JLOG(j.fatal())
748 << "Unable to delete xchain seq number from owner.";
749 result.rmSleTer = tefBAD_LEDGER;
750 return result;
751 }
752
753 // Remove the claim id from the ledger
754 outerSb.erase(sleClaimID);
755
756 adjustOwnerCount(outerSb, sleOwner, -1, j);
757 }
758 }
759
760 return result;
761}
762
773getSignersListAndQuorum(
774 ReadView const& view,
775 SLE const& sleBridge,
777{
780
781 AccountID const thisDoor = sleBridge[sfAccount];
782 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
783
784 if (!sleDoor)
785 {
786 return {r, q, tecINTERNAL};
787 }
788
789 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
790 if (!sleS)
791 {
792 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
793 }
794 q = (*sleS)[sfSignerQuorum];
795
796 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
797
798 if (!accountSigners)
799 {
800 return {r, q, tecINTERNAL};
801 }
802
803 for (auto const& as : *accountSigners)
804 {
805 r[as.account] = as.weight;
806 }
807
808 return {std::move(r), q, tesSUCCESS};
809};
810
811template <class R, class F>
813readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
814{
815 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
816 if (auto r = getter(bridgeSpec, ct))
817 {
818 if ((*r)[sfXChainBridge] == bridgeSpec)
819 return r;
820 }
821 return nullptr;
822 };
823 if (auto r = tryGet(STXChainBridge::ChainType::locking))
824 return r;
826}
827
829peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
830{
831 return readOrpeekBridge<SLE>(
832 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
833 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
834 bridgeSpec);
835}
836
838readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
839{
840 return readOrpeekBridge<SLE const>(
841 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
843 return v.read(keylet::bridge(b, ct));
844 },
845 bridgeSpec);
846}
847
848// Precondition: all the claims in the range are consistent. They must sign for
849// the same event (amount, sending account, claim id, etc).
850template <class TIter>
851TER
852applyClaimAttestations(
853 ApplyView& view,
854 RawView& rawView,
855 TIter attBegin,
856 TIter attEnd,
857 STXChainBridge const& bridgeSpec,
858 STXChainBridge::ChainType const srcChain,
860 std::uint32_t quorum,
862{
863 if (attBegin == attEnd)
864 return tesSUCCESS;
865
866 PaymentSandbox psb(&view);
867
868 auto const claimIDKeylet =
869 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
870
871 struct ScopeResult
872 {
873 OnNewAttestationResult newAttResult;
874 STAmount rewardAmount;
875 AccountID cidOwner;
876 };
877
878 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
879 // This lambda is ugly - admittedly. The purpose of this lambda is to
880 // limit the scope of sles so they don't overlap with
881 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
882 // views, it's important that the sle's lifetime doesn't overlap.
883 auto const sleClaimID = psb.peek(claimIDKeylet);
884 if (!sleClaimID)
886
887 // Add claims that are part of the signer's list to the "claims" vector
889 atts.reserve(std::distance(attBegin, attEnd));
890 for (auto att = attBegin; att != attEnd; ++att)
891 {
892 if (!signersList.contains(att->attestationSignerAccount))
893 continue;
894 atts.push_back(*att);
895 }
896
897 if (atts.empty())
898 {
900 }
901
902 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
903 if (attBegin->sendingAccount != otherChainSource)
904 {
906 }
907
908 {
909 STXChainBridge::ChainType const dstChain =
911
912 STXChainBridge::ChainType const attDstChain =
913 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
914
915 if (attDstChain != dstChain)
916 {
918 }
919 }
920
921 XChainClaimAttestations curAtts{
922 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
923
924 auto const newAttResult = onNewAttestations(
925 curAtts,
926 view,
927 &atts[0],
928 &atts[0] + atts.size(),
929 quorum,
930 signersList,
931 j);
932
933 // update the claim id
934 sleClaimID->setFieldArray(
935 sfXChainClaimAttestations, curAtts.toSTArray());
936 psb.update(sleClaimID);
937
938 return ScopeResult{
939 newAttResult,
940 (*sleClaimID)[sfSignatureReward],
941 (*sleClaimID)[sfAccount]};
942 }();
943
944 if (!scopeResult.has_value())
945 return scopeResult.error();
946
947 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
948 auto const& [rewardAccounts, attListChanged] = newAttResult;
949 if (rewardAccounts && attBegin->dst)
950 {
951 auto const r = finalizeClaimHelper(
952 psb,
953 bridgeSpec,
954 *attBegin->dst,
955 /*dstTag*/ std::nullopt,
956 cidOwner,
957 attBegin->sendingAmount,
958 cidOwner,
959 rewardAmount,
960 *rewardAccounts,
961 srcChain,
962 claimIDKeylet,
963 OnTransferFail::keepClaim,
964 DepositAuthPolicy::normal,
965 j);
966
967 auto const rTer = r.ter();
968
969 if (!isTesSuccess(rTer) &&
970 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
971 return rTer;
972 }
973
974 psb.apply(rawView);
975
976 return tesSUCCESS;
977}
978
979template <class TIter>
980TER
981applyCreateAccountAttestations(
982 ApplyView& view,
983 RawView& rawView,
984 TIter attBegin,
985 TIter attEnd,
986 AccountID const& doorAccount,
987 Keylet const& doorK,
988 STXChainBridge const& bridgeSpec,
989 Keylet const& bridgeK,
990 STXChainBridge::ChainType const srcChain,
992 std::uint32_t quorum,
994{
995 if (attBegin == attEnd)
996 return tesSUCCESS;
997
998 PaymentSandbox psb(&view);
999
1000 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
1001 auto const sleBridge = psb.peek(bridgeK);
1002 if (!sleBridge)
1003 return Unexpected(tecINTERNAL);
1004
1005 return (*sleBridge)[sfXChainAccountClaimCount];
1006 }();
1007
1008 if (!claimCountResult.has_value())
1009 return claimCountResult.error();
1010
1011 std::uint64_t const claimCount = claimCountResult.value();
1012
1013 if (attBegin->createCount <= claimCount)
1014 {
1016 }
1017 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
1018 {
1019 // Limit the number of claims on the account
1021 }
1022
1023 {
1024 STXChainBridge::ChainType const dstChain =
1026
1027 STXChainBridge::ChainType const attDstChain =
1028 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
1029
1030 if (attDstChain != dstChain)
1031 {
1032 return tecXCHAIN_WRONG_CHAIN;
1033 }
1034 }
1035
1036 auto const claimIDKeylet =
1037 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1038
1039 struct ScopeResult
1040 {
1041 OnNewAttestationResult newAttResult;
1042 bool createCID;
1043 XChainCreateAccountAttestations curAtts;
1044 };
1045
1046 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1047 // This lambda is ugly - admittedly. The purpose of this lambda is to
1048 // limit the scope of sles so they don't overlap with
1049 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1050 // views, it's important that the sle's lifetime doesn't overlap.
1051
1052 // sleClaimID may be null. If it's null it isn't created until the end
1053 // of this function (if needed)
1054 auto const sleClaimID = psb.peek(claimIDKeylet);
1055 bool createCID = false;
1056 if (!sleClaimID)
1057 {
1058 createCID = true;
1059
1060 auto const sleDoor = psb.peek(doorK);
1061 if (!sleDoor)
1062 return Unexpected(tecINTERNAL);
1063
1064 // Check reserve
1065 auto const balance = (*sleDoor)[sfBalance];
1066 auto const reserve =
1067 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
1068
1069 if (balance < reserve)
1071 }
1072
1074 atts.reserve(std::distance(attBegin, attEnd));
1075 for (auto att = attBegin; att != attEnd; ++att)
1076 {
1077 if (!signersList.contains(att->attestationSignerAccount))
1078 continue;
1079 atts.push_back(*att);
1080 }
1081 if (atts.empty())
1082 {
1084 }
1085
1086 XChainCreateAccountAttestations curAtts = [&] {
1087 if (sleClaimID)
1088 return XChainCreateAccountAttestations{
1089 sleClaimID->getFieldArray(
1090 sfXChainCreateAccountAttestations)};
1091 return XChainCreateAccountAttestations{};
1092 }();
1093
1094 auto const newAttResult = onNewAttestations(
1095 curAtts,
1096 view,
1097 &atts[0],
1098 &atts[0] + atts.size(),
1099 quorum,
1100 signersList,
1101 j);
1102
1103 if (!createCID)
1104 {
1105 // Modify the object before it's potentially deleted, so the meta
1106 // data will include the new attestations
1107 if (!sleClaimID)
1108 return Unexpected(tecINTERNAL);
1109 sleClaimID->setFieldArray(
1110 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1111 psb.update(sleClaimID);
1112 }
1113 return ScopeResult{newAttResult, createCID, curAtts};
1114 }();
1115
1116 if (!scopeResult.has_value())
1117 return scopeResult.error();
1118
1119 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1120 auto const& [rewardAccounts, attListChanged] = attResult;
1121
1122 // Account create transactions must happen in order
1123 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1124 {
1125 auto const r = finalizeClaimHelper(
1126 psb,
1127 bridgeSpec,
1128 attBegin->toCreate,
1129 /*dstTag*/ std::nullopt,
1130 doorAccount,
1131 attBegin->sendingAmount,
1132 /*rewardPoolSrc*/ doorAccount,
1133 attBegin->rewardAmount,
1134 *rewardAccounts,
1135 srcChain,
1136 claimIDKeylet,
1137 OnTransferFail::removeClaim,
1138 DepositAuthPolicy::normal,
1139 j);
1140
1141 auto const rTer = r.ter();
1142
1143 if (!isTesSuccess(rTer))
1144 {
1145 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
1146 isTefFailure(rTer))
1147 return rTer;
1148 }
1149 // Move past this claim id even if it fails, so it doesn't block
1150 // subsequent claim ids
1151 auto const sleBridge = psb.peek(bridgeK);
1152 if (!sleBridge)
1153 return tecINTERNAL;
1154 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1155 psb.update(sleBridge);
1156 }
1157 else if (createCID)
1158 {
1159 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1160 (*createdSleClaimID)[sfAccount] = doorAccount;
1161 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1162 (*createdSleClaimID)[sfXChainAccountCreateCount] =
1163 attBegin->createCount;
1164 createdSleClaimID->setFieldArray(
1165 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1166
1167 // Add to owner directory of the door account
1168 auto const page = psb.dirInsert(
1169 keylet::ownerDir(doorAccount),
1170 claimIDKeylet,
1171 describeOwnerDir(doorAccount));
1172 if (!page)
1173 return tecDIR_FULL;
1174 (*createdSleClaimID)[sfOwnerNode] = *page;
1175
1176 auto const sleDoor = psb.peek(doorK);
1177 if (!sleDoor)
1178 return tecINTERNAL;
1179
1180 // Reserve was already checked
1181 adjustOwnerCount(psb, sleDoor, 1, j);
1182 psb.insert(createdSleClaimID);
1183 psb.update(sleDoor);
1184 }
1185
1186 psb.apply(rawView);
1187
1188 return tesSUCCESS;
1189}
1190
1191template <class TAttestation>
1193toClaim(STTx const& tx)
1194{
1195 static_assert(
1198
1199 try
1200 {
1201 STObject o{tx};
1202 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1203 return TAttestation(o);
1204 }
1205 catch (...)
1206 {
1207 }
1208 return std::nullopt;
1209}
1210
1211template <class TAttestation>
1212NotTEC
1213attestationPreflight(PreflightContext const& ctx)
1214{
1215 if (!ctx.rules.enabled(featureXChainBridge))
1216 return temDISABLED;
1217
1218 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1219 return ret;
1220
1221 if (ctx.tx.getFlags() & tfUniversalMask)
1222 return temINVALID_FLAG;
1223
1224 if (!publicKeyType(ctx.tx[sfPublicKey]))
1225 return temMALFORMED;
1226
1227 auto const att = toClaim<TAttestation>(ctx.tx);
1228 if (!att)
1229 return temMALFORMED;
1230
1231 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1232 if (!att->verify(bridgeSpec))
1233 return temXCHAIN_BAD_PROOF;
1234 if (!att->validAmounts())
1235 return temXCHAIN_BAD_PROOF;
1236
1237 if (att->sendingAmount.signum() <= 0)
1238 return temXCHAIN_BAD_PROOF;
1239 auto const expectedIssue =
1240 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1241 if (att->sendingAmount.issue() != expectedIssue)
1242 return temXCHAIN_BAD_PROOF;
1243
1244 return preflight2(ctx);
1245}
1246
1247template <class TAttestation>
1248TER
1249attestationPreclaim(PreclaimContext const& ctx)
1250{
1251 auto const att = toClaim<TAttestation>(ctx.tx);
1252 if (!att)
1253 return tecINTERNAL; // checked in preflight
1254
1255 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1256 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1257 if (!sleBridge)
1258 {
1259 return tecNO_ENTRY;
1260 }
1261
1262 AccountID const attestationSignerAccount{
1263 ctx.tx[sfAttestationSignerAccount]};
1264 PublicKey const pk{ctx.tx[sfPublicKey]};
1265
1266 // signersList is a map from account id to weights
1267 auto const [signersList, quorum, slTer] =
1268 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1269
1270 if (!isTesSuccess(slTer))
1271 return slTer;
1272
1273 return checkAttestationPublicKey(
1274 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1275}
1276
1277template <class TAttestation>
1278TER
1279attestationDoApply(ApplyContext& ctx)
1280{
1281 auto const att = toClaim<TAttestation>(ctx.tx);
1282 if (!att)
1283 // Should already be checked in preflight
1284 return tecINTERNAL;
1285
1286 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1287
1288 struct ScopeResult
1289 {
1290 STXChainBridge::ChainType srcChain;
1292 std::uint32_t quorum;
1293 AccountID thisDoor;
1294 Keylet bridgeK;
1295 };
1296
1297 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1298 // This lambda is ugly - admittedly. The purpose of this lambda is to
1299 // limit the scope of sles so they don't overlap with
1300 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1301 // views, it's important that the sle's lifetime doesn't overlap.
1302 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1303 if (!sleBridge)
1304 {
1305 return Unexpected(tecNO_ENTRY);
1306 }
1307 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1308 AccountID const thisDoor = (*sleBridge)[sfAccount];
1309
1311 {
1312 if (thisDoor == bridgeSpec.lockingChainDoor())
1314 else if (thisDoor == bridgeSpec.issuingChainDoor())
1316 else
1317 return Unexpected(tecINTERNAL);
1318 }
1319 STXChainBridge::ChainType const srcChain =
1321
1322 // signersList is a map from account id to weights
1323 auto [signersList, quorum, slTer] =
1324 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1325
1326 if (!isTesSuccess(slTer))
1327 return Unexpected(slTer);
1328
1329 return ScopeResult{
1330 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1331 }();
1332
1333 if (!scopeResult.has_value())
1334 return scopeResult.error();
1335
1336 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
1337 scopeResult.value();
1338
1339 static_assert(
1342
1344 {
1345 return applyClaimAttestations(
1346 ctx.view(),
1347 ctx.rawView(),
1348 &*att,
1349 &*att + 1,
1350 bridgeSpec,
1351 srcChain,
1352 signersList,
1353 quorum,
1354 ctx.journal);
1355 }
1356 else if constexpr (std::is_same_v<
1357 TAttestation,
1358 Attestations::AttestationCreateAccount>)
1359 {
1360 return applyCreateAccountAttestations(
1361 ctx.view(),
1362 ctx.rawView(),
1363 &*att,
1364 &*att + 1,
1365 thisDoor,
1366 keylet::account(thisDoor),
1367 bridgeSpec,
1368 bridgeK,
1369 srcChain,
1370 signersList,
1371 quorum,
1372 ctx.journal);
1373 }
1374}
1375
1376} // namespace
1377//------------------------------------------------------------------------------
1378
1379NotTEC
1381{
1382 if (!ctx.rules.enabled(featureXChainBridge))
1383 return temDISABLED;
1384
1385 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1386 return ret;
1387
1388 if (ctx.tx.getFlags() & tfUniversalMask)
1389 return temINVALID_FLAG;
1390
1391 auto const account = ctx.tx[sfAccount];
1392 auto const reward = ctx.tx[sfSignatureReward];
1393 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1394 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1395 // Doors must be distinct to help prevent transaction replay attacks
1396 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1397 {
1399 }
1400
1401 if (bridgeSpec.lockingChainDoor() != account &&
1402 bridgeSpec.issuingChainDoor() != account)
1403 {
1405 }
1406
1407 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1408 isXRP(bridgeSpec.issuingChainIssue()))
1409 {
1410 // Because ious and xrp have different numeric ranges, both the src and
1411 // dst issues must be both XRP or both IOU.
1413 }
1414
1415 if (!isXRP(reward) || reward.signum() < 0)
1416 {
1418 }
1419
1420 if (minAccountCreate &&
1421 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1422 !isXRP(bridgeSpec.lockingChainIssue()) ||
1423 !isXRP(bridgeSpec.issuingChainIssue())))
1424 {
1426 }
1427
1428 if (isXRP(bridgeSpec.issuingChainIssue()))
1429 {
1430 // Issuing account must be the root account for XRP (which presumably
1431 // owns all the XRP). This is done so the issuing account can't "run
1432 // out" of wrapped tokens.
1433 static auto const rootAccount = calcAccountID(
1435 KeyType::secp256k1, generateSeed("masterpassphrase"))
1436 .first);
1437 if (bridgeSpec.issuingChainDoor() != rootAccount)
1438 {
1440 }
1441 }
1442 else
1443 {
1444 // Issuing account must be the issuer for non-XRP. This is done so the
1445 // issuing account can't "run out" of wrapped tokens.
1446 if (bridgeSpec.issuingChainDoor() !=
1447 bridgeSpec.issuingChainIssue().account)
1448 {
1450 }
1451 }
1452
1453 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1454 {
1455 // If the locking chain door is locking their own asset, in some sense
1456 // nothing is being locked. Disallow this.
1458 }
1459
1460 return preflight2(ctx);
1461}
1462
1463TER
1465{
1466 auto const account = ctx.tx[sfAccount];
1467 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1468 STXChainBridge::ChainType const chainType =
1469 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1470
1471 {
1472 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1473 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1474 };
1475
1476 if (hasBridge(STXChainBridge::ChainType::issuing) ||
1478 {
1479 return tecDUPLICATE;
1480 }
1481 }
1482
1483 if (!isXRP(bridgeSpec.issue(chainType)))
1484 {
1485 auto const sleIssuer =
1486 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1487
1488 if (!sleIssuer)
1489 return tecNO_ISSUER;
1490
1491 // Allowing clawing back funds would break the bridge's invariant that
1492 // wrapped funds are always backed by locked funds
1493 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1494 return tecNO_PERMISSION;
1495 }
1496
1497 {
1498 // Check reserve
1499 auto const sleAcc = ctx.view.read(keylet::account(account));
1500 if (!sleAcc)
1501 return terNO_ACCOUNT;
1502
1503 auto const balance = (*sleAcc)[sfBalance];
1504 auto const reserve =
1505 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1506
1507 if (balance < reserve)
1509 }
1510
1511 return tesSUCCESS;
1512}
1513
1514TER
1516{
1517 auto const account = ctx_.tx[sfAccount];
1518 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1519 auto const reward = ctx_.tx[sfSignatureReward];
1520 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1521
1522 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1523 if (!sleAcct)
1524 return tecINTERNAL;
1525
1526 STXChainBridge::ChainType const chainType =
1527 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1528
1529 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1530 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1531
1532 (*sleBridge)[sfAccount] = account;
1533 (*sleBridge)[sfSignatureReward] = reward;
1534 if (minAccountCreate)
1535 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1536 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1537 (*sleBridge)[sfXChainClaimID] = 0;
1538 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1539 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1540
1541 // Add to owner directory
1542 {
1543 auto const page = ctx_.view().dirInsert(
1544 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1545 if (!page)
1546 return tecDIR_FULL;
1547 (*sleBridge)[sfOwnerNode] = *page;
1548 }
1549
1550 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1551
1552 ctx_.view().insert(sleBridge);
1553 ctx_.view().update(sleAcct);
1554
1555 return tesSUCCESS;
1556}
1557
1558//------------------------------------------------------------------------------
1559
1560NotTEC
1562{
1563 if (!ctx.rules.enabled(featureXChainBridge))
1564 return temDISABLED;
1565
1566 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1567 return ret;
1568
1569 if (ctx.tx.getFlags() & tfBridgeModifyMask)
1570 return temINVALID_FLAG;
1571
1572 auto const account = ctx.tx[sfAccount];
1573 auto const reward = ctx.tx[~sfSignatureReward];
1574 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1575 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1576 bool const clearAccountCreate =
1578
1579 if (!reward && !minAccountCreate && !clearAccountCreate)
1580 {
1581 // Must change something
1582 return temMALFORMED;
1583 }
1584
1585 if (minAccountCreate && clearAccountCreate)
1586 {
1587 // Can't both clear and set account create in the same txn
1588 return temMALFORMED;
1589 }
1590
1591 if (bridgeSpec.lockingChainDoor() != account &&
1592 bridgeSpec.issuingChainDoor() != account)
1593 {
1595 }
1596
1597 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1598 {
1600 }
1601
1602 if (minAccountCreate &&
1603 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1604 !isXRP(bridgeSpec.lockingChainIssue()) ||
1605 !isXRP(bridgeSpec.issuingChainIssue())))
1606 {
1608 }
1609
1610 return preflight2(ctx);
1611}
1612
1613TER
1615{
1616 auto const account = ctx.tx[sfAccount];
1617 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1618
1619 STXChainBridge::ChainType const chainType =
1620 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1621
1622 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1623 {
1624 return tecNO_ENTRY;
1625 }
1626
1627 return tesSUCCESS;
1628}
1629
1630TER
1632{
1633 auto const account = ctx_.tx[sfAccount];
1634 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1635 auto const reward = ctx_.tx[~sfSignatureReward];
1636 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1637 bool const clearAccountCreate =
1639
1640 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1641 if (!sleAcct)
1642 return tecINTERNAL;
1643
1644 STXChainBridge::ChainType const chainType =
1645 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1646
1647 auto const sleBridge =
1648 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1649 if (!sleBridge)
1650 return tecINTERNAL;
1651
1652 if (reward)
1653 (*sleBridge)[sfSignatureReward] = *reward;
1654 if (minAccountCreate)
1655 {
1656 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1657 }
1658 if (clearAccountCreate &&
1659 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1660 {
1661 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1662 }
1663 ctx_.view().update(sleBridge);
1664
1665 return tesSUCCESS;
1666}
1667
1668//------------------------------------------------------------------------------
1669
1670NotTEC
1672{
1673 if (!ctx.rules.enabled(featureXChainBridge))
1674 return temDISABLED;
1675
1676 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1677 return ret;
1678
1679 if (ctx.tx.getFlags() & tfUniversalMask)
1680 return temINVALID_FLAG;
1681
1682 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1683 auto const amount = ctx.tx[sfAmount];
1684
1685 if (amount.signum() <= 0 ||
1686 (amount.issue() != bridgeSpec.lockingChainIssue() &&
1687 amount.issue() != bridgeSpec.issuingChainIssue()))
1688 {
1689 return temBAD_AMOUNT;
1690 }
1691
1692 return preflight2(ctx);
1693}
1694
1695TER
1697{
1698 AccountID const account = ctx.tx[sfAccount];
1699 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1700 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1701 auto const claimID = ctx.tx[sfXChainClaimID];
1702
1703 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1704 if (!sleBridge)
1705 {
1706 return tecNO_ENTRY;
1707 }
1708
1709 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1710 {
1711 return tecNO_DST;
1712 }
1713
1714 auto const thisDoor = (*sleBridge)[sfAccount];
1715 bool isLockingChain = false;
1716 {
1717 if (thisDoor == bridgeSpec.lockingChainDoor())
1718 isLockingChain = true;
1719 else if (thisDoor == bridgeSpec.issuingChainDoor())
1720 isLockingChain = false;
1721 else
1722 return tecINTERNAL;
1723 }
1724
1725 {
1726 // Check that the amount specified matches the expected issue
1727
1728 if (isLockingChain)
1729 {
1730 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1732 }
1733 else
1734 {
1735 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1737 }
1738 }
1739
1740 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1741 isXRP(bridgeSpec.issuingChainIssue()))
1742 {
1743 // Should have been caught when creating the bridge
1744 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1745 // and the numeric issues that need to be addressed with that.
1746 return tecINTERNAL;
1747 }
1748
1749 auto const otherChainAmount = [&]() -> STAmount {
1750 STAmount r(thisChainAmount);
1751 if (isLockingChain)
1752 r.setIssue(bridgeSpec.issuingChainIssue());
1753 else
1754 r.setIssue(bridgeSpec.lockingChainIssue());
1755 return r;
1756 }();
1757
1758 auto const sleClaimID =
1759 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1760 {
1761 // Check that the sequence number is owned by the sender of this
1762 // transaction
1763 if (!sleClaimID)
1764 {
1765 return tecXCHAIN_NO_CLAIM_ID;
1766 }
1767
1768 if ((*sleClaimID)[sfAccount] != account)
1769 {
1770 // Sequence number isn't owned by the sender of this transaction
1772 }
1773 }
1774
1775 // quorum is checked in `doApply`
1776 return tesSUCCESS;
1777}
1778
1779TER
1781{
1782 PaymentSandbox psb(&ctx_.view());
1783
1784 AccountID const account = ctx_.tx[sfAccount];
1785 auto const dst = ctx_.tx[sfDestination];
1786 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1787 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1788 auto const claimID = ctx_.tx[sfXChainClaimID];
1789 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1790
1791 struct ScopeResult
1792 {
1793 std::vector<AccountID> rewardAccounts;
1794 AccountID rewardPoolSrc;
1795 STAmount sendingAmount;
1797 STAmount signatureReward;
1798 };
1799
1800 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1801 // This lambda is ugly - admittedly. The purpose of this lambda is to
1802 // limit the scope of sles so they don't overlap with
1803 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1804 // views, it's important that the sle's lifetime doesn't overlap.
1805
1806 auto const sleAcct = psb.peek(keylet::account(account));
1807 auto const sleBridge = peekBridge(psb, bridgeSpec);
1808 auto const sleClaimID = psb.peek(claimIDKeylet);
1809
1810 if (!(sleBridge && sleClaimID && sleAcct))
1811 return Unexpected(tecINTERNAL);
1812
1813 AccountID const thisDoor = (*sleBridge)[sfAccount];
1814
1816 {
1817 if (thisDoor == bridgeSpec.lockingChainDoor())
1819 else if (thisDoor == bridgeSpec.issuingChainDoor())
1821 else
1822 return Unexpected(tecINTERNAL);
1823 }
1824 STXChainBridge::ChainType const srcChain =
1826
1827 auto const sendingAmount = [&]() -> STAmount {
1828 STAmount r(thisChainAmount);
1829 r.setIssue(bridgeSpec.issue(srcChain));
1830 return r;
1831 }();
1832
1833 auto const [signersList, quorum, slTer] =
1834 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1835
1836 if (!isTesSuccess(slTer))
1837 return Unexpected(slTer);
1838
1840 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1841
1842 auto const claimR = onClaim(
1843 curAtts,
1844 psb,
1845 sendingAmount,
1846 /*wasLockingChainSend*/ srcChain ==
1848 quorum,
1849 signersList,
1850 ctx_.journal);
1851 if (!claimR.has_value())
1852 return Unexpected(claimR.error());
1853
1854 return ScopeResult{
1855 claimR.value(),
1856 (*sleClaimID)[sfAccount],
1857 sendingAmount,
1858 srcChain,
1859 (*sleClaimID)[sfSignatureReward],
1860 };
1861 }();
1862
1863 if (!scopeResult.has_value())
1864 return scopeResult.error();
1865
1866 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1867 scopeResult.value();
1868 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1869
1870 auto const r = finalizeClaimHelper(
1871 psb,
1872 bridgeSpec,
1873 dst,
1874 dstTag,
1875 /*claimOwner*/ account,
1876 sendingAmount,
1877 rewardPoolSrc,
1878 signatureReward,
1879 rewardAccounts,
1880 srcChain,
1881 claimIDKeylet,
1882 OnTransferFail::keepClaim,
1883 DepositAuthPolicy::dstCanBypass,
1884 ctx_.journal);
1885 if (!r.isTesSuccess())
1886 return r.ter();
1887
1888 psb.apply(ctx_.rawView());
1889
1890 return tesSUCCESS;
1891}
1892
1893//------------------------------------------------------------------------------
1894
1897{
1898 auto const maxSpend = [&] {
1899 auto const amount = ctx.tx[sfAmount];
1900 if (amount.native() && amount.signum() > 0)
1901 return amount.xrp();
1902 return XRPAmount{beast::zero};
1903 }();
1904
1905 return TxConsequences{ctx.tx, maxSpend};
1906}
1907
1908NotTEC
1910{
1911 if (!ctx.rules.enabled(featureXChainBridge))
1912 return temDISABLED;
1913
1914 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1915 return ret;
1916
1917 if (ctx.tx.getFlags() & tfUniversalMask)
1918 return temINVALID_FLAG;
1919
1920 auto const amount = ctx.tx[sfAmount];
1921 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1922
1923 if (amount.signum() <= 0 || !isLegalNet(amount))
1924 return temBAD_AMOUNT;
1925
1926 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
1927 amount.issue() != bridgeSpec.issuingChainIssue())
1928 return temBAD_ISSUER;
1929
1930 return preflight2(ctx);
1931}
1932
1933TER
1935{
1936 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1937 auto const amount = ctx.tx[sfAmount];
1938
1939 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1940 if (!sleBridge)
1941 {
1942 return tecNO_ENTRY;
1943 }
1944
1945 AccountID const thisDoor = (*sleBridge)[sfAccount];
1946 AccountID const account = ctx.tx[sfAccount];
1947
1948 if (thisDoor == account)
1949 {
1950 // Door account can't lock funds onto itself
1951 return tecXCHAIN_SELF_COMMIT;
1952 }
1953
1954 bool isLockingChain = false;
1955 {
1956 if (thisDoor == bridgeSpec.lockingChainDoor())
1957 isLockingChain = true;
1958 else if (thisDoor == bridgeSpec.issuingChainDoor())
1959 isLockingChain = false;
1960 else
1961 return tecINTERNAL;
1962 }
1963
1964 if (isLockingChain)
1965 {
1966 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1968 }
1969 else
1970 {
1971 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1973 }
1974
1975 return tesSUCCESS;
1976}
1977
1978TER
1980{
1981 PaymentSandbox psb(&ctx_.view());
1982
1983 auto const account = ctx_.tx[sfAccount];
1984 auto const amount = ctx_.tx[sfAmount];
1985 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1986
1987 if (!psb.read(keylet::account(account)))
1988 return tecINTERNAL;
1989
1990 auto const sleBridge = readBridge(psb, bridgeSpec);
1991 if (!sleBridge)
1992 return tecINTERNAL;
1993
1994 auto const dst = (*sleBridge)[sfAccount];
1995
1996 // Support dipping into reserves to pay the fee
1997 TransferHelperSubmittingAccountInfo submittingAccountInfo{
1999
2000 auto const thTer = transferHelper(
2001 psb,
2002 account,
2003 dst,
2004 /*dstTag*/ std::nullopt,
2005 /*claimOwner*/ std::nullopt,
2006 amount,
2007 CanCreateDstPolicy::no,
2008 DepositAuthPolicy::normal,
2009 submittingAccountInfo,
2010 ctx_.journal);
2011
2012 if (!isTesSuccess(thTer))
2013 return thTer;
2014
2015 psb.apply(ctx_.rawView());
2016
2017 return tesSUCCESS;
2018}
2019
2020//------------------------------------------------------------------------------
2021
2022NotTEC
2024{
2025 if (!ctx.rules.enabled(featureXChainBridge))
2026 return temDISABLED;
2027
2028 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
2029 return ret;
2030
2031 if (ctx.tx.getFlags() & tfUniversalMask)
2032 return temINVALID_FLAG;
2033
2034 auto const reward = ctx.tx[sfSignatureReward];
2035
2036 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
2038
2039 return preflight2(ctx);
2040}
2041
2042TER
2044{
2045 auto const account = ctx.tx[sfAccount];
2046 auto const bridgeSpec = ctx.tx[sfXChainBridge];
2047 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2048
2049 if (!sleBridge)
2050 {
2051 return tecNO_ENTRY;
2052 }
2053
2054 // Check that the reward matches
2055 auto const reward = ctx.tx[sfSignatureReward];
2056
2057 if (reward != (*sleBridge)[sfSignatureReward])
2058 {
2060 }
2061
2062 {
2063 // Check reserve
2064 auto const sleAcc = ctx.view.read(keylet::account(account));
2065 if (!sleAcc)
2066 return terNO_ACCOUNT;
2067
2068 auto const balance = (*sleAcc)[sfBalance];
2069 auto const reserve =
2070 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
2071
2072 if (balance < reserve)
2074 }
2075
2076 return tesSUCCESS;
2077}
2078
2079TER
2081{
2082 auto const account = ctx_.tx[sfAccount];
2083 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2084 auto const reward = ctx_.tx[sfSignatureReward];
2085 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2086
2087 auto const sleAcct = ctx_.view().peek(keylet::account(account));
2088 if (!sleAcct)
2089 return tecINTERNAL;
2090
2091 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2092 if (!sleBridge)
2093 return tecINTERNAL;
2094
2095 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2096 if (claimID == 0)
2097 return tecINTERNAL; // overflow
2098
2099 (*sleBridge)[sfXChainClaimID] = claimID;
2100
2101 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2102 if (ctx_.view().exists(claimIDKeylet))
2103 return tecINTERNAL; // already checked out!?!
2104
2105 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2106
2107 (*sleClaimID)[sfAccount] = account;
2108 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2109 (*sleClaimID)[sfXChainClaimID] = claimID;
2110 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2111 (*sleClaimID)[sfSignatureReward] = reward;
2112 sleClaimID->setFieldArray(
2113 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
2114
2115 // Add to owner directory
2116 {
2117 auto const page = ctx_.view().dirInsert(
2118 keylet::ownerDir(account),
2119 claimIDKeylet,
2120 describeOwnerDir(account));
2121 if (!page)
2122 return tecDIR_FULL;
2123 (*sleClaimID)[sfOwnerNode] = *page;
2124 }
2125
2126 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2127
2128 ctx_.view().insert(sleClaimID);
2129 ctx_.view().update(sleBridge);
2130 ctx_.view().update(sleAcct);
2131
2132 return tesSUCCESS;
2133}
2134
2135//------------------------------------------------------------------------------
2136
2137NotTEC
2139{
2140 return attestationPreflight<Attestations::AttestationClaim>(ctx);
2141}
2142
2143TER
2145{
2146 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2147}
2148
2149TER
2151{
2152 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2153}
2154
2155//------------------------------------------------------------------------------
2156
2157NotTEC
2159{
2160 return attestationPreflight<Attestations::AttestationCreateAccount>(ctx);
2161}
2162
2163TER
2165{
2166 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2167}
2168
2169TER
2171{
2172 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2173}
2174
2175//------------------------------------------------------------------------------
2176
2177NotTEC
2179{
2180 if (!ctx.rules.enabled(featureXChainBridge))
2181 return temDISABLED;
2182
2183 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
2184 return ret;
2185
2186 if (ctx.tx.getFlags() & tfUniversalMask)
2187 return temINVALID_FLAG;
2188
2189 auto const amount = ctx.tx[sfAmount];
2190
2191 if (amount.signum() <= 0 || !amount.native())
2192 return temBAD_AMOUNT;
2193
2194 auto const reward = ctx.tx[sfSignatureReward];
2195 if (reward.signum() < 0 || !reward.native())
2196 return temBAD_AMOUNT;
2197
2198 if (reward.issue() != amount.issue())
2199 return temBAD_AMOUNT;
2200
2201 return preflight2(ctx);
2202}
2203
2204TER
2206{
2207 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2208 STAmount const amount = ctx.tx[sfAmount];
2209 STAmount const reward = ctx.tx[sfSignatureReward];
2210
2211 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2212 if (!sleBridge)
2213 {
2214 return tecNO_ENTRY;
2215 }
2216
2217 if (reward != (*sleBridge)[sfSignatureReward])
2218 {
2220 }
2221
2222 std::optional<STAmount> const minCreateAmount =
2223 (*sleBridge)[~sfMinAccountCreateAmount];
2224
2225 if (!minCreateAmount)
2227
2228 if (amount < *minCreateAmount)
2230
2231 if (minCreateAmount->issue() != amount.issue())
2233
2234 AccountID const thisDoor = (*sleBridge)[sfAccount];
2235 AccountID const account = ctx.tx[sfAccount];
2236 if (thisDoor == account)
2237 {
2238 // Door account can't lock funds onto itself
2239 return tecXCHAIN_SELF_COMMIT;
2240 }
2241
2243 {
2244 if (thisDoor == bridgeSpec.lockingChainDoor())
2246 else if (thisDoor == bridgeSpec.issuingChainDoor())
2248 else
2249 return tecINTERNAL;
2250 }
2251 STXChainBridge::ChainType const dstChain =
2253
2254 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2256
2257 if (!isXRP(bridgeSpec.issue(dstChain)))
2259
2260 return tesSUCCESS;
2261}
2262
2263TER
2265{
2266 PaymentSandbox psb(&ctx_.view());
2267
2268 AccountID const account = ctx_.tx[sfAccount];
2269 STAmount const amount = ctx_.tx[sfAmount];
2270 STAmount const reward = ctx_.tx[sfSignatureReward];
2271 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2272
2273 auto const sle = psb.peek(keylet::account(account));
2274 if (!sle)
2275 return tecINTERNAL;
2276
2277 auto const sleBridge = peekBridge(psb, bridge);
2278 if (!sleBridge)
2279 return tecINTERNAL;
2280
2281 auto const dst = (*sleBridge)[sfAccount];
2282
2283 // Support dipping into reserves to pay the fee
2284 TransferHelperSubmittingAccountInfo submittingAccountInfo{
2286 STAmount const toTransfer = amount + reward;
2287 auto const thTer = transferHelper(
2288 psb,
2289 account,
2290 dst,
2291 /*dstTag*/ std::nullopt,
2292 /*claimOwner*/ std::nullopt,
2293 toTransfer,
2294 CanCreateDstPolicy::yes,
2295 DepositAuthPolicy::normal,
2296 submittingAccountInfo,
2297 ctx_.journal);
2298
2299 if (!isTesSuccess(thTer))
2300 return thTer;
2301
2302 (*sleBridge)[sfXChainAccountCreateCount] =
2303 (*sleBridge)[sfXChainAccountCreateCount] + 1;
2304 psb.update(sleBridge);
2305
2306 psb.apply(ctx_.rawView());
2307
2308 return tesSUCCESS;
2309}
2310
2311} // 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
ApplyView & view()
beast::Journal const journal
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:317
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static 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:471
Issue const & issue() const
Definition STAmount.h:496
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:145
XRPAmount mPriorBalance
Definition Transactor.h:146
XRPAmount mSourceBalance
Definition Transactor.h:147
ApplyContext & ctx_
Definition Transactor.h:141
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
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T contains(T... args)
T distance(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T is_same_v
T max(T... args)
void check(bool condition, std::string const &message)
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:486
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:380
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:473
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:374
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:330
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:500
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:342
std::uint32_t ownerCount(Env const &env, Account const &account)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:48
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:93
constexpr std::uint32_t tfBridgeModifyMask
Definition TxFlags.h:267
bool isXRP(AccountID const &c)
Definition AccountID.h:90
constexpr size_t xbridgeMaxAccountCreateClaims
std::pair< PublicKey, SecretKey > generateKeyPair(KeyType type, Seed const &seed)
Generate a key pair deterministically.
bool isLegalNet(STAmount const &value)
Definition STAmount.h:600
@ lsfRequireDestTag
@ lsfAllowTrustLineClawback
@ lsfDisableMaster
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:1029
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1047
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
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)
@ tefBAD_LEDGER
Definition TER.h:170
bool isTefFailure(TER x) noexcept
Definition TER.h:662
@ no
Definition Steps.h:45
@ yes
Definition Steps.h:45
std::optional< KeyType > publicKeyType(Slice const &slice)
Returns the type of public key.
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
constexpr std::uint32_t tfClearAccountCreateAmount
Definition TxFlags.h:266
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_DST
Definition TER.h:290
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:350
@ tecXCHAIN_WRONG_CHAIN
Definition TER.h:342
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:346
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:352
@ tecNO_ISSUER
Definition TER.h:299
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:337
@ tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE
Definition TER.h:341
@ tecDIR_FULL
Definition TER.h:287
@ tecXCHAIN_PAYMENT_FAILED
Definition TER.h:349
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:339
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:338
@ tecXCHAIN_PROOF_UNKNOWN_KEY
Definition TER.h:340
@ tecDUPLICATE
Definition TER.h:315
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:347
@ tecINTERNAL
Definition TER.h:310
@ tecXCHAIN_BAD_TRANSFER_ISSUE
Definition TER.h:336
@ tecNO_PERMISSION
Definition TER.h:305
@ tecDST_TAG_NEEDED
Definition TER.h:309
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:343
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition TER.h:345
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:351
@ tecUNFUNDED_PAYMENT
Definition TER.h:285
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:344
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition TER.h:348
@ tecNO_DST_INSUF_XRP
Definition TER.h:291
bool isTerRetry(TER x) noexcept
Definition TER.h:668
@ tesSUCCESS
Definition TER.h:244
bool isTesSuccess(TER x) noexcept
Definition TER.h:674
STLedgerEntry SLE
constexpr std::uint32_t tfUniversalMask
Definition TxFlags.h:63
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:645
bool isTecClaim(TER x) noexcept
Definition TER.h:680
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:605
@ 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.
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:80
ReadView const & view
Definition Transactor.h:83
State information when preflighting a tx.
Definition Transactor.h:35