rippled
Loading...
Searching...
No Matches
XChainBridge.cpp
1#include <xrpld/app/paths/Flow.h>
2#include <xrpld/app/tx/detail/SignerEntries.h>
3#include <xrpld/app/tx/detail/Transactor.h>
4#include <xrpld/app/tx/detail/XChainBridge.h>
5
6#include <xrpl/basics/Log.h>
7#include <xrpl/basics/Number.h>
8#include <xrpl/basics/chrono.h>
9#include <xrpl/beast/utility/Journal.h>
10#include <xrpl/beast/utility/instrumentation.h>
11#include <xrpl/ledger/ApplyView.h>
12#include <xrpl/ledger/PaymentSandbox.h>
13#include <xrpl/ledger/View.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/PublicKey.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STObject.h>
21#include <xrpl/protocol/STXChainBridge.h>
22#include <xrpl/protocol/TER.h>
23#include <xrpl/protocol/TxFlags.h>
24#include <xrpl/protocol/XChainAttestations.h>
25#include <xrpl/protocol/XRPAmount.h>
26
27#include <unordered_map>
28#include <unordered_set>
29
30namespace xrpl {
31
32/*
33 Bridges connect two independent ledgers: a "locking chain" and an "issuing
34 chain". An asset can be moved from the locking chain to the issuing chain by
35 putting it into trust on the locking chain, and issuing a "wrapped asset"
36 that represents the locked asset on the issuing chain.
37
38 Note that a bridge is not an exchange. There is no exchange rate: one wrapped
39 asset on the issuing chain always represents one asset in trust on the
40 locking chain. The bridge also does not exchange an asset on the locking
41 chain for an asset on the issuing chain.
42
43 A good model for thinking about bridges is a box that contains an infinite
44 number of "wrapped tokens". When a token from the locking chain
45 (locking-chain-token) is put into the box, a wrapped token is taken out of
46 the box and put onto the issuing chain (issuing-chain-token). No one can use
47 the locking-chain-token while it remains in the box. When an
48 issuing-chain-token is returned to the box, one locking-chain-token is taken
49 out of the box and put back onto the locking chain.
50
51 This requires a way to put assets into trust on one chain (put a
52 locking-chain-token into the box). A regular XRP account is used for this.
53 This account is called a "door account". Much in the same way that a door is
54 used to go from one room to another, a door account is used to move from one
55 chain to another. This account will be jointly controlled by a set of witness
56 servers by using the ledger's multi-signature support. The master key will be
57 disabled. These witness servers are trusted in the sense that if a quorum of
58 them collude, they can steal the funds put into trust.
59
60 This also requires a way to prove that assets were put into the box - either
61 a locking-chain-token on the locking chain or returning an
62 issuing-chain-token on the issuing chain. A set of servers called "witness
63 servers" fill this role. These servers watch the ledger for these
64 transactions, and attest that the given events happened on the different
65 chains by signing messages with the event information.
66
67 There needs to be a way to prevent the attestations from the witness
68 servers from being used more than once. "Claim ids" fill this role. A claim
69 id must be acquired on the destination chain before the asset is "put into
70 the box" on the source chain. This claim id has a unique id, and once it is
71 destroyed it can never exist again (it's a simple counter). The attestations
72 reference this claim id, and are accumulated on the claim id. Once a quorum
73 is reached, funds can move. Once the funds move, the claim id is destroyed.
74
75 Finally, a claim id requires that the sender has an account on the
76 destination chain. For some chains, this can be a problem - especially if
77 the wrapped asset represents XRP, and XRP is needed to create an account.
78 There's a bootstrap problem. To address this, there is a special transaction
79 used to create accounts. This transaction does not require a claim id.
80
81 See the document "docs/bridge/spec.md" for a full description of how
82 bridges and their transactions work.
83*/
84
85namespace {
86
87// Check that the public key is allowed to sign for the given account. If the
88// account does not exist on the ledger, then the public key must be the master
89// key for the given account if it existed. Otherwise the key must be an enabled
90// master key or a regular key for the existing account.
91TER
92checkAttestationPublicKey(
93 ReadView const& view,
95 AccountID const& attestationSignerAccount,
96 PublicKey const& pk,
98{
99 if (!signersList.contains(attestationSignerAccount))
100 {
101 return tecNO_PERMISSION;
102 }
103
104 AccountID const accountFromPK = calcAccountID(pk);
105
106 if (auto const sleAttestationSigningAccount = view.read(keylet::account(attestationSignerAccount)))
107 {
108 if (accountFromPK == attestationSignerAccount)
109 {
110 // master key
111 if (sleAttestationSigningAccount->getFieldU32(sfFlags) & lsfDisableMaster)
112 {
113 JLOG(j.trace()) << "Attempt to add an attestation with "
114 "disabled master key.";
116 }
117 }
118 else
119 {
120 // regular key
121 if (std::optional<AccountID> regularKey = (*sleAttestationSigningAccount)[~sfRegularKey];
122 regularKey != accountFromPK)
123 {
124 if (!regularKey)
125 {
126 JLOG(j.trace()) << "Attempt to add an attestation with "
127 "account present and non-present regular key.";
128 }
129 else
130 {
131 JLOG(j.trace()) << "Attempt to add an attestation with "
132 "account present and mismatched "
133 "regular key/public key.";
134 }
136 }
137 }
138 }
139 else
140 {
141 // account does not exist.
142 if (calcAccountID(pk) != attestationSignerAccount)
143 {
144 JLOG(j.trace()) << "Attempt to add an attestation with non-existant account "
145 "and mismatched pk/account pair.";
147 }
148 }
149
150 return tesSUCCESS;
151}
152
153// If there is a quorum of attestations for the given parameters, then
154// return the reward accounts, otherwise return TER for the error.
155// Also removes attestations that are no longer part of the signers list.
156//
157// Note: the dst parameter is what the attestations are attesting to, which
158// is not always used (it is used when automatically triggering a transfer
159// from an `addAttestation` transaction, it is not used in a `claim`
160// transaction). If the `checkDst` parameter is `check`, the attestations
161// must attest to this destination, if it is `ignore` then the `dst` of the
162// attestations are not checked (as for a `claim` transaction)
163
164enum class CheckDst { check, ignore };
165template <class TAttestation>
166Expected<std::vector<AccountID>, TER>
167claimHelper(
168 XChainAttestationsBase<TAttestation>& attestations,
169 ReadView const& view,
170 typename TAttestation::MatchFields const& toMatch,
171 CheckDst checkDst,
172 std::uint32_t quorum,
175{
176 // Remove attestations that are not valid signers. They may be no longer
177 // part of the signers list, or their master key may have been disabled,
178 // or their regular key may have changed
179 attestations.erase_if([&](auto const& a) {
180 return checkAttestationPublicKey(view, signersList, a.keyAccount, a.publicKey, j) != tesSUCCESS;
181 });
182
183 // Check if we have quorum for the amount specified on the new claimAtt
184 std::vector<AccountID> rewardAccounts;
185 rewardAccounts.reserve(attestations.size());
186 std::uint32_t weight = 0;
187 for (auto const& a : attestations)
188 {
189 auto const matchR = a.match(toMatch);
190 // The dest must match if claimHelper is being run as a result of an add
191 // attestation transaction. The dst does not need to match if the
192 // claimHelper is being run using an explicit claim transaction.
193 using enum AttestationMatch;
194 if (matchR == nonDstMismatch || (checkDst == CheckDst::check && matchR != match))
195 continue;
196 auto i = signersList.find(a.keyAccount);
197 if (i == signersList.end())
198 {
199 // LCOV_EXCL_START
200 UNREACHABLE("xrpl::claimHelper : invalid inputs"); // should have already
201 // been checked
202 continue;
203 // LCOV_EXCL_STOP
204 }
205 weight += i->second;
206 rewardAccounts.push_back(a.rewardAccount);
207 }
208
209 if (weight >= quorum)
210 return rewardAccounts;
211
213}
214
246struct OnNewAttestationResult
247{
249 // `changed` is true if the attestation collection changed in any way
250 // (added/removed/changed)
251 bool changed{false};
252};
253
254template <class TAttestation>
255[[nodiscard]] OnNewAttestationResult
256onNewAttestations(
257 XChainAttestationsBase<TAttestation>& attestations,
258 ReadView const& view,
259 typename TAttestation::TSignedAttestation const* attBegin,
260 typename TAttestation::TSignedAttestation const* attEnd,
261 std::uint32_t quorum,
264{
265 bool changed = false;
266 for (auto att = attBegin; att != attEnd; ++att)
267 {
268 if (checkAttestationPublicKey(view, signersList, att->attestationSignerAccount, att->publicKey, j) !=
270 {
271 // The checkAttestationPublicKey is not strictly necessary here (it
272 // should be checked in a preclaim step), but it would be bad to let
273 // this slip through if that changes, and the check is relatively
274 // cheap, so we check again
275 continue;
276 }
277
278 auto const& claimSigningAccount = att->attestationSignerAccount;
279 if (auto i = std::find_if(
280 attestations.begin(),
281 attestations.end(),
282 [&](auto const& a) { return a.keyAccount == claimSigningAccount; });
283 i != attestations.end())
284 {
285 // existing attestation
286 // replace old attestation with new attestation
287 *i = TAttestation{*att};
288 changed = true;
289 }
290 else
291 {
292 attestations.emplace_back(*att);
293 changed = true;
294 }
295 }
296
297 auto r = claimHelper(
298 attestations, view, typename TAttestation::MatchFields{*attBegin}, CheckDst::check, quorum, signersList, j);
299
300 if (!r.has_value())
301 return {std::nullopt, changed};
302
303 return {std::move(r.value()), changed};
304};
305
306// Check if there is a quorum of attestations for the given amount and
307// chain. If so return the reward accounts, if not return the tec code (most
308// likely tecXCHAIN_CLAIM_NO_QUORUM)
309Expected<std::vector<AccountID>, TER>
310onClaim(
311 XChainClaimAttestations& attestations,
312 ReadView const& view,
313 STAmount const& sendingAmount,
314 bool wasLockingChainSend,
315 std::uint32_t quorum,
318{
319 XChainClaimAttestation::MatchFields toMatch{sendingAmount, wasLockingChainSend, std::nullopt};
320 return claimHelper(attestations, view, toMatch, CheckDst::ignore, quorum, signersList, j);
321}
322
323enum class CanCreateDstPolicy { no, yes };
324
325enum class DepositAuthPolicy { normal, dstCanBypass };
326
327// Allow the fee to dip into the reserve. To support this, information about the
328// submitting account needs to be fed to the transfer helper.
329struct TransferHelperSubmittingAccountInfo
330{
332 STAmount preFeeBalance;
333 STAmount postFeeBalance;
334};
335
358TER
359transferHelper(
360 PaymentSandbox& psb,
361 AccountID const& src,
362 AccountID const& dst,
363 std::optional<std::uint32_t> const& dstTag,
364 std::optional<AccountID> const& claimOwner,
365 STAmount const& amt,
366 CanCreateDstPolicy canCreate,
367 DepositAuthPolicy depositAuthPolicy,
368 std::optional<TransferHelperSubmittingAccountInfo> const& submittingAccountInfo,
370{
371 if (dst == src)
372 return tesSUCCESS;
373
374 auto const dstK = keylet::account(dst);
375 if (auto sleDst = psb.read(dstK))
376 {
377 // Check dst tag and deposit auth
378
379 if ((sleDst->getFlags() & lsfRequireDestTag) && !dstTag)
380 return tecDST_TAG_NEEDED;
381
382 // If the destination is the claim owner, and this is a claim
383 // transaction, that's the dst account sending funds to itself. It
384 // can bypass deposit auth.
385 bool const canBypassDepositAuth = dst == claimOwner && depositAuthPolicy == DepositAuthPolicy::dstCanBypass;
386
387 if (!canBypassDepositAuth && (sleDst->getFlags() & lsfDepositAuth) &&
388 !psb.exists(keylet::depositPreauth(dst, src)))
389 {
390 return tecNO_PERMISSION;
391 }
392 }
393 else if (!amt.native() || canCreate == CanCreateDstPolicy::no)
394 {
395 return tecNO_DST;
396 }
397
398 if (amt.native())
399 {
400 auto const sleSrc = psb.peek(keylet::account(src));
401 XRPL_ASSERT(sleSrc, "xrpl::transferHelper : non-null source account");
402 if (!sleSrc)
403 return tecINTERNAL; // LCOV_EXCL_LINE
404
405 {
406 auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
407 auto const reserve = psb.fees().accountReserve(ownerCount);
408
409 auto const availableBalance = [&]() -> STAmount {
410 STAmount const curBal = (*sleSrc)[sfBalance];
411 // Checking that account == src and postFeeBalance == curBal is
412 // not strictly necessary, but helps protect against future
413 // changes
414 if (!submittingAccountInfo || submittingAccountInfo->account != src ||
415 submittingAccountInfo->postFeeBalance != curBal)
416 return curBal;
417 return submittingAccountInfo->preFeeBalance;
418 }();
419
420 if (availableBalance < amt + reserve)
421 {
422 return tecUNFUNDED_PAYMENT;
423 }
424 }
425
426 auto sleDst = psb.peek(dstK);
427 if (!sleDst)
428 {
429 if (canCreate == CanCreateDstPolicy::no)
430 {
431 // Already checked, but OK to check again
432 return tecNO_DST;
433 }
434 if (amt < psb.fees().reserve)
435 {
436 JLOG(j.trace()) << "Insufficient payment to create account.";
437 return tecNO_DST_INSUF_XRP;
438 }
439
440 // Create the account.
441 sleDst = std::make_shared<SLE>(dstK);
442 sleDst->setAccountID(sfAccount, dst);
443 sleDst->setFieldU32(sfSequence, psb.seq());
444
445 psb.insert(sleDst);
446 }
447
448 (*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
449 (*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
450 psb.update(sleSrc);
451 psb.update(sleDst);
452
453 return tesSUCCESS;
454 }
455
456 auto const result = flow(
457 psb,
458 amt,
459 src,
460 dst,
461 STPathSet{},
462 /*default path*/ true,
463 /*partial payment*/ false,
464 /*owner pays transfer fee*/ true,
465 /*offer crossing*/ OfferCrossing::no,
466 /*limit quality*/ std::nullopt,
467 /*sendmax*/ std::nullopt,
468 /*domain id*/ std::nullopt,
469 j);
470
471 if (auto const r = result.result(); isTesSuccess(r) || isTecClaim(r) || isTerRetry(r))
472 return r;
474}
475
481enum class OnTransferFail {
483 removeClaim,
485 keepClaim
486};
487
488struct FinalizeClaimHelperResult
489{
491 std::optional<TER> mainFundsTer;
492 // TER for transfering the reward funds
493 std::optional<TER> rewardTer;
494 // TER for removing the sle (if is sle is to be removed)
495 std::optional<TER> rmSleTer;
496
497 // Helper to check for overall success. If there wasn't overall success the
498 // individual ters can be used to decide what needs to be done.
499 bool
500 isTesSuccess() const
501 {
502 return mainFundsTer == tesSUCCESS && rewardTer == tesSUCCESS && (!rmSleTer || *rmSleTer == tesSUCCESS);
503 }
504
505 TER
506 ter() const
507 {
508 if ((!mainFundsTer || *mainFundsTer == tesSUCCESS) && (!rewardTer || *rewardTer == tesSUCCESS) &&
509 (!rmSleTer || *rmSleTer == tesSUCCESS))
510 return tesSUCCESS;
511
512 // if any phase return a tecINTERNAL or a tef, prefer returning those
513 // codes
514 if (mainFundsTer && (isTefFailure(*mainFundsTer) || *mainFundsTer == tecINTERNAL))
515 return *mainFundsTer;
516 if (rewardTer && (isTefFailure(*rewardTer) || *rewardTer == tecINTERNAL))
517 return *rewardTer;
518 if (rmSleTer && (isTefFailure(*rmSleTer) || *rmSleTer == tecINTERNAL))
519 return *rmSleTer;
520
521 // Only after the tecINTERNAL and tef are checked, return the first
522 // non-success error code.
523 if (mainFundsTer && mainFundsTer != tesSUCCESS)
524 return *mainFundsTer;
525 if (rewardTer && rewardTer != tesSUCCESS)
526 return *rewardTer;
527 if (rmSleTer && rmSleTer != tesSUCCESS)
528 return *rmSleTer;
529 return tesSUCCESS;
530 }
531};
532
561FinalizeClaimHelperResult
562finalizeClaimHelper(
563 PaymentSandbox& outerSb,
564 STXChainBridge const& bridgeSpec,
565 AccountID const& dst,
566 std::optional<std::uint32_t> const& dstTag,
567 AccountID const& claimOwner,
568 STAmount const& sendingAmount,
569 AccountID const& rewardPoolSrc,
570 STAmount const& rewardPool,
571 std::vector<AccountID> const& rewardAccounts,
572 STXChainBridge::ChainType const srcChain,
573 Keylet const& claimIDKeylet,
574 OnTransferFail onTransferFail,
575 DepositAuthPolicy depositAuthPolicy,
577{
578 FinalizeClaimHelperResult result;
579
580 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
581 STAmount const thisChainAmount = [&] {
582 STAmount r = sendingAmount;
583 r.setIssue(bridgeSpec.issue(dstChain));
584 return r;
585 }();
586 auto const& thisDoor = bridgeSpec.door(dstChain);
587
588 {
589 PaymentSandbox innerSb{&outerSb};
590 // If distributing the reward pool fails, the mainFunds transfer should
591 // be rolled back
592 //
593 // If the claim ID is removed, the rewards should be distributed
594 // even if the mainFunds fails.
595 //
596 // If OnTransferFail::removeClaim, the claim should be removed even if
597 // the rewards cannot be distributed.
598
599 // transfer funds to the dst
600 result.mainFundsTer = transferHelper(
601 innerSb,
602 thisDoor,
603 dst,
604 dstTag,
605 claimOwner,
606 thisChainAmount,
607 CanCreateDstPolicy::yes,
608 depositAuthPolicy,
610 j);
611
612 if (!isTesSuccess(*result.mainFundsTer) && onTransferFail == OnTransferFail::keepClaim)
613 {
614 return result;
615 }
616
617 // handle the reward pool
618 result.rewardTer = [&]() -> TER {
619 if (rewardAccounts.empty())
620 return tesSUCCESS;
621
622 // distribute the reward pool
623 // if the transfer failed, distribute the pool for "OnTransferFail"
624 // cases (the attesters did their job)
625 STAmount const share = [&] {
626 auto const round_mode = innerSb.rules().enabled(fixXChainRewardRounding)
629 saveNumberRoundMode _{Number::setround(round_mode)};
630
631 STAmount const den{rewardAccounts.size()};
632 return divide(rewardPool, den, rewardPool.issue());
633 }();
634 STAmount distributed = rewardPool.zeroed();
635 for (auto const& rewardAccount : rewardAccounts)
636 {
637 auto const thTer = transferHelper(
638 innerSb,
639 rewardPoolSrc,
640 rewardAccount,
641 /*dstTag*/ std::nullopt,
642 // claim owner is not relevant to distributing rewards
643 /*claimOwner*/ std::nullopt,
644 share,
645 CanCreateDstPolicy::no,
646 DepositAuthPolicy::normal,
648 j);
649
650 if (thTer == tecUNFUNDED_PAYMENT || thTer == tecINTERNAL)
651 return thTer;
652
653 if (isTesSuccess(thTer))
654 distributed += share;
655
656 // let txn succeed if error distributing rewards (other than
657 // inability to pay)
658 }
659
660 if (distributed > rewardPool)
661 return tecINTERNAL; // LCOV_EXCL_LINE
662
663 return tesSUCCESS;
664 }();
665
666 if (!isTesSuccess(*result.rewardTer) &&
667 (onTransferFail == OnTransferFail::keepClaim || *result.rewardTer == tecINTERNAL))
668 {
669 return result;
670 }
671
672 if (!isTesSuccess(*result.mainFundsTer) || isTesSuccess(*result.rewardTer))
673 {
674 // Note: if the mainFunds transfer succeeds and the result transfer
675 // fails, we don't apply the inner sandbox (i.e. the mainTransfer is
676 // rolled back)
677 innerSb.apply(outerSb);
678 }
679 }
680
681 if (auto const sleClaimID = outerSb.peek(claimIDKeylet))
682 {
683 auto const cidOwner = (*sleClaimID)[sfAccount];
684 {
685 // Remove the claim id
686 auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
687 auto const page = (*sleClaimID)[sfOwnerNode];
688 if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
689 {
690 JLOG(j.fatal()) << "Unable to delete xchain seq number from owner.";
691 result.rmSleTer = tefBAD_LEDGER;
692 return result;
693 }
694
695 // Remove the claim id from the ledger
696 outerSb.erase(sleClaimID);
697
698 adjustOwnerCount(outerSb, sleOwner, -1, j);
699 }
700 }
701
702 return result;
703}
704
715getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journal j)
716{
719
720 AccountID const thisDoor = sleBridge[sfAccount];
721 auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
722
723 if (!sleDoor)
724 {
725 return {r, q, tecINTERNAL};
726 }
727
728 auto const sleS = view.read(keylet::signers(sleBridge[sfAccount]));
729 if (!sleS)
730 {
731 return {r, q, tecXCHAIN_NO_SIGNERS_LIST};
732 }
733 q = (*sleS)[sfSignerQuorum];
734
735 auto const accountSigners = SignerEntries::deserialize(*sleS, j, "ledger");
736
737 if (!accountSigners)
738 {
739 return {r, q, tecINTERNAL};
740 }
741
742 for (auto const& as : *accountSigners)
743 {
744 r[as.account] = as.weight;
745 }
746
747 return {std::move(r), q, tesSUCCESS};
748};
749
750template <class R, class F>
752readOrpeekBridge(F&& getter, STXChainBridge const& bridgeSpec)
753{
754 auto tryGet = [&](STXChainBridge::ChainType ct) -> std::shared_ptr<R> {
755 if (auto r = getter(bridgeSpec, ct))
756 {
757 if ((*r)[sfXChainBridge] == bridgeSpec)
758 return r;
759 }
760 return nullptr;
761 };
762 if (auto r = tryGet(STXChainBridge::ChainType::locking))
763 return r;
765}
766
768peekBridge(ApplyView& v, STXChainBridge const& bridgeSpec)
769{
770 return readOrpeekBridge<SLE>(
771 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> std::shared_ptr<SLE> {
772 return v.peek(keylet::bridge(b, ct));
773 },
774 bridgeSpec);
775}
776
778readBridge(ReadView const& v, STXChainBridge const& bridgeSpec)
779{
780 return readOrpeekBridge<SLE const>(
781 [&v](STXChainBridge const& b, STXChainBridge::ChainType ct) -> std::shared_ptr<SLE const> {
782 return v.read(keylet::bridge(b, ct));
783 },
784 bridgeSpec);
785}
786
787// Precondition: all the claims in the range are consistent. They must sign for
788// the same event (amount, sending account, claim id, etc).
789template <class TIter>
790TER
791applyClaimAttestations(
792 ApplyView& view,
793 RawView& rawView,
794 TIter attBegin,
795 TIter attEnd,
796 STXChainBridge const& bridgeSpec,
797 STXChainBridge::ChainType const srcChain,
799 std::uint32_t quorum,
801{
802 if (attBegin == attEnd)
803 return tesSUCCESS;
804
805 PaymentSandbox psb(&view);
806
807 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, attBegin->claimID);
808
809 struct ScopeResult
810 {
811 OnNewAttestationResult newAttResult;
812 STAmount rewardAmount;
813 AccountID cidOwner;
814 };
815
816 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
817 // This lambda is ugly - admittedly. The purpose of this lambda is to
818 // limit the scope of sles so they don't overlap with
819 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
820 // views, it's important that the sle's lifetime doesn't overlap.
821 auto const sleClaimID = psb.peek(claimIDKeylet);
822 if (!sleClaimID)
824
825 // Add claims that are part of the signer's list to the "claims" vector
827 atts.reserve(std::distance(attBegin, attEnd));
828 for (auto att = attBegin; att != attEnd; ++att)
829 {
830 if (!signersList.contains(att->attestationSignerAccount))
831 continue;
832 atts.push_back(*att);
833 }
834
835 if (atts.empty())
836 {
838 }
839
840 AccountID const otherChainSource = (*sleClaimID)[sfOtherChainSource];
841 if (attBegin->sendingAccount != otherChainSource)
842 {
844 }
845
846 {
847 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
848
849 STXChainBridge::ChainType const attDstChain = STXChainBridge::dstChain(attBegin->wasLockingChainSend);
850
851 if (attDstChain != dstChain)
852 {
854 }
855 }
856
857 XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)};
858
859 auto const newAttResult =
860 onNewAttestations(curAtts, view, &atts[0], &atts[0] + atts.size(), quorum, signersList, j);
861
862 // update the claim id
863 sleClaimID->setFieldArray(sfXChainClaimAttestations, curAtts.toSTArray());
864 psb.update(sleClaimID);
865
866 return ScopeResult{newAttResult, (*sleClaimID)[sfSignatureReward], (*sleClaimID)[sfAccount]};
867 }();
868
869 if (!scopeResult.has_value())
870 return scopeResult.error();
871
872 auto const& [newAttResult, rewardAmount, cidOwner] = scopeResult.value();
873 auto const& [rewardAccounts, attListChanged] = newAttResult;
874 if (rewardAccounts && attBegin->dst)
875 {
876 auto const r = finalizeClaimHelper(
877 psb,
878 bridgeSpec,
879 *attBegin->dst,
880 /*dstTag*/ std::nullopt,
881 cidOwner,
882 attBegin->sendingAmount,
883 cidOwner,
884 rewardAmount,
885 *rewardAccounts,
886 srcChain,
887 claimIDKeylet,
888 OnTransferFail::keepClaim,
889 DepositAuthPolicy::normal,
890 j);
891
892 auto const rTer = r.ter();
893
894 if (!isTesSuccess(rTer) && (!attListChanged || rTer == tecINTERNAL || rTer == tefBAD_LEDGER))
895 return rTer;
896 }
897
898 psb.apply(rawView);
899
900 return tesSUCCESS;
901}
902
903template <class TIter>
904TER
905applyCreateAccountAttestations(
906 ApplyView& view,
907 RawView& rawView,
908 TIter attBegin,
909 TIter attEnd,
910 AccountID const& doorAccount,
911 Keylet const& doorK,
912 STXChainBridge const& bridgeSpec,
913 Keylet const& bridgeK,
914 STXChainBridge::ChainType const srcChain,
916 std::uint32_t quorum,
918{
919 if (attBegin == attEnd)
920 return tesSUCCESS;
921
922 PaymentSandbox psb(&view);
923
924 auto const claimCountResult = [&]() -> Expected<std::uint64_t, TER> {
925 auto const sleBridge = psb.peek(bridgeK);
926 if (!sleBridge)
927 return Unexpected(tecINTERNAL);
928
929 return (*sleBridge)[sfXChainAccountClaimCount];
930 }();
931
932 if (!claimCountResult.has_value())
933 return claimCountResult.error();
934
935 std::uint64_t const claimCount = claimCountResult.value();
936
937 if (attBegin->createCount <= claimCount)
938 {
940 }
941 if (attBegin->createCount >= claimCount + xbridgeMaxAccountCreateClaims)
942 {
943 // Limit the number of claims on the account
945 }
946
947 {
948 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
949
950 STXChainBridge::ChainType const attDstChain = STXChainBridge::dstChain(attBegin->wasLockingChainSend);
951
952 if (attDstChain != dstChain)
953 {
955 }
956 }
957
958 auto const claimIDKeylet = keylet::xChainCreateAccountClaimID(bridgeSpec, attBegin->createCount);
959
960 struct ScopeResult
961 {
962 OnNewAttestationResult newAttResult;
963 bool createCID;
964 XChainCreateAccountAttestations curAtts;
965 };
966
967 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
968 // This lambda is ugly - admittedly. The purpose of this lambda is to
969 // limit the scope of sles so they don't overlap with
970 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
971 // views, it's important that the sle's lifetime doesn't overlap.
972
973 // sleClaimID may be null. If it's null it isn't created until the end
974 // of this function (if needed)
975 auto const sleClaimID = psb.peek(claimIDKeylet);
976 bool createCID = false;
977 if (!sleClaimID)
978 {
979 createCID = true;
980
981 auto const sleDoor = psb.peek(doorK);
982 if (!sleDoor)
983 return Unexpected(tecINTERNAL);
984
985 // Check reserve
986 auto const balance = (*sleDoor)[sfBalance];
987 auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
988
989 if (balance < reserve)
991 }
992
994 atts.reserve(std::distance(attBegin, attEnd));
995 for (auto att = attBegin; att != attEnd; ++att)
996 {
997 if (!signersList.contains(att->attestationSignerAccount))
998 continue;
999 atts.push_back(*att);
1000 }
1001 if (atts.empty())
1002 {
1004 }
1005
1006 XChainCreateAccountAttestations curAtts = [&] {
1007 if (sleClaimID)
1008 return XChainCreateAccountAttestations{sleClaimID->getFieldArray(sfXChainCreateAccountAttestations)};
1009 return XChainCreateAccountAttestations{};
1010 }();
1011
1012 auto const newAttResult =
1013 onNewAttestations(curAtts, view, &atts[0], &atts[0] + atts.size(), quorum, signersList, j);
1014
1015 if (!createCID)
1016 {
1017 // Modify the object before it's potentially deleted, so the meta
1018 // data will include the new attestations
1019 if (!sleClaimID)
1020 return Unexpected(tecINTERNAL);
1021 sleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray());
1022 psb.update(sleClaimID);
1023 }
1024 return ScopeResult{newAttResult, createCID, curAtts};
1025 }();
1026
1027 if (!scopeResult.has_value())
1028 return scopeResult.error();
1029
1030 auto const& [attResult, createCID, curAtts] = scopeResult.value();
1031 auto const& [rewardAccounts, attListChanged] = attResult;
1032
1033 // Account create transactions must happen in order
1034 if (rewardAccounts && claimCount + 1 == attBegin->createCount)
1035 {
1036 auto const r = finalizeClaimHelper(
1037 psb,
1038 bridgeSpec,
1039 attBegin->toCreate,
1040 /*dstTag*/ std::nullopt,
1041 doorAccount,
1042 attBegin->sendingAmount,
1043 /*rewardPoolSrc*/ doorAccount,
1044 attBegin->rewardAmount,
1045 *rewardAccounts,
1046 srcChain,
1047 claimIDKeylet,
1048 OnTransferFail::removeClaim,
1049 DepositAuthPolicy::normal,
1050 j);
1051
1052 auto const rTer = r.ter();
1053
1054 if (!isTesSuccess(rTer))
1055 {
1056 if (rTer == tecINTERNAL || rTer == tecUNFUNDED_PAYMENT || isTefFailure(rTer))
1057 return rTer;
1058 }
1059 // Move past this claim id even if it fails, so it doesn't block
1060 // subsequent claim ids
1061 auto const sleBridge = psb.peek(bridgeK);
1062 if (!sleBridge)
1063 return tecINTERNAL; // LCOV_EXCL_LINE
1064 (*sleBridge)[sfXChainAccountClaimCount] = attBegin->createCount;
1065 psb.update(sleBridge);
1066 }
1067 else if (createCID)
1068 {
1069 auto const createdSleClaimID = std::make_shared<SLE>(claimIDKeylet);
1070 (*createdSleClaimID)[sfAccount] = doorAccount;
1071 (*createdSleClaimID)[sfXChainBridge] = bridgeSpec;
1072 (*createdSleClaimID)[sfXChainAccountCreateCount] = attBegin->createCount;
1073 createdSleClaimID->setFieldArray(sfXChainCreateAccountAttestations, curAtts.toSTArray());
1074
1075 // Add to owner directory of the door account
1076 auto const page = psb.dirInsert(keylet::ownerDir(doorAccount), claimIDKeylet, describeOwnerDir(doorAccount));
1077 if (!page)
1078 return tecDIR_FULL; // LCOV_EXCL_LINE
1079 (*createdSleClaimID)[sfOwnerNode] = *page;
1080
1081 auto const sleDoor = psb.peek(doorK);
1082 if (!sleDoor)
1083 return tecINTERNAL; // LCOV_EXCL_LINE
1084
1085 // Reserve was already checked
1086 adjustOwnerCount(psb, sleDoor, 1, j);
1087 psb.insert(createdSleClaimID);
1088 psb.update(sleDoor);
1089 }
1090
1091 psb.apply(rawView);
1092
1093 return tesSUCCESS;
1094}
1095
1096template <class TAttestation>
1098toClaim(STTx const& tx)
1099{
1100 static_assert(
1103
1104 try
1105 {
1106 STObject o{tx};
1107 o.setAccountID(sfAccount, o[sfOtherChainSource]);
1108 return TAttestation(o);
1109 }
1110 catch (...)
1111 {
1112 }
1113 return std::nullopt;
1114}
1115
1116template <class TAttestation>
1117NotTEC
1118attestationPreflight(PreflightContext const& ctx)
1119{
1120 if (!publicKeyType(ctx.tx[sfPublicKey]))
1121 return temMALFORMED;
1122
1123 auto const att = toClaim<TAttestation>(ctx.tx);
1124 if (!att)
1125 return temMALFORMED;
1126
1127 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1128 if (!att->verify(bridgeSpec))
1129 return temXCHAIN_BAD_PROOF;
1130 if (!att->validAmounts())
1131 return temXCHAIN_BAD_PROOF;
1132
1133 if (att->sendingAmount.signum() <= 0)
1134 return temXCHAIN_BAD_PROOF;
1135 auto const expectedIssue = bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1136 if (att->sendingAmount.issue() != expectedIssue)
1137 return temXCHAIN_BAD_PROOF;
1138
1139 return tesSUCCESS;
1140}
1141
1142template <class TAttestation>
1143TER
1144attestationPreclaim(PreclaimContext const& ctx)
1145{
1146 auto const att = toClaim<TAttestation>(ctx.tx);
1147 // checked in preflight
1148 if (!att)
1149 return tecINTERNAL; // LCOV_EXCL_LINE
1150
1151 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1152 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1153 if (!sleBridge)
1154 {
1155 return tecNO_ENTRY;
1156 }
1157
1158 AccountID const attestationSignerAccount{ctx.tx[sfAttestationSignerAccount]};
1159 PublicKey const pk{ctx.tx[sfPublicKey]};
1160
1161 // signersList is a map from account id to weights
1162 auto const [signersList, quorum, slTer] = getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1163
1164 if (!isTesSuccess(slTer))
1165 return slTer;
1166
1167 return checkAttestationPublicKey(ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1168}
1169
1170template <class TAttestation>
1171TER
1172attestationDoApply(ApplyContext& ctx)
1173{
1174 auto const att = toClaim<TAttestation>(ctx.tx);
1175 if (!att)
1176 // Should already be checked in preflight
1177 return tecINTERNAL; // LCOV_EXCL_LINE
1178
1179 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1180
1181 struct ScopeResult
1182 {
1183 STXChainBridge::ChainType srcChain;
1185 std::uint32_t quorum;
1186 AccountID thisDoor;
1187 Keylet bridgeK;
1188 };
1189
1190 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1191 // This lambda is ugly - admittedly. The purpose of this lambda is to
1192 // limit the scope of sles so they don't overlap with
1193 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1194 // views, it's important that the sle's lifetime doesn't overlap.
1195 auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1196 if (!sleBridge)
1197 {
1198 return Unexpected(tecNO_ENTRY);
1199 }
1200 Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1201 AccountID const thisDoor = (*sleBridge)[sfAccount];
1202
1204 {
1205 if (thisDoor == bridgeSpec.lockingChainDoor())
1207 else if (thisDoor == bridgeSpec.issuingChainDoor())
1209 else
1210 return Unexpected(tecINTERNAL);
1211 }
1212 STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain);
1213
1214 // signersList is a map from account id to weights
1215 auto [signersList, quorum, slTer] = getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1216
1217 if (!isTesSuccess(slTer))
1218 return Unexpected(slTer);
1219
1220 return ScopeResult{srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1221 }();
1222
1223 if (!scopeResult.has_value())
1224 return scopeResult.error();
1225
1226 auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] = scopeResult.value();
1227
1228 static_assert(
1231
1233 {
1234 return applyClaimAttestations(
1235 ctx.view(), ctx.rawView(), &*att, &*att + 1, bridgeSpec, srcChain, signersList, quorum, ctx.journal);
1236 }
1238 {
1239 return applyCreateAccountAttestations(
1240 ctx.view(),
1241 ctx.rawView(),
1242 &*att,
1243 &*att + 1,
1244 thisDoor,
1245 keylet::account(thisDoor),
1246 bridgeSpec,
1247 bridgeK,
1248 srcChain,
1249 signersList,
1250 quorum,
1251 ctx.journal);
1252 }
1253}
1254
1255} // namespace
1256//------------------------------------------------------------------------------
1257
1258NotTEC
1260{
1261 auto const account = ctx.tx[sfAccount];
1262 auto const reward = ctx.tx[sfSignatureReward];
1263 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1264 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1265 // Doors must be distinct to help prevent transaction replay attacks
1266 if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1267 {
1269 }
1270
1271 if (bridgeSpec.lockingChainDoor() != account && bridgeSpec.issuingChainDoor() != account)
1272 {
1274 }
1275
1276 if (isXRP(bridgeSpec.lockingChainIssue()) != isXRP(bridgeSpec.issuingChainIssue()))
1277 {
1278 // Because ious and xrp have different numeric ranges, both the src and
1279 // dst issues must be both XRP or both IOU.
1281 }
1282
1283 if (!isXRP(reward) || reward.signum() < 0)
1284 {
1286 }
1287
1288 if (minAccountCreate &&
1289 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) || !isXRP(bridgeSpec.lockingChainIssue()) ||
1290 !isXRP(bridgeSpec.issuingChainIssue())))
1291 {
1293 }
1294
1295 if (isXRP(bridgeSpec.issuingChainIssue()))
1296 {
1297 // Issuing account must be the root account for XRP (which presumably
1298 // owns all the XRP). This is done so the issuing account can't "run
1299 // out" of wrapped tokens.
1300 static auto const rootAccount =
1301 calcAccountID(generateKeyPair(KeyType::secp256k1, generateSeed("masterpassphrase")).first);
1302 if (bridgeSpec.issuingChainDoor() != rootAccount)
1303 {
1305 }
1306 }
1307 else
1308 {
1309 // Issuing account must be the issuer for non-XRP. This is done so the
1310 // issuing account can't "run out" of wrapped tokens.
1311 if (bridgeSpec.issuingChainDoor() != bridgeSpec.issuingChainIssue().account)
1312 {
1314 }
1315 }
1316
1317 if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1318 {
1319 // If the locking chain door is locking their own asset, in some sense
1320 // nothing is being locked. Disallow this.
1322 }
1323
1324 return tesSUCCESS;
1325}
1326
1327TER
1329{
1330 auto const account = ctx.tx[sfAccount];
1331 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1332 STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1333
1334 {
1335 auto hasBridge = [&](STXChainBridge::ChainType ct) -> bool {
1336 return ctx.view.exists(keylet::bridge(bridgeSpec, ct));
1337 };
1338
1340 {
1341 return tecDUPLICATE;
1342 }
1343 }
1344
1345 if (!isXRP(bridgeSpec.issue(chainType)))
1346 {
1347 auto const sleIssuer = ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1348
1349 if (!sleIssuer)
1350 return tecNO_ISSUER;
1351
1352 // Allowing clawing back funds would break the bridge's invariant that
1353 // wrapped funds are always backed by locked funds
1354 if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1355 return tecNO_PERMISSION;
1356 }
1357
1358 {
1359 // Check reserve
1360 auto const sleAcc = ctx.view.read(keylet::account(account));
1361 if (!sleAcc)
1362 return terNO_ACCOUNT;
1363
1364 auto const balance = (*sleAcc)[sfBalance];
1365 auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1366
1367 if (balance < reserve)
1369 }
1370
1371 return tesSUCCESS;
1372}
1373
1374TER
1376{
1377 auto const account = ctx_.tx[sfAccount];
1378 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1379 auto const reward = ctx_.tx[sfSignatureReward];
1380 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1381
1382 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1383 if (!sleAcct)
1384 return tecINTERNAL; // LCOV_EXCL_LINE
1385
1386 STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1387
1388 Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1389 auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1390
1391 (*sleBridge)[sfAccount] = account;
1392 (*sleBridge)[sfSignatureReward] = reward;
1393 if (minAccountCreate)
1394 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1395 (*sleBridge)[sfXChainBridge] = bridgeSpec;
1396 (*sleBridge)[sfXChainClaimID] = 0;
1397 (*sleBridge)[sfXChainAccountCreateCount] = 0;
1398 (*sleBridge)[sfXChainAccountClaimCount] = 0;
1399
1400 // Add to owner directory
1401 {
1402 auto const page = ctx_.view().dirInsert(keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1403 if (!page)
1404 return tecDIR_FULL; // LCOV_EXCL_LINE
1405 (*sleBridge)[sfOwnerNode] = *page;
1406 }
1407
1408 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1409
1410 ctx_.view().insert(sleBridge);
1411 ctx_.view().update(sleAcct);
1412
1413 return tesSUCCESS;
1414}
1415
1416//------------------------------------------------------------------------------
1417
1423
1424NotTEC
1426{
1427 auto const account = ctx.tx[sfAccount];
1428 auto const reward = ctx.tx[~sfSignatureReward];
1429 auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1430 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1431 bool const clearAccountCreate = ctx.tx.getFlags() & tfClearAccountCreateAmount;
1432
1433 if (!reward && !minAccountCreate && !clearAccountCreate)
1434 {
1435 // Must change something
1436 return temMALFORMED;
1437 }
1438
1439 if (minAccountCreate && clearAccountCreate)
1440 {
1441 // Can't both clear and set account create in the same txn
1442 return temMALFORMED;
1443 }
1444
1445 if (bridgeSpec.lockingChainDoor() != account && bridgeSpec.issuingChainDoor() != account)
1446 {
1448 }
1449
1450 if (reward && (!isXRP(*reward) || reward->signum() < 0))
1451 {
1453 }
1454
1455 if (minAccountCreate &&
1456 ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) || !isXRP(bridgeSpec.lockingChainIssue()) ||
1457 !isXRP(bridgeSpec.issuingChainIssue())))
1458 {
1460 }
1461
1462 return tesSUCCESS;
1463}
1464
1465TER
1467{
1468 auto const account = ctx.tx[sfAccount];
1469 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1470
1471 STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1472
1473 if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1474 {
1475 return tecNO_ENTRY;
1476 }
1477
1478 return tesSUCCESS;
1479}
1480
1481TER
1483{
1484 auto const account = ctx_.tx[sfAccount];
1485 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1486 auto const reward = ctx_.tx[~sfSignatureReward];
1487 auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1488 bool const clearAccountCreate = ctx_.tx.getFlags() & tfClearAccountCreateAmount;
1489
1490 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1491 if (!sleAcct)
1492 return tecINTERNAL; // LCOV_EXCL_LINE
1493
1494 STXChainBridge::ChainType const chainType = STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1495
1496 auto const sleBridge = ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1497 if (!sleBridge)
1498 return tecINTERNAL; // LCOV_EXCL_LINE
1499
1500 if (reward)
1501 (*sleBridge)[sfSignatureReward] = *reward;
1502 if (minAccountCreate)
1503 {
1504 (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1505 }
1506 if (clearAccountCreate && sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1507 {
1508 sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1509 }
1510 ctx_.view().update(sleBridge);
1511
1512 return tesSUCCESS;
1513}
1514
1515//------------------------------------------------------------------------------
1516
1517NotTEC
1519{
1520 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1521 auto const amount = ctx.tx[sfAmount];
1522
1523 if (amount.signum() <= 0 ||
1524 (amount.issue() != bridgeSpec.lockingChainIssue() && amount.issue() != bridgeSpec.issuingChainIssue()))
1525 {
1526 return temBAD_AMOUNT;
1527 }
1528
1529 return tesSUCCESS;
1530}
1531
1532TER
1534{
1535 AccountID const account = ctx.tx[sfAccount];
1536 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1537 STAmount const& thisChainAmount = ctx.tx[sfAmount];
1538 auto const claimID = ctx.tx[sfXChainClaimID];
1539
1540 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1541 if (!sleBridge)
1542 {
1543 return tecNO_ENTRY;
1544 }
1545
1546 if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1547 {
1548 return tecNO_DST;
1549 }
1550
1551 auto const thisDoor = (*sleBridge)[sfAccount];
1552 bool isLockingChain = false;
1553 {
1554 if (thisDoor == bridgeSpec.lockingChainDoor())
1555 isLockingChain = true;
1556 else if (thisDoor == bridgeSpec.issuingChainDoor())
1557 isLockingChain = false;
1558 else
1559 return tecINTERNAL; // LCOV_EXCL_LINE
1560 }
1561
1562 {
1563 // Check that the amount specified matches the expected issue
1564
1565 if (isLockingChain)
1566 {
1567 if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1569 }
1570 else
1571 {
1572 if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1574 }
1575 }
1576
1577 if (isXRP(bridgeSpec.lockingChainIssue()) != isXRP(bridgeSpec.issuingChainIssue()))
1578 {
1579 // Should have been caught when creating the bridge
1580 // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1581 // and the numeric issues that need to be addressed with that.
1582 return tecINTERNAL; // LCOV_EXCL_LINE
1583 }
1584
1585 auto const otherChainAmount = [&]() -> STAmount {
1586 STAmount r(thisChainAmount);
1587 if (isLockingChain)
1588 r.setIssue(bridgeSpec.issuingChainIssue());
1589 else
1590 r.setIssue(bridgeSpec.lockingChainIssue());
1591 return r;
1592 }();
1593
1594 auto const sleClaimID = ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1595 {
1596 // Check that the sequence number is owned by the sender of this
1597 // transaction
1598 if (!sleClaimID)
1599 {
1600 return tecXCHAIN_NO_CLAIM_ID;
1601 }
1602
1603 if ((*sleClaimID)[sfAccount] != account)
1604 {
1605 // Sequence number isn't owned by the sender of this transaction
1607 }
1608 }
1609
1610 // quorum is checked in `doApply`
1611 return tesSUCCESS;
1612}
1613
1614TER
1616{
1617 PaymentSandbox psb(&ctx_.view());
1618
1619 AccountID const account = ctx_.tx[sfAccount];
1620 auto const dst = ctx_.tx[sfDestination];
1621 STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1622 STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1623 auto const claimID = ctx_.tx[sfXChainClaimID];
1624 auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1625
1626 struct ScopeResult
1627 {
1628 std::vector<AccountID> rewardAccounts;
1629 AccountID rewardPoolSrc;
1630 STAmount sendingAmount;
1632 STAmount signatureReward;
1633 };
1634
1635 auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1636 // This lambda is ugly - admittedly. The purpose of this lambda is to
1637 // limit the scope of sles so they don't overlap with
1638 // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1639 // views, it's important that the sle's lifetime doesn't overlap.
1640
1641 auto const sleAcct = psb.peek(keylet::account(account));
1642 auto const sleBridge = peekBridge(psb, bridgeSpec);
1643 auto const sleClaimID = psb.peek(claimIDKeylet);
1644
1645 if (!(sleBridge && sleClaimID && sleAcct))
1646 return Unexpected(tecINTERNAL);
1647
1648 AccountID const thisDoor = (*sleBridge)[sfAccount];
1649
1651 {
1652 if (thisDoor == bridgeSpec.lockingChainDoor())
1654 else if (thisDoor == bridgeSpec.issuingChainDoor())
1656 else
1657 return Unexpected(tecINTERNAL);
1658 }
1659 STXChainBridge::ChainType const srcChain = STXChainBridge::otherChain(dstChain);
1660
1661 auto const sendingAmount = [&]() -> STAmount {
1662 STAmount r(thisChainAmount);
1663 r.setIssue(bridgeSpec.issue(srcChain));
1664 return r;
1665 }();
1666
1667 auto const [signersList, quorum, slTer] = getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1668
1669 if (!isTesSuccess(slTer))
1670 return Unexpected(slTer);
1671
1672 XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1673
1674 auto const claimR = onClaim(
1675 curAtts,
1676 psb,
1677 sendingAmount,
1678 /*wasLockingChainSend*/ srcChain == STXChainBridge::ChainType::locking,
1679 quorum,
1680 signersList,
1681 ctx_.journal);
1682 if (!claimR.has_value())
1683 return Unexpected(claimR.error());
1684
1685 return ScopeResult{
1686 claimR.value(),
1687 (*sleClaimID)[sfAccount],
1688 sendingAmount,
1689 srcChain,
1690 (*sleClaimID)[sfSignatureReward],
1691 };
1692 }();
1693
1694 if (!scopeResult.has_value())
1695 return scopeResult.error();
1696
1697 auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] = scopeResult.value();
1698 std::optional<std::uint32_t> const dstTag = ctx_.tx[~sfDestinationTag];
1699
1700 auto const r = finalizeClaimHelper(
1701 psb,
1702 bridgeSpec,
1703 dst,
1704 dstTag,
1705 /*claimOwner*/ account,
1706 sendingAmount,
1707 rewardPoolSrc,
1708 signatureReward,
1709 rewardAccounts,
1710 srcChain,
1711 claimIDKeylet,
1712 OnTransferFail::keepClaim,
1713 DepositAuthPolicy::dstCanBypass,
1714 ctx_.journal);
1715 if (!r.isTesSuccess())
1716 return r.ter();
1717
1718 psb.apply(ctx_.rawView());
1719
1720 return tesSUCCESS;
1721}
1722
1723//------------------------------------------------------------------------------
1724
1727{
1728 auto const maxSpend = [&] {
1729 auto const amount = ctx.tx[sfAmount];
1730 if (amount.native() && amount.signum() > 0)
1731 return amount.xrp();
1732 return XRPAmount{beast::zero};
1733 }();
1734
1735 return TxConsequences{ctx.tx, maxSpend};
1736}
1737
1738NotTEC
1740{
1741 auto const amount = ctx.tx[sfAmount];
1742 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1743
1744 if (amount.signum() <= 0 || !isLegalNet(amount))
1745 return temBAD_AMOUNT;
1746
1747 if (amount.issue() != bridgeSpec.lockingChainIssue() && amount.issue() != bridgeSpec.issuingChainIssue())
1748 return temBAD_ISSUER;
1749
1750 return tesSUCCESS;
1751}
1752
1753TER
1755{
1756 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1757 auto const amount = ctx.tx[sfAmount];
1758
1759 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1760 if (!sleBridge)
1761 {
1762 return tecNO_ENTRY;
1763 }
1764
1765 AccountID const thisDoor = (*sleBridge)[sfAccount];
1766 AccountID const account = ctx.tx[sfAccount];
1767
1768 if (thisDoor == account)
1769 {
1770 // Door account can't lock funds onto itself
1771 return tecXCHAIN_SELF_COMMIT;
1772 }
1773
1774 bool isLockingChain = false;
1775 {
1776 if (thisDoor == bridgeSpec.lockingChainDoor())
1777 isLockingChain = true;
1778 else if (thisDoor == bridgeSpec.issuingChainDoor())
1779 isLockingChain = false;
1780 else
1781 return tecINTERNAL; // LCOV_EXCL_LINE
1782 }
1783
1784 if (isLockingChain)
1785 {
1786 if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1788 }
1789 else
1790 {
1791 if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1793 }
1794
1795 return tesSUCCESS;
1796}
1797
1798TER
1800{
1801 PaymentSandbox psb(&ctx_.view());
1802
1803 auto const account = ctx_.tx[sfAccount];
1804 auto const amount = ctx_.tx[sfAmount];
1805 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1806
1807 if (!psb.read(keylet::account(account)))
1808 return tecINTERNAL; // LCOV_EXCL_LINE
1809
1810 auto const sleBridge = readBridge(psb, bridgeSpec);
1811 if (!sleBridge)
1812 return tecINTERNAL; // LCOV_EXCL_LINE
1813
1814 auto const dst = (*sleBridge)[sfAccount];
1815
1816 // Support dipping into reserves to pay the fee
1817 TransferHelperSubmittingAccountInfo submittingAccountInfo{account_, mPriorBalance, mSourceBalance};
1818
1819 auto const thTer = transferHelper(
1820 psb,
1821 account,
1822 dst,
1823 /*dstTag*/ std::nullopt,
1824 /*claimOwner*/ std::nullopt,
1825 amount,
1826 CanCreateDstPolicy::no,
1827 DepositAuthPolicy::normal,
1828 submittingAccountInfo,
1829 ctx_.journal);
1830
1831 if (!isTesSuccess(thTer))
1832 return thTer;
1833
1834 psb.apply(ctx_.rawView());
1835
1836 return tesSUCCESS;
1837}
1838
1839//------------------------------------------------------------------------------
1840
1841NotTEC
1843{
1844 auto const reward = ctx.tx[sfSignatureReward];
1845
1846 if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
1848
1849 return tesSUCCESS;
1850}
1851
1852TER
1854{
1855 auto const account = ctx.tx[sfAccount];
1856 auto const bridgeSpec = ctx.tx[sfXChainBridge];
1857 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1858
1859 if (!sleBridge)
1860 {
1861 return tecNO_ENTRY;
1862 }
1863
1864 // Check that the reward matches
1865 auto const reward = ctx.tx[sfSignatureReward];
1866
1867 if (reward != (*sleBridge)[sfSignatureReward])
1868 {
1870 }
1871
1872 {
1873 // Check reserve
1874 auto const sleAcc = ctx.view.read(keylet::account(account));
1875 if (!sleAcc)
1876 return terNO_ACCOUNT;
1877
1878 auto const balance = (*sleAcc)[sfBalance];
1879 auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1880
1881 if (balance < reserve)
1883 }
1884
1885 return tesSUCCESS;
1886}
1887
1888TER
1890{
1891 auto const account = ctx_.tx[sfAccount];
1892 auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1893 auto const reward = ctx_.tx[sfSignatureReward];
1894 auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
1895
1896 auto const sleAcct = ctx_.view().peek(keylet::account(account));
1897 if (!sleAcct)
1898 return tecINTERNAL; // LCOV_EXCL_LINE
1899
1900 auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
1901 if (!sleBridge)
1902 return tecINTERNAL; // LCOV_EXCL_LINE
1903
1904 std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
1905 if (claimID == 0)
1906 {
1907 // overflow
1908 return tecINTERNAL; // LCOV_EXCL_LINE
1909 }
1910
1911 (*sleBridge)[sfXChainClaimID] = claimID;
1912
1913 Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1914 if (ctx_.view().exists(claimIDKeylet))
1915 {
1916 // already checked out!?!
1917 return tecINTERNAL; // LCOV_EXCL_LINE
1918 }
1919
1920 auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
1921
1922 (*sleClaimID)[sfAccount] = account;
1923 (*sleClaimID)[sfXChainBridge] = bridgeSpec;
1924 (*sleClaimID)[sfXChainClaimID] = claimID;
1925 (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
1926 (*sleClaimID)[sfSignatureReward] = reward;
1927 sleClaimID->setFieldArray(sfXChainClaimAttestations, STArray{sfXChainClaimAttestations});
1928
1929 // Add to owner directory
1930 {
1931 auto const page = ctx_.view().dirInsert(keylet::ownerDir(account), claimIDKeylet, describeOwnerDir(account));
1932 if (!page)
1933 return tecDIR_FULL; // LCOV_EXCL_LINE
1934 (*sleClaimID)[sfOwnerNode] = *page;
1935 }
1936
1937 adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1938
1939 ctx_.view().insert(sleClaimID);
1940 ctx_.view().update(sleBridge);
1941 ctx_.view().update(sleAcct);
1942
1943 return tesSUCCESS;
1944}
1945
1946//------------------------------------------------------------------------------
1947
1948NotTEC
1950{
1951 return attestationPreflight<Attestations::AttestationClaim>(ctx);
1952}
1953
1954TER
1956{
1957 return attestationPreclaim<Attestations::AttestationClaim>(ctx);
1958}
1959
1960TER
1962{
1963 return attestationDoApply<Attestations::AttestationClaim>(ctx_);
1964}
1965
1966//------------------------------------------------------------------------------
1967
1968NotTEC
1970{
1971 return attestationPreflight<Attestations::AttestationCreateAccount>(ctx);
1972}
1973
1974TER
1976{
1977 return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
1978}
1979
1980TER
1982{
1983 return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
1984}
1985
1986//------------------------------------------------------------------------------
1987
1988NotTEC
1990{
1991 auto const amount = ctx.tx[sfAmount];
1992
1993 if (amount.signum() <= 0 || !amount.native())
1994 return temBAD_AMOUNT;
1995
1996 auto const reward = ctx.tx[sfSignatureReward];
1997 if (reward.signum() < 0 || !reward.native())
1998 return temBAD_AMOUNT;
1999
2000 if (reward.issue() != amount.issue())
2001 return temBAD_AMOUNT;
2002
2003 return tesSUCCESS;
2004}
2005
2006TER
2008{
2009 STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2010 STAmount const amount = ctx.tx[sfAmount];
2011 STAmount const reward = ctx.tx[sfSignatureReward];
2012
2013 auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2014 if (!sleBridge)
2015 {
2016 return tecNO_ENTRY;
2017 }
2018
2019 if (reward != (*sleBridge)[sfSignatureReward])
2020 {
2022 }
2023
2024 std::optional<STAmount> const minCreateAmount = (*sleBridge)[~sfMinAccountCreateAmount];
2025
2026 if (!minCreateAmount)
2028
2029 if (amount < *minCreateAmount)
2031
2032 if (minCreateAmount->issue() != amount.issue())
2034
2035 AccountID const thisDoor = (*sleBridge)[sfAccount];
2036 AccountID const account = ctx.tx[sfAccount];
2037 if (thisDoor == account)
2038 {
2039 // Door account can't lock funds onto itself
2040 return tecXCHAIN_SELF_COMMIT;
2041 }
2042
2044 {
2045 if (thisDoor == bridgeSpec.lockingChainDoor())
2047 else if (thisDoor == bridgeSpec.issuingChainDoor())
2049 else
2050 return tecINTERNAL; // LCOV_EXCL_LINE
2051 }
2052 STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain);
2053
2054 if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2056
2057 if (!isXRP(bridgeSpec.issue(dstChain)))
2059
2060 return tesSUCCESS;
2061}
2062
2063TER
2065{
2066 PaymentSandbox psb(&ctx_.view());
2067
2068 AccountID const account = ctx_.tx[sfAccount];
2069 STAmount const amount = ctx_.tx[sfAmount];
2070 STAmount const reward = ctx_.tx[sfSignatureReward];
2071 STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2072
2073 auto const sle = psb.peek(keylet::account(account));
2074 if (!sle)
2075 return tecINTERNAL; // LCOV_EXCL_LINE
2076
2077 auto const sleBridge = peekBridge(psb, bridge);
2078 if (!sleBridge)
2079 return tecINTERNAL; // LCOV_EXCL_LINE
2080
2081 auto const dst = (*sleBridge)[sfAccount];
2082
2083 // Support dipping into reserves to pay the fee
2084 TransferHelperSubmittingAccountInfo submittingAccountInfo{account_, mPriorBalance, mSourceBalance};
2085 STAmount const toTransfer = amount + reward;
2086 auto const thTer = transferHelper(
2087 psb,
2088 account,
2089 dst,
2090 /*dstTag*/ std::nullopt,
2091 /*claimOwner*/ std::nullopt,
2092 toTransfer,
2093 CanCreateDstPolicy::yes,
2094 DepositAuthPolicy::normal,
2095 submittingAccountInfo,
2096 ctx_.journal);
2097
2098 if (!isTesSuccess(thTer))
2099 return thTer;
2100
2101 (*sleBridge)[sfXChainAccountCreateCount] = (*sleBridge)[sfXChainAccountCreateCount] + 1;
2102 psb.update(sleBridge);
2103
2104 psb.apply(ctx_.rawView());
2105
2106 return tesSUCCESS;
2107}
2108
2109} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:41
Stream fatal() const
Definition Journal.h:325
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
STTx const & tx
beast::Journal const journal
RawView & rawView()
ApplyView & view()
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:284
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
TER doApply() override
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static rounding_mode getround()
Definition Number.cpp:33
static rounding_mode setround(rounding_mode mode)
Definition Number.cpp:39
A wrapper which makes credits unavailable to balances.
void apply(RawView &to)
Apply changes to base view.
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 std::shared_ptr< SLE const > 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:419
Issue const & issue() const
Definition STAmount.h:455
std::uint32_t getFlags() const
Definition STObject.cpp:492
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 Expected< std::vector< SignerEntry >, NotTEC > deserialize(STObject const &obj, beast::Journal journal, std::string_view annotation)
AccountID const account_
Definition Transactor.h:113
XRPAmount mSourceBalance
Definition Transactor.h:115
XRPAmount mPriorBalance
Definition Transactor.h:114
ApplyContext & ctx_
Definition Transactor.h:109
Class describing the consequences to the account of applying a transaction if the transaction consume...
Definition applySteps.h:38
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
TER doApply() override
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static NotTEC preflight(PreflightContext const &ctx)
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
T contains(T... args)
T distance(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T is_same_v
T max(T... args)
void check(bool condition, std::string const &message)
Keylet signers(AccountID const &account) noexcept
A SignerList.
Definition Indexes.cpp:287
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:299
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition Indexes.cpp:412
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:325
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:436
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:331
Keylet xChainClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition Indexes.cpp:422
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:6
STAmount divide(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:69
@ terNO_ACCOUNT
Definition TER.h:198
bool isTerRetry(TER x) noexcept
Definition TER.h:644
Unexpected(E(&)[N]) -> Unexpected< E const * >
bool isXRP(AccountID const &c)
Definition AccountID.h:71
@ tefBAD_LEDGER
Definition TER.h:151
bool isLegalNet(STAmount const &value)
Definition STAmount.h:567
constexpr std::uint32_t tfBridgeModifyMask
Definition TxFlags.h:248
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:57
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
constexpr std::uint32_t tfClearAccountCreateAmount
Definition TxFlags.h:247
TERSubset< CanCvtToTER > TER
Definition TER.h:621
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:941
bool isTefFailure(TER x) noexcept
Definition TER.h:638
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:82
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:955
AccountID calcAccountID(PublicKey const &pk)
@ temBAD_ISSUER
Definition TER.h:74
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition TER.h:116
@ temMALFORMED
Definition TER.h:68
@ temXCHAIN_BRIDGE_NONDOOR_OWNER
Definition TER.h:115
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition TER.h:114
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition TER.h:117
@ temBAD_AMOUNT
Definition TER.h:70
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition TER.h:112
@ temXCHAIN_BAD_PROOF
Definition TER.h:113
bool isTesSuccess(TER x) noexcept
Definition TER.h:650
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition TER.h:328
@ tecDIR_FULL
Definition TER.h:269
@ tecUNFUNDED_PAYMENT
Definition TER.h:267
@ tecNO_ENTRY
Definition TER.h:288
@ tecXCHAIN_NO_SIGNERS_LIST
Definition TER.h:326
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition TER.h:327
@ tecXCHAIN_BAD_TRANSFER_ISSUE
Definition TER.h:318
@ tecNO_DST_INSUF_XRP
Definition TER.h:273
@ tecXCHAIN_WRONG_CHAIN
Definition TER.h:324
@ tecINTERNAL
Definition TER.h:292
@ tecXCHAIN_PROOF_UNKNOWN_KEY
Definition TER.h:322
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition TER.h:329
@ tecXCHAIN_PAYMENT_FAILED
Definition TER.h:331
@ tecXCHAIN_NO_CLAIM_ID
Definition TER.h:319
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition TER.h:330
@ tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE
Definition TER.h:323
@ tecXCHAIN_BAD_CLAIM_ID
Definition TER.h:320
@ tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR
Definition TER.h:333
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition TER.h:334
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecXCHAIN_SELF_COMMIT
Definition TER.h:332
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition TER.h:321
@ tecXCHAIN_REWARD_MISMATCH
Definition TER.h:325
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecNO_ISSUER
Definition TER.h:281
@ tecDUPLICATE
Definition TER.h:297
@ tecNO_DST
Definition TER.h:272
bool isTecClaim(TER x) noexcept
Definition TER.h:657
@ lsfAllowTrustLineClawback
@ lsfDepositAuth
@ lsfRequireDestTag
@ lsfDisableMaster
@ no
Definition Steps.h:26
@ yes
Definition Steps.h:26
constexpr size_t xbridgeMaxAccountCreateClaims
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:581
@ tesSUCCESS
Definition TER.h:226
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:20
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:54
ReadView const & view
Definition Transactor.h:57
State information when preflighting a tx.
Definition Transactor.h:16