oxen-core/src/wallet/api/wallet.cpp

2765 lines
84 KiB
C++
Raw Normal View History

// Copyright (c) 2014-2019, The Monero Project
2018-04-10 06:49:20 +02:00
// Copyright (c) 2018, The Loki Project
//
2016-02-15 19:04:00 +01:00
// All rights reserved.
//
2016-02-15 19:04:00 +01:00
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
2016-02-15 19:04:00 +01:00
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
2016-02-15 19:04:00 +01:00
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
2016-02-15 19:04:00 +01:00
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
2016-02-15 19:04:00 +01:00
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
2016-02-15 19:04:00 +01:00
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#ifdef _WIN32
2021-01-04 01:09:45 +01:00
#define __STDC_FORMAT_MACROS // NOTE(oxen): Explicitly define the PRIu64 macro on Mingw
#endif
2016-04-03 13:34:38 +02:00
#include "wallet.h"
#include "pending_transaction.h"
2017-01-08 13:17:09 +01:00
#include "unsigned_transaction.h"
#include "stake_unlock_result.h"
#include "transaction_history.h"
2016-12-12 00:42:46 +01:00
#include "address_book.h"
2017-02-19 03:42:10 +01:00
#include "subaddress.h"
#include "subaddress_account.h"
#include "common_defines.h"
2017-08-09 12:38:29 +02:00
#include "common/util.h"
#include "common/fs.h"
2016-04-03 13:34:38 +02:00
#include "mnemonics/electrum-words.h"
#include "mnemonics/english.h"
2016-04-03 13:34:38 +02:00
#include <boost/format.hpp>
#include <sstream>
2016-12-12 00:42:46 +01:00
#include <unordered_map>
2020-01-15 05:17:26 +01:00
#include <thread>
2016-04-03 13:34:38 +02:00
using namespace cryptonote;
2016-02-20 17:04:56 +01:00
2021-01-04 04:19:42 +01:00
#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "WalletAPI"
2017-02-05 20:12:01 +01:00
namespace Wallet {
2016-02-15 19:04:00 +01:00
namespace {
static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10;
// limit maximum refresh interval as one minute
static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1;
// Default refresh interval when connected to remote node
static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10;
2017-01-14 14:41:56 +01:00
// Connection timeout 30 sec
static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30;
2018-03-04 14:32:35 +01:00
fs::path get_default_ringdb_path(cryptonote::network_type nettype)
2018-03-04 14:32:35 +01:00
{
auto dir = tools::get_default_data_dir();
2021-01-04 01:09:45 +01:00
// remove .oxen, replace with .shared-ringdb
dir.replace_filename(".shared-ringdb");
2018-03-27 05:08:28 +02:00
if (nettype == cryptonote::TESTNET)
dir /= "testnet";
2020-08-11 23:41:54 +02:00
else if (nettype == cryptonote::DEVNET)
dir /= "devnet";
return dir;
2018-03-04 14:32:35 +01:00
}
void checkMultisigWalletReady(LockedWallet& wallet) {
if (!wallet.wallet)
throw std::runtime_error("Wallet is not initialized yet");
bool ready;
if (!wallet->multisig(&ready))
throw std::runtime_error("Wallet is not multisig");
if (!ready)
throw std::runtime_error("Multisig wallet is not finalized yet");
}
void checkMultisigWalletNotReady(LockedWallet& wallet) {
if (!wallet.wallet)
throw std::runtime_error("Wallet is not initialized yet");
bool ready;
if (!wallet->multisig(&ready))
throw std::runtime_error("Wallet is not multisig");
if (ready)
throw std::runtime_error("Multisig wallet is already finalized");
}
2016-04-03 13:34:38 +02:00
}
2016-02-20 17:04:56 +01:00
2016-05-05 21:24:00 +02:00
struct Wallet2CallbackImpl : public tools::i_wallet2_callback
{
EXPORT
Wallet2CallbackImpl(WalletImpl * wallet)
2016-05-16 12:11:44 +02:00
: m_listener(nullptr)
, m_wallet(wallet)
2016-05-16 12:11:44 +02:00
{
}
EXPORT
2016-05-05 21:24:00 +02:00
~Wallet2CallbackImpl()
{
}
EXPORT
2016-05-05 21:24:00 +02:00
void setListener(WalletListener * listener)
{
m_listener = listener;
2016-05-05 21:24:00 +02:00
}
EXPORT
2016-05-05 21:24:00 +02:00
WalletListener * getListener() const
{
return m_listener;
}
EXPORT
void on_new_block(uint64_t height, const cryptonote::block& block) override
2016-05-05 21:24:00 +02:00
{
2017-01-30 18:00:10 +01:00
// Don't flood the GUI with signals. On fast refresh - send signal every 1000th block
// get_refresh_from_block_height() returns the blockheight from when the wallet was
// created or the restore height specified when wallet was recovered
//
if(height >= m_wallet->m_wallet_ptr->get_refresh_from_block_height() || height % 1000 == 0) {
2017-01-30 18:00:10 +01:00
// LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height);
if (m_listener) {
m_listener->newBlock(height);
}
}
2016-05-05 21:24:00 +02:00
}
EXPORT
void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index, uint64_t unlock_time, bool blink) override
2016-05-05 21:24:00 +02:00
{
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
2020-01-13 07:50:22 +01:00
LOG_PRINT_L3(__FUNCTION__ << ": money received." << (blink ? "blink: " : "height: ") << height
<< ", tx: " << tx_hash
2017-02-19 03:42:10 +01:00
<< ", amount: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneyReceived(tx_hash, amount);
2016-07-10 16:17:23 +02:00
m_listener->updated();
}
2016-05-05 21:24:00 +02:00
}
EXPORT
void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) override
{
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": unconfirmed money received. height: " << height
<< ", tx: " << tx_hash
2017-02-19 03:42:10 +01:00
<< ", amount: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
m_listener->updated();
}
2016-05-05 21:24:00 +02:00
}
EXPORT
void on_money_spent(uint64_t height,
const crypto::hash &txid,
const cryptonote::transaction &in_tx,
uint64_t amount,
const cryptonote::transaction &spend_tx,
const cryptonote::subaddress_index &subaddr_index) override
2016-05-05 21:24:00 +02:00
{
// TODO;
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height
<< ", tx: " << tx_hash
2017-02-19 03:42:10 +01:00
<< ", amount: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on sent tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneySpent(tx_hash, amount);
2016-07-10 16:17:23 +02:00
m_listener->updated();
}
2016-05-05 21:24:00 +02:00
}
EXPORT
void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) override
2016-05-05 21:24:00 +02:00
{
// TODO;
}
// Light wallet callbacks
EXPORT
void on_lw_new_block(uint64_t height) override
{
if (m_listener) {
m_listener->newBlock(height);
}
}
EXPORT
void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) override
{
if (m_listener) {
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
m_listener->moneyReceived(tx_hash, amount);
}
}
EXPORT
void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) override
{
if (m_listener) {
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
}
}
EXPORT
void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) override
{
if (m_listener) {
2020-10-23 22:32:28 +02:00
std::string tx_hash = tools::type_to_hex(txid);
m_listener->moneySpent(tx_hash, amount);
}
}
EXPORT
void on_device_button_request(uint64_t code) override
{
if (m_listener) {
m_listener->onDeviceButtonRequest(code);
}
}
EXPORT
void on_device_button_pressed() override
{
if (m_listener) {
m_listener->onDeviceButtonPressed();
}
}
EXPORT
2020-06-02 00:30:19 +02:00
std::optional<epee::wipeable_string> on_device_pin_request() override
{
if (m_listener) {
auto pin = m_listener->onDevicePinRequest();
if (pin){
2020-06-02 00:30:19 +02:00
return std::make_optional(epee::wipeable_string(pin->data(), pin->size()));
}
}
2020-06-02 00:30:19 +02:00
return std::nullopt;
}
EXPORT
std::optional<epee::wipeable_string> on_device_passphrase_request(bool& on_device) override
{
if (m_listener) {
auto passphrase = m_listener->onDevicePassphraseRequest(on_device);
if (passphrase) {
2020-06-02 00:30:19 +02:00
return std::make_optional(epee::wipeable_string(passphrase->data(), passphrase->size()));
}
} else {
on_device = true;
}
2020-06-02 00:30:19 +02:00
return std::nullopt;
}
EXPORT
void on_device_progress(const hw::device_progress & event) override
{
if (m_listener) {
m_listener->onDeviceProgress(DeviceProgress(event.progress(), event.indeterminate()));
}
}
2016-05-05 21:24:00 +02:00
WalletListener * m_listener;
WalletImpl * m_wallet;
2016-05-05 21:24:00 +02:00
};
EXPORT
2016-02-21 19:18:16 +01:00
Wallet::~Wallet() {}
2016-04-03 13:34:38 +02:00
EXPORT
2016-05-16 12:11:44 +02:00
WalletListener::~WalletListener() {}
EXPORT
std::string Wallet::displayAmount(uint64_t amount)
{
return cryptonote::print_money(amount);
}
EXPORT
uint64_t Wallet::amountFromString(const std::string &amount)
{
uint64_t result = 0;
cryptonote::parse_amount(result, amount);
return result;
}
EXPORT
uint64_t Wallet::amountFromDouble(double amount)
{
std::stringstream ss;
ss << std::fixed << std::setprecision(CRYPTONOTE_DISPLAY_DECIMAL_POINT) << amount;
return amountFromString(ss.str());
}
EXPORT
std::string Wallet::genPaymentId()
{
crypto::hash8 payment_id = crypto::rand<crypto::hash8>();
2020-10-23 22:32:28 +02:00
return tools::type_to_hex(payment_id);
}
EXPORT
2020-10-23 22:32:28 +02:00
bool Wallet::paymentIdValid(const std::string &payment_id)
2016-06-24 15:17:06 +02:00
{
2021-01-14 21:28:50 +01:00
return payment_id.size() == 16 && oxenmq::is_hex(payment_id);
2016-06-24 15:17:06 +02:00
}
EXPORT
bool Wallet::serviceNodePubkeyValid(const std::string &str)
{
crypto::public_key sn_key;
2021-01-14 21:28:50 +01:00
return str.size() == 64 && oxenmq::is_hex(str);
}
EXPORT
bool Wallet::addressValid(const std::string &str, NetworkType nettype)
{
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
return get_account_address_from_str(info, static_cast<cryptonote::network_type>(nettype), str);
}
EXPORT
bool Wallet::keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, NetworkType nettype, std::string &error)
2017-01-26 21:33:36 +01:00
{
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
if(!get_account_address_from_str(info, static_cast<cryptonote::network_type>(nettype), address_string)) {
2017-01-26 21:33:36 +01:00
error = tr("Failed to parse address");
return false;
}
cryptonote::blobdata key_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(secret_key_string, key_data) || key_data.size() != sizeof(crypto::secret_key))
{
error = tr("Failed to parse key");
return false;
}
crypto::secret_key key = *reinterpret_cast<const crypto::secret_key*>(key_data.data());
// check the key match the given address
crypto::public_key pkey;
if (!crypto::secret_key_to_public_key(key, pkey)) {
error = tr("failed to verify key");
return false;
}
bool matchAddress = false;
if(isViewKey)
2017-02-19 03:42:10 +01:00
matchAddress = info.address.m_view_public_key == pkey;
2017-01-26 21:33:36 +01:00
else
2017-02-19 03:42:10 +01:00
matchAddress = info.address.m_spend_public_key == pkey;
2017-01-26 21:33:36 +01:00
if(!matchAddress) {
error = tr("key does not match address");
return false;
}
return true;
}
EXPORT
std::string Wallet::paymentIdFromAddress(const std::string &str, NetworkType nettype)
{
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
if (!get_account_address_from_str(info, static_cast<cryptonote::network_type>(nettype), str))
return "";
2017-02-19 03:42:10 +01:00
if (!info.has_payment_id)
return "";
2020-10-23 22:32:28 +02:00
return tools::type_to_hex(info.payment_id);
}
EXPORT
uint64_t Wallet::maximumAllowedAmount()
{
return std::numeric_limits<uint64_t>::max();
}
EXPORT
void Wallet::init(const char *argv0, const char *default_log_base_name, const std::string& log_path, bool console) {
2017-02-18 21:44:23 +01:00
epee::string_tools::set_module_name_and_folder(argv0);
mlog_configure(log_path.empty() ? mlog_get_default_log_path(default_log_base_name) : log_path, console);
2017-02-18 21:44:23 +01:00
}
EXPORT
void Wallet::debug(const std::string &category, const std::string &str) {
2021-01-04 04:19:42 +01:00
MCDEBUG(category.empty() ? OXEN_DEFAULT_LOG_CATEGORY : category.c_str(), str);
}
EXPORT
void Wallet::info(const std::string &category, const std::string &str) {
2021-01-04 04:19:42 +01:00
MCINFO(category.empty() ? OXEN_DEFAULT_LOG_CATEGORY : category.c_str(), str);
}
EXPORT
void Wallet::warning(const std::string &category, const std::string &str) {
2021-01-04 04:19:42 +01:00
MCWARNING(category.empty() ? OXEN_DEFAULT_LOG_CATEGORY : category.c_str(), str);
}
EXPORT
void Wallet::error(const std::string &category, const std::string &str) {
2021-01-04 04:19:42 +01:00
MCERROR(category.empty() ? OXEN_DEFAULT_LOG_CATEGORY : category.c_str(), str);
2017-02-05 20:12:01 +01:00
}
///////////////////////// WalletImpl implementation ////////////////////////
EXPORT
WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds)
:m_wallet_ptr(nullptr)
, m_status(Wallet::Status_Ok, "")
, m_wallet2Callback(nullptr)
, m_recoveringFromSeed(false)
, m_recoveringFromDevice(false)
, m_synchronized(false)
, m_rebuildWalletCache(false)
, m_is_connected(false)
, m_refreshShouldRescan(false)
2016-02-15 19:04:00 +01:00
{
m_wallet_ptr.reset(new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true));
2018-10-04 15:37:41 +02:00
m_history.reset(new TransactionHistoryImpl(this));
m_wallet2Callback.reset(new Wallet2CallbackImpl(this));
m_wallet_ptr->callback(m_wallet2Callback.get());
2016-07-10 16:17:23 +02:00
m_refreshThreadDone = false;
m_refreshEnabled = false;
2018-10-04 15:37:41 +02:00
m_addressBook.reset(new AddressBookImpl(this));
m_subaddress.reset(new SubaddressImpl(this));
m_subaddressAccount.reset(new SubaddressAccountImpl(this));
m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
m_refreshThread = std::thread([this] { refreshThreadFunc(); });
2016-07-10 16:17:23 +02:00
m_longPollThread = std::thread([this] {
2020-01-15 05:17:26 +01:00
for (;;)
{
if (m_wallet_ptr->m_long_poll_disabled)
return true;
try {
if (m_refreshEnabled && m_wallet_ptr->long_poll_pool_state())
2020-01-15 05:17:26 +01:00
m_refreshCV.notify_one();
} catch (...) { /* ignore */ }
std::this_thread::sleep_for(1s);
2020-01-15 05:17:26 +01:00
}
});
2016-02-15 19:04:00 +01:00
}
EXPORT
2016-02-15 19:04:00 +01:00
WalletImpl::~WalletImpl()
{
LOG_PRINT_L1(__FUNCTION__);
m_wallet_ptr->callback(nullptr);
// Stop refresh and long poll threads
2016-07-10 16:17:23 +02:00
stopRefresh();
m_wallet_ptr->cancel_long_poll();
if (m_longPollThread.joinable())
m_longPollThread.join();
// Close wallet - stores cache and stops ongoing refresh operation
close(false); // do not store wallet as part of the closing activities
if (m_wallet2Callback->getListener()) {
m_wallet2Callback->getListener()->onSetWallet(nullptr);
}
LOG_PRINT_L1(__FUNCTION__ << " finished");
2016-02-15 19:04:00 +01:00
}
EXPORT
bool WalletImpl::create(std::string_view path_, const std::string &password, const std::string &language)
2016-02-15 19:04:00 +01:00
{
auto path = fs::u8path(path_);
clearStatus();
m_recoveringFromSeed = false;
m_recoveringFromDevice = false;
2016-02-15 19:04:00 +01:00
bool keys_file_exists;
bool wallet_file_exists;
tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists);
LOG_PRINT_L3("wallet_path: " << path);
2016-02-15 19:04:00 +01:00
LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha
<< " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha);
// add logic to error out if new wallet requested but named wallet file exists
if (keys_file_exists || wallet_file_exists) {
std::string error = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting.";
LOG_ERROR(error);
setStatusCritical(error);
2016-02-15 19:04:00 +01:00
return false;
}
// TODO: validate language
auto w = wallet();
w->set_seed_language(language);
2016-02-15 19:04:00 +01:00
crypto::secret_key recovery_val, secret_key;
try {
recovery_val = w->generate(path, password, secret_key, false, false);
m_password = password;
clearStatus();
} catch (const std::exception &e) {
LOG_ERROR("Error creating wallet: " << e.what());
setStatusCritical(e.what());
2016-02-15 19:04:00 +01:00
return false;
}
2016-02-15 19:04:00 +01:00
return true;
}
EXPORT
bool WalletImpl::createWatchOnly(std::string_view path_, const std::string &password, const std::string &language) const
{
auto path = fs::u8path(path_);
auto w = wallet();
clearStatus();
std::unique_ptr<tools::wallet2> view_wallet(new tools::wallet2(w->nettype()));
// Store same refresh height as original wallet
view_wallet->set_refresh_from_block_height(w->get_refresh_from_block_height());
bool keys_file_exists;
bool wallet_file_exists;
tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists);
LOG_PRINT_L3("wallet_path: " << path);
LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha
<< " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha);
// add logic to error out if new wallet requested but named wallet file exists
if (keys_file_exists || wallet_file_exists) {
std::string error = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting.";
LOG_ERROR(error);
setStatusError(error);
return false;
}
// TODO: validate language
view_wallet->set_seed_language(language);
const crypto::secret_key viewkey = w->get_account().get_keys().m_view_secret_key;
const cryptonote::account_public_address address = w->get_account().get_keys().m_account_address;
try {
// Generate view only wallet
view_wallet->generate(path, password, address, viewkey);
// Export/Import outputs
auto outputs = w->export_outputs();
view_wallet->import_outputs(outputs);
// Copy scanned blockchain
auto bc = w->export_blockchain();
view_wallet->import_blockchain(bc);
// copy payments
auto payments = w->export_payments();
view_wallet->import_payments(payments);
// copy confirmed outgoing payments
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> out_payments;
w->get_payments_out(out_payments, 0);
view_wallet->import_payments_out(out_payments);
// Export/Import key images
// We already know the spent status from the outputs we exported, thus no need to check them again
auto key_images = w->export_key_images(false /* requested_ki_only */);
uint64_t spent = 0;
uint64_t unspent = 0;
view_wallet->import_key_images(key_images.second, key_images.first, spent, unspent, false);
clearStatus();
} catch (const std::exception &e) {
LOG_ERROR("Error creating view only wallet: " << e.what());
setStatusError(e.what());
return false;
}
// Store wallet
view_wallet->store();
return true;
}
EXPORT
bool WalletImpl::recoverFromKeysWithPassword(std::string_view path_,
const std::string &password,
const std::string &language,
const std::string &address_string,
const std::string &viewkey_string,
const std::string &spendkey_string)
2017-01-26 21:33:36 +01:00
{
auto path = fs::u8path(path_);
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
if(!get_account_address_from_str(info, m_wallet_ptr->nettype(), address_string))
2017-01-26 21:33:36 +01:00
{
setStatusError(tr("failed to parse address"));
2017-01-26 21:33:36 +01:00
return false;
}
// parse optional spend key
crypto::secret_key spendkey;
bool has_spendkey = false;
if (!spendkey_string.empty()) {
cryptonote::blobdata spendkey_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key))
{
setStatusError(tr("failed to parse secret spend key"));
2017-01-26 21:33:36 +01:00
return false;
}
has_spendkey = true;
spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data());
}
// parse view secret key
bool has_viewkey = true;
crypto::secret_key viewkey;
2017-01-26 21:33:36 +01:00
if (viewkey_string.empty()) {
if(has_spendkey) {
has_viewkey = false;
}
else {
setStatusError(tr("Neither view key nor spend key supplied, cancelled"));
return false;
}
2017-01-26 21:33:36 +01:00
}
if(has_viewkey) {
cryptonote::blobdata viewkey_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key))
{
setStatusError(tr("failed to parse secret view key"));
return false;
}
viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data());
2017-01-26 21:33:36 +01:00
}
// check the spend and view keys match the given address
crypto::public_key pkey;
if(has_spendkey) {
if (!crypto::secret_key_to_public_key(spendkey, pkey)) {
setStatusError(tr("failed to verify secret spend key"));
2017-01-26 21:33:36 +01:00
return false;
}
2017-02-19 03:42:10 +01:00
if (info.address.m_spend_public_key != pkey) {
setStatusError(tr("spend key does not match address"));
2017-01-26 21:33:36 +01:00
return false;
}
}
if(has_viewkey) {
if (!crypto::secret_key_to_public_key(viewkey, pkey)) {
setStatusError(tr("failed to verify secret view key"));
return false;
}
if (info.address.m_view_public_key != pkey) {
setStatusError(tr("view key does not match address"));
return false;
}
2017-01-26 21:33:36 +01:00
}
try
{
auto w = wallet();
if (has_spendkey && has_viewkey) {
w->generate(path, password, info.address, spendkey, viewkey);
LOG_PRINT_L1("Generated new wallet from spend key and view key");
2017-01-26 21:33:36 +01:00
}
if(!has_spendkey && has_viewkey) {
w->generate(path, password, info.address, viewkey);
2017-01-26 21:33:36 +01:00
LOG_PRINT_L1("Generated new view only wallet from keys");
}
if(has_spendkey && !has_viewkey) {
w->generate(path, password, spendkey, true, false);
setSeedLanguage(language);
LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language);
}
2017-01-26 21:33:36 +01:00
}
catch (const std::exception& e) {
setStatusError(std::string(tr("failed to generate new wallet: ")) + e.what());
2017-01-26 21:33:36 +01:00
return false;
}
return true;
}
EXPORT
bool WalletImpl::recoverFromDevice(std::string_view path_, const std::string &password, const std::string &device_name)
{
auto path = fs::u8path(path_);
clearStatus();
auto w = wallet();
m_recoveringFromSeed = false;
m_recoveringFromDevice = true;
try
{
w->restore_from_device(path, password, device_name);
LOG_PRINT_L1("Generated new wallet from device: " + device_name);
}
catch (const std::exception& e) {
setStatusError(std::string(tr("failed to generate new wallet: ")) + e.what());
return false;
}
return true;
}
2017-01-26 21:33:36 +01:00
EXPORT
2018-08-16 10:31:48 +02:00
Wallet::Device WalletImpl::getDeviceType() const
{
return static_cast<Wallet::Device>(m_wallet_ptr->get_device_type());
2018-08-16 10:31:48 +02:00
}
EXPORT
bool WalletImpl::open(std::string_view path_, const std::string &password)
{
auto path = fs::u8path(path_);
clearStatus();
auto w = wallet();
m_recoveringFromSeed = false;
m_recoveringFromDevice = false;
try {
// TODO: handle "deprecated"
// Check if wallet cache exists
bool keys_file_exists;
bool wallet_file_exists;
tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists);
if(!wallet_file_exists){
// Rebuilding wallet cache, using refresh height from .keys file
m_rebuildWalletCache = true;
}
w->set_ring_database(get_default_ringdb_path(w->nettype()));
w->load(path, password);
m_password = password;
} catch (const std::exception &e) {
LOG_ERROR("Error opening wallet: " << e.what());
setStatusCritical(e.what());
}
return good();
}
EXPORT
bool WalletImpl::recover(std::string_view path_, const std::string &password, const std::string &seed, const std::string &seed_offset/* = {}*/)
{
auto path = fs::u8path(path_);
clearStatus();
if (seed.empty()) {
LOG_ERROR("Electrum seed is empty");
setStatusError(tr("Electrum seed is empty"));
return false;
}
m_recoveringFromSeed = true;
m_recoveringFromDevice = false;
crypto::secret_key recovery_key;
std::string old_language;
if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) {
setStatusError(tr("Electrum-style word list failed verification"));
return false;
}
if (!seed_offset.empty())
{
recovery_key = cryptonote::decrypt_key(recovery_key, seed_offset);
}
if (old_language == crypto::ElectrumWords::old_language_name)
old_language = Language::English().get_language_name();
try {
auto w = wallet();
w->set_seed_language(old_language);
w->generate(path, password, recovery_key, true, false);
} catch (const std::exception &e) {
setStatusCritical(e.what());
}
return good();
}
EXPORT
bool WalletImpl::close(bool store)
{
bool result = false;
LOG_PRINT_L1("closing wallet...");
try {
auto w = wallet();
if (store) {
// Do not store wallet with invalid status
// Status Critical refers to errors on opening or creating wallets.
if (status().first != Status_Critical)
w->store();
else
2018-01-09 22:37:30 +01:00
LOG_ERROR("Status_Critical - not saving wallet");
LOG_PRINT_L1("wallet::store done");
}
LOG_PRINT_L1("Calling wallet::stop...");
w->stop();
LOG_PRINT_L1("wallet::stop done");
w->deinit();
result = true;
clearStatus();
} catch (const std::exception &e) {
setStatusCritical(e.what());
LOG_ERROR("Error closing wallet: " << e.what());
}
return result;
}
EXPORT
2016-02-20 17:04:56 +01:00
std::string WalletImpl::seed() const
{
epee::wipeable_string seed;
if (m_wallet_ptr)
wallet()->get_seed(seed);
return std::string(seed.data(), seed.size()); // TODO
2016-02-21 19:18:16 +01:00
}
EXPORT
2016-02-21 19:18:16 +01:00
std::string WalletImpl::getSeedLanguage() const
{
return wallet()->get_seed_language();
2016-02-20 17:04:56 +01:00
}
EXPORT
2016-02-21 19:18:16 +01:00
void WalletImpl::setSeedLanguage(const std::string &arg)
{
wallet()->set_seed_language(arg);
2016-02-21 19:18:16 +01:00
}
2016-02-15 19:04:00 +01:00
EXPORT
std::pair<int, std::string> WalletImpl::status() const {
std::lock_guard l{m_statusMutex};
return m_status;
}
EXPORT
bool WalletImpl::good() const {
std::lock_guard l{m_statusMutex};
return m_status.first == Status_Ok;
}
EXPORT
bool WalletImpl::setPassword(const std::string &password)
{
clearStatus();
try {
auto w = wallet();
w->change_password(w->get_wallet_file(), m_password, password);
m_password = password;
} catch (const std::exception &e) {
setStatusError(e.what());
}
return good();
}
EXPORT
bool WalletImpl::setDevicePin(const std::string &pin)
{
clearStatus();
try {
wallet()->get_account().get_device().set_pin(epee::wipeable_string(pin.data(), pin.size()));
} catch (const std::exception &e) {
setStatusError(e.what());
}
return good();
}
EXPORT
bool WalletImpl::setDevicePassphrase(const std::string &passphrase)
{
clearStatus();
try {
wallet()->get_account().get_device().set_passphrase(epee::wipeable_string(passphrase.data(), passphrase.size()));
} catch (const std::exception &e) {
setStatusError(e.what());
}
return good();
}
EXPORT
2017-02-19 03:42:10 +01:00
std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const
2016-03-12 15:52:58 +01:00
{
return wallet()->get_subaddress_as_str({accountIndex, addressIndex});
2016-03-12 15:52:58 +01:00
}
EXPORT
std::string WalletImpl::integratedAddress(const std::string &payment_id) const
{
crypto::hash8 pid;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(payment_id, pid))
return "";
return wallet()->get_integrated_address_as_str(pid);
}
EXPORT
2017-05-02 16:48:38 +02:00
std::string WalletImpl::secretViewKey() const
{
return tools::type_to_hex(wallet()->get_account().get_keys().m_view_secret_key);
}
EXPORT
2017-05-02 16:48:38 +02:00
std::string WalletImpl::publicViewKey() const
{
return tools::type_to_hex(wallet()->get_account().get_keys().m_account_address.m_view_public_key);
2017-05-02 16:48:38 +02:00
}
EXPORT
2017-05-02 16:48:38 +02:00
std::string WalletImpl::secretSpendKey() const
{
return tools::type_to_hex(wallet()->get_account().get_keys().m_spend_secret_key);
2017-05-02 16:48:38 +02:00
}
EXPORT
2017-05-02 16:48:38 +02:00
std::string WalletImpl::publicSpendKey() const
{
return tools::type_to_hex(wallet()->get_account().get_keys().m_account_address.m_spend_public_key);
2017-05-02 16:48:38 +02:00
}
EXPORT
std::string WalletImpl::publicMultisigSignerKey() const
{
try {
crypto::public_key signer = wallet()->get_multisig_signer_public_key();
2020-10-23 22:32:28 +02:00
return tools::type_to_hex(signer);
} catch (const std::exception&) {
return "";
}
}
EXPORT
std::string WalletImpl::path() const
{
return wallet()->path().u8string();
}
EXPORT
bool WalletImpl::store(std::string_view path_)
{
auto path = fs::u8path(path_);
clearStatus();
try {
if (path.empty()) {
wallet()->store();
} else {
wallet()->store_to(path, m_password);
}
} catch (const std::exception &e) {
2018-01-09 22:37:30 +01:00
LOG_ERROR("Error saving wallet: " << e.what());
setStatusError(e.what());
return false;
}
return true;
}
EXPORT
std::string WalletImpl::filename() const
{
return wallet()->get_wallet_file().u8string();
}
EXPORT
std::string WalletImpl::keysFilename() const
{
return wallet()->get_keys_file().u8string();
}
EXPORT
bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transaction_size_limit, const std::string &daemon_username, const std::string &daemon_password, bool use_ssl ENABLE_IF_LIGHT_WALLET(, bool lightWallet))
2016-03-25 15:06:30 +01:00
{
clearStatus();
#ifdef ENABLE_LIGHT_WALLET
wallet()->set_light_wallet(lightWallet);
#endif
if(daemon_username != "")
m_daemon_login.emplace(daemon_username, daemon_password);
return doInit(daemon_address, upper_transaction_size_limit, use_ssl);
}
#ifdef ENABLE_LIGHT_WALLET
EXPORT
bool WalletImpl::lightWalletLogin(bool &isNewWallet) const
{
return wallet()->light_wallet_login(isNewWallet);
}
EXPORT
bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status)
{
try
{
tools::light_rpc::IMPORT_WALLET_REQUEST::response response{};
if(!wallet()->light_wallet_import_wallet_request(response)){
setStatusError(tr("Failed to send import wallet request"));
return false;
}
fee = response.import_fee;
payment_id = response.payment_id;
new_request = response.new_request;
request_fulfilled = response.request_fulfilled;
payment_address = response.payment_address;
status = response.status;
}
catch (const std::exception &e)
{
LOG_ERROR("Error sending import wallet request: " << e.what());
setStatusError(e.what());
return false;
}
return true;
}
#endif
EXPORT
void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height)
{
wallet()->set_refresh_from_block_height(refresh_from_block_height);
}
EXPORT
void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed)
{
m_recoveringFromSeed = recoveringFromSeed;
}
EXPORT
void WalletImpl::setRecoveringFromDevice(bool recoveringFromDevice)
{
m_recoveringFromDevice = recoveringFromDevice;
}
EXPORT
void WalletImpl::setSubaddressLookahead(uint32_t major, uint32_t minor)
{
wallet()->set_subaddress_lookahead(major, minor);
}
EXPORT
2017-02-19 03:42:10 +01:00
uint64_t WalletImpl::balance(uint32_t accountIndex) const
2016-03-25 15:06:30 +01:00
{
return wallet()->balance(accountIndex, false);
2016-03-25 15:06:30 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const
{
return wallet()->unlocked_balance(accountIndex, false);
}
EXPORT
std::vector<std::pair<std::string, uint64_t>>* WalletImpl::listCurrentStakes() const
2020-11-21 01:40:11 +01:00
{
std::vector<std::pair<std::string, uint64_t>>* stakes = new std::vector<std::pair<std::string, uint64_t>>;
2020-11-21 02:35:32 +01:00
auto response = wallet()->list_current_stakes();
2020-11-21 02:35:32 +01:00
for (rpc::GET_SERVICE_NODES::response::entry const &node_info : response)
{
for (const auto& contributor : node_info.contributors)
2020-11-21 02:35:32 +01:00
{
stakes->push_back(std::make_pair(node_info.service_node_pubkey, contributor.amount));
2020-11-21 02:35:32 +01:00
}
}
2020-11-21 01:40:11 +01:00
return stakes;
}
EXPORT
uint64_t WalletImpl::blockChainHeight() const
{
// This call is thread-safe
auto& w = m_wallet_ptr;
#ifdef ENABLE_LIGHT_WALLET
if(w->light_wallet()) {
return w->get_light_wallet_scanned_block_height();
}
#endif
return w->get_blockchain_current_height();
}
EXPORT
uint64_t WalletImpl::approximateBlockChainHeight() const
{
return wallet()->get_approximate_blockchain_height();
}
EXPORT
uint64_t WalletImpl::estimateBlockChainHeight() const
{
return wallet()->estimate_blockchain_height();
}
EXPORT
uint64_t WalletImpl::daemonBlockChainHeight() const
{
// I *think* the calls here are thread-safe, so we can do this without locking
//auto w = wallet();
auto& w = m_wallet_ptr;
#ifdef ENABLE_LIGHT_WALLET
if(w->light_wallet()) {
return w->get_light_wallet_scanned_block_height();
2017-08-05 00:08:32 +02:00
}
#endif
if (!m_is_connected)
return 0;
std::string err;
uint64_t result = w->get_daemon_blockchain_height(err);
if (!err.empty()) {
LOG_ERROR(__FUNCTION__ << ": " << err);
result = 0;
setStatusError(err);
} else {
clearStatus();
}
return result;
}
EXPORT
uint64_t WalletImpl::daemonBlockChainTargetHeight() const
{
// As above
//auto w = wallet();
auto& w = m_wallet_ptr;
#ifdef ENABLE_LIGHT_WALLET
if(w->light_wallet()) {
return w->get_light_wallet_blockchain_height();
2017-08-05 00:08:32 +02:00
}
#endif
if (!m_is_connected)
return 0;
std::string err;
uint64_t result = w->get_daemon_blockchain_target_height(err);
if (!err.empty()) {
LOG_ERROR(__FUNCTION__ << ": " << err);
result = 0;
setStatusError(err);
} else {
clearStatus();
}
// Target height can be 0 when daemon is synced. Use blockchain height instead.
if(result == 0)
result = daemonBlockChainHeight();
return result;
}
EXPORT
bool WalletImpl::daemonSynced() const
{
if(connected() == Wallet::ConnectionStatus_Disconnected)
return false;
uint64_t blockChainHeight = daemonBlockChainHeight();
return (blockChainHeight >= daemonBlockChainTargetHeight() && blockChainHeight > 1);
}
EXPORT
bool WalletImpl::synchronized() const
{
return m_synchronized;
}
EXPORT
2016-03-31 15:38:57 +02:00
bool WalletImpl::refresh()
{
clearStatus();
//TODO: make doRefresh return bool to know whether the error occured during refresh or not
//otherwise one may try, say, to send transaction, transfer fails and this method returns false
2016-07-10 16:17:23 +02:00
doRefresh();
return good();
2016-03-31 15:38:57 +02:00
}
EXPORT
2016-07-10 16:17:23 +02:00
void WalletImpl::refreshAsync()
{
2016-12-04 14:13:54 +01:00
LOG_PRINT_L3(__FUNCTION__ << ": Refreshing asynchronously..");
2016-07-10 16:17:23 +02:00
clearStatus();
m_refreshCV.notify_one();
}
EXPORT
bool WalletImpl::isRefreshing(std::chrono::milliseconds max_wait) {
std::unique_lock lock{m_refreshMutex2, std::defer_lock};
return !lock.try_lock_for(max_wait);
}
EXPORT
bool WalletImpl::rescanBlockchain()
{
clearStatus();
m_refreshShouldRescan = true;
doRefresh();
return good();
}
EXPORT
void WalletImpl::rescanBlockchainAsync()
{
m_refreshShouldRescan = true;
refreshAsync();
}
EXPORT
void WalletImpl::setAutoRefreshInterval(int millis)
{
if (millis > MAX_REFRESH_INTERVAL_MILLIS) {
LOG_ERROR(__FUNCTION__<< ": invalid refresh interval " << millis
<< " ms, maximum allowed is " << MAX_REFRESH_INTERVAL_MILLIS << " ms");
m_refreshIntervalMillis = MAX_REFRESH_INTERVAL_MILLIS;
} else {
m_refreshIntervalMillis = millis;
}
}
EXPORT
int WalletImpl::autoRefreshInterval() const
{
return m_refreshIntervalMillis;
}
UnsignedTransaction* WalletImpl::loadUnsignedTx(std::string_view unsigned_filename_) {
auto unsigned_filename = fs::u8path(unsigned_filename_);
2017-01-08 13:17:09 +01:00
clearStatus();
UnsignedTransactionImpl* transaction = new UnsignedTransactionImpl(*this);
if (!wallet()->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){
setStatusError(tr("Failed to load unsigned transactions"));
transaction->m_status = {UnsignedTransaction::Status::Status_Error, status().second};
return transaction;
2017-01-08 13:17:09 +01:00
}
// Check tx data and construct confirmation message
std::string extra_message;
if (!transaction->m_unsigned_tx_set.transfers.second.empty())
extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str();
transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const wallet::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message);
auto [code, msg] = transaction->status();
setStatus(code, std::move(msg));
2017-01-08 13:17:09 +01:00
return transaction;
}
EXPORT
bool WalletImpl::submitTransaction(std::string_view filename_) {
auto fileName = fs::u8path(filename_);
2017-01-08 13:17:09 +01:00
clearStatus();
std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this));
2017-01-08 13:17:09 +01:00
bool r = wallet()->load_tx(fileName, transaction->m_pending_tx);
2017-01-08 13:17:09 +01:00
if (!r) {
setStatus(Status_Error, tr("Failed to load transaction from file"));
2017-01-08 13:17:09 +01:00
return false;
}
if(!transaction->commit()) {
setStatusError(transaction->status().second);
2017-01-08 13:17:09 +01:00
return false;
}
return true;
}
EXPORT
bool WalletImpl::exportKeyImages(std::string_view filename_)
{
auto filename = fs::u8path(filename_);
auto w = wallet();
if (w->watch_only())
{
setStatusError(tr("Wallet is view only"));
return false;
}
try
{
if (!w->export_key_images_to_file(filename, false /* requested_ki_only */))
{
setStatusError(tr("failed to save file ") + filename.u8string());
return false;
}
}
2017-03-18 13:56:07 +01:00
catch (const std::exception &e)
{
LOG_ERROR("Error exporting key images: " << e.what());
setStatusError(e.what());
return false;
}
return true;
}
EXPORT
bool WalletImpl::importKeyImages(std::string_view filename_)
{
auto filename = fs::u8path(filename_);
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 = wallet()->import_key_images_from_file(filename, 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(std::string(tr("Failed to import key images: ")) + e.what());
return false;
}
2017-01-08 13:17:09 +01:00
return true;
}
EXPORT
2017-02-19 03:42:10 +01:00
void WalletImpl::addSubaddressAccount(const std::string& label)
{
wallet()->add_subaddress_account(label);
2017-02-19 03:42:10 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
size_t WalletImpl::numSubaddressAccounts() const
{
return wallet()->get_num_subaddress_accounts();
2017-02-19 03:42:10 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const
{
return wallet()->get_num_subaddresses(accountIndex);
2017-02-19 03:42:10 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label)
{
wallet()->add_subaddress(accountIndex, label);
2017-02-19 03:42:10 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const
{
try
{
return wallet()->get_subaddress_label({accountIndex, addressIndex});
2017-02-19 03:42:10 +01:00
}
catch (const std::exception &e)
{
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error getting subaddress label: " << e.what());
setStatusError(std::string(tr("Failed to get subaddress label: ")) + e.what());
2017-02-19 03:42:10 +01:00
return "";
}
}
EXPORT
2017-02-19 03:42:10 +01:00
void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label)
{
try
{
return wallet()->set_subaddress_label({accountIndex, addressIndex}, label);
2017-02-19 03:42:10 +01:00
}
catch (const std::exception &e)
{
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error setting subaddress label: " << e.what());
setStatusError(std::string(tr("Failed to set subaddress label: ")) + e.what());
2017-02-19 03:42:10 +01:00
}
}
MultisigState WalletImpl::multisig(LockedWallet& w) {
MultisigState state;
state.isMultisig = w->multisig(&state.isReady, &state.threshold, &state.total);
return state;
}
EXPORT
MultisigState WalletImpl::multisig() const {
auto w = wallet();
return multisig(w);
}
EXPORT
std::string WalletImpl::getMultisigInfo() const {
try {
clearStatus();
return wallet()->get_multisig_info();
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on generating multisig info: " << e.what());
setStatusError(std::string(tr("Failed to get multisig info: ")) + e.what());
}
return {};
}
EXPORT
std::string WalletImpl::makeMultisig(const std::vector<std::string>& info, uint32_t threshold) {
try {
clearStatus();
auto w = wallet();
if (w->multisig())
throw std::runtime_error("Wallet is already multisig");
return w->make_multisig(epee::wipeable_string(m_password), info, threshold);
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on making multisig wallet: " << e.what());
setStatusError(std::string(tr("Failed to make multisig: ")) + e.what());
}
return {};
}
EXPORT
std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info) {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletNotReady(w);
return w->exchange_multisig_keys(epee::wipeable_string(m_password), info);
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on exchanging multisig keys: " << e.what());
setStatusError(std::string(tr("Failed to make multisig: ")) + e.what());
}
return {};
}
EXPORT
bool WalletImpl::finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletNotReady(w);
if (w->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) {
return true;
}
setStatusError(tr("Failed to finalize multisig wallet creation"));
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on finalizing multisig wallet creation: " << e.what());
setStatusError(std::string(tr("Failed to finalize multisig wallet creation: ")) + e.what());
}
return false;
}
EXPORT
bool WalletImpl::exportMultisigImages(std::string& images) {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletReady(w);
auto blob = w->export_multisig();
2021-01-14 21:28:50 +01:00
images = oxenmq::to_hex(blob);
return true;
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on exporting multisig images: " << e.what());
setStatusError(std::string(tr("Failed to export multisig images: ")) + e.what());
}
return false;
}
EXPORT
size_t WalletImpl::importMultisigImages(const std::vector<std::string>& images) {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletReady(w);
std::vector<std::string> blobs;
blobs.reserve(images.size());
for (const auto& image: images) {
std::string blob;
if (!epee::string_tools::parse_hexstr_to_binbuff(image, blob)) {
LOG_ERROR("Failed to parse imported multisig images");
setStatusError(tr("Failed to parse imported multisig images"));
return 0;
}
blobs.emplace_back(std::move(blob));
}
return w->import_multisig(blobs);
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on importing multisig images: " << e.what());
setStatusError(std::string(tr("Failed to import multisig images: ")) + e.what());
}
return 0;
}
EXPORT
bool WalletImpl::hasMultisigPartialKeyImages() const {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletReady(w);
return w->has_multisig_partial_key_images();
} catch (const std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on checking for partial multisig key images: " << e.what());
setStatusError(std::string(tr("Failed to check for partial multisig key images: ")) + e.what());
}
return false;
}
EXPORT
PendingTransaction* WalletImpl::restoreMultisigTransaction(const std::string& signData) {
try {
clearStatus();
auto w = wallet();
checkMultisigWalletReady(w);
std::string binary;
if (!epee::string_tools::parse_hexstr_to_binbuff(signData, binary))
throw std::runtime_error("Failed to deserialize multisig transaction");
tools::wallet2::multisig_tx_set txSet;
if (!w->load_multisig_tx(binary, txSet, {}))
throw std::runtime_error("couldn't parse multisig transaction data");
auto ptx = new PendingTransactionImpl(*this);
ptx->m_pending_tx = txSet.m_ptx;
ptx->m_signers = txSet.m_signers;
return ptx;
} catch (std::exception& e) {
2019-01-07 12:56:50 +01:00
LOG_ERROR("Error on restoring multisig transaction: " << e.what());
setStatusError(std::string(tr("Failed to restore multisig transaction: ")) + e.what());
}
return nullptr;
}
// TODO:
2020-10-23 22:32:28 +02:00
// - check / design how "Transaction" can be single interface
// (instead of few different data structures within wallet2 implementation:
// - pending_tx;
// - transfer_details;
// - payment_details;
// - unconfirmed_transfer_details;
// - confirmed_transfer_details)
EXPORT
2020-10-23 22:32:28 +02:00
PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<std::string> &dst_addr, std::optional<std::vector<uint64_t>> amount, uint32_t priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
2016-04-03 13:34:38 +02:00
{
clearStatus();
// Pause refresh thread while creating transaction
pauseRefresh();
2017-01-08 13:17:09 +01:00
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
2016-04-03 13:34:38 +02:00
do {
std::vector<uint8_t> extra;
std::string extra_nonce;
std::vector<cryptonote::tx_destination_entry> dsts;
if (!amount && dst_addr.size() > 1) {
setStatusError(tr("Sending all requires one destination address"));
break;
}
if (amount && (dst_addr.size() != (*amount).size())) {
setStatusError(tr("Destinations and amounts are unequal"));
break;
}
bool error = false;
auto w = wallet();
for (size_t i = 0; i < dst_addr.size() && !error; i++) {
if(!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), dst_addr[i])) {
// TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982
setStatusError(tr("Invalid destination address"));
error = true;
break;
}
if (info.has_payment_id) {
if (!extra_nonce.empty()) {
setStatusError(tr("a single transaction cannot use more than one payment id"));
error = true;
break;
}
set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
}
2016-11-09 13:19:22 +01:00
if (amount) {
cryptonote::tx_destination_entry de;
de.original = dst_addr[i];
2017-02-19 03:42:10 +01:00
de.addr = info.address;
de.amount = (*amount)[i];
2017-02-19 03:42:10 +01:00
de.is_subaddress = info.is_subaddress;
de.is_integrated = info.has_payment_id;
2016-11-09 13:19:22 +01:00
dsts.push_back(de);
2016-11-09 13:19:22 +01:00
} else {
if (subaddr_indices.empty()) {
for (uint32_t index = 0; index < w->get_num_subaddresses(subaddr_account); ++index)
2017-02-19 03:42:10 +01:00
subaddr_indices.insert(index);
}
2016-11-09 13:19:22 +01:00
}
}
if (error) {
break;
}
if (!extra_nonce.empty() && !add_extra_nonce_to_tx_extra(extra, extra_nonce)) {
setStatusError(tr("failed to set up payment id, though it was decoded correctly"));
break;
}
try {
std::optional<uint8_t> hf_version = w->get_hard_fork_version();
if (!hf_version)
{
setStatusError(tools::wallet2::ERR_MSG_NETWORK_VERSION_QUERY_FAILED);
return transaction;
}
if (amount) {
2021-01-04 01:09:45 +01:00
oxen_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::standard, priority);
transaction->m_pending_tx = w->create_transactions_2(
dsts, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */,
priority,
extra, subaddr_account, subaddr_indices, tx_params);
} else {
transaction->m_pending_tx = w->create_transactions_all(
0, info.address, info.is_subaddress, 1, CRYPTONOTE_DEFAULT_TX_MIXIN, 0 /* unlock_time */,
priority,
extra, subaddr_account, subaddr_indices);
2016-11-09 13:19:22 +01:00
}
pendingTxPostProcess(transaction);
if (multisig().isMultisig) {
auto tx_set = w->make_multisig_tx_set(transaction->m_pending_tx);
transaction->m_pending_tx = tx_set.m_ptx;
transaction->m_signers = tx_set.m_signers;
}
} catch (const tools::error::daemon_busy&) {
// TODO: make it translatable with "tr"?
setStatusError(tr("daemon is busy. Please try again later."));
} catch (const tools::error::no_connection_to_daemon&) {
setStatusError(tr("no connection to daemon. Please make sure daemon is running."));
} catch (const tools::error::wallet_rpc_error& e) {
setStatusError(tr("RPC error: ") + e.to_string());
} catch (const tools::error::get_outs_error &e) {
setStatusError((boost::format(tr("failed to get outputs to mix: %s")) % e.what()).str());
} catch (const tools::error::not_enough_unlocked_money& e) {
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());
setStatusError(writer.str());
} catch (const tools::error::not_enough_money& e) {
std::ostringstream writer;
writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) %
print_money(e.available()) %
print_money(e.tx_amount());
setStatusError(writer.str());
} catch (const tools::error::tx_not_possible& e) {
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());
setStatusError(writer.str());
} catch (const tools::error::not_enough_outs_to_mix& e) {
std::ostringstream writer;
writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
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 use") << " = " << outs_for_amount.second;
}
writer << "\n" << tr("Please sweep unmixable outputs.");
setStatusError(writer.str());
} catch (const tools::error::tx_not_constructed&) {
setStatusError(tr("transaction was not constructed"));
} 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();
setStatusError(writer.str());
} catch (const tools::error::tx_sum_overflow& e) {
setStatusError(e.what());
} catch (const tools::error::zero_destination&) {
setStatusError(tr("one of destinations is zero"));
} catch (const tools::error::tx_too_big& e) {
setStatusError(tr("failed to find a suitable way to split transactions"));
} catch (const tools::error::transfer_error& e) {
setStatusError(std::string(tr("unknown transfer error: ")) + e.what());
} catch (const tools::error::wallet_internal_error& e) {
setStatusError(std::string(tr("internal error: ")) + e.what());
} catch (const std::exception& e) {
setStatusError(std::string(tr("unexpected error: ")) + e.what());
} catch (...) {
setStatusError(tr("unknown error"));
}
} while (false);
transaction->m_status = status();
// Resume refresh thread
startRefresh();
return transaction;
}
EXPORT
2020-10-23 22:32:28 +02:00
PendingTransaction *WalletImpl::createTransaction(const std::string &dst_addr, std::optional<uint64_t> amount,
uint32_t priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
{
2020-10-23 22:32:28 +02:00
return createTransactionMultDest(std::vector<std::string> {dst_addr}, amount ? (std::vector<uint64_t> {*amount}) : (std::optional<std::vector<uint64_t>>()), priority, subaddr_account, subaddr_indices);
}
EXPORT
PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
{
clearStatus();
cryptonote::tx_destination_entry de;
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
try {
transaction->m_pending_tx = wallet()->create_unmixable_sweep_transactions();
pendingTxPostProcess(transaction);
} catch (const tools::error::daemon_busy&) {
// TODO: make it translatable with "tr"?
setStatusError(tr("daemon is busy. Please try again later."));
} catch (const tools::error::no_connection_to_daemon&) {
setStatusError(tr("no connection to daemon. Please make sure daemon is running."));
} catch (const tools::error::wallet_rpc_error& e) {
setStatusError(tr("RPC error: ") + e.to_string());
} catch (const tools::error::get_outs_error&) {
setStatusError(tr("failed to get outputs to mix"));
} catch (const tools::error::not_enough_unlocked_money& e) {
setStatusError("");
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());
setStatusError(writer.str());
} catch (const tools::error::not_enough_money& e) {
setStatusError("");
std::ostringstream writer;
writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) %
print_money(e.available()) %
print_money(e.tx_amount());
setStatusError(writer.str());
} catch (const tools::error::tx_not_possible& e) {
setStatusError("");
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());
setStatusError(writer.str());
} catch (const tools::error::not_enough_outs_to_mix& e) {
std::ostringstream writer;
writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
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 use") << " = " << outs_for_amount.second;
}
setStatusError(writer.str());
} catch (const tools::error::tx_not_constructed&) {
setStatusError(tr("transaction was not constructed"));
} 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();
setStatusError(writer.str());
} catch (const tools::error::tx_sum_overflow& e) {
setStatusError(e.what());
} catch (const tools::error::zero_destination&) {
setStatusError(tr("one of destinations is zero"));
} catch (const tools::error::tx_too_big& e) {
setStatusError(tr("failed to find a suitable way to split transactions"));
} catch (const tools::error::transfer_error& e) {
setStatusError(std::string(tr("unknown transfer error: ")) + e.what());
} catch (const tools::error::wallet_internal_error& e) {
setStatusError(std::string(tr("internal error: ")) + e.what());
} catch (const std::exception& e) {
setStatusError(std::string(tr("unexpected error: ")) + e.what());
} catch (...) {
setStatusError(tr("unknown error"));
}
transaction->m_status = status();
return transaction;
}
EXPORT
void WalletImpl::disposeTransaction(PendingTransaction *t)
{
delete t;
}
EXPORT
uint64_t WalletImpl::estimateTransactionFee(uint32_t priority, uint32_t recipients) const
{
constexpr uint32_t typical_size = 2000;
auto w = wallet();
const auto base_fee = w->get_base_fees();
uint64_t pct = w->get_fee_percent(priority == 1 ? 1 : 5, txtype::standard);
return (base_fee.first * typical_size + base_fee.second * (recipients + 1)) * pct / 100;
}
EXPORT
2017-02-19 03:42:10 +01:00
TransactionHistory *WalletImpl::history()
{
return m_history.get();
}
EXPORT
2017-02-19 03:42:10 +01:00
AddressBook *WalletImpl::addressBook()
2016-12-12 00:42:46 +01:00
{
return m_addressBook.get();
2016-12-12 00:42:46 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
Subaddress *WalletImpl::subaddress()
{
return m_subaddress.get();
2017-02-19 03:42:10 +01:00
}
EXPORT
2017-02-19 03:42:10 +01:00
SubaddressAccount *WalletImpl::subaddressAccount()
{
return m_subaddressAccount.get();
2017-02-19 03:42:10 +01:00
}
EXPORT
2016-05-05 21:24:00 +02:00
void WalletImpl::setListener(WalletListener *l)
{
// TODO thread synchronization;
m_wallet2Callback->setListener(l);
}
EXPORT
bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val)
{
wallet()->set_attribute(key, val);
return true;
}
EXPORT
std::string WalletImpl::getCacheAttribute(const std::string &key) const
{
std::string value;
wallet()->get_attribute(key, value);
return value;
}
EXPORT
2016-11-05 21:53:45 +01:00
bool WalletImpl::setUserNote(const std::string &txid, const std::string &note)
{
cryptonote::blobdata txid_data;
2017-02-14 20:35:44 +01:00
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
2016-11-05 21:53:45 +01:00
return false;
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
wallet()->set_tx_note(htxid, note);
2016-11-05 21:53:45 +01:00
return true;
}
EXPORT
2016-11-05 21:53:45 +01:00
std::string WalletImpl::getUserNote(const std::string &txid) const
{
cryptonote::blobdata txid_data;
2017-02-14 20:35:44 +01:00
if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash))
2016-11-05 21:53:45 +01:00
return "";
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
return wallet()->get_tx_note(htxid);
2016-11-05 21:53:45 +01:00
}
2016-05-05 21:24:00 +02:00
EXPORT
std::string WalletImpl::getTxKey(const std::string &txid_str) const
2016-11-06 19:04:59 +01:00
{
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if(!tools::hex_to_type(txid_str, txid))
2016-11-06 19:04:59 +01:00
{
setStatusError(tr("Failed to parse txid"));
return "";
2016-11-06 19:04:59 +01:00
}
crypto::secret_key tx_key;
2017-02-19 03:42:10 +01:00
std::vector<crypto::secret_key> additional_tx_keys;
try
2016-11-06 19:04:59 +01:00
{
clearStatus();
if (wallet()->get_tx_key(txid, tx_key, additional_tx_keys))
{
clearStatus();
std::ostringstream oss;
2020-10-23 22:32:28 +02:00
oss << tools::type_to_hex(tx_key);
for (size_t i = 0; i < additional_tx_keys.size(); ++i)
2020-10-23 22:32:28 +02:00
oss << tools::type_to_hex(additional_tx_keys[i]);
return oss.str();
}
else
{
setStatusError(tr("no tx keys found for this txid"));
return "";
}
2016-11-06 19:04:59 +01:00
}
catch (const std::exception &e)
2016-11-06 19:04:59 +01:00
{
setStatusError(e.what());
return "";
}
}
EXPORT
2020-10-23 22:32:28 +02:00
bool WalletImpl::checkTxKey(const std::string &txid_str, std::string_view tx_key_str, const std::string &address_str, uint64_t &received, bool &in_pool, uint64_t &confirmations)
{
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(txid_str, txid))
{
setStatusError(tr("Failed to parse txid"));
return false;
}
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
2020-10-23 22:32:28 +02:00
bool first = true;
while (first || !tx_key_str.empty())
{
2020-10-23 22:32:28 +02:00
auto& key = first ? tx_key : additional_tx_keys.emplace_back();
if (first) first = false;
if (!tools::hex_to_type(tx_key_str.substr(0, 64), key))
{
setStatusError(tr("Failed to parse tx key"));
return false;
}
2020-10-23 22:32:28 +02:00
tx_key_str.remove_prefix(64);
}
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), address_str))
{
setStatusError(tr("Failed to parse address"));
return false;
}
try
{
wallet()->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations);
clearStatus();
return true;
}
catch (const std::exception &e)
{
setStatusError(e.what());
return false;
}
}
EXPORT
std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const
{
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(txid_str, txid))
{
setStatusError(tr("Failed to parse txid"));
return "";
}
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), address_str))
{
setStatusError(tr("Failed to parse address"));
return "";
}
try
{
clearStatus();
return wallet()->get_tx_proof(txid, info.address, info.is_subaddress, message);
}
catch (const std::exception &e)
{
setStatusError(e.what());
return "";
}
}
EXPORT
bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations)
{
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(txid_str, txid))
{
setStatusError(tr("Failed to parse txid"));
return false;
}
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), address_str))
{
setStatusError(tr("Failed to parse address"));
return false;
}
try
{
good = wallet()->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations);
clearStatus();
return true;
}
catch (const std::exception &e)
{
setStatusError(e.what());
return false;
2016-11-06 19:04:59 +01:00
}
}
EXPORT
2017-08-28 17:34:17 +02:00
std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const {
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if(!tools::hex_to_type(txid_str, txid))
2017-08-28 17:34:17 +02:00
{
setStatusError(tr("Failed to parse txid"));
2017-08-28 17:34:17 +02:00
return "";
}
try
{
clearStatus();
return wallet()->get_spend_proof(txid, message);
2017-08-28 17:34:17 +02:00
}
catch (const std::exception &e)
{
setStatusError(e.what());
2017-08-28 17:34:17 +02:00
return "";
}
}
EXPORT
2017-08-28 17:34:17 +02:00
bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string &message, const std::string &signature, bool &good) const {
good = false;
crypto::hash txid;
2020-10-23 22:32:28 +02:00
if(!tools::hex_to_type(txid_str, txid))
2017-08-28 17:34:17 +02:00
{
setStatusError(tr("Failed to parse txid"));
2017-08-28 17:34:17 +02:00
return false;
}
try
{
clearStatus();
good = wallet()->check_spend_proof(txid, message, signature);
2017-08-28 17:34:17 +02:00
return true;
}
catch (const std::exception &e)
{
setStatusError(e.what());
2017-08-28 17:34:17 +02:00
return false;
}
}
EXPORT
2017-12-28 14:50:10 +01:00
std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const {
try
{
clearStatus();
2020-06-02 00:30:19 +02:00
std::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
2017-12-28 14:50:10 +01:00
if (!all)
{
account_minreserve = std::make_pair(account_index, amount);
}
return wallet()->get_reserve_proof(account_minreserve, message);
2017-12-28 14:50:10 +01:00
}
catch (const std::exception &e)
{
setStatusError(e.what());
2017-12-28 14:50:10 +01:00
return "";
}
}
EXPORT
2017-12-28 14:50:10 +01:00
bool WalletImpl::checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const {
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), address))
2017-12-28 14:50:10 +01:00
{
setStatusError(tr("Failed to parse address"));
2017-12-28 14:50:10 +01:00
return false;
}
if (info.is_subaddress)
{
setStatusError(tr("Address must not be a subaddress"));
2017-12-28 14:50:10 +01:00
return false;
}
good = false;
try
{
clearStatus();
good = wallet()->check_reserve_proof(info.address, message, signature, total, spent);
2017-12-28 14:50:10 +01:00
return true;
}
catch (const std::exception &e)
{
setStatusError(e.what());
2017-12-28 14:50:10 +01:00
return false;
}
}
EXPORT
std::string WalletImpl::signMessage(const std::string &message)
{
return wallet()->sign(message);
}
EXPORT
bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const
{
2017-02-19 03:42:10 +01:00
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str(info, m_wallet_ptr->nettype(), address))
return false;
return wallet()->verify(message, info.address, signature);
}
EXPORT
std::string WalletImpl::signMultisigParticipant(const std::string &message) const
{
clearStatus();
bool ready = false;
auto w = wallet();
if (!w->multisig(&ready) || !ready) {
setStatusError(tr("The wallet must be in multisig ready state"));
return {};
}
try {
return w->sign_multisig_participant(message);
} catch (const std::exception& e) {
setStatusError(e.what());
}
return {};
}
EXPORT
bool WalletImpl::verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const
{
clearStatus();
cryptonote::blobdata pkeyData;
if(!epee::string_tools::parse_hexstr_to_binbuff(publicKey, pkeyData) || pkeyData.size() != sizeof(crypto::public_key))
return setStatusError(tr("Given string is not a key"));
try {
crypto::public_key pkey = *reinterpret_cast<const crypto::public_key*>(pkeyData.data());
return wallet()->verify_with_public_key(message, pkey, signature);
} catch (const std::exception& e) {
return setStatusError(e.what());
}
return false;
}
EXPORT
bool WalletImpl::connectToDaemon()
{
auto w = wallet();
bool result = w->check_connection(NULL, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
if (!result) {
setStatusError("Error connecting to daemon at " + w->get_daemon_address());
2016-07-10 16:17:23 +02:00
} else {
clearStatus();
2016-07-10 16:17:23 +02:00
// start refreshing here
2016-04-03 13:34:38 +02:00
}
return result;
}
EXPORT
Wallet::ConnectionStatus WalletImpl::connected() const
{
RPC overhaul High-level details: This redesigns the RPC layer to make it much easier to work with, decouples it from an embedded HTTP server, and gets the vast majority of the RPC serialization and dispatch code out of a very commonly included header. There is unfortunately rather a lot of interconnected code here that cannot be easily separated out into separate commits. The full details of what happens here are as follows: Major details: - All of the RPC code is now in a `cryptonote::rpc` namespace; this renames quite a bit to be less verbose: e.g. CORE_RPC_STATUS_OK becomes `rpc::STATUS_OK`, and `cryptonote::COMMAND_RPC_SOME_LONG_NAME` becomes `rpc::SOME_LONG_NAME` (or just SOME_LONG_NAME for code already working in the `rpc` namespace). - `core_rpc_server` is now completely decoupled from providing any request protocol: it is now *just* the core RPC call handler. - The HTTP RPC interface now lives in a new rpc/http_server.h; this code handles listening for HTTP requests and dispatching them to core_rpc_server, then sending the results back to the caller. - There is similarly a rpc/lmq_server.h for LMQ RPC code; more details on this (and other LMQ specifics) below. - RPC implementing code now returns the response object and throws when things go wrong which simplifies much of the rpc error handling. They can throw anything; generic exceptions get logged and a generic "internal error" message gets returned to the caller, but there is also an `rpc_error` class to return an error code and message used by some json-rpc commands. - RPC implementing functions now overload `core_rpc_server::invoke` following the pattern: RPC_BLAH_BLAH::response core_rpc_server::invoke(RPC_BLAH_BLAH::request&& req, rpc_context context); This overloading makes the code vastly simpler: all instantiations are now done with a small amount of generic instantiation code in a single .cpp rather than needing to go to hell and back with a nest of epee macros in a core header. - each RPC endpoint is now defined by the RPC types themselves, including its accessible names and permissions, in core_rpc_server_commands_defs.h: - every RPC structure now has a static `names()` function that returns the names by which the end point is accessible. (The first one is the primary, the others are for deprecated aliases). - RPC command wrappers define their permissions and type by inheriting from special tag classes: - rpc::RPC_COMMAND is a basic, admin-only, JSON command, available via JSON RPC. *All* JSON commands are now available via JSON RPC, instead of the previous mix of some being at /foo and others at /json_rpc. (Ones that were previously at /foo are still there for backwards compatibility; see `rpc::LEGACY` below). - rpc::PUBLIC specifies that the command should be available via a restricted RPC connection. - rpc::BINARY specifies that the command is not JSON, but rather is accessible as /name and takes and returns values in the magic epee binary "portable storage" (lol) data format. - rpc::LEGACY specifies that the command should be available via the non-json-rpc interface at `/name` for backwards compatibility (in addition to the JSON-RPC interface). - some epee serialization got unwrapped and de-templatized so that it can be moved into a .cpp file with just declarations in the .h. (This makes a *huge* difference for core_rpc_server_commands_defs.h and for every compilation unit that includes it which previously had to compile all the serialization code and then throw all by one copy away at link time). This required some new macros so as to not break a ton of places that will use the old way putting everything in the headers; The RPC code uses this as does a few other places; there are comments in contrib/epee/include/serialization/keyvalue_serialization.h as to how to use it. - Detemplatized a bunch of epee/storages code. Most of it should have have been using templates at all (because it can only ever be called with one type!), and now it isn't. This broke some things that didn't properly compile because of missing headers or (in one case) a messed up circular dependency. - Significantly simplified a bunch of over-templatized serialization code. - All RPC serialization definitions is now out of core_rpc_server_commands_defs.h and into a single .cpp file (core_rpc_server_commands_defs.cpp). - core RPC no longer uses the disgusting BEGIN_URI_MAP2/MAP_URI_BLAH_BLAH macros. This was a terrible design that forced slamming tons of code into a common header that didn't need to be there. - epee::struct_init is gone. It was a horrible hack that instiated multiple templates just so the coder could be so lazy and write `some_type var;` instead of properly value initializing with `some_type var{};`. - Removed a bunch of useless crap from epee. In particular, forcing extra template instantiations all over the place in order to nest return objects inside JSON RPC values is no longer needed, as are a bunch of stuff related to the above de-macroization of the code. - get_all_service_nodes, get_service_nodes, and get_n_service_nodes are now combined into a single `get_service_nodes` (with deprecated aliases for the others), which eliminates a fair amount of duplication. The biggest obstacle here was getting the requested fields reference passed through: this is now done by a new ability to stash a context in the serialization object that can be retrieved by a sub-serialized type. LMQ-specifics: - The LokiMQ instance moves into `cryptonote::core` rather than being inside cryptonote_protocol. Currently the instance is used both for qnet and rpc calls (and so needs to be in a common place), but I also intend future PRs to use the batching code for job processing (replacing the current threaded job queue). - rpc/lmq_server.h handles the actual LMQ-request-to-core-RPC glue. Unlike http_server it isn't technically running the whole LMQ stack from here, but the parallel name with http_server seemed appropriate. - All RPC endpoints are supported by LMQ under the same names as defined generically, but prefixed with `rpc.` for public commands and `admin.` for restricted ones. - service node keys are now always available, even when not running in `--service-node` mode: this is because we want the x25519 key for being able to offer CURVE encryption for lmq RPC end-points, and because it doesn't hurt to have them available all the time. In the RPC layer this is now called "get_service_keys" (with "get_service_node_key" as an alias) since they aren't strictly only for service nodes. This also means code needs to check m_service_node, and not m_service_node_keys, to tell if it is running as a service node. (This is also easier to notice because m_service_node_keys got renamed to `m_service_keys`). - Added block and mempool monitoring LMQ RPC endpoints: `sub.block` and `sub.mempool` subscribes the connection for new block and new mempool TX notifications. The latter can notify on just blink txes, or all new mempool txes (but only new ones -- txes dumped from a block don't trigger it). The client gets pushed a [`notify.block`, `height`, `hash`] or [`notify.tx`, `txhash`, `blob`] message when something arrives. Minor details: - rpc::version_t is now a {major,minor} pair. Forcing everyone to pack and unpack a uint32_t was gross. - Changed some macros to constexprs (e.g. CORE_RPC_ERROR_CODE_...). (This immediately revealed a couple of bugs in the RPC code that was assigning CORE_RPC_ERROR_CODE_... to a string, and it worked because the macro allows implicit conversion to a char). - De-templatizing useless templates in epee (i.e. a bunch of templated types that were never invoked with different types) revealed a painful circular dependency between epee and non-epee code for tor_address and i2p_address. This crap is now handled in a suitably named `net/epee_network_address_hack.cpp` hack because it really isn't trivial to extricate this mess. - Removed `epee/include/serialization/serialize_base.h`. Amazingly the code somehow still all works perfectly with this previously vital header removed. - Removed bitrotted, unused epee "crypted_storage" and "gzipped_inmemstorage" code. - Replaced a bunch of epee::misc_utils::auto_scope_leave_caller with LOKI_DEFERs. The epee version involves quite a bit more instantiation and is ugly as sin. Also made the `loki::defer` class invokable for some edge cases that need calling before destruction in particular conditions. - Moved the systemd code around; it makes much more sense to do the systemd started notification as in daemon.cpp as late as possible rather than in core (when we can still have startup failures, e.g. if the RPC layer can't start). - Made the systemd short status string available in the get_info RPC (and no longer require building with systemd). - during startup, print (only) the x25519 when not in SN mode, and continue to print all three when in SN mode. - DRYed out some RPC implementation code (such as set_limit) - Made wallet_rpc stop using a raw m_wallet pointer
2020-04-28 01:25:43 +02:00
rpc::version_t version;
auto w = wallet();
m_is_connected = w->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS);
if (!m_is_connected)
return Wallet::ConnectionStatus_Disconnected;
if (
#ifdef ENABLE_LIGHT_WALLET
2017-08-05 00:08:32 +02:00
// Version check is not implemented in light wallets nodes/wallets
!w->light_wallet() &&
#endif
version.first != rpc::VERSION.first)
return Wallet::ConnectionStatus_WrongVersion;
return Wallet::ConnectionStatus_Connected;
}
EXPORT
2016-04-26 12:46:20 +02:00
void WalletImpl::setTrustedDaemon(bool arg)
{
wallet()->set_trusted_daemon(arg);
2016-04-26 12:46:20 +02:00
}
EXPORT
2016-04-26 12:46:20 +02:00
bool WalletImpl::trustedDaemon() const
{
return wallet()->is_trusted_daemon();
2016-04-26 12:46:20 +02:00
}
EXPORT
bool WalletImpl::watchOnly() const
{
return wallet()->watch_only();
}
EXPORT
void WalletImpl::clearStatus() const
{
std::lock_guard l{m_statusMutex};
m_status = {Status_Ok, ""};
}
EXPORT
bool WalletImpl::setStatusError(std::string message) const
{
return setStatus(Status_Error, std::move(message));
}
EXPORT
bool WalletImpl::setStatusCritical(std::string message) const
{
return setStatus(Status_Critical, std::move(message));
}
EXPORT
bool WalletImpl::setStatus(int status, std::string message) const
{
std::lock_guard l{m_statusMutex};
m_status.first = status;
m_status.second = std::move(message);
return status == Status_Ok;
}
EXPORT
2016-07-10 16:17:23 +02:00
void WalletImpl::refreshThreadFunc()
{
LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread");
while (true) {
std::unique_lock lock{m_refreshMutex};
2016-07-10 16:17:23 +02:00
if (m_refreshThreadDone) {
break;
}
LOG_PRINT_L3(__FUNCTION__ << ": waiting for refresh...");
// if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval.
// if not - we wait forever
if (std::chrono::milliseconds max_delay{m_refreshIntervalMillis.load()};
max_delay > 0ms) {
m_refreshCV.wait_for(lock, max_delay);
} else {
m_refreshCV.wait(lock);
}
LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired...");
LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled);
auto st = status();
LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << st.first << ": " << st.second);
LOG_PRINT_L3(__FUNCTION__ << ": m_refreshShouldRescan: " << m_refreshShouldRescan);
if (m_refreshEnabled) {
LOG_PRINT_L3(__FUNCTION__ << ": refreshing...");
2016-07-10 16:17:23 +02:00
doRefresh();
}
}
LOG_PRINT_L3(__FUNCTION__ << ": refresh thread stopped");
}
EXPORT
2016-07-10 16:17:23 +02:00
void WalletImpl::doRefresh()
{
bool rescan = m_refreshShouldRescan.exchange(false);
2016-07-10 16:17:23 +02:00
// synchronizing async and sync refresh calls
std::lock_guard guard{m_refreshMutex2};
do {
try {
LOG_PRINT_L3(__FUNCTION__ << ": doRefresh, rescan = "<<rescan);
auto w = wallet();
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
// Disable refresh if wallet is disconnected or daemon isn't synced.
if (
#ifdef ENABLE_LIGHT_WALLET
w->light_wallet() ||
#endif
daemonSynced()) {
if(rescan)
w->rescan_blockchain(false);
w->refresh(trustedDaemon());
if (!m_synchronized) {
m_synchronized = true;
}
// assuming if we have empty history, it wasn't initialized yet
// for further history changes client need to update history in
// "on_money_received" and "on_money_sent" callbacks
if (m_history->count() == 0) {
m_history->refresh();
}
w->find_and_save_rings(false);
} else {
LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced");
}
} catch (const std::exception &e) {
setStatusError(e.what());
break;
}
} while (!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested
if (m_wallet2Callback->getListener()) {
m_wallet2Callback->getListener()->refreshed();
}
2016-07-10 16:17:23 +02:00
}
EXPORT
2016-07-10 16:17:23 +02:00
void WalletImpl::startRefresh()
{
if (!m_refreshEnabled) {
2017-01-30 18:00:10 +01:00
LOG_PRINT_L2(__FUNCTION__ << ": refresh started/resumed...");
2016-07-10 16:17:23 +02:00
m_refreshEnabled = true;
m_refreshCV.notify_one();
}
}
EXPORT
2016-07-10 16:17:23 +02:00
void WalletImpl::stopRefresh()
{
if (!m_refreshThreadDone) {
m_refreshEnabled = false;
m_refreshThreadDone = true;
m_refreshCV.notify_one();
2016-07-10 16:17:23 +02:00
m_refreshThread.join();
}
}
EXPORT
void WalletImpl::pauseRefresh()
{
LOG_PRINT_L2(__FUNCTION__ << ": refresh paused...");
// TODO synchronize access
if (!m_refreshThreadDone) {
m_refreshEnabled = false;
}
}
EXPORT
bool WalletImpl::isNewWallet() const
{
// in case wallet created without daemon connection, closed and opened again,
// it's the same case as if it created from scratch, i.e. we need "fast sync"
// with the daemon (pull hashes instead of pull blocks).
// If wallet cache is rebuilt, creation height stored in .keys is used.
// Watch only wallet is a copy of an existing wallet.
return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_recoveringFromDevice || m_rebuildWalletCache) && !watchOnly();
}
EXPORT
void WalletImpl::pendingTxPostProcess(PendingTransactionImpl * pending)
{
// If the device being used is HW device with cold signing protocol, cold sign then.
if (!wallet()->get_account().get_device().has_tx_cold_sign()){
return;
}
tools::wallet2::signed_tx_set exported_txs;
std::vector<cryptonote::address_parse_info> dsts_info;
wallet()->cold_sign_tx(pending->m_pending_tx, exported_txs, dsts_info, pending->m_tx_device_aux);
pending->m_key_images = exported_txs.key_images;
pending->m_pending_tx = exported_txs.ptx;
}
EXPORT
bool WalletImpl::doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
{
auto w = wallet();
if (!w->init(daemon_address, m_daemon_login, /*proxy=*/ "", upper_transaction_size_limit))
return false;
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
// If daemon isn't synced a calculated block height will be used instead
//TODO: Handle light wallet scenario where block height = 0.
if (isNewWallet() && daemonSynced()) {
LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight());
w->set_refresh_from_block_height(daemonBlockChainHeight());
}
if (m_rebuildWalletCache)
LOG_PRINT_L2(__FUNCTION__ << ": Rebuilding wallet cache, fast refresh until block " << w->get_refresh_from_block_height());
if (Utils::isAddressLocal(daemon_address)) {
this->setTrustedDaemon(true);
m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
} else {
this->setTrustedDaemon(false);
m_refreshIntervalMillis = DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS;
}
return true;
}
EXPORT
2017-01-08 23:53:24 +01:00
bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error)
{
return m_wallet_ptr->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error);
2017-01-08 23:53:24 +01:00
}
EXPORT
2017-08-09 12:38:29 +02:00
std::string WalletImpl::getDefaultDataDir() const
{
return tools::get_default_data_dir();
}
EXPORT
2017-01-12 22:10:38 +01:00
bool WalletImpl::rescanSpent()
{
clearStatus();
if (!trustedDaemon()) {
setStatusError(tr("Rescan spent can only be used with a trusted daemon"));
2017-01-12 22:10:38 +01:00
return false;
}
try {
wallet()->rescan_spent();
2017-01-12 22:10:38 +01:00
} catch (const std::exception &e) {
LOG_ERROR(__FUNCTION__ << " error: " << e.what());
setStatusError(e.what());
2017-01-12 22:10:38 +01:00
return false;
}
return true;
}
EXPORT
void WalletImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const
{
wallet()->get_hard_fork_info(version, earliest_height);
}
EXPORT
std::optional<uint8_t> WalletImpl::hardForkVersion() const
{
return m_wallet_ptr->get_hard_fork_version();
}
EXPORT
bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const
{
return wallet()->use_fork_rules(version,early_blocks);
}
EXPORT
bool WalletImpl::blackballOutputs(const std::vector<std::string> &outputs, bool add)
2018-03-04 14:32:35 +01:00
{
std::vector<std::pair<uint64_t, uint64_t>> raw_outputs;
raw_outputs.reserve(outputs.size());
uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets;
for (const std::string &str: outputs)
2018-03-04 14:32:35 +01:00
{
if (sscanf(str.c_str(), "@%" PRIu64, &amount) == 1)
continue;
if (amount == std::numeric_limits<uint64_t>::max())
2018-03-04 14:32:35 +01:00
{
setStatusError("First line is not an amount");
return true;
}
if (sscanf(str.c_str(), "%" PRIu64 "*%" PRIu64, &offset, &num_offsets) == 2 && num_offsets <= std::numeric_limits<uint64_t>::max() - offset)
{
while (num_offsets--)
raw_outputs.push_back(std::make_pair(amount, offset++));
}
else if (sscanf(str.c_str(), "%" PRIu64, &offset) == 1)
{
raw_outputs.push_back(std::make_pair(amount, offset));
}
else
{
setStatusError(tr("Invalid output: ") + str);
return false;
2018-03-04 14:32:35 +01:00
}
}
bool ret = wallet()->set_blackballed_outputs(raw_outputs, add);
2018-03-04 14:32:35 +01:00
if (!ret)
{
setStatusError(tr("Failed to mark outputs as spent"));
2018-03-04 14:32:35 +01:00
return false;
}
return true;
}
EXPORT
bool WalletImpl::blackballOutput(const std::string &amount, const std::string &offset)
{
uint64_t raw_amount, raw_offset;
if (!epee::string_tools::get_xtype_from_string(raw_amount, amount))
{
setStatusError(tr("Failed to parse output amount"));
return false;
}
if (!epee::string_tools::get_xtype_from_string(raw_offset, offset))
{
setStatusError(tr("Failed to parse output offset"));
return false;
}
bool ret = wallet()->blackball_output(std::make_pair(raw_amount, raw_offset));
if (!ret)
{
setStatusError(tr("Failed to mark output as spent"));
return false;
}
return true;
}
EXPORT
bool WalletImpl::unblackballOutput(const std::string &amount, const std::string &offset)
2018-03-04 14:32:35 +01:00
{
uint64_t raw_amount, raw_offset;
if (!epee::string_tools::get_xtype_from_string(raw_amount, amount))
{
setStatusError(tr("Failed to parse output amount"));
return false;
}
if (!epee::string_tools::get_xtype_from_string(raw_offset, offset))
2018-03-04 14:32:35 +01:00
{
setStatusError(tr("Failed to parse output offset"));
2018-03-04 14:32:35 +01:00
return false;
}
bool ret = wallet()->unblackball_output(std::make_pair(raw_amount, raw_offset));
2018-03-04 14:32:35 +01:00
if (!ret)
{
setStatusError(tr("Failed to mark output as unspent"));
2018-03-04 14:32:35 +01:00
return false;
}
return true;
}
EXPORT
2018-03-05 16:04:59 +01:00
bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ring) const
{
crypto::key_image raw_key_image;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(key_image, raw_key_image))
2018-03-05 16:04:59 +01:00
{
setStatusError(tr("Failed to parse key image"));
2018-03-05 16:04:59 +01:00
return false;
}
bool ret = wallet()->get_ring(raw_key_image, ring);
2018-03-05 16:04:59 +01:00
if (!ret)
{
setStatusError(tr("Failed to get ring"));
2018-03-05 16:04:59 +01:00
return false;
}
return true;
}
EXPORT
bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const
{
crypto::hash raw_txid;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(txid, raw_txid))
{
setStatusError(tr("Failed to parse txid"));
return false;
}
std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> raw_rings;
bool ret = wallet()->get_rings(raw_txid, raw_rings);
if (!ret)
{
setStatusError(tr("Failed to get rings"));
return false;
}
for (const auto &r: raw_rings)
{
2020-10-23 22:32:28 +02:00
rings.push_back(std::make_pair(tools::type_to_hex(r.first), r.second));
}
return true;
}
EXPORT
2018-03-05 16:04:59 +01:00
bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative)
{
crypto::key_image raw_key_image;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(key_image, raw_key_image))
2018-03-05 16:04:59 +01:00
{
setStatusError(tr("Failed to parse key image"));
2018-03-05 16:04:59 +01:00
return false;
}
bool ret = wallet()->set_ring(raw_key_image, ring, relative);
2018-03-05 16:04:59 +01:00
if (!ret)
{
setStatusError(tr("Failed to set ring"));
2018-03-05 16:04:59 +01:00
return false;
}
return true;
}
EXPORT
void WalletImpl::segregatePreForkOutputs(bool segregate)
{
wallet()->segregate_pre_fork_outputs(segregate);
}
EXPORT
void WalletImpl::segregationHeight(uint64_t height)
{
wallet()->segregation_height(height);
}
EXPORT
void WalletImpl::keyReuseMitigation2(bool mitigation)
{
wallet()->key_reuse_mitigation2(mitigation);
}
EXPORT
bool WalletImpl::lockKeysFile()
{
return wallet()->lock_keys_file();
}
EXPORT
bool WalletImpl::unlockKeysFile()
{
return wallet()->unlock_keys_file();
}
EXPORT
bool WalletImpl::isKeysFileLocked()
{
return wallet()->is_keys_file_locked();
}
EXPORT
PendingTransaction* WalletImpl::stakePending(const std::string& sn_key_str, const uint64_t& amount)
{
/// Note(maxim): need to be careful to call `WalletImpl::disposeTransaction` when it is no longer needed
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
std::string error_msg;
crypto::public_key sn_key;
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(sn_key_str, sn_key))
{
error_msg = "Failed to parse service node pubkey";
LOG_ERROR(error_msg);
transaction->setError(error_msg);
return transaction;
}
tools::wallet2::stake_result stake_result = wallet()->create_stake_tx(sn_key, amount);
if (stake_result.status != tools::wallet2::stake_result_status::success)
{
Infinite Staking Part 2 (#406) * Cleanup and undoing some protocol breakages * Simplify expiration of nodes * Request unlock schedules entire node for expiration * Fix off by one in expiring nodes * Undo expiring code for pre v10 nodes * Fix RPC returning register as unlock height and not checking 0 * Rename key image unlock height const * Undo testnet hardfork debug changes * Remove is_type for get_type, fix missing var rename * Move serialisable data into public namespace * Serialise tx types properly * Fix typo in no service node known msg * Code review * Fix == to >= on serialising tx type * Code review 2 * Fix tests and key image unlock * Add command to print locked key images * Update ui to display lock stakes, query in print cmd blacklist * Modify print stakes to be less slow * Remove autostaking code * Refactor staking into sweep functions It appears staking was derived off stake_main written separately at implementation at the beginning. This merges them back into a common code path, after removing autostake there's only some minor differences. It also makes sure that any changes to sweeping upstream are going to be considered in the staking process which we want. * Display unlock height for stakes * Begin creating output blacklist * Make blacklist output a migration step * Implement get_output_blacklist for lmdb * In wallet output selection ignore blacklisted outputs * Apply blacklisted outputs to output selection * Fix broken tests, switch key image unlock * Fix broken unit_tests * Begin change to limit locked key images to 4 globally * Revamp prepare registration for new min contribution rules * Fix up old back case in prepare registration * Remove debug code * Cleanup debug code and some unecessary changes * Fix migration step on mainnet db * Fix blacklist outputs for pre-existing DB's * Remove irrelevant note * Tweak scanning addresses for locked stakes Since we only now allow contributions from the primary address we can skip checking all subaddress + lookahead to speed up wallet scanning * Define macro for SCNu64 for Mingw * Fix failure on empty DB * Add missing error msg, remove contributor from stake * Improve staking messages * Flush prompt to always display * Return the msg from stake failure and fix stake parsing error * Tweak fork rules for smaller bulletproofs * Tweak pooled nodes minimum amounts * Fix crash on exit, there's no need to store on destructor Since all information about service nodes is derived from the blockchain and we store state every time we receive a block, storing in the destructor is redundant as there is no new information to store. * Make prompt be consistent with CLI * Check max number of key images from per user to node * Implement error message on get_output_blacklist failure * Remove resolved TODO's/comments * Handle infinite staking in print_sn * Atoi->strtol, fix prepare_registration, virtual override, stale msgs
2019-02-14 02:12:57 +01:00
error_msg = "Failed to create a stake transaction: " + stake_result.msg;
LOG_ERROR(error_msg);
transaction->setError(error_msg);
return transaction;
}
transaction->m_pending_tx = {stake_result.ptx};
return transaction;
}
EXPORT
StakeUnlockResult* WalletImpl::canRequestStakeUnlock(const std::string &sn_key)
{
crypto::public_key snode_key;
if (!tools::hex_to_type(sn_key, snode_key))
{
tools::wallet2::request_stake_unlock_result res{};
res.success = false;
res.msg = "Failed to Parse Service Node Key";
return new StakeUnlockResultImpl(*this, res);
}
return new StakeUnlockResultImpl(*this, wallet()->can_request_stake_unlock(snode_key));
}
EXPORT
StakeUnlockResult* WalletImpl::requestStakeUnlock(const std::string &sn_key)
{
tools::wallet2::request_stake_unlock_result res = {};
crypto::public_key snode_key;
if (!tools::hex_to_type(sn_key, snode_key))
{
res.success = false;
res.msg = "Failed to Parse Service Node Key";
return new StakeUnlockResultImpl(*this, res);
}
auto w = wallet();
tools::wallet2::request_stake_unlock_result unlock_result = w->can_request_stake_unlock(snode_key);
if (unlock_result.success)
{
try
{
w->commit_tx(unlock_result.ptx);
}
catch(const std::exception &e)
{
res.success = false;
res.msg = "Failed to commit tx.";
return new StakeUnlockResultImpl(*this, res);
}
}
else
{
res.success = false;
res.msg = tr("Cannot request stake unlock: " + unlock_result.msg);
return new StakeUnlockResultImpl(*this, res);
}
return new StakeUnlockResultImpl(*this, unlock_result);
}
EXPORT
uint64_t WalletImpl::coldKeyImageSync(uint64_t &spent, uint64_t &unspent)
{
return wallet()->cold_key_image_sync(spent, unspent);
}
EXPORT
void WalletImpl::deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId)
{
2020-06-02 00:30:19 +02:00
std::optional<crypto::hash8> payment_id_param = std::nullopt;
if (!paymentId.empty())
2020-10-23 22:32:28 +02:00
if (!tools::hex_to_type(paymentId, payment_id_param.emplace()))
throw std::runtime_error("Invalid payment ID");
wallet()->device_show_address(accountIndex, addressIndex, payment_id_param);
}
} // namespace