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