// Copyright (c) 2011-2016 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "Blockchain.h" #include #include #include #include "Common/Math.h" #include "Common/ShuffleGenerator.h" #include "Common/StdInputStream.h" #include "Common/StdOutputStream.h" #include "Rpc/CoreRpcServerCommandsDefinitions.h" #include "Serialization/BinarySerializationTools.h" #include "CryptoNoteTools.h" using namespace Logging; using namespace Common; namespace { std::string appendPath(const std::string& path, const std::string& fileName) { std::string result = path; if (!result.empty()) { result += '/'; } result += fileName; return result; } } namespace std { bool operator<(const Crypto::Hash& hash1, const Crypto::Hash& hash2) { return memcmp(&hash1, &hash2, Crypto::HASH_SIZE) < 0; } bool operator<(const Crypto::KeyImage& keyImage1, const Crypto::KeyImage& keyImage2) { return memcmp(&keyImage1, &keyImage2, 32) < 0; } } #define CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER 1 #define CURRENT_BLOCKCHAININDICES_STORAGE_ARCHIVE_VER 1 namespace CryptoNote { class BlockCacheSerializer; class BlockchainIndicesSerializer; } namespace CryptoNote { template bool serialize(google::sparse_hash_map& value, Common::StringView name, CryptoNote::ISerializer& serializer) { return serializeMap(value, name, serializer, [&value](size_t size) { value.resize(size); }); } template bool serialize(google::sparse_hash_set& value, Common::StringView name, CryptoNote::ISerializer& serializer) { size_t size = value.size(); if (!serializer.beginArray(size, name)) { return false; } if (serializer.type() == ISerializer::OUTPUT) { for (auto& key : value) { serializer(const_cast(key), ""); } } else { value.resize(size); while (size--) { K key; serializer(key, ""); value.insert(key); } } serializer.endArray(); return true; } // custom serialization to speedup cache loading bool serialize(std::vector>& value, Common::StringView name, CryptoNote::ISerializer& s) { const size_t elementSize = sizeof(std::pair); size_t size = value.size() * elementSize; if (!s.beginArray(size, name)) { return false; } if (s.type() == CryptoNote::ISerializer::INPUT) { if (size % elementSize != 0) { throw std::runtime_error("Invalid vector size"); } value.resize(size / elementSize); } if (size) { s.binary(value.data(), size, ""); } s.endArray(); return true; } void serialize(Blockchain::TransactionIndex& value, ISerializer& s) { s(value.block, "block"); s(value.transaction, "tx"); } class BlockCacheSerializer { public: BlockCacheSerializer(Blockchain& bs, const Crypto::Hash lastBlockHash, ILogger& logger) : m_bs(bs), m_lastBlockHash(lastBlockHash), m_loaded(false), logger(logger, "BlockCacheSerializer") { } void load(const std::string& filename) { try { std::ifstream stdStream(filename, std::ios::binary); if (!stdStream) { return; } StdInputStream stream(stdStream); BinaryInputStreamSerializer s(stream); CryptoNote::serialize(*this, s); } catch (std::exception& e) { logger(WARNING) << "loading failed: " << e.what(); } } bool save(const std::string& filename) { try { std::ofstream file(filename, std::ios::binary); if (!file) { return false; } StdOutputStream stream(file); BinaryOutputStreamSerializer s(stream); CryptoNote::serialize(*this, s); } catch (std::exception&) { return false; } return true; } void serialize(ISerializer& s) { auto start = std::chrono::steady_clock::now(); uint8_t version = CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER; s(version, "version"); // ignore old versions, do rebuild if (version < CURRENT_BLOCKCACHE_STORAGE_ARCHIVE_VER) return; std::string operation; if (s.type() == ISerializer::INPUT) { operation = "- loading "; Crypto::Hash blockHash; s(blockHash, "last_block"); if (blockHash != m_lastBlockHash) { return; } } else { operation = "- saving "; s(m_lastBlockHash, "last_block"); } logger(INFO) << operation << "block index..."; s(m_bs.m_blockIndex, "block_index"); logger(INFO) << operation << "transaction map..."; s(m_bs.m_transactionMap, "transactions"); logger(INFO) << operation << "spent keys..."; s(m_bs.m_spent_keys, "spent_keys"); logger(INFO) << operation << "outputs..."; s(m_bs.m_outputs, "outputs"); logger(INFO) << operation << "multi-signature outputs..."; s(m_bs.m_multisignatureOutputs, "multisig_outputs"); auto dur = std::chrono::steady_clock::now() - start; logger(INFO) << "Serialization time: " << std::chrono::duration_cast(dur).count() << "ms"; m_loaded = true; } bool loaded() const { return m_loaded; } private: LoggerRef logger; bool m_loaded; Blockchain& m_bs; Crypto::Hash m_lastBlockHash; }; class BlockchainIndicesSerializer { public: BlockchainIndicesSerializer(Blockchain& bs, const Crypto::Hash lastBlockHash, ILogger& logger) : m_bs(bs), m_lastBlockHash(lastBlockHash), m_loaded(false), logger(logger, "BlockchainIndicesSerializer") { } void serialize(ISerializer& s) { uint8_t version = CURRENT_BLOCKCHAININDICES_STORAGE_ARCHIVE_VER; KV_MEMBER(version); // ignore old versions, do rebuild if (version != CURRENT_BLOCKCHAININDICES_STORAGE_ARCHIVE_VER) return; std::string operation; if (s.type() == ISerializer::INPUT) { operation = "- loading "; Crypto::Hash blockHash; s(blockHash, "blockHash"); if (blockHash != m_lastBlockHash) { return; } } else { operation = "- saving "; s(m_lastBlockHash, "blockHash"); } logger(INFO) << operation << "paymentID index..."; s(m_bs.m_paymentIdIndex, "paymentIdIndex"); logger(INFO) << operation << "timestamp index..."; s(m_bs.m_timestampIndex, "timestampIndex"); logger(INFO) << operation << "generated transactions index..."; s(m_bs.m_generatedTransactionsIndex, "generatedTransactionsIndex"); m_loaded = true; } template void serialize(Archive& ar, unsigned int version) { // ignore old versions, do rebuild if (version < CURRENT_BLOCKCHAININDICES_STORAGE_ARCHIVE_VER) return; std::string operation; if (Archive::is_loading::value) { operation = "- loading "; Crypto::Hash blockHash; ar & blockHash; if (blockHash != m_lastBlockHash) { return; } } else { operation = "- saving "; ar & m_lastBlockHash; } logger(INFO) << operation << "paymentID index..."; ar & m_bs.m_paymentIdIndex; logger(INFO) << operation << "timestamp index..."; ar & m_bs.m_timestampIndex; logger(INFO) << operation << "generated transactions index..."; ar & m_bs.m_generatedTransactionsIndex; m_loaded = true; } bool loaded() const { return m_loaded; } private: LoggerRef logger; bool m_loaded; Blockchain& m_bs; Crypto::Hash m_lastBlockHash; }; Blockchain::Blockchain(const Currency& currency, tx_memory_pool& tx_pool, ILogger& logger) : logger(logger, "Blockchain"), m_currency(currency), m_tx_pool(tx_pool), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false), m_checkpoints(logger) { m_outputs.set_deleted_key(0); Crypto::KeyImage nullImage = boost::value_initialized(); m_spent_keys.set_deleted_key(nullImage); } bool Blockchain::addObserver(IBlockchainStorageObserver* observer) { return m_observerManager.add(observer); } bool Blockchain::removeObserver(IBlockchainStorageObserver* observer) { return m_observerManager.remove(observer); } bool Blockchain::checkTransactionInputs(const CryptoNote::Transaction& tx, BlockInfo& maxUsedBlock) { return checkTransactionInputs(tx, maxUsedBlock.height, maxUsedBlock.id); } bool Blockchain::checkTransactionInputs(const CryptoNote::Transaction& tx, BlockInfo& maxUsedBlock, BlockInfo& lastFailed) { BlockInfo tail; //not the best implementation at this time, sorry :( //check is ring_signature already checked ? if (maxUsedBlock.empty()) { //not checked, lets try to check if (!lastFailed.empty() && getCurrentBlockchainHeight() > lastFailed.height && getBlockIdByHeight(lastFailed.height) == lastFailed.id) { return false; //we already sure that this tx is broken for this height } if (!checkTransactionInputs(tx, maxUsedBlock.height, maxUsedBlock.id, &tail)) { lastFailed = tail; return false; } } else { if (maxUsedBlock.height >= getCurrentBlockchainHeight()) { return false; } if (getBlockIdByHeight(maxUsedBlock.height) != maxUsedBlock.id) { //if we already failed on this height and id, skip actual ring signature check if (lastFailed.id == getBlockIdByHeight(lastFailed.height)) { return false; } //check ring signature again, it is possible (with very small chance) that this transaction become again valid if (!checkTransactionInputs(tx, maxUsedBlock.height, maxUsedBlock.id, &tail)) { lastFailed = tail; return false; } } } return true; } bool Blockchain::haveSpentKeyImages(const CryptoNote::Transaction& tx) { return this->haveTransactionKeyImagesAsSpent(tx); } /** * \pre m_blockchain_lock is locked */ bool Blockchain::checkTransactionSize(size_t blobSize) { if (blobSize > getCurrentCumulativeBlocksizeLimit() - m_currency.minerTxBlobReservedSize()) { logger(ERROR) << "transaction is too big " << blobSize << ", maximum allowed size is " << (getCurrentCumulativeBlocksizeLimit() - m_currency.minerTxBlobReservedSize()); return false; } return true; } bool Blockchain::haveTransaction(const Crypto::Hash &id) { std::lock_guard lk(m_blockchain_lock); return m_transactionMap.find(id) != m_transactionMap.end(); } bool Blockchain::have_tx_keyimg_as_spent(const Crypto::KeyImage &key_im) { std::lock_guard lk(m_blockchain_lock); return m_spent_keys.find(key_im) != m_spent_keys.end(); } uint32_t Blockchain::getCurrentBlockchainHeight() { std::lock_guard lk(m_blockchain_lock); return static_cast(m_blocks.size()); } bool Blockchain::init(const std::string& config_folder, bool load_existing) { std::lock_guard lk(m_blockchain_lock); if (!config_folder.empty() && !Tools::create_directories_if_necessary(config_folder)) { logger(ERROR, BRIGHT_RED) << "Failed to create data directory: " << m_config_folder; return false; } m_config_folder = config_folder; if (!m_blocks.open(appendPath(config_folder, m_currency.blocksFileName()), appendPath(config_folder, m_currency.blockIndexesFileName()), 1024)) { return false; } if (load_existing && !m_blocks.empty()) { logger(INFO, BRIGHT_WHITE) << "Loading blockchain..."; BlockCacheSerializer loader(*this, get_block_hash(m_blocks.back().bl), logger.getLogger()); loader.load(appendPath(config_folder, m_currency.blocksCacheFileName())); if (!loader.loaded()) { logger(WARNING, BRIGHT_YELLOW) << "No actual blockchain cache found, rebuilding internal structures..."; rebuildCache(); } loadBlockchainIndices(); } else { m_blocks.clear(); } if (m_blocks.empty()) { logger(INFO, BRIGHT_WHITE) << "Blockchain not loaded, generating genesis block."; block_verification_context bvc = boost::value_initialized(); pushBlock(m_currency.genesisBlock(), bvc); if (bvc.m_verifivation_failed) { logger(ERROR, BRIGHT_RED) << "Failed to add genesis block to blockchain"; return false; } } else { Crypto::Hash firstBlockHash = get_block_hash(m_blocks[0].bl); if (!(firstBlockHash == m_currency.genesisBlockHash())) { logger(ERROR, BRIGHT_RED) << "Failed to init: genesis block mismatch. " "Probably you set --testnet flag with data " "dir with non-test blockchain or another " "network."; return false; } } update_next_comulative_size_limit(); uint64_t timestamp_diff = time(NULL) - m_blocks.back().bl.timestamp; if (!m_blocks.back().bl.timestamp) { timestamp_diff = time(NULL) - 1341378000; } logger(INFO, BRIGHT_GREEN) << "Blockchain initialized. last block: " << m_blocks.size() - 1 << ", " << Common::timeIntervalToString(timestamp_diff) << " time ago, current difficulty: " << getDifficultyForNextBlock(); return true; } void Blockchain::rebuildCache() { std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now(); m_blockIndex.clear(); m_transactionMap.clear(); m_spent_keys.clear(); m_outputs.clear(); m_multisignatureOutputs.clear(); for (uint32_t b = 0; b < m_blocks.size(); ++b) { if (b % 1000 == 0) { logger(INFO, BRIGHT_WHITE) << "Height " << b << " of " << m_blocks.size(); } const BlockEntry& block = m_blocks[b]; Crypto::Hash blockHash = get_block_hash(block.bl); m_blockIndex.push(blockHash); for (uint16_t t = 0; t < block.transactions.size(); ++t) { const TransactionEntry& transaction = block.transactions[t]; Crypto::Hash transactionHash = getObjectHash(transaction.tx); TransactionIndex transactionIndex = { b, t }; m_transactionMap.insert(std::make_pair(transactionHash, transactionIndex)); // process inputs for (auto& i : transaction.tx.inputs) { if (i.type() == typeid(KeyInput)) { m_spent_keys.insert(::boost::get(i).keyImage); } else if (i.type() == typeid(MultisignatureInput)) { auto out = ::boost::get(i); m_multisignatureOutputs[out.amount][out.outputIndex].isUsed = true; } } // process outputs for (uint16_t o = 0; o < transaction.tx.outputs.size(); ++o) { const auto& out = transaction.tx.outputs[o]; if (out.target.type() == typeid(KeyOutput)) { m_outputs[out.amount].push_back(std::make_pair<>(transactionIndex, o)); } else if (out.target.type() == typeid(MultisignatureOutput)) { MultisignatureOutputUsage usage = { transactionIndex, o, false }; m_multisignatureOutputs[out.amount].push_back(usage); } } } } std::chrono::duration duration = std::chrono::steady_clock::now() - timePoint; logger(INFO, BRIGHT_WHITE) << "Rebuilding internal structures took: " << duration.count(); } bool Blockchain::storeCache() { std::lock_guard lk(m_blockchain_lock); logger(INFO, BRIGHT_WHITE) << "Saving blockchain..."; BlockCacheSerializer ser(*this, getTailId(), logger.getLogger()); if (!ser.save(appendPath(m_config_folder, m_currency.blocksCacheFileName()))) { logger(ERROR, BRIGHT_RED) << "Failed to save blockchain cache"; return false; } return true; } bool Blockchain::deinit() { storeCache(); storeBlockchainIndices(); assert(m_messageQueueList.empty()); return true; } bool Blockchain::resetAndSetGenesisBlock(const Block& b) { std::lock_guard lk(m_blockchain_lock); m_blocks.clear(); m_blockIndex.clear(); m_transactionMap.clear(); m_spent_keys.clear(); m_alternative_chains.clear(); m_outputs.clear(); m_paymentIdIndex.clear(); m_timestampIndex.clear(); m_generatedTransactionsIndex.clear(); m_orthanBlocksIndex.clear(); block_verification_context bvc = boost::value_initialized(); addNewBlock(b, bvc); return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed; } Crypto::Hash Blockchain::getTailId(uint32_t& height) { assert(!m_blocks.empty()); std::lock_guard lk(m_blockchain_lock); height = getCurrentBlockchainHeight() - 1; return getTailId(); } Crypto::Hash Blockchain::getTailId() { std::lock_guard lk(m_blockchain_lock); return m_blocks.empty() ? NULL_HASH : m_blockIndex.getTailId(); } std::vector Blockchain::buildSparseChain() { std::lock_guard lk(m_blockchain_lock); assert(m_blockIndex.size() != 0); return doBuildSparseChain(m_blockIndex.getTailId()); } std::vector Blockchain::buildSparseChain(const Crypto::Hash& startBlockId) { std::lock_guard lk(m_blockchain_lock); assert(haveBlock(startBlockId)); return doBuildSparseChain(startBlockId); } std::vector Blockchain::doBuildSparseChain(const Crypto::Hash& startBlockId) const { assert(m_blockIndex.size() != 0); std::vector sparseChain; if (m_blockIndex.hasBlock(startBlockId)) { sparseChain = m_blockIndex.buildSparseChain(startBlockId); } else { assert(m_alternative_chains.count(startBlockId) > 0); std::vector alternativeChain; Crypto::Hash blockchainAncestor; for (auto it = m_alternative_chains.find(startBlockId); it != m_alternative_chains.end(); it = m_alternative_chains.find(blockchainAncestor)) { alternativeChain.emplace_back(it->first); blockchainAncestor = it->second.bl.previousBlockHash; } for (size_t i = 1; i <= alternativeChain.size(); i *= 2) { sparseChain.emplace_back(alternativeChain[i - 1]); } assert(!sparseChain.empty()); assert(m_blockIndex.hasBlock(blockchainAncestor)); std::vector sparseMainChain = m_blockIndex.buildSparseChain(blockchainAncestor); sparseChain.reserve(sparseChain.size() + sparseMainChain.size()); std::copy(sparseMainChain.begin(), sparseMainChain.end(), std::back_inserter(sparseChain)); } return sparseChain; } Crypto::Hash Blockchain::getBlockIdByHeight(uint32_t height) { std::lock_guard lk(m_blockchain_lock); assert(height < m_blockIndex.size()); return m_blockIndex.getBlockId(height); } bool Blockchain::getBlockByHash(const Crypto::Hash& blockHash, Block& b) { std::lock_guard lk(m_blockchain_lock); uint32_t height = 0; if (m_blockIndex.getBlockHeight(blockHash, height)) { b = m_blocks[height].bl; return true; } logger(WARNING) << blockHash; auto blockByHashIterator = m_alternative_chains.find(blockHash); if (blockByHashIterator != m_alternative_chains.end()) { b = blockByHashIterator->second.bl; return true; } return false; } bool Blockchain::getBlockHeight(const Crypto::Hash& blockId, uint32_t& blockHeight) { std::lock_guard lock(m_blockchain_lock); return m_blockIndex.getBlockHeight(blockId, blockHeight); } difficulty_type Blockchain::getDifficultyForNextBlock() { std::lock_guard lk(m_blockchain_lock); std::vector timestamps; std::vector commulative_difficulties; size_t offset = m_blocks.size() - std::min(m_blocks.size(), static_cast(m_currency.difficultyBlocksCount())); if (offset == 0) { ++offset; } for (; offset < m_blocks.size(); offset++) { timestamps.push_back(m_blocks[offset].bl.timestamp); commulative_difficulties.push_back(m_blocks[offset].cumulative_difficulty); } return m_currency.nextDifficulty(timestamps, commulative_difficulties); } uint64_t Blockchain::getCoinsInCirculation() { std::lock_guard lk(m_blockchain_lock); if (m_blocks.empty()) { return 0; } else { return m_blocks.back().already_generated_coins; } } bool Blockchain::rollback_blockchain_switching(std::list &original_chain, size_t rollback_height) { std::lock_guard lk(m_blockchain_lock); // remove failed subchain for (size_t i = m_blocks.size() - 1; i >= rollback_height; i--) { popBlock(get_block_hash(m_blocks.back().bl)); } // return back original chain for (auto &bl : original_chain) { block_verification_context bvc = boost::value_initialized(); bool r = pushBlock(bl, bvc); if (!(r && bvc.m_added_to_main_chain)) { logger(ERROR, BRIGHT_RED) << "PANIC!!! failed to add (again) block while " "chain switching during the rollback!"; return false; } } logger(INFO, BRIGHT_WHITE) << "Rollback success."; return true; } bool Blockchain::switch_to_alternative_blockchain(std::list& alt_chain, bool discard_disconnected_chain) { std::lock_guard lk(m_blockchain_lock); if (!(alt_chain.size())) { logger(ERROR, BRIGHT_RED) << "switch_to_alternative_blockchain: empty chain passed"; return false; } size_t split_height = alt_chain.front()->second.height; if (!(m_blocks.size() > split_height)) { logger(ERROR, BRIGHT_RED) << "switch_to_alternative_blockchain: blockchain size is lower than split height"; return false; } //disconnecting old chain std::list disconnected_chain; for (size_t i = m_blocks.size() - 1; i >= split_height; i--) { Block b = m_blocks[i].bl; popBlock(get_block_hash(b)); //if (!(r)) { logger(ERROR, BRIGHT_RED) << "failed to remove block on chain switching"; return false; } disconnected_chain.push_front(b); } //connecting new alternative chain for (auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++) { auto ch_ent = *alt_ch_iter; block_verification_context bvc = boost::value_initialized(); bool r = pushBlock(ch_ent->second.bl, bvc); if (!r || !bvc.m_added_to_main_chain) { logger(INFO, BRIGHT_WHITE) << "Failed to switch to alternative blockchain"; rollback_blockchain_switching(disconnected_chain, split_height); //add_block_as_invalid(ch_ent->second, get_block_hash(ch_ent->second.bl)); logger(INFO, BRIGHT_WHITE) << "The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl); m_orthanBlocksIndex.remove(ch_ent->second.bl); m_alternative_chains.erase(ch_ent); for (auto alt_ch_to_orph_iter = ++alt_ch_iter; alt_ch_to_orph_iter != alt_chain.end(); alt_ch_to_orph_iter++) { //block_verification_context bvc = boost::value_initialized(); //add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first); m_orthanBlocksIndex.remove((*alt_ch_to_orph_iter)->second.bl); m_alternative_chains.erase(*alt_ch_to_orph_iter); } return false; } } if (!discard_disconnected_chain) { //pushing old chain as alternative chain for (auto& old_ch_ent : disconnected_chain) { block_verification_context bvc = boost::value_initialized(); bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc, false); if (!r) { logger(ERROR, BRIGHT_RED) << ("Failed to push ex-main chain blocks to alternative chain "); rollback_blockchain_switching(disconnected_chain, split_height); return false; } } } std::vector blocksFromCommonRoot; blocksFromCommonRoot.reserve(alt_chain.size() + 1); blocksFromCommonRoot.push_back(alt_chain.front()->second.bl.previousBlockHash); //removing all_chain entries from alternative chain for (auto ch_ent : alt_chain) { blocksFromCommonRoot.push_back(get_block_hash(ch_ent->second.bl)); m_orthanBlocksIndex.remove(ch_ent->second.bl); m_alternative_chains.erase(ch_ent); } sendMessage(BlockchainMessage(ChainSwitchMessage(std::move(blocksFromCommonRoot)))); logger(INFO, BRIGHT_GREEN) << "REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_blocks.size(); return true; } difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list& alt_chain, BlockEntry& bei) { std::vector timestamps; std::vector commulative_difficulties; if (alt_chain.size() < m_currency.difficultyBlocksCount()) { std::lock_guard lk(m_blockchain_lock); size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front()->second.height : bei.height; size_t main_chain_count = m_currency.difficultyBlocksCount() - std::min(m_currency.difficultyBlocksCount(), alt_chain.size()); main_chain_count = std::min(main_chain_count, main_chain_stop_offset); size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count; if (!main_chain_start_offset) ++main_chain_start_offset; //skip genesis block for (; main_chain_start_offset < main_chain_stop_offset; ++main_chain_start_offset) { timestamps.push_back(m_blocks[main_chain_start_offset].bl.timestamp); commulative_difficulties.push_back(m_blocks[main_chain_start_offset].cumulative_difficulty); } if (!((alt_chain.size() + timestamps.size()) <= m_currency.difficultyBlocksCount())) { logger(ERROR, BRIGHT_RED) << "Internal error, alt_chain.size()[" << alt_chain.size() << "] + timestamps.size()[" << timestamps.size() << "] NOT <= m_currency.difficultyBlocksCount()[" << m_currency.difficultyBlocksCount() << ']'; return false; } for (auto it : alt_chain) { timestamps.push_back(it->second.bl.timestamp); commulative_difficulties.push_back(it->second.cumulative_difficulty); } } else { timestamps.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount())); commulative_difficulties.resize(std::min(alt_chain.size(), m_currency.difficultyBlocksCount())); size_t count = 0; size_t max_i = timestamps.size() - 1; BOOST_REVERSE_FOREACH(auto it, alt_chain) { timestamps[max_i - count] = it->second.bl.timestamp; commulative_difficulties[max_i - count] = it->second.cumulative_difficulty; count++; if (count >= m_currency.difficultyBlocksCount()) { break; } } } return m_currency.nextDifficulty(timestamps, commulative_difficulties); } bool Blockchain::prevalidate_miner_transaction(const Block& b, uint32_t height) { if (!(b.baseTransaction.inputs.size() == 1)) { logger(ERROR, BRIGHT_RED) << "coinbase transaction in the block has no inputs"; return false; } if (!(b.baseTransaction.inputs[0].type() == typeid(BaseInput))) { logger(ERROR, BRIGHT_RED) << "coinbase transaction in the block has the wrong type"; return false; } if (boost::get(b.baseTransaction.inputs[0]).blockIndex != height) { logger(INFO, BRIGHT_RED) << "The miner transaction in block has invalid height: " << boost::get(b.baseTransaction.inputs[0]).blockIndex << ", expected: " << height; return false; } if (!(b.baseTransaction.unlockTime == height + m_currency.minedMoneyUnlockWindow())) { logger(ERROR, BRIGHT_RED) << "coinbase transaction transaction have wrong unlock time=" << b.baseTransaction.unlockTime << ", expected " << height + m_currency.minedMoneyUnlockWindow(); return false; } if (!check_outs_overflow(b.baseTransaction)) { logger(INFO, BRIGHT_RED) << "miner transaction have money overflow in block " << get_block_hash(b); return false; } return true; } bool Blockchain::validate_miner_transaction(const Block& b, uint32_t height, size_t cumulativeBlockSize, uint64_t alreadyGeneratedCoins, uint64_t fee, uint64_t& reward, int64_t& emissionChange) { uint64_t minerReward = 0; for (auto& o : b.baseTransaction.outputs) { minerReward += o.amount; } std::vector lastBlocksSizes; get_last_n_blocks_sizes(lastBlocksSizes, m_currency.rewardBlocksWindow()); size_t blocksSizeMedian = Common::medianValue(lastBlocksSizes); if (!m_currency.getBlockReward(blocksSizeMedian, cumulativeBlockSize, alreadyGeneratedCoins, fee, reward, emissionChange)) { logger(INFO, BRIGHT_WHITE) << "block size " << cumulativeBlockSize << " is bigger than allowed for this blockchain"; return false; } if (minerReward > reward) { logger(ERROR, BRIGHT_RED) << "Coinbase transaction spend too much money: " << m_currency.formatAmount(minerReward) << ", block reward is " << m_currency.formatAmount(reward); return false; } else if (minerReward < reward) { logger(ERROR, BRIGHT_RED) << "Coinbase transaction doesn't use full amount of block reward: spent " << m_currency.formatAmount(minerReward) << ", block reward is " << m_currency.formatAmount(reward); return false; } return true; } bool Blockchain::getBackwardBlocksSize(size_t from_height, std::vector& sz, size_t count) { std::lock_guard lk(m_blockchain_lock); if (!(from_height < m_blocks.size())) { logger(ERROR, BRIGHT_RED) << "Internal error: get_backward_blocks_sizes called with from_height=" << from_height << ", blockchain height = " << m_blocks.size(); return false; } size_t start_offset = (from_height + 1) - std::min((from_height + 1), count); for (size_t i = start_offset; i != from_height + 1; i++) { sz.push_back(m_blocks[i].block_cumulative_size); } return true; } bool Blockchain::get_last_n_blocks_sizes(std::vector& sz, size_t count) { std::lock_guard lk(m_blockchain_lock); if (!m_blocks.size()) { return true; } return getBackwardBlocksSize(m_blocks.size() - 1, sz, count); } uint64_t Blockchain::getCurrentCumulativeBlocksizeLimit() { return m_current_block_cumul_sz_limit; } bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector& timestamps) { if (timestamps.size() >= m_currency.timestampCheckWindow()) return true; std::lock_guard lk(m_blockchain_lock); size_t need_elements = m_currency.timestampCheckWindow() - timestamps.size(); if (!(start_top_height < m_blocks.size())) { logger(ERROR, BRIGHT_RED) << "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_blocks.size(); return false; } size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0; do { timestamps.push_back(m_blocks[start_top_height].bl.timestamp); if (start_top_height == 0) break; --start_top_height; } while (start_top_height != stop_offset); return true; } bool Blockchain::handle_alternative_block(const Block& b, const Crypto::Hash& id, block_verification_context& bvc, bool sendNewAlternativeBlockMessage) { std::lock_guard lk(m_blockchain_lock); auto block_height = get_block_height(b); if (block_height == 0) { logger(ERROR, BRIGHT_RED) << "Block with id: " << Common::podToHex(id) << " (as alternative) have wrong miner transaction"; bvc.m_verifivation_failed = true; return false; } if (!m_checkpoints.is_alternative_block_allowed(getCurrentBlockchainHeight(), block_height)) { logger(TRACE) << "Block with id: " << id << std::endl << " can't be accepted for alternative chain, block height: " << block_height << std::endl << " blockchain height: " << getCurrentBlockchainHeight(); bvc.m_verifivation_failed = true; return false; } size_t cumulativeSize; if (!getBlockCumulativeSize(b, cumulativeSize)) { logger(TRACE) << "Block with id: " << id << " has at least one unknown transaction. Cumulative size is calculated imprecisely"; } if (!checkCumulativeBlockSize(id, cumulativeSize, block_height)) { bvc.m_verifivation_failed = true; return false; } //block is not related with head of main chain //first of all - look in alternative chains container uint32_t mainPrevHeight = 0; const bool mainPrev = m_blockIndex.getBlockHeight(b.previousBlockHash, mainPrevHeight); const auto it_prev = m_alternative_chains.find(b.previousBlockHash); if (it_prev != m_alternative_chains.end() || mainPrev) { //we have new block in alternative chain //build alternative subchain, front -> mainchain, back -> alternative head blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() std::list alt_chain; std::vector timestamps; while (alt_it != m_alternative_chains.end()) { alt_chain.push_front(alt_it); timestamps.push_back(alt_it->second.bl.timestamp); alt_it = m_alternative_chains.find(alt_it->second.bl.previousBlockHash); } if (alt_chain.size()) { //make sure that it has right connection to main chain if (!(m_blocks.size() > alt_chain.front()->second.height)) { logger(ERROR, BRIGHT_RED) << "main blockchain wrong height"; return false; } Crypto::Hash h = NULL_HASH; get_block_hash(m_blocks[alt_chain.front()->second.height - 1].bl, h); if (!(h == alt_chain.front()->second.bl.previousBlockHash)) { logger(ERROR, BRIGHT_RED) << "alternative chain have wrong connection to main chain"; return false; } complete_timestamps_vector(alt_chain.front()->second.height - 1, timestamps); } else { if (!(mainPrev)) { logger(ERROR, BRIGHT_RED) << "internal error: broken imperative condition it_main_prev != m_blocks_index.end()"; return false; } complete_timestamps_vector(mainPrevHeight, timestamps); } //check timestamp correct if (!check_block_timestamp(timestamps, b)) { logger(INFO, BRIGHT_RED) << "Block with id: " << id << ENDL << " for alternative chain, have invalid timestamp: " << b.timestamp; //add_block_as_invalid(b, id);//do not add blocks to invalid storage before proof of work check was passed bvc.m_verifivation_failed = true; return false; } BlockEntry bei = boost::value_initialized(); bei.bl = b; bei.height = static_cast(alt_chain.size() ? it_prev->second.height + 1 : mainPrevHeight + 1); bool is_a_checkpoint; if (!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) { logger(ERROR, BRIGHT_RED) << "CHECKPOINT VALIDATION FAILED"; bvc.m_verifivation_failed = true; return false; } // Always check PoW for alternative blocks m_is_in_checkpoint_zone = false; difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); if (!(current_diff)) { logger(ERROR, BRIGHT_RED) << "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"; return false; } Crypto::Hash proof_of_work = NULL_HASH; if (!m_currency.checkProofOfWork(m_cn_context, bei.bl, current_diff, proof_of_work)) { logger(INFO, BRIGHT_RED) << "Block with id: " << id << ENDL << " for alternative chain, have not enough proof of work: " << proof_of_work << ENDL << " expected difficulty: " << current_diff; bvc.m_verifivation_failed = true; return false; } if (!prevalidate_miner_transaction(b, bei.height)) { logger(INFO, BRIGHT_RED) << "Block with id: " << Common::podToHex(id) << " (as alternative) have wrong miner transaction."; bvc.m_verifivation_failed = true; return false; } bei.cumulative_difficulty = alt_chain.size() ? it_prev->second.cumulative_difficulty : m_blocks[mainPrevHeight].cumulative_difficulty; bei.cumulative_difficulty += current_diff; #ifdef _DEBUG auto i_dres = m_alternative_chains.find(id); if (!(i_dres == m_alternative_chains.end())) { logger(ERROR, BRIGHT_RED) << "insertion of new alternative block returned as it already exist"; return false; } #endif auto i_res = m_alternative_chains.insert(blocks_ext_by_hash::value_type(id, bei)); if (!(i_res.second)) { logger(ERROR, BRIGHT_RED) << "insertion of new alternative block returned as it already exist"; return false; } m_orthanBlocksIndex.add(bei.bl); alt_chain.push_back(i_res.first); if (is_a_checkpoint) { //do reorganize! logger(INFO, BRIGHT_GREEN) << "###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_blocks.size() - 1 << ", checkpoint is found in alternative chain on height " << bei.height; bool r = switch_to_alternative_blockchain(alt_chain, true); if (r) { bvc.m_added_to_main_chain = true; bvc.m_switched_to_alt_chain = true; } else { bvc.m_verifivation_failed = true; } return r; } else if (m_blocks.back().cumulative_difficulty < bei.cumulative_difficulty) //check if difficulty bigger then in main chain { //do reorganize! logger(INFO, BRIGHT_GREEN) << "###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_blocks.size() - 1 << " with cum_difficulty " << m_blocks.back().cumulative_difficulty << ENDL << " alternative blockchain size: " << alt_chain.size() << " with cum_difficulty " << bei.cumulative_difficulty; bool r = switch_to_alternative_blockchain(alt_chain, false); if (r) { bvc.m_added_to_main_chain = true; bvc.m_switched_to_alt_chain = true; } else { bvc.m_verifivation_failed = true; } return r; } else { logger(INFO, BRIGHT_BLUE) << "----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << bei.height << ENDL << "id:\t" << id << ENDL << "PoW:\t" << proof_of_work << ENDL << "difficulty:\t" << current_diff; if (sendNewAlternativeBlockMessage) { sendMessage(BlockchainMessage(NewAlternativeBlockMessage(id))); } return true; } } else { //block orphaned bvc.m_marked_as_orphaned = true; logger(INFO, BRIGHT_RED) << "Block recognized as orphaned and rejected, id = " << id; } return true; } bool Blockchain::getBlocks(uint32_t start_offset, uint32_t count, std::list& blocks, std::list& txs) { std::lock_guard lk(m_blockchain_lock); if (start_offset >= m_blocks.size()) return false; for (size_t i = start_offset; i < start_offset + count && i < m_blocks.size(); i++) { blocks.push_back(m_blocks[i].bl); std::list missed_ids; getTransactions(m_blocks[i].bl.transactionHashes, txs, missed_ids); if (!(!missed_ids.size())) { logger(ERROR, BRIGHT_RED) << "have missed transactions in own block in main blockchain"; return false; } } return true; } bool Blockchain::getBlocks(uint32_t start_offset, uint32_t count, std::list& blocks) { std::lock_guard lk(m_blockchain_lock); if (start_offset >= m_blocks.size()) { return false; } for (uint32_t i = start_offset; i < start_offset + count && i < m_blocks.size(); i++) { blocks.push_back(m_blocks[i].bl); } return true; } bool Blockchain::handleGetObjects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp) { //Deprecated. Should be removed with CryptoNoteProtocolHandler. std::lock_guard lk(m_blockchain_lock); rsp.current_blockchain_height = getCurrentBlockchainHeight(); std::list blocks; getBlocks(arg.blocks, blocks, rsp.missed_ids); for (const auto& bl : blocks) { std::list missed_tx_id; std::list txs; getTransactions(bl.transactionHashes, txs, rsp.missed_ids); if (!(!missed_tx_id.size())) { logger(ERROR, BRIGHT_RED) << "Internal error: have missed missed_tx_id.size()=" << missed_tx_id.size() << ENDL << "for block id = " << get_block_hash(bl); return false; } //WTF??? rsp.blocks.push_back(block_complete_entry()); block_complete_entry& e = rsp.blocks.back(); //pack block e.block = asString(toBinaryArray(bl)); //pack transactions for (Transaction& tx : txs) { e.txs.push_back(asString(toBinaryArray(tx))); } } //get another transactions, if need std::list txs; getTransactions(arg.txs, txs, rsp.missed_ids); //pack aside transactions for (const auto& tx : txs) { rsp.txs.push_back(asString(toBinaryArray(tx))); } return true; } bool Blockchain::getAlternativeBlocks(std::list& blocks) { std::lock_guard lk(m_blockchain_lock); for (auto& alt_bl : m_alternative_chains) { blocks.push_back(alt_bl.second.bl); } return true; } uint32_t Blockchain::getAlternativeBlocksCount() { std::lock_guard lk(m_blockchain_lock); return static_cast(m_alternative_chains.size()); } bool Blockchain::add_out_to_get_random_outs(std::vector>& amount_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i) { std::lock_guard lk(m_blockchain_lock); const Transaction& tx = transactionByIndex(amount_outs[i].first).tx; if (!(tx.outputs.size() > amount_outs[i].second)) { logger(ERROR, BRIGHT_RED) << "internal error: in global outs index, transaction out index=" << amount_outs[i].second << " more than transaction outputs = " << tx.outputs.size() << ", for tx id = " << getObjectHash(tx); return false; } if (!(tx.outputs[amount_outs[i].second].target.type() == typeid(KeyOutput))) { logger(ERROR, BRIGHT_RED) << "unknown tx out type"; return false; } //check if transaction is unlocked if (!is_tx_spendtime_unlocked(tx.unlockTime)) return false; COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry()); oen.global_amount_index = static_cast(i); oen.out_key = boost::get(tx.outputs[amount_outs[i].second].target).key; return true; } size_t Blockchain::find_end_of_allowed_index(const std::vector>& amount_outs) { std::lock_guard lk(m_blockchain_lock); if (amount_outs.empty()) { return 0; } size_t i = amount_outs.size(); do { --i; if (amount_outs[i].first.block + m_currency.minedMoneyUnlockWindow() <= getCurrentBlockchainHeight()) { return i + 1; } } while (i != 0); return 0; } bool Blockchain::getRandomOutsByAmount(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) { std::lock_guard lk(m_blockchain_lock); for (uint64_t amount : req.amounts) { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount()); result_outs.amount = amount; auto it = m_outputs.find(amount); if (it == m_outputs.end()) { logger(ERROR, BRIGHT_RED) << "COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist"; continue;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist } std::vector>& amount_outs = it->second; //it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split //lets find upper bound of not fresh outs size_t up_index_limit = find_end_of_allowed_index(amount_outs); if (!(up_index_limit <= amount_outs.size())) { logger(ERROR, BRIGHT_RED) << "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << amount_outs.size(); return false; } if (up_index_limit > 0) { ShuffleGenerator> generator(up_index_limit); for (uint64_t j = 0; j < up_index_limit && result_outs.outs.size() < req.outs_count; ++j) { add_out_to_get_random_outs(amount_outs, result_outs, amount, generator()); } } } return true; } uint32_t Blockchain::findBlockchainSupplement(const std::vector& qblock_ids) { assert(!qblock_ids.empty()); assert(qblock_ids.back() == m_blockIndex.getBlockId(0)); std::lock_guard lk(m_blockchain_lock); uint32_t blockIndex; // assert above guarantees that method returns true m_blockIndex.findSupplement(qblock_ids, blockIndex); return blockIndex; } uint64_t Blockchain::blockDifficulty(size_t i) { std::lock_guard lk(m_blockchain_lock); if (!(i < m_blocks.size())) { logger(ERROR, BRIGHT_RED) << "wrong block index i = " << i << " at Blockchain::block_difficulty()"; return false; } if (i == 0) return m_blocks[i].cumulative_difficulty; return m_blocks[i].cumulative_difficulty - m_blocks[i - 1].cumulative_difficulty; } void Blockchain::print_blockchain(uint64_t start_index, uint64_t end_index) { std::stringstream ss; std::lock_guard lk(m_blockchain_lock); if (start_index >= m_blocks.size()) { logger(INFO, BRIGHT_WHITE) << "Wrong starter index set: " << start_index << ", expected max index " << m_blocks.size() - 1; return; } for (size_t i = start_index; i != m_blocks.size() && i != end_index; i++) { ss << "height " << i << ", timestamp " << m_blocks[i].bl.timestamp << ", cumul_dif " << m_blocks[i].cumulative_difficulty << ", cumul_size " << m_blocks[i].block_cumulative_size << "\nid\t\t" << get_block_hash(m_blocks[i].bl) << "\ndifficulty\t\t" << blockDifficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.transactionHashes.size() << ENDL; } logger(DEBUGGING) << "Current blockchain:" << ENDL << ss.str(); logger(INFO, BRIGHT_WHITE) << "Blockchain printed with log level 1"; } void Blockchain::print_blockchain_index() { std::stringstream ss; std::lock_guard lk(m_blockchain_lock); std::vector blockIds = m_blockIndex.getBlockIds(0, std::numeric_limits::max()); logger(INFO, BRIGHT_WHITE) << "Current blockchain index:"; size_t height = 0; for (auto i = blockIds.begin(); i != blockIds.end(); ++i, ++height) { logger(INFO, BRIGHT_WHITE) << "id\t\t" << *i << " height" << height; } } void Blockchain::print_blockchain_outs(const std::string& file) { std::stringstream ss; std::lock_guard lk(m_blockchain_lock); for (const outputs_container::value_type& v : m_outputs) { const std::vector>& vals = v.second; if (!vals.empty()) { ss << "amount: " << v.first << ENDL; for (size_t i = 0; i != vals.size(); i++) { ss << "\t" << getObjectHash(transactionByIndex(vals[i].first).tx) << ": " << vals[i].second << ENDL; } } } if (Common::saveStringToFile(file, ss.str())) { logger(INFO, BRIGHT_WHITE) << "Current outputs index writen to file: " << file; } else { logger(WARNING, BRIGHT_YELLOW) << "Failed to write current outputs index to file: " << file; } } std::vector Blockchain::findBlockchainSupplement(const std::vector& remoteBlockIds, size_t maxCount, uint32_t& totalBlockCount, uint32_t& startBlockIndex) { assert(!remoteBlockIds.empty()); assert(remoteBlockIds.back() == m_blockIndex.getBlockId(0)); std::lock_guard lk(m_blockchain_lock); totalBlockCount = getCurrentBlockchainHeight(); startBlockIndex = findBlockchainSupplement(remoteBlockIds); return m_blockIndex.getBlockIds(startBlockIndex, static_cast(maxCount)); } bool Blockchain::haveBlock(const Crypto::Hash& id) { std::lock_guard lk(m_blockchain_lock); if (m_blockIndex.hasBlock(id)) return true; if (m_alternative_chains.count(id)) return true; return false; } size_t Blockchain::getTotalTransactions() { std::lock_guard lk(m_blockchain_lock); return m_transactionMap.size(); } bool Blockchain::getTransactionOutputGlobalIndexes(const Crypto::Hash& tx_id, std::vector& indexs) { std::lock_guard lk(m_blockchain_lock); auto it = m_transactionMap.find(tx_id); if (it == m_transactionMap.end()) { logger(WARNING, YELLOW) << "warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id; return false; } const TransactionEntry& tx = transactionByIndex(it->second); if (!(tx.m_global_output_indexes.size())) { logger(ERROR, BRIGHT_RED) << "internal error: global indexes for transaction " << tx_id << " is empty"; return false; } indexs.resize(tx.m_global_output_indexes.size()); for (size_t i = 0; i < tx.m_global_output_indexes.size(); ++i) { indexs[i] = tx.m_global_output_indexes[i]; } return true; } bool Blockchain::get_out_by_msig_gindex(uint64_t amount, uint64_t gindex, MultisignatureOutput& out) { std::lock_guard lk(m_blockchain_lock); auto it = m_multisignatureOutputs.find(amount); if (it == m_multisignatureOutputs.end()) { return false; } if (it->second.size() <= gindex) { return false; } auto msigUsage = it->second[gindex]; auto& targetOut = transactionByIndex(msigUsage.transactionIndex).tx.outputs[msigUsage.outputIndex].target; if (targetOut.type() != typeid(MultisignatureOutput)) { return false; } out = boost::get(targetOut); return true; } bool Blockchain::checkTransactionInputs(const Transaction& tx, uint32_t& max_used_block_height, Crypto::Hash& max_used_block_id, BlockInfo* tail) { std::lock_guard lk(m_blockchain_lock); if (tail) tail->id = getTailId(tail->height); bool res = checkTransactionInputs(tx, &max_used_block_height); if (!res) return false; if (!(max_used_block_height < m_blocks.size())) { logger(ERROR, BRIGHT_RED) << "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_blocks.size(); return false; } get_block_hash(m_blocks[max_used_block_height].bl, max_used_block_id); return true; } bool Blockchain::haveTransactionKeyImagesAsSpent(const Transaction &tx) { for (const auto& in : tx.inputs) { if (in.type() == typeid(KeyInput)) { if (have_tx_keyimg_as_spent(boost::get(in).keyImage)) { return true; } } } return false; } bool Blockchain::checkTransactionInputs(const Transaction& tx, uint32_t* pmax_used_block_height) { Crypto::Hash tx_prefix_hash = getObjectHash(*static_cast(&tx)); return checkTransactionInputs(tx, tx_prefix_hash, pmax_used_block_height); } bool Blockchain::checkTransactionInputs(const Transaction& tx, const Crypto::Hash& tx_prefix_hash, uint32_t* pmax_used_block_height) { size_t inputIndex = 0; if (pmax_used_block_height) { *pmax_used_block_height = 0; } Crypto::Hash transactionHash = getObjectHash(tx); for (const auto& txin : tx.inputs) { assert(inputIndex < tx.signatures.size()); if (txin.type() == typeid(KeyInput)) { const KeyInput& in_to_key = boost::get(txin); if (!(!in_to_key.outputIndexes.empty())) { logger(ERROR, BRIGHT_RED) << "empty in_to_key.outputIndexes in transaction with id " << getObjectHash(tx); return false; } if (have_tx_keyimg_as_spent(in_to_key.keyImage)) { logger(DEBUGGING) << "Key image already spent in blockchain: " << Common::podToHex(in_to_key.keyImage); return false; } if (!check_tx_input(in_to_key, tx_prefix_hash, tx.signatures[inputIndex], pmax_used_block_height)) { logger(INFO, BRIGHT_WHITE) << "Failed to check ring signature for tx " << transactionHash; return false; } ++inputIndex; } else if (txin.type() == typeid(MultisignatureInput)) { if (!validateInput(::boost::get(txin), transactionHash, tx_prefix_hash, tx.signatures[inputIndex])) { return false; } ++inputIndex; } else { logger(INFO, BRIGHT_WHITE) << "Transaction << " << transactionHash << " contains input of unsupported type."; return false; } } return true; } bool Blockchain::is_tx_spendtime_unlocked(uint64_t unlock_time) { if (unlock_time < m_currency.maxBlockHeight()) { //interpret as block index if (getCurrentBlockchainHeight() - 1 + m_currency.lockedTxAllowedDeltaBlocks() >= unlock_time) return true; else return false; } else { //interpret as time uint64_t current_time = static_cast(time(NULL)); if (current_time + m_currency.lockedTxAllowedDeltaSeconds() >= unlock_time) return true; else return false; } return false; } bool Blockchain::check_tx_input(const KeyInput& txin, const Crypto::Hash& tx_prefix_hash, const std::vector& sig, uint32_t* pmax_related_block_height) { std::lock_guard lk(m_blockchain_lock); struct outputs_visitor { std::vector& m_results_collector; Blockchain& m_bch; LoggerRef logger; outputs_visitor(std::vector& results_collector, Blockchain& bch, ILogger& logger) :m_results_collector(results_collector), m_bch(bch), logger(logger, "outputs_visitor") { } bool handle_output(const Transaction& tx, const TransactionOutput& out, size_t transactionOutputIndex) { //check tx unlock time if (!m_bch.is_tx_spendtime_unlocked(tx.unlockTime)) { logger(INFO, BRIGHT_WHITE) << "One of outputs for one of inputs have wrong tx.unlockTime = " << tx.unlockTime; return false; } if (out.target.type() != typeid(KeyOutput)) { logger(INFO, BRIGHT_WHITE) << "Output have wrong type id, which=" << out.target.which(); return false; } m_results_collector.push_back(&boost::get(out.target).key); return true; } }; //check ring signature std::vector output_keys; outputs_visitor vi(output_keys, *this, logger.getLogger()); if (!scanOutputKeysForIndexes(txin, vi, pmax_related_block_height)) { logger(INFO, BRIGHT_WHITE) << "Failed to get output keys for tx with amount = " << m_currency.formatAmount(txin.amount) << " and count indexes " << txin.outputIndexes.size(); return false; } if (txin.outputIndexes.size() != output_keys.size()) { logger(INFO, BRIGHT_WHITE) << "Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.outputIndexes.size() << " returned wrong keys count " << output_keys.size(); return false; } if (!(sig.size() == output_keys.size())) { logger(ERROR, BRIGHT_RED) << "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << output_keys.size(); return false; } if (m_is_in_checkpoint_zone) { return true; } return Crypto::check_ring_signature(tx_prefix_hash, txin.keyImage, output_keys, sig.data()); } uint64_t Blockchain::get_adjusted_time() { //TODO: add collecting median time return time(NULL); } bool Blockchain::check_block_timestamp_main(const Block& b) { if (b.timestamp > get_adjusted_time() + m_currency.blockFutureTimeLimit()) { logger(INFO, BRIGHT_WHITE) << "Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 2 hours"; return false; } std::vector timestamps; size_t offset = m_blocks.size() <= m_currency.timestampCheckWindow() ? 0 : m_blocks.size() - m_currency.timestampCheckWindow(); for (; offset != m_blocks.size(); ++offset) { timestamps.push_back(m_blocks[offset].bl.timestamp); } return check_block_timestamp(std::move(timestamps), b); } bool Blockchain::check_block_timestamp(std::vector timestamps, const Block& b) { if (timestamps.size() < m_currency.timestampCheckWindow()) { return true; } uint64_t median_ts = Common::medianValue(timestamps); if (b.timestamp < median_ts) { logger(INFO, BRIGHT_WHITE) << "Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << m_currency.timestampCheckWindow() << " blocks, " << median_ts; return false; } return true; } bool Blockchain::checkCumulativeBlockSize(const Crypto::Hash& blockId, size_t cumulativeBlockSize, uint64_t height) { size_t maxBlockCumulativeSize = m_currency.maxBlockCumulativeSize(height); if (cumulativeBlockSize > maxBlockCumulativeSize) { logger(INFO, BRIGHT_WHITE) << "Block " << blockId << " is too big: " << cumulativeBlockSize << " bytes, " << "exptected no more than " << maxBlockCumulativeSize << " bytes"; return false; } return true; } // Returns true, if cumulativeSize is calculated precisely, else returns false. bool Blockchain::getBlockCumulativeSize(const Block& block, size_t& cumulativeSize) { std::vector blockTxs; std::vector missedTxs; getTransactions(block.transactionHashes, blockTxs, missedTxs, true); cumulativeSize = getObjectBinarySize(block.baseTransaction); for (const Transaction& tx : blockTxs) { cumulativeSize += getObjectBinarySize(tx); } return missedTxs.empty(); } // Precondition: m_blockchain_lock is locked. bool Blockchain::update_next_comulative_size_limit() { std::vector sz; get_last_n_blocks_sizes(sz, m_currency.rewardBlocksWindow()); uint64_t median = Common::medianValue(sz); if (median <= m_currency.blockGrantedFullRewardZone()) { median = m_currency.blockGrantedFullRewardZone(); } m_current_block_cumul_sz_limit = median * 2; return true; } bool Blockchain::addNewBlock(const Block& bl_, block_verification_context& bvc) { //copy block here to let modify block.target Block bl = bl_; Crypto::Hash id; if (!get_block_hash(bl, id)) { logger(ERROR, BRIGHT_RED) << "Failed to get block hash, possible block has invalid format"; bvc.m_verifivation_failed = true; return false; } bool add_result; { //to avoid deadlock lets lock tx_pool for whole add/reorganize process std::lock_guard poolLock(m_tx_pool); std::lock_guard bcLock(m_blockchain_lock); if (haveBlock(id)) { logger(TRACE) << "block with id = " << id << " already exists"; bvc.m_already_exists = true; return false; } //check that block refers to chain tail if (!(bl.previousBlockHash == getTailId())) { //chain switching or wrong block bvc.m_added_to_main_chain = false; add_result = handle_alternative_block(bl, id, bvc); } else { add_result = pushBlock(bl, bvc); if (add_result) { sendMessage(BlockchainMessage(NewBlockMessage(id))); } } } if (add_result && bvc.m_added_to_main_chain) { m_observerManager.notify(&IBlockchainStorageObserver::blockchainUpdated); } return add_result; } const Blockchain::TransactionEntry& Blockchain::transactionByIndex(TransactionIndex index) { return m_blocks[index.block].transactions[index.transaction]; } bool Blockchain::pushBlock(const Block& blockData, block_verification_context& bvc) { std::vector transactions; if (!loadTransactions(blockData, transactions)) { bvc.m_verifivation_failed = true; return false; } if (!pushBlock(blockData, transactions, bvc)) { saveTransactions(transactions); return false; } return true; } bool Blockchain::pushBlock(const Block& blockData, const std::vector& transactions, block_verification_context& bvc) { std::lock_guard lk(m_blockchain_lock); auto blockProcessingStart = std::chrono::steady_clock::now(); Crypto::Hash blockHash = get_block_hash(blockData); if (m_blockIndex.hasBlock(blockHash)) { logger(ERROR, BRIGHT_RED) << "Block " << blockHash << " already exists in blockchain."; bvc.m_verifivation_failed = true; return false; } if (blockData.previousBlockHash != getTailId()) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << " has wrong previousBlockHash: " << blockData.previousBlockHash << ", expected: " << getTailId(); bvc.m_verifivation_failed = true; return false; } if (!check_block_timestamp_main(blockData)) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << " has invalid timestamp: " << blockData.timestamp; bvc.m_verifivation_failed = true; return false; } auto targetTimeStart = std::chrono::steady_clock::now(); difficulty_type currentDifficulty = getDifficultyForNextBlock(); auto target_calculating_time = std::chrono::duration_cast(std::chrono::steady_clock::now() - targetTimeStart).count(); if (!(currentDifficulty)) { logger(ERROR, BRIGHT_RED) << "!!!!!!!!! difficulty overhead !!!!!!!!!"; return false; } auto longhashTimeStart = std::chrono::steady_clock::now(); Crypto::Hash proof_of_work = NULL_HASH; if (m_checkpoints.is_in_checkpoint_zone(getCurrentBlockchainHeight())) { if (!m_checkpoints.check_block(getCurrentBlockchainHeight(), blockHash)) { logger(ERROR, BRIGHT_RED) << "CHECKPOINT VALIDATION FAILED"; bvc.m_verifivation_failed = true; return false; } } else { if (!m_currency.checkProofOfWork(m_cn_context, blockData, currentDifficulty, proof_of_work)) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << ", has too weak proof of work: " << proof_of_work << ", expected difficulty: " << currentDifficulty; bvc.m_verifivation_failed = true; return false; } } auto longhash_calculating_time = std::chrono::duration_cast(std::chrono::steady_clock::now() - longhashTimeStart).count(); if (!prevalidate_miner_transaction(blockData, static_cast(m_blocks.size()))) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << " failed to pass prevalidation"; bvc.m_verifivation_failed = true; return false; } Crypto::Hash minerTransactionHash = getObjectHash(blockData.baseTransaction); BlockEntry block; block.bl = blockData; block.transactions.resize(1); block.transactions[0].tx = blockData.baseTransaction; TransactionIndex transactionIndex = { static_cast(m_blocks.size()), static_cast(0) }; pushTransaction(block, minerTransactionHash, transactionIndex); size_t coinbase_blob_size = getObjectBinarySize(blockData.baseTransaction); size_t cumulative_block_size = coinbase_blob_size; uint64_t fee_summary = 0; for (size_t i = 0; i < transactions.size(); ++i) { const Crypto::Hash& tx_id = blockData.transactionHashes[i]; block.transactions.resize(block.transactions.size() + 1); size_t blob_size = 0; uint64_t fee = 0; block.transactions.back().tx = transactions[i]; blob_size = toBinaryArray(block.transactions.back().tx).size(); fee = getInputAmount(block.transactions.back().tx) - getOutputAmount(block.transactions.back().tx); if (!checkTransactionInputs(block.transactions.back().tx)) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << " has at least one transaction with wrong inputs: " << tx_id; bvc.m_verifivation_failed = true; block.transactions.pop_back(); popTransactions(block, minerTransactionHash); return false; } ++transactionIndex.transaction; pushTransaction(block, tx_id, transactionIndex); cumulative_block_size += blob_size; fee_summary += fee; } if (!checkCumulativeBlockSize(blockHash, cumulative_block_size, m_blocks.size())) { bvc.m_verifivation_failed = true; return false; } int64_t emissionChange = 0; uint64_t reward = 0; uint64_t already_generated_coins = m_blocks.empty() ? 0 : m_blocks.back().already_generated_coins; if (!validate_miner_transaction(blockData, static_cast(m_blocks.size()), cumulative_block_size, already_generated_coins, fee_summary, reward, emissionChange)) { logger(INFO, BRIGHT_WHITE) << "Block " << blockHash << " has invalid miner transaction"; bvc.m_verifivation_failed = true; popTransactions(block, minerTransactionHash); return false; } block.height = static_cast(m_blocks.size()); block.block_cumulative_size = cumulative_block_size; block.cumulative_difficulty = currentDifficulty; block.already_generated_coins = already_generated_coins + emissionChange; if (m_blocks.size() > 0) { block.cumulative_difficulty += m_blocks.back().cumulative_difficulty; } pushBlock(block); auto block_processing_time = std::chrono::duration_cast(std::chrono::steady_clock::now() - blockProcessingStart).count(); logger(DEBUGGING) << "+++++ BLOCK SUCCESSFULLY ADDED" << ENDL << "id:\t" << blockHash << ENDL << "PoW:\t" << proof_of_work << ENDL << "HEIGHT " << block.height << ", difficulty:\t" << currentDifficulty << ENDL << "block reward: " << m_currency.formatAmount(reward) << ", fee = " << m_currency.formatAmount(fee_summary) << ", coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms"; bvc.m_added_to_main_chain = true; update_next_comulative_size_limit(); return true; } bool Blockchain::pushBlock(BlockEntry& block) { Crypto::Hash blockHash = get_block_hash(block.bl); m_blocks.push_back(block); m_blockIndex.push(blockHash); m_timestampIndex.add(block.bl.timestamp, blockHash); m_generatedTransactionsIndex.add(block.bl); assert(m_blockIndex.size() == m_blocks.size()); return true; } void Blockchain::popBlock(const Crypto::Hash& blockHash) { if (m_blocks.empty()) { logger(ERROR, BRIGHT_RED) << "Attempt to pop block from empty blockchain."; return; } std::vector transactions(m_blocks.back().transactions.size() - 1); for (size_t i = 0; i < m_blocks.back().transactions.size() - 1; ++i) { transactions[i] = m_blocks.back().transactions[1 + i].tx; } saveTransactions(transactions); popTransactions(m_blocks.back(), getObjectHash(m_blocks.back().bl.baseTransaction)); m_timestampIndex.remove(m_blocks.back().bl.timestamp, blockHash); m_generatedTransactionsIndex.remove(m_blocks.back().bl); m_blocks.pop_back(); m_blockIndex.pop(); assert(m_blockIndex.size() == m_blocks.size()); } bool Blockchain::pushTransaction(BlockEntry& block, const Crypto::Hash& transactionHash, TransactionIndex transactionIndex) { auto result = m_transactionMap.insert(std::make_pair(transactionHash, transactionIndex)); if (!result.second) { logger(ERROR, BRIGHT_RED) << "Duplicate transaction was pushed to blockchain."; return false; } TransactionEntry& transaction = block.transactions[transactionIndex.transaction]; if (!checkMultisignatureInputsDiff(transaction.tx)) { logger(ERROR, BRIGHT_RED) << "Double spending transaction was pushed to blockchain."; m_transactionMap.erase(transactionHash); return false; } for (size_t i = 0; i < transaction.tx.inputs.size(); ++i) { if (transaction.tx.inputs[i].type() == typeid(KeyInput)) { auto result = m_spent_keys.insert(::boost::get(transaction.tx.inputs[i]).keyImage); if (!result.second) { logger(ERROR, BRIGHT_RED) << "Double spending transaction was pushed to blockchain."; for (size_t j = 0; j < i; ++j) { m_spent_keys.erase(::boost::get(transaction.tx.inputs[i - 1 - j]).keyImage); } m_transactionMap.erase(transactionHash); return false; } } } for (const auto& inv : transaction.tx.inputs) { if (inv.type() == typeid(MultisignatureInput)) { const MultisignatureInput& in = ::boost::get(inv); auto& amountOutputs = m_multisignatureOutputs[in.amount]; amountOutputs[in.outputIndex].isUsed = true; } } transaction.m_global_output_indexes.resize(transaction.tx.outputs.size()); for (uint16_t output = 0; output < transaction.tx.outputs.size(); ++output) { if (transaction.tx.outputs[output].target.type() == typeid(KeyOutput)) { auto& amountOutputs = m_outputs[transaction.tx.outputs[output].amount]; transaction.m_global_output_indexes[output] = static_cast(amountOutputs.size()); amountOutputs.push_back(std::make_pair<>(transactionIndex, output)); } else if (transaction.tx.outputs[output].target.type() == typeid(MultisignatureOutput)) { auto& amountOutputs = m_multisignatureOutputs[transaction.tx.outputs[output].amount]; transaction.m_global_output_indexes[output] = static_cast(amountOutputs.size()); MultisignatureOutputUsage outputUsage = { transactionIndex, output, false }; amountOutputs.push_back(outputUsage); } } m_paymentIdIndex.add(transaction.tx); return true; } void Blockchain::popTransaction(const Transaction& transaction, const Crypto::Hash& transactionHash) { TransactionIndex transactionIndex = m_transactionMap.at(transactionHash); for (size_t outputIndex = 0; outputIndex < transaction.outputs.size(); ++outputIndex) { const TransactionOutput& output = transaction.outputs[transaction.outputs.size() - 1 - outputIndex]; if (output.target.type() == typeid(KeyOutput)) { auto amountOutputs = m_outputs.find(output.amount); if (amountOutputs == m_outputs.end()) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - cannot find specific amount in outputs map."; continue; } if (amountOutputs->second.empty()) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - output array for specific amount is empty."; continue; } if (amountOutputs->second.back().first.block != transactionIndex.block || amountOutputs->second.back().first.transaction != transactionIndex.transaction) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - invalid transaction index."; continue; } if (amountOutputs->second.back().second != transaction.outputs.size() - 1 - outputIndex) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - invalid output index."; continue; } amountOutputs->second.pop_back(); if (amountOutputs->second.empty()) { m_outputs.erase(amountOutputs); } } else if (output.target.type() == typeid(MultisignatureOutput)) { auto amountOutputs = m_multisignatureOutputs.find(output.amount); if (amountOutputs == m_multisignatureOutputs.end()) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - cannot find specific amount in outputs map."; continue; } if (amountOutputs->second.empty()) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - output array for specific amount is empty."; continue; } if (amountOutputs->second.back().isUsed) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - attempting to remove used output."; continue; } if (amountOutputs->second.back().transactionIndex.block != transactionIndex.block || amountOutputs->second.back().transactionIndex.transaction != transactionIndex.transaction) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - invalid transaction index."; continue; } if (amountOutputs->second.back().outputIndex != transaction.outputs.size() - 1 - outputIndex) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - invalid output index."; continue; } amountOutputs->second.pop_back(); if (amountOutputs->second.empty()) { m_multisignatureOutputs.erase(amountOutputs); } } } for (auto& input : transaction.inputs) { if (input.type() == typeid(KeyInput)) { size_t count = m_spent_keys.erase(::boost::get(input).keyImage); if (count != 1) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - cannot find spent key."; } } else if (input.type() == typeid(MultisignatureInput)) { const MultisignatureInput& in = ::boost::get(input); auto& amountOutputs = m_multisignatureOutputs[in.amount]; if (!amountOutputs[in.outputIndex].isUsed) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - multisignature output not marked as used."; } amountOutputs[in.outputIndex].isUsed = false; } } m_paymentIdIndex.remove(transaction); size_t count = m_transactionMap.erase(transactionHash); if (count != 1) { logger(ERROR, BRIGHT_RED) << "Blockchain consistency broken - cannot find transaction by hash."; } } void Blockchain::popTransactions(const BlockEntry& block, const Crypto::Hash& minerTransactionHash) { for (size_t i = 0; i < block.transactions.size() - 1; ++i) { popTransaction(block.transactions[block.transactions.size() - 1 - i].tx, block.bl.transactionHashes[block.transactions.size() - 2 - i]); } popTransaction(block.bl.baseTransaction, minerTransactionHash); } bool Blockchain::validateInput(const MultisignatureInput& input, const Crypto::Hash& transactionHash, const Crypto::Hash& transactionPrefixHash, const std::vector& transactionSignatures) { assert(input.signatureCount == transactionSignatures.size()); MultisignatureOutputsContainer::const_iterator amountOutputs = m_multisignatureOutputs.find(input.amount); if (amountOutputs == m_multisignatureOutputs.end()) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains multisignature input with invalid amount."; return false; } if (input.outputIndex >= amountOutputs->second.size()) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains multisignature input with invalid outputIndex."; return false; } const MultisignatureOutputUsage& outputIndex = amountOutputs->second[input.outputIndex]; if (outputIndex.isUsed) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains double spending multisignature input."; return false; } const Transaction& outputTransaction = m_blocks[outputIndex.transactionIndex.block].transactions[outputIndex.transactionIndex.transaction].tx; if (!is_tx_spendtime_unlocked(outputTransaction.unlockTime)) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains multisignature input which points to a locked transaction."; return false; } assert(outputTransaction.outputs[outputIndex.outputIndex].amount == input.amount); assert(outputTransaction.outputs[outputIndex.outputIndex].target.type() == typeid(MultisignatureOutput)); const MultisignatureOutput& output = ::boost::get(outputTransaction.outputs[outputIndex.outputIndex].target); if (input.signatureCount != output.requiredSignatureCount) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains multisignature input with invalid signature count."; return false; } size_t inputSignatureIndex = 0; size_t outputKeyIndex = 0; while (inputSignatureIndex < input.signatureCount) { if (outputKeyIndex == output.keys.size()) { logger(DEBUGGING) << "Transaction << " << transactionHash << " contains multisignature input with invalid signatures."; return false; } if (Crypto::check_signature(transactionPrefixHash, output.keys[outputKeyIndex], transactionSignatures[inputSignatureIndex])) { ++inputSignatureIndex; } ++outputKeyIndex; } return true; } bool Blockchain::getLowerBound(uint64_t timestamp, uint64_t startOffset, uint32_t& height) { std::lock_guard lk(m_blockchain_lock); assert(startOffset < m_blocks.size()); auto bound = std::lower_bound(m_blocks.begin() + startOffset, m_blocks.end(), timestamp - m_currency.blockFutureTimeLimit(), [](const BlockEntry& b, uint64_t timestamp) { return b.bl.timestamp < timestamp; }); if (bound == m_blocks.end()) { return false; } height = static_cast(std::distance(m_blocks.begin(), bound)); return true; } std::vector Blockchain::getBlockIds(uint32_t startHeight, uint32_t maxCount) { std::lock_guard lk(m_blockchain_lock); return m_blockIndex.getBlockIds(startHeight, maxCount); } bool Blockchain::getBlockContainingTransaction(const Crypto::Hash& txId, Crypto::Hash& blockId, uint32_t& blockHeight) { std::lock_guard lk(m_blockchain_lock); auto it = m_transactionMap.find(txId); if (it == m_transactionMap.end()) { return false; } else { blockHeight = m_blocks[it->second.block].height; blockId = getBlockIdByHeight(blockHeight); return true; } } bool Blockchain::getAlreadyGeneratedCoins(const Crypto::Hash& hash, uint64_t& generatedCoins) { std::lock_guard lk(m_blockchain_lock); // try to find block in main chain uint32_t height = 0; if (m_blockIndex.getBlockHeight(hash, height)) { generatedCoins = m_blocks[height].already_generated_coins; return true; } // try to find block in alternative chain auto blockByHashIterator = m_alternative_chains.find(hash); if (blockByHashIterator != m_alternative_chains.end()) { generatedCoins = blockByHashIterator->second.already_generated_coins; return true; } logger(DEBUGGING) << "Can't find block with hash " << hash << " to get already generated coins."; return false; } bool Blockchain::getBlockSize(const Crypto::Hash& hash, size_t& size) { std::lock_guard lk(m_blockchain_lock); // try to find block in main chain uint32_t height = 0; if (m_blockIndex.getBlockHeight(hash, height)) { size = m_blocks[height].block_cumulative_size; return true; } // try to find block in alternative chain auto blockByHashIterator = m_alternative_chains.find(hash); if (blockByHashIterator != m_alternative_chains.end()) { size = blockByHashIterator->second.block_cumulative_size; return true; } logger(DEBUGGING) << "Can't find block with hash " << hash << " to get block size."; return false; } bool Blockchain::getMultisigOutputReference(const MultisignatureInput& txInMultisig, std::pair& outputReference) { std::lock_guard lk(m_blockchain_lock); MultisignatureOutputsContainer::const_iterator amountIter = m_multisignatureOutputs.find(txInMultisig.amount); if (amountIter == m_multisignatureOutputs.end()) { logger(DEBUGGING) << "Transaction contains multisignature input with invalid amount."; return false; } if (amountIter->second.size() <= txInMultisig.outputIndex) { logger(DEBUGGING) << "Transaction contains multisignature input with invalid outputIndex."; return false; } const MultisignatureOutputUsage& outputIndex = amountIter->second[txInMultisig.outputIndex]; const Transaction& outputTransaction = m_blocks[outputIndex.transactionIndex.block].transactions[outputIndex.transactionIndex.transaction].tx; outputReference.first = getObjectHash(outputTransaction); outputReference.second = outputIndex.outputIndex; return true; } bool Blockchain::storeBlockchainIndices() { std::lock_guard lk(m_blockchain_lock); logger(INFO, BRIGHT_WHITE) << "Saving blockchain indices..."; BlockchainIndicesSerializer ser(*this, getTailId(), logger.getLogger()); if (!storeToBinaryFile(ser, appendPath(m_config_folder, m_currency.blockchinIndicesFileName()))) { logger(ERROR, BRIGHT_RED) << "Failed to save blockchain indices"; return false; } return true; } bool Blockchain::loadBlockchainIndices() { std::lock_guard lk(m_blockchain_lock); logger(INFO, BRIGHT_WHITE) << "Loading blockchain indices for BlockchainExplorer..."; BlockchainIndicesSerializer loader(*this, get_block_hash(m_blocks.back().bl), logger.getLogger()); loadFromBinaryFile(loader, appendPath(m_config_folder, m_currency.blockchinIndicesFileName())); if (!loader.loaded()) { logger(WARNING, BRIGHT_YELLOW) << "No actual blockchain indices for BlockchainExplorer found, rebuilding..."; std::chrono::steady_clock::time_point timePoint = std::chrono::steady_clock::now(); m_paymentIdIndex.clear(); m_timestampIndex.clear(); m_generatedTransactionsIndex.clear(); for (uint32_t b = 0; b < m_blocks.size(); ++b) { if (b % 1000 == 0) { logger(INFO, BRIGHT_WHITE) << "Height " << b << " of " << m_blocks.size(); } const BlockEntry& block = m_blocks[b]; m_timestampIndex.add(block.bl.timestamp, get_block_hash(block.bl)); m_generatedTransactionsIndex.add(block.bl); for (uint16_t t = 0; t < block.transactions.size(); ++t) { const TransactionEntry& transaction = block.transactions[t]; m_paymentIdIndex.add(transaction.tx); } } std::chrono::duration duration = std::chrono::steady_clock::now() - timePoint; logger(INFO, BRIGHT_WHITE) << "Rebuilding blockchain indices took: " << duration.count(); } return true; } bool Blockchain::getGeneratedTransactionsNumber(uint32_t height, uint64_t& generatedTransactions) { std::lock_guard lk(m_blockchain_lock); return m_generatedTransactionsIndex.find(height, generatedTransactions); } bool Blockchain::getOrphanBlockIdsByHeight(uint32_t height, std::vector& blockHashes) { std::lock_guard lk(m_blockchain_lock); return m_orthanBlocksIndex.find(height, blockHashes); } bool Blockchain::getBlockIdsByTimestamp(uint64_t timestampBegin, uint64_t timestampEnd, uint32_t blocksNumberLimit, std::vector& hashes, uint32_t& blocksNumberWithinTimestamps) { std::lock_guard lk(m_blockchain_lock); return m_timestampIndex.find(timestampBegin, timestampEnd, blocksNumberLimit, hashes, blocksNumberWithinTimestamps); } bool Blockchain::getTransactionIdsByPaymentId(const Crypto::Hash& paymentId, std::vector& transactionHashes) { std::lock_guard lk(m_blockchain_lock); return m_paymentIdIndex.find(paymentId, transactionHashes); } bool Blockchain::loadTransactions(const Block& block, std::vector& transactions) { transactions.resize(block.transactionHashes.size()); size_t transactionSize; uint64_t fee; for (size_t i = 0; i < block.transactionHashes.size(); ++i) { if (!m_tx_pool.take_tx(block.transactionHashes[i], transactions[i], transactionSize, fee)) { tx_verification_context context; for (size_t j = 0; j < i; ++j) { if (!m_tx_pool.add_tx(transactions[i - 1 - j], context, true)) { throw std::runtime_error("Blockchain::loadTransactions, failed to add transaction to pool"); } } return false; } } return true; } void Blockchain::saveTransactions(const std::vector& transactions) { tx_verification_context context; for (size_t i = 0; i < transactions.size(); ++i) { if (!m_tx_pool.add_tx(transactions[transactions.size() - 1 - i], context, true)) { throw std::runtime_error("Blockchain::saveTransactions, failed to add transaction to pool"); } } } bool Blockchain::addMessageQueue(MessageQueue& messageQueue) { return m_messageQueueList.insert(messageQueue); } bool Blockchain::removeMessageQueue(MessageQueue& messageQueue) { return m_messageQueueList.remove(messageQueue); } void Blockchain::sendMessage(const BlockchainMessage& message) { for (IntrusiveLinkedList>::iterator iter = m_messageQueueList.begin(); iter != m_messageQueueList.end(); ++iter) { iter->push(message); } } bool Blockchain::isBlockInMainChain(const Crypto::Hash& blockId) { return m_blockIndex.hasBlock(blockId); } }