diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/pending_transaction.cpp | 19 | ||||
-rw-r--r-- | src/wallet/api/pending_transaction.h | 2 | ||||
-rw-r--r-- | src/wallet/api/transaction_history.cpp | 2 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 165 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 9 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 153 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 182 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 60 | ||||
-rw-r--r-- | src/wallet/wallet2_api.h | 65 |
10 files changed, 607 insertions, 51 deletions
diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 26ce9fc7e..2521decea 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -69,11 +69,19 @@ string PendingTransactionImpl::errorString() const return m_errorString; } +std::vector<std::string> PendingTransactionImpl::txid() const +{ + std::vector<std::string> txid; + for (const auto &pt: m_pending_tx) + txid.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(pt.tx))); + return txid; +} + bool PendingTransactionImpl::commit() { - LOG_PRINT_L0("m_pending_tx size: " << m_pending_tx.size()); - assert(m_pending_tx.size() == 1); + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + try { while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); @@ -128,11 +136,16 @@ uint64_t PendingTransactionImpl::dust() const uint64_t PendingTransactionImpl::fee() const { uint64_t result = 0; - for (const auto ptx : m_pending_tx) { + for (const auto &ptx : m_pending_tx) { result += ptx.fee; } return result; } +uint64_t PendingTransactionImpl::txCount() const +{ + return m_pending_tx.size(); +} + } diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 8e09bec91..c5e847c97 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -49,6 +49,8 @@ public: uint64_t amount() const; uint64_t dust() const; uint64_t fee() const; + std::vector<std::string> txid() const; + uint64_t txCount() const; // TODO: continue with interface; private: diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 2ba5f3620..63c4ea3cc 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -157,7 +157,7 @@ void TransactionHistoryImpl::refresh() const tools::wallet2::confirmed_transfer_details &pd = i->second; uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known - uint64_t fee = pd.m_amount_in - pd.m_amount_out - change; + uint64_t fee = pd.m_amount_in - pd.m_amount_out; std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 9a9638b40..6c1c1fea2 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -545,6 +545,8 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const { clearStatus(); + // Pause refresh thread while creating transaction + pauseRefresh(); vector<cryptonote::tx_destination_entry> dsts; cryptonote::tx_destination_entry de; @@ -678,6 +680,100 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const transaction->m_status = m_status; transaction->m_errorString = m_errorString; + // Resume refresh thread + startRefresh(); + return transaction; +} + +PendingTransaction *WalletImpl::createSweepUnmixableTransaction() + +{ + clearStatus(); + vector<cryptonote::tx_destination_entry> dsts; + cryptonote::tx_destination_entry de; + + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + do { + try { + transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(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, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + + } catch (const tools::error::tx_not_possible& 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 std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; + } + 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; } @@ -707,6 +803,63 @@ void WalletImpl::setDefaultMixin(uint32_t arg) m_wallet->default_mixin(arg); } +bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + return false; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + m_wallet->set_tx_note(htxid, note); + return true; +} + +std::string WalletImpl::getUserNote(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + return ""; + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + return m_wallet->get_tx_note(htxid); +} + +std::string WalletImpl::getTxKey(const std::string &txid) const +{ + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data)) + { + return ""; + } + const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + crypto::secret_key tx_key; + if (m_wallet->get_tx_key(htxid, tx_key)) + { + return epee::string_tools::pod_to_hex(tx_key); + } + else + { + return ""; + } +} + +std::string WalletImpl::signMessage(const std::string &message) +{ + return m_wallet->sign(message); +} + +bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const +{ + cryptonote::account_public_address addr; + bool has_payment_id; + crypto::hash8 payment_id; + + if (!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id, m_wallet->testnet(), address)) + return false; + + return m_wallet->verify(message, addr, signature); +} bool WalletImpl::connectToDaemon() { @@ -720,9 +873,15 @@ bool WalletImpl::connectToDaemon() return result; } -bool WalletImpl::connected() const +Wallet::ConnectionStatus WalletImpl::connected() const { - return m_wallet->check_connection(); + bool same_version = false; + bool is_connected = m_wallet->check_connection(&same_version); + if (!is_connected) + return Wallet::ConnectionStatus_Disconnected; + if (!same_version) + return Wallet::ConnectionStatus_WrongVersion; + return Wallet::ConnectionStatus_Connected; } void WalletImpl::setTrustedDaemon(bool arg) @@ -798,6 +957,7 @@ void WalletImpl::doRefresh() void WalletImpl::startRefresh() { + LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed..."); if (!m_refreshEnabled) { m_refreshEnabled = true; m_refreshCV.notify_one(); @@ -818,6 +978,7 @@ void WalletImpl::stopRefresh() void WalletImpl::pauseRefresh() { + LOG_PRINT_L2(__FUNCTION__ << ": refresh paused..."); // TODO synchronize access if (!m_refreshThreadDone) { m_refreshEnabled = false; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index c8a59f7c3..f40551fac 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -70,7 +70,7 @@ public: bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit); void initAsync(const std::string &daemon_address, uint64_t upper_transaction_size_limit); bool connectToDaemon(); - bool connected() const; + ConnectionStatus connected() const; void setTrustedDaemon(bool arg); bool trustedDaemon() const; uint64_t balance() const; @@ -91,12 +91,19 @@ public: PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, uint64_t amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low); + virtual PendingTransaction * createSweepUnmixableTransaction(); virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; virtual void setListener(WalletListener * l); virtual uint32_t defaultMixin() const; virtual void setDefaultMixin(uint32_t arg); + virtual bool setUserNote(const std::string &txid, const std::string ¬e); + virtual std::string getUserNote(const std::string &txid) const; + virtual std::string getTxKey(const std::string &txid) const; + + virtual std::string signMessage(const std::string &message); + virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; private: void clearStatus(); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index d2395ace1..2d1c44d0e 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -31,6 +31,8 @@ #include "wallet_manager.h" #include "wallet.h" +#include "common_defines.h" +#include "net/http_client.h" #include <boost/filesystem.hpp> #include <boost/regex.hpp> @@ -133,6 +135,157 @@ void WalletManagerImpl::setDaemonHost(const std::string &hostname) } +bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const +{ + error = ""; + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data)) + { + error = tr("failed to parse txid"); + return false; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + if (txkey_text.size() < 64 || txkey_text.size() % 64) + { + error = tr("failed to parse tx key"); + return false; + } + crypto::secret_key tx_key; + cryptonote::blobdata tx_key_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data)) + { + error = tr("failed to parse tx key"); + return false; + } + tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); + + bool testnet = address_text[0] != '4'; + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id; + if(!cryptonote::get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_text)) + { + error = tr("failed to parse address"); + return false; + } + + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + epee::net_utils::http::http_simple_client http_client; + if (!epee::net_utils::invoke_http_json_remote_command2(daemon_address + "/gettransactions", req, res, http_client) || + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + { + error = tr("failed to get transaction from daemon"); + return false; + } + cryptonote::blobdata tx_data; + bool ok; + if (res.txs.size() == 1) + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) + { + error = tr("failed to parse transaction from daemon"); + return false; + } + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + { + error = tr("failed to validate transaction from daemon"); + return false; + } + if (tx_hash != txid) + { + error = tr("failed to get the right transaction from daemon"); + return false; + } + + crypto::key_derivation derivation; + if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + { + error = tr("failed to generate key derivation from supplied parameters"); + return false; + } + + received = 0; + try { + for (size_t n = 0; n < tx.vout.size(); ++n) + { + if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) + continue; + const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); + crypto::public_key pubkey; + derive_public_key(derivation, n, address.m_spend_public_key, pubkey); + if (pubkey == tx_out_to_key.key) + { + uint64_t amount; + if (tx.version == 1) + { + amount = tx.vout[n].amount; + } + else + { + try + { + rct::key Ctmp; + //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); + if (!r) + { + LOG_ERROR("Failed to generate key derivation to decode rct output " << n); + amount = 0; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + rct::key C = tx.rct_signatures.outPk[n].mask; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + } + catch (...) { amount = 0; } + } + received += amount; + } + } + } + catch(const std::exception &e) + { + LOG_ERROR("error: " << e.what()); + error = std::string(tr("error: ")) + e.what(); + return false; + } + + if (received > 0) + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); + } + else + { + LOG_PRINT_L1(get_account_address_as_str(testnet, address) << " " << tr("received nothing in txid") << " " << txid); + } + if (res.txs.front().in_pool) + { + height = 0; + } + else + { + height = res.txs.front().block_height; + } + + return true; +} ///////////////////// WalletManagerFactory implementation ////////////////////// diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 2e932a2a1..489abe764 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -46,6 +46,7 @@ public: std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; void setDaemonHost(const std::string &hostname); + bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; private: WalletManagerImpl() {} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8ea605375..ac8802ca4 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -80,6 +80,8 @@ using namespace cryptonote; #define RECENT_OUTPUT_RATIO (0.25) // 25% of outputs are from the recent zone #define RECENT_OUTPUT_ZONE (5 * 86400) // last 5 days are the recent zone +#define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks + #define KILL_IOSERVICE() \ do { \ work.reset(); \ @@ -195,7 +197,7 @@ void wallet2::set_unspent(size_t idx) td.m_spent_height = 0; } //---------------------------------------------------------------------------------------------------- -void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const +void wallet2::check_acc_out_precomp(const crypto::public_key &spend_public_key, const tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const { if (o.target.type() != typeid(txout_to_key)) { @@ -203,7 +205,7 @@ void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const cryp LOG_ERROR("wrong type id in transaction out"); return; } - received = is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i); + received = is_out_to_acc_precomp(spend_public_key, boost::get<txout_to_key>(o.target), derivation, i); if(received) { money_transfered = o.amount; // may be 0 for ringct outputs @@ -250,8 +252,6 @@ bool wallet2::wallet_generate_key_image_helper(const cryptonote::account_keys& a { if (!cryptonote::generate_key_image_helper(ack, tx_public_key, real_output_index, in_ephemeral, ki)) return false; - if (m_watch_only) - memset(&ki, 0, 32); return true; } //---------------------------------------------------------------------------------------------------- @@ -308,6 +308,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s std::deque<uint64_t> amount(tx.vout.size()); std::deque<rct::key> mask(tx.vout.size()); int threads = tools::get_max_concurrency(); + const cryptonote::account_keys& keys = m_account.get_keys(); + crypto::key_derivation derivation; + generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { // assume coinbase isn't for us @@ -316,7 +319,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[0], derivation, 0, received, money_transfered, error); if (error) { r = false; @@ -326,14 +329,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s // this assumes that the miner tx pays a single address if (received) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, 0, in_ephemeral[0], ki[0]); + wallet_generate_key_image_helper(keys, tx_pub_key, 0, in_ephemeral[0], ki[0]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[0].pub != boost::get<cryptonote::txout_to_key>(tx.vout[0].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(0); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, 0, mask[0]); } amount[0] = money_transfered; @@ -349,14 +351,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); // the first one was already checked for (size_t i = 1; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -369,14 +370,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -397,13 +397,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); } - const account_keys &keys = m_account.get_keys(); std::vector<uint64_t> money_transfered(tx.vout.size()); std::deque<bool> error(tx.vout.size()); std::deque<bool> received(tx.vout.size()); for (size_t i = 0; i < tx.vout.size(); ++i) { - ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + ioservice.dispatch(boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(keys.m_account_address.m_spend_public_key), std::cref(tx.vout[i]), std::cref(derivation), i, std::ref(received[i]), std::ref(money_transfered[i]), std::ref(error[i]))); } KILL_IOSERVICE(); @@ -417,14 +416,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } if (received[i]) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered[i] == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered[i] = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } tx_money_got_in_outs += money_transfered[i]; @@ -439,7 +437,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { uint64_t money_transfered = 0; bool error = false, received = false; - check_acc_out(m_account.get_keys(), tx.vout[i], tx_pub_key, i, received, money_transfered, error); + check_acc_out_precomp(keys.m_account_address.m_spend_public_key, tx.vout[i], derivation, i, received, money_transfered, error); if (error) { r = false; @@ -449,14 +447,13 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s { if (received) { - wallet_generate_key_image_helper(m_account.get_keys(), tx_pub_key, i, in_ephemeral[i], ki[i]); + wallet_generate_key_image_helper(keys, tx_pub_key, i, in_ephemeral[i], ki[i]); THROW_WALLET_EXCEPTION_IF(in_ephemeral[i].pub != boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); outs.push_back(i); if (money_transfered == 0) { - const cryptonote::account_keys& keys = m_account.get_keys(); money_transfered = tools::decodeRct(tx.rct_signatures, pub_key_field.pub_key, keys.m_view_secret_key, i, mask[i]); } amount[i] = money_transfered; @@ -484,12 +481,12 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s THROW_WALLET_EXCEPTION_IF(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); - auto kit = m_key_images.find(ki[o]); - THROW_WALLET_EXCEPTION_IF(kit != m_key_images.end() && kit->second >= m_transfers.size(), - error::wallet_internal_error, std::string("Unexpected transfer index from key image: ") - + "got " + (kit == m_key_images.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + auto kit = m_pub_keys.find(in_ephemeral[o].pub); + THROW_WALLET_EXCEPTION_IF(kit != m_pub_keys.end() && kit->second >= m_transfers.size(), + error::wallet_internal_error, std::string("Unexpected transfer index from public key: ") + + "got " + (kit == m_pub_keys.end() ? "<none>" : boost::lexical_cast<std::string>(kit->second)) + ", m_transfers.size() is " + boost::lexical_cast<std::string>(m_transfers.size())); - if (kit == m_key_images.end()) + if (kit == m_pub_keys.end()) { if (!pool) { @@ -501,6 +498,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid(); td.m_key_image = ki[o]; + td.m_key_image_known = !m_watch_only; td.m_amount = tx.vout[o].amount; if (td.m_amount == 0) { @@ -520,6 +518,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } set_unspent(m_transfers.size()-1); m_key_images[td.m_key_image] = m_transfers.size()-1; + m_pub_keys[in_ephemeral[o].pub] = m_transfers.size()-1; LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); if (0 != m_callback) m_callback->on_money_received(height, tx, td.m_amount); @@ -527,14 +526,14 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx.vout[o].amount) { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(kit->first) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << (m_transfers[kit->second].m_spent ? "spent" : "unspent") << " " << print_money(m_transfers[kit->second].amount()) << ", received output ignored"); } else { - LOG_ERROR("key image " << epee::string_tools::pod_to_hex(kit->first) + LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) << " from received " << print_money(tx.vout[o].amount) << " output already exists with " << print_money(m_transfers[kit->second].amount()) << ", replacing with new output"); // The new larger output replaced a previous smaller one @@ -565,7 +564,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, const s td.m_mask = rct::identity(); td.m_rct = false; } - THROW_WALLET_EXCEPTION_IF(td.m_key_image != ki[o], error::wallet_internal_error, "Inconsistent key images"); + THROW_WALLET_EXCEPTION_IF(td.get_public_key() != in_ephemeral[o].pub, error::wallet_internal_error, "Inconsistent public keys"); THROW_WALLET_EXCEPTION_IF(td.m_spent, error::wallet_internal_error, "Inconsistent spent status"); LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid()); @@ -704,6 +703,17 @@ void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t heigh else entry.first->second.m_amount_out = spent - tx.rct_signatures.txnFee; entry.first->second.m_change = received; + + std::vector<tx_extra_field> tx_extra_fields; + if(parse_tx_extra(tx.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + // we do not care about failure here + get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, entry.first->second.m_payment_id); + } + } } entry.first->second.m_block_height = height; entry.first->second.m_timestamp = ts; @@ -1334,7 +1344,13 @@ void wallet2::detach_blockchain(uint64_t height) auto it_ki = m_key_images.find(m_transfers[i].m_key_image); THROW_WALLET_EXCEPTION_IF(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); m_key_images.erase(it_ki); - ++transfers_detached; + } + + for(size_t i = i_start; i!= m_transfers.size();i++) + { + auto it_pk = m_pub_keys.find(m_transfers[i].get_public_key()); + THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); + m_pub_keys.erase(it_pk); } m_transfers.erase(it, m_transfers.end()); @@ -1371,6 +1387,7 @@ bool wallet2::clear() m_blockchain.clear(); m_transfers.clear(); m_key_images.clear(); + m_pub_keys.clear(); m_unconfirmed_txs.clear(); m_payments.clear(); m_tx_keys.clear(); @@ -2147,13 +2164,11 @@ void wallet2::rescan_spent() std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(key_images.size())); // update spent status - key_image zero_ki; - memset(&zero_ki, 0, 32); for (size_t i = 0; i < m_transfers.size(); ++i) { transfer_details& td = m_transfers[i]; // a view wallet may not know about key images - if (td.m_key_image == zero_ki) + if (!td.m_key_image_known) continue; if (td.m_spent != (daemon_resp.spent_status[i] != COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT)) { @@ -2355,6 +2370,7 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo utd.m_amount_out = 0; for (const auto &d: dests) utd.m_amount_out += d.amount; + utd.m_amount_out += change_amount; utd.m_change = change_amount; utd.m_sent_time = time(NULL); utd.m_tx = (const cryptonote::transaction_prefix&)tx; @@ -2753,6 +2769,40 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, bool use_new_fee) const return 1; } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_dynamic_per_kb_fee_estimate() +{ + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_fee_estimate"; + req_t.params.grace_blocks = FEE_ESTIMATE_GRACE_BLOCKS; + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, "Failed to connect to daemon"); + CHECK_AND_ASSERT_THROW_MES(resp_t.result.status == CORE_RPC_STATUS_OK, "Failed to get fee estimate"); + return resp_t.result.fee; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_per_kb_fee() +{ + bool use_dyn_fee = use_fork_rules(HF_VERSION_DYNAMIC_FEE, -720 * 14); + if (!use_dyn_fee) + return FEE_PER_KB; + try + { + return get_dynamic_per_kb_fee_estimate(); + } + catch (...) + { + LOG_PRINT_L1("Failed to query per kB fee, using " << print_money(FEE_PER_KB)); + return FEE_PER_KB; + } +} +//---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // // this function will make multiple calls to wallet2::transfer if multiple @@ -2762,7 +2812,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto const std::vector<size_t> unused_transfers_indices = select_available_outputs_from_histogram(fake_outs_count + 1, true, true, trusted_daemon); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); // failsafe split attempt counter @@ -3483,7 +3533,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const bool use_rct = use_fork_rules(4, 0); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); // throw if attempting a transaction with no destinations @@ -3765,7 +3815,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); const uint64_t fee_multiplier = get_fee_multiplier(priority, use_new_fee); LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); @@ -4071,7 +4121,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bo tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); const bool use_new_fee = use_fork_rules(3, -720 * 14); - const uint64_t fee_per_kb = use_new_fee ? FEE_PER_KB : FEE_PER_KB_OLD; + const uint64_t fee_per_kb = get_per_kb_fee(); // may throw std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); @@ -4264,10 +4314,7 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); - bool zero_key_image = true; - for (size_t i = 0; i < sizeof(td.m_key_image); ++i) - zero_key_image &= (td.m_key_image.data[i] == 0); - THROW_WALLET_EXCEPTION_IF(!zero_key_image && ki != td.m_key_image, + THROW_WALLET_EXCEPTION_IF(td.m_key_image_known && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -4323,7 +4370,10 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag } for (size_t n = 0; n < signed_key_images.size(); ++n) + { m_transfers[n].m_key_image = signed_key_images[n].first; + m_transfers[n].m_key_image_known = true; + } m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/is_key_image_spent", req, daemon_resp, m_http_client, 200000); @@ -4389,6 +4439,7 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail "Public key wasn't found in the transaction extra at index " + i); cryptonote::generate_key_image_helper(m_account.get_keys(), pub_key_field.pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image); + td.m_key_image_known = true; THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + i); @@ -4398,6 +4449,61 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const +{ + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + std::string ciphertext; + crypto::chacha8_iv iv = crypto::rand<crypto::chacha8_iv>(); + ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); + crypto::chacha8(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + memcpy(&ciphertext[0], &iv, sizeof(iv)); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + crypto::signature &signature = *(crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + crypto::generate_signature(hash, pkey, skey, signature); + } + return std::move(ciphertext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated) const +{ + return encrypt(plaintext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const +{ + THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(chacha8_iv), + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(&skey, sizeof(skey), key); + const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + std::string plaintext; + plaintext.resize(ciphertext.size() - sizeof(iv) - (authenticated ? sizeof(crypto::signature) : 0)); + if (authenticated) + { + crypto::hash hash; + crypto::cn_fast_hash(ciphertext.data(), ciphertext.size() - sizeof(signature), hash); + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + const crypto::signature &signature = *(const crypto::signature*)&ciphertext[ciphertext.size() - sizeof(crypto::signature)]; + THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), + error::wallet_internal_error, "Failed to authenticate criphertext"); + } + crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - sizeof(iv), key, iv, &plaintext[0]); + return std::move(plaintext); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated) const +{ + return decrypt(ciphertext, get_account().get_keys().m_view_secret_key, authenticated); +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6cd288ac1..3c4b1015f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -109,9 +109,11 @@ namespace tools rct::key m_mask; uint64_t m_amount; bool m_rct; + bool m_key_image_known; bool is_rct() const { return m_rct; } uint64_t amount() const { return m_amount; } + const crypto::public_key &get_public_key() const { return boost::get<const cryptonote::txout_to_key>(m_tx.vout[m_internal_output_index].target).key; } }; struct payment_details @@ -409,6 +411,19 @@ namespace tools a & m_unconfirmed_payments; if(ver < 14) return; + if(ver < 15) + { + // we're loading an older wallet without a pubkey map, rebuild it + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details &td = m_transfers[i]; + const cryptonote::tx_out &out = td.m_tx.vout[td.m_internal_output_index]; + const cryptonote::txout_to_key &o = boost::get<const cryptonote::txout_to_key>(out.target); + m_pub_keys.emplace(o.key, i); + } + return; + } + a & m_pub_keys; } /*! @@ -480,6 +495,12 @@ namespace tools uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent); void update_pool_state(); + + std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; + std::string decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated = true) const; + std::string decrypt_with_view_secret_key(const std::string &ciphertext, bool authenticated = true) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -515,11 +536,13 @@ namespace tools void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; - void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; + void check_acc_out_precomp(const crypto::public_key &spend_public_key, const cryptonote::tx_out &o, const crypto::key_derivation &derivation, size_t i, bool &received, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> get_unspent_amounts_vector(); uint64_t get_fee_multiplier(uint32_t priority, bool use_new_fee) const; + uint64_t get_dynamic_per_kb_fee_estimate(); + uint64_t get_per_kb_fee(); float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; void set_spent(size_t idx, uint64_t height); @@ -543,6 +566,7 @@ namespace tools transfer_container m_transfers; payment_container m_payments; std::unordered_map<crypto::key_image, size_t> m_key_images; + std::unordered_map<crypto::public_key, size_t> m_pub_keys; cryptonote::account_public_address m_account_public_address; std::unordered_map<crypto::hash, std::string> m_tx_notes; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value @@ -567,11 +591,11 @@ namespace tools bool m_confirm_missing_payment_id; }; } -BOOST_CLASS_VERSION(tools::wallet2, 14) -BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 4) +BOOST_CLASS_VERSION(tools::wallet2, 15) +BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 5) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 5) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 6) +BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 3) namespace boost { @@ -597,6 +621,7 @@ namespace boost { x.m_rct = x.m_tx.vout[x.m_internal_output_index].amount == 0; } + x.m_key_image_known = true; } template <class Archive> @@ -644,6 +669,9 @@ namespace boost return; } a & x.m_rct; + if (ver < 5) + return; + a & x.m_key_image_known; } template <class Archive> @@ -675,6 +703,14 @@ namespace boost return; a & x.m_amount_in; a & x.m_amount_out; + if (ver < 6) + { + // v<6 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. + // We convert it to include change from v6 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + x.m_amount_out += x.m_change; + } } template <class Archive> @@ -691,6 +727,20 @@ namespace boost if (ver < 2) return; a & x.m_timestamp; + if (ver < 3) + { + // v<3 may not have change accumulated in m_amount_out, which is a pain, + // as it's readily understood to be sum of outputs. Whether it got added + // or not depends on whether it came from a unconfirmed_transfer_details + // (not included) or not (included). We can't reliably tell here, so we + // check whether either yields a "negative" fee, or use the other if so. + // We convert it to include change from v3 + if (!typename Archive::is_saving() && x.m_change != (uint64_t)-1) + { + if (x.m_amount_in > (x.m_amount_out + x.m_change)) + x.m_amount_out += x.m_change; + } + } } template <class Archive> diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8427ba250..f0a9ea68b 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -65,6 +65,12 @@ struct PendingTransaction virtual uint64_t amount() const = 0; virtual uint64_t dust() const = 0; virtual uint64_t fee() const = 0; + virtual std::vector<std::string> txid() const = 0; + /*! + * \brief txCount - number of transactions current transaction will be splitted to + * \return + */ + virtual uint64_t txCount() const = 0; }; /** @@ -159,6 +165,12 @@ struct Wallet Status_Error }; + enum ConnectionStatus { + ConnectionStatus_Disconnected, + ConnectionStatus_Connected, + ConnectionStatus_WrongVersion + }; + virtual ~Wallet() = 0; virtual std::string seed() const = 0; virtual std::string getSeedLanguage() const = 0; @@ -243,7 +255,7 @@ struct Wallet * @brief connected - checks if the wallet connected to the daemon * @return - true if connected */ - virtual bool connected() const = 0; + virtual ConnectionStatus connected() const = 0; virtual void setTrustedDaemon(bool arg) = 0; virtual bool trustedDaemon() const = 0; virtual uint64_t balance() const = 0; @@ -324,6 +336,14 @@ struct Wallet PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0; /*! + * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. + * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() + * after object returned + */ + + virtual PendingTransaction * createSweepUnmixableTransaction() = 0; + + /*! * \brief disposeTransaction - destroys transaction object * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; */ @@ -340,6 +360,36 @@ struct Wallet * \param arg */ virtual void setDefaultMixin(uint32_t arg) = 0; + + /*! + * \brief setUserNote - attach an arbitrary string note to a txid + * \param txid - the transaction id to attach the note to + * \param note - the note + * \return true if succesful, false otherwise + */ + virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; + /*! + * \brief getUserNote - return an arbitrary string note attached to a txid + * \param txid - the transaction id to attach the note to + * \return the attached note, or empty string if there is none + */ + virtual std::string getUserNote(const std::string &txid) const = 0; + virtual std::string getTxKey(const std::string &txid) const = 0; + + /* + * \brief signMessage - sign a message with the spend private key + * \param message - the message to sign (arbitrary byte data) + * \return the signature + */ + virtual std::string signMessage(const std::string &message) = 0; + /*! + * \brief verifySignedMessage - verify a signature matches a given message + * \param message - the message (arbitrary byte data) + * \param address - the address the signature claims to be made with + * \param signature - the signature + * \return true if the signature verified, false otherwise + */ + virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; }; /** @@ -400,6 +450,19 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; + /*! + * \brief checkPayment - checks a payment was made using a txkey + * \param address - the address the payment was sent to + * \param txid - the transaction id for that payment + * \param txkey - the transaction's secret key + * \param daemon_address - the address (host and port) to the daemon to request transaction data + * \param received - if succesful, will hold the amount of monero received + * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) + * \param error - if unsuccesful, will hold an error string with more information about the error + * \return - true is succesful, false otherwise + */ + virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; + //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; |