void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr, const crypto::hash* tx_prunable_hash_ptr) { bool miner_tx = false; crypto::hash tx_hash, tx_prunable_hash; if (!tx_hash_ptr) { // should only need to compute hash for miner transactions tx_hash = get_transaction_hash(tx); LOG_PRINT_L3("null tx_hash_ptr - needed to compute: " << tx_hash); } else { tx_hash = *tx_hash_ptr; } if (tx.version >= 2) { if (!tx_prunable_hash_ptr) tx_prunable_hash = get_transaction_prunable_hash(tx); else tx_prunable_hash = *tx_prunable_hash_ptr; } for (const txin_v& tx_input : tx.vin) { if (tx_input.type() == typeid(txin_to_key)) { add_spent_key(boost::get<txin_to_key>(tx_input).k_image); } else if (tx_input.type() == typeid(txin_gen)) { /* nothing to do here */ miner_tx = true; } else { LOG_PRINT_L1("Unsupported input type, removing key images and aborting transaction addition"); for (const txin_v& tx_input : tx.vin) { if (tx_input.type() == typeid(txin_to_key)) { remove_spent_key(boost::get<txin_to_key>(tx_input).k_image); } } return; } } uint64_t tx_id = add_transaction_data(blk_hash, tx, tx_hash, tx_prunable_hash); std::vector<uint64_t> amount_output_indices; // iterate tx.vout using indices instead of C++11 foreach syntax because // we need the index for (uint64_t i = 0; i < tx.vout.size(); ++i) { // miner v2 txes have their coinbase output in one single out to save space, // and we store them as rct outputs with an identity mask if (miner_tx && tx.version == 2) { cryptonote::tx_out vout = tx.vout[i]; rct::key commitment = rct::zeroCommit(vout.amount); vout.amount = 0; amount_output_indices.push_back(add_output(tx_hash, vout, i, tx.unlock_time, &commitment)); } else { amount_output_indices.push_back(add_output(tx_hash, tx.vout[i], i, tx.unlock_time, tx.version > 1 ? &tx.rct_signatures.outPk[i].mask : NULL)); } } add_tx_amount_output_indices(tx_id, amount_output_indices); }
bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) { LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); try { // Save tx to file if (!filename.empty()) { boost::system::error_code ignore; bool tx_file_exists = boost::filesystem::exists(filename, ignore); if(tx_file_exists && !overwrite){ m_errorString = string(tr("Attempting to save transaction to file, but specified file(s) exist. Exiting to not risk overwriting. File:")) + filename; m_status = Status_Error; LOG_ERROR(m_errorString); return false; } bool r = m_wallet.m_wallet->save_tx(m_pending_tx, filename); if (!r) { m_errorString = tr("Failed to write transaction(s) to file"); m_status = Status_Error; } else { m_status = Status_Ok; } } // Commit tx else { while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); m_wallet.m_wallet->commit_tx(ptx); // if no exception, remove element from vector m_pending_tx.pop_back(); } // TODO: extract method; } } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? m_errorString = tr("daemon is busy. Please try again later."); m_status = Status_Error; } catch (const tools::error::no_connection_to_daemon&) { m_errorString = tr("no connection to daemon. Please make sure daemon is running."); m_status = Status_Error; } catch (const tools::error::tx_rejected& e) { std::ostringstream writer(m_errorString); writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); std::string reason = e.reason(); m_status = Status_Error; m_errorString = writer.str(); if (!reason.empty()) m_errorString += string(tr(". Reason: ")) + reason; } catch (const std::exception &e) { m_errorString = string(tr("Unknown exception: ")) + e.what(); m_status = Status_Error; } catch (...) { m_errorString = tr("Unhandled exception"); LOG_ERROR(m_errorString); m_status = Status_Error; } return m_status == Status_Ok; }
crypto::hash transfer_details::tx_hash() const { return get_transaction_hash(m_tx); };
//--------------------------------------------------------------------------------- bool tx_memory_pool::add_tx(const transaction &tx, tx_verification_context& tvc, bool keeped_by_block, std::string alias) { crypto::hash h = null_hash; get_transaction_hash(tx, h); return add_tx(tx, h, tvc, keeped_by_block, alias); }
bool handle_output(const Transaction& tx, const TransactionOutput& out, size_t transactionOutputIndex) { m_resultsCollector.push_back(std::make_pair(get_transaction_hash(tx), transactionOutputIndex)); return true; }
bool CurrentBlockchainStatus::search_if_payment_made( const string& payment_id_str, const uint64_t& desired_amount, string& tx_hash_with_payment) { vector<pair<uint64_t, transaction>> mempool_transactions = get_mempool_txs(); uint64_t current_blockchain_height = current_height; vector<transaction> txs_to_check; for (auto& mtx: mempool_transactions) { txs_to_check.push_back(mtx.second); } // apend txs in last to blocks into the txs_to_check vector for (uint64_t blk_i = current_blockchain_height - 10; blk_i <= current_blockchain_height; ++blk_i) { // get block cointaining this tx block blk; if (!get_block(blk_i, blk)) { cerr << "Cant get block of height: " + to_string(blk_i) << endl; return false; } list <cryptonote::transaction> blk_txs; if (!get_block_txs(blk, blk_txs)) { cerr << "Cant get transactions in block: " << to_string(blk_i) << endl; return false; } // combine mempool txs and txs from given number of // last blocks txs_to_check.insert(txs_to_check.end(), blk_txs.begin(), blk_txs.end()); } for (transaction& tx: txs_to_check) { if (is_coinbase(tx)) { // not interested in coinbase txs continue; } string tx_payment_id_str = get_payment_id_as_string(tx); // we are interested only in txs with encrypted payments id8 // they have length of 16 characters. if (tx_payment_id_str.length() != 16) { continue; } // we have some tx with encrypted payment_id8 // need to decode it using tx public key, and our // private view key, before we can comapre it is // what we are after. crypto::hash8 encrypted_payment_id8; if (!hex_to_pod(tx_payment_id_str, encrypted_payment_id8)) { cerr << "failed parsing hex to pod for encrypted_payment_id8" << '\n'; } // decrypt the encrypted_payment_id8 public_key tx_pub_key = xmreg::get_tx_pub_key_from_received_outs(tx); // public transaction key is combined with our viewkey // to create, so called, derived key. key_derivation derivation; if (!generate_key_derivation(tx_pub_key, import_payment_viewkey, derivation)) { cerr << "Cant get derived key for: " << "\n" << "pub_tx_key: " << tx_pub_key << " and " << "prv_view_key" << import_payment_viewkey << endl; return false; } // decrypt encrypted payment id, as used in integreated addresses crypto::hash8 decrypted_payment_id8 = encrypted_payment_id8; if (decrypted_payment_id8 != null_hash8) { if (!decrypt_payment_id(decrypted_payment_id8, tx_pub_key, import_payment_viewkey)) { cerr << "Cant decrypt decrypted_payment_id8: " << pod_to_hex(decrypted_payment_id8) << "\n"; } } string decrypted_tx_payment_id_str = pod_to_hex(decrypted_payment_id8); // check if decrypted payment id matches what we have stored // in mysql. if (payment_id_str != decrypted_tx_payment_id_str) { // check tx having specific payment id only continue; } // if everything ok with payment id, we proceed with // checking if the amount transfered is correct. // for each output, in a tx, check if it belongs // to the given account of specific address and viewkey // <public_key , amount , out idx> vector<tuple<txout_to_key, uint64_t, uint64_t>> outputs; outputs = get_ouputs_tuple(tx); string tx_hash_str = pod_to_hex(get_transaction_hash(tx)); uint64_t total_received {0}; for (auto& out: outputs) { txout_to_key txout_k = std::get<0>(out); uint64_t amount = std::get<1>(out); uint64_t output_idx_in_tx = std::get<2>(out); // get the tx output public key // that normally would be generated for us, // if someone had sent us some xmr. public_key generated_tx_pubkey; derive_public_key(derivation, output_idx_in_tx, import_payment_address.m_spend_public_key, generated_tx_pubkey); // check if generated public key matches the current output's key bool mine_output = (txout_k.key == generated_tx_pubkey); // if mine output has RingCT, i.e., tx version is 2 // need to decode its amount. otherwise its zero. if (mine_output && tx.version == 2) { // initialize with regular amount uint64_t rct_amount = amount; // cointbase txs have amounts in plain sight. // so use amount from ringct, only for non-coinbase txs if (!is_coinbase(tx)) { bool r; r = decode_ringct(tx.rct_signatures, tx_pub_key, import_payment_viewkey, output_idx_in_tx, tx.rct_signatures.ecdhInfo[output_idx_in_tx].mask, rct_amount); if (!r) { cerr << "Cant decode ringCT!" << endl; throw TxSearchException("Cant decode ringCT!"); } amount = rct_amount; } } // if (mine_output && tx.version == 2) if (mine_output) { total_received += amount; } } cout << " - payment id check in tx: " << tx_hash_str << " found: " << total_received << endl; if (total_received >= desired_amount) { // the payment has been made. tx_hash_with_payment = tx_hash_str; return true; } } return false; }
PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, uint64_t amount, uint32_t mixin_count) { clearStatus(); vector<cryptonote::tx_destination_entry> dsts; cryptonote::tx_destination_entry de; // indicates if dst_addr is integrated address (address + payment_id) bool has_payment_id; crypto::hash8 payment_id_short; // TODO: (https://bitcointalk.org/index.php?topic=753252.msg9985441#msg9985441) size_t fake_outs_count = mixin_count > 0 ? mixin_count : m_wallet->default_mixin(); if (fake_outs_count == 0) fake_outs_count = DEFAULT_MIXIN; PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { if(!cryptonote::get_account_integrated_address_from_str(de.addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 m_status = Status_Error; m_errorString = "Invalid destination address"; break; } std::vector<uint8_t> extra; // if dst_addr is not an integrated address, parse payment_id if (!has_payment_id && !payment_id.empty()) { // copy-pasted from simplewallet.cpp:2212 crypto::hash payment_id_long; bool r = tools::wallet2::parse_long_payment_id(payment_id, payment_id_long); if (r) { std::string extra_nonce; cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_long); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); } else { r = tools::wallet2::parse_short_payment_id(payment_id, payment_id_short); if (r) { std::string extra_nonce; set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short); r = add_extra_nonce_to_tx_extra(extra, extra_nonce); } } if (!r) { m_status = Status_Error; m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; break; } } de.amount = amount; if (de.amount <= 0) { m_status = Status_Error; m_errorString = "Invalid amount"; break; } dsts.push_back(de); //std::vector<tools::wallet2::pending_tx> ptx_vector; try { transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trustedDaemon); } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? m_errorString = tr("daemon is busy. Please try again later."); m_status = Status_Error; } catch (const tools::error::no_connection_to_daemon&) { m_errorString = tr("no connection to daemon. Please make sure daemon is running."); m_status = Status_Error; } catch (const tools::error::wallet_rpc_error& e) { m_errorString = tr("RPC error: ") + e.to_string(); m_status = Status_Error; } catch (const tools::error::get_random_outs_error&) { m_errorString = tr("failed to get random outputs to mix"); m_status = Status_Error; } catch (const tools::error::not_enough_money& e) { m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % print_money(e.available()) % print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); m_errorString = writer.str(); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; for (const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs_for_amount : e.scanty_outs()) { writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.amount) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.outs.size(); } m_errorString = writer.str(); m_status = Status_Error; } catch (const tools::error::tx_not_constructed&) { m_errorString = tr("transaction was not constructed"); m_status = Status_Error; } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); m_errorString = writer.str(); m_status = Status_Error; } catch (const tools::error::tx_sum_overflow& e) { m_errorString = e.what(); m_status = Status_Error; } catch (const tools::error::zero_destination&) { m_errorString = tr("one of destinations is zero"); m_status = Status_Error; } catch (const tools::error::tx_too_big& e) { m_errorString = tr("failed to find a suitable way to split transactions"); m_status = Status_Error; } catch (const tools::error::transfer_error& e) { m_errorString = string(tr("unknown transfer error: ")) + e.what(); m_status = Status_Error; } catch (const tools::error::wallet_internal_error& e) { m_errorString = string(tr("internal error: ")) + e.what(); m_status = Status_Error; } catch (const std::exception& e) { m_errorString = string(tr("unexpected error: ")) + e.what(); m_status = Status_Error; } catch (...) { m_errorString = tr("unknown error"); m_status = Status_Error; } } while (false); transaction->m_status = m_status; transaction->m_errorString = m_errorString; return transaction; }