rippled
Wallet.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2021 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/rdb/Wallet.h>
21 #include <boost/format.hpp>
22 
23 namespace ripple {
24 
27 {
28  // wallet database
29  return std::make_unique<DatabaseCon>(
31 }
32 
34 makeTestWalletDB(DatabaseCon::Setup const& setup, std::string const& dbname)
35 {
36  // wallet database
37  return std::make_unique<DatabaseCon>(
38  setup, dbname.data(), std::array<char const*, 0>(), WalletDBInit);
39 }
40 
41 void
43  soci::session& session,
44  std::string const& dbTable,
45  ManifestCache& mCache,
47 {
48  // Load manifests stored in database
49  std::string const sql = "SELECT RawData FROM " + dbTable + ";";
50  soci::blob sociRawData(session);
51  soci::statement st = (session.prepare << sql, soci::into(sociRawData));
52  st.execute();
53  while (st.fetch())
54  {
55  std::string serialized;
56  convert(sociRawData, serialized);
57  if (auto mo = deserializeManifest(serialized))
58  {
59  if (!mo->verify())
60  {
61  JLOG(j.warn()) << "Unverifiable manifest in db";
62  continue;
63  }
64 
65  mCache.applyManifest(std::move(*mo));
66  }
67  else
68  {
69  JLOG(j.warn()) << "Malformed manifest in database";
70  }
71  }
72 }
73 
74 static void
76  soci::session& session,
77  std::string const& dbTable,
78  std::string const& serialized)
79 {
80  // soci does not support bulk insertion of blob data
81  // Do not reuse blob because manifest ecdsa signatures vary in length
82  // but blob write length is expected to be >= the last write
83  soci::blob rawData(session);
84  convert(serialized, rawData);
85  session << "INSERT INTO " << dbTable << " (RawData) VALUES (:rawData);",
86  soci::use(rawData);
87 }
88 
89 void
91  soci::session& session,
92  std::string const& dbTable,
93  std::function<bool(PublicKey const&)> const& isTrusted,
96 {
97  soci::transaction tr(session);
98  session << "DELETE FROM " << dbTable;
99  for (auto const& v : map)
100  {
101  // Save all revocation manifests,
102  // but only save trusted non-revocation manifests.
103  if (!v.second.revoked() && !isTrusted(v.second.masterKey))
104  {
105  JLOG(j.info()) << "Untrusted manifest in cache not saved to db";
106  continue;
107  }
108 
109  saveManifest(session, dbTable, v.second.serialized);
110  }
111  tr.commit();
112 }
113 
114 void
115 addValidatorManifest(soci::session& session, std::string const& serialized)
116 {
117  soci::transaction tr(session);
118  saveManifest(session, "ValidatorManifests", serialized);
119  tr.commit();
120 }
121 
122 void
123 clearNodeIdentity(soci::session& session)
124 {
125  session << "DELETE FROM NodeIdentity;";
126 }
127 
129 getNodeIdentity(soci::session& session)
130 {
131  {
132  // SOCI requires boost::optional (not std::optional) as the parameter.
133  boost::optional<std::string> pubKO, priKO;
134  soci::statement st =
135  (session.prepare
136  << "SELECT PublicKey, PrivateKey FROM NodeIdentity;",
137  soci::into(pubKO),
138  soci::into(priKO));
139  st.execute();
140  while (st.fetch())
141  {
142  auto const sk = parseBase58<SecretKey>(
143  TokenType::NodePrivate, priKO.value_or(""));
144  auto const pk = parseBase58<PublicKey>(
145  TokenType::NodePublic, pubKO.value_or(""));
146 
147  // Only use if the public and secret keys are a pair
148  if (sk && pk && (*pk == derivePublicKey(KeyType::secp256k1, *sk)))
149  return {*pk, *sk};
150  }
151  }
152 
153  // If a valid identity wasn't found, we randomly generate a new one:
154  auto [newpublicKey, newsecretKey] = randomKeyPair(KeyType::secp256k1);
155 
156  session << str(
157  boost::format("INSERT INTO NodeIdentity (PublicKey,PrivateKey) "
158  "VALUES ('%s','%s');") %
159  toBase58(TokenType::NodePublic, newpublicKey) %
160  toBase58(TokenType::NodePrivate, newsecretKey));
161 
162  return {newpublicKey, newsecretKey};
163 }
164 
166 getPeerReservationTable(soci::session& session, beast::Journal j)
167 {
169  // These values must be boost::optionals (not std) because SOCI expects
170  // boost::optionals.
171  boost::optional<std::string> valPubKey, valDesc;
172  // We should really abstract the table and column names into constants,
173  // but no one else does. Because it is too tedious? It would be easy if we
174  // had a jOOQ for C++.
175  soci::statement st =
176  (session.prepare
177  << "SELECT PublicKey, Description FROM PeerReservations;",
178  soci::into(valPubKey),
179  soci::into(valDesc));
180  st.execute();
181  while (st.fetch())
182  {
183  if (!valPubKey || !valDesc)
184  {
185  // This represents a `NULL` in a `NOT NULL` column. It should be
186  // unreachable.
187  continue;
188  }
189  auto const optNodeId =
190  parseBase58<PublicKey>(TokenType::NodePublic, *valPubKey);
191  if (!optNodeId)
192  {
193  JLOG(j.warn()) << "load: not a public key: " << valPubKey;
194  continue;
195  }
196  table.insert(PeerReservation{*optNodeId, *valDesc});
197  }
198 
199  return table;
200 }
201 
202 void
204  soci::session& session,
205  PublicKey const& nodeId,
206  std::string const& description)
207 {
208  session << "INSERT INTO PeerReservations (PublicKey, Description) "
209  "VALUES (:nodeId, :desc) "
210  "ON CONFLICT (PublicKey) DO UPDATE SET "
211  "Description=excluded.Description",
212  soci::use(toBase58(TokenType::NodePublic, nodeId)),
213  soci::use(description);
214 }
215 
216 void
217 deletePeerReservation(soci::session& session, PublicKey const& nodeId)
218 {
219  session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId",
220  soci::use(toBase58(TokenType::NodePublic, nodeId));
221 }
222 
223 bool
224 createFeatureVotes(soci::session& session)
225 {
226  soci::transaction tr(session);
227  std::string sql =
228  "SELECT count(*) FROM sqlite_master "
229  "WHERE type='table' AND name='FeatureVotes'";
230  // SOCI requires boost::optional (not std::optional) as the parameter.
231  boost::optional<int> featureVotesCount;
232  session << sql, soci::into(featureVotesCount);
233  bool exists = static_cast<bool>(*featureVotesCount);
234 
235  // Create FeatureVotes table in WalletDB if it doesn't exist
236  if (!exists)
237  {
238  session << "CREATE TABLE FeatureVotes ( "
239  "AmendmentHash CHARACTER(64) NOT NULL, "
240  "AmendmentName TEXT, "
241  "Veto INTEGER NOT NULL );";
242  tr.commit();
243  }
244  return exists;
245 }
246 
247 void
249  soci::session& session,
250  std::function<void(
251  boost::optional<std::string> amendment_hash,
252  boost::optional<std::string> amendment_name,
253  boost::optional<AmendmentVote> vote)> const& callback)
254 {
255  // lambda that converts the internally stored int to an AmendmentVote.
256  auto intToVote = [](boost::optional<int> const& dbVote)
257  -> boost::optional<AmendmentVote> {
258  return safe_cast<AmendmentVote>(dbVote.value_or(1));
259  };
260 
261  soci::transaction tr(session);
262  std::string sql =
263  "SELECT AmendmentHash, AmendmentName, Veto FROM "
264  "( SELECT AmendmentHash, AmendmentName, Veto, RANK() OVER "
265  "( PARTITION BY AmendmentHash ORDER BY ROWID DESC ) "
266  "as rnk FROM FeatureVotes ) WHERE rnk = 1";
267  // SOCI requires boost::optional (not std::optional) as parameters.
268  boost::optional<std::string> amendment_hash;
269  boost::optional<std::string> amendment_name;
270  boost::optional<int> vote_to_veto;
271  soci::statement st =
272  (session.prepare << sql,
273  soci::into(amendment_hash),
274  soci::into(amendment_name),
275  soci::into(vote_to_veto));
276  st.execute();
277  while (st.fetch())
278  {
279  callback(amendment_hash, amendment_name, intToVote(vote_to_veto));
280  }
281 }
282 
283 void
285  soci::session& session,
286  uint256 const& amendment,
287  std::string const& name,
288  AmendmentVote vote)
289 {
290  soci::transaction tr(session);
291  std::string sql =
292  "INSERT INTO FeatureVotes (AmendmentHash, AmendmentName, Veto) VALUES "
293  "('";
294  sql += to_string(amendment);
295  sql += "', '" + name;
296  sql += "', '" + std::to_string(safe_cast<int>(vote)) + "');";
297  session << sql;
298  tr.commit();
299 }
300 
301 } // namespace ripple
ripple::WalletDBName
constexpr auto WalletDBName
Definition: DBInit.h:217
std::string
STL class.
ripple::DatabaseCon::Setup
Definition: DatabaseCon.h:84
std::unordered_set
STL class.
std::pair
ripple::convert
void convert(soci::blob &from, std::vector< std::uint8_t > &to)
Definition: SociDB.cpp:154
ripple::createFeatureVotes
bool createFeatureVotes(soci::session &session)
createFeatureVotes Creates the FeatureVote table if it does not exist.
Definition: Wallet.cpp:224
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
beast::Journal::warn
Stream warn() const
Definition: Journal.h:327
ripple::addValidatorManifest
void addValidatorManifest(soci::session &session, std::string const &serialized)
addValidatorManifest Saves the manifest of a validator to the database.
Definition: Wallet.cpp:115
std::function
ripple::makeTestWalletDB
std::unique_ptr< DatabaseCon > makeTestWalletDB(DatabaseCon::Setup const &setup, std::string const &dbname)
makeTestWalletDB Opens a test wallet database with an arbitrary name.
Definition: Wallet.cpp:34
ripple::base_uint< 256 >
ripple::PublicKey
A public key.
Definition: PublicKey.h:59
ripple::derivePublicKey
PublicKey derivePublicKey(KeyType type, SecretKey const &sk)
Derive the public key from a secret key.
Definition: SecretKey.cpp:313
ripple::getManifests
void getManifests(soci::session &session, std::string const &dbTable, ManifestCache &mCache, beast::Journal j)
getManifests Loads a manifest from the wallet database and stores it in the cache.
Definition: Wallet.cpp:42
ripple::insertPeerReservation
void insertPeerReservation(soci::session &session, PublicKey const &nodeId, std::string const &description)
insertPeerReservation Adds an entry to the peer reservation table.
Definition: Wallet.cpp:203
ripple::deletePeerReservation
void deletePeerReservation(soci::session &session, PublicKey const &nodeId)
deletePeerReservation Deletes an entry from the peer reservation table.
Definition: Wallet.cpp:217
ripple::readAmendments
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)
readAmendments Reads all amendments from the FeatureVotes table.
Definition: Wallet.cpp:248
std::to_string
T to_string(T... args)
std::array
STL class.
beast::Journal::info
Stream info() const
Definition: Journal.h:321
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::PeerReservation
Definition: PeerReservationTable.h:43
ripple::KeyType::secp256k1
@ secp256k1
ripple::randomKeyPair
std::pair< PublicKey, SecretKey > randomKeyPair(KeyType type)
Create a key pair using secure random numbers.
Definition: SecretKey.cpp:368
ripple::getPeerReservationTable
std::unordered_set< PeerReservation, beast::uhash<>, KeyEqual > getPeerReservationTable(soci::session &session, beast::Journal j)
getPeerReservationTable Returns the peer reservation table.
Definition: Wallet.cpp:166
ripple::ManifestCache
Remembers manifests with the highest sequence number.
Definition: Manifest.h:231
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
std::unordered_set::insert
T insert(T... args)
ripple::voteAmendment
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:284
ripple::makeWalletDB
std::unique_ptr< DatabaseCon > makeWalletDB(DatabaseCon::Setup const &setup)
makeWalletDB Opens the wallet database and returns it.
Definition: Wallet.cpp:26
ripple::saveManifest
static void saveManifest(soci::session &session, std::string const &dbTable, std::string const &serialized)
Definition: Wallet.cpp:75
ripple::AmendmentVote
AmendmentVote
Definition: Wallet.h:147
ripple::TokenType::NodePublic
@ NodePublic
ripple::getNodeIdentity
std::pair< PublicKey, SecretKey > getNodeIdentity(Application &app, boost::program_options::variables_map const &cmdline)
The cryptographic credentials identifying this server instance.
Definition: NodeIdentity.cpp:30
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::deserializeManifest
std::optional< Manifest > deserializeManifest(Slice s, beast::Journal journal)
Constructs Manifest from serialized string.
Definition: app/misc/impl/Manifest.cpp:53
ripple::KeyEqual
Definition: PeerReservationTable.h:70
ripple::WalletDBInit
constexpr std::array< char const *, 6 > WalletDBInit
Definition: DBInit.h:219
std::unique_ptr
STL class.
ripple::ManifestCache::applyManifest
ManifestDisposition applyManifest(Manifest m)
Add manifest to cache.
Definition: app/misc/impl/Manifest.cpp:363
ripple::clearNodeIdentity
void clearNodeIdentity(soci::session &session)
Delete any saved public/private key associated with this node.
Definition: Wallet.cpp:123
std::unordered_map
STL class.
ripple::saveManifests
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:90
std::string::data
T data(T... args)
ripple::TokenType::NodePrivate
@ NodePrivate