bool core::check_tx_semantic(const Transaction& tx, bool keeped_by_block) { if (!tx.vin.size()) { logger(ERROR) << "tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx); return false; } if (!check_inputs_types_supported(tx)) { logger(ERROR) << "unsupported input types for tx id= " << get_transaction_hash(tx); return false; } std::string errmsg; if (!check_outs_valid(tx, &errmsg)) { logger(ERROR) << "tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx) << ": " << errmsg; return false; } if (!check_money_overflow(tx)) { logger(ERROR) << "tx have money overflow, rejected for tx id= " << get_transaction_hash(tx); return false; } uint64_t amount_in = 0; get_inputs_money_amount(tx, amount_in); uint64_t amount_out = get_outs_money_amount(tx); if (amount_in < amount_out) { logger(ERROR) << "tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx); return false; } if (!keeped_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - m_currency.minerTxBlobReservedSize()) { logger(ERROR) << "transaction is too big " << get_object_blobsize(tx) << ", maximum allowed size is " << (m_blockchain_storage.get_current_comulative_blocksize_limit() - m_currency.minerTxBlobReservedSize()); return false; } //check if tx use different key images if (!check_tx_inputs_keyimages_diff(tx)) { logger(ERROR) << "tx has a few inputs with identical keyimages"; return false; } if (!checkMultisignatureInputsDiff(tx)) { logger(ERROR) << "tx has a few multisignature inputs with identical output indexes"; return false; } return true; }
bool BlockchainExplorerDataBuilder::fillTransactionDetails(const Transaction& transaction, TransactionDetails& transactionDetails, uint64_t timestamp) { Crypto::Hash hash = getObjectHash(transaction); transactionDetails.hash = hash; transactionDetails.timestamp = timestamp; Crypto::Hash blockHash; uint32_t blockHeight; if (!core.getBlockContainingTx(hash, blockHash, blockHeight)) { transactionDetails.inBlockchain = false; transactionDetails.blockHeight = boost::value_initialized<uint32_t>(); transactionDetails.blockHash = boost::value_initialized<Crypto::Hash>(); } else { transactionDetails.inBlockchain = true; transactionDetails.blockHeight = blockHeight; transactionDetails.blockHash = blockHash; if (timestamp == 0) { Block block; if (!core.getBlockByHash(blockHash, block)) { return false; } transactionDetails.timestamp = block.timestamp; } } transactionDetails.size = getObjectBinarySize(transaction); transactionDetails.unlockTime = transaction.unlockTime; transactionDetails.totalOutputsAmount = get_outs_money_amount(transaction); uint64_t inputsAmount; if (!get_inputs_money_amount(transaction, inputsAmount)) { return false; } transactionDetails.totalInputsAmount = inputsAmount; if (transaction.inputs.size() > 0 && transaction.inputs.front().type() == typeid(BaseInput)) { //It's gen transaction transactionDetails.fee = 0; transactionDetails.mixin = 0; } else { uint64_t fee; if (!get_tx_fee(transaction, fee)) { return false; } transactionDetails.fee = fee; uint64_t mixin; if (!getMixin(transaction, mixin)) { return false; } transactionDetails.mixin = mixin; } Crypto::Hash paymentId; if (getPaymentId(transaction, paymentId)) { transactionDetails.paymentId = paymentId; } else { transactionDetails.paymentId = boost::value_initialized<Crypto::Hash>(); } fillTxExtra(transaction.extra, transactionDetails.extra); transactionDetails.signatures.reserve(transaction.signatures.size()); for (const std::vector<Crypto::Signature>& signatures : transaction.signatures) { std::vector<Crypto::Signature> signaturesDetails; signaturesDetails.reserve(signatures.size()); for (const Crypto::Signature& signature : signatures) { signaturesDetails.push_back(std::move(signature)); } transactionDetails.signatures.push_back(std::move(signaturesDetails)); } transactionDetails.inputs.reserve(transaction.inputs.size()); for (const TransactionInput& txIn : transaction.inputs) { TransactionInputDetails txInDetails; if (txIn.type() == typeid(BaseInput)) { TransactionInputGenerateDetails txInGenDetails; txInGenDetails.height = boost::get<BaseInput>(txIn).blockIndex; txInDetails.amount = 0; for (const TransactionOutput& out : transaction.outputs) { txInDetails.amount += out.amount; } txInDetails.input = txInGenDetails; } else if (txIn.type() == typeid(KeyInput)) { TransactionInputToKeyDetails txInToKeyDetails; const KeyInput& txInToKey = boost::get<KeyInput>(txIn); std::list<std::pair<Crypto::Hash, size_t>> outputReferences; if (!core.scanOutputkeysForIndices(txInToKey, outputReferences)) { return false; } txInDetails.amount = txInToKey.amount; txInToKeyDetails.outputIndexes = txInToKey.outputIndexes; txInToKeyDetails.keyImage = txInToKey.keyImage; txInToKeyDetails.mixin = txInToKey.outputIndexes.size(); txInToKeyDetails.output.number = outputReferences.back().second; txInToKeyDetails.output.transactionHash = outputReferences.back().first; txInDetails.input = txInToKeyDetails; } else if (txIn.type() == typeid(MultisignatureInput)) { TransactionInputMultisignatureDetails txInMultisigDetails; const MultisignatureInput& txInMultisig = boost::get<MultisignatureInput>(txIn); txInDetails.amount = txInMultisig.amount; txInMultisigDetails.signatures = txInMultisig.signatureCount; std::pair<Crypto::Hash, size_t> outputReference; if (!core.getMultisigOutputReference(txInMultisig, outputReference)) { return false; } txInMultisigDetails.output.number = outputReference.second; txInMultisigDetails.output.transactionHash = outputReference.first; txInDetails.input = txInMultisigDetails; } else { return false; } transactionDetails.inputs.push_back(std::move(txInDetails)); } transactionDetails.outputs.reserve(transaction.outputs.size()); std::vector<uint32_t> globalIndices; globalIndices.reserve(transaction.outputs.size()); if (!transactionDetails.inBlockchain || !core.get_tx_outputs_gindexs(hash, globalIndices)) { for (size_t i = 0; i < transaction.outputs.size(); ++i) { globalIndices.push_back(0); } } typedef boost::tuple<TransactionOutput, uint32_t> outputWithIndex; auto range = boost::combine(transaction.outputs, globalIndices); for (const outputWithIndex& txOutput : range) { TransactionOutputDetails txOutDetails; txOutDetails.amount = txOutput.get<0>().amount; txOutDetails.globalIndex = txOutput.get<1>(); if (txOutput.get<0>().target.type() == typeid(KeyOutput)) { TransactionOutputToKeyDetails txOutToKeyDetails; txOutToKeyDetails.txOutKey = boost::get<KeyOutput>(txOutput.get<0>().target).key; txOutDetails.output = txOutToKeyDetails; } else if (txOutput.get<0>().target.type() == typeid(MultisignatureOutput)) { TransactionOutputMultisignatureDetails txOutMultisigDetails; MultisignatureOutput txOutMultisig = boost::get<MultisignatureOutput>(txOutput.get<0>().target); txOutMultisigDetails.keys.reserve(txOutMultisig.keys.size()); for (const Crypto::PublicKey& key : txOutMultisig.keys) { txOutMultisigDetails.keys.push_back(std::move(key)); } txOutMultisigDetails.requiredSignatures = txOutMultisig.requiredSignatureCount; txOutDetails.output = txOutMultisigDetails; } else { return false; } transactionDetails.outputs.push_back(std::move(txOutDetails)); } return true; }
//--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, const crypto::hash &id, tx_verification_context& tvc, bool kept_by_block, std::string alias) { size_t blob_size = get_object_blobsize(tx); //#9Protection from big transaction flood if (!kept_by_block && blob_size > currency::get_max_transaction_blob_size(m_blockchain.get_current_blockchain_height())) { LOG_PRINT_L0("transaction is too big (" << blob_size << ")bytes for current transaction flow, tx_id: " << id); tvc.m_verifivation_failed = true; return false; } if (!check_inputs_types_supported(tx)) { tvc.m_verifivation_failed = true; return false; } uint64_t inputs_amount = 0; if (!get_inputs_money_amount(tx, inputs_amount)) { tvc.m_verifivation_failed = true; return false; } uint64_t outputs_amount = get_outs_money_amount(tx); if (outputs_amount >= inputs_amount) { LOG_PRINT_L0("transaction use more money then it has: use " << outputs_amount << ", have " << inputs_amount); tvc.m_verifivation_failed = true; return false; } //check key images for transaction if it is not kept by blockhave_tx_keyimges_as_spent if (!kept_by_block) { if (have_tx_keyimges_as_spent(tx)) { LOG_ERROR("Transaction with id= " << id << " used already spent key images"); tvc.m_verifivation_failed = true; return false; } //transaction spam protection, soft rule if (inputs_amount - outputs_amount < TX_POOL_MINIMUM_FEE) { LOG_ERROR("Transaction with id= " << id << " has too small fee: " << inputs_amount - outputs_amount << ", expected fee: " << DEFAULT_FEE); tvc.m_verifivation_failed = true; return false; } } crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); CRITICAL_REGION_LOCAL(m_transactions_lock); if (!ch_inp_res) { if (kept_by_block) { //if there is a same alias on the block, then delete the tx with the same alias in the pool crypto::hash hash; if (alias.size() && (hash = find_alias(alias)) != null_hash) { transaction tx = AUTO_VAL_INIT(tx); size_t size = 0; uint64_t fee = 0; take_tx(hash, tx, size, fee); LOG_PRINT_L2("Found alias " << alias << " in block, delete pool tx with the same alias: " << id); } //anyway add this transaction to pool, because it related to block auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); if (!txd_p.second) { return false; } //CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.receive_time = time(nullptr); tvc.m_verifivation_impossible = true; tvc.m_added_to_pool = true; } else { LOG_PRINT_L0("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; return false; } } else { //check alias repeat or not if (!add_alias_tx_pair(alias, id)) { tvc.m_verifivation_failed = true; tvc.m_added_to_pool = false; return false; } //update transactions container auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); if (!txd_p.second) { return false; } //CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = max_used_block_id; txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; txd_p.first->second.last_failed_id = null_hash; txd_p.first->second.receive_time = time(nullptr); tvc.m_added_to_pool = true; if (txd_p.first->second.fee > 0) tvc.m_should_be_relayed = true; } tvc.m_verifivation_failed = true; //update image_keys container, here should everything goes ok. BOOST_FOREACH(const auto& in, tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: keeped_by_block=" << kept_by_block << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL << "tx_id=" << id); auto ins_res = kei_image_set.insert(id); CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); } tvc.m_verifivation_failed = false; //succeed return true; }
//--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block) { //#9Protection from big transaction flood if(!kept_by_block && blob_size > m_blockchain.get_current_comulative_blocksize_limit() / 2) { LOG_PRINT_L0("transaction is too big for current transaction flow, tx_id: " << id); tvc.m_verifivation_failed = true; return false; } //TODO: add rule for relay, based on tx size/fee ratio if(!check_inputs_types_supported(tx)) { tvc.m_verifivation_failed = true; return false; } uint64_t inputs_amount = 0; if(!get_inputs_money_amount(tx, inputs_amount)) { tvc.m_verifivation_failed = true; return false; } uint64_t outputs_amount = get_outs_money_amount(tx); if(outputs_amount >= inputs_amount) { LOG_PRINT_L0("transaction use more money then it has: use " << outputs_amount << ", have " << inputs_amount); tvc.m_verifivation_failed = true; return false; } //check key images for transaction if it is not kept by block if(!kept_by_block) { if(have_tx_keyimges_as_spent(tx)) { LOG_ERROR("Transaction with id= "<< id << " used already spent key images"); tvc.m_verifivation_failed = true; return false; } } crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); CRITICAL_REGION_LOCAL(m_transactions_lock); if(!ch_inp_res) { if(kept_by_block) { //anyway add this transaction to pool, because it related to block auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.receive_time = time(nullptr); tvc.m_verifivation_impossible = true; tvc.m_added_to_pool = true; } else { LOG_PRINT_L0("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; return false; } } else { //update transactions container auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = max_used_block_id; txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; txd_p.first->second.last_failed_id = null_hash; txd_p.first->second.receive_time = time(nullptr); tvc.m_added_to_pool = true; if(txd_p.first->second.fee > 0) tvc.m_should_be_relayed = true; } tvc.m_verifivation_failed = true; //update image_keys container, here should everything goes ok. BOOST_FOREACH(const auto& in, tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: keeped_by_block=" << kept_by_block << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL << "tx_id=" << id ); auto ins_res = kei_image_set.insert(id); CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); } tvc.m_verifivation_failed = false; //succeed return true; }
//--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, size_t blob_size, tx_verification_context& tvc, bool kept_by_block) { if(!check_inputs_types_supported(tx)) { tvc.m_verifivation_failed = true; return false; } uint64_t inputs_amount = 0; if(!get_inputs_money_amount(tx, inputs_amount)) { tvc.m_verifivation_failed = true; return false; } uint64_t outputs_amount = get_outs_money_amount(tx); if(outputs_amount >= inputs_amount) { LOG_PRINT_L1("transaction use more money then it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount)); tvc.m_verifivation_failed = true; return false; } uint64_t fee = inputs_amount - outputs_amount; uint64_t needed_fee = blob_size / 1024; needed_fee += (blob_size % 1024) ? 1 : 0; needed_fee *= FEE_PER_KB; if (!kept_by_block && fee < needed_fee /*&& fee < MINING_ALLOWED_LEGACY_FEE*/) { LOG_PRINT_L1("transaction fee is not enough: " << print_money(fee) << ", minumim fee: " << print_money(needed_fee)); tvc.m_verifivation_failed = true; return false; } if (!kept_by_block && blob_size >= TRANSACTION_SIZE_LIMIT) { LOG_PRINT_L1("transaction is too big: " << blob_size << " bytes, maximum size: " << TRANSACTION_SIZE_LIMIT); tvc.m_verifivation_failed = true; return false; } //check key images for transaction if it is not kept by block if(!kept_by_block) { if(have_tx_keyimges_as_spent(tx)) { LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images"); tvc.m_verifivation_failed = true; return false; } } crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; bool ch_inp_res = m_blockchain.check_tx_inputs(tx, max_used_block_height, max_used_block_id); CRITICAL_REGION_LOCAL(m_transactions_lock); if(!ch_inp_res) { if(kept_by_block) { //anyway add this transaction to pool, because it related to block auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); CHECK_AND_ASSERT_MES(txd_p.second, false, "transaction already exists at inserting in memory pool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = null_hash; txd_p.first->second.max_used_block_height = 0; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.receive_time = time(nullptr); tvc.m_verifivation_impossible = true; tvc.m_added_to_pool = true; }else { LOG_PRINT_L1("tx used wrong inputs, rejected"); tvc.m_verifivation_failed = true; return false; } }else { //update transactions container auto txd_p = m_transactions.insert(transactions_container::value_type(id, tx_details())); CHECK_AND_ASSERT_MES(txd_p.second, false, "intrnal error: transaction already exists at inserting in memorypool"); txd_p.first->second.blob_size = blob_size; txd_p.first->second.tx = tx; txd_p.first->second.kept_by_block = kept_by_block; txd_p.first->second.fee = inputs_amount - outputs_amount; txd_p.first->second.max_used_block_id = max_used_block_id; txd_p.first->second.max_used_block_height = max_used_block_height; txd_p.first->second.last_failed_height = 0; txd_p.first->second.last_failed_id = null_hash; txd_p.first->second.receive_time = time(nullptr); tvc.m_added_to_pool = true; if(txd_p.first->second.fee > 0) tvc.m_should_be_relayed = true; } tvc.m_verifivation_failed = true; //update image_keys container, here should everything goes ok. BOOST_FOREACH(const auto& in, tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: keeped_by_block=" << kept_by_block << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL << "tx_id=" << id ); auto ins_res = kei_image_set.insert(id); CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); } tvc.m_verifivation_failed = false; m_txs_by_fee.emplace((double)blob_size / fee, id); //succeed return true; }