danicoin/src/WalletLegacy/WalletTransactionSender.cpp

368 lines
14 KiB
C++
Raw Normal View History

// Copyright (c) 2011-2016 The Cryptonote developers
2014-07-18 10:49:01 +00:00
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
2015-07-30 15:22:07 +00:00
#include "crypto/crypto.h" //for rand()
#include "CryptoNoteCore/Account.h"
#include "CryptoNoteCore/CryptoNoteFormatUtils.h"
#include "CryptoNoteCore/CryptoNoteTools.h"
2014-08-13 10:51:37 +00:00
2015-07-30 15:22:07 +00:00
#include "WalletLegacy/WalletTransactionSender.h"
#include "WalletLegacy/WalletUtils.h"
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
#include "CryptoNoteCore/CryptoNoteBasicImpl.h"
2014-06-25 17:21:42 +00:00
2015-05-27 12:08:46 +00:00
#include <Logging/LoggerGroup.h>
#include <random>
2015-07-30 15:22:07 +00:00
using namespace Crypto;
2014-06-25 17:21:42 +00:00
namespace {
using namespace CryptoNote;
2015-07-30 15:22:07 +00:00
uint64_t countNeededMoney(uint64_t fee, const std::vector<WalletLegacyTransfer>& transfers) {
2014-06-25 17:21:42 +00:00
uint64_t needed_money = fee;
for (auto& transfer: transfers) {
2015-07-30 15:22:07 +00:00
throwIf(transfer.amount == 0, error::ZERO_DESTINATION);
throwIf(transfer.amount < 0, error::WRONG_AMOUNT);
2014-06-25 17:21:42 +00:00
needed_money += transfer.amount;
2015-07-30 15:22:07 +00:00
throwIf(static_cast<int64_t>(needed_money) < transfer.amount, error::SUM_OVERFLOW);
2014-06-25 17:21:42 +00:00
}
return needed_money;
}
2015-07-30 15:22:07 +00:00
void createChangeDestinations(const AccountPublicAddress& address, uint64_t neededMoney, uint64_t foundMoney, TransactionDestinationEntry& changeDts) {
2014-06-25 17:21:42 +00:00
if (neededMoney < foundMoney) {
changeDts.addr = address;
changeDts.amount = foundMoney - neededMoney;
}
}
2015-07-30 15:22:07 +00:00
void constructTx(const AccountKeys keys, const std::vector<TransactionSourceEntry>& sources, const std::vector<TransactionDestinationEntry>& splittedDests,
const std::string& extra, uint64_t unlockTimestamp, uint64_t sizeLimit, Transaction& tx) {
2014-06-25 17:21:42 +00:00
std::vector<uint8_t> extraVec;
extraVec.reserve(extra.size());
std::for_each(extra.begin(), extra.end(), [&extraVec] (const char el) { extraVec.push_back(el);});
2015-05-27 12:08:46 +00:00
Logging::LoggerGroup nullLog;
2015-07-30 15:22:07 +00:00
bool r = constructTransaction(keys, sources, splittedDests, extraVec, tx, unlockTimestamp, nullLog);
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
throwIf(!r, error::INTERNAL_WALLET_ERROR);
throwIf(getObjectBinarySize(tx) >= sizeLimit, error::TRANSACTION_SIZE_TOO_BIG);
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
std::shared_ptr<WalletLegacyEvent> makeCompleteEvent(WalletUserTransactionsCache& transactionCache, size_t transactionId, std::error_code ec) {
transactionCache.updateTransactionSendingState(transactionId, ec);
return std::make_shared<WalletSendTransactionCompletedEvent>(transactionId, ec);
}
2014-06-25 17:21:42 +00:00
} //namespace
namespace CryptoNote {
2015-07-30 15:22:07 +00:00
WalletTransactionSender::WalletTransactionSender(const Currency& currency, WalletUserTransactionsCache& transactionsCache, AccountKeys keys, ITransfersContainer& transfersContainer) :
m_currency(currency),
m_transactionsCache(transactionsCache),
m_isStoping(false),
m_keys(keys),
m_transferDetails(transfersContainer),
m_upperTransactionSizeLimit(m_currency.blockGrantedFullRewardZone() * 2 - m_currency.minerTxBlobReservedSize()) {}
2014-06-25 17:21:42 +00:00
void WalletTransactionSender::stop() {
m_isStoping = true;
}
2014-08-13 10:51:37 +00:00
bool WalletTransactionSender::validateDestinationAddress(const std::string& address) {
2015-07-30 15:22:07 +00:00
AccountPublicAddress ignore;
2014-08-13 10:51:37 +00:00
return m_currency.parseAccountAddressString(address, ignore);
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
void WalletTransactionSender::validateTransfersAddresses(const std::vector<WalletLegacyTransfer>& transfers) {
for (const WalletLegacyTransfer& tr : transfers) {
2014-08-13 10:51:37 +00:00
if (!validateDestinationAddress(tr.address)) {
2015-07-30 15:22:07 +00:00
throw std::system_error(make_error_code(error::BAD_ADDRESS));
2014-08-13 10:51:37 +00:00
}
}
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
std::shared_ptr<WalletRequest> WalletTransactionSender::makeSendRequest(TransactionId& transactionId, std::deque<std::shared_ptr<WalletLegacyEvent>>& events,
const std::vector<WalletLegacyTransfer>& transfers, uint64_t fee, const std::string& extra, uint64_t mixIn, uint64_t unlockTimestamp) {
2014-06-25 17:21:42 +00:00
2015-05-27 12:08:46 +00:00
using namespace CryptoNote;
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
throwIf(transfers.empty(), error::ZERO_DESTINATION);
2014-08-13 10:51:37 +00:00
validateTransfersAddresses(transfers);
2014-06-25 17:21:42 +00:00
uint64_t neededMoney = countNeededMoney(fee, transfers);
2014-08-13 10:51:37 +00:00
std::shared_ptr<SendTransactionContext> context = std::make_shared<SendTransactionContext>();
2014-06-25 17:21:42 +00:00
context->foundMoney = selectTransfersToSend(neededMoney, 0 == mixIn, context->dustPolicy.dustThreshold, context->selectedTransfers);
2015-07-30 15:22:07 +00:00
throwIf(context->foundMoney < neededMoney, error::WRONG_AMOUNT);
2014-06-25 17:21:42 +00:00
transactionId = m_transactionsCache.addNewTransaction(neededMoney, fee, extra, transfers, unlockTimestamp);
context->transactionId = transactionId;
2014-06-25 17:21:42 +00:00
context->mixIn = mixIn;
if(context->mixIn) {
std::shared_ptr<WalletRequest> request = makeGetRandomOutsRequest(context);
return request;
}
return doSendTransaction(context, events);
}
std::shared_ptr<WalletRequest> WalletTransactionSender::makeGetRandomOutsRequest(std::shared_ptr<SendTransactionContext> context) {
uint64_t outsCount = context->mixIn + 1;// add one to make possible (if need) to skip real output key
std::vector<uint64_t> amounts;
for (const auto& td : context->selectedTransfers) {
amounts.push_back(td.amount);
2014-06-25 17:21:42 +00:00
}
return std::make_shared<WalletGetRandomOutsByAmountsRequest>(amounts, outsCount, context, std::bind(&WalletTransactionSender::sendTransactionRandomOutsByAmount,
this, context, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}
2015-07-30 15:22:07 +00:00
void WalletTransactionSender::sendTransactionRandomOutsByAmount(std::shared_ptr<SendTransactionContext> context, std::deque<std::shared_ptr<WalletLegacyEvent>>& events,
2014-06-25 17:21:42 +00:00
boost::optional<std::shared_ptr<WalletRequest> >& nextRequest, std::error_code ec) {
2014-06-25 17:21:42 +00:00
if (m_isStoping) {
2015-07-30 15:22:07 +00:00
ec = make_error_code(error::TX_CANCELLED);
2014-06-25 17:21:42 +00:00
}
if (ec) {
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, ec));
2014-06-25 17:21:42 +00:00
return;
}
auto scanty_it = std::find_if(context->outs.begin(), context->outs.end(),
2015-07-30 15:22:07 +00:00
[&] (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& out) {return out.outs.size() < context->mixIn;});
2014-06-25 17:21:42 +00:00
if (scanty_it != context->outs.end()) {
2015-07-30 15:22:07 +00:00
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, make_error_code(error::MIXIN_COUNT_TOO_BIG)));
2014-06-25 17:21:42 +00:00
return;
}
std::shared_ptr<WalletRequest> req = doSendTransaction(context, events);
if (req)
nextRequest = req;
}
2015-07-30 15:22:07 +00:00
std::shared_ptr<WalletRequest> WalletTransactionSender::doSendTransaction(std::shared_ptr<SendTransactionContext> context, std::deque<std::shared_ptr<WalletLegacyEvent>>& events) {
2014-06-25 17:21:42 +00:00
if (m_isStoping) {
2015-07-30 15:22:07 +00:00
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, make_error_code(error::TX_CANCELLED)));
2014-06-25 17:21:42 +00:00
return std::shared_ptr<WalletRequest>();
}
try
{
2015-07-30 15:22:07 +00:00
WalletLegacyTransaction& transaction = m_transactionsCache.getTransaction(context->transactionId);
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
std::vector<TransactionSourceEntry> sources;
2014-06-25 17:21:42 +00:00
prepareInputs(context->selectedTransfers, context->outs, sources, context->mixIn);
2015-07-30 15:22:07 +00:00
TransactionDestinationEntry changeDts;
2015-05-27 12:08:46 +00:00
changeDts.amount = 0;
uint64_t totalAmount = -transaction.totalAmount;
2015-07-30 15:22:07 +00:00
createChangeDestinations(m_keys.address, totalAmount, context->foundMoney, changeDts);
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
std::vector<TransactionDestinationEntry> splittedDests;
2014-06-25 17:21:42 +00:00
splitDestinations(transaction.firstTransferId, transaction.transferCount, changeDts, context->dustPolicy, splittedDests);
2015-07-30 15:22:07 +00:00
Transaction tx;
constructTx(m_keys, sources, splittedDests, transaction.extra, transaction.unlockTime, m_upperTransactionSizeLimit, tx);
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
getObjectHash(tx, transaction.hash);
2014-06-25 17:21:42 +00:00
m_transactionsCache.updateTransaction(context->transactionId, tx, totalAmount, context->selectedTransfers);
2014-06-25 17:21:42 +00:00
notifyBalanceChanged(events);
return std::make_shared<WalletRelayTransactionRequest>(tx, std::bind(&WalletTransactionSender::relayTransactionCallback, this, context,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
2014-06-25 17:21:42 +00:00
}
catch(std::system_error& ec) {
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, ec.code()));
2014-06-25 17:21:42 +00:00
}
catch(std::exception&) {
2015-07-30 15:22:07 +00:00
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, make_error_code(error::INTERNAL_WALLET_ERROR)));
2014-06-25 17:21:42 +00:00
}
return std::shared_ptr<WalletRequest>();
}
2015-07-30 15:22:07 +00:00
void WalletTransactionSender::relayTransactionCallback(std::shared_ptr<SendTransactionContext> context, std::deque<std::shared_ptr<WalletLegacyEvent>>& events,
boost::optional<std::shared_ptr<WalletRequest> >& nextRequest, std::error_code ec) {
if (m_isStoping) {
return;
2014-06-25 17:21:42 +00:00
}
events.push_back(makeCompleteEvent(m_transactionsCache, context->transactionId, ec));
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
void WalletTransactionSender::splitDestinations(TransferId firstTransferId, size_t transfersCount, const TransactionDestinationEntry& changeDts,
const TxDustPolicy& dustPolicy, std::vector<TransactionDestinationEntry>& splittedDests) {
2014-06-25 17:21:42 +00:00
uint64_t dust = 0;
digitSplitStrategy(firstTransferId, transfersCount, changeDts, dustPolicy.dustThreshold, splittedDests, dust);
2015-07-30 15:22:07 +00:00
throwIf(dustPolicy.dustThreshold < dust, error::INTERNAL_WALLET_ERROR);
2014-06-25 17:21:42 +00:00
if (0 != dust && !dustPolicy.addToFee) {
2015-07-30 15:22:07 +00:00
splittedDests.push_back(TransactionDestinationEntry(dust, dustPolicy.addrForDust));
2014-06-25 17:21:42 +00:00
}
}
void WalletTransactionSender::digitSplitStrategy(TransferId firstTransferId, size_t transfersCount,
2015-07-30 15:22:07 +00:00
const TransactionDestinationEntry& change_dst, uint64_t dust_threshold,
std::vector<TransactionDestinationEntry>& splitted_dsts, uint64_t& dust) {
2014-06-25 17:21:42 +00:00
splitted_dsts.clear();
dust = 0;
for (TransferId idx = firstTransferId; idx < firstTransferId + transfersCount; ++idx) {
2015-07-30 15:22:07 +00:00
WalletLegacyTransfer& de = m_transactionsCache.getTransfer(idx);
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
AccountPublicAddress addr;
2014-08-13 10:51:37 +00:00
if (!m_currency.parseAccountAddressString(de.address, addr)) {
2015-07-30 15:22:07 +00:00
throw std::system_error(make_error_code(error::BAD_ADDRESS));
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
decompose_amount_into_digits(de.amount, dust_threshold,
[&](uint64_t chunk) { splitted_dsts.push_back(TransactionDestinationEntry(chunk, addr)); },
[&](uint64_t a_dust) { splitted_dsts.push_back(TransactionDestinationEntry(a_dust, addr)); });
2014-06-25 17:21:42 +00:00
}
2015-07-30 15:22:07 +00:00
decompose_amount_into_digits(change_dst.amount, dust_threshold,
[&](uint64_t chunk) { splitted_dsts.push_back(TransactionDestinationEntry(chunk, change_dst.addr)); },
2014-06-25 17:21:42 +00:00
[&](uint64_t a_dust) { dust = a_dust; } );
}
void WalletTransactionSender::prepareInputs(
const std::list<TransactionOutputInformation>& selectedTransfers,
2015-07-30 15:22:07 +00:00
std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount>& outs,
std::vector<TransactionSourceEntry>& sources, uint64_t mixIn) {
2014-06-25 17:21:42 +00:00
size_t i = 0;
for (const auto& td: selectedTransfers) {
2014-06-25 17:21:42 +00:00
sources.resize(sources.size()+1);
2015-07-30 15:22:07 +00:00
TransactionSourceEntry& src = sources.back();
src.amount = td.amount;
2014-06-25 17:21:42 +00:00
//paste mixin transaction
if(outs.size()) {
2015-07-30 15:22:07 +00:00
std::sort(outs[i].outs.begin(), outs[i].outs.end(),
[](const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& a, const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& b){return a.global_amount_index < b.global_amount_index;});
2014-06-25 17:21:42 +00:00
for (auto& daemon_oe: outs[i].outs) {
if(td.globalOutputIndex == daemon_oe.global_amount_index)
continue;
2015-07-30 15:22:07 +00:00
TransactionSourceEntry::OutputEntry oe;
oe.first = static_cast<uint32_t>(daemon_oe.global_amount_index);
2014-06-25 17:21:42 +00:00
oe.second = daemon_oe.out_key;
src.outputs.push_back(oe);
if(src.outputs.size() >= mixIn)
break;
}
}
//paste real transaction to the random index
2015-07-30 15:22:07 +00:00
auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const TransactionSourceEntry::OutputEntry& a) { return a.first >= td.globalOutputIndex; });
2014-06-25 17:21:42 +00:00
2015-07-30 15:22:07 +00:00
TransactionSourceEntry::OutputEntry real_oe;
2014-06-25 17:21:42 +00:00
real_oe.first = td.globalOutputIndex;
2015-07-30 15:22:07 +00:00
real_oe.second = td.outputKey;
2014-06-25 17:21:42 +00:00
auto interted_it = src.outputs.insert(it_to_insert, real_oe);
2015-07-30 15:22:07 +00:00
src.realTransactionPublicKey = td.transactionPublicKey;
src.realOutput = interted_it - src.outputs.begin();
src.realOutputIndexInTransaction = td.outputInTransaction;
2014-06-25 17:21:42 +00:00
++i;
}
}
2015-07-30 15:22:07 +00:00
void WalletTransactionSender::notifyBalanceChanged(std::deque<std::shared_ptr<WalletLegacyEvent>>& events) {
uint64_t unconfirmedOutsAmount = m_transactionsCache.unconfrimedOutsAmount();
uint64_t change = unconfirmedOutsAmount - m_transactionsCache.unconfirmedTransactionsAmount();
uint64_t actualBalance = m_transferDetails.balance(ITransfersContainer::IncludeKeyUnlocked) - unconfirmedOutsAmount;
uint64_t pendingBalance = m_transferDetails.balance(ITransfersContainer::IncludeKeyNotUnlocked) + change;
2014-06-25 17:21:42 +00:00
events.push_back(std::make_shared<WalletActualBalanceUpdatedEvent>(actualBalance));
events.push_back(std::make_shared<WalletPendingBalanceUpdatedEvent>(pendingBalance));
}
namespace {
template<typename URNG, typename T>
T popRandomValue(URNG& randomGenerator, std::vector<T>& vec) {
2015-07-15 12:23:00 +00:00
assert(!vec.empty());
if (vec.empty()) {
return T();
}
std::uniform_int_distribution<size_t> distribution(0, vec.size() - 1);
size_t idx = distribution(randomGenerator);
T res = vec[idx];
if (idx + 1 != vec.size()) {
vec[idx] = vec.back();
}
vec.resize(vec.size() - 1);
return res;
}
}
uint64_t WalletTransactionSender::selectTransfersToSend(uint64_t neededMoney, bool addDust, uint64_t dust, std::list<TransactionOutputInformation>& selectedTransfers) {
std::vector<size_t> unusedTransfers;
std::vector<size_t> unusedDust;
std::vector<TransactionOutputInformation> outputs;
m_transferDetails.getOutputs(outputs, ITransfersContainer::IncludeKeyUnlocked);
for (size_t i = 0; i < outputs.size(); ++i) {
const auto& out = outputs[i];
if (!m_transactionsCache.isUsed(out)) {
if (dust < out.amount)
unusedTransfers.push_back(i);
else
unusedDust.push_back(i);
}
}
2015-07-30 15:22:07 +00:00
std::default_random_engine randomGenerator(Crypto::rand<std::default_random_engine::result_type>());
bool selectOneDust = addDust && !unusedDust.empty();
uint64_t foundMoney = 0;
while (foundMoney < neededMoney && (!unusedTransfers.empty() || !unusedDust.empty())) {
size_t idx;
if (selectOneDust) {
idx = popRandomValue(randomGenerator, unusedDust);
selectOneDust = false;
} else {
idx = !unusedTransfers.empty() ? popRandomValue(randomGenerator, unusedTransfers) : popRandomValue(randomGenerator, unusedDust);
}
selectedTransfers.push_back(outputs[idx]);
foundMoney += outputs[idx].amount;
}
return foundMoney;
}
2014-06-25 17:21:42 +00:00
} /* namespace CryptoNote */