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/format/free_funcs.hpp>
20#include <boost/optional/optional.hpp> // IWYU pragma: keep
21
22#include <soci/blob.h>
23#include <soci/into.h>
24#include <soci/session.h>
25#include <soci/statement.h>
26#include <soci/transaction.h>
27#include <soci/use.h>
28
29#include <array>
30#include <functional>
31#include <memory>
32#include <string>
33#include <unordered_set>
34#include <utility>
35
36namespace xrpl {
37
38std::unique_ptr<DatabaseCon>
45
48{
49 // wallet database
51 setup, dbname.data(), std::array<std::string, 0>(), kWalletDbInit, j);
52}
53
54void
56 soci::session& session,
57 std::string const& dbTable,
58 ManifestCache& cache,
60{
61 // Load manifests stored in database
62 std::string const sql = "SELECT RawData FROM " + dbTable + ";";
63 soci::blob sociRawData(session);
64 soci::statement st = (session.prepare << sql, soci::into(sociRawData));
65 st.execute();
66 while (st.fetch())
67 {
68 std::string serialized;
69 convert(sociRawData, serialized);
70 if (auto mo = deserializeManifest(serialized))
71 {
72 if (!mo->verify())
73 {
74 JLOG(j.warn()) << "Unverifiable manifest in db";
75 continue;
76 }
77
78 cache.applyManifest(std::move(*mo));
79 }
80 else
81 {
82 JLOG(j.warn()) << "Malformed manifest in database";
83 }
84 }
85}
86
87static void
88saveManifest(soci::session& session, std::string const& dbTable, std::string const& serialized)
89{
90 // soci does not support bulk insertion of blob data
91 // Do not reuse blob because manifest ecdsa signatures vary in length
92 // but blob write length is expected to be >= the last write
93 soci::blob rawData(session);
94 convert(serialized, rawData);
95 session << "INSERT INTO " << dbTable << " (RawData) VALUES (:rawData);", soci::use(rawData);
96}
97
98void
100 soci::session& session,
101 std::string const& dbTable,
102 std::function<bool(PublicKey const&)> const& isTrusted,
105{
106 soci::transaction tr(session);
107 session << "DELETE FROM " << dbTable;
108 for (auto const& v : map)
109 {
110 // Save all revocation manifests,
111 // but only save trusted non-revocation manifests.
112 if (!v.second.revoked() && !isTrusted(v.second.masterKey))
113 {
114 JLOG(j.info()) << "Untrusted manifest in cache not saved to db";
115 continue;
116 }
117
118 saveManifest(session, dbTable, v.second.serialized);
119 }
120 tr.commit();
121}
122
123void
124addValidatorManifest(soci::session& session, std::string const& serialized)
125{
126 soci::transaction tr(session);
127 saveManifest(session, "ValidatorManifests", serialized);
128 tr.commit();
129}
130
131void
132clearNodeIdentity(soci::session& session)
133{
134 session << "DELETE FROM NodeIdentity;";
135}
136
138getNodeIdentity(soci::session& session)
139{
140 {
141 // SOCI requires boost::optional (not std::optional) as the parameter.
142 boost::optional<std::string> pubKO, priKO;
143 soci::statement st =
144 (session.prepare << "SELECT PublicKey, PrivateKey FROM NodeIdentity;",
145 soci::into(pubKO),
146 soci::into(priKO));
147 st.execute();
148 while (st.fetch())
149 {
150 auto const sk = parseBase58<SecretKey>(TokenType::NodePrivate, priKO.value_or(""));
151 auto const pk = parseBase58<PublicKey>(TokenType::NodePublic, pubKO.value_or(""));
152
153 // Only use if the public and secret keys are a pair
154 if (sk && pk && (*pk == derivePublicKey(KeyType::Secp256k1, *sk)))
155 return {*pk, *sk};
156 }
157 }
158
159 // If a valid identity wasn't found, we randomly generate a new one:
160 auto [newpublicKey, newsecretKey] = randomKeyPair(KeyType::Secp256k1);
161
162 session << str(
163 boost::format(
164 "INSERT INTO NodeIdentity (PublicKey,PrivateKey) "
165 "VALUES ('%s','%s');") %
166 toBase58(TokenType::NodePublic, newpublicKey) %
167 toBase58(TokenType::NodePrivate, newsecretKey));
168
169 return {newpublicKey, newsecretKey};
170}
171
173getPeerReservationTable(soci::session& session, beast::Journal j)
174{
176 // These values must be boost::optionals (not std) because SOCI expects
177 // boost::optionals.
178 boost::optional<std::string> valPubKey, valDesc;
179 // We should really abstract the table and column names into constants,
180 // but no one else does. Because it is too tedious? It would be easy if we
181 // had a jOOQ for C++.
182 soci::statement st =
183 (session.prepare << "SELECT PublicKey, Description FROM PeerReservations;",
184 soci::into(valPubKey),
185 soci::into(valDesc));
186 st.execute();
187 while (st.fetch())
188 {
189 if (!valPubKey || !valDesc)
190 {
191 // This represents a `NULL` in a `NOT NULL` column. It should be
192 // unreachable.
193 continue;
194 }
195 auto const optNodeId = parseBase58<PublicKey>(TokenType::NodePublic, *valPubKey);
196 if (!optNodeId)
197 {
198 JLOG(j.warn()) << "load: not a public key: " << valPubKey;
199 continue;
200 }
201 table.insert(PeerReservation{.nodeId = *optNodeId, .description = *valDesc});
202 }
203
204 return table;
205}
206
207void
209 soci::session& session,
210 PublicKey const& nodeId,
211 std::string const& description)
212{
213 auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
214 session << "INSERT INTO PeerReservations (PublicKey, Description) "
215 "VALUES (:nodeId, :desc) "
216 "ON CONFLICT (PublicKey) DO UPDATE SET "
217 "Description=excluded.Description",
218 soci::use(sNodeId), soci::use(description);
219}
220
221void
222deletePeerReservation(soci::session& session, PublicKey const& nodeId)
223{
224 auto const sNodeId = toBase58(TokenType::NodePublic, nodeId);
225 session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId", soci::use(sNodeId);
226}
227
228bool
229createFeatureVotes(soci::session& session)
230{
231 soci::transaction tr(session);
232 std::string const sql =
233 "SELECT count(*) FROM sqlite_master "
234 "WHERE type='table' AND name='FeatureVotes'";
235 // SOCI requires boost::optional (not std::optional) as the parameter.
236 boost::optional<int> featureVotesCount;
237 session << sql, soci::into(featureVotesCount);
238 bool const exists = static_cast<bool>(*featureVotesCount);
239
240 // Create FeatureVotes table in WalletDB if it doesn't exist
241 if (!exists)
242 {
243 session << "CREATE TABLE FeatureVotes ( "
244 "AmendmentHash CHARACTER(64) NOT NULL, "
245 "AmendmentName TEXT, "
246 "Veto INTEGER NOT NULL );";
247 tr.commit();
248 }
249 return exists;
250}
251
252void
254 soci::session& session,
255 std::function<void(
256 boost::optional<std::string> amendmentHash,
257 boost::optional<std::string> amendmentName,
258 boost::optional<AmendmentVote> vote)> const& callback)
259{
260 // lambda that converts the internally stored int to an AmendmentVote.
261 auto intToVote = [](boost::optional<int> const& dbVote) -> boost::optional<AmendmentVote> {
262 return safeCast<AmendmentVote>(dbVote.value_or(1));
263 };
264
265 soci::transaction const tr(session);
266 std::string const sql =
267 "SELECT AmendmentHash, AmendmentName, Veto FROM "
268 "( SELECT AmendmentHash, AmendmentName, Veto, RANK() OVER "
269 "( PARTITION BY AmendmentHash ORDER BY ROWID DESC ) "
270 "as rnk FROM FeatureVotes ) WHERE rnk = 1";
271 // SOCI requires boost::optional (not std::optional) as parameters.
272 boost::optional<std::string> amendmentHash;
273 boost::optional<std::string> amendmentName;
274 boost::optional<int> voteToVeto;
275 soci::statement st =
276 (session.prepare << sql,
277 soci::into(amendmentHash),
278 soci::into(amendmentName),
279 soci::into(voteToVeto));
280 st.execute();
281 while (st.fetch())
282 {
283 callback(amendmentHash, amendmentName, intToVote(voteToVeto));
284 }
285}
286
287void
289 soci::session& session,
290 uint256 const& amendment,
291 std::string const& name,
292 AmendmentVote vote)
293{
294 soci::transaction tr(session);
295 std::string sql =
296 "INSERT INTO FeatureVotes (AmendmentHash, AmendmentName, Veto) VALUES "
297 "('";
298 sql += to_string(amendment);
299 sql += "', '" + name;
300 sql += "', '" + std::to_string(safeCast<int>(vote)) + "');";
301 session << sql;
302 tr.commit();
303}
304
305} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream info() const
Definition Journal.h:303
Stream warn() const
Definition Journal.h:309
Remembers manifests with the highest sequence number.
Definition Manifest.h:236
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
A public key.
Definition PublicKey.h:42
T data(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:99
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:88
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safeCast(Src s) noexcept
Definition safe_cast.h:21
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
void deletePeerReservation(soci::session &session, PublicKey const &nodeId)
deletePeerReservation Deletes an entry from the peer reservation table.
Definition Wallet.cpp:222
std::pair< PublicKey, SecretKey > getNodeIdentity(soci::session &session)
Returns a stable public and private key for this node.
Definition Wallet.cpp:138
void insertPeerReservation(soci::session &session, PublicKey const &nodeId, std::string const &description)
insertPeerReservation Adds an entry to the peer reservation table.
Definition Wallet.cpp:208
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:253
constexpr auto kWalletDbName
Definition DBInit.h:85
std::unordered_set< PeerReservation, beast::Uhash<>, KeyEqual > getPeerReservationTable(soci::session &session, beast::Journal j)
getPeerReservationTable Returns the peer reservation table.
Definition Wallet.cpp:173
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void addValidatorManifest(soci::session &session, std::string const &serialized)
addValidatorManifest Saves the manifest of a validator to the database.
Definition Wallet.cpp:124
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
constexpr std::array< char const *, 6 > kWalletDbInit
Definition DBInit.h:87
std::unique_ptr< DatabaseCon > makeWalletDB(DatabaseCon::Setup const &setup, beast::Journal j)
makeWalletDB Opens the wallet database and returns it.
Definition Wallet.cpp:39
bool createFeatureVotes(soci::session &session)
createFeatureVotes Creates the FeatureVote table if it does not exist.
Definition Wallet.cpp:229
AmendmentVote
Definition Wallet.h:126
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:132
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:55
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:47
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:288
BaseUInt< 256 > uint256
Definition base_uint.h:562
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition SociDB.cpp:147
T to_string(T... args)