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(
322 attestations,
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(
341 XChainClaimAttestations& attestations,
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
388
389TER
390transferHelper(
391 PaymentSandbox& psb,
392 AccountID const& src,
393 AccountID const& dst,
394 std::optional<std::uint32_t> const& dstTag,
395 std::optional<AccountID> const& claimOwner,
396 STAmount const& amt,
397 CanCreateDstPolicy canCreate,
398 DepositAuthPolicy depositAuthPolicy,
399 std::optional<TransferHelperSubmittingAccountInfo> const& submittingAccountInfo,
400 beast::Journal j)
401{
402 if (dst == src)
403 return tesSUCCESS;
404
405 auto const dstK = keylet::account(dst);
406 if (auto sleDst = psb.read(dstK))
407 {
408 // Check dst tag and deposit auth
409
410 if (sleDst->isFlag(lsfRequireDestTag) && !dstTag)
411 return tecDST_TAG_NEEDED;
412
413 // If the destination is the claim owner, and this is a claim
414 // transaction, that's the dst account sending funds to itself. It
415 // can bypass deposit auth.
416 bool const canBypassDepositAuth =
417 dst == claimOwner && depositAuthPolicy == DepositAuthPolicy::DstCanBypass;
418
419 if (!canBypassDepositAuth && sleDst->isFlag(lsfDepositAuth) &&
420 !psb.exists(keylet::depositPreauth(dst, src)))
421 {
422 return tecNO_PERMISSION;
423 }
424 }
425 else if (!amt.native() || canCreate == CanCreateDstPolicy::No)
426 {
427 return tecNO_DST;
428 }
429
430 if (amt.native())
431 {
432 auto const sleSrc = psb.peek(keylet::account(src));
433 XRPL_ASSERT(sleSrc, "xrpl::transferHelper : non-null source account");
434 if (!sleSrc)
435 return tecINTERNAL; // LCOV_EXCL_LINE
436
437 {
438 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
439 auto const reserve = psb.fees().accountReserve(ownerCount);
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
513enum class OnTransferFail {
515 RemoveClaim,
517 KeepClaim
518};
519
520struct FinalizeClaimHelperResult
521{
523 std::optional<TER> mainFundsTer;
524 // TER for transfering the reward funds
525 std::optional<TER> rewardTer;
526 // TER for removing the sle (if is sle is to be removed)
527 std::optional<TER> rmSleTer;
528
529 // Helper to check for overall success. If there wasn't overall success the
530 // individual ters can be used to decide what needs to be done.
531 [[nodiscard]] bool
532 isTesSuccess() const
533 {
534 return (!mainFundsTer || xrpl::isTesSuccess(*mainFundsTer)) &&
535 (!rewardTer || xrpl::isTesSuccess(*rewardTer)) &&
536 (!rmSleTer || xrpl::isTesSuccess(*rmSleTer));
537 }
538
539 [[nodiscard]] TER
540 ter() const
541 {
542 if (isTesSuccess())
543 return tesSUCCESS;
544
545 // if any phase return a tecINTERNAL or a tef, prefer returning those
546 // codes
547 if (mainFundsTer && (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
548 return *mainFundsTer;
549 if (rewardTer && (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
550 return *rewardTer;
551 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
552 return *rmSleTer;
553
554 // Only after the tecINTERNAL and tef are checked, return the first
555 // non-success error code.
556 if (mainFundsTer && !xrpl::isTesSuccess(*mainFundsTer))
557 return *mainFundsTer;
558 if (rewardTer && !xrpl::isTesSuccess(*rewardTer))
559 return *rewardTer;
560 if (rmSleTer && !xrpl::isTesSuccess(*rmSleTer))
561 return *rmSleTer;
562 return tesSUCCESS;
563 }
564};
565
593
594FinalizeClaimHelperResult
595finalizeClaimHelper(
596 PaymentSandbox& outerSb,
597 STXChainBridge const& bridgeSpec,
598 AccountID const& dst,
599 std::optional<std::uint32_t> const& dstTag,
600 AccountID const& claimOwner,
601 STAmount const& sendingAmount,
602 AccountID const& rewardPoolSrc,
603 STAmount const& rewardPool,
604 std::vector<AccountID> const& rewardAccounts,
605 STXChainBridge::ChainType const srcChain,
606 Keylet const& claimIDKeylet,
607 OnTransferFail onTransferFail,
608 DepositAuthPolicy depositAuthPolicy,
609 beast::Journal j)
610{
611 FinalizeClaimHelperResult result;
612
613 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
614 STAmount const thisChainAmount = [&] {
615 STAmount r = sendingAmount;
616 r.setIssue(bridgeSpec.issue(dstChain));
617 return r;
618 }();
619 auto const& thisDoor = bridgeSpec.door(dstChain);
620
621 {
622 PaymentSandbox innerSb{&outerSb};
623 // If distributing the reward pool fails, the mainFunds transfer should
624 // be rolled back
625 //
626 // If the claim ID is removed, the rewards should be distributed
627 // even if the mainFunds fails.
628 //
629 // If OnTransferFail::removeClaim, the claim should be removed even if
630 // the rewards cannot be distributed.
631
632 // transfer funds to the dst
633 result.mainFundsTer = transferHelper(
634 innerSb,
635 thisDoor,
636 dst,
637 dstTag,
638 claimOwner,
639 thisChainAmount,
640 CanCreateDstPolicy::Yes,
641 depositAuthPolicy,
642 std::nullopt,
643 j);
644
645 if (!isTesSuccess(*result.mainFundsTer) && onTransferFail == OnTransferFail::KeepClaim)
646 {
647 return result;
648 }
649
650 // handle the reward pool
651 result.rewardTer = [&]() -> TER {
652 if (rewardAccounts.empty())
653 return tesSUCCESS;
654
655 // distribute the reward pool
656 // if the transfer failed, distribute the pool for "OnTransferFail"
657 // cases (the attesters did their job)
658 STAmount const share = [&] {
659 auto const roundMode = innerSb.rules().enabled(fixXChainRewardRounding)
662 SaveNumberRoundMode const _{Number::setround(roundMode)};
663
664 STAmount const den{rewardAccounts.size()};
665 return divide(rewardPool, den, rewardPool.asset());
666 }();
667 STAmount distributed = rewardPool.zeroed();
668 for (auto const& rewardAccount : rewardAccounts)
669 {
670 auto const thTer = transferHelper(
671 innerSb,
672 rewardPoolSrc,
673 rewardAccount,
674 /*dstTag*/ std::nullopt,
675 // claim owner is not relevant to distributing rewards
676 /*claimOwner*/ std::nullopt,
677 share,
678 CanCreateDstPolicy::No,
679 DepositAuthPolicy::Normal,
680 std::nullopt,
681 j);
682
683 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
684 return thTer;
685
686 if (isTesSuccess(thTer))
687 distributed += share;
688
689 // let txn succeed if error distributing rewards (other than
690 // inability to pay)
691 }
692
693 if (distributed > rewardPool)
694 return tecINTERNAL; // LCOV_EXCL_LINE
695
696 return tesSUCCESS;
697 }();
698
699 if (!isTesSuccess(*result.rewardTer) &&
700 (onTransferFail == OnTransferFail::KeepClaim || *result.rewardTer == tecINTERNAL))
701 {
702 return result;
703 }
704
705 if (!isTesSuccess(*result.mainFundsTer) || isTesSuccess(*result.rewardTer))
706 {
707 // Note: if the mainFunds transfer succeeds and the result transfer
708 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
709 // rolled back)
710 innerSb.apply(outerSb);
711 }
712 }
713
714 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
715 {
716 auto const cidOwner = (*sleClaimID)[sfAccount];
717 {
718 // Remove the claim id
719 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
720 auto const page = (*sleClaimID)[sfOwnerNode];
721 if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
722 {
723 JLOG(j.fatal()) << "Unable to delete xchain seq number from owner.";
724 result.rmSleTer = tefBAD_LEDGER;
725 return result;
726 }
727
728 // Remove the claim id from the ledger
729 outerSb.erase(sleClaimID);
730
731 adjustOwnerCount(outerSb, sleOwner, -1, j);
732 }
733 }
734
735 return result;
736}
737
747std::tuple<std::unordered_map<AccountID, std::uint32_t>, std::uint32_t, TER>
748getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journal j)
749{
750 std::unordered_map<AccountID, std::uint32_t> r;
751 std::uint32_t q = std::numeric_limits<std::uint32_t>::max();
752
753 AccountID const thisDoor = sleBridge[sfAccount];
754 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
755
756 if (!sleDoor)
757 {
758 return {r, q, tecINTERNAL};
759 }
760
761 auto const sleS = view.read(keylet::signerList(sleBridge[sfAccount]));
762 if (!sleS)
763 {
764 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
765 }
766 q = (*sleS)[sfSignerQuorum];
767
768 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
769
770 if (!accountSigners)
771 {
772 return {r, q, tecINTERNAL};
773 }
774
775 for (auto const& as : *accountSigners)
776 {
777 r[as.account] = as.weight;
778 }
779
780 return {std::move(r), q, tesSUCCESS};
781};
782
783template <class R, class F>
784std::shared_ptr<R>
785readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
786{
787 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
788 if (auto r = getter(bridgeSpec, ct))
789 {
790 if ((*r)[sfXChainBridge] == bridgeSpec)
791 return r;
792 }
793 return nullptr;
794 };
795 if (auto r = tryGet(STXChainBridge::ChainType::Locking))
796 return r;
798}
799
801peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
802{
803 return readOrpeekBridge<SLE>(
805 return v.peek(keylet::bridge(b, ct));
806 },
807 bridgeSpec);
808}
809
811readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
812{
813 return readOrpeekBridge<SLE const>(
815 return v.read(keylet::bridge(b, ct));
816 },
817 bridgeSpec);
818}
819
820// Precondition: all the claims in the range are consistent. They must sign for
821// the same event (amount, sending account, claim id, etc).
822template <class TIter>
823TER
824applyClaimAttestations(
825 ApplyView& view,
826 RawView& rawView,
827 TIter attBegin,
828 TIter attEnd,
829 STXChainBridge const& bridgeSpec,
830 STXChainBridge::ChainType const srcChain,
831 std::unordered_map<AccountID, std::uint32_t> const& signersList,
832 std::uint32_t quorum,
833 beast::Journal j)
834{
835 if (attBegin == attEnd)
836 return tesSUCCESS;
837
838 PaymentSandbox psb(&view);
839
840 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
841
842 struct ScopeResult
843 {
844 OnNewAttestationResult newAttResult;
845 STAmount rewardAmount;
846 AccountID cidOwner;
847 };
848
849 auto const scopeResult = [&]() -> std::expected<ScopeResult, TER> {
850 // This lambda is ugly - admittedly. The purpose of this lambda is to
851 // limit the scope of sles so they don't overlap with
852 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
853 // views, it's important that the sle's lifetime doesn't overlap.
854 auto const sleClaimID = psb.peek(claimIDKeylet);
855 if (!sleClaimID)
857
858 // Add claims that are part of the signer's list to the "claims" vector
859 std::vector<Attestations::AttestationClaim> atts;
860 atts.reserve(std::distance(attBegin, attEnd));
861 for (auto att = attBegin; att != attEnd; ++att)
862 {
863 if (!signersList.contains(att->attestationSignerAccount))
864 continue;
865 atts.push_back(*att);
866 }
867
868 if (atts.empty())
869 {
871 }
872
873 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
874 if (attBegin->sendingAccount != otherChainSource)
875 {
877 }
878
879 {
880 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
881
882 STXChainBridge::ChainType const attDstChain =
883 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
884
885 if (attDstChain != dstChain)
886 {
888 }
889 }
890
891 XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)};
892
893 auto const newAttResult = onNewAttestations(
894 curAtts,
895 view,
896 &atts[0],
897 &atts[0] + atts.size(), // NOLINT(bugprone-pointer-arithmetic-on-polymorphic-object)
898 quorum,
899 signersList,
900 j);
901
902 // update the claim id
903 sleClaimID->setFieldArray(sfXChainClaimAttestations, curAtts.toSTArray());
904 psb.update(sleClaimID);
905
906 return ScopeResult{
907 newAttResult, (*sleClaimID)[sfSignatureReward], (*sleClaimID)[sfAccount]};
908 }();
909
910 if (!scopeResult.has_value())
911 return scopeResult.error();
912
913 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
914 auto const& [rewardAccounts, attListChanged] = newAttResult;
915 if (rewardAccounts && attBegin->dst)
916 {
917 auto const r = finalizeClaimHelper(
918 psb,
919 bridgeSpec,
920 *attBegin->dst,
921 /*dstTag*/ std::nullopt,
922 cidOwner,
923 attBegin->sendingAmount,
924 cidOwner,
925 rewardAmount,
926 *rewardAccounts,
927 srcChain,
928 claimIDKeylet,
929 OnTransferFail::KeepClaim,
930 DepositAuthPolicy::Normal,
931 j);
932
933 auto const rTer = r.ter();
934
935 if (!isTesSuccess(rTer) &&
936 (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
937 return rTer;
938 }
939
940 psb.apply(rawView);
941
942 return tesSUCCESS;
943}
944
945template <class TIter>
946TER
947applyCreateAccountAttestations(
948 ApplyView& view,
949 RawView& rawView,
950 TIter attBegin,
951 TIter attEnd,
952 AccountID const& doorAccount,
953 Keylet const& doorK,
954 STXChainBridge const& bridgeSpec,
955 Keylet const& bridgeK,
956 STXChainBridge::ChainType const srcChain,
957 std::unordered_map<AccountID, std::uint32_t> const& signersList,
958 std::uint32_t quorum,
959 beast::Journal j)
960{
961 if (attBegin == attEnd)
962 return tesSUCCESS;
963
964 PaymentSandbox psb(&view);
965
966 auto const claimCountResult = [&]() -> std::expected<std::uint64_t, TER> {
967 auto const sleBridge = psb.peek(bridgeK);
968 if (!sleBridge)
970
971 return (*sleBridge)[sfXChainAccountClaimCount];
972 }();
973
974 if (!claimCountResult.has_value())
975 return claimCountResult.error();
976
977 std::uint64_t const claimCount = claimCountResult.value();
978
979 if (attBegin->createCount <= claimCount)
980 {
982 }
983 if (attBegin->createCount >= claimCount + kXbridgeMaxAccountCreateClaims)
984 {
985 // Limit the number of claims on the account
987 }
988
989 {
990 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
991
992 STXChainBridge::ChainType const attDstChain =
993 STXChainBridge::dstChain(attBegin->wasLockingChainSend);
994
995 if (attDstChain != dstChain)
996 {
998 }
999 }
1000
1001 auto const claimIDKeylet =
1002 keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
1003
1004 struct ScopeResult
1005 {
1006 OnNewAttestationResult newAttResult;
1007 bool createCID{};
1008 XChainCreateAccountAttestations curAtts;
1009 };
1010
1011 auto const scopeResult = [&]() -> std::expected<ScopeResult, TER> {
1012 // This lambda is ugly - admittedly. The purpose of this lambda is to
1013 // limit the scope of sles so they don't overlap with
1014 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1015 // views, it's important that the sle's lifetime doesn't overlap.
1016
1017 // sleClaimID may be null. If it's null it isn't created until the end
1018 // of this function (if needed)
1019 auto const sleClaimID = psb.peek(claimIDKeylet);
1020 bool createCID = false;
1021 if (!sleClaimID)
1022 {
1023 createCID = true;
1024
1025 auto const sleDoor = psb.peek(doorK);
1026 if (!sleDoor)
1028
1029 // Check reserve
1030 auto const balance = (*sleDoor)[sfBalance];
1031 auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
1032
1033 if (balance < reserve)
1035 }
1036
1037 std::vector<Attestations::AttestationCreateAccount> atts;
1038 atts.reserve(std::distance(attBegin, attEnd));
1039 for (auto att = attBegin; att != attEnd; ++att)
1040 {
1041 if (!signersList.contains(att->attestationSignerAccount))
1042 continue;
1043 atts.push_back(*att);
1044 }
1045 if (atts.empty())
1046 {
1048 }
1049
1050 XChainCreateAccountAttestations curAtts = [&] {
1051 if (sleClaimID)
1052 {
1054 sleClaimID->getFieldArray(sfXChainCreateAccountAttestations)};
1055 }
1057 }();
1058
1059 auto const newAttResult = onNewAttestations(
1060 curAtts,
1061 view,
1062 &atts[0],
1063 &atts[0] + atts.size(), // NOLINT(bugprone-pointer-arithmetic-on-polymorphic-object)
1064 quorum,
1065 signersList,
1066 j);
1067
1068 if (!createCID)
1069 {
1070 // Modify the object before it's potentially deleted, so the meta
1071 // data will include the new attestations
1072 if (!sleClaimID)
1074 sleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray());
1075 psb.update(sleClaimID);
1076 }
1077 return ScopeResult{newAttResult, createCID, curAtts};
1078 }();
1079
1080 if (!scopeResult.has_value())
1081 return scopeResult.error();
1082
1083 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1084 auto const& [rewardAccounts, attListChanged] = attResult;
1085
1086 // Account create transactions must happen in order
1087 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1088 {
1089 auto const r = finalizeClaimHelper(
1090 psb,
1091 bridgeSpec,
1092 attBegin->toCreate,
1093 /*dstTag*/ std::nullopt,
1094 doorAccount,
1095 attBegin->sendingAmount,
1096 /*rewardPoolSrc*/ doorAccount,
1097 attBegin->rewardAmount,
1098 *rewardAccounts,
1099 srcChain,
1100 claimIDKeylet,
1101 OnTransferFail::RemoveClaim,
1102 DepositAuthPolicy::Normal,
1103 j);
1104
1105 auto const rTer = r.ter();
1106
1107 if (!isTesSuccess(rTer))
1108 {
1109 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT || isTefFailure(rTer))
1110 return rTer;
1111 }
1112 // Move past this claim id even if it fails, so it doesn't block
1113 // subsequent claim ids
1114 auto const sleBridge = psb.peek(bridgeK);
1115 if (!sleBridge)
1116 return tecINTERNAL; // LCOV_EXCL_LINE
1117 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1118 psb.update(sleBridge);
1119 }
1120 else if (createCID)
1121 {
1122 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1123 (*createdSleClaimID)[sfAccount] = doorAccount;
1124 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1125 (*createdSleClaimID)[sfXChainAccountCreateCount] = attBegin->createCount;
1126 createdSleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray());
1127
1128 // Add to owner directory of the door account
1129 auto const page = psb.dirInsert(
1130 keylet::ownerDir(doorAccount), claimIDKeylet, describeOwnerDir(doorAccount));
1131 if (!page)
1132 return tecDIR_FULL; // LCOV_EXCL_LINE
1133 (*createdSleClaimID)[sfOwnerNode] = *page;
1134
1135 auto const sleDoor = psb.peek(doorK);
1136 if (!sleDoor)
1137 return tecINTERNAL; // LCOV_EXCL_LINE
1138
1139 // Reserve was already checked
1140 adjustOwnerCount(psb, sleDoor, 1, j);
1141 psb.insert(createdSleClaimID);
1142 psb.update(sleDoor);
1143 }
1144
1145 psb.apply(rawView);
1146
1147 return tesSUCCESS;
1148}
1149
1150template <class TAttestation>
1151std::optional<TAttestation>
1152toClaim(STTx const& tx)
1153{
1154 static_assert(
1157
1158 try
1159 {
1160 STObject o{tx};
1161 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1162 return TAttestation(o);
1163 }
1164 catch (...)
1165 {
1166 return std::nullopt;
1167 }
1168}
1169
1170template <class TAttestation>
1171NotTEC
1172attestationPreflight(PreflightContext const& ctx)
1173{
1174 if (!publicKeyType(ctx.tx[sfPublicKey]))
1175 return temMALFORMED;
1176
1177 auto const att = toClaim<TAttestation>(ctx.tx);
1178 if (!att)
1179 return temMALFORMED;
1180
1181 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1182 if (!att->verify(bridgeSpec))
1183 return temXCHAIN_BAD_PROOF;
1184 if (!att->validAmounts())
1185 return temXCHAIN_BAD_PROOF;
1186
1187 if (att->sendingAmount.signum() <= 0)
1188 return temXCHAIN_BAD_PROOF;
1189 auto const expectedIssue = bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1190 if (att->sendingAmount.asset() != expectedIssue)
1191 return temXCHAIN_BAD_PROOF;
1192
1193 return tesSUCCESS;
1194}
1195
1196template <class TAttestation>
1197TER
1198attestationPreclaim(PreclaimContext const& ctx)
1199{
1200 auto const att = toClaim<TAttestation>(ctx.tx);
1201 // checked in preflight
1202 if (!att)
1203 return tecINTERNAL; // LCOV_EXCL_LINE
1204
1205 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1206 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1207 if (!sleBridge)
1208 {
1209 return tecNO_ENTRY;
1210 }
1211
1212 AccountID const attestationSignerAccount{ctx.tx[sfAttestationSignerAccount]};
1213 PublicKey const pk{ctx.tx[sfPublicKey]};
1214
1215 // signersList is a map from account id to weights
1216 auto const [signersList, quorum, slTer] = getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1217
1218 if (!isTesSuccess(slTer))
1219 return slTer;
1220
1221 return checkAttestationPublicKey(ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1222}
1223
1224template <class TAttestation>
1225TER
1226attestationDoApply(ApplyContext& ctx)
1227{
1228 auto const att = toClaim<TAttestation>(ctx.tx);
1229 if (!att)
1230 {
1231 // Should already be checked in preflight
1232 return tecINTERNAL; // LCOV_EXCL_LINE
1233 }
1234
1235 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1236
1237 struct ScopeResult
1238 {
1239 STXChainBridge::ChainType srcChain = STXChainBridge::ChainType::Locking;
1240 std::unordered_map<AccountID, std::uint32_t> signersList;
1241 std::uint32_t quorum{};
1242 AccountID thisDoor;
1243 Keylet bridgeK;
1244 };
1245
1246 auto const scopeResult = [&]() -> std::expected<ScopeResult, TER> {
1247 // This lambda is ugly - admittedly. The purpose of this lambda is to
1248 // limit the scope of sles so they don't overlap with
1249 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1250 // views, it's important that the sle's lifetime doesn't overlap.
1251 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1252 if (!sleBridge)
1253 {
1255 }
1256 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1257 AccountID const thisDoor = (*sleBridge)[sfAccount];
1258
1260 {
1261 if (thisDoor == bridgeSpec.lockingChainDoor())
1262 {
1264 }
1265 else if (thisDoor == bridgeSpec.issuingChainDoor())
1266 {
1268 }
1269 else
1270 {
1272 }
1273 }
1274 STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain);
1275
1276 // signersList is a map from account id to weights
1277 auto [signersList, quorum, slTer] =
1278 getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1279
1280 if (!isTesSuccess(slTer))
1281 return std::unexpected(slTer);
1282
1283 return ScopeResult{srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1284 }();
1285
1286 if (!scopeResult.has_value())
1287 return scopeResult.error();
1288
1289 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] = scopeResult.value();
1290
1291 static_assert(
1294
1296 {
1297 return applyClaimAttestations(
1298 ctx.view(),
1299 ctx.rawView(),
1300 &*att,
1301 &*att + 1,
1302 bridgeSpec,
1303 srcChain,
1304 signersList,
1305 quorum,
1306 ctx.journal);
1307 }
1309 {
1310 return applyCreateAccountAttestations(
1311 ctx.view(),
1312 ctx.rawView(),
1313 &*att,
1314 &*att + 1,
1315 thisDoor,
1316 keylet::account(thisDoor),
1317 bridgeSpec,
1318 bridgeK,
1319 srcChain,
1320 signersList,
1321 quorum,
1322 ctx.journal);
1323 }
1324}
1325
1326} // namespace
1327//------------------------------------------------------------------------------
1328
1329NotTEC
1331{
1332 auto const account = ctx.tx[sfAccount];
1333 auto const reward = ctx.tx[sfSignatureReward];
1334 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1335 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1336 // Doors must be distinct to help prevent transaction replay attacks
1337 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1338 {
1340 }
1341
1342 if (bridgeSpec.lockingChainDoor() != account && bridgeSpec.issuingChainDoor() != account)
1343 {
1345 }
1346
1347 if (isXRP(bridgeSpec.lockingChainIssue()) != isXRP(bridgeSpec.issuingChainIssue()))
1348 {
1349 // Because ious and xrp have different numeric ranges, both the src and
1350 // dst issues must be both XRP or both IOU.
1352 }
1353
1354 if (!isXRP(reward) || reward.signum() < 0)
1355 {
1357 }
1358
1359 if (minAccountCreate &&
1360 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1361 !isXRP(bridgeSpec.lockingChainIssue()) || !isXRP(bridgeSpec.issuingChainIssue())))
1362 {
1364 }
1365
1366 if (isXRP(bridgeSpec.issuingChainIssue()))
1367 {
1368 // Issuing account must be the root account for XRP (which presumably
1369 // owns all the XRP). This is done so the issuing account can't "run
1370 // out" of wrapped tokens.
1371 static auto const kRootAccount = calcAccountID(
1372 generateKeyPair(KeyType::Secp256k1, generateSeed("masterpassphrase")).first);
1373 if (bridgeSpec.issuingChainDoor() != kRootAccount)
1374 {
1376 }
1377 }
1378 else
1379 {
1380 // Issuing account must be the issuer for non-XRP. This is done so the
1381 // issuing account can't "run out" of wrapped tokens.
1382 if (bridgeSpec.issuingChainDoor() != bridgeSpec.issuingChainIssue().account)
1383 {
1385 }
1386 }
1387
1388 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1389 {
1390 // If the locking chain door is locking their own asset, in some sense
1391 // nothing is being locked. Disallow this.
1393 }
1394
1395 return tesSUCCESS;
1396}
1397
1398TER
1400{
1401 auto const account = ctx.tx[sfAccount];
1402 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1403 STXChainBridge::ChainType const chainType =
1404 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1405
1406 {
1407 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1408 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1409 };
1410
1411 if (hasBridge(STXChainBridge::ChainType::Issuing) ||
1413 {
1414 return tecDUPLICATE;
1415 }
1416 }
1417
1418 if (!isXRP(bridgeSpec.issue(chainType)))
1419 {
1420 auto const sleIssuer = ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1421
1422 if (!sleIssuer)
1423 return tecNO_ISSUER;
1424
1425 // Allowing clawing back funds would break the bridge's invariant that
1426 // wrapped funds are always backed by locked funds
1427 if (sleIssuer->isFlag(lsfAllowTrustLineClawback))
1428 return tecNO_PERMISSION;
1429 }
1430
1431 {
1432 // Check reserve
1433 auto const sleAcc = ctx.view.read(keylet::account(account));
1434 if (!sleAcc)
1435 return terNO_ACCOUNT;
1436
1437 auto const balance = (*sleAcc)[sfBalance];
1438 auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1439
1440 if (balance < reserve)
1442 }
1443
1444 return tesSUCCESS;
1445}
1446
1447TER
1449{
1450 auto const account = ctx_.tx[sfAccount];
1451 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1452 auto const reward = ctx_.tx[sfSignatureReward];
1453 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1454
1455 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1456 if (!sleAcct)
1457 return tecINTERNAL; // LCOV_EXCL_LINE
1458
1459 STXChainBridge::ChainType const chainType =
1460 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1461
1462 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1463 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1464
1465 (*sleBridge)[sfAccount] = account;
1466 (*sleBridge)[sfSignatureReward] = reward;
1467 if (minAccountCreate)
1468 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1469 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1470 (*sleBridge)[sfXChainClaimID] = 0;
1471 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1472 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1473
1474 // Add to owner directory
1475 {
1476 auto const page = ctx_.view().dirInsert(
1477 keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1478 if (!page)
1479 return tecDIR_FULL; // LCOV_EXCL_LINE
1480 (*sleBridge)[sfOwnerNode] = *page;
1481 }
1482
1483 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1484
1485 ctx_.view().insert(sleBridge);
1486 ctx_.view().update(sleAcct);
1487
1488 return tesSUCCESS;
1489}
1490
1491//------------------------------------------------------------------------------
1492
1495{
1496 return tfXChainModifyBridgeMask;
1497}
1498
1499NotTEC
1501{
1502 auto const account = ctx.tx[sfAccount];
1503 auto const reward = ctx.tx[~sfSignatureReward];
1504 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1505 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1506 bool const clearAccountCreate = ctx.tx.isFlag(tfClearAccountCreateAmount);
1507
1508 if (!reward && !minAccountCreate && !clearAccountCreate)
1509 {
1510 // Must change something
1511 return temMALFORMED;
1512 }
1513
1514 if (minAccountCreate && clearAccountCreate)
1515 {
1516 // Can't both clear and set account create in the same txn
1517 return temMALFORMED;
1518 }
1519
1520 if (bridgeSpec.lockingChainDoor() != account && bridgeSpec.issuingChainDoor() != account)
1521 {
1523 }
1524
1525 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1526 {
1528 }
1529
1530 if (minAccountCreate &&
1531 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1532 !isXRP(bridgeSpec.lockingChainIssue()) || !isXRP(bridgeSpec.issuingChainIssue())))
1533 {
1535 }
1536
1537 return tesSUCCESS;
1538}
1539
1540TER
1542{
1543 auto const account = ctx.tx[sfAccount];
1544 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1545
1546 STXChainBridge::ChainType const chainType =
1547 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1548
1549 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1550 {
1551 return tecNO_ENTRY;
1552 }
1553
1554 return tesSUCCESS;
1555}
1556
1557TER
1559{
1560 auto const account = ctx_.tx[sfAccount];
1561 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1562 auto const reward = ctx_.tx[~sfSignatureReward];
1563 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1564 bool const clearAccountCreate = ctx_.tx.isFlag(tfClearAccountCreateAmount);
1565
1566 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1567 if (!sleAcct)
1568 return tecINTERNAL; // LCOV_EXCL_LINE
1569
1570 STXChainBridge::ChainType const chainType =
1571 STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1572
1573 auto const sleBridge = ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1574 if (!sleBridge)
1575 return tecINTERNAL; // LCOV_EXCL_LINE
1576
1577 if (reward)
1578 (*sleBridge)[sfSignatureReward] = *reward;
1579 if (minAccountCreate)
1580 {
1581 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1582 }
1583 if (clearAccountCreate && sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1584 {
1585 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1586 }
1587 ctx_.view().update(sleBridge);
1588
1589 return tesSUCCESS;
1590}
1591
1592//------------------------------------------------------------------------------
1593
1594NotTEC
1596{
1597 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1598 auto const amount = ctx.tx[sfAmount];
1599
1600 if (amount.signum() <= 0 ||
1601 (amount.asset() != bridgeSpec.lockingChainIssue() &&
1602 amount.asset() != bridgeSpec.issuingChainIssue()))
1603 {
1604 return temBAD_AMOUNT;
1605 }
1606
1607 return tesSUCCESS;
1608}
1609
1610TER
1612{
1613 AccountID const account = ctx.tx[sfAccount];
1614 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1615 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1616 auto const claimID = ctx.tx[sfXChainClaimID];
1617
1618 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1619 if (!sleBridge)
1620 {
1621 return tecNO_ENTRY;
1622 }
1623
1624 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1625 {
1626 return tecNO_DST;
1627 }
1628
1629 auto const thisDoor = (*sleBridge)[sfAccount];
1630 bool isLockingChain = false;
1631 {
1632 if (thisDoor == bridgeSpec.lockingChainDoor())
1633 {
1634 isLockingChain = true;
1635 }
1636 else if (thisDoor == bridgeSpec.issuingChainDoor())
1637 {
1638 isLockingChain = false;
1639 }
1640 else
1641 {
1642 return tecINTERNAL; // LCOV_EXCL_LINE
1643 }
1644 }
1645
1646 {
1647 // Check that the amount specified matches the expected issue
1648
1649 if (isLockingChain)
1650 {
1651 if (bridgeSpec.lockingChainIssue() != thisChainAmount.asset())
1653 }
1654 else
1655 {
1656 if (bridgeSpec.issuingChainIssue() != thisChainAmount.asset())
1658 }
1659 }
1660
1661 if (isXRP(bridgeSpec.lockingChainIssue()) != isXRP(bridgeSpec.issuingChainIssue()))
1662 {
1663 // Should have been caught when creating the bridge
1664 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1665 // and the numeric issues that need to be addressed with that.
1666 return tecINTERNAL; // LCOV_EXCL_LINE
1667 }
1668
1669 auto const otherChainAmount = [&]() -> STAmount {
1670 STAmount r(thisChainAmount);
1671 if (isLockingChain)
1672 {
1673 r.setIssue(bridgeSpec.issuingChainIssue());
1674 }
1675 else
1676 {
1677 r.setIssue(bridgeSpec.lockingChainIssue());
1678 }
1679 return r;
1680 }();
1681
1682 auto const sleClaimID = ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1683 {
1684 // Check that the sequence number is owned by the sender of this
1685 // transaction
1686 if (!sleClaimID)
1687 {
1688 return tecXCHAIN_NO_CLAIM_ID;
1689 }
1690
1691 if ((*sleClaimID)[sfAccount] != account)
1692 {
1693 // Sequence number isn't owned by the sender of this transaction
1695 }
1696 }
1697
1698 // quorum is checked in `doApply`
1699 return tesSUCCESS;
1700}
1701
1702TER
1704{
1705 PaymentSandbox psb(&ctx_.view());
1706
1707 AccountID const account = ctx_.tx[sfAccount];
1708 auto const dst = ctx_.tx[sfDestination];
1709 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1710 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1711 auto const claimID = ctx_.tx[sfXChainClaimID];
1712 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1713
1714 struct ScopeResult
1715 {
1716 std::vector<AccountID> rewardAccounts;
1717 AccountID rewardPoolSrc;
1718 STAmount sendingAmount;
1720 STAmount signatureReward;
1721 };
1722
1723 auto const scopeResult = [&]() -> std::expected<ScopeResult, TER> {
1724 // This lambda is ugly - admittedly. The purpose of this lambda is to
1725 // limit the scope of sles so they don't overlap with
1726 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1727 // views, it's important that the sle's lifetime doesn't overlap.
1728
1729 auto const sleAcct = psb.peek(keylet::account(account));
1730 auto const sleBridge = peekBridge(psb, bridgeSpec);
1731 auto const sleClaimID = psb.peek(claimIDKeylet);
1732
1733 if (!(sleBridge && sleClaimID && sleAcct))
1735
1736 AccountID const thisDoor = (*sleBridge)[sfAccount];
1737
1739 {
1740 if (thisDoor == bridgeSpec.lockingChainDoor())
1741 {
1743 }
1744 else if (thisDoor == bridgeSpec.issuingChainDoor())
1745 {
1747 }
1748 else
1749 {
1751 }
1752 }
1753 STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain);
1754
1755 auto const sendingAmount = [&]() -> STAmount {
1756 STAmount r(thisChainAmount);
1757 r.setIssue(bridgeSpec.issue(srcChain));
1758 return r;
1759 }();
1760
1761 auto const [signersList, quorum, slTer] =
1762 getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1763
1764 if (!isTesSuccess(slTer))
1765 return std::unexpected(slTer);
1766
1767 XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1768
1769 auto const claimR = onClaim(
1770 curAtts,
1771 psb,
1772 sendingAmount,
1773 /*wasLockingChainSend*/ srcChain == STXChainBridge::ChainType::Locking,
1774 quorum,
1775 signersList,
1776 ctx_.journal);
1777 if (!claimR.has_value())
1778 return std::unexpected(claimR.error());
1779
1780 return ScopeResult{
1781 .rewardAccounts = claimR.value(),
1782 .rewardPoolSrc = (*sleClaimID)[sfAccount],
1783 .sendingAmount = sendingAmount,
1784 .srcChain = srcChain,
1785 .signatureReward = (*sleClaimID)[sfSignatureReward],
1786 };
1787 }();
1788
1789 if (!scopeResult.has_value())
1790 return scopeResult.error();
1791
1792 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1793 scopeResult.value();
1794 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1795
1796 auto const r = finalizeClaimHelper(
1797 psb,
1798 bridgeSpec,
1799 dst,
1800 dstTag,
1801 /*claimOwner*/ account,
1802 sendingAmount,
1803 rewardPoolSrc,
1804 signatureReward,
1805 rewardAccounts,
1806 srcChain,
1807 claimIDKeylet,
1808 OnTransferFail::KeepClaim,
1809 DepositAuthPolicy::DstCanBypass,
1810 ctx_.journal);
1811 if (!r.isTesSuccess())
1812 return r.ter();
1813
1814 psb.apply(ctx_.rawView());
1815
1816 return tesSUCCESS;
1817}
1818
1819//------------------------------------------------------------------------------
1820
1823{
1824 auto const maxSpend = [&] {
1825 auto const amount = ctx.tx[sfAmount];
1826 if (amount.native() && amount.signum() > 0)
1827 return amount.xrp();
1828 return XRPAmount{beast::kZero};
1829 }();
1830
1831 return TxConsequences{ctx.tx, maxSpend};
1832}
1833
1834NotTEC
1836{
1837 auto const amount = ctx.tx[sfAmount];
1838 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1839
1840 if (amount.signum() <= 0 || !isLegalNet(amount))
1841 return temBAD_AMOUNT;
1842
1843 if (amount.asset() != bridgeSpec.lockingChainIssue() &&
1844 amount.asset() != bridgeSpec.issuingChainIssue())
1845 return temBAD_ISSUER;
1846
1847 return tesSUCCESS;
1848}
1849
1850TER
1852{
1853 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1854 auto const amount = ctx.tx[sfAmount];
1855
1856 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1857 if (!sleBridge)
1858 {
1859 return tecNO_ENTRY;
1860 }
1861
1862 AccountID const thisDoor = (*sleBridge)[sfAccount];
1863 AccountID const account = ctx.tx[sfAccount];
1864
1865 if (thisDoor == account)
1866 {
1867 // Door account can't lock funds onto itself
1868 return tecXCHAIN_SELF_COMMIT;
1869 }
1870
1871 bool isLockingChain = false;
1872 {
1873 if (thisDoor == bridgeSpec.lockingChainDoor())
1874 {
1875 isLockingChain = true;
1876 }
1877 else if (thisDoor == bridgeSpec.issuingChainDoor())
1878 {
1879 isLockingChain = false;
1880 }
1881 else
1882 {
1883 return tecINTERNAL; // LCOV_EXCL_LINE
1884 }
1885 }
1886
1887 if (isLockingChain)
1888 {
1889 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].asset())
1891 }
1892 else
1893 {
1894 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].asset())
1896 }
1897
1898 return tesSUCCESS;
1899}
1900
1901TER
1903{
1904 PaymentSandbox psb(&ctx_.view());
1905
1906 auto const account = ctx_.tx[sfAccount];
1907 auto const amount = ctx_.tx[sfAmount];
1908 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1909
1910 auto const sleAccount = psb.read(keylet::account(account));
1911 if (!sleAccount)
1912 return tecINTERNAL; // LCOV_EXCL_LINE
1913
1914 auto const sleBridge = readBridge(psb, bridgeSpec);
1915 if (!sleBridge)
1916 return tecINTERNAL; // LCOV_EXCL_LINE
1917
1918 auto const dst = (*sleBridge)[sfAccount];
1919
1920 // Support dipping into reserves to pay the fee
1921 TransferHelperSubmittingAccountInfo submittingAccountInfo{
1922 .account = accountID_,
1923 .preFeeBalance = preFeeBalance_,
1924 .postFeeBalance = (*sleAccount)[sfBalance]};
1925
1926 auto const thTer = transferHelper(
1927 psb,
1928 account,
1929 dst,
1930 /*dstTag*/ std::nullopt,
1931 /*claimOwner*/ std::nullopt,
1932 amount,
1933 CanCreateDstPolicy::No,
1934 DepositAuthPolicy::Normal,
1935 submittingAccountInfo,
1936 ctx_.journal);
1937
1938 if (!isTesSuccess(thTer))
1939 return thTer;
1940
1941 psb.apply(ctx_.rawView());
1942
1943 return tesSUCCESS;
1944}
1945
1946//------------------------------------------------------------------------------
1947
1948NotTEC
1950{
1951 auto const reward = ctx.tx[sfSignatureReward];
1952
1953 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
1955
1956 return tesSUCCESS;
1957}
1958
1959TER
1961{
1962 auto const account = ctx.tx[sfAccount];
1963 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1964 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1965
1966 if (!sleBridge)
1967 {
1968 return tecNO_ENTRY;
1969 }
1970
1971 // Check that the reward matches
1972 auto const reward = ctx.tx[sfSignatureReward];
1973
1974 if (reward != (*sleBridge)[sfSignatureReward])
1975 {
1977 }
1978
1979 {
1980 // Check reserve
1981 auto const sleAcc = ctx.view.read(keylet::account(account));
1982 if (!sleAcc)
1983 return terNO_ACCOUNT;
1984
1985 auto const balance = (*sleAcc)[sfBalance];
1986 auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1987
1988 if (balance < reserve)
1990 }
1991
1992 return tesSUCCESS;
1993}
1994
1995TER
1997{
1998 auto const account = ctx_.tx[sfAccount];
1999 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2000 auto const reward = ctx_.tx[sfSignatureReward];
2001 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2002
2003 auto const sleAcct = ctx_.view().peek(keylet::account(account));
2004 if (!sleAcct)
2005 return tecINTERNAL; // LCOV_EXCL_LINE
2006
2007 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2008 if (!sleBridge)
2009 return tecINTERNAL; // LCOV_EXCL_LINE
2010
2011 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2012 if (claimID == 0)
2013 {
2014 // overflow
2015 return tecINTERNAL; // LCOV_EXCL_LINE
2016 }
2017
2018 (*sleBridge)[sfXChainClaimID] = claimID;
2019
2020 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2021 if (ctx_.view().exists(claimIDKeylet))
2022 {
2023 // already checked out!?!
2024 return tecINTERNAL; // LCOV_EXCL_LINE
2025 }
2026
2027 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2028
2029 (*sleClaimID)[sfAccount] = account;
2030 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2031 (*sleClaimID)[sfXChainClaimID] = claimID;
2032 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2033 (*sleClaimID)[sfSignatureReward] = reward;
2034 sleClaimID->setFieldArray(sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
2035
2036 // Add to owner directory
2037 {
2038 auto const page = ctx_.view().dirInsert(
2039 keylet::ownerDir(account), claimIDKeylet, describeOwnerDir(account));
2040 if (!page)
2041 return tecDIR_FULL; // LCOV_EXCL_LINE
2042 (*sleClaimID)[sfOwnerNode] = *page;
2043 }
2044
2045 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2046
2047 ctx_.view().insert(sleClaimID);
2048 ctx_.view().update(sleBridge);
2049 ctx_.view().update(sleAcct);
2050
2051 return tesSUCCESS;
2052}
2053
2054//------------------------------------------------------------------------------
2055
2056NotTEC
2058{
2059 return attestationPreflight<Attestations::AttestationClaim>(ctx);
2060}
2061
2062TER
2064{
2065 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2066}
2067
2068TER
2070{
2071 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2072}
2073
2074//------------------------------------------------------------------------------
2075
2076NotTEC
2078{
2079 return attestationPreflight<Attestations::AttestationCreateAccount>(ctx);
2080}
2081
2082TER
2084{
2085 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2086}
2087
2088TER
2090{
2091 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2092}
2093
2094//------------------------------------------------------------------------------
2095
2096NotTEC
2098{
2099 auto const amount = ctx.tx[sfAmount];
2100
2101 if (amount.signum() <= 0 || !amount.native())
2102 return temBAD_AMOUNT;
2103
2104 auto const reward = ctx.tx[sfSignatureReward];
2105 if (reward.signum() < 0 || !reward.native())
2106 return temBAD_AMOUNT;
2107
2108 if (reward.asset() != amount.asset())
2109 return temBAD_AMOUNT;
2110
2111 return tesSUCCESS;
2112}
2113
2114TER
2116{
2117 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2118 STAmount const amount = ctx.tx[sfAmount];
2119 STAmount const reward = ctx.tx[sfSignatureReward];
2120
2121 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2122 if (!sleBridge)
2123 {
2124 return tecNO_ENTRY;
2125 }
2126
2127 if (reward != (*sleBridge)[sfSignatureReward])
2128 {
2130 }
2131
2132 std::optional<STAmount> const minCreateAmount = (*sleBridge)[~sfMinAccountCreateAmount];
2133
2134 if (!minCreateAmount)
2136
2137 if (amount < *minCreateAmount)
2139
2140 if (minCreateAmount->asset() != amount.asset())
2142
2143 AccountID const thisDoor = (*sleBridge)[sfAccount];
2144 AccountID const account = ctx.tx[sfAccount];
2145 if (thisDoor == account)
2146 {
2147 // Door account can't lock funds onto itself
2148 return tecXCHAIN_SELF_COMMIT;
2149 }
2150
2152 {
2153 if (thisDoor == bridgeSpec.lockingChainDoor())
2154 {
2156 }
2157 else if (thisDoor == bridgeSpec.issuingChainDoor())
2158 {
2160 }
2161 else
2162 {
2163 return tecINTERNAL; // LCOV_EXCL_LINE
2164 }
2165 }
2166 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
2167
2168 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].asset())
2170
2171 if (!isXRP(bridgeSpec.issue(dstChain)))
2173
2174 return tesSUCCESS;
2175}
2176
2177TER
2179{
2180 PaymentSandbox psb(&ctx_.view());
2181
2182 AccountID const account = ctx_.tx[sfAccount];
2183 STAmount const amount = ctx_.tx[sfAmount];
2184 STAmount const reward = ctx_.tx[sfSignatureReward];
2185 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2186
2187 auto const sle = psb.peek(keylet::account(account));
2188 if (!sle)
2189 return tecINTERNAL; // LCOV_EXCL_LINE
2190
2191 auto const sleBridge = peekBridge(psb, bridge);
2192 if (!sleBridge)
2193 return tecINTERNAL; // LCOV_EXCL_LINE
2194
2195 auto const dst = (*sleBridge)[sfAccount];
2196
2197 // Support dipping into reserves to pay the fee
2198 TransferHelperSubmittingAccountInfo submittingAccountInfo{
2199 .account = accountID_,
2200 .preFeeBalance = preFeeBalance_,
2201 .postFeeBalance = (*sle)[sfBalance]};
2202 STAmount const toTransfer = amount + reward;
2203 auto const thTer = transferHelper(
2204 psb,
2205 account,
2206 dst,
2207 /*dstTag*/ std::nullopt,
2208 /*claimOwner*/ std::nullopt,
2209 toTransfer,
2210 CanCreateDstPolicy::Yes,
2211 DepositAuthPolicy::Normal,
2212 submittingAccountInfo,
2213 ctx_.journal);
2214
2215 if (!isTesSuccess(thTer))
2216 return thTer;
2217
2218 (*sleBridge)[sfXChainAccountCreateCount] = (*sleBridge)[sfXChainAccountCreateCount] + 1;
2219 psb.update(sleBridge);
2220
2221 psb.apply(ctx_.rawView());
2222
2223 return tesSUCCESS;
2224}
2225
2226void
2228{
2229 // No transaction-specific invariants yet (future work).
2230}
2231
2232bool
2234 STTx const&,
2235 TER,
2236 XRPAmount,
2237 ReadView const&,
2238 beast::Journal const&)
2239{
2240 // No transaction-specific invariants yet (future work).
2241 return true;
2242}
2243
2244void
2246{
2247 // No transaction-specific invariants yet (future work).
2248}
2249
2250bool
2252 STTx const&,
2253 TER,
2254 XRPAmount,
2255 ReadView const&,
2256 beast::Journal const&)
2257{
2258 // No transaction-specific invariants yet (future work).
2259 return true;
2260}
2261
2262void
2264{
2265 // No transaction-specific invariants yet (future work).
2266}
2267
2268bool
2270{
2271 // No transaction-specific invariants yet (future work).
2272 return true;
2273}
2274
2275void
2277{
2278 // No transaction-specific invariants yet (future work).
2279}
2280
2281bool
2283 STTx const&,
2284 TER,
2285 XRPAmount,
2286 ReadView const&,
2287 beast::Journal const&)
2288{
2289 // No transaction-specific invariants yet (future work).
2290 return true;
2291}
2292
2293void
2295{
2296 // No transaction-specific invariants yet (future work).
2297}
2298
2299bool
2301 STTx const&,
2302 TER,
2303 XRPAmount,
2304 ReadView const&,
2305 beast::Journal const&)
2306{
2307 // No transaction-specific invariants yet (future work).
2308 return true;
2309}
2310
2311void
2313{
2314 // No transaction-specific invariants yet (future work).
2315}
2316
2317bool
2319 STTx const&,
2320 TER,
2321 XRPAmount,
2322 ReadView const&,
2323 beast::Journal const&)
2324{
2325 // No transaction-specific invariants yet (future work).
2326 return true;
2327}
2328
2329void
2331{
2332 // No transaction-specific invariants yet (future work).
2333}
2334
2335bool
2337 STTx const&,
2338 TER,
2339 XRPAmount,
2340 ReadView const&,
2341 beast::Journal const&)
2342{
2343 // No transaction-specific invariants yet (future work).
2344 return true;
2345}
2346
2347void
2349{
2350 // No transaction-specific invariants yet (future work).
2351}
2352
2353bool
2355 STTx const&,
2356 TER,
2357 XRPAmount,
2358 ReadView const&,
2359 beast::Journal const&)
2360{
2361 // No transaction-specific invariants yet (future work).
2362 return true;
2363}
2364
2365} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
State information when applying a tx.
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:118
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)
Definition Number.cpp:111
static RoundingMode getround()
Definition Number.cpp:105
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
A public key.
Definition PublicKey.h:42
Interface for ledger entry changes.
Definition RawView.h:14
A view into a ledger.
Definition ReadView.h:31
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.
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:512
Asset const & asset() const
Definition STAmount.h:478
STAmount const & value() const noexcept
Definition STAmount.h:592
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:501
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:775
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:120
XRPAmount preFeeBalance_
Definition Transactor.h:121
ApplyContext & ctx_
Definition Transactor.h:116
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:38
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)
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:328
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:471
Keylet signerList(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:316
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:495
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:363
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:481
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:5
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_ACCOUNT
Definition TER.h:209
constexpr size_t kXbridgeMaxAccountCreateClaims
Definition XChainBridge.h:8
bool isTerRetry(TER x) noexcept
Definition TER.h:657
bool isXRP(AccountID const &c)
Definition AccountID.h:70
@ tefBAD_LEDGER
Definition TER.h:160
bool isLegalNet(STAmount const &value)
Definition STAmount.h:598
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
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
bool isTefFailure(TER x) noexcept
Definition TER.h:651
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:81
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
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:28
@ temBAD_ISSUER
Definition TER.h:79
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:121
@ temMALFORMED
Definition TER.h:73
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition TER.h:120
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:119
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition TER.h:122
@ temBAD_AMOUNT
Definition TER.h:75
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition TER.h:117
@ temXCHAIN_BAD_PROOF
Definition TER.h:118
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:344
@ tecDIR_FULL
Definition TER.h:285
@ tecUNFUNDED_PAYMENT
Definition TER.h:283
@ tecNO_ENTRY
Definition TER.h:304
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:342
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition TER.h:343
@ tecXCHAIN_BAD_TRANSFER_ISSUE
Definition TER.h:334
@ tecNO_DST_INSUF_XRP
Definition TER.h:289
@ tecXCHAIN_WRONG_CHAIN
Definition TER.h:340
@ tecINTERNAL
Definition TER.h:308
@ tecXCHAIN_PROOF_UNKNOWN_KEY
Definition TER.h:338
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:345
@ tecXCHAIN_PAYMENT_FAILED
Definition TER.h:347
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:335
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition TER.h:346
@ tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE
Definition TER.h:339
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:336
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:349
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:350
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:348
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:337
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:341
@ tecNO_PERMISSION
Definition TER.h:303
@ tecDST_TAG_NEEDED
Definition TER.h:307
@ tecNO_ISSUER
Definition TER.h:297
@ tecDUPLICATE
Definition TER.h:313
@ tecNO_DST
Definition TER.h:288
bool isTecClaim(TER x) noexcept
Definition TER.h:670
@ tesSUCCESS
Definition TER.h:240
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:19
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
State information when preflighting a tx.
Definition Transactor.h:18
T unexpected(T... args)