#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { std::unique_ptr makeWalletDB(DatabaseCon::Setup const& setup, beast::Journal j) { // wallet database return std::make_unique( setup, kWALLET_DB_NAME, std::array(), kWALLET_DB_INIT, j); } std::unique_ptr makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname, beast::Journal j) { // wallet database return std::make_unique( setup, dbname.data(), std::array(), kWALLET_DB_INIT, j); } void getManifests( soci::session& session, std::string const& dbTable, ManifestCache& cache, 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; } cache.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 const& isTrusted, hash_map 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 getNodeIdentity(soci::session& session) { { // SOCI requires boost::optional (not std::optional) as the parameter. boost::optional 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(TokenType::NodePrivate, priKO.value_or("")); auto const pk = parseBase58(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, KeyEqual> getPeerReservationTable(soci::session& session, beast::Journal j) { std::unordered_set, KeyEqual> table; // These values must be boost::optionals (not std) because SOCI expects // boost::optionals. boost::optional 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(TokenType::NodePublic, *valPubKey); if (!optNodeId) { JLOG(j.warn()) << "load: not a public key: " << valPubKey; continue; } table.insert(PeerReservation{.nodeId = *optNodeId, .description = *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 const sql = "SELECT count(*) FROM sqlite_master " "WHERE type='table' AND name='FeatureVotes'"; // SOCI requires boost::optional (not std::optional) as the parameter. boost::optional featureVotesCount; session << sql, soci::into(featureVotesCount); bool const exists = static_cast(*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 amendmentHash, boost::optional amendmentName, boost::optional vote)> const& callback) { // lambda that converts the internally stored int to an AmendmentVote. auto intToVote = [](boost::optional const& dbVote) -> boost::optional { return safeCast(dbVote.value_or(1)); }; soci::transaction const tr(session); std::string const 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 amendmentHash; boost::optional amendmentName; boost::optional voteToVeto; soci::statement st = (session.prepare << sql, soci::into(amendmentHash), soci::into(amendmentName), soci::into(voteToVeto)); st.execute(); while (st.fetch()) { callback(amendmentHash, amendmentName, intToVote(voteToVeto)); } } 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(safeCast(vote)) + "');"; session << sql; tr.commit(); } } // namespace xrpl