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