XChainBridge: Introduce sidechain support (XLS-38): (#4292)

A bridge connects two blockchains: a locking chain and an issuing
chain (also called a mainchain and a sidechain). Both are independent
ledgers, with their own validators and potentially their own custom
transactions. Importantly, there is a way to move assets from the
locking chain to the issuing chain and a way to return those assets from
the issuing chain back to the locking chain: the bridge. This key
operation is called a cross-chain transfer. A cross-chain transfer is
not a single transaction. It happens on two chains, requires multiple
transactions, and involves an additional server type called a "witness".

A bridge does not exchange assets between two ledgers. Instead, it locks
assets on one ledger (the "locking chain") and represents those assets
with wrapped assets on another chain (the "issuing chain"). A good model
to keep in mind is a box with an infinite supply of wrapped assets.
Putting an asset from the locking chain into the box will release a
wrapped asset onto the issuing chain. Putting a wrapped asset from the
issuing chain back into the box will release one of the existing locking
chain assets back onto the locking chain. There is no other way to get
assets into or out of the box. Note that there is no way for the box to
"run out of" wrapped assets - it has an infinite supply.

Co-authored-by: Gregory Popovitch <greg7mdp@gmail.com>
This commit is contained in:
Scott Determan
2023-09-14 16:08:41 -04:00
committed by GitHub
parent 7fae1c1262
commit 649c11a78e
59 changed files with 12142 additions and 174 deletions

View File

@@ -18,8 +18,10 @@ macro(group_sources curdir)
endmacro()
macro (exclude_from_default target_)
set_target_properties (${target_} PROPERTIES EXCLUDE_FROM_ALL ON)
set_target_properties (${target_} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON)
if(target_)
set_target_properties (${target_} PROPERTIES EXCLUDE_FROM_ALL ON)
set_target_properties (${target_} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON)
endif()
endmacro ()
macro (exclude_if_included target_)

View File

@@ -103,7 +103,9 @@ target_sources (xrpl_core PRIVATE
src/ripple/protocol/impl/STObject.cpp
src/ripple/protocol/impl/STParsedJSON.cpp
src/ripple/protocol/impl/STPathSet.cpp
src/ripple/protocol/impl/STXChainBridge.cpp
src/ripple/protocol/impl/STTx.cpp
src/ripple/protocol/impl/XChainAttestations.cpp
src/ripple/protocol/impl/STValidation.cpp
src/ripple/protocol/impl/STVar.cpp
src/ripple/protocol/impl/STVector256.cpp
@@ -243,6 +245,7 @@ install (
src/ripple/protocol/Indexes.h
src/ripple/protocol/InnerObjectFormats.h
src/ripple/protocol/Issue.h
src/ripple/protocol/json_get_or_throw.h
src/ripple/protocol/KeyType.h
src/ripple/protocol/Keylet.h
src/ripple/protocol/KnownFormats.h
@@ -273,6 +276,8 @@ install (
src/ripple/protocol/STParsedJSON.h
src/ripple/protocol/STPathSet.h
src/ripple/protocol/STTx.h
src/ripple/protocol/XChainAttestations.h
src/ripple/protocol/STXChainBridge.h
src/ripple/protocol/STValidation.h
src/ripple/protocol/STVector256.h
src/ripple/protocol/SecretKey.h
@@ -528,6 +533,7 @@ target_sources (rippled PRIVATE
src/ripple/app/tx/impl/SetRegularKey.cpp
src/ripple/app/tx/impl/SetSignerList.cpp
src/ripple/app/tx/impl/SetTrust.cpp
src/ripple/app/tx/impl/XChainBridge.cpp
src/ripple/app/tx/impl/SignerEntries.cpp
src/ripple/app/tx/impl/Taker.cpp
src/ripple/app/tx/impl/Transactor.cpp
@@ -809,6 +815,7 @@ if (tests)
src/test/app/ReducedOffer_test.cpp
src/test/app/Regression_test.cpp
src/test/app/SHAMapStore_test.cpp
src/test/app/XChain_test.cpp
src/test/app/SetAuth_test.cpp
src/test/app/SetRegularKey_test.cpp
src/test/app/SetTrust_test.cpp
@@ -927,6 +934,7 @@ if (tests)
src/test/jtx/impl/acctdelete.cpp
src/test/jtx/impl/account_txn_id.cpp
src/test/jtx/impl/amount.cpp
src/test/jtx/impl/attester.cpp
src/test/jtx/impl/balance.cpp
src/test/jtx/impl/check.cpp
src/test/jtx/impl/delivermin.cpp
@@ -948,6 +956,7 @@ if (tests)
src/test/jtx/impl/regkey.cpp
src/test/jtx/impl/sendmax.cpp
src/test/jtx/impl/seq.cpp
src/test/jtx/impl/xchain_bridge.cpp
src/test/jtx/impl/sig.cpp
src/test/jtx/impl/tag.cpp
src/test/jtx/impl/ticket.cpp

View File

@@ -2,6 +2,12 @@
package/container targets - (optional)
#]===================================================================]
# Early return if the `containers` directory is missing,
# e.g. when we are building a Conan package.
if(NOT EXISTS containers)
return()
endif()
if (is_root_project)
if (NOT DOCKER)
find_program (DOCKER docker)

View File

@@ -19,7 +19,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# make GIT_COMMIT_HASH define available to all sources
find_package(Git)
if(Git_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --abbrev=40
execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git describe --always --abbrev=40
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch)
if(gch)
set(GIT_COMMIT_HASH "${gch}")

View File

@@ -1279,6 +1279,12 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline)
}
}
if (auto const& forcedRange = config().FORCED_LEDGER_RANGE_PRESENT)
{
m_ledgerMaster->setLedgerRangePresent(
forcedRange->first, forcedRange->second);
}
if (!config().reporting())
m_orderBookDB.setup(getLedgerMaster().getCurrentLedger());

View File

@@ -378,8 +378,13 @@ run(int argc, char** argv)
"Override the minimum validation quorum.")(
"reportingReadOnly", "Run in read-only reporting mode")(
"silent", "No output to the console after startup.")(
"standalone,a", "Run with no peers.")("verbose,v", "Verbose logging.")(
"version", "Display the build version.");
"standalone,a", "Run with no peers.")("verbose,v", "Verbose logging.")
("force_ledger_present_range",
po::value<std::string>(),
"Specify the range of present ledgers for testing purposes. Min and "
"max values are comma separated.")(
"version", "Display the build version.");
po::options_description data("Ledger/Data Options");
data.add_options()("import", importText.c_str())(
@@ -602,6 +607,51 @@ run(int argc, char** argv)
return 0;
}
if (vm.contains("force_ledger_present_range"))
{
try
{
auto const r = [&vm]() -> std::vector<std::uint32_t> {
std::vector<std::string> strVec;
boost::split(
strVec,
vm["force_ledger_present_range"].as<std::string>(),
boost::algorithm::is_any_of(","));
std::vector<std::uint32_t> result;
for (auto& s : strVec)
{
boost::trim(s);
if (!s.empty())
result.push_back(std::stoi(s));
}
return result;
}();
if (r.size() == 2)
{
if (r[0] > r[1])
{
throw std::runtime_error(
"Invalid force_ledger_present_range parameter");
}
config->FORCED_LEDGER_RANGE_PRESENT.emplace(r[0], r[1]);
}
else
{
throw std::runtime_error(
"Invalid force_ledger_present_range parameter");
}
}
catch (std::exception const& e)
{
std::cerr << "invalid 'force_ledger_present_range' parameter. The "
"parameter must be two numbers separated by a comma. "
"The first number must be <= the second."
<< std::endl;
return -1;
}
}
if (vm.count("start"))
{
config->START_UP = Config::FRESH;

View File

@@ -607,12 +607,14 @@ private:
void
pubValidatedTransaction(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& transaction);
AcceptedLedgerTx const& transaction,
bool last);
void
pubAccountTransaction(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& transaction);
AcceptedLedgerTx const& transaction,
bool last);
void
pubProposedAccountTransaction(
@@ -3031,7 +3033,8 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
for (auto const& accTx : *alpAccepted)
{
JLOG(m_journal.trace()) << "pubAccepted: " << accTx->getJson();
pubValidatedTransaction(lpAccepted, *accTx);
pubValidatedTransaction(
lpAccepted, *accTx, accTx == *(--alpAccepted->end()));
}
}
@@ -3138,7 +3141,8 @@ NetworkOPsImp::transJson(
void
NetworkOPsImp::pubValidatedTransaction(
std::shared_ptr<ReadView const> const& ledger,
const AcceptedLedgerTx& transaction)
const AcceptedLedgerTx& transaction,
bool last)
{
auto const& stTxn = transaction.getTxn();
@@ -3187,13 +3191,14 @@ NetworkOPsImp::pubValidatedTransaction(
if (transaction.getResult() == tesSUCCESS)
app_.getOrderBookDB().processTxn(ledger, transaction, jvObj);
pubAccountTransaction(ledger, transaction);
pubAccountTransaction(ledger, transaction, last);
}
void
NetworkOPsImp::pubAccountTransaction(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& transaction)
AcceptedLedgerTx const& transaction,
bool last)
{
hash_set<InfoSub::pointer> notify;
int iProposed = 0;
@@ -3301,6 +3306,9 @@ NetworkOPsImp::pubAccountTransaction(
for (InfoSub::ref isrListener : notify)
isrListener->send(jvObj, true);
if (last)
jvObj[jss::account_history_boundary] = true;
assert(!jvObj.isMember(jss::account_history_tx_stream));
for (auto& info : accountHistoryNotify)
{
@@ -3699,8 +3707,11 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
auto const& txns = dbResult->first;
marker = dbResult->second;
for (auto const& [tx, meta] : txns)
size_t num_txns = txns.size();
for (size_t i = 0; i < num_txns; ++i)
{
auto const& [tx, meta] = txns[i];
if (!tx || !meta)
{
JLOG(m_journal.debug())
@@ -3735,6 +3746,11 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo)
*stTxn, meta->getResultTER(), true, curTxLedger);
jvTx[jss::meta] = meta->getJson(JsonOptions::none);
jvTx[jss::account_history_tx_index] = txHistoryIndex--;
if (i + 1 == num_txns ||
txns[i + 1].first->getLedger() != tx->getLedger())
jvTx[jss::account_history_boundary] = true;
RPC::insertDeliveredAmount(
jvTx[jss::meta], *curTxLedger, stTxn, *meta);
if (isFirstTx(tx, meta))

View File

@@ -27,6 +27,7 @@
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/SystemParameters.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/nftPageMask.h>
namespace ripple {
@@ -387,6 +388,9 @@ LedgerEntryTypesMatch::visitEntry(
case ltNFTOKEN_PAGE:
case ltNFTOKEN_OFFER:
case ltAMM:
case ltBRIDGE:
case ltXCHAIN_OWNED_CLAIM_ID:
case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
break;
default:
invalidTypeAdded_ = true;
@@ -487,7 +491,9 @@ ValidNewAccountRoot::finalize(
}
// From this point on we know exactly one account was created.
if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE) &&
if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
result == tesSUCCESS)
{
std::uint32_t const startingSeq{

View File

@@ -25,6 +25,7 @@
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TER.h>
#include <cstdint>
#include <map>
#include <tuple>
@@ -300,7 +301,7 @@ public:
class ValidNewAccountRoot
{
std::uint32_t accountsCreated_ = 0;
std::uint32_t accountSeq_ = 0; // Only meaningful if accountsCreated_ > 0
std::uint32_t accountSeq_ = 0;
public:
void

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,255 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TX_XCHAINBRIDGE_H_INCLUDED
#define RIPPLE_TX_XCHAINBRIDGE_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/XChainAttestations.h>
namespace ripple {
constexpr size_t xbridgeMaxAccountCreateClaims = 128;
// Attach a new bridge to a door account. Once this is done, the cross-chain
// transfer transactions may be used to transfer funds from this account.
class XChainCreateBridge : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateBridge(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
class BridgeModify : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit BridgeModify(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Claim funds from a `XChainCommit` transaction. This is normally not needed,
// but may be used to handle transaction failures or if the destination account
// was not specified in the `XChainCommit` transaction. It may only be used
// after a quorum of signatures have been sent from the witness servers.
//
// If the transaction succeeds in moving funds, the referenced `XChainClaimID`
// ledger object will be destroyed. This prevents transaction replay. If the
// transaction fails, the `XChainClaimID` will not be destroyed and the
// transaction may be re-run with different parameters.
class XChainClaim : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainClaim(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Put assets into trust on the locking-chain so they may be wrapped on the
// issuing-chain, or return wrapped assets on the issuing-chain so they can be
// unlocked on the locking-chain. The second step in a cross-chain transfer.
class XChainCommit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
explicit XChainCommit(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Create a new claim id owned by the account. This is the first step in a
// cross-chain transfer. The claim id must be created on the destination chain
// before the `XChainCommit` transaction (which must reference this number) can
// be sent on the source chain. The account that will send the `XChainCommit` on
// the source chain must be specified in this transaction (see note on the
// `SourceAccount` field in the `XChainClaimID` ledger object for
// justification). The actual sequence number must be retrieved from a validated
// ledger.
class XChainCreateClaimID : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateClaimID(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// Provide attestations from a witness server attesting to events on
// the other chain. The signatures must be from one of the keys on the door's
// signer's list at the time the signature was provided. However, if the
// signature list changes between the time the signature was submitted and the
// quorum is reached, the new signature set is used and some of the currently
// collected signatures may be removed. Also note the reward is only sent to
// accounts that have keys on the current list.
class XChainAddClaimAttestation : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainAddClaimAttestation(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
class XChainAddAccountCreateAttestation : public Transactor
{
public:
// Blocker since we cannot accurately calculate the consequences
static constexpr ConsequencesFactoryType ConsequencesFactory{Blocker};
explicit XChainAddAccountCreateAttestation(ApplyContext& ctx)
: Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
// This is a special transaction used for creating accounts through a
// cross-chain transfer. A normal cross-chain transfer requires a "chain claim
// id" (which requires an existing account on the destination chain). One
// purpose of the "chain claim id" is to prevent transaction replay. For this
// transaction, we use a different mechanism: the accounts must be claimed on
// the destination chain in the same order that the `XChainCreateAccountCommit`
// transactions occurred on the source chain.
//
// This transaction can only be used for XRP to XRP bridges.
//
// IMPORTANT: This transaction should only be enabled if the witness
// attestations will be reliably delivered to the destination chain. If the
// signatures are not delivered (for example, the chain relies on user wallets
// to collect signatures) then account creation would be blocked for all
// transactions that happened after the one waiting on attestations. This could
// be used maliciously. To disable this transaction on XRP to XRP bridges, the
// bridge's `MinAccountCreateAmount` should not be present.
//
// Note: If this account already exists, the XRP is transferred to the existing
// account. However, note that unlike the `XChainCommit` transaction, there is
// no error handling mechanism. If the claim transaction fails, there is no
// mechanism for refunds. The funds are permanently lost. This transaction
// should still only be used for account creation.
class XChainCreateAccountCommit : public Transactor
{
public:
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
explicit XChainCreateAccountCommit(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
} // namespace ripple
#endif

View File

@@ -47,6 +47,8 @@
#include <ripple/app/tx/impl/SetRegularKey.h>
#include <ripple/app/tx/impl/SetSignerList.h>
#include <ripple/app/tx/impl/SetTrust.h>
#include <ripple/app/tx/impl/XChainBridge.h>
#include <ripple/protocol/TxFormats.h>
namespace ripple {
@@ -168,6 +170,23 @@ invoke_preflight(PreflightContext const& ctx)
return invoke_preflight_helper<AMMBid>(ctx);
case ttAMM_DELETE:
return invoke_preflight_helper<AMMDelete>(ctx);
case ttXCHAIN_CREATE_BRIDGE:
return invoke_preflight_helper<XChainCreateBridge>(ctx);
case ttXCHAIN_MODIFY_BRIDGE:
return invoke_preflight_helper<BridgeModify>(ctx);
case ttXCHAIN_CREATE_CLAIM_ID:
return invoke_preflight_helper<XChainCreateClaimID>(ctx);
case ttXCHAIN_COMMIT:
return invoke_preflight_helper<XChainCommit>(ctx);
case ttXCHAIN_CLAIM:
return invoke_preflight_helper<XChainClaim>(ctx);
case ttXCHAIN_ADD_CLAIM_ATTESTATION:
return invoke_preflight_helper<XChainAddClaimAttestation>(ctx);
case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION:
return invoke_preflight_helper<XChainAddAccountCreateAttestation>(
ctx);
case ttXCHAIN_ACCOUNT_CREATE_COMMIT:
return invoke_preflight_helper<XChainCreateAccountCommit>(ctx);
default:
assert(false);
return {temUNKNOWN, TxConsequences{temUNKNOWN}};
@@ -283,6 +302,22 @@ invoke_preclaim(PreclaimContext const& ctx)
return invoke_preclaim<AMMBid>(ctx);
case ttAMM_DELETE:
return invoke_preclaim<AMMDelete>(ctx);
case ttXCHAIN_CREATE_BRIDGE:
return invoke_preclaim<XChainCreateBridge>(ctx);
case ttXCHAIN_MODIFY_BRIDGE:
return invoke_preclaim<BridgeModify>(ctx);
case ttXCHAIN_CREATE_CLAIM_ID:
return invoke_preclaim<XChainCreateClaimID>(ctx);
case ttXCHAIN_COMMIT:
return invoke_preclaim<XChainCommit>(ctx);
case ttXCHAIN_CLAIM:
return invoke_preclaim<XChainClaim>(ctx);
case ttXCHAIN_ACCOUNT_CREATE_COMMIT:
return invoke_preclaim<XChainCreateAccountCommit>(ctx);
case ttXCHAIN_ADD_CLAIM_ATTESTATION:
return invoke_preclaim<XChainAddClaimAttestation>(ctx);
case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION:
return invoke_preclaim<XChainAddAccountCreateAttestation>(ctx);
default:
assert(false);
return temUNKNOWN;
@@ -360,6 +395,23 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx)
return AMMBid::calculateBaseFee(view, tx);
case ttAMM_DELETE:
return AMMDelete::calculateBaseFee(view, tx);
case ttXCHAIN_CREATE_BRIDGE:
return XChainCreateBridge::calculateBaseFee(view, tx);
case ttXCHAIN_MODIFY_BRIDGE:
return BridgeModify::calculateBaseFee(view, tx);
case ttXCHAIN_CREATE_CLAIM_ID:
return XChainCreateClaimID::calculateBaseFee(view, tx);
case ttXCHAIN_COMMIT:
return XChainCommit::calculateBaseFee(view, tx);
case ttXCHAIN_CLAIM:
return XChainClaim::calculateBaseFee(view, tx);
case ttXCHAIN_ADD_CLAIM_ATTESTATION:
return XChainAddClaimAttestation::calculateBaseFee(view, tx);
case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION:
return XChainAddAccountCreateAttestation::calculateBaseFee(
view, tx);
case ttXCHAIN_ACCOUNT_CREATE_COMMIT:
return XChainCreateAccountCommit::calculateBaseFee(view, tx);
default:
assert(false);
return XRPAmount{0};
@@ -540,6 +592,38 @@ invoke_apply(ApplyContext& ctx)
AMMDelete p(ctx);
return p();
}
case ttXCHAIN_CREATE_BRIDGE: {
XChainCreateBridge p(ctx);
return p();
}
case ttXCHAIN_MODIFY_BRIDGE: {
BridgeModify p(ctx);
return p();
}
case ttXCHAIN_CREATE_CLAIM_ID: {
XChainCreateClaimID p(ctx);
return p();
}
case ttXCHAIN_COMMIT: {
XChainCommit p(ctx);
return p();
}
case ttXCHAIN_CLAIM: {
XChainClaim p(ctx);
return p();
}
case ttXCHAIN_ADD_CLAIM_ATTESTATION: {
XChainAddClaimAttestation p(ctx);
return p();
}
case ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION: {
XChainAddAccountCreateAttestation p(ctx);
return p();
}
case ttXCHAIN_ACCOUNT_CREATE_COMMIT: {
XChainCreateAccountCommit p(ctx);
return p();
}
default:
assert(false);
return {temUNKNOWN, false};

View File

@@ -134,7 +134,6 @@ public:
class Stream;
private:
/* Scoped ostream-based container for writing messages to a Journal. */
class ScopedStream
{

View File

@@ -26,9 +26,11 @@
#include <ripple/beast/net/IPEndpoint.h>
#include <ripple/beast/utility/Journal.h>
#include <ripple/protocol/SystemParameters.h> // VFALCO Breaks levelization
#include <boost/beast/core/string.hpp>
#include <boost/filesystem.hpp> // VFALCO FIX: This include should not be here
#include <boost/lexical_cast.hpp>
#include <algorithm>
#include <chrono>
#include <cstdint>
@@ -37,6 +39,7 @@
#include <string>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <vector>
namespace ripple {
@@ -295,6 +298,14 @@ public:
// First, attempt to load the latest ledger directly from disk.
bool FAST_LOAD = false;
// When starting rippled with existing database it do not know it has those
// ledgers locally until the server naturally tries to backfill. This makes
// is difficult to test some functionality (in particular performance
// testing sidechains). With this variable the user is able to force rippled
// to consider the ledger range to be present. It should be used for testing
// only.
std::optional<std::pair<std::uint32_t, std::uint32_t>>
FORCED_LEDGER_RANGE_PRESENT;
public:
Config();

View File

@@ -26,9 +26,6 @@
#include <ripple/json/json_forwards.h>
#include <ripple/protocol/PublicKey.h>
#define SOCI_USE_BOOST
#include <soci/soci.h>
#include <mutex>
#include <optional>
#include <string>

View File

@@ -26,6 +26,8 @@
#include <ripple/basics/UnorderedContainers.h>
#include <ripple/basics/base_uint.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/json_get_or_throw.h>
#include <cstddef>
#include <mutex>
#include <optional>
@@ -123,6 +125,21 @@ initAccountIdCache(std::size_t count);
} // namespace ripple
//------------------------------------------------------------------------------
namespace Json {
template <>
inline ripple::AccountID
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
std::string const b58 = getOrThrow<std::string>(v, field);
if (auto const r = parseBase58<AccountID>(b58))
return *r;
Throw<JsonTypeMismatchError>(field.getJsonName(), "AccountID");
}
} // namespace Json
//------------------------------------------------------------------------------
namespace std {

View File

@@ -74,7 +74,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 61;
static constexpr std::size_t numFeatures = 62;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -348,6 +348,7 @@ extern uint256 const fixNonFungibleTokensV1_2;
extern uint256 const fixNFTokenRemint;
extern uint256 const fixReducedOffersV1;
extern uint256 const featureClawback;
extern uint256 const featureXChainBridge;
} // namespace ripple

View File

@@ -26,6 +26,7 @@
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/UintTypes.h>
#include <cstdint>
@@ -270,6 +271,15 @@ amm(Issue const& issue1, Issue const& issue2) noexcept;
Keylet
amm(uint256 const& amm) noexcept;
Keylet
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType);
Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq);
Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq);
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:

View File

@@ -91,6 +91,13 @@ enum LedgerEntryType : std::uint16_t
*/
ltOFFER = 0x006f,
/** The ledger object which lists details about sidechains.
\sa keylet::bridge
*/
ltBRIDGE = 0x0069,
/** A ledger object that contains a list of ledger hashes.
This type is used to store the ledger hashes which the protocol uses
@@ -109,6 +116,18 @@ enum LedgerEntryType : std::uint16_t
*/
ltAMENDMENTS = 0x0066,
/** A claim id for a cross chain transaction.
\sa keylet::xChainClaimID
*/
ltXCHAIN_OWNED_CLAIM_ID = 0x0071,
/** A claim id for a cross chain create account transaction.
\sa keylet::xChainCreateAccountClaimID
*/
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID = 0x0074,
/** The ledger object which lists the network's fee settings.
\note This is a singleton: only one such object exists in the ledger.

View File

@@ -24,7 +24,9 @@
#include <ripple/protocol/KeyType.h>
#include <ripple/protocol/STExchange.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/json_get_or_throw.h>
#include <ripple/protocol/tokens.h>
#include <algorithm>
#include <cstdint>
#include <cstring>
@@ -268,4 +270,27 @@ calcAccountID(PublicKey const& pk);
} // namespace ripple
//------------------------------------------------------------------------------
namespace Json {
template <>
inline ripple::PublicKey
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
std::string const b58 = getOrThrow<std::string>(v, field);
if (auto pubKeyBlob = strUnHex(b58); publicKeyType(makeSlice(*pubKeyBlob)))
{
return PublicKey{makeSlice(*pubKeyBlob)};
}
for (auto const tokenType :
{TokenType::NodePublic, TokenType::AccountPublic})
{
if (auto const pk = parseBase58<PublicKey>(tokenType, b58))
return *pk;
}
Throw<JsonTypeMismatchError>(field.getJsonName(), "PublicKey");
}
} // namespace Json
#endif

View File

@@ -49,6 +49,7 @@ template <int>
class STBitString;
template <class>
class STInteger;
class STXChainBridge;
class STVector256;
enum SerializedTypeID {
@@ -79,6 +80,7 @@ enum SerializedTypeID {
STI_UINT384 = 22,
STI_UINT512 = 23,
STI_ISSUE = 24,
STI_XCHAIN_BRIDGE = 25,
// high level types
// cannot be serialized inside other types
@@ -318,6 +320,7 @@ using SF_AMOUNT = TypedField<STAmount>;
using SF_ISSUE = TypedField<STIssue>;
using SF_VL = TypedField<STBlob>;
using SF_VECTOR256 = TypedField<STVector256>;
using SF_XCHAIN_BRIDGE = TypedField<STXChainBridge>;
//------------------------------------------------------------------------------
@@ -332,6 +335,7 @@ extern SField const sfMetadata;
extern SF_UINT8 const sfCloseResolution;
extern SF_UINT8 const sfMethod;
extern SF_UINT8 const sfTransactionResult;
extern SF_UINT8 const sfWasLockingChainSend;
// 8-bit integers (uncommon)
extern SF_UINT8 const sfTickSize;
@@ -424,6 +428,9 @@ extern SF_UINT64 const sfHookOn;
extern SF_UINT64 const sfHookInstructionCount;
extern SF_UINT64 const sfHookReturnCode;
extern SF_UINT64 const sfReferenceCount;
extern SF_UINT64 const sfXChainClaimID;
extern SF_UINT64 const sfXChainAccountCreateCount;
extern SF_UINT64 const sfXChainAccountClaimCount;
// 128-bit
extern SF_UINT128 const sfEmailHash;
@@ -499,6 +506,8 @@ extern SF_AMOUNT const sfLPTokenIn;
extern SF_AMOUNT const sfBaseFeeDrops;
extern SF_AMOUNT const sfReserveBaseDrops;
extern SF_AMOUNT const sfReserveIncrementDrops;
extern SF_AMOUNT const sfSignatureReward;
extern SF_AMOUNT const sfMinAccountCreateAmount;
// variable length (common)
extern SF_VL const sfPublicKey;
@@ -541,6 +550,12 @@ extern SF_ACCOUNT const sfEmitCallback;
// account (uncommon)
extern SF_ACCOUNT const sfHookAccount;
extern SF_ACCOUNT const sfOtherChainSource;
extern SF_ACCOUNT const sfOtherChainDestination;
extern SF_ACCOUNT const sfAttestationSignerAccount;
extern SF_ACCOUNT const sfAttestationRewardAccount;
extern SF_ACCOUNT const sfLockingChainDoor;
extern SF_ACCOUNT const sfIssuingChainDoor;
// path set
extern SField const sfPaths;
@@ -548,6 +563,11 @@ extern SField const sfPaths;
// issue
extern SF_ISSUE const sfAsset;
extern SF_ISSUE const sfAsset2;
extern SF_ISSUE const sfLockingChainIssue;
extern SF_ISSUE const sfIssuingChainIssue;
// bridge
extern SF_XCHAIN_BRIDGE const sfXChainBridge;
// vector of 256-bit
extern SF_VECTOR256 const sfIndexes;
@@ -582,6 +602,10 @@ extern SField const sfHookExecution;
extern SField const sfHookDefinition;
extern SField const sfHookParameter;
extern SField const sfHookGrant;
extern SField const sfXChainClaimProofSig;
extern SField const sfXChainCreateAccountProofSig;
extern SField const sfXChainClaimAttestationCollectionElement;
extern SField const sfXChainCreateAccountAttestationCollectionElement;
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -604,6 +628,8 @@ extern SField const sfDisabledValidators;
extern SField const sfHookExecutions;
extern SField const sfHookParameters;
extern SField const sfHookGrants;
extern SField const sfXChainClaimAttestations;
extern SField const sfXChainCreateAccountAttestations;
//------------------------------------------------------------------------------

View File

@@ -64,7 +64,7 @@ public:
STAccount&
operator=(AccountID const& value);
AccountID
AccountID const&
value() const noexcept;
void
@@ -86,7 +86,7 @@ STAccount::operator=(AccountID const& value)
return *this;
}
inline AccountID
inline AccountID const&
STAccount::value() const noexcept
{
return value_;
@@ -99,6 +99,36 @@ STAccount::setValue(AccountID const& v)
default_ = false;
}
inline bool
operator==(STAccount const& lhs, STAccount const& rhs)
{
return lhs.value() == rhs.value();
}
inline auto
operator<(STAccount const& lhs, STAccount const& rhs)
{
return lhs.value() < rhs.value();
}
inline bool
operator==(STAccount const& lhs, AccountID const& rhs)
{
return lhs.value() == rhs;
}
inline auto
operator<(STAccount const& lhs, AccountID const& rhs)
{
return lhs.value() < rhs;
}
inline auto
operator<(AccountID const& lhs, STAccount const& rhs)
{
return lhs < rhs.value();
}
} // namespace ripple
#endif

View File

@@ -29,6 +29,7 @@
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/json_get_or_throw.h>
namespace ripple {
@@ -125,6 +126,8 @@ public:
explicit STAmount(std::uint64_t mantissa = 0, bool negative = false);
explicit STAmount(SField const& name, STAmount const& amt);
STAmount(
Issue const& issue,
std::uint64_t mantissa = 0,
@@ -582,4 +585,18 @@ private:
} // namespace ripple
//------------------------------------------------------------------------------
namespace Json {
template <>
inline ripple::STAmount
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
Json::StaticString const& key = field.getJsonName();
if (!v.isMember(key))
Throw<JsonMissingKeyError>(key);
Json::Value const& inner = v[key];
return amountFromJson(field, inner);
}
} // namespace Json
#endif

View File

@@ -61,7 +61,7 @@ public:
STArray&
operator=(STArray&&);
STArray(SField const& f, int n);
STArray(SField const& f, std::size_t n);
STArray(SerialIter& sit, SField const& f, int depth = 0);
explicit STArray(int n);
explicit STArray(SField const& f);

View File

@@ -100,6 +100,10 @@ STIssue::value() const noexcept
inline void
STIssue::setIssue(Issue const& issue)
{
if (isXRP(issue_.currency) != isXRP(issue_.account))
Throw<std::runtime_error>(
"invalid issue: currency and account native mismatch");
issue_ = issue;
}

View File

@@ -0,0 +1,235 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_PROTOCOL_STXCHAINBRIDGE_H_INCLUDED
#define RIPPLE_PROTOCOL_STXCHAINBRIDGE_H_INCLUDED
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STIssue.h>
namespace ripple {
class Serializer;
class STObject;
class STXChainBridge final : public STBase
{
STAccount lockingChainDoor_{sfLockingChainDoor};
STIssue lockingChainIssue_{sfLockingChainIssue};
STAccount issuingChainDoor_{sfIssuingChainDoor};
STIssue issuingChainIssue_{sfIssuingChainIssue};
public:
using value_type = STXChainBridge;
enum class ChainType { locking, issuing };
static ChainType
otherChain(ChainType ct);
static ChainType
srcChain(bool wasLockingChainSend);
static ChainType
dstChain(bool wasLockingChainSend);
STXChainBridge();
explicit STXChainBridge(SField const& name);
STXChainBridge(STXChainBridge const& rhs) = default;
STXChainBridge(STObject const& o);
STXChainBridge(
AccountID const& srcChainDoor,
Issue const& srcChainIssue,
AccountID const& dstChainDoor,
Issue const& dstChainIssue);
explicit STXChainBridge(Json::Value const& v);
explicit STXChainBridge(SField const& name, Json::Value const& v);
explicit STXChainBridge(SerialIter& sit, SField const& name);
STXChainBridge&
operator=(STXChainBridge const& rhs) = default;
std::string
getText() const override;
STObject
toSTObject() const;
AccountID const&
lockingChainDoor() const;
Issue const&
lockingChainIssue() const;
AccountID const&
issuingChainDoor() const;
Issue const&
issuingChainIssue() const;
AccountID const&
door(ChainType ct) const;
Issue const&
issue(ChainType ct) const;
SerializedTypeID
getSType() const override;
Json::Value getJson(JsonOptions) const override;
void
add(Serializer& s) const override;
bool
isEquivalent(const STBase& t) const override;
bool
isDefault() const override;
value_type const&
value() const noexcept;
private:
static std::unique_ptr<STXChainBridge>
construct(SerialIter&, SField const& name);
STBase*
copy(std::size_t n, void* buf) const override;
STBase*
move(std::size_t n, void* buf) override;
friend bool
operator==(STXChainBridge const& lhs, STXChainBridge const& rhs);
friend bool
operator<(STXChainBridge const& lhs, STXChainBridge const& rhs);
};
inline bool
operator==(STXChainBridge const& lhs, STXChainBridge const& rhs)
{
return std::tie(
lhs.lockingChainDoor_,
lhs.lockingChainIssue_,
lhs.issuingChainDoor_,
lhs.issuingChainIssue_) ==
std::tie(
rhs.lockingChainDoor_,
rhs.lockingChainIssue_,
rhs.issuingChainDoor_,
rhs.issuingChainIssue_);
}
inline bool
operator<(STXChainBridge const& lhs, STXChainBridge const& rhs)
{
return std::tie(
lhs.lockingChainDoor_,
lhs.lockingChainIssue_,
lhs.issuingChainDoor_,
lhs.issuingChainIssue_) <
std::tie(
rhs.lockingChainDoor_,
rhs.lockingChainIssue_,
rhs.issuingChainDoor_,
rhs.issuingChainIssue_);
}
inline AccountID const&
STXChainBridge::lockingChainDoor() const
{
return lockingChainDoor_.value();
};
inline Issue const&
STXChainBridge::lockingChainIssue() const
{
return lockingChainIssue_.value();
};
inline AccountID const&
STXChainBridge::issuingChainDoor() const
{
return issuingChainDoor_.value();
};
inline Issue const&
STXChainBridge::issuingChainIssue() const
{
return issuingChainIssue_.value();
};
inline STXChainBridge::value_type const&
STXChainBridge::value() const noexcept
{
return *this;
}
inline AccountID const&
STXChainBridge::door(ChainType ct) const
{
if (ct == ChainType::locking)
return lockingChainDoor();
return issuingChainDoor();
}
inline Issue const&
STXChainBridge::issue(ChainType ct) const
{
if (ct == ChainType::locking)
return lockingChainIssue();
return issuingChainIssue();
}
inline STXChainBridge::ChainType
STXChainBridge::otherChain(ChainType ct)
{
if (ct == ChainType::locking)
return ChainType::issuing;
return ChainType::locking;
}
inline STXChainBridge::ChainType
STXChainBridge::srcChain(bool wasLockingChainSend)
{
if (wasLockingChainSend)
return ChainType::locking;
return ChainType::issuing;
}
inline STXChainBridge::ChainType
STXChainBridge::dstChain(bool wasLockingChainSend)
{
if (wasLockingChainSend)
return ChainType::issuing;
return ChainType::locking;
}
} // namespace ripple
#endif

View File

@@ -125,6 +125,13 @@ enum TEMcodes : TERUnderlyingType {
temBAD_NFTOKEN_TRANSFER_FEE,
temBAD_AMM_TOKENS,
temXCHAIN_EQUAL_DOOR_ACCOUNTS,
temXCHAIN_BAD_PROOF,
temXCHAIN_BRIDGE_BAD_ISSUES,
temXCHAIN_BRIDGE_NONDOOR_OWNER,
temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT,
temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT,
};
//------------------------------------------------------------------------------
@@ -303,7 +310,24 @@ enum TECcodes : TERUnderlyingType {
tecAMM_EMPTY = 166,
tecAMM_NOT_EMPTY = 167,
tecAMM_ACCOUNT = 168,
tecINCOMPLETE = 169
tecINCOMPLETE = 169,
tecXCHAIN_BAD_TRANSFER_ISSUE = 170,
tecXCHAIN_NO_CLAIM_ID = 171,
tecXCHAIN_BAD_CLAIM_ID = 172,
tecXCHAIN_CLAIM_NO_QUORUM = 173,
tecXCHAIN_PROOF_UNKNOWN_KEY = 174,
tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE = 175,
tecXCHAIN_WRONG_CHAIN = 176,
tecXCHAIN_REWARD_MISMATCH = 177,
tecXCHAIN_NO_SIGNERS_LIST = 178,
tecXCHAIN_SENDING_ACCOUNT_MISMATCH = 179,
tecXCHAIN_INSUFF_CREATE_AMOUNT = 180,
tecXCHAIN_ACCOUNT_CREATE_PAST = 181,
tecXCHAIN_ACCOUNT_CREATE_TOO_MANY = 182,
tecXCHAIN_PAYMENT_FAILED = 183,
tecXCHAIN_SELF_COMMIT = 184,
tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR = 185,
tecXCHAIN_CREATE_ACCOUNT_DISABLED = 186,
};
//------------------------------------------------------------------------------

View File

@@ -181,6 +181,10 @@ constexpr std::uint32_t tfDepositSubTx =
constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx);
constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx);
// BridgeModify flags:
constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000;
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
// clang-format on
} // namespace ripple

View File

@@ -160,6 +160,31 @@ enum TxType : std::uint16_t
/** This transaction type deletes AMM in the empty state */
ttAMM_DELETE = 40,
/** This transactions creates a crosschain sequence number */
ttXCHAIN_CREATE_CLAIM_ID = 41,
/** This transactions initiates a crosschain transaction */
ttXCHAIN_COMMIT = 42,
/** This transaction completes a crosschain transaction */
ttXCHAIN_CLAIM = 43,
/** This transaction initiates a crosschain account create transaction */
ttXCHAIN_ACCOUNT_CREATE_COMMIT = 44,
/** This transaction adds an attestation to a claimid*/
ttXCHAIN_ADD_CLAIM_ATTESTATION = 45,
/** This transaction adds an attestation to a claimid*/
ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION = 46,
/** This transaction modifies a sidechain */
ttXCHAIN_MODIFY_BRIDGE = 47,
/** This transactions creates a sidechain */
ttXCHAIN_CREATE_BRIDGE = 48,
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -0,0 +1,514 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_PROTOCOL_STXATTESTATIONS_H_INCLUDED
#define RIPPLE_PROTOCOL_STXATTESTATIONS_H_INCLUDED
#include <ripple/basics/Buffer.h>
#include <ripple/basics/Expected.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/TER.h>
#include <boost/container/flat_set.hpp>
#include <boost/container/vector.hpp>
#include <cstddef>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace ripple {
namespace Attestations {
struct AttestationBase
{
// Account associated with the public key
AccountID attestationSignerAccount;
// Public key from the witness server attesting to the event
PublicKey publicKey;
// Signature from the witness server attesting to the event
Buffer signature;
// Account on the sending chain that triggered the event (sent the
// transaction)
AccountID sendingAccount;
// Amount transfered on the sending chain
STAmount sendingAmount;
// Account on the destination chain that collects a share of the attestation
// reward
AccountID rewardAccount;
// Amount was transfered on the locking chain
bool wasLockingChainSend;
explicit AttestationBase(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_);
AttestationBase(AttestationBase const&) = default;
virtual ~AttestationBase() = default;
AttestationBase&
operator=(AttestationBase const&) = default;
// verify that the signature attests to the data.
bool
verify(STXChainBridge const& bridge) const;
protected:
explicit AttestationBase(STObject const& o);
explicit AttestationBase(Json::Value const& v);
[[nodiscard]] static bool
equalHelper(AttestationBase const& lhs, AttestationBase const& rhs);
[[nodiscard]] static bool
sameEventHelper(AttestationBase const& lhs, AttestationBase const& rhs);
void
addHelper(STObject& o) const;
private:
[[nodiscard]] virtual std::vector<std::uint8_t>
message(STXChainBridge const& bridge) const = 0;
};
// Attest to a regular cross-chain transfer
struct AttestationClaim : AttestationBase
{
std::uint64_t claimID;
std::optional<AccountID> dst;
explicit AttestationClaim(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t claimID_,
std::optional<AccountID> const& dst_);
explicit AttestationClaim(
STXChainBridge const& bridge,
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
SecretKey const& secretKey_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t claimID_,
std::optional<AccountID> const& dst_);
explicit AttestationClaim(STObject const& o);
explicit AttestationClaim(Json::Value const& v);
[[nodiscard]] STObject
toSTObject() const;
// return true if the two attestations attest to the same thing
[[nodiscard]] bool
sameEvent(AttestationClaim const& rhs) const;
[[nodiscard]] static std::vector<std::uint8_t>
message(
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<AccountID> const& dst);
[[nodiscard]] bool
validAmounts() const;
private:
[[nodiscard]] std::vector<std::uint8_t>
message(STXChainBridge const& bridge) const override;
friend bool
operator==(AttestationClaim const& lhs, AttestationClaim const& rhs);
};
struct CmpByClaimID
{
bool
operator()(AttestationClaim const& lhs, AttestationClaim const& rhs) const
{
return lhs.claimID < rhs.claimID;
}
};
// Attest to a cross-chain transfer that creates an account
struct AttestationCreateAccount : AttestationBase
{
// createCount on the sending chain. This is the value of the `CreateCount`
// field of the bridge on the sending chain when the transaction was
// executed.
std::uint64_t createCount;
// Account to create on the destination chain
AccountID toCreate;
// Total amount of the reward pool
STAmount rewardAmount;
explicit AttestationCreateAccount(STObject const& o);
explicit AttestationCreateAccount(Json::Value const& v);
explicit AttestationCreateAccount(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t createCount_,
AccountID const& toCreate_);
explicit AttestationCreateAccount(
STXChainBridge const& bridge,
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
SecretKey const& secretKey_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t createCount_,
AccountID const& toCreate_);
[[nodiscard]] STObject
toSTObject() const;
// return true if the two attestations attest to the same thing
[[nodiscard]] bool
sameEvent(AttestationCreateAccount const& rhs) const;
friend bool
operator==(
AttestationCreateAccount const& lhs,
AttestationCreateAccount const& rhs);
[[nodiscard]] static std::vector<std::uint8_t>
message(
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
STAmount const& rewardAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
AccountID const& dst);
[[nodiscard]] bool
validAmounts() const;
private:
[[nodiscard]] std::vector<std::uint8_t>
message(STXChainBridge const& bridge) const override;
};
struct CmpByCreateCount
{
bool
operator()(
AttestationCreateAccount const& lhs,
AttestationCreateAccount const& rhs) const
{
return lhs.createCount < rhs.createCount;
}
};
}; // namespace Attestations
// Result when checking when two attestation match.
enum class AttestationMatch {
// One of the fields doesn't match, and it isn't the dst field
nonDstMismatch,
// all of the fields match, except the dst field
matchExceptDst,
// all of the fields match
match
};
struct XChainClaimAttestation
{
using TSignedAttestation = Attestations::AttestationClaim;
static SField const& ArrayFieldName;
AccountID keyAccount;
PublicKey publicKey;
STAmount amount;
AccountID rewardAccount;
bool wasLockingChainSend;
std::optional<AccountID> dst;
struct MatchFields
{
STAmount amount;
bool wasLockingChainSend;
std::optional<AccountID> dst;
MatchFields(TSignedAttestation const& att);
MatchFields(
STAmount const& a,
bool b,
std::optional<AccountID> const& d)
: amount{a}, wasLockingChainSend{b}, dst{d}
{
}
};
explicit XChainClaimAttestation(
AccountID const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::optional<AccountID> const& dst);
explicit XChainClaimAttestation(
STAccount const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
STAccount const& rewardAccount_,
bool wasLockingChainSend_,
std::optional<STAccount> const& dst);
explicit XChainClaimAttestation(TSignedAttestation const& claimAtt);
explicit XChainClaimAttestation(STObject const& o);
explicit XChainClaimAttestation(Json::Value const& v);
AttestationMatch
match(MatchFields const& rhs) const;
[[nodiscard]] STObject
toSTObject() const;
friend bool
operator==(
XChainClaimAttestation const& lhs,
XChainClaimAttestation const& rhs);
};
struct XChainCreateAccountAttestation
{
using TSignedAttestation = Attestations::AttestationCreateAccount;
static SField const& ArrayFieldName;
AccountID keyAccount;
PublicKey publicKey;
STAmount amount;
STAmount rewardAmount;
AccountID rewardAccount;
bool wasLockingChainSend;
AccountID dst;
struct MatchFields
{
STAmount amount;
STAmount rewardAmount;
bool wasLockingChainSend;
AccountID dst;
MatchFields(TSignedAttestation const& att);
};
explicit XChainCreateAccountAttestation(
AccountID const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
AccountID const& dst_);
explicit XChainCreateAccountAttestation(TSignedAttestation const& claimAtt);
explicit XChainCreateAccountAttestation(STObject const& o);
explicit XChainCreateAccountAttestation(Json::Value const& v);
[[nodiscard]] STObject
toSTObject() const;
AttestationMatch
match(MatchFields const& rhs) const;
friend bool
operator==(
XChainCreateAccountAttestation const& lhs,
XChainCreateAccountAttestation const& rhs);
};
// Attestations from witness servers for a particular claimid and bridge.
// Only one attestation per signature is allowed.
template <class TAttestation>
class XChainAttestationsBase
{
public:
using AttCollection = std::vector<TAttestation>;
private:
// Set a max number of allowed attestations to limit the amount of memory
// allocated and processing time. This number is much larger than the actual
// number of attestation a server would ever expect.
static constexpr std::uint32_t maxAttestations = 256;
AttCollection attestations_;
protected:
// Prevent slicing to the base class
~XChainAttestationsBase() = default;
public:
XChainAttestationsBase() = default;
XChainAttestationsBase(XChainAttestationsBase const& rhs) = default;
XChainAttestationsBase&
operator=(XChainAttestationsBase const& rhs) = default;
explicit XChainAttestationsBase(AttCollection&& sigs);
explicit XChainAttestationsBase(Json::Value const& v);
explicit XChainAttestationsBase(STArray const& arr);
[[nodiscard]] STArray
toSTArray() const;
typename AttCollection::const_iterator
begin() const;
typename AttCollection::const_iterator
end() const;
typename AttCollection::iterator
begin();
typename AttCollection::iterator
end();
template <class F>
std::size_t
erase_if(F&& f);
std::size_t
size() const;
bool
empty() const;
AttCollection const&
attestations() const;
template <class T>
void
emplace_back(T&& att);
// verify that all the signatures attest to transaction data.
[[nodiscard]] bool
verify() const;
protected:
// Return the message that was expected to be signed by the attesters given
// the data to be proved.
[[nodiscard]] std::vector<std::uint8_t>
message() const;
};
template <class TAttestation>
[[nodiscard]] inline bool
operator==(
XChainAttestationsBase<TAttestation> const& lhs,
XChainAttestationsBase<TAttestation> const& rhs)
{
return lhs.attestations() == rhs.attestations();
}
template <class TAttestation>
inline typename XChainAttestationsBase<TAttestation>::AttCollection const&
XChainAttestationsBase<TAttestation>::attestations() const
{
return attestations_;
};
template <class TAttestation>
template <class T>
inline void
XChainAttestationsBase<TAttestation>::emplace_back(T&& att)
{
attestations_.emplace_back(std::forward<T>(att));
};
template <class TAttestation>
template <class F>
inline std::size_t
XChainAttestationsBase<TAttestation>::erase_if(F&& f)
{
return std::erase_if(attestations_, std::forward<F>(f));
}
template <class TAttestation>
inline std::size_t
XChainAttestationsBase<TAttestation>::size() const
{
return attestations_.size();
}
template <class TAttestation>
inline bool
XChainAttestationsBase<TAttestation>::empty() const
{
return attestations_.empty();
}
class XChainClaimAttestations final
: public XChainAttestationsBase<XChainClaimAttestation>
{
using TBase = XChainAttestationsBase<XChainClaimAttestation>;
using TBase::TBase;
};
class XChainCreateAccountAttestations final
: public XChainAttestationsBase<XChainCreateAccountAttestation>
{
using TBase = XChainAttestationsBase<XChainCreateAccountAttestation>;
using TBase::TBase;
};
} // namespace ripple
#endif // STXCHAINATTESTATIONS_H_

View File

@@ -455,6 +455,7 @@ REGISTER_FIX (fixNFTokenRemint, Supported::yes, VoteBehavior::De
REGISTER_FIX (fixReducedOffersV1, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo);
// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.

View File

@@ -18,9 +18,13 @@
//==============================================================================
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/SeqProxy.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/nftPageMask.h>
#include <algorithm>
#include <cassert>
@@ -64,6 +68,9 @@ enum class LedgerNameSpace : std::uint16_t {
NFTOKEN_BUY_OFFERS = 'h',
NFTOKEN_SELL_OFFERS = 'i',
AMM = 'A',
BRIDGE = 'H',
XCHAIN_CLAIM_ID = 'Q',
XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K',
// No longer used or supported. Left here to reserve the space
// to avoid accidental reuse.
@@ -389,6 +396,47 @@ amm(uint256 const& id) noexcept
return {ltAMM, id};
}
Keylet
bridge(STXChainBridge const& bridge, STXChainBridge::ChainType chainType)
{
// A door account can support multiple bridges. On the locking chain
// there can only be one bridge per lockingChainCurrency. On the issuing
// chain there can only be one bridge per issuingChainCurrency.
auto const& issue = bridge.issue(chainType);
return {
ltBRIDGE,
indexHash(
LedgerNameSpace::BRIDGE, bridge.door(chainType), issue.currency)};
}
Keylet
xChainClaimID(STXChainBridge const& bridge, std::uint64_t seq)
{
return {
ltXCHAIN_OWNED_CLAIM_ID,
indexHash(
LedgerNameSpace::XCHAIN_CLAIM_ID,
bridge.lockingChainDoor(),
bridge.lockingChainIssue(),
bridge.issuingChainDoor(),
bridge.issuingChainIssue(),
seq)};
}
Keylet
xChainCreateAccountClaimID(STXChainBridge const& bridge, std::uint64_t seq)
{
return {
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
indexHash(
LedgerNameSpace::XCHAIN_CREATE_ACCOUNT_CLAIM_ID,
bridge.lockingChainDoor(),
bridge.lockingChainIssue(),
bridge.issuingChainDoor(),
bridge.issuingChainIssue(),
seq)};
}
} // namespace keylet
} // namespace ripple

View File

@@ -18,6 +18,8 @@
//==============================================================================
#include <ripple/protocol/InnerObjectFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/SOTemplate.h>
namespace ripple {
@@ -70,12 +72,62 @@ InnerObjectFormats::InnerObjectFormats()
add(sfAuctionSlot.jsonName.c_str(),
sfAuctionSlot.getCode(),
{{sfAccount, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfDiscountedFee, soeDEFAULT},
{sfPrice, soeREQUIRED},
{sfAuthAccounts, soeOPTIONAL}});
add(sfXChainClaimAttestationCollectionElement.jsonName.c_str(),
sfXChainClaimAttestationCollectionElement.getCode(),
{
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSignature, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAccount, soeREQUIRED},
{sfExpiration, soeREQUIRED},
{sfDiscountedFee, soeDEFAULT},
{sfPrice, soeREQUIRED},
{sfAuthAccounts, soeOPTIONAL},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfDestination, soeOPTIONAL},
});
add(sfXChainCreateAccountAttestationCollectionElement.jsonName.c_str(),
sfXChainCreateAccountAttestationCollectionElement.getCode(),
{
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSignature, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAccount, soeREQUIRED},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfXChainAccountCreateCount, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
});
add(sfXChainClaimProofSig.jsonName.c_str(),
sfXChainClaimProofSig.getCode(),
{
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfDestination, soeOPTIONAL},
});
add(sfXChainCreateAccountProofSig.jsonName.c_str(),
sfXChainCreateAccountProofSig.getCode(),
{
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfDestination, soeREQUIRED},
});
add(sfAuthAccount.jsonName.c_str(),

View File

@@ -79,8 +79,8 @@ issueFromJson(Json::Value const& v)
{
if (!v.isObject())
{
Throw<Json::error>(
"issueFromJson can only be specified with a 'object' Json value");
Throw<std::runtime_error>(
"issueFromJson can only be specified with an 'object' Json value");
}
Json::Value const curStr = v[jss::currency];

View File

@@ -283,6 +283,49 @@ LedgerFormats::LedgerFormats()
},
commonFields);
add(jss::Bridge,
ltBRIDGE,
{
{sfAccount, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
{sfMinAccountCreateAmount, soeOPTIONAL},
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfXChainAccountCreateCount, soeREQUIRED},
{sfXChainAccountClaimCount, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
add(jss::XChainOwnedClaimID,
ltXCHAIN_OWNED_CLAIM_ID,
{
{sfAccount, soeREQUIRED},
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfOtherChainSource, soeREQUIRED},
{sfXChainClaimAttestations, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
add(jss::XChainOwnedCreateAccountClaimID,
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
{
{sfAccount, soeREQUIRED},
{sfXChainBridge, soeREQUIRED},
{sfXChainAccountCreateCount, soeREQUIRED},
{sfXChainCreateAccountAttestations, soeREQUIRED},
{sfOwnerNode, soeREQUIRED},
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED}
},
commonFields);
// clang-format on
}

View File

@@ -96,6 +96,7 @@ CONSTRUCT_TYPED_SFIELD(sfTransactionResult, "TransactionResult", UINT8,
CONSTRUCT_TYPED_SFIELD(sfTickSize, "TickSize", UINT8, 16);
CONSTRUCT_TYPED_SFIELD(sfUNLModifyDisabling, "UNLModifyDisabling", UINT8, 17);
CONSTRUCT_TYPED_SFIELD(sfHookResult, "HookResult", UINT8, 18);
CONSTRUCT_TYPED_SFIELD(sfWasLockingChainSend, "WasLockingChainSend", UINT8, 19);
// 16-bit integers
CONSTRUCT_TYPED_SFIELD(sfLedgerEntryType, "LedgerEntryType", UINT16, 1, SField::sMD_Never);
@@ -180,10 +181,13 @@ CONSTRUCT_TYPED_SFIELD(sfNFTokenOfferNode, "NFTokenOfferNode", UINT64,
CONSTRUCT_TYPED_SFIELD(sfEmitBurden, "EmitBurden", UINT64, 13);
// 64-bit integers (uncommon)
CONSTRUCT_TYPED_SFIELD(sfHookOn, "HookOn", UINT64, 16);
CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17);
CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18);
CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19);
CONSTRUCT_TYPED_SFIELD(sfHookOn, "HookOn", UINT64, 16);
CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17);
CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18);
CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19);
CONSTRUCT_TYPED_SFIELD(sfXChainClaimID, "XChainClaimID", UINT64, 20);
CONSTRUCT_TYPED_SFIELD(sfXChainAccountCreateCount, "XChainAccountCreateCount", UINT64, 21);
CONSTRUCT_TYPED_SFIELD(sfXChainAccountClaimCount, "XChainAccountClaimCount", UINT64, 22);
// 128-bit
CONSTRUCT_TYPED_SFIELD(sfEmailHash, "EmailHash", UINT128, 1);
@@ -263,7 +267,8 @@ CONSTRUCT_TYPED_SFIELD(sfLPTokenOut, "LPTokenOut", AMOUNT,
CONSTRUCT_TYPED_SFIELD(sfLPTokenIn, "LPTokenIn", AMOUNT, 26);
CONSTRUCT_TYPED_SFIELD(sfEPrice, "EPrice", AMOUNT, 27);
CONSTRUCT_TYPED_SFIELD(sfPrice, "Price", AMOUNT, 28);
// 29 and 30 are reserved for side-chains
CONSTRUCT_TYPED_SFIELD(sfSignatureReward, "SignatureReward", AMOUNT, 29);
CONSTRUCT_TYPED_SFIELD(sfMinAccountCreateAmount, "MinAccountCreateAmount", AMOUNT, 30);
CONSTRUCT_TYPED_SFIELD(sfLPTokenBalance, "LPTokenBalance", AMOUNT, 31);
// variable length (common)
@@ -308,6 +313,12 @@ CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT,
// account (uncommon)
CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
CONSTRUCT_TYPED_SFIELD(sfOtherChainSource, "OtherChainSource", ACCOUNT, 18);
CONSTRUCT_TYPED_SFIELD(sfOtherChainDestination, "OtherChainDestination",ACCOUNT, 19);
CONSTRUCT_TYPED_SFIELD(sfAttestationSignerAccount, "AttestationSignerAccount", ACCOUNT, 20);
CONSTRUCT_TYPED_SFIELD(sfAttestationRewardAccount, "AttestationRewardAccount", ACCOUNT, 21);
CONSTRUCT_TYPED_SFIELD(sfLockingChainDoor, "LockingChainDoor", ACCOUNT, 22);
CONSTRUCT_TYPED_SFIELD(sfIssuingChainDoor, "IssuingChainDoor", ACCOUNT, 23);
// vector of 256-bit
CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
@@ -319,9 +330,14 @@ CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR25
CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1);
// issue
CONSTRUCT_TYPED_SFIELD(sfLockingChainIssue, "LockingChainIssue", ISSUE, 1);
CONSTRUCT_TYPED_SFIELD(sfIssuingChainIssue, "IssuingChainIssue", ISSUE, 2);
CONSTRUCT_TYPED_SFIELD(sfAsset, "Asset", ISSUE, 3);
CONSTRUCT_TYPED_SFIELD(sfAsset2, "Asset2", ISSUE, 4);
// Bridge
CONSTRUCT_TYPED_SFIELD(sfXChainBridge, "XChainBridge", XCHAIN_BRIDGE,
1);
// inner object
// OBJECT/1 is reserved for end of object
CONSTRUCT_UNTYPED_SFIELD(sfTransactionMetaData, "TransactionMetaData", OBJECT, 2);
@@ -351,6 +367,16 @@ CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT,
CONSTRUCT_UNTYPED_SFIELD(sfVoteEntry, "VoteEntry", OBJECT, 25);
CONSTRUCT_UNTYPED_SFIELD(sfAuctionSlot, "AuctionSlot", OBJECT, 26);
CONSTRUCT_UNTYPED_SFIELD(sfAuthAccount, "AuthAccount", OBJECT, 27);
CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimProofSig, "XChainClaimProofSig", OBJECT, 28);
CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountProofSig,
"XChainCreateAccountProofSig",
OBJECT, 29);
CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement,
"XChainClaimAttestationCollectionElement",
OBJECT, 30);
CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement,
"XChainCreateAccountAttestationCollectionElement",
OBJECT, 31);
// array of objects
// ARRAY/1 is reserved for end of array
@@ -372,7 +398,13 @@ CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY,
CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
// 21-24 is reserved for side-chains
CONSTRUCT_UNTYPED_SFIELD(sfXChainClaimAttestations,
"XChainClaimAttestations",
ARRAY, 21);
CONSTRUCT_UNTYPED_SFIELD(sfXChainCreateAccountAttestations,
"XChainCreateAccountAttestations",
ARRAY, 22);
// 23 and 24 are unused and available for use
CONSTRUCT_UNTYPED_SFIELD(sfAuthAccounts, "AuthAccounts", ARRAY, 25);
// clang-format on

View File

@@ -239,6 +239,17 @@ STAmount::STAmount(
canonicalize();
}
STAmount::STAmount(SField const& name, STAmount const& from)
: STBase(name)
, mIssue(from.mIssue)
, mValue(from.mValue)
, mOffset(from.mOffset)
, mIsNegative(from.mIsNegative)
{
assert(mValue <= std::numeric_limits<std::int64_t>::max());
canonicalize();
}
//------------------------------------------------------------------------------
STAmount::STAmount(std::uint64_t mantissa, bool negative)

View File

@@ -46,7 +46,7 @@ STArray::STArray(SField const& f) : STBase(f)
{
}
STArray::STArray(SField const& f, int n) : STBase(f)
STArray::STArray(SField const& f, std::size_t n) : STBase(f)
{
v_.reserve(n);
}

View File

@@ -19,7 +19,18 @@
#include <ripple/protocol/STIssue.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/contract.h>
#include <ripple/protocol/SystemParameters.h>
#include <ripple/protocol/jss.h>
#include <boost/algorithm/string.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/regex.hpp>
#include <iostream>
#include <iterator>
#include <memory>
namespace ripple {

View File

@@ -23,6 +23,7 @@
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STArray.h>
@@ -33,10 +34,13 @@
#include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/STPathSet.h>
#include <ripple/protocol/STVector256.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/TER.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/XChainAttestations.h>
#include <ripple/protocol/impl/STVar.h>
#include <cassert>
#include <charconv>
#include <memory>
@@ -742,6 +746,20 @@ parseLeaf(
return ret;
}
break;
case STI_XCHAIN_BRIDGE:
try
{
ret = detail::make_stvar<STXChainBridge>(
STXChainBridge(field, value));
}
catch (std::exception const&)
{
error = invalid_data(json_name, fieldName);
return ret;
}
break;
default:
error = bad_type(json_name, fieldName);
return ret;
@@ -951,6 +969,7 @@ parseArray(
if (ret->getFName().fieldType != STI_OBJECT)
{
ss << "Field type: " << ret->getFName().fieldType << " ";
error = non_object_in_array(ss.str(), i);
return std::nullopt;
}

View File

@@ -17,6 +17,8 @@
*/
//==============================================================================
#include <ripple/protocol/impl/STVar.h>
#include <ripple/basics/contract.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STAmount.h>
@@ -29,7 +31,8 @@
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/STPathSet.h>
#include <ripple/protocol/STVector256.h>
#include <ripple/protocol/impl/STVar.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/XChainAttestations.h>
namespace ripple {
namespace detail {
@@ -161,6 +164,9 @@ STVar::STVar(SerialIter& sit, SField const& name, int depth)
case STI_ISSUE:
construct<STIssue>(sit, name);
return;
case STI_XCHAIN_BRIDGE:
construct<STXChainBridge>(sit, name);
return;
default:
Throw<std::runtime_error>("Unknown object type");
}
@@ -219,6 +225,9 @@ STVar::STVar(SerializedTypeID id, SField const& name)
case STI_ISSUE:
construct<STIssue>(name);
return;
case STI_XCHAIN_BRIDGE:
construct<STXChainBridge>(name);
return;
default:
Throw<std::runtime_error>("Unknown object type");
}

View File

@@ -125,7 +125,7 @@ private:
void
construct(Args&&... args)
{
if (sizeof(T) > max_size)
if constexpr (sizeof(T) > max_size)
p_ = new T(std::forward<Args>(args)...);
else
p_ = new (&d_) T(std::forward<Args>(args)...);

View File

@@ -0,0 +1,227 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/tokens.h>
#include <boost/format.hpp>
namespace ripple {
STXChainBridge::STXChainBridge() : STBase{sfXChainBridge}
{
}
STXChainBridge::STXChainBridge(SField const& name) : STBase{name}
{
}
STXChainBridge::STXChainBridge(
AccountID const& srcChainDoor,
Issue const& srcChainIssue,
AccountID const& dstChainDoor,
Issue const& dstChainIssue)
: STBase{sfXChainBridge}
, lockingChainDoor_{sfLockingChainDoor, srcChainDoor}
, lockingChainIssue_{sfLockingChainIssue, srcChainIssue}
, issuingChainDoor_{sfIssuingChainDoor, dstChainDoor}
, issuingChainIssue_{sfIssuingChainIssue, dstChainIssue}
{
}
STXChainBridge::STXChainBridge(STObject const& o)
: STBase{sfXChainBridge}
, lockingChainDoor_{sfLockingChainDoor, o[sfLockingChainDoor]}
, lockingChainIssue_{sfLockingChainIssue, o[sfLockingChainIssue]}
, issuingChainDoor_{sfIssuingChainDoor, o[sfIssuingChainDoor]}
, issuingChainIssue_{sfIssuingChainIssue, o[sfIssuingChainIssue]}
{
}
STXChainBridge::STXChainBridge(Json::Value const& v)
: STXChainBridge{sfXChainBridge, v}
{
}
STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v)
: STBase{name}
{
if (!v.isObject())
{
Throw<std::runtime_error>(
"STXChainBridge can only be specified with a 'object' Json value");
}
auto checkExtra = [](Json::Value const& v) {
static auto const jbridge =
ripple::STXChainBridge().getJson(ripple::JsonOptions::none);
for (auto it = v.begin(); it != v.end(); ++it)
{
std::string const name = it.memberName();
if (!jbridge.isMember(name))
{
Throw<std::runtime_error>(
"STXChainBridge extra field detected: " + name);
}
}
return true;
};
checkExtra(v);
Json::Value const& lockingChainDoorStr =
v[sfLockingChainDoor.getJsonName()];
Json::Value const& lockingChainIssue = v[sfLockingChainIssue.getJsonName()];
Json::Value const& issuingChainDoorStr =
v[sfIssuingChainDoor.getJsonName()];
Json::Value const& issuingChainIssue = v[sfIssuingChainIssue.getJsonName()];
if (!lockingChainDoorStr.isString())
{
Throw<std::runtime_error>(
"STXChainBridge LockingChainDoor must be a string Json value");
}
if (!issuingChainDoorStr.isString())
{
Throw<std::runtime_error>(
"STXChainBridge IssuingChainDoor must be a string Json value");
}
auto const lockingChainDoor =
parseBase58<AccountID>(lockingChainDoorStr.asString());
auto const issuingChainDoor =
parseBase58<AccountID>(issuingChainDoorStr.asString());
if (!lockingChainDoor)
{
Throw<std::runtime_error>(
"STXChainBridge LockingChainDoor must be a valid account");
}
if (!issuingChainDoor)
{
Throw<std::runtime_error>(
"STXChainBridge IssuingChainDoor must be a valid account");
}
lockingChainDoor_ = STAccount{sfLockingChainDoor, *lockingChainDoor};
lockingChainIssue_ =
STIssue{sfLockingChainIssue, issueFromJson(lockingChainIssue)};
issuingChainDoor_ = STAccount{sfIssuingChainDoor, *issuingChainDoor};
issuingChainIssue_ =
STIssue{sfIssuingChainIssue, issueFromJson(issuingChainIssue)};
}
STXChainBridge::STXChainBridge(SerialIter& sit, SField const& name)
: STBase{name}
, lockingChainDoor_{sit, sfLockingChainDoor}
, lockingChainIssue_{sit, sfLockingChainIssue}
, issuingChainDoor_{sit, sfIssuingChainDoor}
, issuingChainIssue_{sit, sfIssuingChainIssue}
{
}
void
STXChainBridge::add(Serializer& s) const
{
lockingChainDoor_.add(s);
lockingChainIssue_.add(s);
issuingChainDoor_.add(s);
issuingChainIssue_.add(s);
}
Json::Value
STXChainBridge::getJson(JsonOptions jo) const
{
Json::Value v;
v[sfLockingChainDoor.getJsonName()] = lockingChainDoor_.getJson(jo);
v[sfLockingChainIssue.getJsonName()] = lockingChainIssue_.getJson(jo);
v[sfIssuingChainDoor.getJsonName()] = issuingChainDoor_.getJson(jo);
v[sfIssuingChainIssue.getJsonName()] = issuingChainIssue_.getJson(jo);
return v;
}
std::string
STXChainBridge::getText() const
{
return str(
boost::format("{ %s = %s, %s = %s, %s = %s, %s = %s }") %
sfLockingChainDoor.getName() % lockingChainDoor_.getText() %
sfLockingChainIssue.getName() % lockingChainIssue_.getText() %
sfIssuingChainDoor.getName() % issuingChainDoor_.getText() %
sfIssuingChainIssue.getName() % issuingChainIssue_.getText());
}
STObject
STXChainBridge::toSTObject() const
{
STObject o{sfXChainBridge};
o[sfLockingChainDoor] = lockingChainDoor_;
o[sfLockingChainIssue] = lockingChainIssue_;
o[sfIssuingChainDoor] = issuingChainDoor_;
o[sfIssuingChainIssue] = issuingChainIssue_;
return o;
}
SerializedTypeID
STXChainBridge::getSType() const
{
return STI_XCHAIN_BRIDGE;
}
bool
STXChainBridge::isEquivalent(const STBase& t) const
{
const STXChainBridge* v = dynamic_cast<const STXChainBridge*>(&t);
return v && (*v == *this);
}
bool
STXChainBridge::isDefault() const
{
return lockingChainDoor_.isDefault() && lockingChainIssue_.isDefault() &&
issuingChainDoor_.isDefault() && issuingChainIssue_.isDefault();
}
std::unique_ptr<STXChainBridge>
STXChainBridge::construct(SerialIter& sit, SField const& name)
{
return std::make_unique<STXChainBridge>(sit, name);
}
STBase*
STXChainBridge::copy(std::size_t n, void* buf) const
{
return emplace(n, buf, *this);
}
STBase*
STXChainBridge::move(std::size_t n, void* buf)
{
return emplace(n, buf, std::move(*this));
}
} // namespace ripple

View File

@@ -96,6 +96,23 @@ transResults()
MAKE_ERROR(tecOBJECT_NOT_FOUND, "A requested object could not be located."),
MAKE_ERROR(tecINSUFFICIENT_PAYMENT, "The payment is not sufficient."),
MAKE_ERROR(tecINCOMPLETE, "Some work was completed, but more submissions required to finish."),
MAKE_ERROR(tecXCHAIN_BAD_TRANSFER_ISSUE, "Bad xchain transfer issue."),
MAKE_ERROR(tecXCHAIN_NO_CLAIM_ID, "No such xchain claim id."),
MAKE_ERROR(tecXCHAIN_BAD_CLAIM_ID, "Bad xchain claim id."),
MAKE_ERROR(tecXCHAIN_CLAIM_NO_QUORUM, "Quorum was not reached on the xchain claim."),
MAKE_ERROR(tecXCHAIN_PROOF_UNKNOWN_KEY, "Unknown key for the xchain proof."),
MAKE_ERROR(tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE, "Only XRP may be used for xchain create account."),
MAKE_ERROR(tecXCHAIN_WRONG_CHAIN, "XChain Transaction was submitted to the wrong chain."),
MAKE_ERROR(tecXCHAIN_REWARD_MISMATCH, "The reward amount must match the reward specified in the xchain bridge."),
MAKE_ERROR(tecXCHAIN_NO_SIGNERS_LIST, "The account did not have a signers list."),
MAKE_ERROR(tecXCHAIN_SENDING_ACCOUNT_MISMATCH,"The sending account did not match the expected sending account."),
MAKE_ERROR(tecXCHAIN_INSUFF_CREATE_AMOUNT, "Insufficient amount to create an account."),
MAKE_ERROR(tecXCHAIN_ACCOUNT_CREATE_PAST, "The account create count has already passed."),
MAKE_ERROR(tecXCHAIN_ACCOUNT_CREATE_TOO_MANY, "There are too many pending account create transactions to submit a new one."),
MAKE_ERROR(tecXCHAIN_PAYMENT_FAILED, "Failed to transfer funds in a xchain transaction."),
MAKE_ERROR(tecXCHAIN_SELF_COMMIT, "Account cannot commit funds to itself."),
MAKE_ERROR(tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR, "Bad public key account pair in an xchain transaction."),
MAKE_ERROR(tecXCHAIN_CREATE_ACCOUNT_DISABLED, "This bridge does not support account creation."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -175,6 +192,12 @@ transResults()
MAKE_ERROR(temINVALID_COUNT, "Malformed: Count field outside valid range."),
MAKE_ERROR(temSEQ_AND_TICKET, "Transaction contains a TicketSequence and a non-zero Sequence."),
MAKE_ERROR(temBAD_NFTOKEN_TRANSFER_FEE, "Malformed: The NFToken transfer fee must be between 1 and 5000, inclusive."),
MAKE_ERROR(temXCHAIN_EQUAL_DOOR_ACCOUNTS, "Malformed: Bridge must have unique door accounts."),
MAKE_ERROR(temXCHAIN_BAD_PROOF, "Malformed: Bad cross-chain claim proof."),
MAKE_ERROR(temXCHAIN_BRIDGE_BAD_ISSUES, "Malformed: Bad bridge issues."),
MAKE_ERROR(temXCHAIN_BRIDGE_NONDOOR_OWNER, "Malformed: Bridge owner must be one of the door accounts."),
MAKE_ERROR(temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT, "Malformed: Bad min account create amount."),
MAKE_ERROR(temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, "Malformed: Bad reward amount."),
MAKE_ERROR(terRETRY, "Retry transaction."),
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),

View File

@@ -18,6 +18,9 @@
//==============================================================================
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/SOTemplate.h>
#include <ripple/protocol/jss.h>
namespace ripple {
@@ -390,6 +393,101 @@ TxFormats::TxFormats()
{sfAmount, soeREQUIRED},
},
commonFields);
add(jss::XChainCreateBridge,
ttXCHAIN_CREATE_BRIDGE,
{
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
{sfMinAccountCreateAmount, soeOPTIONAL},
},
commonFields);
add(jss::XChainModifyBridge,
ttXCHAIN_MODIFY_BRIDGE,
{
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeOPTIONAL},
{sfMinAccountCreateAmount, soeOPTIONAL},
},
commonFields);
add(jss::XChainCreateClaimID,
ttXCHAIN_CREATE_CLAIM_ID,
{
{sfXChainBridge, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
{sfOtherChainSource, soeREQUIRED},
},
commonFields);
add(jss::XChainCommit,
ttXCHAIN_COMMIT,
{
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfOtherChainDestination, soeOPTIONAL},
},
commonFields);
add(jss::XChainClaim,
ttXCHAIN_CLAIM,
{
{sfXChainBridge, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfDestinationTag, soeOPTIONAL},
{sfAmount, soeREQUIRED},
},
commonFields);
add(jss::XChainAddClaimAttestation,
ttXCHAIN_ADD_CLAIM_ATTESTATION,
{
{sfXChainBridge, soeREQUIRED},
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSignature, soeREQUIRED},
{sfOtherChainSource, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfXChainClaimID, soeREQUIRED},
{sfDestination, soeOPTIONAL},
},
commonFields);
add(jss::XChainAddAccountCreateAttestation,
ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION,
{
{sfXChainBridge, soeREQUIRED},
{sfAttestationSignerAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSignature, soeREQUIRED},
{sfOtherChainSource, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfAttestationRewardAccount, soeREQUIRED},
{sfWasLockingChainSend, soeREQUIRED},
{sfXChainAccountCreateCount, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
},
commonFields);
add(jss::XChainAccountCreateCommit,
ttXCHAIN_ACCOUNT_CREATE_COMMIT,
{
{sfXChainBridge, soeREQUIRED},
{sfDestination, soeREQUIRED},
{sfAmount, soeREQUIRED},
{sfSignatureReward, soeREQUIRED},
},
commonFields);
}
TxFormats const&

View File

@@ -0,0 +1,759 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/protocol/XChainAttestations.h>
#include <ripple/basics/Expected.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/XChainAttestations.h>
#include <ripple/protocol/json_get_or_throw.h>
#include <ripple/protocol/jss.h>
#include <algorithm>
#include <optional>
namespace ripple {
namespace Attestations {
AttestationBase::AttestationBase(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_)
: attestationSignerAccount{attestationSignerAccount_}
, publicKey{publicKey_}
, signature{std::move(signature_)}
, sendingAccount{sendingAccount_}
, sendingAmount{sendingAmount_}
, rewardAccount{rewardAccount_}
, wasLockingChainSend{wasLockingChainSend_}
{
}
bool
AttestationBase::equalHelper(
AttestationBase const& lhs,
AttestationBase const& rhs)
{
return std::tie(
lhs.attestationSignerAccount,
lhs.publicKey,
lhs.signature,
lhs.sendingAccount,
lhs.sendingAmount,
lhs.rewardAccount,
lhs.wasLockingChainSend) ==
std::tie(
rhs.attestationSignerAccount,
rhs.publicKey,
rhs.signature,
rhs.sendingAccount,
rhs.sendingAmount,
rhs.rewardAccount,
rhs.wasLockingChainSend);
}
bool
AttestationBase::sameEventHelper(
AttestationBase const& lhs,
AttestationBase const& rhs)
{
return std::tie(
lhs.sendingAccount,
lhs.sendingAmount,
lhs.wasLockingChainSend) ==
std::tie(
rhs.sendingAccount, rhs.sendingAmount, rhs.wasLockingChainSend);
}
bool
AttestationBase::verify(STXChainBridge const& bridge) const
{
std::vector<std::uint8_t> msg = message(bridge);
return ripple::verify(publicKey, makeSlice(msg), signature);
}
AttestationBase::AttestationBase(STObject const& o)
: attestationSignerAccount{o[sfAttestationSignerAccount]}
, publicKey{o[sfPublicKey]}
, signature{o[sfSignature]}
, sendingAccount{o[sfAccount]}
, sendingAmount{o[sfAmount]}
, rewardAccount{o[sfAttestationRewardAccount]}
, wasLockingChainSend{bool(o[sfWasLockingChainSend])}
{
}
AttestationBase::AttestationBase(Json::Value const& v)
: attestationSignerAccount{Json::getOrThrow<AccountID>(
v,
sfAttestationSignerAccount)}
, publicKey{Json::getOrThrow<PublicKey>(v, sfPublicKey)}
, signature{Json::getOrThrow<Buffer>(v, sfSignature)}
, sendingAccount{Json::getOrThrow<AccountID>(v, sfAccount)}
, sendingAmount{Json::getOrThrow<STAmount>(v, sfAmount)}
, rewardAccount{Json::getOrThrow<AccountID>(v, sfAttestationRewardAccount)}
, wasLockingChainSend{Json::getOrThrow<bool>(v, sfWasLockingChainSend)}
{
}
void
AttestationBase::addHelper(STObject& o) const
{
o[sfAttestationSignerAccount] = attestationSignerAccount;
o[sfPublicKey] = publicKey;
o[sfSignature] = signature;
o[sfAmount] = sendingAmount;
o[sfAccount] = sendingAccount;
o[sfAttestationRewardAccount] = rewardAccount;
o[sfWasLockingChainSend] = wasLockingChainSend;
}
AttestationClaim::AttestationClaim(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t claimID_,
std::optional<AccountID> const& dst_)
: AttestationBase(
attestationSignerAccount_,
publicKey_,
std::move(signature_),
sendingAccount_,
sendingAmount_,
rewardAccount_,
wasLockingChainSend_)
, claimID{claimID_}
, dst{dst_}
{
}
AttestationClaim::AttestationClaim(
STXChainBridge const& bridge,
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
SecretKey const& secretKey_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t claimID_,
std::optional<AccountID> const& dst_)
: AttestationClaim{
attestationSignerAccount_,
publicKey_,
Buffer{},
sendingAccount_,
sendingAmount_,
rewardAccount_,
wasLockingChainSend_,
claimID_,
dst_}
{
auto const toSign = message(bridge);
signature = sign(publicKey_, secretKey_, makeSlice(toSign));
}
AttestationClaim::AttestationClaim(STObject const& o)
: AttestationBase(o), claimID{o[sfXChainClaimID]}, dst{o[~sfDestination]}
{
}
AttestationClaim::AttestationClaim(Json::Value const& v)
: AttestationBase{v}
, claimID{Json::getOrThrow<std::uint64_t>(v, sfXChainClaimID)}
{
if (v.isMember(sfDestination.getJsonName()))
dst = Json::getOrThrow<AccountID>(v, sfDestination);
}
STObject
AttestationClaim::toSTObject() const
{
STObject o{sfXChainClaimAttestationCollectionElement};
addHelper(o);
o[sfXChainClaimID] = claimID;
if (dst)
o[sfDestination] = *dst;
return o;
}
std::vector<std::uint8_t>
AttestationClaim::message(
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<AccountID> const& dst)
{
STObject o{sfGeneric};
// Serialize in SField order to make python serializers easier to write
o[sfXChainClaimID] = claimID;
o[sfAmount] = sendingAmount;
if (dst)
o[sfDestination] = *dst;
o[sfOtherChainSource] = sendingAccount;
o[sfAttestationRewardAccount] = rewardAccount;
o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0;
o[sfXChainBridge] = bridge;
Serializer s;
o.add(s);
return std::move(s.modData());
}
std::vector<std::uint8_t>
AttestationClaim::message(STXChainBridge const& bridge) const
{
return AttestationClaim::message(
bridge,
sendingAccount,
sendingAmount,
rewardAccount,
wasLockingChainSend,
claimID,
dst);
}
bool
AttestationClaim::validAmounts() const
{
return isLegalNet(sendingAmount);
}
bool
AttestationClaim::sameEvent(AttestationClaim const& rhs) const
{
return AttestationClaim::sameEventHelper(*this, rhs) &&
tie(claimID, dst) == tie(rhs.claimID, rhs.dst);
}
bool
operator==(AttestationClaim const& lhs, AttestationClaim const& rhs)
{
return AttestationClaim::equalHelper(lhs, rhs) &&
tie(lhs.claimID, lhs.dst) == tie(rhs.claimID, rhs.dst);
}
AttestationCreateAccount::AttestationCreateAccount(STObject const& o)
: AttestationBase(o)
, createCount{o[sfXChainAccountCreateCount]}
, toCreate{o[sfDestination]}
, rewardAmount{o[sfSignatureReward]}
{
}
AttestationCreateAccount::AttestationCreateAccount(Json::Value const& v)
: AttestationBase{v}
, createCount{Json::getOrThrow<std::uint64_t>(
v,
sfXChainAccountCreateCount)}
, toCreate{Json::getOrThrow<AccountID>(v, sfDestination)}
, rewardAmount{Json::getOrThrow<STAmount>(v, sfSignatureReward)}
{
}
AttestationCreateAccount::AttestationCreateAccount(
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
Buffer signature_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t createCount_,
AccountID const& toCreate_)
: AttestationBase(
attestationSignerAccount_,
publicKey_,
std::move(signature_),
sendingAccount_,
sendingAmount_,
rewardAccount_,
wasLockingChainSend_)
, createCount{createCount_}
, toCreate{toCreate_}
, rewardAmount{rewardAmount_}
{
}
AttestationCreateAccount::AttestationCreateAccount(
STXChainBridge const& bridge,
AccountID attestationSignerAccount_,
PublicKey const& publicKey_,
SecretKey const& secretKey_,
AccountID const& sendingAccount_,
STAmount const& sendingAmount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::uint64_t createCount_,
AccountID const& toCreate_)
: AttestationCreateAccount{
attestationSignerAccount_,
publicKey_,
Buffer{},
sendingAccount_,
sendingAmount_,
rewardAmount_,
rewardAccount_,
wasLockingChainSend_,
createCount_,
toCreate_}
{
auto const toSign = message(bridge);
signature = sign(publicKey_, secretKey_, makeSlice(toSign));
}
STObject
AttestationCreateAccount::toSTObject() const
{
STObject o{sfXChainCreateAccountAttestationCollectionElement};
addHelper(o);
o[sfXChainAccountCreateCount] = createCount;
o[sfDestination] = toCreate;
o[sfSignatureReward] = rewardAmount;
return o;
}
std::vector<std::uint8_t>
AttestationCreateAccount::message(
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
STAmount const& rewardAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
AccountID const& dst)
{
STObject o{sfGeneric};
// Serialize in SField order to make python serializers easier to write
o[sfXChainAccountCreateCount] = createCount;
o[sfAmount] = sendingAmount;
o[sfSignatureReward] = rewardAmount;
o[sfDestination] = dst;
o[sfOtherChainSource] = sendingAccount;
o[sfAttestationRewardAccount] = rewardAccount;
o[sfWasLockingChainSend] = wasLockingChainSend ? 1 : 0;
o[sfXChainBridge] = bridge;
Serializer s;
o.add(s);
return std::move(s.modData());
}
std::vector<std::uint8_t>
AttestationCreateAccount::message(STXChainBridge const& bridge) const
{
return AttestationCreateAccount::message(
bridge,
sendingAccount,
sendingAmount,
rewardAmount,
rewardAccount,
wasLockingChainSend,
createCount,
toCreate);
}
bool
AttestationCreateAccount::validAmounts() const
{
return isLegalNet(rewardAmount) && isLegalNet(sendingAmount);
}
bool
AttestationCreateAccount::sameEvent(AttestationCreateAccount const& rhs) const
{
return AttestationCreateAccount::sameEventHelper(*this, rhs) &&
std::tie(createCount, toCreate, rewardAmount) ==
std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount);
}
bool
operator==(
AttestationCreateAccount const& lhs,
AttestationCreateAccount const& rhs)
{
return AttestationCreateAccount::equalHelper(lhs, rhs) &&
std::tie(lhs.createCount, lhs.toCreate, lhs.rewardAmount) ==
std::tie(rhs.createCount, rhs.toCreate, rhs.rewardAmount);
}
} // namespace Attestations
SField const& XChainClaimAttestation::ArrayFieldName{sfXChainClaimAttestations};
SField const& XChainCreateAccountAttestation::ArrayFieldName{
sfXChainCreateAccountAttestations};
XChainClaimAttestation::XChainClaimAttestation(
AccountID const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
std::optional<AccountID> const& dst_)
: keyAccount(keyAccount_)
, publicKey(publicKey_)
, amount(sfAmount, amount_)
, rewardAccount(rewardAccount_)
, wasLockingChainSend(wasLockingChainSend_)
, dst(dst_)
{
}
XChainClaimAttestation::XChainClaimAttestation(
STAccount const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
STAccount const& rewardAccount_,
bool wasLockingChainSend_,
std::optional<STAccount> const& dst_)
: XChainClaimAttestation{
keyAccount_.value(),
publicKey_,
amount_,
rewardAccount_.value(),
wasLockingChainSend_,
dst_ ? std::optional<AccountID>{dst_->value()} : std::nullopt}
{
}
XChainClaimAttestation::XChainClaimAttestation(STObject const& o)
: XChainClaimAttestation{
o[sfAttestationSignerAccount],
PublicKey{o[sfPublicKey]},
o[sfAmount],
o[sfAttestationRewardAccount],
o[sfWasLockingChainSend] != 0,
o[~sfDestination]} {};
XChainClaimAttestation::XChainClaimAttestation(Json::Value const& v)
: XChainClaimAttestation{
Json::getOrThrow<AccountID>(v, sfAttestationSignerAccount),
Json::getOrThrow<PublicKey>(v, sfPublicKey),
Json::getOrThrow<STAmount>(v, sfAmount),
Json::getOrThrow<AccountID>(v, sfAttestationRewardAccount),
Json::getOrThrow<bool>(v, sfWasLockingChainSend),
std::nullopt}
{
if (v.isMember(sfDestination.getJsonName()))
dst = Json::getOrThrow<AccountID>(v, sfDestination);
};
XChainClaimAttestation::XChainClaimAttestation(
XChainClaimAttestation::TSignedAttestation const& claimAtt)
: XChainClaimAttestation{
claimAtt.attestationSignerAccount,
claimAtt.publicKey,
claimAtt.sendingAmount,
claimAtt.rewardAccount,
claimAtt.wasLockingChainSend,
claimAtt.dst}
{
}
STObject
XChainClaimAttestation::toSTObject() const
{
STObject o{sfXChainClaimProofSig};
o[sfAttestationSignerAccount] =
STAccount{sfAttestationSignerAccount, keyAccount};
o[sfPublicKey] = publicKey;
o[sfAmount] = STAmount{sfAmount, amount};
o[sfAttestationRewardAccount] =
STAccount{sfAttestationRewardAccount, rewardAccount};
o[sfWasLockingChainSend] = wasLockingChainSend;
if (dst)
o[sfDestination] = STAccount{sfDestination, *dst};
return o;
}
bool
operator==(XChainClaimAttestation const& lhs, XChainClaimAttestation const& rhs)
{
return std::tie(
lhs.keyAccount,
lhs.publicKey,
lhs.amount,
lhs.rewardAccount,
lhs.wasLockingChainSend,
lhs.dst) ==
std::tie(
rhs.keyAccount,
rhs.publicKey,
rhs.amount,
rhs.rewardAccount,
rhs.wasLockingChainSend,
rhs.dst);
}
XChainClaimAttestation::MatchFields::MatchFields(
XChainClaimAttestation::TSignedAttestation const& att)
: amount{att.sendingAmount}
, wasLockingChainSend{att.wasLockingChainSend}
, dst{att.dst}
{
}
AttestationMatch
XChainClaimAttestation::match(
XChainClaimAttestation::MatchFields const& rhs) const
{
if (std::tie(amount, wasLockingChainSend) !=
std::tie(rhs.amount, rhs.wasLockingChainSend))
return AttestationMatch::nonDstMismatch;
if (dst != rhs.dst)
return AttestationMatch::matchExceptDst;
return AttestationMatch::match;
}
//------------------------------------------------------------------------------
XChainCreateAccountAttestation::XChainCreateAccountAttestation(
AccountID const& keyAccount_,
PublicKey const& publicKey_,
STAmount const& amount_,
STAmount const& rewardAmount_,
AccountID const& rewardAccount_,
bool wasLockingChainSend_,
AccountID const& dst_)
: keyAccount(keyAccount_)
, publicKey(publicKey_)
, amount(sfAmount, amount_)
, rewardAmount(sfSignatureReward, rewardAmount_)
, rewardAccount(rewardAccount_)
, wasLockingChainSend(wasLockingChainSend_)
, dst(dst_)
{
}
XChainCreateAccountAttestation::XChainCreateAccountAttestation(
STObject const& o)
: XChainCreateAccountAttestation{
o[sfAttestationSignerAccount],
PublicKey{o[sfPublicKey]},
o[sfAmount],
o[sfSignatureReward],
o[sfAttestationRewardAccount],
o[sfWasLockingChainSend] != 0,
o[sfDestination]} {};
XChainCreateAccountAttestation ::XChainCreateAccountAttestation(
Json::Value const& v)
: XChainCreateAccountAttestation{
Json::getOrThrow<AccountID>(v, sfAttestationSignerAccount),
Json::getOrThrow<PublicKey>(v, sfPublicKey),
Json::getOrThrow<STAmount>(v, sfAmount),
Json::getOrThrow<STAmount>(v, sfSignatureReward),
Json::getOrThrow<AccountID>(v, sfAttestationRewardAccount),
Json::getOrThrow<bool>(v, sfWasLockingChainSend),
Json::getOrThrow<AccountID>(v, sfDestination)}
{
}
XChainCreateAccountAttestation::XChainCreateAccountAttestation(
XChainCreateAccountAttestation::TSignedAttestation const& createAtt)
: XChainCreateAccountAttestation{
createAtt.attestationSignerAccount,
createAtt.publicKey,
createAtt.sendingAmount,
createAtt.rewardAmount,
createAtt.rewardAccount,
createAtt.wasLockingChainSend,
createAtt.toCreate}
{
}
STObject
XChainCreateAccountAttestation::toSTObject() const
{
STObject o{sfXChainCreateAccountProofSig};
o[sfAttestationSignerAccount] =
STAccount{sfAttestationSignerAccount, keyAccount};
o[sfPublicKey] = publicKey;
o[sfAmount] = STAmount{sfAmount, amount};
o[sfSignatureReward] = STAmount{sfSignatureReward, rewardAmount};
o[sfAttestationRewardAccount] =
STAccount{sfAttestationRewardAccount, rewardAccount};
o[sfWasLockingChainSend] = wasLockingChainSend;
o[sfDestination] = STAccount{sfDestination, dst};
return o;
}
XChainCreateAccountAttestation::MatchFields::MatchFields(
XChainCreateAccountAttestation::TSignedAttestation const& att)
: amount{att.sendingAmount}
, rewardAmount(att.rewardAmount)
, wasLockingChainSend{att.wasLockingChainSend}
, dst{att.toCreate}
{
}
AttestationMatch
XChainCreateAccountAttestation::match(
XChainCreateAccountAttestation::MatchFields const& rhs) const
{
if (std::tie(amount, rewardAmount, wasLockingChainSend) !=
std::tie(rhs.amount, rhs.rewardAmount, rhs.wasLockingChainSend))
return AttestationMatch::nonDstMismatch;
if (dst != rhs.dst)
return AttestationMatch::matchExceptDst;
return AttestationMatch::match;
}
bool
operator==(
XChainCreateAccountAttestation const& lhs,
XChainCreateAccountAttestation const& rhs)
{
return std::tie(
lhs.keyAccount,
lhs.publicKey,
lhs.amount,
lhs.rewardAmount,
lhs.rewardAccount,
lhs.wasLockingChainSend,
lhs.dst) ==
std::tie(
rhs.keyAccount,
rhs.publicKey,
rhs.amount,
rhs.rewardAmount,
rhs.rewardAccount,
rhs.wasLockingChainSend,
rhs.dst);
}
//------------------------------------------------------------------------------
//
template <class TAttestation>
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(
XChainAttestationsBase<TAttestation>::AttCollection&& atts)
: attestations_{std::move(atts)}
{
}
template <class TAttestation>
typename XChainAttestationsBase<TAttestation>::AttCollection::const_iterator
XChainAttestationsBase<TAttestation>::begin() const
{
return attestations_.begin();
}
template <class TAttestation>
typename XChainAttestationsBase<TAttestation>::AttCollection::const_iterator
XChainAttestationsBase<TAttestation>::end() const
{
return attestations_.end();
}
template <class TAttestation>
typename XChainAttestationsBase<TAttestation>::AttCollection::iterator
XChainAttestationsBase<TAttestation>::begin()
{
return attestations_.begin();
}
template <class TAttestation>
typename XChainAttestationsBase<TAttestation>::AttCollection::iterator
XChainAttestationsBase<TAttestation>::end()
{
return attestations_.end();
}
template <class TAttestation>
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(
Json::Value const& v)
{
if (!v.isObject())
{
Throw<std::runtime_error>(
"XChainAttestationsBase can only be specified with an 'object' "
"Json value");
}
attestations_ = [&] {
auto const jAtts = v[jss::attestations];
if (jAtts.size() > maxAttestations)
Throw<std::runtime_error>(
"XChainAttestationsBase exceeded max number of attestations");
std::vector<TAttestation> r;
r.reserve(jAtts.size());
for (auto const& a : jAtts)
r.emplace_back(a);
return r;
}();
}
template <class TAttestation>
XChainAttestationsBase<TAttestation>::XChainAttestationsBase(STArray const& arr)
{
if (arr.size() > maxAttestations)
Throw<std::runtime_error>(
"XChainAttestationsBase exceeded max number of attestations");
attestations_.reserve(arr.size());
for (auto const& o : arr)
attestations_.emplace_back(o);
}
template <class TAttestation>
STArray
XChainAttestationsBase<TAttestation>::toSTArray() const
{
STArray r{TAttestation::ArrayFieldName, attestations_.size()};
for (auto const& e : attestations_)
r.emplace_back(e.toSTObject());
return r;
}
template class XChainAttestationsBase<XChainClaimAttestation>;
template class XChainAttestationsBase<XChainCreateAccountAttestation>;
} // namespace ripple

View File

@@ -0,0 +1,159 @@
#ifndef PROTOCOL_GET_OR_THROW_H_
#define PROTOCOL_GET_OR_THROW_H_
#include <ripple/basics/Buffer.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/contract.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/SField.h>
#include <charconv>
#include <exception>
#include <optional>
#include <string>
namespace Json {
struct JsonMissingKeyError : std::exception
{
char const* const key;
mutable std::string msg;
JsonMissingKeyError(Json::StaticString const& k) : key{k.c_str()}
{
}
const char*
what() const noexcept override
{
if (msg.empty())
{
msg = std::string("Missing json key: ") + key;
}
return msg.c_str();
}
};
struct JsonTypeMismatchError : std::exception
{
char const* const key;
std::string const expectedType;
mutable std::string msg;
JsonTypeMismatchError(Json::StaticString const& k, std::string et)
: key{k.c_str()}, expectedType{std::move(et)}
{
}
const char*
what() const noexcept override
{
if (msg.empty())
{
msg = std::string("Type mismatch on json key: ") + key +
"; expected type: " + expectedType;
}
return msg.c_str();
}
};
template <class T>
T
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
static_assert(sizeof(T) == -1, "This function must be specialized");
}
template <>
inline std::string
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
Json::StaticString const& key = field.getJsonName();
if (!v.isMember(key))
Throw<JsonMissingKeyError>(key);
Json::Value const& inner = v[key];
if (!inner.isString())
Throw<JsonTypeMismatchError>(key, "string");
return inner.asString();
}
// Note, this allows integer numeric fields to act as bools
template <>
inline bool
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
Json::StaticString const& key = field.getJsonName();
if (!v.isMember(key))
Throw<JsonMissingKeyError>(key);
Json::Value const& inner = v[key];
if (inner.isBool())
return inner.asBool();
if (!inner.isIntegral())
Throw<JsonTypeMismatchError>(key, "bool");
return inner.asInt() != 0;
}
template <>
inline std::uint64_t
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
Json::StaticString const& key = field.getJsonName();
if (!v.isMember(key))
Throw<JsonMissingKeyError>(key);
Json::Value const& inner = v[key];
if (inner.isUInt())
return inner.asUInt();
if (inner.isInt())
{
auto const r = inner.asInt();
if (r < 0)
Throw<JsonTypeMismatchError>(key, "uint64");
return r;
}
if (inner.isString())
{
auto const s = inner.asString();
// parse as hex
std::uint64_t val;
auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), val, 16);
if (ec != std::errc() || (p != s.data() + s.size()))
Throw<JsonTypeMismatchError>(key, "uint64");
return val;
}
Throw<JsonTypeMismatchError>(key, "uint64");
}
template <>
inline ripple::Buffer
getOrThrow(Json::Value const& v, ripple::SField const& field)
{
using namespace ripple;
std::string const hex = getOrThrow<std::string>(v, field);
if (auto const r = strUnHex(hex))
{
// TODO: mismatch between a buffer and a blob
return Buffer{r->data(), r->size()};
}
Throw<JsonTypeMismatchError>(field.getJsonName(), "Buffer");
}
// This function may be used by external projects (like the witness server).
template <class T>
std::optional<T>
getOptional(Json::Value const& v, ripple::SField const& field)
{
try
{
return getOrThrow<T>(v, field);
}
catch (...)
{
}
return {};
}
} // namespace Json
#endif // PROTOCOL_GET_OR_THROW_H_

View File

@@ -62,6 +62,7 @@ JSS(Asset); // in: AMM Asset1
JSS(Asset2); // in: AMM Asset2
JSS(AuthAccount); // in: AMM Auction Slot
JSS(AuthAccounts); // in: AMM Auction Slot
JSS(Bridge); // ledger type.
JSS(Check); // ledger type.
JSS(CheckCancel); // transaction type.
JSS(CheckCash); // transaction type.
@@ -107,31 +108,41 @@ JSS(Paths); // in/out: TransactionSign
JSS(PayChannel); // ledger type.
JSS(Payment); // transaction type.
JSS(PaymentChannelClaim); // transaction type.
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.
JSS(UNLModify); // transaction type.
JSS(SettleDelay); // in: TransactionSign
JSS(SendMax); // in: TransactionSign
JSS(Sequence); // in/out: TransactionSign; field.
JSS(SetFlag); // field.
JSS(SetRegularKey); // transaction type.
JSS(SignerList); // ledger type.
JSS(SignerListSet); // transaction type.
JSS(SigningPubKey); // field.
JSS(TakerGets); // field.
JSS(TakerPays); // field.
JSS(Ticket); // ledger type.
JSS(TicketCreate); // transaction type.
JSS(TxnSignature); // field.
JSS(TradingFee); // in/out: AMM trading fee
JSS(TransactionType); // in: TransactionSign.
JSS(TransferRate); // in: TransferRate.
JSS(TrustSet); // transaction type.
JSS(VoteSlots); // out: AMM Vote
JSS(aborted); // out: InboundLedger
JSS(PaymentChannelCreate); // transaction type.
JSS(PaymentChannelFund); // transaction type.
JSS(RippleState); // ledger type.
JSS(SLE_hit_rate); // out: GetCounts.
JSS(SetFee); // transaction type.
JSS(UNLModify); // transaction type.
JSS(SettleDelay); // in: TransactionSign
JSS(SendMax); // in: TransactionSign
JSS(Sequence); // in/out: TransactionSign; field.
JSS(SetFlag); // field.
JSS(SetRegularKey); // transaction type.
JSS(SignerList); // ledger type.
JSS(SignerListSet); // transaction type.
JSS(SigningPubKey); // field.
JSS(TakerGets); // field.
JSS(TakerPays); // field.
JSS(Ticket); // ledger type.
JSS(TicketCreate); // transaction type.
JSS(TxnSignature); // field.
JSS(TradingFee); // in/out: AMM trading fee
JSS(TransactionType); // in: TransactionSign.
JSS(TransferRate); // in: TransferRate.
JSS(TrustSet); // transaction type.
JSS(VoteSlots); // out: AMM Vote
JSS(XChainAddAccountCreateAttestation); // transaction type.
JSS(XChainAddClaimAttestation); // transaction type.
JSS(XChainAccountCreateCommit); // transaction type.
JSS(XChainClaim); // transaction type.
JSS(XChainCommit); // transaction type.
JSS(XChainCreateBridge); // transaction type.
JSS(XChainCreateClaimID); // transaction type.
JSS(XChainModifyBridge); // transaction type.
JSS(XChainOwnedClaimID); // ledger type.
JSS(XChainOwnedCreateAccountClaimID); // ledger type.
JSS(aborted); // out: InboundLedger
JSS(accepted); // out: LedgerToJson, OwnerInfo, SubmitTransaction
JSS(account); // in/out: many
JSS(accountState); // out: LedgerToJson
@@ -147,96 +158,102 @@ JSS(account_sequence_next); // out: SubmitTransaction
JSS(account_sequence_available); // out: SubmitTransaction
JSS(account_history_tx_stream); // in: Subscribe, Unsubscribe
JSS(account_history_tx_index); // out: Account txn history subscribe
JSS(account_history_tx_first); // out: Account txn history subscribe
JSS(accounts); // in: LedgerEntry, Subscribe,
// handlers/Ledger, Unsubscribe
JSS(accounts_proposed); // in: Subscribe, Unsubscribe
JSS(account_history_tx_first); // out: Account txn history subscribe
JSS(account_history_boundary); // out: Account txn history subscribe
JSS(accounts); // in: LedgerEntry, Subscribe,
// handlers/Ledger, Unsubscribe
JSS(accounts_proposed); // in: Subscribe, Unsubscribe
JSS(action);
JSS(acquiring); // out: LedgerRequest
JSS(address); // out: PeerImp
JSS(affected); // out: AcceptedLedgerTx
JSS(age); // out: NetworkOPs, Peers
JSS(alternatives); // out: PathRequest, RipplePathFind
JSS(amendment_blocked); // out: NetworkOPs
JSS(amendments); // in: AccountObjects, out: NetworkOPs
JSS(amm); // out: amm_info
JSS(amm_account); // in: amm_info
JSS(amount); // out: AccountChannels, amm_info
JSS(amount2); // out: amm_info
JSS(api_version); // in: many, out: Version
JSS(api_version_low); // out: Version
JSS(applied); // out: SubmitTransaction
JSS(asks); // out: Subscribe
JSS(asset); // in: amm_info
JSS(asset2); // in: amm_info
JSS(assets); // out: GatewayBalances
JSS(asset_frozen); // out: amm_info
JSS(asset2_frozen); // out: amm_info
JSS(auction_slot); // out: amm_info
JSS(authorized); // out: AccountLines
JSS(auth_accounts); // out: amm_info
JSS(auth_change); // out: AccountInfo
JSS(auth_change_queued); // out: AccountInfo
JSS(available); // out: ValidatorList
JSS(avg_bps_recv); // out: Peers
JSS(avg_bps_sent); // out: Peers
JSS(balance); // out: AccountLines
JSS(balances); // out: GatewayBalances
JSS(base); // out: LogLevel
JSS(base_fee); // out: NetworkOPs
JSS(base_fee_xrp); // out: NetworkOPs
JSS(bids); // out: Subscribe
JSS(binary); // in: AccountTX, LedgerEntry,
// AccountTxOld, Tx LedgerData
JSS(blob); // out: ValidatorList
JSS(blobs_v2); // out: ValidatorList
// in: UNL
JSS(books); // in: Subscribe, Unsubscribe
JSS(both); // in: Subscribe, Unsubscribe
JSS(both_sides); // in: Subscribe, Unsubscribe
JSS(broadcast); // out: SubmitTransaction
JSS(build_path); // in: TransactionSign
JSS(build_version); // out: NetworkOPs
JSS(cancel_after); // out: AccountChannels
JSS(can_delete); // out: CanDelete
JSS(changes); // out: BookChanges
JSS(channel_id); // out: AccountChannels
JSS(channels); // out: AccountChannels
JSS(check); // in: AccountObjects
JSS(check_nodes); // in: LedgerCleaner
JSS(clear); // in/out: FetchInfo
JSS(close); // out: BookChanges
JSS(close_flags); // out: LedgerToJson
JSS(close_time); // in: Application, out: NetworkOPs,
// RCLCxPeerPos, LedgerToJson
JSS(close_time_estimated); // in: Application, out: LedgerToJson
JSS(close_time_human); // out: LedgerToJson
JSS(close_time_offset); // out: NetworkOPs
JSS(close_time_resolution); // in: Application; out: LedgerToJson
JSS(closed); // out: NetworkOPs, LedgerToJson,
// handlers/Ledger
JSS(closed_ledger); // out: NetworkOPs
JSS(cluster); // out: PeerImp
JSS(code); // out: errors
JSS(command); // in: RPCHandler
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(complete_shards); // out: OverlayImpl, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currentShard); // out: NodeToShardStatus
JSS(currentShardIndex); // out: NodeToShardStatus
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount,
// AccountLines
JSS(current); // out: OwnerInfo
JSS(acquiring); // out: LedgerRequest
JSS(address); // out: PeerImp
JSS(affected); // out: AcceptedLedgerTx
JSS(age); // out: NetworkOPs, Peers
JSS(alternatives); // out: PathRequest, RipplePathFind
JSS(amendment_blocked); // out: NetworkOPs
JSS(amendments); // in: AccountObjects, out: NetworkOPs
JSS(amm); // out: amm_info
JSS(amm_account); // in: amm_info
JSS(amount); // out: AccountChannels, amm_info
JSS(amount2); // out: amm_info
JSS(api_version); // in: many, out: Version
JSS(api_version_low); // out: Version
JSS(applied); // out: SubmitTransaction
JSS(asks); // out: Subscribe
JSS(asset); // in: amm_info
JSS(asset2); // in: amm_info
JSS(assets); // out: GatewayBalances
JSS(asset_frozen); // out: amm_info
JSS(asset2_frozen); // out: amm_info
JSS(attestations); //
JSS(attestation_reward_account); //
JSS(auction_slot); // out: amm_info
JSS(authorized); // out: AccountLines
JSS(auth_accounts); // out: amm_info
JSS(auth_change); // out: AccountInfo
JSS(auth_change_queued); // out: AccountInfo
JSS(available); // out: ValidatorList
JSS(avg_bps_recv); // out: Peers
JSS(avg_bps_sent); // out: Peers
JSS(balance); // out: AccountLines
JSS(balances); // out: GatewayBalances
JSS(base); // out: LogLevel
JSS(base_fee); // out: NetworkOPs
JSS(base_fee_xrp); // out: NetworkOPs
JSS(bids); // out: Subscribe
JSS(binary); // in: AccountTX, LedgerEntry,
// AccountTxOld, Tx LedgerData
JSS(blob); // out: ValidatorList
JSS(blobs_v2); // out: ValidatorList
// in: UNL
JSS(books); // in: Subscribe, Unsubscribe
JSS(both); // in: Subscribe, Unsubscribe
JSS(both_sides); // in: Subscribe, Unsubscribe
JSS(broadcast); // out: SubmitTransaction
JSS(bridge); // in: LedgerEntry
JSS(bridge_account); // in: LedgerEntry
JSS(build_path); // in: TransactionSign
JSS(build_version); // out: NetworkOPs
JSS(cancel_after); // out: AccountChannels
JSS(can_delete); // out: CanDelete
JSS(changes); // out: BookChanges
JSS(channel_id); // out: AccountChannels
JSS(channels); // out: AccountChannels
JSS(check); // in: AccountObjects
JSS(check_nodes); // in: LedgerCleaner
JSS(clear); // in/out: FetchInfo
JSS(close); // out: BookChanges
JSS(close_flags); // out: LedgerToJson
JSS(close_time); // in: Application, out: NetworkOPs,
// RCLCxPeerPos, LedgerToJson
JSS(close_time_estimated); // in: Application, out: LedgerToJson
JSS(close_time_human); // out: LedgerToJson
JSS(close_time_offset); // out: NetworkOPs
JSS(close_time_resolution); // in: Application; out: LedgerToJson
JSS(closed); // out: NetworkOPs, LedgerToJson,
// handlers/Ledger
JSS(closed_ledger); // out: NetworkOPs
JSS(cluster); // out: PeerImp
JSS(code); // out: errors
JSS(command); // in: RPCHandler
JSS(complete); // out: NetworkOPs, InboundLedger
JSS(complete_ledgers); // out: NetworkOPs, PeerImp
JSS(complete_shards); // out: OverlayImpl, PeerImp
JSS(consensus); // out: NetworkOPs, LedgerConsensus
JSS(converge_time); // out: NetworkOPs
JSS(converge_time_s); // out: NetworkOPs
JSS(cookie); // out: NetworkOPs
JSS(count); // in: AccountTx*, ValidatorList
JSS(counters); // in/out: retrieve counters
JSS(ctid); // in/out: Tx RPC
JSS(currency_a); // out: BookChanges
JSS(currency_b); // out: BookChanges
JSS(currentShard); // out: NodeToShardStatus
JSS(currentShardIndex); // out: NodeToShardStatus
JSS(currency); // in: paths/PathRequest, STAmount
// out: STPathSet, STAmount,
// AccountLines
JSS(current); // out: OwnerInfo
JSS(current_activities);
JSS(current_ledger_size); // out: TxQ
JSS(current_queue_size); // out: TxQ
@@ -698,8 +715,10 @@ JSS(vote_weight); // out: amm_info
JSS(warning); // rpc:
JSS(warnings); // out: server_info, server_state
JSS(workers);
JSS(write_load); // out: GetCounts
JSS(NegativeUNL); // out: ValidatorList; ledger type
JSS(write_load); // out: GetCounts
JSS(xchain_owned_claim_id); // in: LedgerEntry, AccountObjects
JSS(xchain_owned_create_account_claim_id); // in: LedgerEntry
JSS(NegativeUNL); // out: ValidatorList; ledger type
#undef JSS
} // namespace jss

View File

@@ -197,7 +197,11 @@ doAccountObjects(RPC::JsonContext& context)
{jss::escrow, ltESCROW},
{jss::nft_page, ltNFTOKEN_PAGE},
{jss::payment_channel, ltPAYCHAN},
{jss::state, ltRIPPLE_STATE}};
{jss::state, ltRIPPLE_STATE},
{jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID},
{jss::xchain_owned_create_account_claim_id,
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID},
{jss::bridge, ltBRIDGE}};
typeFilter.emplace();
typeFilter->reserve(std::size(deletionBlockers));

View File

@@ -25,6 +25,7 @@
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
@@ -390,6 +391,203 @@ doLedgerEntry(RPC::JsonContext& context)
}
}
}
else if (context.params.isMember(jss::bridge))
{
expectedType = ltBRIDGE;
// return the keylet for the specified bridge or nullopt if the
// request is malformed
auto const maybeKeylet = [&]() -> std::optional<Keylet> {
try
{
if (!context.params.isMember(jss::bridge_account))
return std::nullopt;
auto const& jsBridgeAccount =
context.params[jss::bridge_account];
if (!jsBridgeAccount.isString())
{
return std::nullopt;
}
auto const account =
parseBase58<AccountID>(jsBridgeAccount.asString());
if (!account || account->isZero())
{
return std::nullopt;
}
// This may throw and is the reason for the `try` block. The
// try block has a larger scope so the `bridge` variable
// doesn't need to be an optional.
STXChainBridge const bridge(context.params[jss::bridge]);
STXChainBridge::ChainType const chainType =
STXChainBridge::srcChain(
account == bridge.lockingChainDoor());
if (account != bridge.door(chainType))
return std::nullopt;
return keylet::bridge(bridge, chainType);
}
catch (...)
{
return std::nullopt;
}
}();
if (maybeKeylet)
{
uNodeIndex = maybeKeylet->key;
}
else
{
uNodeIndex = beast::zero;
jvResult[jss::error] = "malformedRequest";
}
}
else if (context.params.isMember(jss::xchain_owned_claim_id))
{
expectedType = ltXCHAIN_OWNED_CLAIM_ID;
auto& claim_id = context.params[jss::xchain_owned_claim_id];
if (claim_id.isString())
{
// we accept a node id as specifier of a xchain claim id
if (!uNodeIndex.parseHex(claim_id.asString()))
{
uNodeIndex = beast::zero;
jvResult[jss::error] = "malformedRequest";
}
}
else if (
!claim_id.isObject() ||
!(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
!(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
!claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
!claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
!claim_id.isMember(jss::xchain_owned_claim_id))
{
jvResult[jss::error] = "malformedRequest";
}
else
{
// if not specified with a node id, a claim_id is specified by
// four strings defining the bridge (locking_chain_door,
// locking_chain_issue, issuing_chain_door, issuing_chain_issue)
// and the claim id sequence number.
auto lockingChainDoor = parseBase58<AccountID>(
claim_id[sfLockingChainDoor.getJsonName()].asString());
auto issuingChainDoor = parseBase58<AccountID>(
claim_id[sfIssuingChainDoor.getJsonName()].asString());
Issue lockingChainIssue, issuingChainIssue;
bool valid = lockingChainDoor && issuingChainDoor;
if (valid)
{
try
{
lockingChainIssue = issueFromJson(
claim_id[sfLockingChainIssue.getJsonName()]);
issuingChainIssue = issueFromJson(
claim_id[sfIssuingChainIssue.getJsonName()]);
}
catch (std::runtime_error const& ex)
{
valid = false;
jvResult[jss::error] = "malformedRequest";
}
}
if (valid && claim_id[jss::xchain_owned_claim_id].isIntegral())
{
auto seq = claim_id[jss::xchain_owned_claim_id].asUInt();
STXChainBridge bridge_spec(
*lockingChainDoor,
lockingChainIssue,
*issuingChainDoor,
issuingChainIssue);
Keylet keylet = keylet::xChainClaimID(bridge_spec, seq);
uNodeIndex = keylet.key;
}
}
}
else if (context.params.isMember(
jss::xchain_owned_create_account_claim_id))
{
// see object definition in LedgerFormats.cpp
expectedType = ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID;
auto& claim_id =
context.params[jss::xchain_owned_create_account_claim_id];
if (claim_id.isString())
{
// we accept a node id as specifier of a xchain create account
// claim_id
if (!uNodeIndex.parseHex(claim_id.asString()))
{
uNodeIndex = beast::zero;
jvResult[jss::error] = "malformedRequest";
}
}
else if (
!claim_id.isObject() ||
!(claim_id.isMember(sfIssuingChainDoor.getJsonName()) &&
claim_id[sfIssuingChainDoor.getJsonName()].isString()) ||
!(claim_id.isMember(sfLockingChainDoor.getJsonName()) &&
claim_id[sfLockingChainDoor.getJsonName()].isString()) ||
!claim_id.isMember(sfIssuingChainIssue.getJsonName()) ||
!claim_id.isMember(sfLockingChainIssue.getJsonName()) ||
!claim_id.isMember(jss::xchain_owned_create_account_claim_id))
{
jvResult[jss::error] = "malformedRequest";
}
else
{
// if not specified with a node id, a create account claim_id is
// specified by four strings defining the bridge
// (locking_chain_door, locking_chain_issue, issuing_chain_door,
// issuing_chain_issue) and the create account claim id sequence
// number.
auto lockingChainDoor = parseBase58<AccountID>(
claim_id[sfLockingChainDoor.getJsonName()].asString());
auto issuingChainDoor = parseBase58<AccountID>(
claim_id[sfIssuingChainDoor.getJsonName()].asString());
Issue lockingChainIssue, issuingChainIssue;
bool valid = lockingChainDoor && issuingChainDoor;
if (valid)
{
try
{
lockingChainIssue = issueFromJson(
claim_id[sfLockingChainIssue.getJsonName()]);
issuingChainIssue = issueFromJson(
claim_id[sfIssuingChainIssue.getJsonName()]);
}
catch (std::runtime_error const& ex)
{
valid = false;
jvResult[jss::error] = "malformedRequest";
}
}
if (valid &&
claim_id[jss::xchain_owned_create_account_claim_id]
.isIntegral())
{
auto seq =
claim_id[jss::xchain_owned_create_account_claim_id]
.asUInt();
STXChainBridge bridge_spec(
*lockingChainDoor,
lockingChainIssue,
*issuingChainDoor,
issuingChainIssue);
Keylet keylet =
keylet::xChainCreateAccountClaimID(bridge_spec, seq);
uNodeIndex = keylet.key;
}
}
}
else
{
if (context.params.isMember("params") &&

View File

@@ -982,7 +982,7 @@ chooseLedgerEntryType(Json::Value const& params)
std::pair<RPC::Status, LedgerEntryType> result{RPC::Status::OK, ltANY};
if (params.isMember(jss::type))
{
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 16>
static constexpr std::array<std::pair<char const*, LedgerEntryType>, 19>
types{
{{jss::account, ltACCOUNT_ROOT},
{jss::amendments, ltAMENDMENTS},
@@ -999,7 +999,11 @@ chooseLedgerEntryType(Json::Value const& params)
{jss::ticket, ltTICKET},
{jss::nft_offer, ltNFTOKEN_OFFER},
{jss::nft_page, ltNFTOKEN_PAGE},
{jss::amm, ltAMM}}};
{jss::amm, ltAMM},
{jss::bridge, ltBRIDGE},
{jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID},
{jss::xchain_owned_create_account_claim_id,
ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}}};
auto const& p = params[jss::type];
if (!p.isString())
@@ -1103,11 +1107,13 @@ getLedgerByContext(RPC::JsonContext& context)
return RPC::make_param_error("Ledger index too small");
auto const j = context.app.journal("RPCHandler");
// Try to get the hash of the desired ledger from the validated ledger
// Try to get the hash of the desired ledger from the validated
// ledger
auto neededHash = hashOfSeq(*ledger, ledgerIndex, j);
if (!neededHash)
{
// Find a ledger more likely to have the hash of the desired ledger
// Find a ledger more likely to have the hash of the desired
// ledger
auto const refIndex = getCandidateLedger(ledgerIndex);
auto refHash = hashOfSeq(*ledger, refIndex, j);
assert(refHash);
@@ -1115,8 +1121,8 @@ getLedgerByContext(RPC::JsonContext& context)
ledger = ledgerMaster.getLedgerByHash(*refHash);
if (!ledger)
{
// We don't have the ledger we need to figure out which ledger
// they want. Try to get it.
// We don't have the ledger we need to figure out which
// ledger they want. Try to get it.
if (auto il = context.app.getInboundLedgers().acquire(
*refHash, refIndex, InboundLedger::Reason::GENERIC))

5121
src/test/app/XChain_test.cpp Normal file

File diff suppressed because it is too large Load Diff

67
src/test/jtx/attester.h Normal file
View File

@@ -0,0 +1,67 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_ATTESTER_H_INCLUDED
#define RIPPLE_TEST_JTX_ATTESTER_H_INCLUDED
#include <ripple/basics/Buffer.h>
#include <ripple/protocol/AccountID.h>
#include <cstdint>
#include <optional>
namespace ripple {
class PublicKey;
class SecretKey;
class STXChainBridge;
class STAmount;
namespace test {
namespace jtx {
Buffer
sign_claim_attestation(
PublicKey const& pk,
SecretKey const& sk,
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<AccountID> const& dst);
Buffer
sign_create_account_attestation(
PublicKey const& pk,
SecretKey const& sk,
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
STAmount const& rewardAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
AccountID const& dst);
} // namespace jtx
} // namespace test
} // namespace ripple
#endif

View File

@@ -0,0 +1,82 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx/attester.h>
#include <ripple/basics/Slice.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/XChainAttestations.h>
namespace ripple {
namespace test {
namespace jtx {
Buffer
sign_claim_attestation(
PublicKey const& pk,
SecretKey const& sk,
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<AccountID> const& dst)
{
auto const toSign = Attestations::AttestationClaim::message(
bridge,
sendingAccount,
sendingAmount,
rewardAccount,
wasLockingChainSend,
claimID,
dst);
return sign(pk, sk, makeSlice(toSign));
}
Buffer
sign_create_account_attestation(
PublicKey const& pk,
SecretKey const& sk,
STXChainBridge const& bridge,
AccountID const& sendingAccount,
STAmount const& sendingAmount,
STAmount const& rewardAmount,
AccountID const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
AccountID const& dst)
{
auto const toSign = Attestations::AttestationCreateAccount::message(
bridge,
sendingAccount,
sendingAmount,
rewardAmount,
rewardAccount,
wasLockingChainSend,
createCount,
dst);
return sign(pk, sk, makeSlice(toSign));
}
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -0,0 +1,516 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2022 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <test/jtx/xchain_bridge.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/Issue.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STInteger.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/XChainAttestations.h>
#include <ripple/protocol/jss.h>
#include <test/jtx/Env.h>
#include <test/jtx/attester.h>
namespace ripple {
namespace test {
namespace jtx {
// use this for creating a bridge for a transaction
Json::Value
bridge(
Account const& lockingChainDoor,
Issue const& lockingChainIssue,
Account const& issuingChainDoor,
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
return jv;
}
// use this for creating a bridge for a rpc query
Json::Value
bridge_rpc(
Account const& lockingChainDoor,
Issue const& lockingChainIssue,
Account const& issuingChainDoor,
Issue const& issuingChainIssue)
{
Json::Value jv;
jv[sfLockingChainDoor.getJsonName()] = lockingChainDoor.human();
jv[sfLockingChainIssue.getJsonName()] = to_json(lockingChainIssue);
jv[sfIssuingChainDoor.getJsonName()] = issuingChainDoor.human();
jv[sfIssuingChainIssue.getJsonName()] = to_json(issuingChainIssue);
return jv;
}
Json::Value
bridge_create(
Account const& acc,
Json::Value const& bridge,
STAmount const& reward,
std::optional<STAmount> const& minAccountCreate)
{
Json::Value jv;
jv[jss::Account] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
jv[sfSignatureReward.getJsonName()] = reward.getJson(JsonOptions::none);
if (minAccountCreate)
jv[sfMinAccountCreateAmount.getJsonName()] =
minAccountCreate->getJson(JsonOptions::none);
jv[jss::TransactionType] = jss::XChainCreateBridge;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
bridge_modify(
Account const& acc,
Json::Value const& bridge,
std::optional<STAmount> const& reward,
std::optional<STAmount> const& minAccountCreate)
{
Json::Value jv;
jv[jss::Account] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
if (reward)
jv[sfSignatureReward.getJsonName()] =
reward->getJson(JsonOptions::none);
if (minAccountCreate)
jv[sfMinAccountCreateAmount.getJsonName()] =
minAccountCreate->getJson(JsonOptions::none);
jv[jss::TransactionType] = jss::XChainModifyBridge;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
xchain_create_claim_id(
Account const& acc,
Json::Value const& bridge,
STAmount const& reward,
Account const& otherChainSource)
{
Json::Value jv;
jv[jss::Account] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
jv[sfSignatureReward.getJsonName()] = reward.getJson(JsonOptions::none);
jv[sfOtherChainSource.getJsonName()] = otherChainSource.human();
jv[jss::TransactionType] = jss::XChainCreateClaimID;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
xchain_commit(
Account const& acc,
Json::Value const& bridge,
std::uint32_t claimID,
AnyAmount const& amt,
std::optional<Account> const& dst)
{
Json::Value jv;
jv[jss::Account] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
jv[sfXChainClaimID.getJsonName()] = claimID;
jv[jss::Amount] = amt.value.getJson(JsonOptions::none);
if (dst)
jv[sfOtherChainDestination.getJsonName()] = dst->human();
jv[jss::TransactionType] = jss::XChainCommit;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
xchain_claim(
Account const& acc,
Json::Value const& bridge,
std::uint32_t claimID,
AnyAmount const& amt,
Account const& dst)
{
Json::Value jv;
jv[sfAccount.getJsonName()] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
jv[sfXChainClaimID.getJsonName()] = claimID;
jv[sfDestination.getJsonName()] = dst.human();
jv[sfAmount.getJsonName()] = amt.value.getJson(JsonOptions::none);
jv[jss::TransactionType] = jss::XChainClaim;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
sidechain_xchain_account_create(
Account const& acc,
Json::Value const& bridge,
Account const& dst,
AnyAmount const& amt,
AnyAmount const& reward)
{
Json::Value jv;
jv[sfAccount.getJsonName()] = acc.human();
jv[sfXChainBridge.getJsonName()] = bridge;
jv[sfDestination.getJsonName()] = dst.human();
jv[sfAmount.getJsonName()] = amt.value.getJson(JsonOptions::none);
jv[sfSignatureReward.getJsonName()] =
reward.value.getJson(JsonOptions::none);
jv[jss::TransactionType] = jss::XChainAccountCreateCommit;
jv[jss::Flags] = tfUniversal;
return jv;
}
Json::Value
claim_attestation(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::Account const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<jtx::Account> const& dst,
jtx::signer const& signer)
{
STXChainBridge const stBridge(jvBridge);
auto const& pk = signer.account.pk();
auto const& sk = signer.account.sk();
auto const sig = sign_claim_attestation(
pk,
sk,
stBridge,
sendingAccount,
sendingAmount.value,
rewardAccount,
wasLockingChainSend,
claimID,
dst);
Json::Value result;
result[sfAccount.getJsonName()] = submittingAccount.human();
result[sfXChainBridge.getJsonName()] = jvBridge;
result[sfAttestationSignerAccount.getJsonName()] = signer.account.human();
result[sfPublicKey.getJsonName()] = strHex(pk.slice());
result[sfSignature.getJsonName()] = strHex(sig);
result[sfOtherChainSource.getJsonName()] = toBase58(sendingAccount);
result[sfAmount.getJsonName()] =
sendingAmount.value.getJson(JsonOptions::none);
result[sfAttestationRewardAccount.getJsonName()] = toBase58(rewardAccount);
result[sfWasLockingChainSend.getJsonName()] = wasLockingChainSend ? 1 : 0;
result[sfXChainClaimID.getJsonName()] =
STUInt64{claimID}.getJson(JsonOptions::none);
if (dst)
result[sfDestination.getJsonName()] = toBase58(*dst);
result[jss::TransactionType] = jss::XChainAddClaimAttestation;
result[jss::Flags] = tfUniversal;
return result;
}
Json::Value
create_account_attestation(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::AnyAmount const& rewardAmount,
jtx::Account const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
jtx::Account const& dst,
jtx::signer const& signer)
{
STXChainBridge const stBridge(jvBridge);
auto const& pk = signer.account.pk();
auto const& sk = signer.account.sk();
auto const sig = jtx::sign_create_account_attestation(
pk,
sk,
stBridge,
sendingAccount,
sendingAmount.value,
rewardAmount.value,
rewardAccount,
wasLockingChainSend,
createCount,
dst);
Json::Value result;
result[sfAccount.getJsonName()] = submittingAccount.human();
result[sfXChainBridge.getJsonName()] = jvBridge;
result[sfAttestationSignerAccount.getJsonName()] = signer.account.human();
result[sfPublicKey.getJsonName()] = strHex(pk.slice());
result[sfSignature.getJsonName()] = strHex(sig);
result[sfOtherChainSource.getJsonName()] = toBase58(sendingAccount);
result[sfAmount.getJsonName()] =
sendingAmount.value.getJson(JsonOptions::none);
result[sfAttestationRewardAccount.getJsonName()] = toBase58(rewardAccount);
result[sfWasLockingChainSend.getJsonName()] = wasLockingChainSend ? 1 : 0;
result[sfXChainAccountCreateCount.getJsonName()] =
STUInt64{createCount}.getJson(JsonOptions::none);
result[sfDestination.getJsonName()] = toBase58(dst);
result[sfSignatureReward.getJsonName()] =
rewardAmount.value.getJson(JsonOptions::none);
result[jss::TransactionType] = jss::XChainAddAccountCreateAttestation;
result[jss::Flags] = tfUniversal;
return result;
}
JValueVec
claim_attestations(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
std::vector<jtx::Account> const& rewardAccounts,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<jtx::Account> const& dst,
std::vector<jtx::signer> const& signers,
std::size_t const numAtts,
std::size_t const fromIdx)
{
assert(fromIdx + numAtts <= rewardAccounts.size());
assert(fromIdx + numAtts <= signers.size());
JValueVec vec;
vec.reserve(numAtts);
for (auto i = fromIdx; i < fromIdx + numAtts; ++i)
vec.emplace_back(claim_attestation(
submittingAccount,
jvBridge,
sendingAccount,
sendingAmount,
rewardAccounts[i],
wasLockingChainSend,
claimID,
dst,
signers[i]));
return vec;
}
JValueVec
create_account_attestations(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::AnyAmount const& rewardAmount,
std::vector<jtx::Account> const& rewardAccounts,
bool wasLockingChainSend,
std::uint64_t createCount,
jtx::Account const& dst,
std::vector<jtx::signer> const& signers,
std::size_t const numAtts,
std::size_t const fromIdx)
{
assert(fromIdx + numAtts <= rewardAccounts.size());
assert(fromIdx + numAtts <= signers.size());
JValueVec vec;
vec.reserve(numAtts);
for (auto i = fromIdx; i < fromIdx + numAtts; ++i)
vec.emplace_back(create_account_attestation(
submittingAccount,
jvBridge,
sendingAccount,
sendingAmount,
rewardAmount,
rewardAccounts[i],
wasLockingChainSend,
createCount,
dst,
signers[i]));
return vec;
}
XChainBridgeObjects::XChainBridgeObjects()
: mcDoor("mcDoor")
, mcAlice("mcAlice")
, mcBob("mcBob")
, mcCarol("mcCarol")
, mcGw("mcGw")
, scDoor("scDoor")
, scAlice("scAlice")
, scBob("scBob")
, scCarol("scCarol")
, scGw("scGw")
, scAttester("scAttester")
, scReward("scReward")
, mcuDoor("mcuDoor")
, mcuAlice("mcuAlice")
, mcuBob("mcuBob")
, mcuCarol("mcuCarol")
, mcuGw("mcuGw")
, scuDoor("scuDoor")
, scuAlice("scuAlice")
, scuBob("scuBob")
, scuCarol("scuCarol")
, scuGw("scuGw")
, mcUSD(mcGw["USD"])
, scUSD(scGw["USD"])
, jvXRPBridgeRPC(
bridge_rpc(mcDoor, xrpIssue(), Account::master, xrpIssue()))
, jvb(bridge(mcDoor, xrpIssue(), Account::master, xrpIssue()))
, jvub(bridge(mcuDoor, xrpIssue(), Account::master, xrpIssue()))
, features(supported_amendments() | FeatureBitset{featureXChainBridge})
, signers([] {
constexpr int numSigners = UT_XCHAIN_DEFAULT_NUM_SIGNERS;
std::vector<signer> result;
result.reserve(numSigners);
for (int i = 0; i < numSigners; ++i)
{
using namespace std::literals;
auto const a = Account(
"signer_"s + std::to_string(i),
(i % 2) ? KeyType::ed25519 : KeyType::secp256k1);
result.emplace_back(a);
}
return result;
}())
, alt_signers([] {
constexpr int numSigners = UT_XCHAIN_DEFAULT_NUM_SIGNERS;
std::vector<signer> result;
result.reserve(numSigners);
for (int i = 0; i < numSigners; ++i)
{
using namespace std::literals;
auto const a = Account(
"alt_signer_"s + std::to_string(i),
(i % 2) ? KeyType::ed25519 : KeyType::secp256k1);
result.emplace_back(a);
}
return result;
}())
, payee([&] {
std::vector<Account> r;
r.reserve(signers.size());
for (int i = 0, e = signers.size(); i != e; ++i)
{
r.push_back(scReward);
}
return r;
}())
, payees([&] {
std::vector<Account> r;
r.reserve(signers.size());
for (int i = 0, e = signers.size(); i != e; ++i)
{
using namespace std::literals;
auto const a = Account("reward_"s + std::to_string(i));
r.push_back(a);
}
return r;
}())
, quorum(UT_XCHAIN_DEFAULT_QUORUM)
, reward(XRP(1))
, split_reward_quorum(
divide(reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), reward.issue()))
, split_reward_everyone(divide(
reward,
STAmount(UT_XCHAIN_DEFAULT_NUM_SIGNERS),
reward.issue()))
, tiny_reward(drops(37))
, tiny_reward_split((divide(
tiny_reward,
STAmount(UT_XCHAIN_DEFAULT_QUORUM),
tiny_reward.issue())))
, tiny_reward_remainder(
tiny_reward -
multiply(
tiny_reward_split,
STAmount(UT_XCHAIN_DEFAULT_QUORUM),
tiny_reward.issue()))
, one_xrp(XRP(1))
, xrp_dust(divide(one_xrp, STAmount(10000), one_xrp.issue()))
{
}
void
XChainBridgeObjects::createMcBridgeObjects(Env& mcEnv)
{
STAmount xrp_funds{XRP(10000)};
mcEnv.fund(xrp_funds, mcDoor, mcAlice, mcBob, mcCarol, mcGw);
// Signer's list must match the attestation signers
mcEnv(jtx::signers(mcDoor, signers.size(), signers));
// create XRP bridges in both direction
auto const reward = XRP(1);
STAmount const minCreate = XRP(20);
mcEnv(bridge_create(mcDoor, jvb, reward, minCreate));
mcEnv.close();
}
void
XChainBridgeObjects::createScBridgeObjects(Env& scEnv)
{
STAmount xrp_funds{XRP(10000)};
scEnv.fund(
xrp_funds, scDoor, scAlice, scBob, scCarol, scGw, scAttester, scReward);
// Signer's list must match the attestation signers
scEnv(jtx::signers(Account::master, signers.size(), signers));
// create XRP bridges in both direction
auto const reward = XRP(1);
STAmount const minCreate = XRP(20);
scEnv(bridge_create(Account::master, jvb, reward, minCreate));
scEnv.close();
}
void
XChainBridgeObjects::createBridgeObjects(Env& mcEnv, Env& scEnv)
{
createMcBridgeObjects(mcEnv);
createScBridgeObjects(scEnv);
}
} // namespace jtx
} // namespace test
} // namespace ripple

View File

@@ -0,0 +1,260 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_XCHAINBRIDGE_H_INCLUDED
#define RIPPLE_TEST_JTX_XCHAINBRIDGE_H_INCLUDED
#include <ripple/json/json_value.h>
#include <ripple/protocol/SField.h>
#include <ripple/protocol/XChainAttestations.h>
#include <test/jtx/Account.h>
#include <test/jtx/amount.h>
#include <test/jtx/multisign.h>
namespace ripple {
namespace test {
namespace jtx {
using JValueVec = std::vector<Json::Value>;
constexpr std::size_t UT_XCHAIN_DEFAULT_NUM_SIGNERS = 5;
constexpr std::size_t UT_XCHAIN_DEFAULT_QUORUM = 4;
Json::Value
bridge(
Account const& lockingChainDoor,
Issue const& lockingChainIssue,
Account const& issuingChainDoor,
Issue const& issuingChainIssue);
Json::Value
bridge_create(
Account const& acc,
Json::Value const& bridge,
STAmount const& reward,
std::optional<STAmount> const& minAccountCreate = std::nullopt);
Json::Value
bridge_modify(
Account const& acc,
Json::Value const& bridge,
std::optional<STAmount> const& reward,
std::optional<STAmount> const& minAccountCreate = std::nullopt);
Json::Value
xchain_create_claim_id(
Account const& acc,
Json::Value const& bridge,
STAmount const& reward,
Account const& otherChainSource);
Json::Value
xchain_commit(
Account const& acc,
Json::Value const& bridge,
std::uint32_t claimID,
AnyAmount const& amt,
std::optional<Account> const& dst = std::nullopt);
Json::Value
xchain_claim(
Account const& acc,
Json::Value const& bridge,
std::uint32_t claimID,
AnyAmount const& amt,
Account const& dst);
Json::Value
sidechain_xchain_account_create(
Account const& acc,
Json::Value const& bridge,
Account const& dst,
AnyAmount const& amt,
AnyAmount const& xChainFee);
Json::Value
sidechain_xchain_account_claim(
Account const& acc,
Json::Value const& bridge,
Account const& dst,
AnyAmount const& amt);
Json::Value
claim_attestation(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::Account const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<jtx::Account> const& dst,
jtx::signer const& signer);
Json::Value
create_account_attestation(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::AnyAmount const& rewardAmount,
jtx::Account const& rewardAccount,
bool wasLockingChainSend,
std::uint64_t createCount,
jtx::Account const& dst,
jtx::signer const& signer);
JValueVec
claim_attestations(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
std::vector<jtx::Account> const& rewardAccounts,
bool wasLockingChainSend,
std::uint64_t claimID,
std::optional<jtx::Account> const& dst,
std::vector<jtx::signer> const& signers,
std::size_t const numAtts = UT_XCHAIN_DEFAULT_QUORUM,
std::size_t const fromIdx = 0);
JValueVec
create_account_attestations(
jtx::Account const& submittingAccount,
Json::Value const& jvBridge,
jtx::Account const& sendingAccount,
jtx::AnyAmount const& sendingAmount,
jtx::AnyAmount const& rewardAmount,
std::vector<jtx::Account> const& rewardAccounts,
bool wasLockingChainSend,
std::uint64_t createCount,
jtx::Account const& dst,
std::vector<jtx::signer> const& signers,
std::size_t const numAtts = UT_XCHAIN_DEFAULT_QUORUM,
std::size_t const fromIdx = 0);
struct XChainBridgeObjects
{
// funded accounts
Account const mcDoor;
Account const mcAlice;
Account const mcBob;
Account const mcCarol;
Account const mcGw;
Account const scDoor;
Account const scAlice;
Account const scBob;
Account const scCarol;
Account const scGw;
Account const scAttester;
Account const scReward;
// unfunded accounts
Account const mcuDoor;
Account const mcuAlice;
Account const mcuBob;
Account const mcuCarol;
Account const mcuGw;
Account const scuDoor;
Account const scuAlice;
Account const scuBob;
Account const scuCarol;
Account const scuGw;
IOU const mcUSD;
IOU const scUSD;
Json::Value const jvXRPBridgeRPC;
Json::Value jvb; // standard xrp bridge def for tx
Json::Value jvub; // standard xrp bridge def for tx, unfunded accounts
FeatureBitset const features;
std::vector<signer> const signers;
std::vector<signer> const alt_signers;
std::vector<Account> const payee;
std::vector<Account> const payees;
std::uint32_t const quorum;
STAmount const reward; // 1 xrp
STAmount const split_reward_quorum; // 250,000 drops
STAmount const split_reward_everyone; // 200,000 drops
const STAmount tiny_reward; // 37 drops
const STAmount tiny_reward_split; // 9 drops
const STAmount tiny_reward_remainder; // 1 drops
const STAmount one_xrp;
const STAmount xrp_dust;
static constexpr int drop_per_xrp = 1000000;
XChainBridgeObjects();
void
createMcBridgeObjects(Env& mcEnv);
void
createScBridgeObjects(Env& scEnv);
void
createBridgeObjects(Env& mcEnv, Env& scEnv);
JValueVec
att_create_acct_vec(
std::uint64_t createCount,
jtx::AnyAmount const& amt,
jtx::Account const& dst,
std::size_t const numAtts,
std::size_t const fromIdx = 0)
{
return create_account_attestations(
scAttester,
jvb,
mcCarol,
amt,
reward,
payees,
true,
createCount,
dst,
signers,
numAtts,
fromIdx);
}
Json::Value
create_bridge(
Account const& acc,
Json::Value const& bridge = Json::nullValue,
STAmount const& _reward = XRP(1),
std::optional<STAmount> const& minAccountCreate = std::nullopt)
{
return bridge_create(
acc,
bridge == Json::nullValue ? jvb : bridge,
_reward,
minAccountCreate);
}
};
} // namespace jtx
} // namespace test
} // namespace ripple
#endif

View File

@@ -23,6 +23,7 @@
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <test/jtx/xchain_bridge.h>
#include <boost/utility/string_ref.hpp>
@@ -550,7 +551,9 @@ public:
Account const gw{"gateway"};
auto const USD = gw["USD"];
Env env(*this);
auto const features =
supported_amendments() | FeatureBitset{featureXChainBridge};
Env env(*this, features);
// Make a lambda we can use to get "account_objects" easily.
auto acct_objs = [&env](
@@ -676,6 +679,142 @@ public:
BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
}
{
// Create a bridge
test::jtx::XChainBridgeObjects x;
Env scEnv(*this, envconfig(port_increment, 3), features);
x.createScBridgeObjects(scEnv);
auto scenv_acct_objs = [&](Account const& acct, char const* type) {
Json::Value params;
params[jss::account] = acct.human();
params[jss::type] = type;
params[jss::ledger_index] = "validated";
return scEnv.rpc("json", "account_objects", to_string(params));
};
Json::Value const resp =
scenv_acct_objs(Account::master, jss::bridge);
BEAST_EXPECT(acct_objs_is_size(resp, 1));
auto const& acct_bridge =
resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(
acct_bridge[sfAccount.jsonName] == Account::master.human());
BEAST_EXPECT(
acct_bridge[sfLedgerEntryType.getJsonName()] == "Bridge");
BEAST_EXPECT(
acct_bridge[sfXChainClaimID.getJsonName()].asUInt() == 0);
BEAST_EXPECT(
acct_bridge[sfXChainAccountClaimCount.getJsonName()].asUInt() ==
0);
BEAST_EXPECT(
acct_bridge[sfXChainAccountCreateCount.getJsonName()]
.asUInt() == 0);
BEAST_EXPECT(
acct_bridge[sfMinAccountCreateAmount.getJsonName()].asUInt() ==
20000000);
BEAST_EXPECT(
acct_bridge[sfSignatureReward.getJsonName()].asUInt() ==
1000000);
BEAST_EXPECT(acct_bridge[sfXChainBridge.getJsonName()] == x.jvb);
}
{
// Alice and Bob create a xchain sequence number that we can look
// for in the ledger.
test::jtx::XChainBridgeObjects x;
Env scEnv(*this, envconfig(port_increment, 3), features);
x.createScBridgeObjects(scEnv);
scEnv(
xchain_create_claim_id(x.scAlice, x.jvb, x.reward, x.mcAlice));
scEnv.close();
scEnv(xchain_create_claim_id(x.scBob, x.jvb, x.reward, x.mcBob));
scEnv.close();
auto scenv_acct_objs = [&](Account const& acct, char const* type) {
Json::Value params;
params[jss::account] = acct.human();
params[jss::type] = type;
params[jss::ledger_index] = "validated";
return scEnv.rpc("json", "account_objects", to_string(params));
};
{
// Find the xchain sequence number for Andrea.
Json::Value const resp =
scenv_acct_objs(x.scAlice, jss::xchain_owned_claim_id);
BEAST_EXPECT(acct_objs_is_size(resp, 1));
auto const& xchain_seq =
resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(
xchain_seq[sfAccount.jsonName] == x.scAlice.human());
BEAST_EXPECT(
xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 1);
}
{
// and the one for Bob
Json::Value const resp =
scenv_acct_objs(x.scBob, jss::xchain_owned_claim_id);
BEAST_EXPECT(acct_objs_is_size(resp, 1));
auto const& xchain_seq =
resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scBob.human());
BEAST_EXPECT(
xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 2);
}
}
{
test::jtx::XChainBridgeObjects x;
Env scEnv(*this, envconfig(port_increment, 3), features);
x.createScBridgeObjects(scEnv);
auto const amt = XRP(1000);
// send first batch of account create attestations, so the
// xchain_create_account_claim_id should be present on the door
// account (Account::master) to collect the signatures until a
// quorum is reached
scEnv(test::jtx::create_account_attestation(
x.scAttester,
x.jvb,
x.mcCarol,
amt,
x.reward,
x.payees[0],
true,
1,
x.scuAlice,
x.signers[0]));
scEnv.close();
auto scenv_acct_objs = [&](Account const& acct, char const* type) {
Json::Value params;
params[jss::account] = acct.human();
params[jss::type] = type;
params[jss::ledger_index] = "validated";
return scEnv.rpc("json", "account_objects", to_string(params));
};
{
// Find the xchain_create_account_claim_id
Json::Value const resp = scenv_acct_objs(
Account::master, jss::xchain_owned_create_account_claim_id);
BEAST_EXPECT(acct_objs_is_size(resp, 1));
auto const& xchain_create_account_claim_id =
resp[jss::result][jss::account_objects][0u];
BEAST_EXPECT(
xchain_create_account_claim_id[sfAccount.jsonName] ==
Account::master.human());
BEAST_EXPECT(
xchain_create_account_claim_id[sfXChainAccountCreateCount
.getJsonName()]
.asUInt() == 1);
}
}
// gw creates an offer that we can look for in the ledger.
env(offer(gw, USD(7), XRP(14)));
env.close();
@@ -690,7 +829,8 @@ public:
BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
}
{
// Create a payment channel from qw to alice that we can look for.
// Create a payment channel from qw to alice that we can look
// for.
Json::Value jvPayChan;
jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
jvPayChan[jss::Flags] = tfUniversal;
@@ -715,7 +855,7 @@ public:
payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
}
// Make gw multisigning by adding a signerList.
env(signers(gw, 6, {{alice, 7}}));
env(jtx::signers(gw, 6, {{alice, 7}}));
env.close();
{
// Find the signer list.

View File

@@ -21,12 +21,331 @@
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/beast/unit_test.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/STXChainBridge.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
#include <test/jtx/attester.h>
#include <test/jtx/multisign.h>
#include <test/jtx/xchain_bridge.h>
namespace ripple {
class LedgerRPC_XChain_test : public beast::unit_test::suite,
public test::jtx::XChainBridgeObjects
{
void
checkErrorValue(
Json::Value const& jv,
std::string const& err,
std::string const& msg)
{
if (BEAST_EXPECT(jv.isMember(jss::status)))
BEAST_EXPECT(jv[jss::status] == "error");
if (BEAST_EXPECT(jv.isMember(jss::error)))
BEAST_EXPECT(jv[jss::error] == err);
if (msg.empty())
{
BEAST_EXPECT(
jv[jss::error_message] == Json::nullValue ||
jv[jss::error_message] == "");
}
else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
BEAST_EXPECT(jv[jss::error_message] == msg);
}
void
testLedgerEntryBridge()
{
testcase("ledger_entry: bridge");
using namespace test::jtx;
Env mcEnv{*this, features};
Env scEnv(*this, envconfig(port_increment, 3), features);
createBridgeObjects(mcEnv, scEnv);
std::string const ledgerHash{to_string(mcEnv.closed()->info().hash)};
std::string bridge_index;
Json::Value mcBridge;
{
// request the bridge via RPC
Json::Value jvParams;
jvParams[jss::bridge_account] = mcDoor.human();
jvParams[jss::bridge] = jvb;
Json::Value const jrr = mcEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::node));
auto r = jrr[jss::node];
// std::cout << to_string(r) << '\n';
BEAST_EXPECT(r.isMember(jss::Account));
BEAST_EXPECT(r[jss::Account] == mcDoor.human());
BEAST_EXPECT(r.isMember(jss::Flags));
BEAST_EXPECT(r.isMember(sfLedgerEntryType.jsonName));
BEAST_EXPECT(r[sfLedgerEntryType.jsonName] == jss::Bridge);
// we not created an account yet
BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 0);
// we have not claimed a locking chain tx yet
BEAST_EXPECT(r.isMember(sfXChainAccountClaimCount.jsonName));
BEAST_EXPECT(r[sfXChainAccountClaimCount.jsonName].asInt() == 0);
BEAST_EXPECT(r.isMember(jss::index));
bridge_index = r[jss::index].asString();
mcBridge = r;
}
{
// request the bridge via RPC by index
Json::Value jvParams;
jvParams[jss::index] = bridge_index;
Json::Value const jrr = mcEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::node));
BEAST_EXPECT(jrr[jss::node] == mcBridge);
}
{
// swap door accounts and make sure we get an error value
Json::Value jvParams;
// Sidechain door account is "master", not scDoor
jvParams[jss::bridge_account] = Account::master.human();
jvParams[jss::bridge] = jvb;
jvParams[jss::ledger_hash] = ledgerHash;
Json::Value const jrr = mcEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "");
}
{
// create two claim ids and verify that the bridge counter was
// incremented
mcEnv(xchain_create_claim_id(mcAlice, jvb, reward, scAlice));
mcEnv.close();
mcEnv(xchain_create_claim_id(mcBob, jvb, reward, scBob));
mcEnv.close();
// request the bridge via RPC
Json::Value jvParams;
jvParams[jss::bridge_account] = mcDoor.human();
jvParams[jss::bridge] = jvb;
// std::cout << to_string(jvParams) << '\n';
Json::Value const jrr = mcEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::node));
auto r = jrr[jss::node];
// we executed two create claim id txs
BEAST_EXPECT(r.isMember(sfXChainClaimID.jsonName));
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
}
}
void
testLedgerEntryClaimID()
{
testcase("ledger_entry: xchain_claim_id");
using namespace test::jtx;
Env mcEnv{*this, features};
Env scEnv(*this, envconfig(port_increment, 3), features);
createBridgeObjects(mcEnv, scEnv);
scEnv(xchain_create_claim_id(scAlice, jvb, reward, mcAlice));
scEnv.close();
scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob));
scEnv.close();
std::string bridge_index;
{
// request the xchain_claim_id via RPC
Json::Value jvParams;
jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
1;
// std::cout << to_string(jvParams) << '\n';
Json::Value const jrr = scEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::node));
auto r = jrr[jss::node];
// std::cout << to_string(r) << '\n';
BEAST_EXPECT(r.isMember(jss::Account));
BEAST_EXPECT(r[jss::Account] == scAlice.human());
BEAST_EXPECT(
r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 1);
BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
}
{
// request the xchain_claim_id via RPC
Json::Value jvParams;
jvParams[jss::xchain_owned_claim_id] = jvXRPBridgeRPC;
jvParams[jss::xchain_owned_claim_id][jss::xchain_owned_claim_id] =
2;
Json::Value const jrr = scEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
BEAST_EXPECT(jrr.isMember(jss::node));
auto r = jrr[jss::node];
// std::cout << to_string(r) << '\n';
BEAST_EXPECT(r.isMember(jss::Account));
BEAST_EXPECT(r[jss::Account] == scBob.human());
BEAST_EXPECT(
r[sfLedgerEntryType.jsonName] == jss::XChainOwnedClaimID);
BEAST_EXPECT(r[sfXChainClaimID.jsonName].asInt() == 2);
BEAST_EXPECT(r[sfOwnerNode.jsonName].asInt() == 0);
}
}
void
testLedgerEntryCreateAccountClaimID()
{
testcase("ledger_entry: xchain_create_account_claim_id");
using namespace test::jtx;
Env mcEnv{*this, features};
Env scEnv(*this, envconfig(port_increment, 3), features);
// note: signers.size() and quorum are both 5 in createBridgeObjects
createBridgeObjects(mcEnv, scEnv);
auto scCarol =
Account("scCarol"); // Don't fund it - it will be created with the
// xchain transaction
auto const amt = XRP(1000);
mcEnv(sidechain_xchain_account_create(
mcAlice, jvb, scCarol, amt, reward));
mcEnv.close();
// send less than quorum of attestations (otherwise funds are
// immediately transferred and no "claim" object is created)
size_t constexpr num_attest = 3;
auto attestations = create_account_attestations(
scAttester,
jvb,
mcAlice,
amt,
reward,
payee,
/*wasLockingChainSend*/ true,
1,
scCarol,
signers,
UT_XCHAIN_DEFAULT_NUM_SIGNERS);
for (size_t i = 0; i < num_attest; ++i)
{
scEnv(attestations[i]);
}
scEnv.close();
{
// request the create account claim_id via RPC
Json::Value jvParams;
jvParams[jss::xchain_owned_create_account_claim_id] =
jvXRPBridgeRPC;
jvParams[jss::xchain_owned_create_account_claim_id]
[jss::xchain_owned_create_account_claim_id] = 1;
// std::cout << to_string(jvParams) << '\n';
Json::Value const jrr = scEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
// std::cout << to_string(jrr) << '\n';
BEAST_EXPECT(jrr.isMember(jss::node));
auto r = jrr[jss::node];
BEAST_EXPECT(r.isMember(jss::Account));
BEAST_EXPECT(r[jss::Account] == Account::master.human());
BEAST_EXPECT(r.isMember(sfXChainAccountCreateCount.jsonName));
BEAST_EXPECT(r[sfXChainAccountCreateCount.jsonName].asInt() == 1);
BEAST_EXPECT(
r.isMember(sfXChainCreateAccountAttestations.jsonName));
auto attest = r[sfXChainCreateAccountAttestations.jsonName];
BEAST_EXPECT(attest.isArray());
BEAST_EXPECT(attest.size() == 3);
BEAST_EXPECT(attest[Json::Value::UInt(0)].isMember(
sfXChainCreateAccountProofSig.jsonName));
Json::Value a[num_attest];
for (size_t i = 0; i < num_attest; ++i)
{
a[i] = attest[Json::Value::UInt(0)]
[sfXChainCreateAccountProofSig.jsonName];
BEAST_EXPECT(
a[i].isMember(jss::Amount) &&
a[i][jss::Amount].asInt() == 1000 * drop_per_xrp);
BEAST_EXPECT(
a[i].isMember(jss::Destination) &&
a[i][jss::Destination] == scCarol.human());
BEAST_EXPECT(
a[i].isMember(sfAttestationSignerAccount.jsonName) &&
std::any_of(
signers.begin(), signers.end(), [&](signer const& s) {
return a[i][sfAttestationSignerAccount.jsonName] ==
s.account.human();
}));
BEAST_EXPECT(
a[i].isMember(sfAttestationRewardAccount.jsonName) &&
std::any_of(
payee.begin(),
payee.end(),
[&](Account const& account) {
return a[i][sfAttestationRewardAccount.jsonName] ==
account.human();
}));
BEAST_EXPECT(
a[i].isMember(sfWasLockingChainSend.jsonName) &&
a[i][sfWasLockingChainSend.jsonName] == 1);
BEAST_EXPECT(
a[i].isMember(sfSignatureReward.jsonName) &&
a[i][sfSignatureReward.jsonName].asInt() ==
1 * drop_per_xrp);
}
}
// complete attestations quorum - CreateAccountClaimID should not be
// present anymore
for (size_t i = num_attest; i < UT_XCHAIN_DEFAULT_NUM_SIGNERS; ++i)
{
scEnv(attestations[i]);
}
scEnv.close();
{
// request the create account claim_id via RPC
Json::Value jvParams;
jvParams[jss::xchain_owned_create_account_claim_id] =
jvXRPBridgeRPC;
jvParams[jss::xchain_owned_create_account_claim_id]
[jss::xchain_owned_create_account_claim_id] = 1;
// std::cout << to_string(jvParams) << '\n';
Json::Value const jrr = scEnv.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "entryNotFound", "");
}
}
public:
void
run() override
{
testLedgerEntryBridge();
testLedgerEntryClaimID();
testLedgerEntryCreateAccountClaimID();
}
};
class LedgerRPC_test : public beast::unit_test::suite
{
void
@@ -1940,5 +2259,6 @@ public:
};
BEAST_DEFINE_TESTSUITE(LedgerRPC, app, ripple);
BEAST_DEFINE_TESTSUITE(LedgerRPC_XChain, app, ripple);
} // namespace ripple

View File

@@ -25,6 +25,7 @@
#include <test/jtx.h>
#include <test/jtx/WSClient.h>
#include <test/jtx/envconfig.h>
#include <tuple>
namespace ripple {
namespace test {
@@ -743,7 +744,7 @@ public:
using namespace std::chrono_literals;
using namespace jtx;
using IdxHashVec = std::vector<std::pair<int, std::string>>;
using IdxHashVec = std::vector<std::tuple<int, std::string, bool, int>>;
Account alice("alice");
Account bob("bob");
@@ -781,11 +782,14 @@ public:
idx = r[jss::account_history_tx_index].asInt();
if (r.isMember(jss::account_history_tx_first))
first_flag = true;
bool boundary = r.isMember(jss::account_history_boundary);
int ledger_idx = r[jss::ledger_index].asInt();
if (r.isMember(jss::transaction) &&
r[jss::transaction].isMember(jss::hash))
{
auto t{r[jss::transaction]};
v.emplace_back(
idx, r[jss::transaction][jss::hash].asString());
idx, t[jss::hash].asString(), boundary, ledger_idx);
continue;
}
}
@@ -838,13 +842,13 @@ public:
hash_map<std::string, int> txHistoryMap;
for (auto const& tx : txHistoryVec)
{
txHistoryMap.emplace(tx.second, tx.first);
txHistoryMap.emplace(std::get<1>(tx), std::get<0>(tx));
}
auto getHistoryIndex = [&](std::size_t i) -> std::optional<int> {
if (i >= accountVec.size())
return {};
auto it = txHistoryMap.find(accountVec[i].second);
auto it = txHistoryMap.find(std::get<1>(accountVec[i]));
if (it == txHistoryMap.end())
return {};
return it->second;
@@ -862,6 +866,48 @@ public:
return true;
};
// example of vector created from the return of `subscribe` rpc
// with jss::accounts
// boundary == true on last tx of ledger
// ------------------------------------------------------------
// (0, "E5B8B...", false, 4
// (0, "39E1C...", false, 4
// (0, "14EF1...", false, 4
// (0, "386E6...", false, 4
// (0, "00F3B...", true, 4
// (0, "1DCDC...", false, 5
// (0, "BD02A...", false, 5
// (0, "D3E16...", false, 5
// (0, "CB593...", false, 5
// (0, "8F28B...", true, 5
//
// example of vector created from the return of `subscribe` rpc
// with jss::account_history_tx_stream.
// boundary == true on first tx of ledger
// ------------------------------------------------------------
// (-1, "8F28B...", false, 5
// (-2, "CB593...", false, 5
// (-3, "D3E16...", false, 5
// (-4, "BD02A...", false, 5
// (-5, "1DCDC...", true, 5
// (-6, "00F3B...", false, 4
// (-7, "386E6...", false, 4
// (-8, "14EF1...", false, 4
// (-9, "39E1C...", false, 4
// (-10, "E5B8B...", true, 4
auto checkBoundary = [](IdxHashVec const& vec, bool /* forward */) {
size_t num_tx = vec.size();
for (size_t i = 0; i < num_tx; ++i)
{
auto [idx, hash, boundary, ledger] = vec[i];
if ((i + 1 == num_tx || ledger != std::get<3>(vec[i + 1])) !=
boundary)
return false;
}
return true;
};
///////////////////////////////////////////////////////////////////
{
@@ -880,6 +926,7 @@ public:
auto jv = wscTxHistory->invoke("subscribe", request);
if (!BEAST_EXPECT(goodSubRPC(jv)))
return;
jv = wscTxHistory->invoke("subscribe", request);
BEAST_EXPECT(!goodSubRPC(jv));
@@ -911,7 +958,6 @@ public:
r = getTxHash(*wscTxHistory, vec, 1);
BEAST_EXPECT(!r.first);
}
{
/*
* subscribe genesis account tx history without txns
@@ -950,8 +996,8 @@ public:
if (!BEAST_EXPECT(r.first && r.second))
return;
BEAST_EXPECT(
bobFullHistoryVec.back().second ==
genesisFullHistoryVec.back().second);
std::get<1>(bobFullHistoryVec.back()) ==
std::get<1>(genesisFullHistoryVec.back()));
/*
* unsubscribe to prepare next test
@@ -987,8 +1033,8 @@ public:
jv = wscTxHistory->invoke("unsubscribe", request);
BEAST_EXPECT(
bobFullHistoryVec.back().second ==
genesisFullHistoryVec.back().second);
std::get<1>(bobFullHistoryVec.back()) ==
std::get<1>(genesisFullHistoryVec.back()));
}
{
@@ -1030,11 +1076,17 @@ public:
if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
return;
// check boundary tags
// only account_history_tx_stream has ledger boundary information.
if (!BEAST_EXPECT(checkBoundary(txHistoryVec, false)))
return;
{
// take out all history txns from stream to prepare next test
IdxHashVec initFundTxns;
if (!BEAST_EXPECT(
getTxHash(*wscTxHistory, initFundTxns, 10).second))
getTxHash(*wscTxHistory, initFundTxns, 10).second) ||
!BEAST_EXPECT(checkBoundary(initFundTxns, false)))
return;
}
@@ -1046,6 +1098,12 @@ public:
return;
if (!BEAST_EXPECT(hashCompare(accountVec, txHistoryVec, true)))
return;
// check boundary tags
// only account_history_tx_stream has ledger boundary information.
if (!BEAST_EXPECT(checkBoundary(txHistoryVec, false)))
return;
wscTxHistory->invoke("unsubscribe", request);
wscAccount->invoke("unsubscribe", stream);
}