diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 9783da5bc..ba84a3733 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -162,6 +162,44 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) return m_status == Status_Ok; } +std::string PendingTransactionImpl::commit_string() +{ + + std::string tx; + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + + try { + tx = m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); + m_status = Status_Ok; + } catch (const tools::error::daemon_busy&) { + // TODO: make it translatable with "tr"? + m_errorString = tr("daemon is busy. Please try again later."); + m_status = Status_Error; + } catch (const tools::error::no_connection_to_daemon&) { + m_errorString = tr("no connection to daemon. Please make sure daemon is running."); + m_status = Status_Error; + } catch (const tools::error::tx_rejected& e) { + std::ostringstream writer(m_errorString); + writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + m_status = Status_Error; + m_errorString = writer.str(); + if (!reason.empty()) + m_errorString += string(tr(". Reason: ")) + reason; + } catch (const std::exception &e) { + m_errorString = string(tr("Unknown exception: ")) + e.what(); + m_status = Status_Error; + } catch (...) { + m_errorString = tr("Unhandled exception"); + LOG_ERROR(m_errorString); + m_status = Status_Error; + } + m_wallet.startRefresh(); + if (m_status != Status_Ok) + return ""; + return tx; +} + uint64_t PendingTransactionImpl::amount() const { uint64_t result = 0; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 9d8d754c0..0b9b09ece 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -45,6 +45,7 @@ public: ~PendingTransactionImpl(); int status() const override; std::string errorString() const override; + std::string commit_string() override; bool commit(const std::string &filename = "", bool overwrite = false) override; uint64_t amount() const override; uint64_t dust() const override; diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index c549539e5..378e1886f 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -96,6 +96,28 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) return true; } +std::string UnsignedTransactionImpl::signAsString() +{ + if(m_wallet.watchOnly()) + { + m_errorString = tr("This is a watch only wallet"); + m_status = Status_Error; + return ""; + } + tools::wallet2::signed_tx_set signed_txes; + std::vector<tools::wallet2::pending_tx> ptx; + try + { + return m_wallet.m_wallet->sign_tx_dump_to_str(m_unsigned_tx_set, ptx, signed_txes); + } + catch (const std::exception &e) + { + m_errorString = string(tr("Failed to sign transaction")) + e.what(); + m_status = Status_Error; + return ""; + } +} + //---------------------------------------------------------------------------------------------------- bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message) { diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index b07d43fb1..6a4d87b53 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -53,6 +53,7 @@ public: uint64_t txCount() const override; // sign txs and save to file bool sign(const std::string &signedFileName) override; + std::string signAsString() override; std::string confirmationMessage() const override {return m_confirmationMessage;} uint64_t minMixinCount() const override; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index c8257919d..44b386f3d 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1134,6 +1134,27 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file return transaction; } +UnsignedTransaction *WalletImpl::loadUnsignedTxFromString(const std::string &data) { + clearStatus(); + UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); + if (checkBackgroundSync("cannot load tx") || !m_wallet->parse_unsigned_tx_from_str(data, transaction->m_unsigned_tx_set)){ + setStatusError(tr("Failed to load unsigned transactions")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + + return transaction; + } + + // Check tx data and construct confirmation message + std::string extra_message; + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); + transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); + setStatus(transaction->status(), transaction->errorString()); + + return transaction; +} + bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); if (checkBackgroundSync("cannot submit tx")) @@ -1154,6 +1175,48 @@ bool WalletImpl::submitTransaction(const string &fileName) { return true; } +bool WalletImpl::submitTransactionFromString(const string &data) { + clearStatus(); + if (checkBackgroundSync("cannot submit tx")) + return false; + std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this)); + + bool r = m_wallet->parse_tx_from_str(data, transaction->m_pending_tx, NULL); + if (!r) { + setStatus(Status_Ok, tr("Failed to load transaction from string")); + return false; + } + + if(!transaction->commit()) { + setStatusError(transaction->m_errorString); + return false; + } + + return true; +} + +std::string WalletImpl::exportKeyImagesAsString(bool all) +{ + if (m_wallet->watch_only()) + { + setStatusError(tr("Wallet is view only")); + return ""; + } + if (checkBackgroundSync("cannot export key images")) + return ""; + + try + { + return m_wallet->export_key_images_string(all); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + setStatusError(e.what()); + return ""; + } +} + bool WalletImpl::exportKeyImages(const string &filename, bool all) { if (m_wallet->watch_only()) @@ -1181,6 +1244,31 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) return true; } +bool WalletImpl::importKeyImagesFromString(const std::string &data) +{ + if (checkBackgroundSync("cannot import key images")) + return false; + if (!trustedDaemon()) { + setStatusError(tr("Key images can only be imported with a trusted daemon")); + return false; + } + try + { + uint64_t spent = 0, unspent = 0; + uint64_t height = m_wallet->import_key_images_string(data, spent, unspent); + LOG_PRINT_L2("Signed key images imported to height " << height << ", " + << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + setStatusError(string(tr("Failed to import key images: ")) + e.what()); + return false; + } + + return true; +} + bool WalletImpl::importKeyImages(const string &filename) { if (checkBackgroundSync("cannot import key images")) @@ -1206,6 +1294,29 @@ bool WalletImpl::importKeyImages(const string &filename) return true; } +std::string WalletImpl::exportOutputsAsString(bool all) +{ + if (checkBackgroundSync("cannot export outputs")) + return ""; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return ""; + } + + try + { + return m_wallet->export_outputs_to_str(all); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting outputs: " << e.what()); + setStatusError(string(tr("Error exporting outputs: ")) + e.what()); + return ""; + } + return ""; +} + bool WalletImpl::exportOutputs(const string &filename, bool all) { if (checkBackgroundSync("cannot export outputs")) @@ -1238,6 +1349,32 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) return true; } +bool WalletImpl::importOutputsFromString(const std::string &data) +{ + if (checkBackgroundSync("cannot import outputs")) + return false; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return false; + } + + + try + { + size_t n_outputs = m_wallet->import_outputs_from_str(data); + LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to import outputs: " << e.what()); + setStatusError(string(tr("Failed to import outputs: ")) + e.what()); + return false; + } + + return true; +} + bool WalletImpl::importOutputs(const string &filename) { if (checkBackgroundSync("cannot import outputs")) diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index d48d7f130..2364f0741 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -166,10 +166,16 @@ public: std::set<uint32_t> subaddr_indices = {}) override; virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; + bool submitTransactionFromString(const std::string &fileName) override; virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; + virtual UnsignedTransaction * loadUnsignedTxFromString(const std::string &unsigned_filename) override; + std::string exportKeyImagesAsString(bool all = false) override; bool exportKeyImages(const std::string &filename, bool all = false) override; + bool importKeyImagesFromString(const std::string &data) override; bool importKeyImages(const std::string &filename) override; + std::string exportOutputsAsString(bool all = false) override; bool exportOutputs(const std::string &filename, bool all = false) override; + bool importOutputsFromString(const std::string &data) override; bool importOutputs(const std::string &filename) override; bool scanTransactions(const std::vector<std::string> &txids) override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index c374d1574..cd4206e6c 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -89,6 +89,8 @@ struct PendingTransaction virtual ~PendingTransaction() = 0; virtual int status() const = 0; virtual std::string errorString() const = 0; + // return string of transaction gives the same content which would be saved to file with commit(filename) + virtual std::string commit_string() = 0; // commit transaction or save to file if filename is provided. virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; virtual uint64_t amount() const = 0; @@ -161,6 +163,11 @@ struct UnsignedTransaction * return - true on success */ virtual bool sign(const std::string &signedFileName) = 0; + /*! + * @brief sign - Sign txs and return as string + * return - true on success + */ + virtual std::string signAsString() = 0; }; /** @@ -895,11 +902,24 @@ struct Wallet */ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; + /*! + * \brief loadUnsignedTxFromString - creates transaction from unsigned tx string + * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() + * after object returned + */ + virtual UnsignedTransaction * loadUnsignedTxFromString(const std::string &unsigned_filename) = 0; + /*! * \brief submitTransaction - submits transaction in signed tx file * \return - true on success */ virtual bool submitTransaction(const std::string &fileName) = 0; + + /*! + * \brief submitTransactionFromString - submits transaction in signed tx file + * \return - true on success + */ + virtual bool submitTransactionFromString(const std::string &fileName) = 0; /*! @@ -916,6 +936,13 @@ struct Wallet virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, PendingTransaction::Priority priority) const = 0; + /*! + * \brief exportKeyImages - exports key images as string + * \param all - export all key images or only those that have not yet been exported + * \return - key images as std::string + */ + virtual std::string exportKeyImagesAsString(bool all = false) = 0; + /*! * \brief exportKeyImages - exports key images to file * \param filename @@ -923,6 +950,13 @@ struct Wallet * \return - true on success */ virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; + + /*! + * \brief importKeyImagesFromString - imports key images from string for UR use. + * \param data + * \return - true on success + */ + virtual bool importKeyImagesFromString(const std::string &data) = 0; /*! * \brief importKeyImages - imports key images from file @@ -932,12 +966,25 @@ struct Wallet virtual bool importKeyImages(const std::string &filename) = 0; /*! - * \brief importOutputs - exports outputs to file + * \brief exportOutputsAsString - exports outputs to a string for UR + * \return - true on success + */ + virtual std::string exportOutputsAsString(bool all = false) = 0; + + /*! + * \brief exportOutputs - exports outputs to file * \param filename * \return - true on success */ virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; + /*! + * \brief importOutputsFromString - imports outputs from string for UR + * \param filename + * \return - true on success + */ + virtual bool importOutputsFromString(const std::string &data) = 0; + /*! * \brief importOutputs - imports outputs from file * \param filename diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index fa9c51bb2..0bf3ff8c1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13262,9 +13262,8 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle return tx_pub_key; } -bool wallet2::export_key_images(const std::string &filename, bool all) const +std::string wallet2::export_key_images_string(bool all) const { - PERF_TIMER(export_key_images); std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images(all); std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; @@ -13287,8 +13286,13 @@ bool wallet2::export_key_images(const std::string &filename, bool all) const // encrypt data, keep magic plaintext PERF_TIMER(export_key_images_encrypt); - std::string ciphertext = encrypt_with_view_secret_key(data); - return save_to_file(filename, magic + ciphertext); + return magic + encrypt_with_view_secret_key(data); +} + +bool wallet2::export_key_images(const std::string &filename, bool all) const +{ + PERF_TIMER(export_key_images); + return save_to_file(filename, export_key_images_string(all)); } //---------------------------------------------------------------------------------------------------- @@ -13353,10 +13357,16 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + return import_key_images_string(data, spent, unspent); +} + +uint64_t wallet2::import_key_images_string(const std::string &keyImages, uint64_t &spent, uint64_t &unspent) +{ + std::string data = keyImages; const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic")); } try @@ -13366,24 +13376,24 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt: ") + e.what()); } const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); - THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size")); const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images are from a different account" )); } THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, - error::wallet_internal_error, std::string("Bad data size from file ") + filename); + error::wallet_internal_error, std::string("Bad data size")); size_t nki = (data.size() - headerlen) / record_size; std::vector<std::pair<crypto::key_image, crypto::signature>> ski; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7228f1907..1da82e734 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1630,10 +1630,12 @@ private: void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments); std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const; void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc); + std::string export_key_images_string(bool all = false) const; bool export_key_images(const std::string &filename, bool all = false) const; std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const; uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); + uint64_t import_key_images_string(const std::string &data, uint64_t &spent, uint64_t &unspent); bool import_key_images(std::vector<crypto::key_image> key_images, size_t offset=0, boost::optional<std::unordered_set<size_t>> selected_transfers=boost::none); bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;