Electroneum
blockchain_db.cpp
Go to the documentation of this file.
1 // Copyrights(c) 2017-2021, The Electroneum Project
2 // Copyrights(c) 2014-2019, The Monero Project
3 //
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without modification, are
7 // permitted provided that the following conditions are met:
8 //
9 // 1. Redistributions of source code must retain the above copyright notice, this list of
10 // conditions and the following disclaimer.
11 //
12 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
13 // of conditions and the following disclaimer in the documentation and/or other
14 // materials provided with the distribution.
15 //
16 // 3. Neither the name of the copyright holder nor the names of its contributors may be
17 // used to endorse or promote products derived from this software without specific
18 // prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
21 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
23 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include <boost/range/adaptor/reversed.hpp>
31 
32 #include "string_tools.h"
33 #include "blockchain_db.h"
35 #include "profile_tools.h"
36 #include "ringct/rctOps.h"
37 
38 #include "lmdb/db_lmdb.h"
39 #ifdef BERKELEY_DB
40 #include "berkeleydb/db_bdb.h"
41 #endif
42 
43 static const char *db_types[] = {
44  "lmdb",
45 #ifdef BERKELEY_DB
46  "berkeley",
47 #endif
48  NULL
49 };
50 
51 #undef ELECTRONEUM_DEFAULT_LOG_CATEGORY
52 #define ELECTRONEUM_DEFAULT_LOG_CATEGORY "blockchain.db"
53 
55 
56 namespace cryptonote
57 {
58 
60 {
61  int i;
62  for (i=0; db_types[i]; i++)
63  {
64  if (db_types[i] == db_type)
65  return true;
66  }
67  return false;
68 }
69 
71 {
72  int i;
73  std::string ret = "";
74  for (i=0; db_types[i]; i++)
75  {
76  if (i)
77  ret += sep;
78  ret += db_types[i];
79  }
80  return ret;
81 }
82 
83 std::string arg_db_type_description = "Specify database type, available: " + cryptonote::blockchain_db_types(", ");
85  "db-type"
87 , DEFAULT_DB_TYPE
88 };
90  "db-sync-mode"
91 , "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
92 , "fast:async:250000000bytes"
93 };
95  "db-salvage"
96 , "Try to salvage a blockchain database if it seems corrupted"
97 , false
98 };
99 
101 {
102  if (db_type == "lmdb")
103  return new BlockchainLMDB();
104 #if defined(BERKELEY_DB)
105  if (db_type == "berkeley")
106  return new BlockchainBDB();
107 #endif
108  return NULL;
109 }
110 
111 void BlockchainDB::init_options(boost::program_options::options_description& desc)
112 {
116 }
117 
118 void BlockchainDB::pop_block()
119 {
120  block blk;
121  std::vector<transaction> txs;
122  pop_block(blk, txs);
123 }
124 
125 void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const std::pair<transaction, blobdata>& txp, const crypto::hash* tx_hash_ptr, const crypto::hash* tx_prunable_hash_ptr)
126 {
127  const transaction &tx = txp.first;
128 
129  bool miner_tx = false;
130  crypto::hash tx_hash, tx_prunable_hash;
131  if (!tx_hash_ptr)
132  {
133  // should only need to compute hash for miner transactions
134  tx_hash = get_transaction_hash(tx);
135  LOG_PRINT_L3("null tx_hash_ptr - needed to compute: " << tx_hash);
136  }
137  else
138  {
139  tx_hash = *tx_hash_ptr;
140  }
141 
142  std::vector<std::pair<crypto::hash, uint64_t>> utxos_to_remove;
143 
144  // Sanity check on supported input types
145  for (size_t i = 0; i < tx.vin.size(); ++i)
146  {
147  const txin_v& tx_input = tx.vin[i];
148  if (tx_input.type() == typeid(txin_to_key))
149  {
150  add_spent_key(boost::get<txin_to_key>(tx_input).k_image);
151  }
152  else if (tx_input.type() == typeid(txin_to_key_public))
153  {
154  const auto &txin = boost::get<txin_to_key_public>(tx_input);
155  utxos_to_remove.push_back({txin.tx_hash, txin.relative_offset});
156  add_tx_input(txin.tx_hash, txin.relative_offset, tx.hash, i);
157  }
158  else if (tx_input.type() == typeid(txin_gen))
159  {
160  /* nothing to do here */
161  miner_tx = true;
162  }
163  else
164  {
165  LOG_PRINT_L1("Unsupported input type, removing key images and aborting transaction addition");
166  for (const txin_v& tx_input : tx.vin)
167  {
168  if (tx_input.type() == typeid(txin_to_key))
169  {
170  remove_spent_key(boost::get<txin_to_key>(tx_input).k_image); // inputs are already checked here regardless of version
171  }
172  }
173  return;
174  }
175  }
176 
177  if (tx.version == 1)
178  {
179  uint64_t tx_id = add_transaction_data(blk_hash, txp, tx_hash, tx_prunable_hash);
180 
181  std::vector<uint64_t> amount_output_indices(tx.vout.size());
182 
183  // iterate tx.vout using indices instead of C++11 foreach syntax because
184  // we need the index
185  for (uint64_t i = 0; i < tx.vout.size(); ++i)
186  {
187  amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, tx.unlock_time, NULL);
188  }
189  add_tx_amount_output_indices(tx_id, amount_output_indices);
190  }
191  else if (tx.version >= 2)
192  {
193  add_transaction_data(blk_hash, txp, tx_hash, tx_prunable_hash);
194 
195  // Sanity check on supported output types
196  for (uint64_t i = 0; i < tx.vout.size(); ++i)
197  {
198  if(tx.vout[i].target.type() != typeid(txout_to_key_public))
199  {
200  LOG_PRINT_L1("Unsupported output type, reinstating UTXOs, removing key images and aborting transaction addition");
201  for (const txin_v& tx_input : tx.vin)
202  {
203  if (tx_input.type() == typeid(txin_to_key))
204  {
205  remove_spent_key(boost::get<txin_to_key>(tx_input).k_image);
206  }
207  }
208  return;
209  }// if outs are all of the right type, we're ok to proceed by removing the utxos that are now spent
210 
211  for(auto utxo: utxos_to_remove)
212  {
213  remove_chainstate_utxo(utxo.first, utxo.second);
214  }
215 
216  const auto &txout = boost::get<txout_to_key_public>(tx.vout[i].target);
217  add_chainstate_utxo(tx.hash, i, addKeys(txout.address.m_view_public_key, txout.address.m_spend_public_key) , tx.vout[i].amount, txp.first.unlock_time, miner_tx);
218  add_addr_output(tx.hash, i, addKeys(txout.address.m_view_public_key, txout.address.m_spend_public_key), tx.vout[i].amount, txp.first.unlock_time);
219  }//end of v2+ processing
220  }
221 }
222 
223 uint64_t BlockchainDB::add_block( const std::pair<block, blobdata>& blck
224  , size_t block_weight
225  , uint64_t long_term_block_weight
226  , const difficulty_type& cumulative_difficulty
227  , const uint64_t& coins_generated
228  , const std::vector<std::pair<transaction, blobdata>>& txs
229  )
230 {
231  const block &blk = blck.first;
232 
233  // sanity
234  if (blk.tx_hashes.size() != txs.size())
235  throw std::runtime_error("Inconsistent tx/hashes sizes");
236 
237  TIME_MEASURE_START(time1);
238  crypto::hash blk_hash = get_block_hash(blk);
239  TIME_MEASURE_FINISH(time1);
240  time_blk_hash += time1;
241 
242  uint64_t prev_height = height();
243 
244  // call out to add the transactions
245 
247 
248  add_transaction(blk_hash, std::make_pair(blk.miner_tx, tx_to_blob(blk.miner_tx)));
249  int tx_i = 0;
250  crypto::hash tx_hash = crypto::null_hash;
251  for (const std::pair<transaction, blobdata>& tx : txs)
252  {
253  tx_hash = blk.tx_hashes[tx_i];
254  add_transaction(blk_hash, tx, &tx_hash);
255  ++tx_i;
256  }
257  TIME_MEASURE_FINISH(time1);
258  time_add_transaction += time1;
259 
260  // call out to subclass implementation to add the block & metadata
262  add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, 0, blk_hash);
263  TIME_MEASURE_FINISH(time1);
264  time_add_block1 += time1;
265  //voting mechanism
266  m_hardfork->add(blk, prev_height);
267 
268  ++num_calls;
269 
270  return prev_height;
271 }
272 
274 {
275  m_hardfork = hf;
276 }
277 
278 void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs)
279 {
280  blk = get_top_block();
281 
282  remove_block();
283 
284  for (const auto& h : boost::adaptors::reverse(blk.tx_hashes))
285  {
287  if (!get_tx(h, tx) && !get_pruned_tx(h, tx))
288  throw DB_ERROR("Failed to get pruned or unpruned transaction from the db");
289  txs.push_back(std::move(tx));
290  remove_transaction(h);
291  }
292  remove_transaction(get_transaction_hash(blk.miner_tx));
293 }
294 
296 {
297  return m_open;
298 }
299 
300 void BlockchainDB::remove_transaction(const crypto::hash& tx_hash)
301 {
302  transaction tx = get_pruned_tx(tx_hash);
303 
304  for (const txin_v& tx_input : tx.vin)
305  {
306  if (tx_input.type() == typeid(txin_to_key))
307  {
308  remove_spent_key(boost::get<txin_to_key>(tx_input).k_image);
309  }
310  else if (tx_input.type() == typeid(txin_to_key_public))
311  {
312  const auto &txin = boost::get<txin_to_key_public>(tx_input); // input being used in the tx to be removed.
313 
314  transaction parent_tx = get_tx(txin.tx_hash);
315  const auto &txout = boost::get<txout_to_key_public>(parent_tx.vout[txin.relative_offset].target); //previous tx out that this tx in references
316  //reinstate that out as a utxo
317  bool reinstate_coinbase = cryptonote::is_coinbase(get_pruned_tx(txin.tx_hash));
318  add_chainstate_utxo(txin.tx_hash, txin.relative_offset, addKeys(txout.address.m_view_public_key, txout.address.m_spend_public_key), txin.amount, get_tx_unlock_time(txin.tx_hash), reinstate_coinbase);
319  remove_tx_input(txin.tx_hash, txin.relative_offset);
320  }
321  }
322 
323  if (tx.version >= 2)
324  {
325  for (uint64_t i = 0; i < tx.vout.size(); ++i)
326  {
327  const auto &txout = boost::get<txout_to_key_public>(tx.vout[i].target);
328 
329  remove_chainstate_utxo(tx.hash, i);
330  remove_addr_output(tx_hash, i, addKeys(txout.address.m_view_public_key, txout.address.m_spend_public_key), tx.vout[i].amount, tx.unlock_time);
331  }
332  }
333 
334  // need tx as tx.vout has the tx outputs, and the output amounts are needed
335  remove_transaction_data(tx_hash, tx);
336 }
337 
339 {
341  block b;
343  throw DB_ERROR("Failed to parse block from blob retrieved from the db");
344 
345  return b;
346 }
347 
349 {
350  blobdata bd = get_block_blob(h);
351  block b;
353  throw DB_ERROR("Failed to parse block from blob retrieved from the db");
354 
355  return b;
356 }
357 
359 {
360  blobdata bd;
361  if (!get_tx_blob(h, bd))
362  return false;
363  if (!parse_and_validate_tx_from_blob(bd, tx))
364  throw DB_ERROR("Failed to parse transaction from blob retrieved from the db");
365 
366  return true;
367 }
368 
370 {
371  blobdata bd;
372  if (!get_pruned_tx_blob(h, bd))
373  return false;
375  throw DB_ERROR("Failed to parse transaction base from blob retrieved from the db");
376 
377  return true;
378 }
379 
381 {
382  transaction tx;
383  if (!get_tx(h, tx))
384  throw TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str());
385  return tx;
386 }
387 
389 {
390  transaction tx;
391  if (!get_pruned_tx(h, tx))
392  throw TX_DNE(std::string("pruned tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str());
393  return tx;
394 }
395 
397 {
398  num_calls = 0;
399  time_blk_hash = 0;
400  time_tx_exists = 0;
401  time_add_block1 = 0;
402  time_add_transaction = 0;
403  time_commit1 = 0;
404 }
405 
407 {
409  << "*********************************"
410  << ENDL
411  << "num_calls: " << num_calls
412  << ENDL
413  << "time_blk_hash: " << time_blk_hash << "ms"
414  << ENDL
415  << "time_tx_exists: " << time_tx_exists << "ms"
416  << ENDL
417  << "time_add_block1: " << time_add_block1 << "ms"
418  << ENDL
419  << "time_add_transaction: " << time_add_transaction << "ms"
420  << ENDL
421  << "time_commit1: " << time_commit1 << "ms"
422  << ENDL
423  << "*********************************"
424  << ENDL
425  );
426 }
427 
429 {
430  if (is_read_only()) {
431  LOG_PRINT_L1("Database is opened read only - skipping fixup check");
432  return;
433  }
434 
435  // Apply fixes to DB if needed.
436 
437  batch_stop();
438 }
439 
440 } // namespace cryptonote
uint64_t height
Definition: blockchain.cpp:91
The BlockchainDB backing store interface declaration/contract.
virtual cryptonote::blobdata get_block_blob_from_height(const uint64_t &height) const =0
fetch a block blob by height
virtual void set_hard_fork(HardFork *hf)
virtual block get_block_from_height(const uint64_t &height) const
fetch a block by height
static void init_options(boost::program_options::options_description &desc)
init command line options
virtual bool get_tx_blob(const crypto::hash &h, cryptonote::blobdata &tx) const =0
fetches the transaction blob with the given hash
virtual void batch_stop()=0
ends a batch transaction
bool m_open
Whether or not the BlockchainDB is open/ready for use.
virtual block get_block(const crypto::hash &h) const
fetches the block with the given hash
void show_stats()
show profiling stats
virtual transaction get_pruned_tx(const crypto::hash &h) const
fetches the transaction base with the given hash
virtual bool is_read_only() const =0
is BlockchainDB in read-only mode?
virtual uint64_t height() const =0
fetch the current blockchain height
virtual block get_top_block() const =0
fetch the top block
void add_transaction(const crypto::hash &blk_hash, const std::pair< transaction, blobdata > &tx, const crypto::hash *tx_hash_ptr=NULL, const crypto::hash *tx_prunable_hash_ptr=NULL)
helper function for add_transactions, to add each individual transaction
bool is_open() const
Gets the current open/ready state of the BlockchainDB.
virtual void fixup()
fix up anything that may be wrong due to past bugs
virtual uint64_t get_tx_unlock_time(const crypto::hash &h) const =0
fetch a transaction's unlock time/height
virtual cryptonote::blobdata get_block_blob(const crypto::hash &h) const =0
fetches the block with the given hash
virtual bool get_pruned_tx_blob(const crypto::hash &h, cryptonote::blobdata &tx) const =0
fetches the pruned transaction blob with the given hash
void reset_stats()
reset profiling stats
virtual transaction get_tx(const crypto::hash &h) const
fetches the transaction with the given hash
uint64_t time_tx_exists
a performance metric
uint64_t time_commit1
a performance metric
A generic BlockchainDB exception.
bool add(const cryptonote::block &block, uint64_t height)
add a new block
Definition: hardfork.cpp:164
thrown when a requested transaction does not exist
#define LOG_PRINT_L3(x)
Definition: misc_log_ex.h:102
#define ENDL
Definition: misc_log_ex.h:149
#define LOG_PRINT_L1(x)
Definition: misc_log_ex.h:100
void add_arg(boost::program_options::options_description &description, const arg_descriptor< T, required, dependent, NUM_DEPS > &arg, bool unique=true)
Definition: command_line.h:188
public_key addKeys(const public_key &A, const public_key &B)
Definition: crypto.h:337
POD_CLASS hash
Definition: hash.h:50
Holds cryptonote related classes and helpers.
Definition: ban.cpp:40
boost::multiprecision::uint128_t difficulty_type
Definition: difficulty.h:43
std::string arg_db_type_description
const command_line::arg_descriptor< bool > arg_db_salvage
boost::variant< txin_gen, txin_to_script, txin_to_scripthash, txin_to_key, txin_to_key_public > txin_v
bool get_block_hash(const block &b, crypto::hash &res)
bool parse_and_validate_block_from_blob(const blobdata &b_blob, block &b, crypto::hash *block_hash)
BlockchainDB * new_db(const std::string &db_type)
crypto::hash get_transaction_hash(const transaction &t)
const command_line::arg_descriptor< std::string > arg_db_type
bool is_coinbase(const transaction &tx)
bool blockchain_valid_db_type(const std::string &db_type)
blobdata tx_to_blob(const transaction &tx)
std::string blobdata
Definition: blobdatatype.h:39
bool parse_and_validate_tx_from_blob(const blobdata &tx_blob, transaction &tx)
std::string blockchain_db_types(const std::string &sep)
const command_line::arg_descriptor< std::string > arg_db_sync_mode
bool parse_and_validate_tx_base_from_blob(const blobdata &tx_blob, transaction &tx)
uint64_t get_tick_count()
std::string pod_to_hex(const t_pod_type &s)
Definition: string_tools.h:317
::std::string string
Definition: gtest-port.h:1097
const T & move(const T &t)
Definition: gtest-port.h:1317
#define TIME_MEASURE_FINISH(var_name)
Definition: profile_tools.h:64
#define TIME_MEASURE_START(var_name)
Definition: profile_tools.h:61
unsigned __int64 uint64_t
Definition: stdint.h:136
std::vector< crypto::hash > tx_hashes