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