xrpld
Loading...
Searching...
No Matches
Wallet.cpp
1#include <xrpl/server/Wallet.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/UnorderedContainers.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/safe_cast.h>
7#include <xrpl/beast/hash/uhash.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/core/PeerReservationTable.h>
10#include <xrpl/protocol/KeyType.h>
11#include <xrpl/protocol/PublicKey.h>
12#include <xrpl/protocol/SecretKey.h>
13#include <xrpl/protocol/tokens.h>
14#include <xrpl/rdb/DBInit.h>
15#include <xrpl/rdb/DatabaseCon.h>
16#include <xrpl/rdb/SociDB.h>
17#include <xrpl/server/Manifest.h>
18
19#include <boost/optional/optional.hpp> // IWYU pragma: keep
20
21#include <soci/blob-exchange.h> // IWYU pragma: keep
22#include <soci/blob.h>
23#include <soci/boost-optional.h> // IWYU pragma: keep
24#include <soci/into.h>
25#include <soci/session.h>
26#include <soci/statement.h>
27#include <soci/transaction.h>
28#include <soci/use.h>
29
30#include <array>
31#include <cstddef>
32#include <format>
33#include <functional>
34#include <memory>
35#include <string>
36#include <unordered_set>
37#include <utility>
38
39namespace xrpl {
40
41std::unique_ptr<DatabaseCon>
48
51{
52 // wallet database
54 setup, dbname.data(), std::array<std::string, 0>(), kWalletDbInit, j);
55}
56
57void
59 soci::session& session,
60 std::string const& dbTable,
61 ManifestCache& cache,
63{
64 // Load manifests stored in database
65 std::string const sql = "SELECT RawData FROM " + dbTable + ";";
66 soci::blob sociRawData(session);
67 soci::statement st = (session.prepare << sql, soci::into(sociRawData));
68 st.execute();
69 while (st.fetch())
70 {
71 std::string serialized;
72 convert(sociRawData, serialized);
73 if (auto mo = deserializeManifest(serialized))
74 {
75 if (!mo->verify())
76 {
77 JLOG(j.warn()) << "Unverifiable manifest in db";
78 continue;
79 }
80
81 // Only trusted manifests are persisted (see saveManifests), so
82 // anything loaded from the DB bypasses the untrusted cap.
84 }
85 else
86 {
87 JLOG(j.warn()) << "Malformed manifest in database";
88 }
89 }
90}
91
92static void
93saveManifest(soci::session& session, std::string const& dbTable, std::string const& serialized)
94{
95 // soci does not support bulk insertion of blob data
96 // Do not reuse blob because manifest ecdsa signatures vary in length
97 // but blob write length is expected to be >= the last write
98 soci::blob rawData(session);
99 convert(serialized, rawData);
100 session << "INSERT INTO " << dbTable << " (RawData) VALUES (:rawData);", soci::use(rawData);
101}
102
103void
105 soci::session& session,
106 std::string const& dbTable,
107 std::function<bool(PublicKey const&)> const& isTrusted,
110{
111 soci::transaction tr(session);
112 session << "DELETE FROM " << dbTable;
113 // Count skipped untrusted manifests and log one summary afterwards, since
114 // the cache can hold many and per-entry logging would flood at shutdown.
115 std::size_t skipped = 0;
116 for (auto const& v : map)
117 {
118 // Persist only trusted keys. Untrusted gossip is left out so a flood
119 // cannot survive a restart on disk.
120 if (!isTrusted(v.second.masterKey))
121 {
122 ++skipped;
123 continue;
124 }
125
126 saveManifest(session, dbTable, v.second.serialized);
127 }
128 tr.commit();
129
130 if (skipped != 0)
131 {
132 JLOG(j.info()) << skipped << " untrusted manifest(s) in cache not saved to db";
133 }
134}
135
136void
137addValidatorManifest(soci::session& session, std::string const& serialized)
138{
139 soci::transaction tr(session);
140 saveManifest(session, "ValidatorManifests", serialized);
141 tr.commit();
142}
143
144void
145clearNodeIdentity(soci::session& session)
146{
147 session << "DELETE FROM NodeIdentity;";
148}
149
151getNodeIdentity(soci::session& session)
152{
153 {
154 // SOCI requires boost::optional (not std::optional) as the parameter.
155 boost::optional<std::string> pubKO, priKO;
156 soci::statement st =
157 (session.prepare << "SELECT PublicKey, PrivateKey FROM NodeIdentity;",
158 soci::into(pubKO),
159 soci::into(priKO));
160 st.execute();
161 while (st.fetch())
162 {
163 auto const sk = parseBase58<SecretKey>(TokenType::NodePrivate, priKO.value_or(""));
164 auto const pk = parseBase58<PublicKey>(TokenType::NodePublic, pubKO.value_or(""));
165
166 // Only use if the public and secret keys are a pair
167 if (sk && pk && (*pk == derivePublicKey(KeyType::Secp256k1, *sk)))
168 return {*pk, *sk};
169 }
170 }
171
172 // If a valid identity wasn't found, we randomly generate a new one:
173 auto [newpublicKey, newsecretKey] = randomKeyPair(KeyType::Secp256k1);
174
175 session << std::format(
176 "INSERT INTO NodeIdentity (PublicKey,PrivateKey) "
177 "VALUES ('{}','{}');",
178 toBase58(TokenType::NodePublic, newpublicKey),
179 toBase58(TokenType::NodePrivate, newsecretKey));
180
181 return {newpublicKey, newsecretKey};
182}
183
185getPeerReservationTable(soci::session& session, beast::Journal j)
186{
188 // These values must be boost::optionals (not std) because SOCI expects
189 // boost::optionals.
190 boost::optional<std::string> valPubKey, valDesc;
191 // We should really abstract the table and column names into constants,
192 // but no one else does. Because it is too tedious? It would be easy if we
193 // had a jOOQ for C++.
194 soci::statement st =
195 (session.prepare << "SELECT PublicKey, Description FROM PeerReservations;",
196 soci::into(valPubKey),
197 soci::into(valDesc));
198 st.execute();
199 while (st.fetch())
200 {
201 if (!valPubKey || !valDesc)
202 {
203 // This represents a `NULL` in a `NOT NULL` column. It should be
204 // unreachable.
205 continue;
206 }
207 auto const optNodeId = parseBase58<PublicKey>(TokenType::NodePublic, *valPubKey);
208 if (!optNodeId)
209 {
210 JLOG(j.warn()) << "load: not a public key: " << valPubKey;
211 continue;
212 }
213 table.insert(PeerReservation{.nodeId = *optNodeId, .description = *valDesc});
214 }
215
216 return table;
217}
218
219void
221 soci::session& session,
222 PublicKey const& nodeId,
223 std::string const& description)
224{
225 auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
226 session << "INSERT INTO PeerReservations (PublicKey, Description) "
227 "VALUES (:nodeId, :desc) "
228 "ON CONFLICT (PublicKey) DO UPDATE SET "
229 "Description=excluded.Description",
230 soci::use(sNodeId), soci::use(description);
231}
232
233void
234deletePeerReservation(soci::session& session, PublicKey const& nodeId)
235{
236 auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
237 session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId", soci::use(sNodeId);
238}
239
240bool
241createFeatureVotes(soci::session& session)
242{
243 soci::transaction tr(session);
244 std::string const sql =
245 "SELECT count(*) FROM sqlite_master "
246 "WHERE type='table' AND name='FeatureVotes'";
247 // SOCI requires boost::optional (not std::optional) as the parameter.
248 boost::optional<int> featureVotesCount;
249 session << sql, soci::into(featureVotesCount);
250 bool const exists = static_cast<bool>(*featureVotesCount);
251
252 // Create FeatureVotes table in WalletDB if it doesn't exist
253 if (!exists)
254 {
255 session << "CREATE TABLE FeatureVotes ( "
256 "AmendmentHash CHARACTER(64) NOT NULL, "
257 "AmendmentName TEXT, "
258 "Veto INTEGER NOT NULL );";
259 tr.commit();
260 }
261 return exists;
262}
263
264void
266 soci::session& session,
267 std::function<void(
268 boost::optional<std::string> amendmentHash,
269 boost::optional<std::string> amendmentName,
270 boost::optional<AmendmentVote> vote)> const& callback)
271{
272 // lambda that converts the internally stored int to an AmendmentVote.
273 auto intToVote = [](boost::optional<int> const& dbVote) -> boost::optional<AmendmentVote> {
274 return safeCast<AmendmentVote>(dbVote.value_or(1));
275 };
276
277 soci::transaction const tr(session);
278 std::string const sql =
279 "SELECT AmendmentHash, AmendmentName, Veto FROM "
280 "( SELECT AmendmentHash, AmendmentName, Veto, RANK() OVER "
281 "( PARTITION BY AmendmentHash ORDER BY ROWID DESC ) "
282 "as rnk FROM FeatureVotes ) WHERE rnk = 1";
283 // SOCI requires boost::optional (not std::optional) as parameters.
284 boost::optional<std::string> amendmentHash;
285 boost::optional<std::string> amendmentName;
286 boost::optional<int> voteToVeto;
287 soci::statement st =
288 (session.prepare << sql,
289 soci::into(amendmentHash),
290 soci::into(amendmentName),
291 soci::into(voteToVeto));
292 st.execute();
293 while (st.fetch())
294 {
295 callback(amendmentHash, amendmentName, intToVote(voteToVeto));
296 }
297}
298
299void
301 soci::session& session,
302 uint256 const& amendment,
303 std::string const& name,
304 AmendmentVote vote)
305{
306 soci::transaction tr(session);
307 std::string sql =
308 "INSERT INTO FeatureVotes (AmendmentHash, AmendmentName, Veto) VALUES "
309 "('";
310 sql += to_string(amendment);
311 sql += "', '" + name;
312 sql += "', '" + std::to_string(safeCast<int>(vote)) + "');";
313 session << sql;
314 tr.commit();
315}
316
317} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream info() const
Definition Journal.h:350
Stream warn() const
Definition Journal.h:356
Remembers manifests with the highest sequence number.
Definition Manifest.h:374
ManifestDisposition applyManifest(Manifest m, ManifestRateLimitCapPolicy cap)
Add manifest to cache.
A public key.
Definition PublicKey.h:53
T data(T... args)
T format(T... args)
T insert(T... args)
T make_unique(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
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)
saveManifests Saves all given manifests to the database.
Definition Wallet.cpp:104
std::optional< AccountID > parseBase58(std::string const &s)
Parse AccountID from checked, base58 string.
static void saveManifest(soci::session &session, std::string const &dbTable, std::string const &serialized)
Definition Wallet.cpp:93
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
void deletePeerReservation(soci::session &session, PublicKey const &nodeId)
deletePeerReservation Deletes an entry from the peer reservation table.
Definition Wallet.cpp:234
std::pair< PublicKey, SecretKey > getNodeIdentity(soci::session &session)
Returns a stable public and private key for this node.
Definition Wallet.cpp:151
void insertPeerReservation(soci::session &session, PublicKey const &nodeId, std::string const &description)
insertPeerReservation Adds an entry to the peer reservation table.
Definition Wallet.cpp:220
void readAmendments(soci::session &session, std::function< void(boost::optional< std::string > amendmentHash, boost::optional< std::string > amendmentName, boost::optional< AmendmentVote > vote)> const &callback)
readAmendments Reads all amendments from the FeatureVotes table.
Definition Wallet.cpp:265
constexpr auto kWalletDbName
Definition DBInit.h:108
std::unordered_set< PeerReservation, beast::Uhash<>, KeyEqual > getPeerReservationTable(soci::session &session, beast::Journal j)
getPeerReservationTable Returns the peer reservation table.
Definition Wallet.cpp:185
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
void addValidatorManifest(soci::session &session, std::string const &serialized)
addValidatorManifest Saves the manifest of a validator to the database.
Definition Wallet.cpp:137
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
constexpr std::array< char const *, 6 > kWalletDbInit
Definition DBInit.h:110
@ Uncapped
Bypasses the cap (listed/trusted or config manifests).
Definition Manifest.h:365
std::unique_ptr< DatabaseCon > makeWalletDB(DatabaseCon::Setup const &setup, beast::Journal j)
makeWalletDB Opens the wallet database and returns it.
Definition Wallet.cpp:42
bool createFeatureVotes(soci::session &session)
createFeatureVotes Creates the FeatureVote table if it does not exist.
Definition Wallet.cpp:241
AmendmentVote
Definition Wallet.h:145
std::unordered_map< Key, Value, Hash, Pred, Allocator > hash_map
void clearNodeIdentity(soci::session &session)
Delete any saved public/private key associated with this node.
Definition Wallet.cpp:145
void getManifests(soci::session &session, std::string const &dbTable, ManifestCache &cache, beast::Journal j)
getManifests Loads a manifest from the wallet database and stores it in the cache.
Definition Wallet.cpp:58
std::unique_ptr< DatabaseCon > makeTestWalletDB(DatabaseCon::Setup const &setup, std::string const &dbname, beast::Journal j)
makeTestWalletDB Opens a test wallet database with an arbitrary name.
Definition Wallet.cpp:50
void voteAmendment(soci::session &session, uint256 const &amendment, std::string const &name, AmendmentVote vote)
voteAmendment Set the veto value for a particular amendment.
Definition Wallet.cpp:300
BaseUInt< 256 > uint256
Definition base_uint.h:580
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition SociDB.cpp:145
T to_string(T... args)