Skip to content

Commit

Permalink
feat: support Concise Transaction Identifier (CTID) (XLS-37) (XRPLF#4418
Browse files Browse the repository at this point in the history
)

* add CTIM to tx rpc


---------

Co-authored-by: Rome Reginelli <mduo13@gmail.com>
Co-authored-by: Elliot Lee <github.public@intelliot.com>
Co-authored-by: Denis Angell <dangell@transia.co>
  • Loading branch information
4 people authored Aug 18, 2023
1 parent aded4a7 commit 5530a0b
Show file tree
Hide file tree
Showing 15 changed files with 843 additions and 28 deletions.
1 change: 1 addition & 0 deletions Builds/CMake/RippledCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,7 @@ if (tests)
src/test/app/HashRouter_test.cpp
src/test/app/LedgerHistory_test.cpp
src/test/app/LedgerLoad_test.cpp
src/test/app/LedgerMaster_test.cpp
src/test/app/LedgerReplay_test.cpp
src/test/app/LoadFeeTrack_test.cpp
src/test/app/Manifest_test.cpp
Expand Down
4 changes: 4 additions & 0 deletions src/ripple/app/ledger/LedgerMaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,10 @@ class LedgerMaster : public AbstractFetchPackContainer
std::optional<LedgerIndex>
minSqlSeq();

// Iff a txn exists at the specified ledger and offset then return its txnid
std::optional<uint256>
txnIdFromIndex(uint32_t ledgerSeq, uint32_t txnIndex);

private:
void
setValidLedger(std::shared_ptr<Ledger const> const& l);
Expand Down
21 changes: 21 additions & 0 deletions src/ripple/app/ledger/impl/LedgerMaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2369,4 +2369,25 @@ LedgerMaster::minSqlSeq()
return app_.getRelationalDatabase().getMinLedgerSeq();
}

std::optional<uint256>
LedgerMaster::txnIdFromIndex(uint32_t ledgerSeq, uint32_t txnIndex)
{
uint32_t first = 0, last = 0;

if (!getValidatedRange(first, last) || last < ledgerSeq)
return {};

auto const lgr = getLedgerBySeq(ledgerSeq);
if (!lgr || lgr->txs.empty())
return {};

for (auto it = lgr->txs.begin(); it != lgr->txs.end(); ++it)
if (it->first && it->second &&
it->second->isFieldPresent(sfTransactionIndex) &&
it->second->getFieldU32(sfTransactionIndex) == txnIndex)
return it->first->getTransactionID();

return {};
}

} // namespace ripple
6 changes: 5 additions & 1 deletion src/ripple/net/impl/RPCCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1096,7 +1096,11 @@ class RPCParser
jvRequest[jss::max_ledger] = jvParams[2u + offset].asString();
}

jvRequest[jss::transaction] = jvParams[0u].asString();
if (jvParams[0u].asString().length() == 16)
jvRequest[jss::ctid] = jvParams[0u].asString();
else
jvRequest[jss::transaction] = jvParams[0u].asString();

return jvRequest;
}

Expand Down
2 changes: 1 addition & 1 deletion src/ripple/protocol/ErrorCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ enum error_code_i {
rpcJSON_RPC = 2,
rpcFORBIDDEN = 3,

rpcWRONG_NETWORK = 4,
// Misc failure
// unused 4,
// unused 5,
rpcNO_PERMISSION = 6,
rpcNO_EVENTS = 7,
Expand Down
1 change: 1 addition & 0 deletions src/ripple/protocol/jss.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ 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
Expand Down
88 changes: 88 additions & 0 deletions src/ripple/rpc/CTID.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2019 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_RPC_CTID_H_INCLUDED
#define RIPPLE_RPC_CTID_H_INCLUDED

#include <boost/algorithm/string/predicate.hpp>
#include <boost/regex.hpp>
#include <optional>
#include <regex>
#include <sstream>

namespace ripple {

namespace RPC {

inline std::optional<std::string>
encodeCTID(
uint32_t ledger_seq,
uint16_t txn_index,
uint16_t network_id) noexcept
{
if (ledger_seq > 0x0FFF'FFFF)
return {};

uint64_t ctidValue =
((0xC000'0000ULL + static_cast<uint64_t>(ledger_seq)) << 32) +
(static_cast<uint64_t>(txn_index) << 16) + network_id;

std::stringstream buffer;
buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
<< ctidValue;
return {buffer.str()};
}

template <typename T>
inline std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
decodeCTID(const T ctid) noexcept
{
uint64_t ctidValue{0};
if constexpr (
std::is_same_v<T, std::string> || std::is_same_v<T, char*> ||
std::is_same_v<T, const char*> || std::is_same_v<T, std::string_view>)
{
std::string const ctidString(ctid);

if (ctidString.length() != 16)
return {};

if (!boost::regex_match(ctidString, boost::regex("^[0-9A-F]+$")))
return {};

ctidValue = std::stoull(ctidString, nullptr, 16);
}
else if constexpr (std::is_integral_v<T>)
ctidValue = ctid;
else
return {};

if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL)
return {};

uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL;
uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
uint16_t network_id = ctidValue & 0xFFFFU;
return {{ledger_seq, txn_index, network_id}};
}

} // namespace RPC
} // namespace ripple

#endif
90 changes: 75 additions & 15 deletions src/ripple/rpc/handlers/Tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,17 @@
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/CTID.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/NFTSyntheticSerializer.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <charconv>
#include <regex>

namespace ripple {

// {
// transaction: <hex>
// }

static bool
isValidated(LedgerMaster& ledgerMaster, std::uint32_t seq, uint256 const& hash)
{
Expand All @@ -54,12 +53,14 @@ struct TxResult
Transaction::pointer txn;
std::variant<std::shared_ptr<TxMeta>, Blob> meta;
bool validated = false;
std::optional<std::string> ctid;
TxSearched searchedAll;
};

struct TxArgs
{
uint256 hash;
std::optional<uint256> hash;
std::optional<std::pair<uint32_t, uint16_t>> ctid;
bool binary = false;
std::optional<std::pair<uint32_t, uint32_t>> ledgerRange;
};
Expand All @@ -73,11 +74,19 @@ doTxPostgres(RPC::Context& context, TxArgs const& args)
Throw<std::runtime_error>(
"Called doTxPostgres yet not in reporting mode");
}

TxResult res;
res.searchedAll = TxSearched::unknown;

if (!args.hash)
return {
res,
{rpcNOT_IMPL,
"Use of CTIDs on reporting mode is not currently supported."}};

JLOG(context.j.debug()) << "Fetching from postgres";
Transaction::Locator locator = Transaction::locate(args.hash, context.app);
Transaction::Locator locator =
Transaction::locate(*(args.hash), context.app);

std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
pair;
Expand Down Expand Up @@ -127,7 +136,7 @@ doTxPostgres(RPC::Context& context, TxArgs const& args)
else
{
res.meta = std::make_shared<TxMeta>(
args.hash, res.txn->getLedger(), *meta);
*(args.hash), res.txn->getLedger(), *meta);
}
res.validated = true;
return {res, rpcSUCCESS};
Expand Down Expand Up @@ -168,7 +177,7 @@ doTxPostgres(RPC::Context& context, TxArgs const& args)
}

std::pair<TxResult, RPC::Status>
doTxHelp(RPC::Context& context, TxArgs const& args)
doTxHelp(RPC::Context& context, TxArgs args)
{
if (context.app.config().reporting())
return doTxPostgres(context, args);
Expand Down Expand Up @@ -196,15 +205,28 @@ doTxHelp(RPC::Context& context, TxArgs const& args)
std::pair<std::shared_ptr<Transaction>, std::shared_ptr<TxMeta>>;

result.searchedAll = TxSearched::unknown;

std::variant<TxPair, TxSearched> v;

if (args.ctid)
{
args.hash = context.app.getLedgerMaster().txnIdFromIndex(
args.ctid->first, args.ctid->second);

if (args.hash)
range =
ClosedInterval<uint32_t>(args.ctid->first, args.ctid->second);
}

if (!args.hash)
return {result, rpcTXN_NOT_FOUND};

if (args.ledgerRange)
{
v = context.app.getMasterTransaction().fetch(args.hash, range, ec);
v = context.app.getMasterTransaction().fetch(*(args.hash), range, ec);
}
else
{
v = context.app.getMasterTransaction().fetch(args.hash, ec);
v = context.app.getMasterTransaction().fetch(*(args.hash), ec);
}

if (auto e = std::get_if<TxSearched>(&v))
Expand Down Expand Up @@ -246,6 +268,15 @@ doTxHelp(RPC::Context& context, TxArgs const& args)
}
result.validated = isValidated(
context.ledgerMaster, ledger->info().seq, ledger->info().hash);

// compute outgoing CTID
uint32_t lgrSeq = ledger->info().seq;
uint32_t txnIdx = meta->getAsObject().getFieldU32(sfTransactionIndex);
uint32_t netID = context.app.config().NETWORK_ID;

if (txnIdx <= 0xFFFFU && netID < 0xFFFFU && lgrSeq < 0x0FFF'FFFFUL)
result.ctid =
RPC::encodeCTID(lgrSeq, (uint16_t)txnIdx, (uint16_t)netID);
}

return {result, rpcSUCCESS};
Expand Down Expand Up @@ -301,6 +332,9 @@ populateJsonResponse(
}
}
response[jss::validated] = result.validated;

if (result.ctid)
response[jss::ctid] = *(result.ctid);
}
return response;
}
Expand All @@ -313,13 +347,39 @@ doTxJson(RPC::JsonContext& context)

// Deserialize and validate JSON arguments

if (!context.params.isMember(jss::transaction))
TxArgs args;

if (context.params.isMember(jss::transaction) &&
context.params.isMember(jss::ctid))
// specifying both is ambiguous
return rpcError(rpcINVALID_PARAMS);

TxArgs args;
if (context.params.isMember(jss::transaction))
{
uint256 hash;
if (!hash.parseHex(context.params[jss::transaction].asString()))
return rpcError(rpcNOT_IMPL);
args.hash = hash;
}
else if (context.params.isMember(jss::ctid))
{
auto ctid = RPC::decodeCTID(context.params[jss::ctid].asString());
if (!ctid)
return rpcError(rpcINVALID_PARAMS);

if (!args.hash.parseHex(context.params[jss::transaction].asString()))
return rpcError(rpcNOT_IMPL);
auto const [lgr_seq, txn_idx, net_id] = *ctid;
if (net_id != context.app.config().NETWORK_ID)
{
std::stringstream out;
out << "Wrong network. You should submit this request to a node "
"running on NetworkID: "
<< net_id;
return RPC::make_error(rpcWRONG_NETWORK, out.str());
}
args.ctid = {lgr_seq, txn_idx};
}
else
return rpcError(rpcINVALID_PARAMS);

args.binary = context.params.isMember(jss::binary) &&
context.params[jss::binary].asBool();
Expand Down
1 change: 1 addition & 0 deletions src/ripple/rpc/impl/RPCHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <regex>

namespace ripple {
namespace RPC {
Expand Down
Loading

0 comments on commit 5530a0b

Please sign in to comment.