bool isDust(interfaces::Node& node, const QString& address, const CAmount& amount) { CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); return IsDust(txOut, node.getDustRelayFee()); }
bool isDust(const QString& address, const CAmount& amount) { CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); return IsDust(txOut, ::dustRelayFee); }
bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient) { if (!optionsModel) return false; if (request.IsInitialized()) { // Payment request network matches client network? if (!verifyNetwork(request.getDetails())) { Q_EMIT message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."), CClientUIInterface::MSG_ERROR); return false; } // Make sure any payment requests involved are still valid. // This is re-checked just before sending coins in WalletModel::sendCoins(). if (verifyExpired(request.getDetails())) { Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."), CClientUIInterface::MSG_ERROR); return false; } } else { Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."), CClientUIInterface::MSG_ERROR); return false; } recipient.paymentRequest = request; recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo()); request.getMerchant(certStore.get(), recipient.authenticatedMerchant); QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo(); QStringList addresses; for (const std::pair<CScript, CAmount>& sendingTo : sendingTos) { // Extract and check destination addresses CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { // Append destination address addresses.append(QString::fromStdString(EncodeDestination(dest))); } else if (!recipient.authenticatedMerchant.isEmpty()) { // Unauthenticated payment requests to custom bitcoin addresses are not supported // (there is no good way to tell the user where they are paying in a way they'd // have a chance of understanding). Q_EMIT message(tr("Payment request rejected"), tr("Unverified payment requests to custom payment scripts are unsupported."), CClientUIInterface::MSG_ERROR); return false; } // Bitcoin amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto), // but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range // and no overflow has happened. if (!verifyAmount(sendingTo.second)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } // Extract and check amounts CTxOut txOut(sendingTo.second, sendingTo.first); if (IsDust(txOut, ::dustRelayFee)) { Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).") .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)), CClientUIInterface::MSG_ERROR); return false; } recipient.amount += sendingTo.second; // Also verify that the final amount is still in a valid range after adding additional amounts. if (!verifyAmount(recipient.amount)) { Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR); return false; } } // Store addresses and format them to fit nicely into the GUI recipient.address = addresses.join("<br />"); if (!recipient.authenticatedMerchant.isEmpty()) { qDebug() << "PaymentServer::processPaymentRequest: Secure payment request from " << recipient.authenticatedMerchant; } else { qDebug() << "PaymentServer::processPaymentRequest: Insecure payment request to " << addresses.join(", "); } return true; }
void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) { if (!model) return; // nPayAmount CAmount nPayAmount = 0; bool fDust = false; CMutableTransaction txDummy; for (const CAmount &amount : CoinControlDialog::payAmounts) { nPayAmount += amount; if (amount > 0) { CTxOut txout(amount, (CScript)std::vector<unsigned char>(24, 0)); txDummy.vout.push_back(txout); fDust |= IsDust(txout, ::dustRelayFee); } } CAmount nAmount = 0; CAmount nPayFee = 0; CAmount nAfterFee = 0; CAmount nChange = 0; unsigned int nBytes = 0; unsigned int nBytesInputs = 0; unsigned int nQuantity = 0; bool fWitness = false; std::vector<COutPoint> vCoinControl; std::vector<COutput> vOutputs; coinControl->ListSelected(vCoinControl); model->getOutputs(vCoinControl, vOutputs); for (const COutput& out : vOutputs) { // unselect already spent, very unlikely scenario, this could happen // when selected are spent elsewhere, like rpc or another computer uint256 txhash = out.tx->GetHash(); COutPoint outpt(txhash, out.i); if (model->isSpent(outpt)) { coinControl->UnSelect(outpt); continue; } // Quantity nQuantity++; // Amount nAmount += out.tx->tx->vout[out.i].nValue; // Bytes CTxDestination address; int witnessversion = 0; std::vector<unsigned char> witnessprogram; if (out.tx->tx->vout[out.i].scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { nBytesInputs += (32 + 4 + 1 + (107 / WITNESS_SCALE_FACTOR) + 4); fWitness = true; } else if(ExtractDestination(out.tx->tx->vout[out.i].scriptPubKey, address)) { CPubKey pubkey; CKeyID *keyid = boost::get<CKeyID>(&address); if (keyid && model->getPubKey(*keyid, pubkey)) { nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); } else nBytesInputs += 148; // in all error cases, simply assume 148 here } else nBytesInputs += 148; } // calculation if (nQuantity > 0) { // Bytes nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here if (fWitness) { // there is some fudging in these numbers related to the actual virtual transaction size calculation that will keep this estimate from being exact. // usually, the result will be an overestimate within a couple of satoshis so that the confirmation dialog ends up displaying a slightly smaller fee. // also, the witness stack size value is a variable sized integer. usually, the number of stack items will be well under the single byte var int limit. nBytes += 2; // account for the serialized marker and flag bytes nBytes += nQuantity; // account for the witness byte that holds the number of stack items for each input. } // in the subtract fee from amount case, we can tell if zero change already and subtract the bytes, so that fee calculation afterwards is accurate if (CoinControlDialog::fSubtractFeeFromAmount) if (nAmount - nPayAmount == 0) nBytes -= 34; // Fee nPayFee = CWallet::GetMinimumFee(nBytes, *coinControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */); if (nPayAmount > 0) { nChange = nAmount - nPayAmount; if (!CoinControlDialog::fSubtractFeeFromAmount) nChange -= nPayFee; // Never create dust outputs; if we would, just add the dust to the fee. if (nChange > 0 && nChange < MIN_CHANGE) { CTxOut txout(nChange, (CScript)std::vector<unsigned char>(24, 0)); if (IsDust(txout, ::dustRelayFee)) { nPayFee += nChange; nChange = 0; if (CoinControlDialog::fSubtractFeeFromAmount) nBytes -= 34; // we didn't detect lack of change above } } if (nChange == 0 && !CoinControlDialog::fSubtractFeeFromAmount) nBytes -= 34; } // after fee nAfterFee = std::max<CAmount>(nAmount - nPayFee, 0); } // actually update labels int nDisplayUnit = WiFicoinUnits::WFC; if (model && model->getOptionsModel()) nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); QLabel *l1 = dialog->findChild<QLabel *>("labelCoinControlQuantity"); QLabel *l2 = dialog->findChild<QLabel *>("labelCoinControlAmount"); QLabel *l3 = dialog->findChild<QLabel *>("labelCoinControlFee"); QLabel *l4 = dialog->findChild<QLabel *>("labelCoinControlAfterFee"); QLabel *l5 = dialog->findChild<QLabel *>("labelCoinControlBytes"); QLabel *l7 = dialog->findChild<QLabel *>("labelCoinControlLowOutput"); QLabel *l8 = dialog->findChild<QLabel *>("labelCoinControlChange"); // enable/disable "dust" and "change" dialog->findChild<QLabel *>("labelCoinControlLowOutputText")->setEnabled(nPayAmount > 0); dialog->findChild<QLabel *>("labelCoinControlLowOutput") ->setEnabled(nPayAmount > 0); dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setEnabled(nPayAmount > 0); dialog->findChild<QLabel *>("labelCoinControlChange") ->setEnabled(nPayAmount > 0); // stats l1->setText(QString::number(nQuantity)); // Quantity l2->setText(WiFicoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Amount l3->setText(WiFicoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee l4->setText(WiFicoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee l5->setText(((nBytes > 0) ? ASYMP_UTF8 : "") + QString::number(nBytes)); // Bytes l7->setText(fDust ? tr("yes") : tr("no")); // Dust l8->setText(WiFicoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change if (nPayFee > 0) { l3->setText(ASYMP_UTF8 + l3->text()); l4->setText(ASYMP_UTF8 + l4->text()); if (nChange > 0 && !CoinControlDialog::fSubtractFeeFromAmount) l8->setText(ASYMP_UTF8 + l8->text()); } // turn label red when dust l7->setStyleSheet((fDust) ? "color:red;" : ""); // tool tips QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold."); // how many satoshis the estimated fee can vary per byte we guess wrong double dFeeVary = (double)nPayFee / nBytes; QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); l3->setToolTip(toolTip4); l4->setToolTip(toolTip4); l7->setToolTip(toolTipDust); l8->setToolTip(toolTip4); dialog->findChild<QLabel *>("labelCoinControlFeeText") ->setToolTip(l3->toolTip()); dialog->findChild<QLabel *>("labelCoinControlAfterFeeText") ->setToolTip(l4->toolTip()); dialog->findChild<QLabel *>("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); dialog->findChild<QLabel *>("labelCoinControlLowOutputText")->setToolTip(l7->toolTip()); dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); // Insufficient funds QLabel *label = dialog->findChild<QLabel *>("labelCoinControlInsuffFunds"); if (label) label->setVisible(nChange < 0); }
bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnessEnabled) { if (tx.nVersion > CTransaction::MAX_STANDARD_VERSION || tx.nVersion < 1) { reason = "version"; return false; } // Extremely large transactions with lots of inputs can cost the network // almost as much to process as they cost the sender in fees, because // computing signature hashes is O(ninputs*txsize). Limiting transactions // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. unsigned int sz = GetTransactionWeight(tx); if (sz >= MAX_STANDARD_TX_WEIGHT) { reason = "tx-size"; return false; } for (const CTxIn& txin : tx.vin) { // Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed // keys (remember the 520 byte limit on redeemScript size). That works // out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627 // bytes of scriptSig, which we round off to 1650 bytes for some minor // future-proofing. That's also enough to spend a 20-of-20 // CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not // considered standard. if (txin.scriptSig.size() > 1650) { reason = "scriptsig-size"; return false; } if (!txin.scriptSig.IsPushOnly()) { reason = "scriptsig-not-pushonly"; return false; } } unsigned int nDataOut = 0; txnouttype whichType; for (const CTxOut& txout : tx.vout) { if (!::IsStandard(txout.scriptPubKey, whichType, witnessEnabled)) { reason = "scriptpubkey"; return false; } if (whichType == TX_NULL_DATA) nDataOut++; else if ((whichType == TX_MULTISIG) && (!fIsBareMultisigStd)) { reason = "bare-multisig"; return false; } else if (IsDust(txout, ::dustRelayFee)) { reason = "dust"; return false; } } // only one OP_RETURN txout is permitted if (nDataOut > 1) { reason = "multi-op-return"; return false; } return true; }