Files
rippled/src/libxrpl/server/Wallet.cpp
Jingchen ef284692db refactor: Modularize WalletDB and Manifest (#6223)
This change modularizes the `WalletDB` and `Manifest`. Note that the wallet db has nothing to do with account wallets and it stores node configuration, which is why it depends on the manifest code.
2026-02-11 13:42:31 +00:00

258 lines
8.4 KiB
C++

#include <xrpl/rdb/DBInit.h>
#include <xrpl/server/Wallet.h>
#include <boost/format.hpp>
namespace xrpl {
std::unique_ptr<DatabaseCon>
makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j)
{
// wallet database
return std::make_unique<DatabaseCon>(setup, WalletDBName, std::array<std::string, 0>(), WalletDBInit, j);
}
std::unique_ptr<DatabaseCon>
makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname, beast::Journal j)
{
// wallet database
return std::make_unique<DatabaseCon>(setup, dbname.data(), std::array<std::string, 0>(), WalletDBInit, j);
}
void
getManifests(soci::session& session, std::string const& dbTable, ManifestCache& mCache, beast::Journal j)
{
// Load manifests stored in database
std::string const sql = "SELECT RawData FROM " + dbTable + ";";
soci::blob sociRawData(session);
soci::statement st = (session.prepare << sql, soci::into(sociRawData));
st.execute();
while (st.fetch())
{
std::string serialized;
convert(sociRawData, serialized);
if (auto mo = deserializeManifest(serialized))
{
if (!mo->verify())
{
JLOG(j.warn()) << "Unverifiable manifest in db";
continue;
}
mCache.applyManifest(std::move(*mo));
}
else
{
JLOG(j.warn()) << "Malformed manifest in database";
}
}
}
static void
saveManifest(soci::session& session, std::string const& dbTable, std::string const& serialized)
{
// soci does not support bulk insertion of blob data
// Do not reuse blob because manifest ecdsa signatures vary in length
// but blob write length is expected to be >= the last write
soci::blob rawData(session);
convert(serialized, rawData);
session << "INSERT INTO " << dbTable << " (RawData) VALUES (:rawData);", soci::use(rawData);
}
void
saveManifests(
soci::session& session,
std::string const& dbTable,
std::function<bool(PublicKey const&)> const& isTrusted,
hash_map<PublicKey, Manifest> const& map,
beast::Journal j)
{
soci::transaction tr(session);
session << "DELETE FROM " << dbTable;
for (auto const& v : map)
{
// Save all revocation manifests,
// but only save trusted non-revocation manifests.
if (!v.second.revoked() && !isTrusted(v.second.masterKey))
{
JLOG(j.info()) << "Untrusted manifest in cache not saved to db";
continue;
}
saveManifest(session, dbTable, v.second.serialized);
}
tr.commit();
}
void
addValidatorManifest(soci::session& session, std::string const& serialized)
{
soci::transaction tr(session);
saveManifest(session, "ValidatorManifests", serialized);
tr.commit();
}
void
clearNodeIdentity(soci::session& session)
{
session << "DELETE FROM NodeIdentity;";
}
std::pair<PublicKey, SecretKey>
getNodeIdentity(soci::session& session)
{
{
// SOCI requires boost::optional (not std::optional) as the parameter.
boost::optional<std::string> pubKO, priKO;
soci::statement st =
(session.prepare << "SELECT PublicKey, PrivateKey FROM NodeIdentity;",
soci::into(pubKO),
soci::into(priKO));
st.execute();
while (st.fetch())
{
auto const sk = parseBase58<SecretKey>(TokenType::NodePrivate, priKO.value_or(""));
auto const pk = parseBase58<PublicKey>(TokenType::NodePublic, pubKO.value_or(""));
// Only use if the public and secret keys are a pair
if (sk && pk && (*pk == derivePublicKey(KeyType::secp256k1, *sk)))
return {*pk, *sk};
}
}
// If a valid identity wasn't found, we randomly generate a new one:
auto [newpublicKey, newsecretKey] = randomKeyPair(KeyType::secp256k1);
session << str(
boost::format("INSERT INTO NodeIdentity (PublicKey,PrivateKey) "
"VALUES ('%s','%s');") %
toBase58(TokenType::NodePublic, newpublicKey) % toBase58(TokenType::NodePrivate, newsecretKey));
return {newpublicKey, newsecretKey};
}
std::unordered_set<PeerReservation, beast::uhash<>, KeyEqual>
getPeerReservationTable(soci::session& session, beast::Journal j)
{
std::unordered_set<PeerReservation, beast::uhash<>, KeyEqual> table;
// These values must be boost::optionals (not std) because SOCI expects
// boost::optionals.
boost::optional<std::string> valPubKey, valDesc;
// We should really abstract the table and column names into constants,
// but no one else does. Because it is too tedious? It would be easy if we
// had a jOOQ for C++.
soci::statement st =
(session.prepare << "SELECT PublicKey, Description FROM PeerReservations;",
soci::into(valPubKey),
soci::into(valDesc));
st.execute();
while (st.fetch())
{
if (!valPubKey || !valDesc)
{
// This represents a `NULL` in a `NOT NULL` column. It should be
// unreachable.
continue;
}
auto const optNodeId = parseBase58<PublicKey>(TokenType::NodePublic, *valPubKey);
if (!optNodeId)
{
JLOG(j.warn()) << "load: not a public key: " << valPubKey;
continue;
}
table.insert(PeerReservation{*optNodeId, *valDesc});
}
return table;
}
void
insertPeerReservation(soci::session& session, PublicKey const& nodeId, std::string const& description)
{
auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
session << "INSERT INTO PeerReservations (PublicKey, Description) "
"VALUES (:nodeId, :desc) "
"ON CONFLICT (PublicKey) DO UPDATE SET "
"Description=excluded.Description",
soci::use(sNodeId), soci::use(description);
}
void
deletePeerReservation(soci::session& session, PublicKey const& nodeId)
{
auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId", soci::use(sNodeId);
}
bool
createFeatureVotes(soci::session& session)
{
soci::transaction tr(session);
std::string sql =
"SELECT count(*) FROM sqlite_master "
"WHERE type='table' AND name='FeatureVotes'";
// SOCI requires boost::optional (not std::optional) as the parameter.
boost::optional<int> featureVotesCount;
session << sql, soci::into(featureVotesCount);
bool exists = static_cast<bool>(*featureVotesCount);
// Create FeatureVotes table in WalletDB if it doesn't exist
if (!exists)
{
session << "CREATE TABLE FeatureVotes ( "
"AmendmentHash CHARACTER(64) NOT NULL, "
"AmendmentName TEXT, "
"Veto INTEGER NOT NULL );";
tr.commit();
}
return exists;
}
void
readAmendments(
soci::session& session,
std::function<void(
boost::optional<std::string> amendment_hash,
boost::optional<std::string> amendment_name,
boost::optional<AmendmentVote> vote)> const& callback)
{
// lambda that converts the internally stored int to an AmendmentVote.
auto intToVote = [](boost::optional<int> const& dbVote) -> boost::optional<AmendmentVote> {
return safe_cast<AmendmentVote>(dbVote.value_or(1));
};
soci::transaction tr(session);
std::string sql =
"SELECT AmendmentHash, AmendmentName, Veto FROM "
"( SELECT AmendmentHash, AmendmentName, Veto, RANK() OVER "
"( PARTITION BY AmendmentHash ORDER BY ROWID DESC ) "
"as rnk FROM FeatureVotes ) WHERE rnk = 1";
// SOCI requires boost::optional (not std::optional) as parameters.
boost::optional<std::string> amendment_hash;
boost::optional<std::string> amendment_name;
boost::optional<int> vote_to_veto;
soci::statement st =
(session.prepare << sql, soci::into(amendment_hash), soci::into(amendment_name), soci::into(vote_to_veto));
st.execute();
while (st.fetch())
{
callback(amendment_hash, amendment_name, intToVote(vote_to_veto));
}
}
void
voteAmendment(soci::session& session, uint256 const& amendment, std::string const& name, AmendmentVote vote)
{
soci::transaction tr(session);
std::string sql =
"INSERT INTO FeatureVotes (AmendmentHash, AmendmentName, Veto) VALUES "
"('";
sql += to_string(amendment);
sql += "', '" + name;
sql += "', '" + std::to_string(safe_cast<int>(vote)) + "');";
session << sql;
tr.commit();
}
} // namespace xrpl