oxen-core/src/blockchain_db/blockchain_db.cpp
Jason Rhinelander 4f721c0098 Make tx type and version scoped enums
This converts the transaction type and version to scoped enum, giving
type safety and making the tx type assignment less error prone because
there is no implicit conversion or comparison with raw integers that has
to be worried about.

This ends up converting any use of `cryptonote::transaction::type_xyz`
to `cryptonote::transaction::txtype::xyz`.  For version, names like
`transaction::version_v4` become `cryptonote::txversion::v4_tx_types`.

This also allows/includes various other simplifications related to or
enabled by this change:
- handle `is_deregister` dynamically in serialization code (setting
  `type::standard` or `type::deregister` rather than using a
  version-determined union)
- `get_type()` is no longer needed with the above change: it is now
  much simpler to directly access `type` which will always have the
  correct value (even for v2 or v3 transaction types).  And though there
  was an assertion on the enum value, `get_type()` was being used only
  sporadically: many places accessed `.type` directly.
- the old unscoped enum didn't have a type but was assumed castable
  to/from `uint16_t`, which technically meant there was potential
  undefined behaviour when deserializing any type values >= 8.
- tx type range checks weren't being done in all serialization paths;
  they are now.  Because `get_type()` was not used everywhere (lots of
  places simply accessed `.type` directory) these might not have been
  caught.
- `set_type()` is not needed; it was only being used in a single place
  (wallet2.cpp) and only for v4 txes, so the version protection code was
  never doing anything.
- added a std::ostream << operator for the enum types so that they can be
  output with `<< tx_type <<` rather than needing to wrap it in
  `type_to_string(tx_type)` everywhere.  For the versions, you get the
  annotated version string (e.g. 4_tx_types) rather than just the number
  4.
2019-06-19 17:47:03 -03:00

426 lines
12 KiB
C++

// Copyright (c) 2014-2019, The Monero Project
// Copyright (c) 2018, The Loki Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/range/adaptor/reversed.hpp>
#include "string_tools.h"
#include "blockchain_db.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "profile_tools.h"
#include "ringct/rctOps.h"
#include "lmdb/db_lmdb.h"
#ifdef BERKELEY_DB
#include "berkeleydb/db_bdb.h"
#endif
static const char *db_types[] = {
"lmdb",
#ifdef BERKELEY_DB
"berkeley",
#endif
NULL
};
#undef LOKI_DEFAULT_LOG_CATEGORY
#define LOKI_DEFAULT_LOG_CATEGORY "blockchain.db"
using epee::string_tools::pod_to_hex;
namespace cryptonote
{
bool blockchain_valid_db_type(const std::string& db_type)
{
int i;
for (i=0; db_types[i]; i++)
{
if (db_types[i] == db_type)
return true;
}
return false;
}
std::string blockchain_db_types(const std::string& sep)
{
int i;
std::string ret = "";
for (i=0; db_types[i]; i++)
{
if (i)
ret += sep;
ret += db_types[i];
}
return ret;
}
std::string arg_db_type_description = "Specify database type, available: " + cryptonote::blockchain_db_types(", ");
const command_line::arg_descriptor<std::string> arg_db_type = {
"db-type"
, arg_db_type_description.c_str()
, DEFAULT_DB_TYPE
};
const command_line::arg_descriptor<std::string> arg_db_sync_mode = {
"db-sync-mode"
, "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]."
, "fast:async:250000000bytes"
};
const command_line::arg_descriptor<bool> arg_db_salvage = {
"db-salvage"
, "Try to salvage a blockchain database if it seems corrupted"
, false
};
BlockchainDB *new_db(const std::string& db_type)
{
if (db_type == "lmdb")
return new BlockchainLMDB();
#if defined(BERKELEY_DB)
if (db_type == "berkeley")
return new BlockchainBDB();
#endif
return NULL;
}
void BlockchainDB::init_options(boost::program_options::options_description& desc)
{
command_line::add_arg(desc, arg_db_type);
command_line::add_arg(desc, arg_db_sync_mode);
command_line::add_arg(desc, arg_db_salvage);
}
void BlockchainDB::pop_block()
{
block blk;
std::vector<transaction> txs;
pop_block(blk, txs);
}
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)
{
const transaction &tx = txp.first;
bool miner_tx = false;
crypto::hash tx_hash, tx_prunable_hash;
if (!tx_hash_ptr)
{
// should only need to compute hash for miner transactions
tx_hash = get_transaction_hash(tx);
LOG_PRINT_L3("null tx_hash_ptr - needed to compute: " << tx_hash);
}
else
{
tx_hash = *tx_hash_ptr;
}
bool has_blacklisted_outputs = false;
if (tx.version >= cryptonote::txversion::v2_ringct)
{
if (!tx_prunable_hash_ptr)
tx_prunable_hash = get_transaction_prunable_hash(tx, &txp.second);
else
tx_prunable_hash = *tx_prunable_hash_ptr;
crypto::secret_key secret_tx_key;
cryptonote::account_public_address address;
if (get_tx_secret_key_from_tx_extra(tx.extra, secret_tx_key) && get_service_node_contributor_from_tx_extra(tx.extra, address))
has_blacklisted_outputs = true;
}
for (const txin_v& tx_input : tx.vin)
{
if (tx_input.type() == typeid(txin_to_key))
{
add_spent_key(boost::get<txin_to_key>(tx_input).k_image);
}
else if (tx_input.type() == typeid(txin_gen))
{
/* nothing to do here */
miner_tx = true;
}
else
{
LOG_PRINT_L1("Unsupported input type, removing key images and aborting transaction addition");
for (const txin_v& tx_input : tx.vin)
{
if (tx_input.type() == typeid(txin_to_key))
{
remove_spent_key(boost::get<txin_to_key>(tx_input).k_image);
}
}
return;
}
}
uint64_t tx_id = add_transaction_data(blk_hash, txp, tx_hash, tx_prunable_hash);
std::vector<uint64_t> amount_output_indices(tx.vout.size());
// iterate tx.vout using indices instead of C++11 foreach syntax because
// we need the index
for (uint64_t i = 0; i < tx.vout.size(); ++i)
{
uint64_t unlock_time = 0;
if (tx.version >= cryptonote::txversion::v3_per_output_unlock_times)
{
unlock_time = tx.output_unlock_times[i];
}
else
{
unlock_time = tx.unlock_time;
}
// miner v2 txes have their coinbase output in one single out to save space,
// and we store them as rct outputs with an identity mask
if (miner_tx && tx.version >= cryptonote::txversion::v2_ringct)
{
cryptonote::tx_out vout = tx.vout[i];
const rct::key commitment = rct::zeroCommit(vout.amount);
vout.amount = 0;
amount_output_indices[i] = add_output(tx_hash, vout, i, unlock_time,
&commitment);
}
else
{
amount_output_indices[i] = add_output(tx_hash, tx.vout[i], i, unlock_time,
tx.version >= cryptonote::txversion::v2_ringct ? &tx.rct_signatures.outPk[i].mask : NULL);
}
}
if (has_blacklisted_outputs)
add_output_blacklist(amount_output_indices);
add_tx_amount_output_indices(tx_id, amount_output_indices);
}
uint64_t BlockchainDB::add_block( const std::pair<block, blobdata>& blck
, size_t block_weight
, uint64_t long_term_block_weight
, const difficulty_type& cumulative_difficulty
, const uint64_t& coins_generated
, const std::vector<std::pair<transaction, blobdata>>& txs
)
{
const block &blk = blck.first;
// sanity
if (blk.tx_hashes.size() != txs.size())
throw std::runtime_error("Inconsistent tx/hashes sizes");
TIME_MEASURE_START(time1);
crypto::hash blk_hash = get_block_hash(blk);
TIME_MEASURE_FINISH(time1);
time_blk_hash += time1;
uint64_t prev_height = height();
// call out to add the transactions
time1 = epee::misc_utils::get_tick_count();
uint64_t num_rct_outs = 0;
add_transaction(blk_hash, std::make_pair(blk.miner_tx, tx_to_blob(blk.miner_tx)));
if (blk.miner_tx.version >= cryptonote::txversion::v2_ringct)
num_rct_outs += blk.miner_tx.vout.size();
int tx_i = 0;
crypto::hash tx_hash = crypto::null_hash;
for (const std::pair<transaction, blobdata>& tx : txs)
{
tx_hash = blk.tx_hashes[tx_i];
add_transaction(blk_hash, tx, &tx_hash);
for (const auto &vout: tx.first.vout)
{
if (vout.amount == 0)
++num_rct_outs;
}
++tx_i;
}
TIME_MEASURE_FINISH(time1);
time_add_transaction += time1;
// call out to subclass implementation to add the block & metadata
time1 = epee::misc_utils::get_tick_count();
add_block(blk, block_weight, long_term_block_weight, cumulative_difficulty, coins_generated, num_rct_outs, blk_hash);
TIME_MEASURE_FINISH(time1);
time_add_block1 += time1;
m_hardfork->add(blk, prev_height);
++num_calls;
return prev_height;
}
void BlockchainDB::set_hard_fork(HardFork* hf)
{
m_hardfork = hf;
}
void BlockchainDB::pop_block(block& blk, std::vector<transaction>& txs)
{
blk = get_top_block();
remove_block();
for (const auto& h : boost::adaptors::reverse(blk.tx_hashes))
{
cryptonote::transaction tx;
if (!get_tx(h, tx) && !get_pruned_tx(h, tx))
throw DB_ERROR("Failed to get pruned or unpruned transaction from the db");
txs.push_back(std::move(tx));
remove_transaction(h);
}
remove_transaction(get_transaction_hash(blk.miner_tx));
}
void BlockchainDB::remove_transaction(const crypto::hash& tx_hash)
{
transaction tx = get_pruned_tx(tx_hash);
for (const txin_v& tx_input : tx.vin)
{
if (tx_input.type() == typeid(txin_to_key))
{
remove_spent_key(boost::get<txin_to_key>(tx_input).k_image);
}
}
// need tx as tx.vout has the tx outputs, and the output amounts are needed
remove_transaction_data(tx_hash, tx);
}
block BlockchainDB::get_block_from_height(const uint64_t& height) const
{
blobdata bd = get_block_blob_from_height(height);
block b;
if (!parse_and_validate_block_from_blob(bd, b))
throw DB_ERROR("Failed to parse block from blob retrieved from the db");
return b;
}
block BlockchainDB::get_block(const crypto::hash& h) const
{
blobdata bd = get_block_blob(h);
block b;
if (!parse_and_validate_block_from_blob(bd, b))
throw DB_ERROR("Failed to parse block from blob retrieved from the db");
return b;
}
bool BlockchainDB::get_tx(const crypto::hash& h, cryptonote::transaction &tx) const
{
blobdata bd;
if (!get_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_from_blob(bd, tx))
throw DB_ERROR("Failed to parse transaction from blob retrieved from the db");
return true;
}
bool BlockchainDB::get_pruned_tx(const crypto::hash& h, cryptonote::transaction &tx) const
{
blobdata bd;
if (!get_pruned_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_base_from_blob(bd, tx))
throw DB_ERROR("Failed to parse transaction base from blob retrieved from the db");
return true;
}
transaction BlockchainDB::get_tx(const crypto::hash& h) const
{
transaction tx;
if (!get_tx(h, tx))
throw TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str());
return tx;
}
uint64_t BlockchainDB::get_output_unlock_time(const uint64_t amount, const uint64_t amount_index) const
{
output_data_t odata = get_output_key(amount, amount_index);
return odata.unlock_time;
}
transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const
{
transaction tx;
if (!get_pruned_tx(h, tx))
throw TX_DNE(std::string("pruned tx with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found in db").c_str());
return tx;
}
void BlockchainDB::reset_stats()
{
num_calls = 0;
time_blk_hash = 0;
time_tx_exists = 0;
time_add_block1 = 0;
time_add_transaction = 0;
time_commit1 = 0;
}
void BlockchainDB::show_stats()
{
LOG_PRINT_L1(ENDL
<< "*********************************"
<< ENDL
<< "num_calls: " << num_calls
<< ENDL
<< "time_blk_hash: " << time_blk_hash << "ms"
<< ENDL
<< "time_tx_exists: " << time_tx_exists << "ms"
<< ENDL
<< "time_add_block1: " << time_add_block1 << "ms"
<< ENDL
<< "time_add_transaction: " << time_add_transaction << "ms"
<< ENDL
<< "time_commit1: " << time_commit1 << "ms"
<< ENDL
<< "*********************************"
<< ENDL
);
}
void BlockchainDB::fixup(fixup_context const context)
{
(void)context;
if (is_read_only()) {
LOG_PRINT_L1("Database is opened read only - skipping fixup check");
return;
}
set_batch_transactions(true);
}
} // namespace cryptonote