Compare commits

..

3 Commits

Author SHA1 Message Date
Valentin Balaschenko
068cea41b6 Track latencies of certain code blocks, and log if they take too long 2025-06-15 22:01:36 +09:00
John Freeman
4cf97ad212 Use error codes throughout fast Base58 implementation 2025-06-15 21:59:37 +09:00
Mayukha Vadari
a0e19f9dcd Improve error handling in some RPC commands 2025-06-15 21:59:24 +09:00
53 changed files with 654 additions and 183 deletions

View File

@@ -1171,7 +1171,7 @@ if (tests)
#]===============================]
src/test/rpc/AccountCurrencies_test.cpp
src/test/rpc/AccountInfo_test.cpp
src/test/rpc/AccountLinesRPC_test.cpp
src/test/rpc/AccountLines_test.cpp
src/test/rpc/AccountObjects_test.cpp
src/test/rpc/AccountOffers_test.cpp
src/test/rpc/AccountNamespace_test.cpp

View File

@@ -25,6 +25,7 @@
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/PerfLog.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/chrono.h>
#include <ripple/consensus/LedgerTiming.h>
@@ -126,7 +127,13 @@ RCLValidationsAdaptor::now() const
std::optional<RCLValidatedLedger>
RCLValidationsAdaptor::acquire(LedgerHash const& hash)
{
auto ledger = app_.getLedgerMaster().getLedgerByHash(hash);
using namespace std::chrono_literals;
auto ledger = perf::measureDurationAndLog(
[&]() { return app_.getLedgerMaster().getLedgerByHash(hash); },
"getLedgerByHash",
10ms,
j_);
if (!ledger)
{
JLOG(j_.debug())

View File

@@ -23,6 +23,7 @@
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/basics/DecayingSample.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/PerfLog.h>
#include <ripple/beast/container/aged_map.h>
#include <ripple/beast/core/LexicalCast.h>
#include <ripple/core/JobQueue.h>
@@ -70,76 +71,83 @@ public:
std::uint32_t seq,
InboundLedger::Reason reason) override
{
assert(hash.isNonZero());
assert(
reason != InboundLedger::Reason::SHARD ||
(seq != 0 && app_.getShardStore()));
auto doAcquire = [&, seq, reason]() -> std::shared_ptr<Ledger const> {
assert(hash.isNonZero());
assert(
reason != InboundLedger::Reason::SHARD ||
(seq != 0 && app_.getShardStore()));
// probably not the right rule
if (app_.getOPs().isNeedNetworkLedger() &&
(reason != InboundLedger::Reason::GENERIC) &&
(reason != InboundLedger::Reason::CONSENSUS))
return {};
bool isNew = true;
std::shared_ptr<InboundLedger> inbound;
{
ScopedLockType sl(mLock);
if (stopping_)
{
// probably not the right rule
if (app_.getOPs().isNeedNetworkLedger() &&
(reason != InboundLedger::Reason::GENERIC) &&
(reason != InboundLedger::Reason::CONSENSUS))
return {};
bool isNew = true;
std::shared_ptr<InboundLedger> inbound;
{
ScopedLockType sl(mLock);
if (stopping_)
{
return {};
}
auto it = mLedgers.find(hash);
if (it != mLedgers.end())
{
isNew = false;
inbound = it->second;
}
else
{
inbound = std::make_shared<InboundLedger>(
app_,
hash,
seq,
reason,
std::ref(m_clock),
mPeerSetBuilder->build());
mLedgers.emplace(hash, inbound);
inbound->init(sl);
++mCounter;
}
}
auto it = mLedgers.find(hash);
if (it != mLedgers.end())
{
isNew = false;
inbound = it->second;
}
else
{
inbound = std::make_shared<InboundLedger>(
app_,
hash,
seq,
reason,
std::ref(m_clock),
mPeerSetBuilder->build());
mLedgers.emplace(hash, inbound);
inbound->init(sl);
++mCounter;
}
}
if (inbound->isFailed())
return {};
if (!isNew)
inbound->update(seq);
if (!inbound->isComplete())
return {};
if (reason == InboundLedger::Reason::HISTORY)
{
if (inbound->getLedger()->stateMap().family().isShardBacked())
app_.getNodeStore().storeLedger(inbound->getLedger());
}
else if (reason == InboundLedger::Reason::SHARD)
{
auto shardStore = app_.getShardStore();
if (!shardStore)
{
JLOG(j_.error())
<< "Acquiring shard with no shard store available";
if (inbound->isFailed())
return {};
if (!isNew)
inbound->update(seq);
if (!inbound->isComplete())
return {};
if (reason == InboundLedger::Reason::HISTORY)
{
if (inbound->getLedger()->stateMap().family().isShardBacked())
app_.getNodeStore().storeLedger(inbound->getLedger());
}
if (inbound->getLedger()->stateMap().family().isShardBacked())
shardStore->setStored(inbound->getLedger());
else
shardStore->storeLedger(inbound->getLedger());
}
return inbound->getLedger();
else if (reason == InboundLedger::Reason::SHARD)
{
auto shardStore = app_.getShardStore();
if (!shardStore)
{
JLOG(j_.error())
<< "Acquiring shard with no shard store available";
return {};
}
if (inbound->getLedger()->stateMap().family().isShardBacked())
shardStore->setStored(inbound->getLedger());
else
shardStore->storeLedger(inbound->getLedger());
}
return inbound->getLedger();
};
using namespace std::chrono_literals;
std::shared_ptr<Ledger const> ledger = perf::measureDurationAndLog(
doAcquire, "InboundLedgersImp::acquire", 500ms, j_);
return ledger;
}
void

View File

@@ -945,7 +945,7 @@ public:
auto setup = setup_DatabaseCon(*config_, m_journal);
setup.useGlobalPragma = false;
mWalletDB = makeWalletDB(setup);
mWalletDB = makeWalletDB(setup, m_journal);
}
catch (std::exception const& e)
{

View File

@@ -594,7 +594,7 @@ run(int argc, char** argv)
try
{
auto setup = setup_DatabaseCon(*config);
if (!doVacuumDB(setup))
if (!doVacuumDB(setup, config->journal()))
return -1;
}
catch (std::exception const& e)

View File

@@ -36,13 +36,15 @@ namespace ripple {
* download process or continues an existing one.
* @param setup Path to the database and other opening parameters.
* @param path Path of the new file to download.
* @param j Journal.
* @return Pair containing a unique pointer to the database and the amount of
* bytes already downloaded if a download is being continued.
*/
std::pair<std::unique_ptr<DatabaseCon>, std::optional<std::uint64_t>>
openDatabaseBodyDb(
DatabaseCon::Setup const& setup,
boost::filesystem::path const& path);
boost::filesystem::path const& path,
beast::Journal j);
/**
* @brief databaseBodyDoPut Saves a new fragment of a downloaded file.

View File

@@ -30,10 +30,14 @@ namespace ripple {
* descriptor.
* @param dir Path to the database to open.
* @param dbName Name of the database.
* @param j Journal.
* @return Unique pointer to the opened database.
*/
std::unique_ptr<DatabaseCon>
makeArchiveDB(boost::filesystem::path const& dir, std::string const& dbName);
makeArchiveDB(
boost::filesystem::path const& dir,
std::string const& dbName,
beast::Journal j);
/**
* @brief readArchiveDB Reads entries from the shard archive database and

View File

@@ -39,13 +39,15 @@ struct DatabasePair
* and returns their descriptors.
* @param config Config object.
* @param setup Path to the databases and other opening parameters.
* @param j Journal.
* @return Pair of unique pointers to the opened ledger and transaction
* databases.
*/
DatabasePair
makeShardCompleteLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup);
DatabaseCon::Setup const& setup,
beast::Journal j);
/**
* @brief makeShardIncompleteLedgerDBs Opens shard databases for partially
@@ -53,6 +55,7 @@ makeShardCompleteLedgerDBs(
* @param config Config object.
* @param setup Path to the databases and other opening parameters.
* @param checkpointerSetup Checkpointer parameters.
* @param j Journal.
* @return Pair of unique pointers to the opened ledger and transaction
* databases.
*/
@@ -60,7 +63,8 @@ DatabasePair
makeShardIncompleteLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup);
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j);
/**
* @brief updateLedgerDBs Saves the given ledger to shard databases.
@@ -86,12 +90,14 @@ updateLedgerDBs(
* descriptor.
* @param setup Path to the database and other opening parameters.
* @param checkpointerSetup Checkpointer parameters.
* @param j Journal.
* @return Unique pointer to the opened database.
*/
std::unique_ptr<DatabaseCon>
makeAcquireDB(
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup);
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j);
/**
* @brief insertAcquireDBIndex Adds a new shard index to the shard acquire

View File

@@ -27,10 +27,11 @@ namespace ripple {
/**
* @brief doVacuumDB Creates, initialises, and performs cleanup on a database.
* @param setup Path to the database and other opening parameters.
* @param j Journal.
* @return True if the vacuum process completed successfully.
*/
bool
doVacuumDB(DatabaseCon::Setup const& setup);
doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j);
} // namespace ripple

View File

@@ -32,19 +32,24 @@ namespace ripple {
/**
* @brief makeWalletDB Opens the wallet database and returns it.
* @param setup Path to the database and other opening parameters.
* @param j Journal.
* @return Unique pointer to the database descriptor.
*/
std::unique_ptr<DatabaseCon>
makeWalletDB(DatabaseCon::Setup const& setup);
makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j);
/**
* @brief makeTestWalletDB Opens a test wallet database with an arbitrary name.
* @param setup Path to the database and other opening parameters.
* @param dbname Name of the database.
* @param j Journal.
* @return Unique pointer to the database descriptor.
*/
std::unique_ptr<DatabaseCon>
makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname);
makeTestWalletDB(
DatabaseCon::Setup const& setup,
std::string const& dbname,
beast::Journal j);
/**
* @brief getManifests Loads a manifest from the wallet database and stores it

View File

@@ -47,6 +47,7 @@ struct DatabasePairValid
* @param config Config object.
* @param setup Path to database and opening parameters.
* @param checkpointerSetup Database checkpointer setup.
* @param j Journal.
* @return Struct DatabasePairValid which contain unique pointers to ledger
* and transaction databases and flag if opening was successfull.
*/
@@ -54,7 +55,8 @@ DatabasePairValid
makeLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup);
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j);
/**
* @brief getMinLedgerSeq Returns minimum ledger sequence in given table.

View File

@@ -37,6 +37,7 @@ namespace detail {
* @param config Config object.
* @param setup Path to database and opening parameters.
* @param checkpointerSetup Database checkpointer setup.
* @param j Journal.
* @return Struct DatabasePair which contains unique pointers to the ledger
* and transaction databases.
*/
@@ -44,7 +45,8 @@ DatabasePair
makeMetaDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup);
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j);
/**
* @brief saveLedgerMeta Stores (transaction ID -> shard index) and

View File

@@ -67,11 +67,12 @@ DatabasePairValid
makeLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j)
{
// ledger database
auto lgr{std::make_unique<DatabaseCon>(
setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup)};
setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup, j)};
lgr->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::lgrDBCache)));
@@ -80,7 +81,7 @@ makeLedgerDBs(
{
// transaction database
auto tx{std::make_unique<DatabaseCon>(
setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup)};
setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup, j)};
tx->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::txnDBCache)));

View File

@@ -32,7 +32,8 @@ DatabasePair
makeMetaDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j)
{
// ledger meta database
auto lgrMetaDB{std::make_unique<DatabaseCon>(
@@ -40,14 +41,20 @@ makeMetaDBs(
LgrMetaDBName,
LgrMetaDBPragma,
LgrMetaDBInit,
checkpointerSetup)};
checkpointerSetup,
j)};
if (!config.useTxTables())
return {std::move(lgrMetaDB), nullptr};
// transaction meta database
auto txMetaDB{std::make_unique<DatabaseCon>(
setup, TxMetaDBName, TxMetaDBPragma, TxMetaDBInit, checkpointerSetup)};
setup,
TxMetaDBName,
TxMetaDBPragma,
TxMetaDBInit,
checkpointerSetup,
j)};
return {std::move(lgrMetaDB), std::move(txMetaDB)};
}

View File

@@ -447,7 +447,7 @@ SQLiteDatabaseImp::makeLedgerDBs(
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
{
auto [lgr, tx, res] =
detail::makeLedgerDBs(config, setup, checkpointerSetup);
detail::makeLedgerDBs(config, setup, checkpointerSetup, j_);
txdb_ = std::move(tx);
lgrdb_ = std::move(lgr);
return res;
@@ -460,7 +460,7 @@ SQLiteDatabaseImp::makeMetaDBs(
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
{
auto [lgrMetaDB, txMetaDB] =
detail::makeMetaDBs(config, setup, checkpointerSetup);
detail::makeMetaDBs(config, setup, checkpointerSetup, j_);
txMetaDB_ = std::move(txMetaDB);
lgrMetaDB_ = std::move(lgrMetaDB);

View File

@@ -25,14 +25,15 @@ namespace ripple {
std::pair<std::unique_ptr<DatabaseCon>, std::optional<std::uint64_t>>
openDatabaseBodyDb(
DatabaseCon::Setup const& setup,
boost::filesystem::path const& path)
boost::filesystem::path const& path,
beast::Journal j)
{
// SOCI requires boost::optional (not std::optional) as the parameter.
boost::optional<std::string> pathFromDb;
boost::optional<std::uint64_t> size;
auto conn = std::make_unique<DatabaseCon>(
setup, "Download", DownloaderDBPragma, DatabaseBodyDBInit);
setup, "Download", DownloaderDBPragma, DatabaseBodyDBInit, j);
auto& session = *conn->checkoutDb();

View File

@@ -22,10 +22,13 @@
namespace ripple {
std::unique_ptr<DatabaseCon>
makeArchiveDB(boost::filesystem::path const& dir, std::string const& dbName)
makeArchiveDB(
boost::filesystem::path const& dir,
std::string const& dbName,
beast::Journal j)
{
return std::make_unique<DatabaseCon>(
dir, dbName, DownloaderDBPragma, ShardArchiveHandlerDBInit);
dir, dbName, DownloaderDBPragma, ShardArchiveHandlerDBInit, j);
}
void

View File

@@ -27,16 +27,17 @@ namespace ripple {
DatabasePair
makeShardCompleteLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup)
DatabaseCon::Setup const& setup,
beast::Journal j)
{
auto tx{std::make_unique<DatabaseCon>(
setup, TxDBName, FinalShardDBPragma, TxDBInit)};
setup, TxDBName, FinalShardDBPragma, TxDBInit, j)};
tx->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::txnDBCache, std::nullopt)));
auto lgr{std::make_unique<DatabaseCon>(
setup, LgrDBName, FinalShardDBPragma, LgrDBInit)};
setup, LgrDBName, FinalShardDBPragma, LgrDBInit, j)};
lgr->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::lgrDBCache, std::nullopt)));
@@ -48,18 +49,19 @@ DatabasePair
makeShardIncompleteLedgerDBs(
Config const& config,
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j)
{
// transaction database
auto tx{std::make_unique<DatabaseCon>(
setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup)};
setup, TxDBName, TxDBPragma, TxDBInit, checkpointerSetup, j)};
tx->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::txnDBCache)));
// ledger database
auto lgr{std::make_unique<DatabaseCon>(
setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup)};
setup, LgrDBName, LgrDBPragma, LgrDBInit, checkpointerSetup, j)};
lgr->getSession() << boost::str(
boost::format("PRAGMA cache_size=-%d;") %
kilobytes(config.getValueFor(SizedItem::lgrDBCache)));
@@ -209,14 +211,16 @@ updateLedgerDBs(
std::unique_ptr<DatabaseCon>
makeAcquireDB(
DatabaseCon::Setup const& setup,
DatabaseCon::CheckpointerSetup const& checkpointerSetup)
DatabaseCon::CheckpointerSetup const& checkpointerSetup,
beast::Journal j)
{
return std::make_unique<DatabaseCon>(
setup,
AcquireShardDBName,
AcquireShardDBPragma,
AcquireShardDBInit,
checkpointerSetup);
checkpointerSetup,
j);
}
void

View File

@@ -23,7 +23,7 @@
namespace ripple {
bool
doVacuumDB(DatabaseCon::Setup const& setup)
doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j)
{
boost::filesystem::path dbPath = setup.dataDir / TxDBName;
@@ -41,7 +41,7 @@ doVacuumDB(DatabaseCon::Setup const& setup)
}
auto txnDB =
std::make_unique<DatabaseCon>(setup, TxDBName, TxDBPragma, TxDBInit);
std::make_unique<DatabaseCon>(setup, TxDBName, TxDBPragma, TxDBInit, j);
auto& session = txnDB->getSession();
std::uint32_t pageSize;

View File

@@ -23,19 +23,22 @@
namespace ripple {
std::unique_ptr<DatabaseCon>
makeWalletDB(DatabaseCon::Setup const& setup)
makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j)
{
// wallet database
return std::make_unique<DatabaseCon>(
setup, WalletDBName, std::array<char const*, 0>(), WalletDBInit);
setup, WalletDBName, std::array<char const*, 0>(), WalletDBInit, j);
}
std::unique_ptr<DatabaseCon>
makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname)
makeTestWalletDB(
DatabaseCon::Setup const& setup,
std::string const& dbname,
beast::Journal j)
{
// wallet database
return std::make_unique<DatabaseCon>(
setup, dbname.data(), std::array<char const*, 0>(), WalletDBInit);
setup, dbname.data(), std::array<char const*, 0>(), WalletDBInit, j);
}
void

View File

@@ -179,6 +179,30 @@ make_PerfLog(
beast::Journal journal,
std::function<void()>&& signalStop);
template <typename Func, class Rep, class Period>
auto
measureDurationAndLog(
Func&& func,
const std::string& actionDescription,
std::chrono::duration<Rep, Period> maxDelay,
const beast::Journal& journal)
{
auto start_time = std::chrono::high_resolution_clock::now();
auto result = func();
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end_time - start_time);
if (duration > maxDelay)
{
JLOG(journal.warn())
<< actionDescription << " took " << duration.count() << " ms";
}
return result;
}
} // namespace perf
} // namespace ripple

View File

@@ -426,6 +426,12 @@ public:
int
getValueFor(SizedItem item, std::optional<std::size_t> node = std::nullopt)
const;
beast::Journal
journal() const
{
return j_;
}
};
FeeSetup

View File

@@ -21,6 +21,7 @@
#define RIPPLE_APP_DATA_DATABASECON_H_INCLUDED
#include <ripple/app/main/DBInit.h>
#include <ripple/basics/PerfLog.h>
#include <ripple/core/Config.h>
#include <ripple/core/SociDB.h>
#include <boost/filesystem/path.hpp>
@@ -115,7 +116,8 @@ public:
Setup const& setup,
std::string const& dbName,
std::array<char const*, N> const& pragma,
std::array<char const*, M> const& initSQL)
std::array<char const*, M> const& initSQL,
beast::Journal journal)
// Use temporary files or regular DB files?
: DatabaseCon(
setup.standAlone && !setup.reporting &&
@@ -126,7 +128,8 @@ public:
: (setup.dataDir / dbName),
setup.commonPragma(),
pragma,
initSQL)
initSQL,
journal)
{
}
@@ -137,8 +140,9 @@ public:
std::string const& dbName,
std::array<char const*, N> const& pragma,
std::array<char const*, M> const& initSQL,
CheckpointerSetup const& checkpointerSetup)
: DatabaseCon(setup, dbName, pragma, initSQL)
CheckpointerSetup const& checkpointerSetup,
beast::Journal journal)
: DatabaseCon(setup, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
@@ -148,8 +152,9 @@ public:
boost::filesystem::path const& dataDir,
std::string const& dbName,
std::array<char const*, N> const& pragma,
std::array<char const*, M> const& initSQL)
: DatabaseCon(dataDir / dbName, nullptr, pragma, initSQL)
std::array<char const*, M> const& initSQL,
beast::Journal journal)
: DatabaseCon(dataDir / dbName, nullptr, pragma, initSQL, journal)
{
}
@@ -160,8 +165,9 @@ public:
std::string const& dbName,
std::array<char const*, N> const& pragma,
std::array<char const*, M> const& initSQL,
CheckpointerSetup const& checkpointerSetup)
: DatabaseCon(dataDir, dbName, pragma, initSQL)
CheckpointerSetup const& checkpointerSetup,
beast::Journal journal)
: DatabaseCon(dataDir, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
@@ -177,7 +183,14 @@ public:
LockedSociSession
checkoutDb()
{
return LockedSociSession(session_, lock_);
using namespace std::chrono_literals;
LockedSociSession session = perf::measureDurationAndLog(
[&]() { return LockedSociSession(session_, lock_); },
"checkoutDb",
10ms,
j_);
return session;
}
private:
@@ -189,8 +202,9 @@ private:
boost::filesystem::path const& pPath,
std::vector<std::string> const* commonPragma,
std::array<char const*, N> const& pragma,
std::array<char const*, M> const& initSQL)
: session_(std::make_shared<soci::session>())
std::array<char const*, M> const& initSQL,
beast::Journal journal)
: session_(std::make_shared<soci::session>()), j_(journal)
{
open(*session_, "sqlite", pPath.string());
@@ -224,6 +238,8 @@ private:
// shared_ptr in this class. session_ will never be null.
std::shared_ptr<soci::session> const session_;
std::shared_ptr<Checkpointer> checkpointer_;
beast::Journal const j_;
};
// Return the checkpointer from its id. If the checkpointer no longer exists, an

View File

@@ -102,13 +102,16 @@ public:
@param io_service The asio context for running a strand.
@param ec Set to the error, if any occurred
@param j Journal.
*/
void
open(
boost::filesystem::path const& path,
Config const& config,
boost::asio::io_service& io_service,
boost::system::error_code& ec);
boost::system::error_code& ec,
beast::Journal j);
};
/** Algorithm for storing buffers when parsing.

View File

@@ -43,7 +43,8 @@ private:
getParser(
boost::filesystem::path dstPath,
std::function<void(boost::filesystem::path)> complete,
boost::system::error_code& ec) override;
boost::system::error_code& ec,
beast::Journal j) override;
bool
checkPath(boost::filesystem::path const& dstPath) override;

View File

@@ -113,7 +113,8 @@ private:
getParser(
boost::filesystem::path dstPath,
std::function<void(boost::filesystem::path)> complete,
boost::system::error_code& ec) = 0;
boost::system::error_code& ec,
beast::Journal j) = 0;
virtual bool
checkPath(boost::filesystem::path const& dstPath) = 0;

View File

@@ -47,7 +47,8 @@ DatabaseBody::value_type::open(
boost::filesystem::path const& path,
Config const& config,
boost::asio::io_service& io_service,
boost::system::error_code& ec)
boost::system::error_code& ec,
beast::Journal j)
{
strand_.reset(new boost::asio::io_service::strand(io_service));
path_ = path;
@@ -56,7 +57,7 @@ DatabaseBody::value_type::open(
setup.dataDir = path.parent_path();
setup.useGlobalPragma = false;
auto [conn, size] = openDatabaseBodyDb(setup, path);
auto [conn, size] = openDatabaseBodyDb(setup, path, j);
conn_ = std::move(conn);
if (size)
fileSize_ = *size;

View File

@@ -45,13 +45,14 @@ auto
DatabaseDownloader::getParser(
boost::filesystem::path dstPath,
std::function<void(boost::filesystem::path)> complete,
boost::system::error_code& ec) -> std::shared_ptr<parser>
boost::system::error_code& ec,
beast::Journal j) -> std::shared_ptr<parser>
{
using namespace boost::beast;
auto p = std::make_shared<http::response_parser<DatabaseBody>>();
p->body_limit(std::numeric_limits<std::uint64_t>::max());
p->get().body().open(dstPath, config_, io_service_, ec);
p->get().body().open(dstPath, config_, io_service_, ec, j);
if (ec)
p->get().body().close();

View File

@@ -146,7 +146,7 @@ HTTPDownloader::do_session(
if (stop_.load())
return exit();
auto p = this->getParser(dstPath, complete, ec);
auto p = this->getParser(dstPath, complete, ec, j_);
if (ec)
return failAndExit("getParser", p);

View File

@@ -859,7 +859,8 @@ Shard::open(std::lock_guard<std::mutex> const& lock)
acquireInfo_ = std::make_unique<AcquireInfo>();
acquireInfo_->SQLiteDB = makeAcquireDB(
setup,
DatabaseCon::CheckpointerSetup{&app_.getJobQueue(), &app_.logs()});
DatabaseCon::CheckpointerSetup{&app_.getJobQueue(), &app_.logs()},
j_);
state_ = ShardState::acquire;
progress_ = 0;
@@ -978,7 +979,7 @@ Shard::initSQLite(std::lock_guard<std::mutex> const&)
case ShardState::complete:
case ShardState::finalizing:
case ShardState::finalized: {
auto [lgr, tx] = makeShardCompleteLedgerDBs(config, setup);
auto [lgr, tx] = makeShardCompleteLedgerDBs(config, setup, j_);
lgrSQLiteDB_ = std::move(lgr);
lgrSQLiteDB_->getSession() << boost::str(
@@ -1002,7 +1003,8 @@ Shard::initSQLite(std::lock_guard<std::mutex> const&)
config,
setup,
DatabaseCon::CheckpointerSetup{
&app_.getJobQueue(), &app_.logs()});
&app_.getJobQueue(), &app_.logs()},
j_);
lgrSQLiteDB_ = std::move(lgr);
lgrSQLiteDB_->getSession() << boost::str(

View File

@@ -28,6 +28,7 @@
#include <ripple/app/misc/Transaction.h>
#include <ripple/app/misc/ValidatorList.h>
#include <ripple/app/tx/apply.h>
#include <ripple/basics/PerfLog.h>
#include <ripple/basics/UptimeClock.h>
#include <ripple/basics/base64.h>
#include <ripple/basics/random.h>
@@ -920,8 +921,16 @@ PeerImp::onReadMessage(error_code ec, std::size_t bytes_transferred)
while (read_buffer_.size() > 0)
{
std::size_t bytes_consumed;
std::tie(bytes_consumed, ec) =
invokeProtocolMessage(read_buffer_.data(), *this, hint);
using namespace std::chrono_literals;
std::tie(bytes_consumed, ec) = perf::measureDurationAndLog(
[&]() {
return invokeProtocolMessage(read_buffer_.data(), *this, hint);
},
"invokeProtocolMessage",
350ms,
journal_);
if (ec)
return fail("onReadMessage", ec);
if (!socket_.is_open())

View File

@@ -21,6 +21,7 @@
#define RIPPLE_PROTOCOL_B58_UTILS_H_INCLUDED
#include <ripple/basics/contract.h>
#include <ripple/protocol/impl/token_errors.h>
#include <boost/outcome.hpp>
#include <boost/outcome/result.hpp>
@@ -71,12 +72,12 @@ carrying_add(std::uint64_t a, std::uint64_t b)
// (i.e a[0] is the 2^0 coefficient, a[n] is the 2^(64*n) coefficient)
// panics if overflows (this is a specialized adder for b58 decoding.
// it should never overflow).
inline void
[[nodiscard]] inline TokenCodecErrc
inplace_bigint_add(std::span<std::uint64_t> a, std::uint64_t b)
{
if (a.size() <= 1)
{
ripple::LogicError("Input span too small for inplace_bigint_add");
return TokenCodecErrc::inputTooSmall;
}
std::uint64_t carry;
@@ -86,28 +87,29 @@ inplace_bigint_add(std::span<std::uint64_t> a, std::uint64_t b)
{
if (!carry)
{
return;
return TokenCodecErrc::success;
}
std::tie(v, carry) = carrying_add(v, 1);
}
if (carry)
{
LogicError("Overflow in inplace_bigint_add");
return TokenCodecErrc::overflowAdd;
}
return TokenCodecErrc::success;
}
inline void
[[nodiscard]] inline TokenCodecErrc
inplace_bigint_mul(std::span<std::uint64_t> a, std::uint64_t b)
{
if (a.empty())
{
LogicError("Empty span passed to inplace_bigint_mul");
return TokenCodecErrc::inputTooSmall;
}
auto const last_index = a.size() - 1;
if (a[last_index] != 0)
{
LogicError("Non-zero element in inplace_bigint_mul last index");
return TokenCodecErrc::inputTooLarge;
}
std::uint64_t carry = 0;
@@ -116,7 +118,9 @@ inplace_bigint_mul(std::span<std::uint64_t> a, std::uint64_t b)
std::tie(coeff, carry) = carrying_mul(coeff, b, carry);
}
a[last_index] = carry;
return TokenCodecErrc::success;
}
// divide a "big uint" value inplace and return the mod
// numerator is stored so smallest coefficients come first
[[nodiscard]] inline std::uint64_t
@@ -166,11 +170,7 @@ inplace_bigint_div_rem(std::span<uint64_t> numerator, std::uint64_t divisor)
b58_10_to_b58_be(std::uint64_t input)
{
constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10;
if (input >= B_58_10)
{
LogicError("Input to b58_10_to_b58_be equals or exceeds 58^10.");
}
assert(input < B_58_10);
constexpr std::size_t resultSize = 10;
std::array<std::uint8_t, resultSize> result{};
int i = 0;

View File

@@ -32,6 +32,7 @@ enum class TokenCodecErrc {
mismatchedTokenType,
mismatchedChecksum,
invalidEncodingChar,
overflowAdd,
unknown,
};
}

View File

@@ -472,6 +472,11 @@ b256_to_b58_be(std::span<std::uint8_t const> input, std::span<std::uint8_t> out)
{
continue;
}
static constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10;
if (base_58_10_coeff[i] >= B_58_10)
{
return Unexpected(TokenCodecErrc::inputTooLarge);
}
std::array<std::uint8_t, 10> const b58_be =
ripple::b58_fast::detail::b58_10_to_b58_be(base_58_10_coeff[i]);
std::size_t to_skip = 0;
@@ -570,10 +575,23 @@ b58_to_b256_be(std::string_view input, std::span<std::uint8_t> out)
for (int i = 1; i < num_b_58_10_coeffs; ++i)
{
std::uint64_t const c = b_58_10_coeff[i];
ripple::b58_fast::detail::inplace_bigint_mul(
std::span(&result[0], cur_result_size + 1), B_58_10);
ripple::b58_fast::detail::inplace_bigint_add(
std::span(&result[0], cur_result_size + 1), c);
{
auto code = ripple::b58_fast::detail::inplace_bigint_mul(
std::span(&result[0], cur_result_size + 1), B_58_10);
if (code != TokenCodecErrc::success)
{
return Unexpected(code);
}
}
{
auto code = ripple::b58_fast::detail::inplace_bigint_add(
std::span(&result[0], cur_result_size + 1), c);
if (code != TokenCodecErrc::success)
{
return Unexpected(code);
}
}
if (result[cur_result_size] != 0)
{
cur_result_size += 1;

View File

@@ -71,6 +71,9 @@ doAccountChannels(RPC::JsonContext& context)
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (!ledger)

View File

@@ -33,19 +33,29 @@ doAccountCurrencies(RPC::JsonContext& context)
{
auto& params = context.params;
if (!(params.isMember(jss::account) || params.isMember(jss::ident)))
return RPC::missing_field_error(jss::account);
std::string strIdent;
if (params.isMember(jss::account))
{
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
strIdent = params[jss::account].asString();
}
else if (params.isMember(jss::ident))
{
if (!params[jss::ident].isString())
return RPC::invalid_field_error(jss::ident);
strIdent = params[jss::ident].asString();
}
// Get the current ledger
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (!ledger)
return result;
if (!(params.isMember(jss::account) || params.isMember(jss::ident)))
return RPC::missing_field_error(jss::account);
std::string const strIdent(
params.isMember(jss::account) ? params[jss::account].asString()
: params[jss::ident].asString());
// Get info on account.
auto id = parseBase58<AccountID>(strIdent);
if (!id)

View File

@@ -23,6 +23,7 @@
#include <ripple/ledger/ReadView.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/RPCErr.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
@@ -53,9 +54,17 @@ doAccountInfo(RPC::JsonContext& context)
std::string strIdent;
if (params.isMember(jss::account))
{
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
strIdent = params[jss::account].asString();
}
else if (params.isMember(jss::ident))
{
if (!params[jss::ident].isString())
return RPC::invalid_field_error(jss::ident);
strIdent = params[jss::ident].asString();
}
else
return RPC::missing_field_error(jss::account);

View File

@@ -92,6 +92,9 @@ doAccountLines(RPC::JsonContext& context)
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (!ledger)

View File

@@ -54,17 +54,19 @@ doAccountNFTs(RPC::JsonContext& context)
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (ledger == nullptr)
return result;
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
auto id = parseBase58<AccountID>(params[jss::account].asString());
if (!id)
{
RPC::inject_error(rpcACT_MALFORMED, result);
return result;
return rpcError(rpcACT_MALFORMED);
}
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (ledger == nullptr)
return result;
auto const accountID{id.value()};
if (!ledger->exists(keylet::account(accountID)))
@@ -167,6 +169,9 @@ doAccountObjects(RPC::JsonContext& context)
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (ledger == nullptr)

View File

@@ -60,6 +60,9 @@ doAccountOffers(RPC::JsonContext& context)
if (!params.isMember(jss::account))
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
std::shared_ptr<ReadView const> ledger;
auto result = RPC::lookupLedger(ledger, context);
if (!ledger)
@@ -84,7 +87,7 @@ doAccountOffers(RPC::JsonContext& context)
return *err;
if (limit == 0)
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::limit);
Json::Value& jsonOffers(result[jss::offers] = Json::arrayValue);
std::vector<std::shared_ptr<SLE const>> offers;
@@ -101,13 +104,13 @@ doAccountOffers(RPC::JsonContext& context)
std::stringstream marker(params[jss::marker].asString());
std::string value;
if (!std::getline(marker, value, ','))
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::marker);
if (!startAfter.parseHex(value))
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::marker);
if (!std::getline(marker, value, ','))
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::marker);
try
{
@@ -115,7 +118,7 @@ doAccountOffers(RPC::JsonContext& context)
}
catch (boost::bad_lexical_cast&)
{
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::marker);
}
// We then must check if the object pointed to by the marker is actually

View File

@@ -426,12 +426,12 @@ doAccountTxJson(RPC::JsonContext& context)
if (context.apiVersion > 1u && params.isMember(jss::binary) &&
!params[jss::binary].isBool())
{
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::binary);
}
if (context.apiVersion > 1u && params.isMember(jss::forward) &&
!params[jss::forward].isBool())
{
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::forward);
}
args.limit = params.isMember(jss::limit) ? params[jss::limit].asUInt() : 0;
@@ -440,7 +440,10 @@ doAccountTxJson(RPC::JsonContext& context)
params.isMember(jss::forward) && params[jss::forward].asBool();
if (!params.isMember(jss::account))
return rpcError(rpcINVALID_PARAMS);
return RPC::missing_field_error(jss::account);
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
auto const account =
parseBase58<AccountID>(params[jss::account].asString());

View File

@@ -66,6 +66,10 @@ doNoRippleCheck(RPC::JsonContext& context)
if (!params.isMember("role"))
return RPC::missing_field_error("role");
if (!params[jss::account].isString())
return RPC::invalid_field_error(jss::account);
bool roleGateway = false;
{
std::string const role = params["role"].asString();
@@ -90,7 +94,7 @@ doNoRippleCheck(RPC::JsonContext& context)
if (context.apiVersion > 1u && params.isMember(jss::transactions) &&
!params[jss::transactions].isBool())
{
return rpcError(rpcINVALID_PARAMS);
return RPC::invalid_field_error(jss::transactions);
}
std::shared_ptr<ReadView const> ledger;

View File

@@ -114,7 +114,7 @@ ShardArchiveHandler::init()
{
create_directories(downloadDir_);
sqlDB_ = makeArchiveDB(downloadDir_, stateDBName);
sqlDB_ = makeArchiveDB(downloadDir_, stateDBName, j_);
}
catch (std::exception const& e)
{
@@ -139,7 +139,7 @@ ShardArchiveHandler::initFromDB(std::lock_guard<std::mutex> const& lock)
exists(downloadDir_ / stateDBName) &&
is_regular_file(downloadDir_ / stateDBName));
sqlDB_ = makeArchiveDB(downloadDir_, stateDBName);
sqlDB_ = makeArchiveDB(downloadDir_, stateDBName, j_);
readArchiveDB(*sqlDB_, [&](std::string const& url_, int state) {
parsedURL url;

View File

@@ -255,7 +255,7 @@ public:
setup.dataDir = getDatabasePath();
assert(!setup.useGlobalPragma);
auto dbCon = makeTestWalletDB(setup, dbName);
auto dbCon = makeTestWalletDB(setup, dbName, env.journal);
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*> {

View File

@@ -953,6 +953,25 @@ struct PayChan_test : public beast::unit_test::suite
auto const chan1Str = to_string(channel(alice, bob, env.seq(alice)));
env(paychan::create(alice, bob, channelFunds, settleDelay, pk));
env.close();
{
// test account non-string
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_channels", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{
auto const r =
env.rpc("account_channels", alice.human(), bob.human());

View File

@@ -177,6 +177,7 @@ class base58_test : public beast::unit_test::suite
constexpr std::size_t iters = 100000;
auto eng = randEngine();
std::uniform_int_distribution<std::uint64_t> dist;
std::uniform_int_distribution<std::uint64_t> dist1(1);
for (int i = 0; i < iters; ++i)
{
std::uint64_t const d = dist(eng);
@@ -209,12 +210,31 @@ class base58_test : public beast::unit_test::suite
auto const refAdd = boostBigInt + d;
b58_fast::detail::inplace_bigint_add(
auto const result = b58_fast::detail::inplace_bigint_add(
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
BEAST_EXPECT(result == TokenCodecErrc::success);
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
BEAST_EXPECT(refAdd == foundAdd);
}
for (int i = 0; i < iters; ++i)
{
std::uint64_t const d = dist1(eng);
// Force overflow
std::vector<std::uint64_t> bigInt(
5, std::numeric_limits<std::uint64_t>::max());
auto const boostBigInt = multiprecision_utils::toBoostMP(
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
auto const refAdd = boostBigInt + d;
auto const result = b58_fast::detail::inplace_bigint_add(
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
BEAST_EXPECT(result == TokenCodecErrc::overflowAdd);
auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
BEAST_EXPECT(refAdd != foundAdd);
}
for (int i = 0; i < iters; ++i)
{
std::uint64_t const d = dist(eng);
auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
@@ -226,11 +246,29 @@ class base58_test : public beast::unit_test::suite
auto const refMul = boostBigInt * d;
b58_fast::detail::inplace_bigint_mul(
auto const result = b58_fast::detail::inplace_bigint_mul(
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
BEAST_EXPECT(result == TokenCodecErrc::success);
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
BEAST_EXPECT(refMul == foundMul);
}
for (int i = 0; i < iters; ++i)
{
std::uint64_t const d = dist1(eng);
// Force overflow
std::vector<std::uint64_t> bigInt(
5, std::numeric_limits<std::uint64_t>::max());
auto const boostBigInt = multiprecision_utils::toBoostMP(
std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
auto const refMul = boostBigInt * d;
auto const result = b58_fast::detail::inplace_bigint_mul(
std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
BEAST_EXPECT(result == TokenCodecErrc::inputTooLarge);
auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
BEAST_EXPECT(refMul != foundMul);
}
}
void

View File

@@ -39,6 +39,7 @@ class AccountCurrencies_test : public beast::unit_test::suite
{ // invalid ledger (hash)
Json::Value params;
params[jss::account] = Account{"bob"}.human();
params[jss::ledger_hash] = 1;
auto const result = env.rpc(
"json",
@@ -56,6 +57,50 @@ class AccountCurrencies_test : public beast::unit_test::suite
result[jss::error_message] == "Missing field 'account'.");
}
{
// test account non-string
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json",
"account_currencies",
to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{
// test ident non-string
auto testInvalidIdentParam = [&](auto const& param) {
Json::Value params;
params[jss::ident] = param;
auto jrr = env.rpc(
"json",
"account_currencies",
to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'ident'.");
};
testInvalidIdentParam(1);
testInvalidIdentParam(1.1);
testInvalidIdentParam(true);
testInvalidIdentParam(Json::Value(Json::nullValue));
testInvalidIdentParam(Json::Value(Json::objectValue));
testInvalidIdentParam(Json::Value(Json::arrayValue));
}
{
Json::Value params;
params[jss::account] =
@@ -198,6 +243,6 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(AccountCurrencies, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountCurrencies, rpc, ripple);
} // namespace ripple

View File

@@ -36,6 +36,7 @@ public:
void
testErrors()
{
testcase("Errors");
using namespace jtx;
Env env(*this);
{
@@ -78,12 +79,53 @@ public:
BEAST_EXPECT(
info[jss::result][jss::error_message] == "Account malformed.");
}
{
// Cannot pass a non-string into the `account` param
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_info", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{
// Cannot pass a non-string into the `ident` param
auto testInvalidIdentParam = [&](auto const& param) {
Json::Value params;
params[jss::ident] = param;
auto jrr = env.rpc(
"json", "account_info", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'ident'.");
};
testInvalidIdentParam(1);
testInvalidIdentParam(1.1);
testInvalidIdentParam(true);
testInvalidIdentParam(Json::Value(Json::nullValue));
testInvalidIdentParam(Json::Value(Json::objectValue));
testInvalidIdentParam(Json::Value(Json::arrayValue));
}
}
// Test the "signer_lists" argument in account_info.
void
testSignerLists()
{
testcase("Signer lists");
using namespace jtx;
Env env(*this);
Account const alice{"alice"};
@@ -205,6 +247,7 @@ public:
void
testSignerListsApiVersion2()
{
testcase("Signer lists APIv2");
using namespace jtx;
Env env{*this};
Account const alice{"alice"};
@@ -326,6 +369,7 @@ public:
void
testSignerListsV2()
{
testcase("Signer lists v2");
using namespace jtx;
Env env(*this);
Account const alice{"alice"};
@@ -515,6 +559,7 @@ public:
void
testAccountFlags(FeatureBitset const& features)
{
testcase("Account flags");
using namespace jtx;
Env env(*this, features);
@@ -654,7 +699,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(AccountInfo, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountInfo, rpc, ripple);
} // namespace test
} // namespace ripple

View File

@@ -27,7 +27,7 @@ namespace ripple {
namespace RPC {
class AccountLinesRPC_test : public beast::unit_test::suite
class AccountLines_test : public beast::unit_test::suite
{
public:
void
@@ -55,6 +55,25 @@ public:
lines[jss::result][jss::error_message] ==
RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
}
{
// test account non-string
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_lines", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
Account const alice{"alice"};
{
// account_lines on an unfunded account.
@@ -1474,7 +1493,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(AccountLinesRPC, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountLines, rpc, ripple);
} // namespace RPC
} // namespace ripple

View File

@@ -125,8 +125,30 @@ public:
// test error on no account
{
auto resp = env.rpc("json", "account_objects");
BEAST_EXPECT(resp[jss::error_message] == "Syntax error.");
Json::Value params;
auto resp = env.rpc("json", "account_objects", to_string(params));
BEAST_EXPECT(
resp[jss::result][jss::error_message] ==
"Missing field 'account'.");
}
// test account non-string
{
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_objects", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
// test error on malformed account string.
{
@@ -1090,6 +1112,35 @@ public:
BEAST_EXPECT(acct_objs_is_size(acct_objs(gw, jss::hashes), 0));
}
void
testAccountNFTs()
{
testcase("account_nfts");
using namespace jtx;
Env env(*this);
// test validation
{
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_nfts", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
}
void
run() override
{
@@ -1099,10 +1150,11 @@ public:
testUnsteppedThenStepped(all);
testUnsteppedThenSteppedWithNFTs(all);
testObjectTypes(all);
testAccountNFTs();
}
};
BEAST_DEFINE_TESTSUITE(AccountObjects, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountObjects, rpc, ripple);
} // namespace test
} // namespace ripple

View File

@@ -37,6 +37,8 @@ public:
void
testNonAdminMinLimit(FeatureBitset features)
{
testcase("Non-Admin Min Limit");
using namespace jtx;
Env env{*this, envconfig(no_admin), features};
Account const gw("G1");
@@ -81,6 +83,9 @@ public:
void
testSequential(FeatureBitset features, bool asAdmin)
{
testcase(
std::string("Sequential - ") + (asAdmin ? "admin" : "non-admin"));
using namespace jtx;
Env env{*this, asAdmin ? envconfig() : envconfig(no_admin), features};
Account const gw("G1");
@@ -215,6 +220,8 @@ public:
void
testBadInput(FeatureBitset features)
{
testcase("Bad input");
using namespace jtx;
Env env(*this, features);
Account const gw("G1");
@@ -233,6 +240,26 @@ public:
BEAST_EXPECT(jrr[jss::error_message] == "Syntax error.");
}
{
// test account non-string
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_offers", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{
// empty string account
Json::Value jvParams;
@@ -282,7 +309,9 @@ public:
jvParams.toStyledString())[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::status] == "error");
BEAST_EXPECT(jrr[jss::error_message] == "Invalid parameters.");
BEAST_EXPECTS(
jrr[jss::error_message] == "Invalid field 'marker'.",
jrr.toStyledString());
}
{
@@ -328,7 +357,7 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(AccountOffers, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountOffers, rpc, ripple);
} // namespace test
} // namespace ripple

View File

@@ -112,6 +112,7 @@ class AccountTx_test : public beast::unit_test::suite
void
testParameters(unsigned int apiVersion)
{
testcase("Parameters APIv" + std::to_string(apiVersion));
using namespace test::jtx;
Env env(*this, supported_amendments() - featureXahauGenesis);
@@ -356,6 +357,25 @@ class AccountTx_test : public beast::unit_test::suite
env.rpc("json", "account_tx", to_string(p)),
rpcLGR_IDX_MALFORMED));
}
// test account non-string
{
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
auto jrr = env.rpc(
"json", "account_tx", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
// test binary and forward for bool/non bool values
{
Json::Value p{jParms};
@@ -391,6 +411,8 @@ class AccountTx_test : public beast::unit_test::suite
void
testContents()
{
testcase("Contents");
// Get results for all transaction types that can be associated
// with an account. Start by generating all transaction types.
using namespace test::jtx;
@@ -787,6 +809,8 @@ class AccountTx_test : public beast::unit_test::suite
void
testAccountDelete()
{
testcase("AccountDelete");
// Verify that if an account is resurrected then the account_tx RPC
// command still recovers all transactions on that account before
// and after resurrection.
@@ -927,7 +951,7 @@ public:
testAccountDelete();
}
};
BEAST_DEFINE_TESTSUITE(AccountTx, app, ripple);
BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple);
} // namespace test
} // namespace ripple

View File

@@ -64,6 +64,27 @@ class NoRippleCheck_test : public beast::unit_test::suite
BEAST_EXPECT(result[jss::error_message] == "Missing field 'role'.");
}
// test account non-string
{
auto testInvalidAccountParam = [&](auto const& param) {
Json::Value params;
params[jss::account] = param;
params[jss::role] = "user";
auto jrr = env.rpc(
"json", "noripple_check", to_string(params))[jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(
jrr[jss::error_message] == "Invalid field 'account'.");
};
testInvalidAccountParam(1);
testInvalidAccountParam(1.1);
testInvalidAccountParam(true);
testInvalidAccountParam(Json::Value(Json::nullValue));
testInvalidAccountParam(Json::Value(Json::objectValue));
testInvalidAccountParam(Json::Value(Json::arrayValue));
}
{ // invalid role field
Json::Value params;
params[jss::account] = alice.human();
@@ -369,12 +390,12 @@ public:
}
};
BEAST_DEFINE_TESTSUITE(NoRippleCheck, app, ripple);
BEAST_DEFINE_TESTSUITE(NoRippleCheck, rpc, ripple);
// These tests that deal with limit amounts are slow because of the
// offer/account setup, so making them manual -- the additional coverage
// provided by them is minimal
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, app, ripple, 1);
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, rpc, ripple, 1);
} // namespace ripple