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 // LCOV_EXCL_START
227 UNREACHABLE(
228 "ripple::claimHelper : invalid inputs"); // should have already
229 // been checked
230 continue;
231 // LCOV_EXCL_STOP
232 }
233 weight += i->second;
234 rewardAccounts.push_back(a.rewardAccount);
235 }
236
237 if (weight >= quorum)
238 return rewardAccounts;
239
241}
242
274struct OnNewAttestationResult
275{
277 // `changed` is true if the attestation collection changed in any way
278 // (added/removed/changed)
279 bool changed{false};
280};
281
282template <class TAttestation>
283[[nodiscard]] OnNewAttestationResult
284onNewAttestations(
285 XChainAttestationsBase<TAttestation>& attestations,
286 ReadView const& view,
287 typename TAttestation::TSignedAttestation const* attBegin,
288 typename TAttestation::TSignedAttestation const* attEnd,
289 std::uint32_t quorum,
292{
293 bool changed = false;
294 for (auto att = attBegin; att != attEnd; ++att)
295 {
296 if (checkAttestationPublicKey(
297 view,
298 signersList,
299 att->attestationSignerAccount,
300 att->publicKey,
301 j) != tesSUCCESS)
302 {
303 // The checkAttestationPublicKey is not strictly necessary here (it
304 // should be checked in a preclaim step), but it would be bad to let
305 // this slip through if that changes, and the check is relatively
306 // cheap, so we check again
307 continue;
308 }
309
310 auto const& claimSigningAccount = att->attestationSignerAccount;
311 if (auto i = std::find_if(
312 attestations.begin(),
313 attestations.end(),
314 [&](auto const& a) {
315 return a.keyAccount == claimSigningAccount;
316 });
317 i != attestations.end())
318 {
319 // existing attestation
320 // replace old attestation with new attestation
321 *i = TAttestation{*att};
322 changed = true;
323 }
324 else
325 {
326 attestations.emplace_back(*att);
327 changed = true;
328 }
329 }
330
331 auto r = claimHelper(
332 attestations,
333 view,
334 typename TAttestation::MatchFields{*attBegin},
335 CheckDst::check,
336 quorum,
337 signersList,
338 j);
339
340 if (!r.has_value())
341 return {std::nullopt, changed};
342
343 return {std::move(r.value()), changed};
344};
345
346// Check if there is a quorurm of attestations for the given amount and
347// chain. If so return the reward accounts, if not return the tec code (most
348// likely tecXCHAIN_CLAIM_NO_QUORUM)
349Expected<std::vector<AccountID>, TER>
350onClaim(
351 XChainClaimAttestations& attestations,
352 ReadView const& view,
353 STAmount const& sendingAmount,
354 bool wasLockingChainSend,
355 std::uint32_t quorum,
358{
359 XChainClaimAttestation::MatchFields toMatch{
360 sendingAmount, wasLockingChainSend, std::nullopt};
361 return claimHelper(
362 attestations, view, toMatch, CheckDst::ignore, quorum, signersList, j);
363}
364
365enum class CanCreateDstPolicy { no, yes };
366
367enum class DepositAuthPolicy { normal, dstCanBypass };
368
369// Allow the fee to dip into the reserve. To support this, information about the
370// submitting account needs to be fed to the transfer helper.
371struct TransferHelperSubmittingAccountInfo
372{
373 AccountID account;
374 STAmount preFeeBalance;
375 STAmount postFeeBalance;
376};
377
400TER
401transferHelper(
402 PaymentSandbox& psb,
403 AccountID const& src,
404 AccountID const& dst,
405 std::optional<std::uint32_t> const& dstTag,
406 std::optional<AccountID> const& claimOwner,
407 STAmount const& amt,
408 CanCreateDstPolicy canCreate,
409 DepositAuthPolicy depositAuthPolicy,
411 submittingAccountInfo,
413{
414 if (dst == src)
415 return tesSUCCESS;
416
417 auto const dstK = keylet::account(dst);
418 if (auto sleDst = psb.read(dstK))
419 {
420 // Check dst tag and deposit auth
421
422 if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag)
423 return tecDST_TAG_NEEDED;
424
425 // If the destination is the claim owner, and this is a claim
426 // transaction, that's the dst account sending funds to itself. It
427 // can bypass deposit auth.
428 bool const canBypassDepositAuth = dst == claimOwner &&
429 depositAuthPolicy == DepositAuthPolicy::dstCanBypass;
430
431 if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) &&
432 !psb.exists(keylet::depositPreauth(dst, src)))
433 {
434 return tecNO_PERMISSION;
435 }
436 }
437 else if (!amt.native() || canCreate == CanCreateDstPolicy::no)
438 {
439 return tecNO_DST;
440 }
441
442 if (amt.native())
443 {
444 auto const sleSrc = psb.peek(keylet::account(src));
445 XRPL_ASSERT(sleSrc, "ripple::transferHelper : non-null source account");
446 if (!sleSrc)
447 return tecINTERNAL; // LCOV_EXCL_LINE
448
449 {
450 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
451 auto const reserve = psb.fees().accountReserve(ownerCount);
452
453 auto const availableBalance = [&]() -> STAmount {
454 STAmount const curBal = (*sleSrc)[sfBalance];
455 // Checking that account == src and postFeeBalance == curBal is
456 // not strictly nessisary, but helps protect against future
457 // changes
458 if (!submittingAccountInfo ||
459 submittingAccountInfo->account != src ||
460 submittingAccountInfo->postFeeBalance != curBal)
461 return curBal;
462 return submittingAccountInfo->preFeeBalance;
463 }();
464
465 if (availableBalance < amt + reserve)
466 {
467 return tecUNFUNDED_PAYMENT;
468 }
469 }
470
471 auto sleDst = psb.peek(dstK);
472 if (!sleDst)
473 {
474 if (canCreate == CanCreateDstPolicy::no)
475 {
476 // Already checked, but OK to check again
477 return tecNO_DST;
478 }
479 if (amt < psb.fees().reserve)
480 {
481 JLOG(j.trace()) << "Insufficient payment to create account.";
482 return tecNO_DST_INSUF_XRP;
483 }
484
485 // Create the account.
486 std::uint32_t const seqno{
487 psb.rules().enabled(featureDeletableAccounts) ? psb.seq() : 1};
488
489 sleDst = std::make_shared<SLE>(dstK);
490 sleDst->setAccountID(sfAccount, dst);
491 sleDst->setFieldU32(sfSequence, seqno);
492
493 psb.insert(sleDst);
494 }
495
496 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
497 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
498 psb.update(sleSrc);
499 psb.update(sleDst);
500
501 return tesSUCCESS;
502 }
503
504 auto const result = flow(
505 psb,
506 amt,
507 src,
508 dst,
509 STPathSet{},
510 /*default path*/ true,
511 /*partial payment*/ false,
512 /*owner pays transfer fee*/ true,
513 /*offer crossing*/ OfferCrossing::no,
514 /*limit quality*/ std::nullopt,
515 /*sendmax*/ std::nullopt,
516 /*domain id*/ std::nullopt,
517 j);
518
519 if (auto const r = result.result();
520 isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
521 return r;
523}
524
530enum class OnTransferFail {
532 removeClaim,
534 keepClaim
535};
536
537struct FinalizeClaimHelperResult
538{
540 std::optional<TER> mainFundsTer;
541 // TER for transfering the reward funds
542 std::optional<TER> rewardTer;
543 // TER for removing the sle (if is sle is to be removed)
544 std::optional<TER> rmSleTer;
545
546 // Helper to check for overall success. If there wasn't overall success the
547 // individual ters can be used to decide what needs to be done.
548 bool
549 isTesSuccess() const
550 {
551 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS &&
552 (!rmSleTer || *rmSleTer == tesSUCCESS);
553 }
554
555 TER
556 ter() const
557 {
558 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) &&
559 (!rewardTer || *rewardTer == tesSUCCESS) &&
560 (!rmSleTer || *rmSleTer == tesSUCCESS))
561 return tesSUCCESS;
562
563 // if any phase return a tecINTERNAL or a tef, prefer returning those
564 // codes
565 if (mainFundsTer &&
566 (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
567 return *mainFundsTer;
568 if (rewardTer &&
569 (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
570 return *rewardTer;
571 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
572 return *rmSleTer;
573
574 // Only after the tecINTERNAL and tef are checked, return the first
575 // non-success error code.
576 if (mainFundsTer && mainFundsTer != tesSUCCESS)
577 return *mainFundsTer;
578 if (rewardTer && rewardTer != tesSUCCESS)
579 return *rewardTer;
580 if (rmSleTer && rmSleTer != tesSUCCESS)
581 return *rmSleTer;
582 return tesSUCCESS;
583 }
584};
585
614FinalizeClaimHelperResult
615finalizeClaimHelper(
616 PaymentSandbox& outerSb,
617 STXChainBridge const& bridgeSpec,
618 AccountID const& dst,
619 std::optional<std::uint32_t> const& dstTag,
620 AccountID const& claimOwner,
621 STAmount const& sendingAmount,
622 AccountID const& rewardPoolSrc,
623 STAmount const& rewardPool,
624 std::vector<AccountID> const& rewardAccounts,
625 STXChainBridge::ChainType const srcChain,
626 Keylet const& claimIDKeylet,
627 OnTransferFail onTransferFail,
628 DepositAuthPolicy depositAuthPolicy,
630{
631 FinalizeClaimHelperResult result;
632
633 STXChainBridge::ChainType const dstChain =
635 STAmount const thisChainAmount = [&] {
636 STAmount r = sendingAmount;
637 r.setIssue(bridgeSpec.issue(dstChain));
638 return r;
639 }();
640 auto const& thisDoor = bridgeSpec.door(dstChain);
641
642 {
643 PaymentSandbox innerSb{&outerSb};
644 // If distributing the reward pool fails, the mainFunds transfer should
645 // be rolled back
646 //
647 // If the claimid is removed, the rewards should be distributed
648 // even if the mainFunds fails.
649 //
650 // If OnTransferFail::removeClaim, the claim should be removed even if
651 // the rewards cannot be distributed.
652
653 // transfer funds to the dst
654 result.mainFundsTer = transferHelper(
655 innerSb,
656 thisDoor,
657 dst,
658 dstTag,
659 claimOwner,
660 thisChainAmount,
661 CanCreateDstPolicy::yes,
662 depositAuthPolicy,
664 j);
665
666 if (!isTesSuccess(*result.mainFundsTer) &&
667 onTransferFail == OnTransferFail::keepClaim)
668 {
669 return result;
670 }
671
672 // handle the reward pool
673 result.rewardTer = [&]() -> TER {
674 if (rewardAccounts.empty())
675 return tesSUCCESS;
676
677 // distribute the reward pool
678 // if the transfer failed, distribute the pool for "OnTransferFail"
679 // cases (the attesters did their job)
680 STAmount const share = [&] {
681 auto const round_mode =
682 innerSb.rules().enabled(fixXChainRewardRounding)
685 saveNumberRoundMode _{Number::setround(round_mode)};
686
687 STAmount const den{rewardAccounts.size()};
688 return divide(rewardPool, den, rewardPool.issue());
689 }();
690 STAmount distributed = rewardPool.zeroed();
691 for (auto const& rewardAccount : rewardAccounts)
692 {
693 auto const thTer = transferHelper(
694 innerSb,
695 rewardPoolSrc,
696 rewardAccount,
697 /*dstTag*/ std::nullopt,
698 // claim owner is not relevant to distributing rewards
699 /*claimOwner*/ std::nullopt,
700 share,
701 CanCreateDstPolicy::no,
702 DepositAuthPolicy::normal,
704 j);
705
706 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
707 return thTer;
708
709 if (isTesSuccess(thTer))
710 distributed += share;
711
712 // let txn succeed if error distributing rewards (other than
713 // inability to pay)
714 }
715
716 if (distributed > rewardPool)
717 return tecINTERNAL; // LCOV_EXCL_LINE
718
719 return tesSUCCESS;
720 }();
721
722 if (!isTesSuccess(*result.rewardTer) &&
723 (onTransferFail == OnTransferFail::keepClaim ||
724 *result.rewardTer == tecINTERNAL))
725 {
726 return result;
727 }
728
729 if (!isTesSuccess(*result.mainFundsTer) ||
730 isTesSuccess(*result.rewardTer))
731 {
732 // Note: if the mainFunds transfer succeeds and the result transfer
733 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
734 // rolled back)
735 innerSb.apply(outerSb);
736 }
737 }
738
739 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
740 {
741 auto const cidOwner = (*sleClaimID)[sfAccount];
742 {
743 // Remove the claim id
744 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
745 auto const page = (*sleClaimID)[sfOwnerNode];
746 if (!outerSb.dirRemove(
747 keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
748 {
749 JLOG(j.fatal())
750 << "Unable to delete xchain seq number from owner.";
751 result.rmSleTer = tefBAD_LEDGER;
752 return result;
753 }
754
755 // Remove the claim id from the ledger
756 outerSb.erase(sleClaimID);
757
758 adjustOwnerCount(outerSb, sleOwner, -1, j);
759 }
760 }
761
762 return result;
763}
764
775getSignersListAndQuorum(
776 ReadView const& view,
777 SLE const& sleBridge,
779{
782
783 AccountID const thisDoor = sleBridge[sfAccount];
784 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
785
786 if (!sleDoor)
787 {
788 return {r, q, tecINTERNAL};
789 }
790
791 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
792 if (!sleS)
793 {
794 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
795 }
796 q = (*sleS)[sfSignerQuorum];
797
798 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
799
800 if (!accountSigners)
801 {
802 return {r, q, tecINTERNAL};
803 }
804
805 for (auto const& as : *accountSigners)
806 {
807 r[as.account] = as.weight;
808 }
809
810 return {std::move(r), q, tesSUCCESS};
811};
812
813template <class R, class F>
815readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
816{
817 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
818 if (auto r = getter(bridgeSpec, ct))
819 {
820 if ((*r)[sfXChainBridge] == bridgeSpec)
821 return r;
822 }
823 return nullptr;
824 };
825 if (auto r = tryGet(STXChainBridge::ChainType::locking))
826 return r;
828}
829
831peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
832{
833 return readOrpeekBridge<SLE>(
834 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
835 -> std::shared_ptr<SLE> { return v.peek(keylet::bridge(b, ct)); },
836 bridgeSpec);
837}
838
840readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
841{
842 return readOrpeekBridge<SLE const>(
843 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct)
845 return v.read(keylet::bridge(b, ct));
846 },
847 bridgeSpec);
848}
849
850// Precondition: all the claims in the range are consistent. They must sign for
851// the same event (amount, sending account, claim id, etc).
852template <class TIter>
853TER
854applyClaimAttestations(
855 ApplyView& view,
856 RawView& rawView,
857 TIter attBegin,
858 TIter attEnd,
859 STXChainBridge const& bridgeSpec,
860 STXChainBridge::ChainType const srcChain,
862 std::uint32_t quorum,
864{
865 if (attBegin == attEnd)
866 return tesSUCCESS;
867
868 PaymentSandbox psb(&view);
869
870 auto const claimIDKeylet =
871 keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
872
873 struct ScopeResult
874 {
875 OnNewAttestationResult newAttResult;
876 STAmount rewardAmount;
877 AccountID cidOwner;
878 };
879
880 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
881 // This lambda is ugly - admittedly. The purpose of this lambda is to
882 // limit the scope of sles so they don't overlap with
883 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
884 // views, it's important that the sle's lifetime doesn't overlap.
885 auto const sleClaimID = psb.peek(claimIDKeylet);
886 if (!sleClaimID)
888
889 // Add claims that are part of the signer's list to the "claims" vector
891 atts.reserve(std::distance(attBegin, attEnd));
892 for (auto att = attBegin; att != attEnd; ++att)
893 {
894 if (!signersList.contains(att->attestationSignerAccount))
895 continue;
896 atts.push_back(*att);
897 }
898
899 if (atts.empty())
900 {
902 }
903
904 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
905 if (attBegin->sendingAccount != otherChainSource)
906 {
908 }
909
910 {
911 STXChainBridge::ChainType const dstChain =
913
914 STXChainBridge::ChainType const attDstChain =
915 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
916
917 if (attDstChain != dstChain)
918 {
920 }
921 }
922
923 XChainClaimAttestations curAtts{
924 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
925
926 auto const newAttResult = onNewAttestations(
927 curAtts,
928 view,
929 &atts[0],
930 &atts[0] + atts.size(),
931 quorum,
932 signersList,
933 j);
934
935 // update the claim id
936 sleClaimID->setFieldArray(
937 sfXChainClaimAttestations, curAtts.toSTArray());
938 psb.update(sleClaimID);
939
940 return ScopeResult{
941 newAttResult,
942 (*sleClaimID)[sfSignatureReward],
943 (*sleClaimID)[sfAccount]};
944 }();
945
946 if (!scopeResult.has_value())
947 return scopeResult.error();
948
949 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
950 auto const& [rewardAccounts, attListChanged] = newAttResult;
951 if (rewardAccounts && attBegin->dst)
952 {
953 auto const r = finalizeClaimHelper(
954 psb,
955 bridgeSpec,
956 *attBegin->dst,
957 /*dstTag*/ std::nullopt,
958 cidOwner,
959 attBegin->sendingAmount,
960 cidOwner,
961 rewardAmount,
962 *rewardAccounts,
963 srcChain,
964 claimIDKeylet,
965 OnTransferFail::keepClaim,
966 DepositAuthPolicy::normal,
967 j);
968
969 auto const rTer = r.ter();
970
971 if (!isTesSuccess(rTer) &&
972 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
973 return rTer;
974 }
975
976 psb.apply(rawView);
977
978 return tesSUCCESS;
979}
980
981template <class TIter>
982TER
983applyCreateAccountAttestations(
984 ApplyView& view,
985 RawView& rawView,
986 TIter attBegin,
987 TIter attEnd,
988 AccountID const& doorAccount,
989 Keylet const& doorK,
990 STXChainBridge const& bridgeSpec,
991 Keylet const& bridgeK,
992 STXChainBridge::ChainType const srcChain,
994 std::uint32_t quorum,
996{
997 if (attBegin == attEnd)
998 return tesSUCCESS;
999
1000 PaymentSandbox psb(&view);
1001
1002 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
1003 auto const sleBridge = psb.peek(bridgeK);
1004 if (!sleBridge)
1005 return Unexpected(tecINTERNAL);
1006
1007 return (*sleBridge)[sfXChainAccountClaimCount];
1008 }();
1009
1010 if (!claimCountResult.has_value())
1011 return claimCountResult.error();
1012
1013 std::uint64_t const claimCount = claimCountResult.value();
1014
1015 if (attBegin->createCount <= claimCount)
1016 {
1018 }
1019 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
1020 {
1021 // Limit the number of claims on the account
1023 }
1024
1025 {
1026 STXChainBridge::ChainType const dstChain =
1028
1029 STXChainBridge::ChainType const attDstChain =
1030 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
1031
1032 if (attDstChain != dstChain)
1033 {
1034 return tecXCHAIN_WRONG_CHAIN;
1035 }
1036 }
1037
1038 auto const claimIDKeylet =
1039 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1040
1041 struct ScopeResult
1042 {
1043 OnNewAttestationResult newAttResult;
1044 bool createCID;
1045 XChainCreateAccountAttestations curAtts;
1046 };
1047
1048 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1049 // This lambda is ugly - admittedly. The purpose of this lambda is to
1050 // limit the scope of sles so they don't overlap with
1051 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1052 // views, it's important that the sle's lifetime doesn't overlap.
1053
1054 // sleClaimID may be null. If it's null it isn't created until the end
1055 // of this function (if needed)
1056 auto const sleClaimID = psb.peek(claimIDKeylet);
1057 bool createCID = false;
1058 if (!sleClaimID)
1059 {
1060 createCID = true;
1061
1062 auto const sleDoor = psb.peek(doorK);
1063 if (!sleDoor)
1064 return Unexpected(tecINTERNAL);
1065
1066 // Check reserve
1067 auto const balance = (*sleDoor)[sfBalance];
1068 auto const reserve =
1069 psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
1070
1071 if (balance < reserve)
1073 }
1074
1076 atts.reserve(std::distance(attBegin, attEnd));
1077 for (auto att = attBegin; att != attEnd; ++att)
1078 {
1079 if (!signersList.contains(att->attestationSignerAccount))
1080 continue;
1081 atts.push_back(*att);
1082 }
1083 if (atts.empty())
1084 {
1086 }
1087
1088 XChainCreateAccountAttestations curAtts = [&] {
1089 if (sleClaimID)
1090 return XChainCreateAccountAttestations{
1091 sleClaimID->getFieldArray(
1092 sfXChainCreateAccountAttestations)};
1093 return XChainCreateAccountAttestations{};
1094 }();
1095
1096 auto const newAttResult = onNewAttestations(
1097 curAtts,
1098 view,
1099 &atts[0],
1100 &atts[0] + atts.size(),
1101 quorum,
1102 signersList,
1103 j);
1104
1105 if (!createCID)
1106 {
1107 // Modify the object before it's potentially deleted, so the meta
1108 // data will include the new attestations
1109 if (!sleClaimID)
1110 return Unexpected(tecINTERNAL);
1111 sleClaimID->setFieldArray(
1112 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1113 psb.update(sleClaimID);
1114 }
1115 return ScopeResult{newAttResult, createCID, curAtts};
1116 }();
1117
1118 if (!scopeResult.has_value())
1119 return scopeResult.error();
1120
1121 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1122 auto const& [rewardAccounts, attListChanged] = attResult;
1123
1124 // Account create transactions must happen in order
1125 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1126 {
1127 auto const r = finalizeClaimHelper(
1128 psb,
1129 bridgeSpec,
1130 attBegin->toCreate,
1131 /*dstTag*/ std::nullopt,
1132 doorAccount,
1133 attBegin->sendingAmount,
1134 /*rewardPoolSrc*/ doorAccount,
1135 attBegin->rewardAmount,
1136 *rewardAccounts,
1137 srcChain,
1138 claimIDKeylet,
1139 OnTransferFail::removeClaim,
1140 DepositAuthPolicy::normal,
1141 j);
1142
1143 auto const rTer = r.ter();
1144
1145 if (!isTesSuccess(rTer))
1146 {
1147 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT ||
1148 isTefFailure(rTer))
1149 return rTer;
1150 }
1151 // Move past this claim id even if it fails, so it doesn't block
1152 // subsequent claim ids
1153 auto const sleBridge = psb.peek(bridgeK);
1154 if (!sleBridge)
1155 return tecINTERNAL; // LCOV_EXCL_LINE
1156 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1157 psb.update(sleBridge);
1158 }
1159 else if (createCID)
1160 {
1161 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1162 (*createdSleClaimID)[sfAccount] = doorAccount;
1163 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1164 (*createdSleClaimID)[sfXChainAccountCreateCount] =
1165 attBegin->createCount;
1166 createdSleClaimID->setFieldArray(
1167 sfXChainCreateAccountAttestations, curAtts.toSTArray());
1168
1169 // Add to owner directory of the door account
1170 auto const page = psb.dirInsert(
1171 keylet::ownerDir(doorAccount),
1172 claimIDKeylet,
1173 describeOwnerDir(doorAccount));
1174 if (!page)
1175 return tecDIR_FULL; // LCOV_EXCL_LINE
1176 (*createdSleClaimID)[sfOwnerNode] = *page;
1177
1178 auto const sleDoor = psb.peek(doorK);
1179 if (!sleDoor)
1180 return tecINTERNAL; // LCOV_EXCL_LINE
1181
1182 // Reserve was already checked
1183 adjustOwnerCount(psb, sleDoor, 1, j);
1184 psb.insert(createdSleClaimID);
1185 psb.update(sleDoor);
1186 }
1187
1188 psb.apply(rawView);
1189
1190 return tesSUCCESS;
1191}
1192
1193template <class TAttestation>
1195toClaim(STTx const& tx)
1196{
1197 static_assert(
1200
1201 try
1202 {
1203 STObject o{tx};
1204 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1205 return TAttestation(o);
1206 }
1207 catch (...)
1208 {
1209 }
1210 return std::nullopt;
1211}
1212
1213template <class TAttestation>
1214NotTEC
1215attestationpreflight(PreflightContext const& ctx)
1216{
1217 if (!publicKeyType(ctx.tx[sfPublicKey]))
1218 return temMALFORMED;
1219
1220 auto const att = toClaim<TAttestation>(ctx.tx);
1221 if (!att)
1222 return temMALFORMED;
1223
1224 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1225 if (!att->verify(bridgeSpec))
1226 return temXCHAIN_BAD_PROOF;
1227 if (!att->validAmounts())
1228 return temXCHAIN_BAD_PROOF;
1229
1230 if (att->sendingAmount.signum() <= 0)
1231 return temXCHAIN_BAD_PROOF;
1232 auto const expectedIssue =
1233 bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1234 if (att->sendingAmount.issue() != expectedIssue)
1235 return temXCHAIN_BAD_PROOF;
1236
1237 return tesSUCCESS;
1238}
1239
1240template <class TAttestation>
1241TER
1242attestationPreclaim(PreclaimContext const& ctx)
1243{
1244 auto const att = toClaim<TAttestation>(ctx.tx);
1245 // checked in preflight
1246 if (!att)
1247 return tecINTERNAL; // LCOV_EXCL_LINE
1248
1249 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1250 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1251 if (!sleBridge)
1252 {
1253 return tecNO_ENTRY;
1254 }
1255
1256 AccountID const attestationSignerAccount{
1257 ctx.tx[sfAttestationSignerAccount]};
1258 PublicKey const pk{ctx.tx[sfPublicKey]};
1259
1260 // signersList is a map from account id to weights
1261 auto const [signersList, quorum, slTer] =
1262 getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1263
1264 if (!isTesSuccess(slTer))
1265 return slTer;
1266
1267 return checkAttestationPublicKey(
1268 ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1269}
1270
1271template <class TAttestation>
1272TER
1273attestationDoApply(ApplyContext& ctx)
1274{
1275 auto const att = toClaim<TAttestation>(ctx.tx);
1276 if (!att)
1277 // Should already be checked in preflight
1278 return tecINTERNAL; // LCOV_EXCL_LINE
1279
1280 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1281
1282 struct ScopeResult
1283 {
1284 STXChainBridge::ChainType srcChain;
1286 std::uint32_t quorum;
1287 AccountID thisDoor;
1288 Keylet bridgeK;
1289 };
1290
1291 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1292 // This lambda is ugly - admittedly. The purpose of this lambda is to
1293 // limit the scope of sles so they don't overlap with
1294 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1295 // views, it's important that the sle's lifetime doesn't overlap.
1296 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1297 if (!sleBridge)
1298 {
1299 return Unexpected(tecNO_ENTRY);
1300 }
1301 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1302 AccountID const thisDoor = (*sleBridge)[sfAccount];
1303
1305 {
1306 if (thisDoor == bridgeSpec.lockingChainDoor())
1308 else if (thisDoor == bridgeSpec.issuingChainDoor())
1310 else
1311 return Unexpected(tecINTERNAL);
1312 }
1313 STXChainBridge::ChainType const srcChain =
1315
1316 // signersList is a map from account id to weights
1317 auto [signersList, quorum, slTer] =
1318 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1319
1320 if (!isTesSuccess(slTer))
1321 return Unexpected(slTer);
1322
1323 return ScopeResult{
1324 srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1325 }();
1326
1327 if (!scopeResult.has_value())
1328 return scopeResult.error();
1329
1330 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
1331 scopeResult.value();
1332
1333 static_assert(
1336
1338 {
1339 return applyClaimAttestations(
1340 ctx.view(),
1341 ctx.rawView(),
1342 &*att,
1343 &*att + 1,
1344 bridgeSpec,
1345 srcChain,
1346 signersList,
1347 quorum,
1348 ctx.journal);
1349 }
1350 else if constexpr (std::is_same_v<
1351 TAttestation,
1352 Attestations::AttestationCreateAccount>)
1353 {
1354 return applyCreateAccountAttestations(
1355 ctx.view(),
1356 ctx.rawView(),
1357 &*att,
1358 &*att + 1,
1359 thisDoor,
1360 keylet::account(thisDoor),
1361 bridgeSpec,
1362 bridgeK,
1363 srcChain,
1364 signersList,
1365 quorum,
1366 ctx.journal);
1367 }
1368}
1369
1370} // namespace
1371//------------------------------------------------------------------------------
1372
1373NotTEC
1375{
1376 auto const account = ctx.tx[sfAccount];
1377 auto const reward = ctx.tx[sfSignatureReward];
1378 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1379 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1380 // Doors must be distinct to help prevent transaction replay attacks
1381 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1382 {
1384 }
1385
1386 if (bridgeSpec.lockingChainDoor() != account &&
1387 bridgeSpec.issuingChainDoor() != account)
1388 {
1390 }
1391
1392 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1393 isXRP(bridgeSpec.issuingChainIssue()))
1394 {
1395 // Because ious and xrp have different numeric ranges, both the src and
1396 // dst issues must be both XRP or both IOU.
1398 }
1399
1400 if (!isXRP(reward) || reward.signum() < 0)
1401 {
1403 }
1404
1405 if (minAccountCreate &&
1406 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1407 !isXRP(bridgeSpec.lockingChainIssue()) ||
1408 !isXRP(bridgeSpec.issuingChainIssue())))
1409 {
1411 }
1412
1413 if (isXRP(bridgeSpec.issuingChainIssue()))
1414 {
1415 // Issuing account must be the root account for XRP (which presumably
1416 // owns all the XRP). This is done so the issuing account can't "run
1417 // out" of wrapped tokens.
1418 static auto const rootAccount = calcAccountID(
1420 KeyType::secp256k1, generateSeed("masterpassphrase"))
1421 .first);
1422 if (bridgeSpec.issuingChainDoor() != rootAccount)
1423 {
1425 }
1426 }
1427 else
1428 {
1429 // Issuing account must be the issuer for non-XRP. This is done so the
1430 // issuing account can't "run out" of wrapped tokens.
1431 if (bridgeSpec.issuingChainDoor() !=
1432 bridgeSpec.issuingChainIssue().account)
1433 {
1435 }
1436 }
1437
1438 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1439 {
1440 // If the locking chain door is locking their own asset, in some sense
1441 // nothing is being locked. Disallow this.
1443 }
1444
1445 return tesSUCCESS;
1446}
1447
1448TER
1450{
1451 auto const account = ctx.tx[sfAccount];
1452 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1453 STXChainBridge::ChainType const chainType =
1454 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1455
1456 {
1457 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1458 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1459 };
1460
1461 if (hasBridge(STXChainBridge::ChainType::issuing) ||
1463 {
1464 return tecDUPLICATE;
1465 }
1466 }
1467
1468 if (!isXRP(bridgeSpec.issue(chainType)))
1469 {
1470 auto const sleIssuer =
1471 ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1472
1473 if (!sleIssuer)
1474 return tecNO_ISSUER;
1475
1476 // Allowing clawing back funds would break the bridge's invariant that
1477 // wrapped funds are always backed by locked funds
1478 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1479 return tecNO_PERMISSION;
1480 }
1481
1482 {
1483 // Check reserve
1484 auto const sleAcc = ctx.view.read(keylet::account(account));
1485 if (!sleAcc)
1486 return terNO_ACCOUNT;
1487
1488 auto const balance = (*sleAcc)[sfBalance];
1489 auto const reserve =
1490 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1491
1492 if (balance < reserve)
1494 }
1495
1496 return tesSUCCESS;
1497}
1498
1499TER
1501{
1502 auto const account = ctx_.tx[sfAccount];
1503 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1504 auto const reward = ctx_.tx[sfSignatureReward];
1505 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1506
1507 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1508 if (!sleAcct)
1509 return tecINTERNAL; // LCOV_EXCL_LINE
1510
1511 STXChainBridge::ChainType const chainType =
1512 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1513
1514 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1515 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1516
1517 (*sleBridge)[sfAccount] = account;
1518 (*sleBridge)[sfSignatureReward] = reward;
1519 if (minAccountCreate)
1520 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1521 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1522 (*sleBridge)[sfXChainClaimID] = 0;
1523 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1524 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1525
1526 // Add to owner directory
1527 {
1528 auto const page = ctx_.view().dirInsert(
1529 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1530 if (!page)
1531 return tecDIR_FULL; // LCOV_EXCL_LINE
1532 (*sleBridge)[sfOwnerNode] = *page;
1533 }
1534
1535 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1536
1537 ctx_.view().insert(sleBridge);
1538 ctx_.view().update(sleAcct);
1539
1540 return tesSUCCESS;
1541}
1542
1543//------------------------------------------------------------------------------
1544
1550
1551NotTEC
1553{
1554 auto const account = ctx.tx[sfAccount];
1555 auto const reward = ctx.tx[~sfSignatureReward];
1556 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1557 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1558 bool const clearAccountCreate =
1560
1561 if (!reward && !minAccountCreate && !clearAccountCreate)
1562 {
1563 // Must change something
1564 return temMALFORMED;
1565 }
1566
1567 if (minAccountCreate && clearAccountCreate)
1568 {
1569 // Can't both clear and set account create in the same txn
1570 return temMALFORMED;
1571 }
1572
1573 if (bridgeSpec.lockingChainDoor() != account &&
1574 bridgeSpec.issuingChainDoor() != account)
1575 {
1577 }
1578
1579 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1580 {
1582 }
1583
1584 if (minAccountCreate &&
1585 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1586 !isXRP(bridgeSpec.lockingChainIssue()) ||
1587 !isXRP(bridgeSpec.issuingChainIssue())))
1588 {
1590 }
1591
1592 return tesSUCCESS;
1593}
1594
1595TER
1597{
1598 auto const account = ctx.tx[sfAccount];
1599 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1600
1601 STXChainBridge::ChainType const chainType =
1602 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1603
1604 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1605 {
1606 return tecNO_ENTRY;
1607 }
1608
1609 return tesSUCCESS;
1610}
1611
1612TER
1614{
1615 auto const account = ctx_.tx[sfAccount];
1616 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1617 auto const reward = ctx_.tx[~sfSignatureReward];
1618 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1619 bool const clearAccountCreate =
1621
1622 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1623 if (!sleAcct)
1624 return tecINTERNAL; // LCOV_EXCL_LINE
1625
1626 STXChainBridge::ChainType const chainType =
1627 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1628
1629 auto const sleBridge =
1630 ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1631 if (!sleBridge)
1632 return tecINTERNAL; // LCOV_EXCL_LINE
1633
1634 if (reward)
1635 (*sleBridge)[sfSignatureReward] = *reward;
1636 if (minAccountCreate)
1637 {
1638 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1639 }
1640 if (clearAccountCreate &&
1641 sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1642 {
1643 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1644 }
1645 ctx_.view().update(sleBridge);
1646
1647 return tesSUCCESS;
1648}
1649
1650//------------------------------------------------------------------------------
1651
1652NotTEC
1654{
1655 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1656 auto const amount = ctx.tx[sfAmount];
1657
1658 if (amount.signum() <= 0 ||
1659 (amount.issue() != bridgeSpec.lockingChainIssue() &&
1660 amount.issue() != bridgeSpec.issuingChainIssue()))
1661 {
1662 return temBAD_AMOUNT;
1663 }
1664
1665 return tesSUCCESS;
1666}
1667
1668TER
1670{
1671 AccountID const account = ctx.tx[sfAccount];
1672 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1673 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1674 auto const claimID = ctx.tx[sfXChainClaimID];
1675
1676 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1677 if (!sleBridge)
1678 {
1679 return tecNO_ENTRY;
1680 }
1681
1682 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1683 {
1684 return tecNO_DST;
1685 }
1686
1687 auto const thisDoor = (*sleBridge)[sfAccount];
1688 bool isLockingChain = false;
1689 {
1690 if (thisDoor == bridgeSpec.lockingChainDoor())
1691 isLockingChain = true;
1692 else if (thisDoor == bridgeSpec.issuingChainDoor())
1693 isLockingChain = false;
1694 else
1695 return tecINTERNAL; // LCOV_EXCL_LINE
1696 }
1697
1698 {
1699 // Check that the amount specified matches the expected issue
1700
1701 if (isLockingChain)
1702 {
1703 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1705 }
1706 else
1707 {
1708 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1710 }
1711 }
1712
1713 if (isXRP(bridgeSpec.lockingChainIssue()) !=
1714 isXRP(bridgeSpec.issuingChainIssue()))
1715 {
1716 // Should have been caught when creating the bridge
1717 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1718 // and the numeric issues that need to be addressed with that.
1719 return tecINTERNAL; // LCOV_EXCL_LINE
1720 }
1721
1722 auto const otherChainAmount = [&]() -> STAmount {
1723 STAmount r(thisChainAmount);
1724 if (isLockingChain)
1725 r.setIssue(bridgeSpec.issuingChainIssue());
1726 else
1727 r.setIssue(bridgeSpec.lockingChainIssue());
1728 return r;
1729 }();
1730
1731 auto const sleClaimID =
1732 ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1733 {
1734 // Check that the sequence number is owned by the sender of this
1735 // transaction
1736 if (!sleClaimID)
1737 {
1738 return tecXCHAIN_NO_CLAIM_ID;
1739 }
1740
1741 if ((*sleClaimID)[sfAccount] != account)
1742 {
1743 // Sequence number isn't owned by the sender of this transaction
1745 }
1746 }
1747
1748 // quorum is checked in `doApply`
1749 return tesSUCCESS;
1750}
1751
1752TER
1754{
1755 PaymentSandbox psb(&ctx_.view());
1756
1757 AccountID const account = ctx_.tx[sfAccount];
1758 auto const dst = ctx_.tx[sfDestination];
1759 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1760 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1761 auto const claimID = ctx_.tx[sfXChainClaimID];
1762 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1763
1764 struct ScopeResult
1765 {
1766 std::vector<AccountID> rewardAccounts;
1767 AccountID rewardPoolSrc;
1768 STAmount sendingAmount;
1770 STAmount signatureReward;
1771 };
1772
1773 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1774 // This lambda is ugly - admittedly. The purpose of this lambda is to
1775 // limit the scope of sles so they don't overlap with
1776 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1777 // views, it's important that the sle's lifetime doesn't overlap.
1778
1779 auto const sleAcct = psb.peek(keylet::account(account));
1780 auto const sleBridge = peekBridge(psb, bridgeSpec);
1781 auto const sleClaimID = psb.peek(claimIDKeylet);
1782
1783 if (!(sleBridge && sleClaimID && sleAcct))
1784 return Unexpected(tecINTERNAL);
1785
1786 AccountID const thisDoor = (*sleBridge)[sfAccount];
1787
1789 {
1790 if (thisDoor == bridgeSpec.lockingChainDoor())
1792 else if (thisDoor == bridgeSpec.issuingChainDoor())
1794 else
1795 return Unexpected(tecINTERNAL);
1796 }
1797 STXChainBridge::ChainType const srcChain =
1799
1800 auto const sendingAmount = [&]() -> STAmount {
1801 STAmount r(thisChainAmount);
1802 r.setIssue(bridgeSpec.issue(srcChain));
1803 return r;
1804 }();
1805
1806 auto const [signersList, quorum, slTer] =
1807 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1808
1809 if (!isTesSuccess(slTer))
1810 return Unexpected(slTer);
1811
1813 sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1814
1815 auto const claimR = onClaim(
1816 curAtts,
1817 psb,
1818 sendingAmount,
1819 /*wasLockingChainSend*/ srcChain ==
1821 quorum,
1822 signersList,
1823 ctx_.journal);
1824 if (!claimR.has_value())
1825 return Unexpected(claimR.error());
1826
1827 return ScopeResult{
1828 claimR.value(),
1829 (*sleClaimID)[sfAccount],
1830 sendingAmount,
1831 srcChain,
1832 (*sleClaimID)[sfSignatureReward],
1833 };
1834 }();
1835
1836 if (!scopeResult.has_value())
1837 return scopeResult.error();
1838
1839 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1840 scopeResult.value();
1841 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1842
1843 auto const r = finalizeClaimHelper(
1844 psb,
1845 bridgeSpec,
1846 dst,
1847 dstTag,
1848 /*claimOwner*/ account,
1849 sendingAmount,
1850 rewardPoolSrc,
1851 signatureReward,
1852 rewardAccounts,
1853 srcChain,
1854 claimIDKeylet,
1855 OnTransferFail::keepClaim,
1856 DepositAuthPolicy::dstCanBypass,
1857 ctx_.journal);
1858 if (!r.isTesSuccess())
1859 return r.ter();
1860
1861 psb.apply(ctx_.rawView());
1862
1863 return tesSUCCESS;
1864}
1865
1866//------------------------------------------------------------------------------
1867
1870{
1871 auto const maxSpend = [&] {
1872 auto const amount = ctx.tx[sfAmount];
1873 if (amount.native() && amount.signum() > 0)
1874 return amount.xrp();
1875 return XRPAmount{beast::zero};
1876 }();
1877
1878 return TxConsequences{ctx.tx, maxSpend};
1879}
1880
1881NotTEC
1883{
1884 auto const amount = ctx.tx[sfAmount];
1885 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1886
1887 if (amount.signum() <= 0 || !isLegalNet(amount))
1888 return temBAD_AMOUNT;
1889
1890 if (amount.issue() != bridgeSpec.lockingChainIssue() &&
1891 amount.issue() != bridgeSpec.issuingChainIssue())
1892 return temBAD_ISSUER;
1893
1894 return tesSUCCESS;
1895}
1896
1897TER
1899{
1900 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1901 auto const amount = ctx.tx[sfAmount];
1902
1903 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1904 if (!sleBridge)
1905 {
1906 return tecNO_ENTRY;
1907 }
1908
1909 AccountID const thisDoor = (*sleBridge)[sfAccount];
1910 AccountID const account = ctx.tx[sfAccount];
1911
1912 if (thisDoor == account)
1913 {
1914 // Door account can't lock funds onto itself
1915 return tecXCHAIN_SELF_COMMIT;
1916 }
1917
1918 bool isLockingChain = false;
1919 {
1920 if (thisDoor == bridgeSpec.lockingChainDoor())
1921 isLockingChain = true;
1922 else if (thisDoor == bridgeSpec.issuingChainDoor())
1923 isLockingChain = false;
1924 else
1925 return tecINTERNAL; // LCOV_EXCL_LINE
1926 }
1927
1928 if (isLockingChain)
1929 {
1930 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1932 }
1933 else
1934 {
1935 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1937 }
1938
1939 return tesSUCCESS;
1940}
1941
1942TER
1944{
1945 PaymentSandbox psb(&ctx_.view());
1946
1947 auto const account = ctx_.tx[sfAccount];
1948 auto const amount = ctx_.tx[sfAmount];
1949 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1950
1951 if (!psb.read(keylet::account(account)))
1952 return tecINTERNAL; // LCOV_EXCL_LINE
1953
1954 auto const sleBridge = readBridge(psb, bridgeSpec);
1955 if (!sleBridge)
1956 return tecINTERNAL; // LCOV_EXCL_LINE
1957
1958 auto const dst = (*sleBridge)[sfAccount];
1959
1960 // Support dipping into reserves to pay the fee
1961 TransferHelperSubmittingAccountInfo submittingAccountInfo{
1963
1964 auto const thTer = transferHelper(
1965 psb,
1966 account,
1967 dst,
1968 /*dstTag*/ std::nullopt,
1969 /*claimOwner*/ std::nullopt,
1970 amount,
1971 CanCreateDstPolicy::no,
1972 DepositAuthPolicy::normal,
1973 submittingAccountInfo,
1974 ctx_.journal);
1975
1976 if (!isTesSuccess(thTer))
1977 return thTer;
1978
1979 psb.apply(ctx_.rawView());
1980
1981 return tesSUCCESS;
1982}
1983
1984//------------------------------------------------------------------------------
1985
1986NotTEC
1988{
1989 auto const reward = ctx.tx[sfSignatureReward];
1990
1991 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
1993
1994 return tesSUCCESS;
1995}
1996
1997TER
1999{
2000 auto const account = ctx.tx[sfAccount];
2001 auto const bridgeSpec = ctx.tx[sfXChainBridge];
2002 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2003
2004 if (!sleBridge)
2005 {
2006 return tecNO_ENTRY;
2007 }
2008
2009 // Check that the reward matches
2010 auto const reward = ctx.tx[sfSignatureReward];
2011
2012 if (reward != (*sleBridge)[sfSignatureReward])
2013 {
2015 }
2016
2017 {
2018 // Check reserve
2019 auto const sleAcc = ctx.view.read(keylet::account(account));
2020 if (!sleAcc)
2021 return terNO_ACCOUNT;
2022
2023 auto const balance = (*sleAcc)[sfBalance];
2024 auto const reserve =
2025 ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
2026
2027 if (balance < reserve)
2029 }
2030
2031 return tesSUCCESS;
2032}
2033
2034TER
2036{
2037 auto const account = ctx_.tx[sfAccount];
2038 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2039 auto const reward = ctx_.tx[sfSignatureReward];
2040 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2041
2042 auto const sleAcct = ctx_.view().peek(keylet::account(account));
2043 if (!sleAcct)
2044 return tecINTERNAL; // LCOV_EXCL_LINE
2045
2046 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2047 if (!sleBridge)
2048 return tecINTERNAL; // LCOV_EXCL_LINE
2049
2050 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2051 if (claimID == 0)
2052 {
2053 // overflow
2054 return tecINTERNAL; // LCOV_EXCL_LINE
2055 }
2056
2057 (*sleBridge)[sfXChainClaimID] = claimID;
2058
2059 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2060 if (ctx_.view().exists(claimIDKeylet))
2061 {
2062 // already checked out!?!
2063 return tecINTERNAL; // LCOV_EXCL_LINE
2064 }
2065
2066 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2067
2068 (*sleClaimID)[sfAccount] = account;
2069 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2070 (*sleClaimID)[sfXChainClaimID] = claimID;
2071 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2072 (*sleClaimID)[sfSignatureReward] = reward;
2073 sleClaimID->setFieldArray(
2074 sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
2075
2076 // Add to owner directory
2077 {
2078 auto const page = ctx_.view().dirInsert(
2079 keylet::ownerDir(account),
2080 claimIDKeylet,
2081 describeOwnerDir(account));
2082 if (!page)
2083 return tecDIR_FULL; // LCOV_EXCL_LINE
2084 (*sleClaimID)[sfOwnerNode] = *page;
2085 }
2086
2087 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2088
2089 ctx_.view().insert(sleClaimID);
2090 ctx_.view().update(sleBridge);
2091 ctx_.view().update(sleAcct);
2092
2093 return tesSUCCESS;
2094}
2095
2096//------------------------------------------------------------------------------
2097
2098NotTEC
2100{
2101 return attestationpreflight<Attestations::AttestationClaim>(ctx);
2102}
2103
2104TER
2106{
2107 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2108}
2109
2110TER
2112{
2113 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2114}
2115
2116//------------------------------------------------------------------------------
2117
2118NotTEC
2120{
2121 return attestationpreflight<Attestations::AttestationCreateAccount>(ctx);
2122}
2123
2124TER
2126{
2127 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2128}
2129
2130TER
2132{
2133 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2134}
2135
2136//------------------------------------------------------------------------------
2137
2138NotTEC
2140{
2141 auto const amount = ctx.tx[sfAmount];
2142
2143 if (amount.signum() <= 0 || !amount.native())
2144 return temBAD_AMOUNT;
2145
2146 auto const reward = ctx.tx[sfSignatureReward];
2147 if (reward.signum() < 0 || !reward.native())
2148 return temBAD_AMOUNT;
2149
2150 if (reward.issue() != amount.issue())
2151 return temBAD_AMOUNT;
2152
2153 return tesSUCCESS;
2154}
2155
2156TER
2158{
2159 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2160 STAmount const amount = ctx.tx[sfAmount];
2161 STAmount const reward = ctx.tx[sfSignatureReward];
2162
2163 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2164 if (!sleBridge)
2165 {
2166 return tecNO_ENTRY;
2167 }
2168
2169 if (reward != (*sleBridge)[sfSignatureReward])
2170 {
2172 }
2173
2174 std::optional<STAmount> const minCreateAmount =
2175 (*sleBridge)[~sfMinAccountCreateAmount];
2176
2177 if (!minCreateAmount)
2179
2180 if (amount < *minCreateAmount)
2182
2183 if (minCreateAmount->issue() != amount.issue())
2185
2186 AccountID const thisDoor = (*sleBridge)[sfAccount];
2187 AccountID const account = ctx.tx[sfAccount];
2188 if (thisDoor == account)
2189 {
2190 // Door account can't lock funds onto itself
2191 return tecXCHAIN_SELF_COMMIT;
2192 }
2193
2195 {
2196 if (thisDoor == bridgeSpec.lockingChainDoor())
2198 else if (thisDoor == bridgeSpec.issuingChainDoor())
2200 else
2201 return tecINTERNAL; // LCOV_EXCL_LINE
2202 }
2203 STXChainBridge::ChainType const dstChain =
2205
2206 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2208
2209 if (!isXRP(bridgeSpec.issue(dstChain)))
2211
2212 return tesSUCCESS;
2213}
2214
2215TER
2217{
2218 PaymentSandbox psb(&ctx_.view());
2219
2220 AccountID const account = ctx_.tx[sfAccount];
2221 STAmount const amount = ctx_.tx[sfAmount];
2222 STAmount const reward = ctx_.tx[sfSignatureReward];
2223 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2224
2225 auto const sle = psb.peek(keylet::account(account));
2226 if (!sle)
2227 return tecINTERNAL; // LCOV_EXCL_LINE
2228
2229 auto const sleBridge = peekBridge(psb, bridge);
2230 if (!sleBridge)
2231 return tecINTERNAL; // LCOV_EXCL_LINE
2232
2233 auto const dst = (*sleBridge)[sfAccount];
2234
2235 // Support dipping into reserves to pay the fee
2236 TransferHelperSubmittingAccountInfo submittingAccountInfo{
2238 STAmount const toTransfer = amount + reward;
2239 auto const thTer = transferHelper(
2240 psb,
2241 account,
2242 dst,
2243 /*dstTag*/ std::nullopt,
2244 /*claimOwner*/ std::nullopt,
2245 toTransfer,
2246 CanCreateDstPolicy::yes,
2247 DepositAuthPolicy::normal,
2248 submittingAccountInfo,
2249 ctx_.journal);
2250
2251 if (!isTesSuccess(thTer))
2252 return thTer;
2253
2254 (*sleBridge)[sfXChainAccountCreateCount] =
2255 (*sleBridge)[sfXChainAccountCreateCount] + 1;
2256 psb.update(sleBridge);
2257
2258 psb.apply(ctx_.rawView());
2259
2260 return tesSUCCESS;
2261}
2262
2263} // 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:319
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static rounding_mode getround()
Definition Number.cpp: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.
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:147
XRPAmount mPriorBalance
Definition Transactor.h:148
XRPAmount mSourceBalance
Definition Transactor.h:149
ApplyContext & ctx_
Definition Transactor.h:143
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:1032
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1050
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.
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
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:681
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
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:133
@ 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