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*/ false,
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  auto const att = toClaim<TAttestation>(ctx.tx);
1215  if (!att)
1216  return temMALFORMED;
1217 
1218  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1219  if (!att->verify(bridgeSpec))
1220  return temXCHAIN_BAD_PROOF;
1221  if (!att->validAmounts())
1222  return temXCHAIN_BAD_PROOF;
1223 
1224  if (att->sendingAmount.signum() <= 0)
1225  return temXCHAIN_BAD_PROOF;
1226  auto const expectedIssue =
1227  bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend));
1228  if (att->sendingAmount.issue() != expectedIssue)
1229  return temXCHAIN_BAD_PROOF;
1230 
1231  return preflight2(ctx);
1232 }
1233 
1234 template <class TAttestation>
1235 TER
1236 attestationPreclaim(PreclaimContext const& ctx)
1237 {
1238  auto const att = toClaim<TAttestation>(ctx.tx);
1239  if (!att)
1240  return tecINTERNAL; // checked in preflight
1241 
1242  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1243  auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1244  if (!sleBridge)
1245  {
1246  return tecNO_ENTRY;
1247  }
1248 
1249  AccountID const attestationSignerAccount{
1250  ctx.tx[sfAttestationSignerAccount]};
1251  PublicKey const pk{ctx.tx[sfPublicKey]};
1252 
1253  // signersList is a map from account id to weights
1254  auto const [signersList, quorum, slTer] =
1255  getSignersListAndQuorum(ctx.view, *sleBridge, ctx.j);
1256 
1257  if (!isTesSuccess(slTer))
1258  return slTer;
1259 
1260  return checkAttestationPublicKey(
1261  ctx.view, signersList, attestationSignerAccount, pk, ctx.j);
1262 }
1263 
1264 template <class TAttestation>
1265 TER
1266 attestationDoApply(ApplyContext& ctx)
1267 {
1268  auto const att = toClaim<TAttestation>(ctx.tx);
1269  if (!att)
1270  // Should already be checked in preflight
1271  return tecINTERNAL;
1272 
1273  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1274 
1275  struct ScopeResult
1276  {
1277  STXChainBridge::ChainType srcChain;
1279  std::uint32_t quorum;
1280  AccountID thisDoor;
1281  Keylet bridgeK;
1282  };
1283 
1284  auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1285  // This lambda is ugly - admittedly. The purpose of this lambda is to
1286  // limit the scope of sles so they don't overlap with
1287  // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1288  // views, it's important that the sle's lifetime doesn't overlap.
1289  auto sleBridge = readBridge(ctx.view(), bridgeSpec);
1290  if (!sleBridge)
1291  {
1292  return Unexpected(tecNO_ENTRY);
1293  }
1294  Keylet const bridgeK{ltBRIDGE, sleBridge->key()};
1295  AccountID const thisDoor = (*sleBridge)[sfAccount];
1296 
1298  {
1299  if (thisDoor == bridgeSpec.lockingChainDoor())
1301  else if (thisDoor == bridgeSpec.issuingChainDoor())
1303  else
1304  return Unexpected(tecINTERNAL);
1305  }
1306  STXChainBridge::ChainType const srcChain =
1307  STXChainBridge::otherChain(dstChain);
1308 
1309  // signersList is a map from account id to weights
1310  auto [signersList, quorum, slTer] =
1311  getSignersListAndQuorum(ctx.view(), *sleBridge, ctx.journal);
1312 
1313  if (!isTesSuccess(slTer))
1314  return Unexpected(slTer);
1315 
1316  return ScopeResult{
1317  srcChain, std::move(signersList), quorum, thisDoor, bridgeK};
1318  }();
1319 
1320  if (!scopeResult.has_value())
1321  return scopeResult.error();
1322 
1323  auto const& [srcChain, signersList, quorum, thisDoor, bridgeK] =
1324  scopeResult.value();
1325 
1326  static_assert(
1327  std::is_same_v<TAttestation, Attestations::AttestationClaim> ||
1328  std::is_same_v<TAttestation, Attestations::AttestationCreateAccount>);
1329 
1330  if constexpr (std::is_same_v<TAttestation, Attestations::AttestationClaim>)
1331  {
1332  return applyClaimAttestations(
1333  ctx.view(),
1334  ctx.rawView(),
1335  &*att,
1336  &*att + 1,
1337  bridgeSpec,
1338  srcChain,
1339  signersList,
1340  quorum,
1341  ctx.journal);
1342  }
1343  else if constexpr (std::is_same_v<
1344  TAttestation,
1345  Attestations::AttestationCreateAccount>)
1346  {
1347  return applyCreateAccountAttestations(
1348  ctx.view(),
1349  ctx.rawView(),
1350  &*att,
1351  &*att + 1,
1352  thisDoor,
1353  keylet::account(thisDoor),
1354  bridgeSpec,
1355  bridgeK,
1356  srcChain,
1357  signersList,
1358  quorum,
1359  ctx.journal);
1360  }
1361 }
1362 
1363 } // namespace
1364 //------------------------------------------------------------------------------
1365 
1366 NotTEC
1368 {
1369  if (!ctx.rules.enabled(featureXChainBridge))
1370  return temDISABLED;
1371 
1372  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1373  return ret;
1374 
1375  if (ctx.tx.getFlags() & tfUniversalMask)
1376  return temINVALID_FLAG;
1377 
1378  auto const account = ctx.tx[sfAccount];
1379  auto const reward = ctx.tx[sfSignatureReward];
1380  auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1381  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1382  // Doors must be distinct to help prevent transaction replay attacks
1383  if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor())
1384  {
1386  }
1387 
1388  if (bridgeSpec.lockingChainDoor() != account &&
1389  bridgeSpec.issuingChainDoor() != account)
1390  {
1392  }
1393 
1394  if (isXRP(bridgeSpec.lockingChainIssue()) !=
1395  isXRP(bridgeSpec.issuingChainIssue()))
1396  {
1397  // Because ious and xrp have different numeric ranges, both the src and
1398  // dst issues must be both XRP or both IOU.
1400  }
1401 
1402  if (!isXRP(reward) || reward.signum() < 0)
1403  {
1405  }
1406 
1407  if (minAccountCreate &&
1408  ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1409  !isXRP(bridgeSpec.lockingChainIssue()) ||
1410  !isXRP(bridgeSpec.issuingChainIssue())))
1411  {
1413  }
1414 
1415  if (isXRP(bridgeSpec.issuingChainIssue()))
1416  {
1417  // Issuing account must be the root account for XRP (which presumably
1418  // owns all the XRP). This is done so the issuing account can't "run
1419  // out" of wrapped tokens.
1420  static auto const rootAccount = calcAccountID(
1422  KeyType::secp256k1, generateSeed("masterpassphrase"))
1423  .first);
1424  if (bridgeSpec.issuingChainDoor() != rootAccount)
1425  {
1427  }
1428  }
1429  else
1430  {
1431  // Issuing account must be the issuer for non-XRP. This is done so the
1432  // issuing account can't "run out" of wrapped tokens.
1433  if (bridgeSpec.issuingChainDoor() !=
1434  bridgeSpec.issuingChainIssue().account)
1435  {
1437  }
1438  }
1439 
1440  if (bridgeSpec.lockingChainDoor() == bridgeSpec.lockingChainIssue().account)
1441  {
1442  // If the locking chain door is locking their own asset, in some sense
1443  // nothing is being locked. Disallow this.
1445  }
1446 
1447  return preflight2(ctx);
1448 }
1449 
1450 TER
1452 {
1453  auto const account = ctx.tx[sfAccount];
1454  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1455 
1456  STXChainBridge::ChainType const chainType =
1457  STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1458 
1459  if (ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1460  {
1461  return tecDUPLICATE;
1462  }
1463 
1464  if (!isXRP(bridgeSpec.issue(chainType)))
1465  {
1466  auto const sleIssuer =
1467  ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
1468 
1469  if (!sleIssuer)
1470  return tecNO_ISSUER;
1471 
1472  // Allowing clawing back funds would break the bridge's invariant that
1473  // wrapped funds are always backed by locked funds
1474  if (sleIssuer->getFlags() & lsfAllowTrustLineClawback)
1475  return tecNO_PERMISSION;
1476  }
1477 
1478  {
1479  // Check reserve
1480  auto const sleAcc = ctx.view.read(keylet::account(account));
1481  if (!sleAcc)
1482  return terNO_ACCOUNT;
1483 
1484  auto const balance = (*sleAcc)[sfBalance];
1485  auto const reserve =
1486  ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
1487 
1488  if (balance < reserve)
1489  return tecINSUFFICIENT_RESERVE;
1490  }
1491 
1492  return tesSUCCESS;
1493 }
1494 
1495 TER
1497 {
1498  auto const account = ctx_.tx[sfAccount];
1499  auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1500  auto const reward = ctx_.tx[sfSignatureReward];
1501  auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1502 
1503  auto const sleAcct = ctx_.view().peek(keylet::account(account));
1504  if (!sleAcct)
1505  return tecINTERNAL;
1506 
1507  STXChainBridge::ChainType const chainType =
1508  STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1509 
1510  Keylet const bridgeKeylet = keylet::bridge(bridgeSpec, chainType);
1511  auto const sleBridge = std::make_shared<SLE>(bridgeKeylet);
1512 
1513  (*sleBridge)[sfAccount] = account;
1514  (*sleBridge)[sfSignatureReward] = reward;
1515  if (minAccountCreate)
1516  (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1517  (*sleBridge)[sfXChainBridge] = bridgeSpec;
1518  (*sleBridge)[sfXChainClaimID] = 0;
1519  (*sleBridge)[sfXChainAccountCreateCount] = 0;
1520  (*sleBridge)[sfXChainAccountClaimCount] = 0;
1521 
1522  // Add to owner directory
1523  {
1524  auto const page = ctx_.view().dirInsert(
1525  keylet::ownerDir(account), bridgeKeylet, describeOwnerDir(account));
1526  if (!page)
1527  return tecDIR_FULL;
1528  (*sleBridge)[sfOwnerNode] = *page;
1529  }
1530 
1531  adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
1532 
1533  ctx_.view().insert(sleBridge);
1534  ctx_.view().update(sleAcct);
1535 
1536  return tesSUCCESS;
1537 }
1538 
1539 //------------------------------------------------------------------------------
1540 
1541 NotTEC
1543 {
1544  if (!ctx.rules.enabled(featureXChainBridge))
1545  return temDISABLED;
1546 
1547  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1548  return ret;
1549 
1550  if (ctx.tx.getFlags() & tfBridgeModifyMask)
1551  return temINVALID_FLAG;
1552 
1553  auto const account = ctx.tx[sfAccount];
1554  auto const reward = ctx.tx[~sfSignatureReward];
1555  auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount];
1556  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1557  bool const clearAccountCreate =
1559 
1560  if (!reward && !minAccountCreate && !clearAccountCreate)
1561  {
1562  // Must change something
1563  return temMALFORMED;
1564  }
1565 
1566  if (minAccountCreate && clearAccountCreate)
1567  {
1568  // Can't both clear and set account create in the same txn
1569  return temMALFORMED;
1570  }
1571 
1572  if (bridgeSpec.lockingChainDoor() != account &&
1573  bridgeSpec.issuingChainDoor() != account)
1574  {
1576  }
1577 
1578  if (reward && (!isXRP(*reward) || reward->signum() < 0))
1579  {
1581  }
1582 
1583  if (minAccountCreate &&
1584  ((!isXRP(*minAccountCreate) || minAccountCreate->signum() <= 0) ||
1585  !isXRP(bridgeSpec.lockingChainIssue()) ||
1586  !isXRP(bridgeSpec.issuingChainIssue())))
1587  {
1589  }
1590 
1591  return preflight2(ctx);
1592 }
1593 
1594 TER
1596 {
1597  auto const account = ctx.tx[sfAccount];
1598  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1599 
1600  STXChainBridge::ChainType const chainType =
1601  STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1602 
1603  if (!ctx.view.read(keylet::bridge(bridgeSpec, chainType)))
1604  {
1605  return tecNO_ENTRY;
1606  }
1607 
1608  return tesSUCCESS;
1609 }
1610 
1611 TER
1613 {
1614  auto const account = ctx_.tx[sfAccount];
1615  auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1616  auto const reward = ctx_.tx[~sfSignatureReward];
1617  auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
1618  bool const clearAccountCreate =
1620 
1621  auto const sleAcct = ctx_.view().peek(keylet::account(account));
1622  if (!sleAcct)
1623  return tecINTERNAL;
1624 
1625  STXChainBridge::ChainType const chainType =
1626  STXChainBridge::srcChain(account == bridgeSpec.lockingChainDoor());
1627 
1628  auto const sleBridge =
1629  ctx_.view().peek(keylet::bridge(bridgeSpec, chainType));
1630  if (!sleBridge)
1631  return tecINTERNAL;
1632 
1633  if (reward)
1634  (*sleBridge)[sfSignatureReward] = *reward;
1635  if (minAccountCreate)
1636  {
1637  (*sleBridge)[sfMinAccountCreateAmount] = *minAccountCreate;
1638  }
1639  if (clearAccountCreate &&
1640  sleBridge->isFieldPresent(sfMinAccountCreateAmount))
1641  {
1642  sleBridge->makeFieldAbsent(sfMinAccountCreateAmount);
1643  }
1644  ctx_.view().update(sleBridge);
1645 
1646  return tesSUCCESS;
1647 }
1648 
1649 //------------------------------------------------------------------------------
1650 
1651 NotTEC
1653 {
1654  if (!ctx.rules.enabled(featureXChainBridge))
1655  return temDISABLED;
1656 
1657  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1658  return ret;
1659 
1660  if (ctx.tx.getFlags() & tfUniversalMask)
1661  return temINVALID_FLAG;
1662 
1663  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1664  auto const amount = ctx.tx[sfAmount];
1665 
1666  if (amount.signum() <= 0 ||
1667  (amount.issue() != bridgeSpec.lockingChainIssue() &&
1668  amount.issue() != bridgeSpec.issuingChainIssue()))
1669  {
1670  return temBAD_AMOUNT;
1671  }
1672 
1673  return preflight2(ctx);
1674 }
1675 
1676 TER
1678 {
1679  AccountID const account = ctx.tx[sfAccount];
1680  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
1681  STAmount const& thisChainAmount = ctx.tx[sfAmount];
1682  auto const claimID = ctx.tx[sfXChainClaimID];
1683 
1684  auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1685  if (!sleBridge)
1686  {
1687  return tecNO_ENTRY;
1688  }
1689 
1690  if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
1691  {
1692  return tecNO_DST;
1693  }
1694 
1695  auto const thisDoor = (*sleBridge)[sfAccount];
1696  bool isLockingChain = false;
1697  {
1698  if (thisDoor == bridgeSpec.lockingChainDoor())
1699  isLockingChain = true;
1700  else if (thisDoor == bridgeSpec.issuingChainDoor())
1701  isLockingChain = false;
1702  else
1703  return tecINTERNAL;
1704  }
1705 
1706  {
1707  // Check that the amount specified matches the expected issue
1708 
1709  if (isLockingChain)
1710  {
1711  if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue())
1713  }
1714  else
1715  {
1716  if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue())
1718  }
1719  }
1720 
1721  if (isXRP(bridgeSpec.lockingChainIssue()) !=
1722  isXRP(bridgeSpec.issuingChainIssue()))
1723  {
1724  // Should have been caught when creating the bridge
1725  // Detect here so `otherChainAmount` doesn't switch from IOU -> XRP
1726  // and the numeric issues that need to be addressed with that.
1727  return tecINTERNAL;
1728  }
1729 
1730  auto const otherChainAmount = [&]() -> STAmount {
1731  STAmount r(thisChainAmount);
1732  if (isLockingChain)
1733  r.setIssue(bridgeSpec.issuingChainIssue());
1734  else
1735  r.setIssue(bridgeSpec.lockingChainIssue());
1736  return r;
1737  }();
1738 
1739  auto const sleClaimID =
1740  ctx.view.read(keylet::xChainClaimID(bridgeSpec, claimID));
1741  {
1742  // Check that the sequence number is owned by the sender of this
1743  // transaction
1744  if (!sleClaimID)
1745  {
1746  return tecXCHAIN_NO_CLAIM_ID;
1747  }
1748 
1749  if ((*sleClaimID)[sfAccount] != account)
1750  {
1751  // Sequence number isn't owned by the sender of this transaction
1752  return tecXCHAIN_BAD_CLAIM_ID;
1753  }
1754  }
1755 
1756  // quorum is checked in `doApply`
1757  return tesSUCCESS;
1758 }
1759 
1760 TER
1762 {
1763  PaymentSandbox psb(&ctx_.view());
1764 
1765  AccountID const account = ctx_.tx[sfAccount];
1766  auto const dst = ctx_.tx[sfDestination];
1767  STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
1768  STAmount const& thisChainAmount = ctx_.tx[sfAmount];
1769  auto const claimID = ctx_.tx[sfXChainClaimID];
1770  auto const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
1771 
1772  struct ScopeResult
1773  {
1774  std::vector<AccountID> rewardAccounts;
1775  AccountID rewardPoolSrc;
1776  STAmount sendingAmount;
1777  STXChainBridge::ChainType srcChain;
1778  STAmount signatureReward;
1779  };
1780 
1781  auto const scopeResult = [&]() -> Expected<ScopeResult, TER> {
1782  // This lambda is ugly - admittedly. The purpose of this lambda is to
1783  // limit the scope of sles so they don't overlap with
1784  // `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
1785  // views, it's important that the sle's lifetime doesn't overlap.
1786 
1787  auto const sleAcct = psb.peek(keylet::account(account));
1788  auto const sleBridge = peekBridge(psb, bridgeSpec);
1789  auto const sleClaimID = psb.peek(claimIDKeylet);
1790 
1791  if (!(sleBridge && sleClaimID && sleAcct))
1792  return Unexpected(tecINTERNAL);
1793 
1794  AccountID const thisDoor = (*sleBridge)[sfAccount];
1795 
1797  {
1798  if (thisDoor == bridgeSpec.lockingChainDoor())
1800  else if (thisDoor == bridgeSpec.issuingChainDoor())
1802  else
1803  return Unexpected(tecINTERNAL);
1804  }
1805  STXChainBridge::ChainType const srcChain =
1806  STXChainBridge::otherChain(dstChain);
1807 
1808  auto const sendingAmount = [&]() -> STAmount {
1809  STAmount r(thisChainAmount);
1810  r.setIssue(bridgeSpec.issue(srcChain));
1811  return r;
1812  }();
1813 
1814  auto const [signersList, quorum, slTer] =
1815  getSignersListAndQuorum(ctx_.view(), *sleBridge, ctx_.journal);
1816 
1817  if (!isTesSuccess(slTer))
1818  return Unexpected(slTer);
1819 
1820  XChainClaimAttestations curAtts{
1821  sleClaimID->getFieldArray(sfXChainClaimAttestations)};
1822 
1823  auto const claimR = onClaim(
1824  curAtts,
1825  psb,
1826  sendingAmount,
1827  /*wasLockingChainSend*/ srcChain ==
1829  quorum,
1830  signersList,
1831  ctx_.journal);
1832  if (!claimR.has_value())
1833  return Unexpected(claimR.error());
1834 
1835  return ScopeResult{
1836  claimR.value(),
1837  (*sleClaimID)[sfAccount],
1838  sendingAmount,
1839  srcChain,
1840  (*sleClaimID)[sfSignatureReward],
1841  };
1842  }();
1843 
1844  if (!scopeResult.has_value())
1845  return scopeResult.error();
1846 
1847  auto const& [rewardAccounts, rewardPoolSrc, sendingAmount, srcChain, signatureReward] =
1848  scopeResult.value();
1850 
1851  auto const r = finalizeClaimHelper(
1852  psb,
1853  bridgeSpec,
1854  dst,
1855  dstTag,
1856  /*claimOwner*/ account,
1857  sendingAmount,
1858  rewardPoolSrc,
1859  signatureReward,
1860  rewardAccounts,
1861  srcChain,
1862  claimIDKeylet,
1863  OnTransferFail::keepClaim,
1864  DepositAuthPolicy::dstCanBypass,
1865  ctx_.journal);
1866  if (!r.isTesSuccess())
1867  return r.ter();
1868 
1869  psb.apply(ctx_.rawView());
1870 
1871  return tesSUCCESS;
1872 }
1873 
1874 //------------------------------------------------------------------------------
1875 
1878 {
1879  auto const maxSpend = [&] {
1880  auto const amount = ctx.tx[sfAmount];
1881  if (amount.native() && amount.signum() > 0)
1882  return amount.xrp();
1883  return XRPAmount{beast::zero};
1884  }();
1885 
1886  return TxConsequences{ctx.tx, maxSpend};
1887 }
1888 
1889 NotTEC
1891 {
1892  if (!ctx.rules.enabled(featureXChainBridge))
1893  return temDISABLED;
1894 
1895  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
1896  return ret;
1897 
1898  if (ctx.tx.getFlags() & tfUniversalMask)
1899  return temINVALID_FLAG;
1900 
1901  auto const amount = ctx.tx[sfAmount];
1902  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1903 
1904  if (amount.signum() <= 0 || !isLegalNet(amount))
1905  return temBAD_AMOUNT;
1906 
1907  if (amount.issue() != bridgeSpec.lockingChainIssue() &&
1908  amount.issue() != bridgeSpec.issuingChainIssue())
1909  return temBAD_ISSUER;
1910 
1911  return preflight2(ctx);
1912 }
1913 
1914 TER
1916 {
1917  auto const bridgeSpec = ctx.tx[sfXChainBridge];
1918  auto const amount = ctx.tx[sfAmount];
1919 
1920  auto const sleBridge = readBridge(ctx.view, bridgeSpec);
1921  if (!sleBridge)
1922  {
1923  return tecNO_ENTRY;
1924  }
1925 
1926  AccountID const thisDoor = (*sleBridge)[sfAccount];
1927  AccountID const account = ctx.tx[sfAccount];
1928 
1929  if (thisDoor == account)
1930  {
1931  // Door account can't lock funds onto itself
1932  return tecXCHAIN_SELF_COMMIT;
1933  }
1934 
1935  bool isLockingChain = false;
1936  {
1937  if (thisDoor == bridgeSpec.lockingChainDoor())
1938  isLockingChain = true;
1939  else if (thisDoor == bridgeSpec.issuingChainDoor())
1940  isLockingChain = false;
1941  else
1942  return tecINTERNAL;
1943  }
1944 
1945  if (isLockingChain)
1946  {
1947  if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue())
1949  }
1950  else
1951  {
1952  if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue())
1954  }
1955 
1956  return tesSUCCESS;
1957 }
1958 
1959 TER
1961 {
1962  PaymentSandbox psb(&ctx_.view());
1963 
1964  auto const account = ctx_.tx[sfAccount];
1965  auto const amount = ctx_.tx[sfAmount];
1966  auto const bridgeSpec = ctx_.tx[sfXChainBridge];
1967 
1968  if (!psb.read(keylet::account(account)))
1969  return tecINTERNAL;
1970 
1971  auto const sleBridge = readBridge(psb, bridgeSpec);
1972  if (!sleBridge)
1973  return tecINTERNAL;
1974 
1975  auto const dst = (*sleBridge)[sfAccount];
1976 
1977  // Support dipping into reserves to pay the fee
1978  TransferHelperSubmittingAccountInfo submittingAccountInfo{
1980 
1981  auto const thTer = transferHelper(
1982  psb,
1983  account,
1984  dst,
1985  /*dstTag*/ std::nullopt,
1986  /*claimOwner*/ std::nullopt,
1987  amount,
1988  CanCreateDstPolicy::no,
1989  DepositAuthPolicy::normal,
1990  submittingAccountInfo,
1991  ctx_.journal);
1992 
1993  if (!isTesSuccess(thTer))
1994  return thTer;
1995 
1996  psb.apply(ctx_.rawView());
1997 
1998  return tesSUCCESS;
1999 }
2000 
2001 //------------------------------------------------------------------------------
2002 
2003 NotTEC
2005 {
2006  if (!ctx.rules.enabled(featureXChainBridge))
2007  return temDISABLED;
2008 
2009  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
2010  return ret;
2011 
2012  if (ctx.tx.getFlags() & tfUniversalMask)
2013  return temINVALID_FLAG;
2014 
2015  auto const reward = ctx.tx[sfSignatureReward];
2016 
2017  if (!isXRP(reward) || reward.signum() < 0 || !isLegalNet(reward))
2019 
2020  return preflight2(ctx);
2021 }
2022 
2023 TER
2025 {
2026  auto const account = ctx.tx[sfAccount];
2027  auto const bridgeSpec = ctx.tx[sfXChainBridge];
2028  auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2029 
2030  if (!sleBridge)
2031  {
2032  return tecNO_ENTRY;
2033  }
2034 
2035  // Check that the reward matches
2036  auto const reward = ctx.tx[sfSignatureReward];
2037 
2038  if (reward != (*sleBridge)[sfSignatureReward])
2039  {
2041  }
2042 
2043  {
2044  // Check reserve
2045  auto const sleAcc = ctx.view.read(keylet::account(account));
2046  if (!sleAcc)
2047  return terNO_ACCOUNT;
2048 
2049  auto const balance = (*sleAcc)[sfBalance];
2050  auto const reserve =
2051  ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
2052 
2053  if (balance < reserve)
2054  return tecINSUFFICIENT_RESERVE;
2055  }
2056 
2057  return tesSUCCESS;
2058 }
2059 
2060 TER
2062 {
2063  auto const account = ctx_.tx[sfAccount];
2064  auto const bridgeSpec = ctx_.tx[sfXChainBridge];
2065  auto const reward = ctx_.tx[sfSignatureReward];
2066  auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
2067 
2068  auto const sleAcct = ctx_.view().peek(keylet::account(account));
2069  if (!sleAcct)
2070  return tecINTERNAL;
2071 
2072  auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
2073  if (!sleBridge)
2074  return tecINTERNAL;
2075 
2076  std::uint32_t const claimID = (*sleBridge)[sfXChainClaimID] + 1;
2077  if (claimID == 0)
2078  return tecINTERNAL; // overflow
2079 
2080  (*sleBridge)[sfXChainClaimID] = claimID;
2081 
2082  Keylet const claimIDKeylet = keylet::xChainClaimID(bridgeSpec, claimID);
2083  if (ctx_.view().exists(claimIDKeylet))
2084  return tecINTERNAL; // already checked out!?!
2085 
2086  auto const sleClaimID = std::make_shared<SLE>(claimIDKeylet);
2087 
2088  (*sleClaimID)[sfAccount] = account;
2089  (*sleClaimID)[sfXChainBridge] = bridgeSpec;
2090  (*sleClaimID)[sfXChainClaimID] = claimID;
2091  (*sleClaimID)[sfOtherChainSource] = otherChainSrc;
2092  (*sleClaimID)[sfSignatureReward] = reward;
2093  sleClaimID->setFieldArray(
2095 
2096  // Add to owner directory
2097  {
2098  auto const page = ctx_.view().dirInsert(
2099  keylet::ownerDir(account),
2100  claimIDKeylet,
2101  describeOwnerDir(account));
2102  if (!page)
2103  return tecDIR_FULL;
2104  (*sleClaimID)[sfOwnerNode] = *page;
2105  }
2106 
2107  adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
2108 
2109  ctx_.view().insert(sleClaimID);
2110  ctx_.view().update(sleBridge);
2111  ctx_.view().update(sleAcct);
2112 
2113  return tesSUCCESS;
2114 }
2115 
2116 //------------------------------------------------------------------------------
2117 
2118 NotTEC
2120 {
2121  return attestationPreflight<Attestations::AttestationClaim>(ctx);
2122 }
2123 
2124 TER
2126 {
2127  return attestationPreclaim<Attestations::AttestationClaim>(ctx);
2128 }
2129 
2130 TER
2132 {
2133  return attestationDoApply<Attestations::AttestationClaim>(ctx_);
2134 }
2135 
2136 //------------------------------------------------------------------------------
2137 
2138 NotTEC
2140 {
2141  return attestationPreflight<Attestations::AttestationCreateAccount>(ctx);
2142 }
2143 
2144 TER
2146 {
2147  return attestationPreclaim<Attestations::AttestationCreateAccount>(ctx);
2148 }
2149 
2150 TER
2152 {
2153  return attestationDoApply<Attestations::AttestationCreateAccount>(ctx_);
2154 }
2155 
2156 //------------------------------------------------------------------------------
2157 
2158 NotTEC
2160 {
2161  if (!ctx.rules.enabled(featureXChainBridge))
2162  return temDISABLED;
2163 
2164  if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
2165  return ret;
2166 
2167  if (ctx.tx.getFlags() & tfUniversalMask)
2168  return temINVALID_FLAG;
2169 
2170  auto const amount = ctx.tx[sfAmount];
2171 
2172  if (amount.signum() <= 0 || !amount.native())
2173  return temBAD_AMOUNT;
2174 
2175  auto const reward = ctx.tx[sfSignatureReward];
2176  if (reward.signum() < 0 || !reward.native())
2177  return temBAD_AMOUNT;
2178 
2179  if (reward.issue() != amount.issue())
2180  return temBAD_AMOUNT;
2181 
2182  return preflight2(ctx);
2183 }
2184 
2185 TER
2187 {
2188  STXChainBridge const bridgeSpec = ctx.tx[sfXChainBridge];
2189  STAmount const amount = ctx.tx[sfAmount];
2190  STAmount const reward = ctx.tx[sfSignatureReward];
2191 
2192  auto const sleBridge = readBridge(ctx.view, bridgeSpec);
2193  if (!sleBridge)
2194  {
2195  return tecNO_ENTRY;
2196  }
2197 
2198  if (reward != (*sleBridge)[sfSignatureReward])
2199  {
2201  }
2202 
2203  std::optional<STAmount> const minCreateAmount =
2204  (*sleBridge)[~sfMinAccountCreateAmount];
2205 
2206  if (!minCreateAmount)
2208 
2209  if (amount < *minCreateAmount)
2211 
2212  if (minCreateAmount->issue() != amount.issue())
2214 
2215  AccountID const thisDoor = (*sleBridge)[sfAccount];
2216  AccountID const account = ctx.tx[sfAccount];
2217  if (thisDoor == account)
2218  {
2219  // Door account can't lock funds onto itself
2220  return tecXCHAIN_SELF_COMMIT;
2221  }
2222 
2224  {
2225  if (thisDoor == bridgeSpec.lockingChainDoor())
2227  else if (thisDoor == bridgeSpec.issuingChainDoor())
2229  else
2230  return tecINTERNAL;
2231  }
2232  STXChainBridge::ChainType const dstChain =
2233  STXChainBridge::otherChain(srcChain);
2234 
2235  if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue())
2237 
2238  if (!isXRP(bridgeSpec.issue(dstChain)))
2240 
2241  return tesSUCCESS;
2242 }
2243 
2244 TER
2246 {
2247  PaymentSandbox psb(&ctx_.view());
2248 
2249  AccountID const account = ctx_.tx[sfAccount];
2250  STAmount const amount = ctx_.tx[sfAmount];
2251  STAmount const reward = ctx_.tx[sfSignatureReward];
2252  STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
2253 
2254  auto const sle = psb.peek(keylet::account(account));
2255  if (!sle)
2256  return tecINTERNAL;
2257 
2258  auto const sleBridge = peekBridge(psb, bridge);
2259  if (!sleBridge)
2260  return tecINTERNAL;
2261 
2262  auto const dst = (*sleBridge)[sfAccount];
2263 
2264  // Support dipping into reserves to pay the fee
2265  TransferHelperSubmittingAccountInfo submittingAccountInfo{
2267  STAmount const toTransfer = amount + reward;
2268  auto const thTer = transferHelper(
2269  psb,
2270  account,
2271  dst,
2272  /*dstTag*/ std::nullopt,
2273  /*claimOwner*/ std::nullopt,
2274  toTransfer,
2275  CanCreateDstPolicy::yes,
2276  DepositAuthPolicy::normal,
2277  submittingAccountInfo,
2278  ctx_.journal);
2279 
2280  if (!isTesSuccess(thTer))
2281  return thTer;
2282 
2283  (*sleBridge)[sfXChainAccountCreateCount] =
2284  (*sleBridge)[sfXChainAccountCreateCount] + 1;
2285  psb.update(sleBridge);
2286 
2287  psb.apply(ctx_.rawView());
2288 
2289  return tesSUCCESS;
2290 }
2291 
2292 } // namespace ripple
beast::Journal::fatal
Stream fatal() const
Definition: Journal.h:338
ripple::XChainClaim::doApply
TER doApply() override
Definition: XChainBridge.cpp:1761
ripple::temXCHAIN_BAD_PROOF
@ temXCHAIN_BAD_PROOF
Definition: TER.h:130
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:311
ripple::tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
@ tecXCHAIN_ACCOUNT_CREATE_TOO_MANY
Definition: TER.h:326
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:210
ripple::preflight2
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:130
ripple::tecXCHAIN_BAD_CLAIM_ID
@ tecXCHAIN_BAD_CLAIM_ID
Definition: TER.h:316
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
ripple::STXChainBridge::srcChain
static ChainType srcChain(bool wasLockingChainSend)
Definition: STXChainBridge.h:218
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:318
ripple::sfOwnerNode
const SF_UINT64 sfOwnerNode
ripple::tecXCHAIN_CLAIM_NO_QUORUM
@ tecXCHAIN_CLAIM_NO_QUORUM
Definition: TER.h:317
ripple::STXChainBridge::ChainType
ChainType
Definition: STXChainBridge.h:42
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:202
ripple::lsfDisableMaster
@ lsfDisableMaster
Definition: LedgerFormats.h:254
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:633
unordered_set
std::vector::reserve
T reserve(T... args)
ripple::STXChainBridge::dstChain
static ChainType dstChain(bool wasLockingChainSend)
Definition: STXChainBridge.h:226
ripple::STXChainBridge::lockingChainIssue
Issue const & lockingChainIssue() const
Definition: STXChainBridge.h:170
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:320
ripple::tecXCHAIN_SENDING_ACCOUNT_MISMATCH
@ tecXCHAIN_SENDING_ACCOUNT_MISMATCH
Definition: TER.h:323
ripple::temXCHAIN_EQUAL_DOOR_ACCOUNTS
@ temXCHAIN_EQUAL_DOOR_ACCOUNTS
Definition: TER.h:129
ripple::Unexpected
Unexpected(E(&)[N]) -> Unexpected< E const * >
ripple::XChainCreateClaimID::doApply
TER doApply() override
Definition: XChainBridge.cpp:2061
ripple::tecDST_TAG_NEEDED
@ tecDST_TAG_NEEDED
Definition: TER.h:287
ripple::detail::ApplyViewBase::update
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
Definition: ApplyViewBase.cpp:146
ripple::XChainCommit::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:1915
ripple::STXChainBridge::ChainType::locking
@ locking
ripple::sfOtherChainSource
const SF_ACCOUNT sfOtherChainSource
ripple::tecXCHAIN_NO_CLAIM_ID
@ tecXCHAIN_NO_CLAIM_ID
Definition: TER.h:315
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:413
ripple::isLegalNet
bool isLegalNet(STAmount const &value)
Definition: STAmount.h:449
ripple::ApplyContext::rawView
RawView & rawView()
Definition: ApplyContext.h:67
ripple::sfXChainAccountClaimCount
const SF_UINT64 sfXChainAccountClaimCount
ripple::temBAD_ISSUER
@ temBAD_ISSUER
Definition: TER.h:91
ripple::lsfAllowTrustLineClawback
@ lsfAllowTrustLineClawback
Definition: LedgerFormats.h:272
ripple::XChainAddClaimAttestation::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:2125
ripple::isTecClaim
bool isTecClaim(TER x)
Definition: TER.h:639
ripple::temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
@ temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT
Definition: TER.h:134
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:78
ripple::lsfDepositAuth
@ lsfDepositAuth
Definition: LedgerFormats.h:259
ripple::sfXChainClaimID
const SF_UINT64 sfXChainClaimID
ripple::temXCHAIN_BRIDGE_BAD_ISSUES
@ temXCHAIN_BRIDGE_BAD_ISSUES
Definition: TER.h:131
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::isTerRetry
bool isTerRetry(TER x)
Definition: TER.h:627
ripple::tecNO_DST_INSUF_XRP
@ tecNO_DST_INSUF_XRP
Definition: TER.h:269
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:329
ripple::temINVALID_FLAG
@ temINVALID_FLAG
Definition: TER.h:109
ripple::XChainCreateBridge::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:1367
ripple::tefBAD_LEDGER
@ tefBAD_LEDGER
Definition: TER.h:161
ripple::adjustOwnerCount
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:730
ripple::XChainCreateAccountCommit::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:2186
ripple::STXChainBridge::issuingChainIssue
Issue const & issuingChainIssue() const
Definition: STXChainBridge.h:182
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:141
ripple::XChainCreateAccountCommit::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:2159
ripple::sfXChainClaimAttestations
const SField sfXChainClaimAttestations
ripple::tecDUPLICATE
@ tecDUPLICATE
Definition: TER.h:293
ripple::keylet::bridge
Keylet bridge(STXChainBridge const &bridge, STXChainBridge::ChainType chainType)
Definition: Indexes.cpp:400
ripple::TERSubset< CanCvtToTER >
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:317
ripple::isTefFailure
bool isTefFailure(TER x)
Definition: TER.h:621
ripple::XChainCreateBridge::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:1451
ripple::calcAccountID
AccountID calcAccountID(PublicKey const &pk)
Definition: AccountID.cpp:158
ripple::XChainCreateClaimID::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:2004
ripple::STArray
Definition: STArray.h:28
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, bool 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::TER
TERSubset< CanCvtToTER > TER
Definition: TER.h:604
ripple::STAmount
Definition: STAmount.h:46
ripple::tecUNFUNDED_PAYMENT
@ tecUNFUNDED_PAYMENT
Definition: TER.h:263
ripple::tecXCHAIN_ACCOUNT_CREATE_PAST
@ tecXCHAIN_ACCOUNT_CREATE_PAST
Definition: TER.h:325
ripple::ReadView::exists
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
ripple::tecINTERNAL
@ tecINTERNAL
Definition: TER.h:288
ripple::STObject::getFlags
std::uint32_t getFlags() const
Definition: STObject.cpp:481
ripple::XChainClaim::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:1677
ripple::isXRP
bool isXRP(AccountID const &c)
Definition: AccountID.h:91
ripple::tecXCHAIN_PAYMENT_FAILED
@ tecXCHAIN_PAYMENT_FAILED
Definition: TER.h:327
ripple::temBAD_AMOUNT
@ temBAD_AMOUNT
Definition: TER.h:87
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:319
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:314
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:1612
ripple::ApplyContext::view
ApplyView & view()
Definition: ApplyContext.h:54
ripple::STXChainBridge::lockingChainDoor
AccountID const & lockingChainDoor() const
Definition: STXChainBridge.h:164
ripple::PreclaimContext::tx
STTx const & tx
Definition: Transactor.h:58
ripple::STXChainBridge::issuingChainDoor
AccountID const & issuingChainDoor() const
Definition: STXChainBridge.h:176
ripple::STXChainBridge
Definition: STXChainBridge.h:32
ripple::lsfRequireDestTag
@ lsfRequireDestTag
Definition: LedgerFormats.h:249
ripple::tecDIR_FULL
@ tecDIR_FULL
Definition: TER.h:265
ripple::KeyType::secp256k1
@ secp256k1
ripple::terNO_ACCOUNT
@ terNO_ACCOUNT
Definition: TER.h:207
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:2024
ripple::tfClearAccountCreateAmount
constexpr std::uint32_t tfClearAccountCreateAmount
Definition: TxFlags.h:185
ripple::XChainCreateAccountCommit::doApply
TER doApply() override
Definition: XChainBridge.cpp:2245
ripple::tecXCHAIN_CREATE_ACCOUNT_DISABLED
@ tecXCHAIN_CREATE_ACCOUNT_DISABLED
Definition: TER.h:330
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:328
ripple::temDISABLED
@ temDISABLED
Definition: TER.h:112
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:2145
ripple::tecXCHAIN_REWARD_MISMATCH
@ tecXCHAIN_REWARD_MISMATCH
Definition: TER.h:321
ripple::sfFlags
const SF_UINT32 sfFlags
ripple::XChainCommit::makeTxConsequences
static TxConsequences makeTxConsequences(PreflightContext const &ctx)
Definition: XChainBridge.cpp:1877
ripple::tecNO_ISSUER
@ tecNO_ISSUER
Definition: TER.h:277
ripple::sfDestinationTag
const SF_UINT32 sfDestinationTag
ripple::XChainCommit::doApply
TER doApply() override
Definition: XChainBridge.cpp:1960
ripple::Transactor::mPriorBalance
XRPAmount mPriorBalance
Definition: Transactor.h:92
ripple::tecNO_PERMISSION
@ tecNO_PERMISSION
Definition: TER.h:283
ripple::Transactor::mSourceBalance
XRPAmount mSourceBalance
Definition: Transactor.h:93
ripple::XChainAddClaimAttestation::doApply
TER doApply() override
Definition: XChainBridge.cpp:2131
ripple::sfXChainAccountCreateCount
const SF_UINT64 sfXChainAccountCreateCount
ripple::sfBalance
const SF_AMOUNT sfBalance
ripple::XChainCommit::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:1890
ripple::XChainCreateBridge::doApply
TER doApply() override
Definition: XChainBridge.cpp:1496
ripple::XChainAddAccountCreateAttestation::doApply
TER doApply() override
Definition: XChainBridge.cpp:2151
ripple::keylet::xChainCreateAccountClaimID
Keylet xChainCreateAccountClaimID(STXChainBridge const &bridge, std::uint64_t seq)
Definition: Indexes.cpp:427
ripple::tecINSUFFICIENT_RESERVE
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:285
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:2119
ripple::BridgeModify::preclaim
static TER preclaim(PreclaimContext const &ctx)
Definition: XChainBridge.cpp:1595
ripple::temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
@ temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT
Definition: TER.h:133
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:284
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:85
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:276
ripple::XChainClaim::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:1652
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::BridgeModify::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:1542
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:295
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:132
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:233
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:322
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:268
ripple::XChainAddAccountCreateAttestation::preflight
static NotTEC preflight(PreflightContext const &ctx)
Definition: XChainBridge.cpp:2139
ripple::XRPAmount
Definition: XRPAmount.h:46
ripple::NotTEC
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:564
ripple::tecXCHAIN_INSUFF_CREATE_AMOUNT
@ tecXCHAIN_INSUFF_CREATE_AMOUNT
Definition: TER.h:324