mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-04 17:27:00 +00:00
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.
258 lines
8.4 KiB
C++
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
|