Monero GUI Wallet发送交易源码分析

源码: https://github.com/monero-project/monero-gui

Monero GUI Wallet 使用了 QML技术

  • Transfer.qml

    Rectangle {id: rootsignal paymentClicked(string address, string paymentId, string amount, int mixinCount,int priority, string description)signal sweepUnmixableClicked()......RowLayout {StandardButton {id: sendButtonrightIcon: "qrc:///images/rightArrow.png"rightIconInactive: "qrc:///images/rightArrowInactive.png"Layout.topMargin: 4text: qsTr("Send") + translationManager.emptyStringenabled: !sendButtonWarningBox.visible && !warningContent && addressLine.text && !paymentIdWarningBox.visibleonClicked: {console.log("Transfer: paymentClicked")var priority = priorityModelV5.get(priorityDropdown.currentIndex).priorityconsole.log("priority: " + priority)console.log("amount: " + amountLine.text)addressLine.text = addressLine.text.trim()setPaymentId(paymentIdLine.text.trim());root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, root.mixin, priority, descriptionLine.text)}}}
    
  • main.qml

  middlePanel.paymentClicked.connect(handlePayment);  //将Send按钮的点击事件与handlePayment绑定middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable);....// called on "transfer"function handlePayment(address, paymentId, amount, mixinCount, priority, description, createFile) {console.log("Creating transaction: ")console.log("\taddress: ", address,", payment_id: ", paymentId,", amount: ", amount,", mixins: ", mixinCount,", priority: ", priority,", description: ", description);var splashMsg = qsTr("Creating transaction...");splashMsg += appWindow.currentWallet.isLedger() ? qsTr("\n\nPlease check your hardware wallet –\nyour input may be required.") : "";showProcessingSplash(splashMsg);transactionDescription = description;// validate amount;if (amount !== "(all)") {var amountxmr = walletManager.amountFromString(amount);console.log("integer amount: ", amountxmr);console.log("integer unlocked", currentWallet.unlockedBalance())if (amountxmr <= 0) {hideProcessingSplash()informationPopup.title = qsTr("Error") + translationManager.emptyString;informationPopup.text  = qsTr("Amount is wrong: expected number from %1 to %2").arg(walletManager.displayAmount(0)).arg(walletManager.displayAmount(currentWallet.unlockedBalance()))+ translationManager.emptyStringinformationPopup.icon  = StandardIcon.CriticalinformationPopup.onCloseCallback = nullinformationPopup.open()return;} else if (amountxmr > currentWallet.unlockedBalance()) {hideProcessingSplash()informationPopup.title = qsTr("Error") + translationManager.emptyString;informationPopup.text  = qsTr("Insufficient funds. Unlocked balance: %1").arg(walletManager.displayAmount(currentWallet.unlockedBalance()))+ translationManager.emptyStringinformationPopup.icon  = StandardIcon.CriticalinformationPopup.onCloseCallback = nullinformationPopup.open()return;}}if (amount === "(all)")currentWallet.createTransactionAllAsync(address, paymentId, mixinCount, priority);else//调用 C++的函数进行异步创建交易(不阻塞, 会显示 "创建中..."遮挡页面), 当创建成功后会发送创建成功的信号currentWallet.createTransactionAsync(address, paymentId, amountxmr, mixinCount, priority);}//交易异步创建后,  响应C++发来的信号, 弹出对话框让用户确认function onTransactionCreated(pendingTransaction,address,paymentId,mixinCount){console.log("Transaction created");hideProcessingSplash();transaction = pendingTransaction;// validate address;if (transaction.status !== PendingTransaction.Status_Ok) {console.error("Can't create transaction: ", transaction.errorString);informationPopup.title = qsTr("Error") + translationManager.emptyString;if (currentWallet.connected() == Wallet.ConnectionStatus_WrongVersion)informationPopup.text  = qsTr("Can't create transaction: Wrong daemon version: ") + transaction.errorStringelseinformationPopup.text  = qsTr("Can't create transaction: ") + transaction.errorStringinformationPopup.icon  = StandardIcon.CriticalinformationPopup.onCloseCallback = nullinformationPopup.open();// deleting transaction object, we don't want memleakscurrentWallet.disposeTransaction(transaction);} else if (transaction.txCount == 0) {informationPopup.title = qsTr("Error") + translationManager.emptyStringinformationPopup.text  = qsTr("No unmixable outputs to sweep") + translationManager.emptyStringinformationPopup.icon = StandardIcon.InformationinformationPopup.onCloseCallback = nullinformationPopup.open()// deleting transaction object, we don't want memleakscurrentWallet.disposeTransaction(transaction);} else {console.log("Transaction created, amount: " + walletManager.displayAmount(transaction.amount)+ ", fee: " + walletManager.displayAmount(transaction.fee));// here we show confirmation popup;transactionConfirmationPopup.title = qsTr("Please confirm transaction:\n") + translationManager.emptyString;transactionConfirmationPopup.text = "";transactionConfirmationPopup.text += (address === "" ? "" : (qsTr("Address: ") + address));transactionConfirmationPopup.text += (paymentId === "" ? "" : (qsTr("\nPayment ID: ") + paymentId));transactionConfirmationPopup.text +=  qsTr("\n\nAmount: ") + walletManager.displayAmount(transaction.amount);transactionConfirmationPopup.text +=  qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee);transactionConfirmationPopup.text +=  qsTr("\nRingsize: ") + (mixinCount + 1);transactionConfirmationPopup.text +=  qsTr("\n\nNumber of transactions: ") + transaction.txCounttransactionConfirmationPopup.text +=  (transactionDescription === "" ? "" : (qsTr("\nDescription: ") + transactionDescription))for (var i = 0; i < transaction.subaddrIndices.length; ++i){transactionConfirmationPopup.text += qsTr("\nSpending address index: ") + transaction.subaddrIndices[i];}transactionConfirmationPopup.text += translationManager.emptyString;transactionConfirmationPopup.icon = StandardIcon.QuestiontransactionConfirmationPopup.open()}}//对交易进行确认// called after user confirms transactionfunction handleTransactionConfirmed(fileName) {// View only wallet - we save the txif(viewOnly && saveTxDialog.fileUrl){// No file specified - abortif(!saveTxDialog.fileUrl) {currentWallet.disposeTransaction(transaction)return;}var path = walletManager.urlToLocalPath(saveTxDialog.fileUrl)// Store to filetransaction.setFilename(path);}appWindow.showProcessingSplash(qsTr("Sending transaction ..."));currentWallet.commitTransactionAsync(transaction);}//交易广播后function onTransactionCommitted(success, transaction, txid) {hideProcessingSplash();if (!success) {console.log("Error committing transaction: " + transaction.errorString);informationPopup.title = qsTr("Error") + translationManager.emptyStringinformationPopup.text  = qsTr("Couldn't send the money: ") + transaction.errorStringinformationPopup.icon  = StandardIcon.Critical} else {var txid_text = ""informationPopup.title = qsTr("Information") + translationManager.emptyStringfor (var i = 0; i < txid.length; ++i) {if (txid_text.length > 0)txid_text += ", "txid_text += txid[i]}informationPopup.text  = (viewOnly)? qsTr("Transaction saved to file: %1").arg(path) : qsTr("Monero sent successfully: %1 transaction(s) ").arg(txid.length) + txid_text + translationManager.emptyStringinformationPopup.icon  = StandardIcon.Informationif (transactionDescription.length > 0) {for (var i = 0; i < txid.length; ++i)currentWallet.setUserNote(txid[i], transactionDescription);}// Clear tx fieldsmiddlePanel.transferView.clearFields()}informationPopup.onCloseCallback = nullinformationPopup.open()currentWallet.refresh()currentWallet.disposeTransaction(transaction)currentWallet.store();}
  • src/libwalletqt/Wallet.cpp

    void Wallet::createTransactionAsync(const QString &dst_addr, const QString &payment_id,quint64 amount, quint32 mixin_count,PendingTransaction::Priority priority)
    {m_scheduler.run([this, dst_addr, payment_id, amount, mixin_count, priority] {PendingTransaction *tx = createTransaction(dst_addr, payment_id, amount, mixin_count, priority);//异步创建完成后,  发送信号 给QML, 对应QML中的 onTransactionCreated,//弹出对话框让用户确认emit transactionCreated(tx, dst_addr, payment_id, mixin_count);});
    }
    
  • monero/src/wallet/api/wallet.cpp

    
    PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count,PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices){clearStatus();// Pause refresh thread while creating transactionpauseRefresh();cryptonote::address_parse_info info;// indicates if dst_addr is integrated address (address + payment_id)// 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;fake_outs_count = m_wallet->adjust_mixin(fake_outs_count);uint32_t adjusted_priority = m_wallet->adjust_priority(static_cast<uint32_t>(priority));PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);do {if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) {// TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982setStatusError(tr("Invalid destination address"));break;}std::vector<uint8_t> extra;// if dst_addr is not an integrated address, parse payment_idif (!info.has_payment_id && !payment_id.empty()) {// copy-pasted from simplewallet.cpp:2212crypto::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, info.payment_id);if (r) {std::string extra_nonce;set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);r = add_extra_nonce_to_tx_extra(extra, extra_nonce);}}if (!r) {setStatusError(tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id);break;}}else if (info.has_payment_id) {std::string extra_nonce;set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce);if (!r) {setStatusError(tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id));break;}}//std::vector<tools::wallet2::pending_tx> ptx_vector;try {if (amount) {vector<cryptonote::tx_destination_entry> dsts;cryptonote::tx_destination_entry de;de.original = dst_addr;de.addr = info.address;de.amount = *amount;de.is_subaddress = info.is_subaddress;de.is_integrated = info.has_payment_id;dsts.push_back(de);//调用底层函数进行创建transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */,adjusted_priority,extra, subaddr_account, subaddr_indices);} else {// for the GUI, sweep_all (i.e. amount set as "(all)") will always sweep all the funds in all the addressesif (subaddr_indices.empty()){for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index)subaddr_indices.insert(index);}transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */,adjusted_priority,extra, subaddr_account, subaddr_indices);}pendingTxPostProcess(transaction);if (multisig().isMultisig) {auto tx_set = m_wallet->make_multisig_tx_set(transaction->m_pending_tx);transaction->m_pending_tx = tx_set.m_ptx;transaction->m_signers = tx_set.m_signers;}} catch (const tools::error::daemon_busy&) {//省略其他异常捕获...........} catch (...) {setStatusError(tr("unknown error"));}} while (false);statusWithErrorString(transaction->m_status, transaction->m_errorString);// Resume refresh threadstartRefresh();return transaction;
    }
    
  • wallet2.cpp

    // Another implementation of transaction creation that is hopefully better
    // While there is anything left to pay, it goes through random outputs and tries
    // to fill the next destination/amount. If it fully fills it, it will use the
    // remainder to try to fill the next one as well.
    // The tx size if roughly estimated as a linear function of only inputs, and a
    // new tx will be created when that size goes above a given fraction of the
    // max tx size. At that point, more outputs may be added if the fee cannot be
    // satisfied.
    // If the next output in the next tx would go to the same destination (ie, we
    // cut off at a tx boundary in the middle of paying a given destination), the
    // fee will be carved out of the current input if possible, to avoid having to
    // add another output just for the fee and getting change.
    // This system allows for sending (almost) the entire balance, since it does
    // not generate spurious change in all txes, thus decreasing the instantaneous
    // usable balance.
    std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
    {//ensure device is let in NONE mode in any casehw::device &hwdev = m_account.get_device();boost::unique_lock<hw::device> hwdev_lock (hwdev);hw::reset_mode rst(hwdev);  auto original_dsts = dsts;if(m_light_wallet) {// Populate m_transferslight_wallet_get_unspent_outs();}std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_transfers_indices_per_subaddr;std::vector<std::pair<uint32_t, std::vector<size_t>>> unused_dust_indices_per_subaddr;uint64_t needed_money;uint64_t accumulated_fee, accumulated_outputs, accumulated_change;struct TX {std::vector<size_t> selected_transfers;std::vector<cryptonote::tx_destination_entry> dsts;cryptonote::transaction tx;pending_tx ptx;size_t weight;uint64_t needed_fee;std::vector<std::vector<tools::wallet2::get_outs_entry>> outs;TX() : weight(0), needed_fee(0) {}void add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations) {if (merge_destinations){std::vector<cryptonote::tx_destination_entry>::iterator i;i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &de.addr, sizeof(de.addr)); });if (i == dsts.end()){dsts.push_back(de);i = dsts.end() - 1;i->amount = 0;}i->amount += amount;}else{THROW_WALLET_EXCEPTION_IF(original_output_index > dsts.size(), error::wallet_internal_error,std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size()));if (original_output_index == dsts.size()){dsts.push_back(de);dsts.back().amount = 0;}THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address");dsts[original_output_index].amount += amount;}}};std::vector<TX> txes;bool adding_fee; // true if new outputs go towards fee, rather than destinationsuint64_t needed_fee, available_for_fee = 0;uint64_t upper_transaction_weight_limit = get_upper_transaction_weight_limit();const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);const bool use_rct = use_fork_rules(4, 0);const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);const rct::RCTConfig rct_config {bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0};const uint64_t base_fee  = get_base_fee();const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());const uint64_t fee_quantization_mask = get_fee_quantization_mask();// throw if attempting a transaction with no destinationsTHROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);// calculate total amount being sent to all destinations// throw if total amount overflows uint64_tneeded_money = 0;for(auto& dt: dsts){THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination);needed_money += dt.amount;LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money));THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, 0, m_nettype);}// throw if attempting a transaction with no moneyTHROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination);std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account);std::map<uint32_t, uint64_t> balance_per_subaddr = balance_per_subaddress(subaddr_account);if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked balance{for (const auto& i : balance_per_subaddr)subaddr_indices.insert(i.first);}// early out if we know we can't make it anyway// we could also check for being within FEE_PER_KB, but if the fee calculation// ever changes, this might be missed, so let this go throughconst uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof));uint64_t balance_subtotal = 0;uint64_t unlocked_balance_subtotal = 0;for (uint32_t index_minor : subaddr_indices){balance_subtotal += balance_per_subaddr[index_minor];unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first;}THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money,balance_subtotal, needed_money, 0);// first check overall balance is enough, then unlocked one, so we throw distinct exceptionsTHROW_WALLET_EXCEPTION_IF(needed_money + min_fee > unlocked_balance_subtotal, error::not_enough_unlocked_money,unlocked_balance_subtotal, needed_money, 0);for (uint32_t i : subaddr_indices)LOG_PRINT_L2("Candidate subaddress index for spending: " << i);// determine threshold for fractional amountconst size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof);const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof);THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);// gather all dust and non-dust outputs belonging to specified subaddressessize_t num_nondust_outputs = 0;size_t num_dust_outputs = 0;for (size_t i = 0; i < m_transfers.size(); ++i){const transfer_details& td = m_transfers[i];if (m_ignore_fractional_outputs && td.amount() < fractional_threshold){MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold));continue;}if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1){const uint32_t index_minor = td.m_subaddr_index.minor;auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; };if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())){auto found = std::find_if(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), find_predicate);if (found == unused_transfers_indices_per_subaddr.end()){unused_transfers_indices_per_subaddr.push_back({index_minor, {i}});}else{found->second.push_back(i);}++num_nondust_outputs;}else{auto found = std::find_if(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), find_predicate);if (found == unused_dust_indices_per_subaddr.end()){unused_dust_indices_per_subaddr.push_back({index_minor, {i}});}else{found->second.push_back(i);}++num_dust_outputs;}}}// sort output indices{auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y){return unlocked_balance_per_subaddr[x.first].first > unlocked_balance_per_subaddr[y.first].first;};std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate);std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate);}LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs");if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty())return std::vector<wallet2::pending_tx>();// if empty, put dummy entry so that the front can be referenced later in the loopif (unused_dust_indices_per_subaddr.empty())unused_dust_indices_per_subaddr.push_back({});if (unused_transfers_indices_per_subaddr.empty())unused_transfers_indices_per_subaddr.push_back({});// start with an empty txtxes.push_back(TX());accumulated_fee = 0;accumulated_outputs = 0;accumulated_change = 0;adding_fee = false;needed_fee = 0;std::vector<std::vector<tools::wallet2::get_outs_entry>> outs;// for rct, since we don't see the amounts, we will try to make all transactions// look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as// this prevents linking to another by provenance analysis, but two is ok if we// try to pick outputs not from the same block. We will get two outputs, one for// the destination, and one for change.LOG_PRINT_L2("checking preferred");std::vector<size_t> preferred_inputs;uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);rct_outs_needed += 100; // some fudge factor since we don't know how many are lockedif (use_rct){// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which// will get us a known fee.uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);if (!preferred_inputs.empty()){string s;for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + " (" + print_money(m_transfers[i].amount()) + ") ";LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s);// bring the list of available outputs stored by the same subaddress index to the front of the listuint32_t index_minor = m_transfers[preferred_inputs[0]].m_subaddr_index.minor;for (size_t i = 1; i < unused_transfers_indices_per_subaddr.size(); ++i){if (unused_transfers_indices_per_subaddr[i].first == index_minor){std::swap(unused_transfers_indices_per_subaddr[0], unused_transfers_indices_per_subaddr[i]);break;}}for (size_t i = 1; i < unused_dust_indices_per_subaddr.size(); ++i){if (unused_dust_indices_per_subaddr[i].first == index_minor){std::swap(unused_dust_indices_per_subaddr[0], unused_dust_indices_per_subaddr[i]);break;}}}}LOG_PRINT_L2("done checking preferred");// while:// - we have something to send// - or we need to gather more fee// - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2)unsigned int original_output_index = 0;std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;std::vector<size_t>* unused_dust_indices      = &unused_dust_indices_per_subaddr[0].second;hwdev.set_mode(hw::device::TRANSACTION_CREATE_FAKE);while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) {TX &tx = txes.back();LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size() << ", tx.dsts.size() " << tx.dsts.size());LOG_PRINT_L2("unused_transfers_indices: " << strjoin(*unused_transfers_indices, " "));LOG_PRINT_L2("unused_dust_indices: " << strjoin(*unused_dust_indices, " "));LOG_PRINT_L2("dsts size " << dsts.size() << ", first " << (dsts.empty() ? "-" : cryptonote::print_money(dsts[0].amount)));LOG_PRINT_L2("adding_fee " << adding_fee << ", use_rct " << use_rct);// if we need to spend money and don't have any left, we failif (unused_dust_indices->empty() && unused_transfers_indices->empty()) {LOG_PRINT_L2("No more outputs to choose from");THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee);}// get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc)// This could be more clever, but maybe at the cost of making probabilistic inferences easiersize_t idx;if (!preferred_inputs.empty()) {idx = pop_back(preferred_inputs);pop_if_present(*unused_transfers_indices, idx);pop_if_present(*unused_dust_indices, idx);} else if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) {// the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet toostd::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices);idx = pop_best_value(indices, tx.selected_transfers, true);// we might not want to add it if it's a large output and we don't have many leftif (m_transfers[idx].amount() >= m_min_output_value) {if (get_count_above(m_transfers, *unused_transfers_indices, m_min_output_value) < m_min_output_count) {LOG_PRINT_L2("Second output was not strictly needed, and we're running out of outputs above " << print_money(m_min_output_value) << ", not adding");break;}}// since we're trying to add a second output which is not strictly needed,// we only add it if it's unrelated enough to the first onefloat relatedness = get_output_relatedness(m_transfers[idx], m_transfers[tx.selected_transfers.front()]);if (relatedness > SECOND_OUTPUT_RELATEDNESS_THRESHOLD){LOG_PRINT_L2("Second output was not strictly needed, and relatedness " << relatedness << ", not adding");break;}pop_if_present(*unused_transfers_indices, idx);pop_if_present(*unused_dust_indices, idx);} elseidx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers);const transfer_details &td = m_transfers[idx];LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image);// add this output to the list to spendtx.selected_transfers.push_back(idx);uint64_t available_amount = td.amount();accumulated_outputs += available_amount;// clear any fake outs we'd already gathered, since we'll need a new setouts.clear();if (adding_fee){LOG_PRINT_L2("We need more fee, adding it to fee");available_for_fee += available_amount;}else{while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)){// we can fully pay that destinationLOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<" for " << print_money(dsts[0].amount));tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations);available_amount -= dsts[0].amount;dsts[0].amount = 0;pop_index(dsts, 0);++original_output_index;}if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {// we can partially fill that destinationLOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations);dsts[0].amount -= available_amount;available_amount = 0;}}// here, check if we need to sent tx and start a new oneLOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "<< upper_transaction_weight_limit);bool try_tx = false;// if we have preferred picks, but haven't yet used all of them, continueif (preferred_inputs.empty()){if (adding_fee){/* might not actually be enough if adding this output bumps size to next kB, but we need to try */try_tx = available_for_fee >= needed_fee;}else{const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof);try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit);}}if (try_tx) {cryptonote::transaction test_tx;pending_tx test_ptx;needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);uint64_t inputs = 0, outputs = needed_fee;for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();for (const auto &o: tx.dsts) outputs += o.amount;if (inputs < outputs){LOG_PRINT_L2("We don't have enough for the basic fee, switching to adding_fee");adding_fee = true;goto skip_tx;}LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<tx.selected_transfers.size() << " inputs");if (use_rct)transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,test_tx, test_ptx, rct_config);elsetransfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);auto txBlob = t_serializable_object_to_blob(test_ptx.tx);needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask);available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0);LOG_PRINT_L2("Made a " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(available_for_fee) << " available for fee (" <<print_money(needed_fee) << " needed)");if (needed_fee > available_for_fee && !dsts.empty() && dsts[0].amount > 0){// we don't have enough for the fee, but we've only partially paid the current address,// so we can take the fee from the paid amount, since we'll have to make another tx anywaystd::vector<cryptonote::tx_destination_entry>::iterator i;i = std::find_if(tx.dsts.begin(), tx.dsts.end(),[&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &dsts[0].addr, sizeof(dsts[0].addr)); });THROW_WALLET_EXCEPTION_IF(i == tx.dsts.end(), error::wallet_internal_error, "paid address not found in outputs");if (i->amount > needed_fee){uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee;LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_nettype, i->is_subaddress, i->addr) << " from " <<print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accommodate " <<print_money(needed_fee) << " fee");dsts[0].amount += i->amount - new_paid_amount;i->amount = new_paid_amount;test_ptx.fee = needed_fee;available_for_fee = needed_fee;}}if (needed_fee > available_for_fee){LOG_PRINT_L2("We could not make a tx, switching to fee accumulation");adding_fee = true;}else{LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee));while (needed_fee > test_ptx.fee) {if (use_rct)transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,test_tx, test_ptx, rct_config);elsetransfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx);txBlob = t_serializable_object_to_blob(test_ptx.tx);needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_multiplier, fee_quantization_mask);LOG_PRINT_L2("Made an attempt at a  final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) <<" fee  and " << print_money(test_ptx.change_dts.amount) << " change");}LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) <<" fee  and " << print_money(test_ptx.change_dts.amount) << " change");tx.tx = test_tx;tx.ptx = test_ptx;tx.weight = get_transaction_weight(test_tx, txBlob.size());tx.outs = outs;tx.needed_fee = test_ptx.fee;accumulated_fee += test_ptx.fee;accumulated_change += test_ptx.change_dts.amount;adding_fee = false;if (!dsts.empty()){LOG_PRINT_L2("We have more to pay, starting another tx");txes.push_back(TX());original_output_index = 0;}}}skip_tx:// if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay, // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddrif ((!dsts.empty() && dsts[0].amount > 0) || adding_fee){if (unused_transfers_indices->empty() && unused_transfers_indices_per_subaddr.size() > 1){unused_transfers_indices_per_subaddr.erase(unused_transfers_indices_per_subaddr.begin());unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;}if (unused_dust_indices->empty() && unused_dust_indices_per_subaddr.size() > 1){unused_dust_indices_per_subaddr.erase(unused_dust_indices_per_subaddr.begin());unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;}}}if (adding_fee){LOG_PRINT_L1("We ran out of outputs while trying to gather final fee");THROW_WALLET_EXCEPTION_IF(1, error::tx_not_possible, unlocked_balance(subaddr_account), needed_money, accumulated_fee + needed_fee);}LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) <<" total fee, " << print_money(accumulated_change) << " total change");hwdev.set_mode(hw::device::TRANSACTION_CREATE_REAL);for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i){TX &tx = *i;cryptonote::transaction test_tx;pending_tx test_ptx;if (use_rct) {transfer_selected_rct(tx.dsts,                    /* NOMOD std::vector<cryptonote::tx_destination_entry> dsts,*/tx.selected_transfers,      /* const std::list<size_t> selected_transfers */fake_outs_count,            /* CONST size_t fake_outputs_count, */tx.outs,                    /* MOD   std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */unlock_time,                /* CONST uint64_t unlock_time,  */tx.needed_fee,              /* CONST uint64_t fee, */extra,                      /* const std::vector<uint8_t>& extra, */test_tx,                    /* OUT   cryptonote::transaction& tx, */test_ptx,                   /* OUT   cryptonote::transaction& tx, */rct_config);} else {transfer_selected(tx.dsts,tx.selected_transfers,fake_outs_count,tx.outs,unlock_time,tx.needed_fee,extra,detail::digit_split_strategy,tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD),test_tx,test_ptx);}auto txBlob = t_serializable_object_to_blob(test_ptx.tx);tx.tx = test_tx;tx.ptx = test_ptx;tx.weight = get_transaction_weight(test_tx, txBlob.size());}std::vector<wallet2::pending_tx> ptx_vector;for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i){TX &tx = *i;uint64_t tx_money = 0;for (size_t idx: tx.selected_transfers)tx_money += m_transfers[idx].amount();LOG_PRINT_L1("  Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() <<" " << get_transaction_hash(tx.ptx.tx) << ": " << get_weight_string(tx.weight) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() <<" outputs to " << tx.dsts.size() << " destination(s), including " <<print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change");ptx_vector.push_back(tx.ptx);}THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, original_dsts), error::wallet_internal_error, "Created transaction(s) failed sanity check");// if we made it this far, we're OK to actually send the transactionsreturn ptx_vector;
    }

Monero GUI Wallet发送交易源码分析相关推荐

  1. 【区块链 | 智能合约】Ethereum源代码(8)- Ethereum服务和以太坊P2P协议发送广播源码分析

    在"[区块链 | 智能合约]Ethereum源代码(2)- go-ethereum 客户端入口代码和Node分析"一文中,我们提到Ethereum作为一个service,被Node ...

  2. Android—EventBus使用与源码分析

    EventBus 安卓事件发布/订阅框架 事件传递既可用于Android四大组件间通讯 EventBus的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦 在onStart进行注册,onStop进 ...

  3. 以太坊C++客户端Aleth源码分析,转账交易和智能合约的入口代码

    本文主要记录以太坊C++客户端Aleth的源码分析和相关实验过程和结果.本文将讲解两部分的内容,一是转账交易和智能合约的入口代码在哪里?二是通过实验验证转账交易和智能合约交易这两种不同交易所对应的不同 ...

  4. Hyperledger Fabric从源码分析交易

    在上一章Hyperledger Fabric从源码分析区块结构中提到了区块的概念,并从源码角度对区块的结构进行了剖析,知道一个简单的区块包含了下面几个部分 BlockHeader, 区块头 Block ...

  5. celery源码分析-Task的初始化与发送任务

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery的任务发送 在Django项目中使用了装饰器来包装待执行任务, from cel ...

  6. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  7. 以太坊源码分析-交易

    以太坊源码分析-交易 机理 先说一点区块链转账的基本概念和流程 用户输入转账的地址和转入的地址和转出的金额 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行) 系统对交易信 ...

  8. producer send源码_RocketMq系列之Producer顺序消息发送源码分析(四)

    有序消息 消息有序指的是可以按照消息的发送顺序来消费. RocketMQ可以严格的保证消息有序.但这个顺序,不是全局顺序,只是分区(queue)顺序. 顺序消息生产者 public static vo ...

  9. 第三季2:ORTP库的源码分析、RTP发送实验的源码分析

    以下内容源于朱有鹏课程,如有侵权,请告知删除. 一.ORTP库源码分析 1.ORTP库概览 (1)库提供一堆功能函数(本身没有main),都在src目录下 (2)库的使用给了案例(有main),在sr ...

  10. NEO从源码分析看共识协议

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 概论 不同于比特币使用的工作量证明(PoW)来实现共识,NEO提出了DBFT共识算法.DBFT改良自股权证明算法(P ...

最新文章

  1. 可持久化数据结构讲解
  2. ESP-12F模块转接板测试版调试说明,下载MicroPython程序。ESP8266-12F
  3. 设计模式之Prototype(原型)(转)
  4. 给定地址段为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为____到____
  5. [DeeplearningAI笔记]卷积神经网络2.9-2.10迁移学习与数据增强
  6. 非常有趣的Console
  7. jboss eap 7_使用JBoss EAP 7的HTTP / 2
  8. React开发(150):判断方法有避免报错
  9. 2020 数据技术嘉年华:吹响国产数据库的集结号 诚邀参会览技术前沿
  10. 这是一个关键……可是这个门槛我上不去了
  11. 全面了解Nginx主要应用场景
  12. Docker存储驱动之总览
  13. 【Storm入门指南】第六章 真实示例
  14. 驱动人生6网卡版 v6.1.19.90 官方版
  15. 这两种方法能使PDF不能被复制和修改
  16. 如何把视频转换为gif动图
  17. 用html+css做一个网页设计
  18. AutoFac基本使用-笔记
  19. w ndows无法连接到System,电脑无法连接到System Event Notification Service服务
  20. 生物电镜常见问题及解答

热门文章

  1. react中数组添加值,超时设置
  2. 数商云:大宗商品撮合交易平台搭建丨加强业务、技术、应用与集成的创新
  3. python爬取58同城所有租房信息_Python 爬虫之-58租房数据
  4. GIS地图布局设置总结
  5. Swift 5.1 温故而知新笔记系列之第七天
  6. 三个数相减的平方公式_小学二年级数学34个必考公式以及重难点解析
  7. html自我介绍怎么弄,用html设计一个自我介绍的静态网页
  8. vim 方向键和backspace乱码
  9. 2020美团秋招,二本计算机,疯狂复习半年,拿下美团offer
  10. 三阶魔方层先法自动复原_Python