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 
123 getNodeIdentity(soci::session& session)
124 {
125  {
126  // SOCI requires boost::optional (not std::optional) as the parameter.
127  boost::optional<std::string> pubKO, priKO;
128  soci::statement st =
129  (session.prepare
130  << "SELECT PublicKey, PrivateKey FROM NodeIdentity;",
131  soci::into(pubKO),
132  soci::into(priKO));
133  st.execute();
134  while (st.fetch())
135  {
136  auto const sk = parseBase58<SecretKey>(
137  TokenType::NodePrivate, priKO.value_or(""));
138  auto const pk = parseBase58<PublicKey>(
139  TokenType::NodePublic, pubKO.value_or(""));
140 
141  // Only use if the public and secret keys are a pair
142  if (sk && pk && (*pk == derivePublicKey(KeyType::secp256k1, *sk)))
143  return {*pk, *sk};
144  }
145  }
146 
147  // If a valid identity wasn't found, we randomly generate a new one:
148  auto [newpublicKey, newsecretKey] = randomKeyPair(KeyType::secp256k1);
149 
150  session << str(
151  boost::format("INSERT INTO NodeIdentity (PublicKey,PrivateKey) "
152  "VALUES ('%s','%s');") %
153  toBase58(TokenType::NodePublic, newpublicKey) %
154  toBase58(TokenType::NodePrivate, newsecretKey));
155 
156  return {newpublicKey, newsecretKey};
157 }
158 
160 getPeerReservationTable(soci::session& session, beast::Journal j)
161 {
163  // These values must be boost::optionals (not std) because SOCI expects
164  // boost::optionals.
165  boost::optional<std::string> valPubKey, valDesc;
166  // We should really abstract the table and column names into constants,
167  // but no one else does. Because it is too tedious? It would be easy if we
168  // had a jOOQ for C++.
169  soci::statement st =
170  (session.prepare
171  << "SELECT PublicKey, Description FROM PeerReservations;",
172  soci::into(valPubKey),
173  soci::into(valDesc));
174  st.execute();
175  while (st.fetch())
176  {
177  if (!valPubKey || !valDesc)
178  {
179  // This represents a `NULL` in a `NOT NULL` column. It should be
180  // unreachable.
181  continue;
182  }
183  auto const optNodeId =
184  parseBase58<PublicKey>(TokenType::NodePublic, *valPubKey);
185  if (!optNodeId)
186  {
187  JLOG(j.warn()) << "load: not a public key: " << valPubKey;
188  continue;
189  }
190  table.insert(PeerReservation{*optNodeId, *valDesc});
191  }
192 
193  return table;
194 }
195 
196 void
198  soci::session& session,
199  PublicKey const& nodeId,
200  std::string const& description)
201 {
202  session << "INSERT INTO PeerReservations (PublicKey, Description) "
203  "VALUES (:nodeId, :desc) "
204  "ON CONFLICT (PublicKey) DO UPDATE SET "
205  "Description=excluded.Description",
206  soci::use(toBase58(TokenType::NodePublic, nodeId)),
207  soci::use(description);
208 }
209 
210 void
211 deletePeerReservation(soci::session& session, PublicKey const& nodeId)
212 {
213  session << "DELETE FROM PeerReservations WHERE PublicKey = :nodeId",
214  soci::use(toBase58(TokenType::NodePublic, nodeId));
215 }
216 
217 bool
218 createFeatureVotes(soci::session& session)
219 {
220  soci::transaction tr(session);
221  std::string sql =
222  "SELECT count(*) FROM sqlite_master "
223  "WHERE type='table' AND name='FeatureVotes'";
224  // SOCI requires boost::optional (not std::optional) as the parameter.
225  boost::optional<int> featureVotesCount;
226  session << sql, soci::into(featureVotesCount);
227  bool exists = static_cast<bool>(*featureVotesCount);
228 
229  // Create FeatureVotes table in WalletDB if it doesn't exist
230  if (!exists)
231  {
232  session << "CREATE TABLE FeatureVotes ( "
233  "AmendmentHash CHARACTER(64) NOT NULL, "
234  "AmendmentName TEXT, "
235  "Veto INTEGER NOT NULL );";
236  tr.commit();
237  }
238  return exists;
239 }
240 
241 void
243  soci::session& session,
244  std::function<void(
245  boost::optional<std::string> amendment_hash,
246  boost::optional<std::string> amendment_name,
247  boost::optional<AmendmentVote> vote)> const& callback)
248 {
249  // lambda that converts the internally stored int to an AmendmentVote.
250  auto intToVote = [](boost::optional<int> const& dbVote)
251  -> boost::optional<AmendmentVote> {
252  return safe_cast<AmendmentVote>(dbVote.value_or(1));
253  };
254 
255  soci::transaction tr(session);
256  std::string sql =
257  "SELECT AmendmentHash, AmendmentName, Veto FROM FeatureVotes";
258  // SOCI requires boost::optional (not std::optional) as parameters.
259  boost::optional<std::string> amendment_hash;
260  boost::optional<std::string> amendment_name;
261  boost::optional<int> vote_to_veto;
262  soci::statement st =
263  (session.prepare << sql,
264  soci::into(amendment_hash),
265  soci::into(amendment_name),
266  soci::into(vote_to_veto));
267  st.execute();
268  while (st.fetch())
269  {
270  callback(amendment_hash, amendment_name, intToVote(vote_to_veto));
271  }
272 }
273 
274 void
276  soci::session& session,
277  uint256 const& amendment,
278  std::string const& name,
279  AmendmentVote vote)
280 {
281  soci::transaction tr(session);
282  std::string sql =
283  "INSERT INTO FeatureVotes (AmendmentHash, AmendmentName, Veto) VALUES "
284  "('";
285  sql += to_string(amendment);
286  sql += "', '" + name;
287  sql += "', '" + std::to_string(safe_cast<int>(vote)) + "');";
288  session << sql;
289  tr.commit();
290 }
291 
292 } // namespace ripple
ripple::getNodeIdentity
std::pair< PublicKey, SecretKey > getNodeIdentity(Application &app)
The cryptographic credentials identifying this server instance.
Definition: NodeIdentity.cpp:32
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:218
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
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
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:75
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:197
ripple::deletePeerReservation
void deletePeerReservation(soci::session &session, PublicKey const &nodeId)
deletePeerReservation Deletes an entry from the peer reservation table.
Definition: Wallet.cpp:211
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:242
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:160
ripple::ManifestCache
Remembers manifests with the highest sequence number.
Definition: Manifest.h:225
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:275
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:138
ripple::TokenType::NodePublic
@ NodePublic
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::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:359
std::unordered_map
STL class.
ripple::deserializeManifest
std::optional< Manifest > deserializeManifest(Slice s)
Constructs Manifest from serialized string.
Definition: app/misc/impl/Manifest.cpp:53
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