aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/blockchain_db/blockchain_db.h1
-rw-r--r--src/blockchain_utilities/blockchain_blackball.cpp2
-rw-r--r--src/blockchain_utilities/blockchain_import.cpp20
-rw-r--r--src/common/password.cpp2
-rw-r--r--src/common/threadpool.cpp8
-rw-r--r--src/common/threadpool.h14
-rw-r--r--src/common/util.cpp25
-rw-r--r--src/common/util.h3
-rw-r--r--src/cryptonote_basic/account.cpp2
-rw-r--r--src/cryptonote_basic/connection_context.h10
-rw-r--r--src/cryptonote_basic/miner.cpp5
-rw-r--r--src/cryptonote_core/blockchain.cpp50
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp7
-rw-r--r--src/cryptonote_core/cryptonote_core.h3
-rw-r--r--src/cryptonote_core/tx_pool.cpp45
-rw-r--r--src/cryptonote_core/tx_pool.h4
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl3
-rw-r--r--src/daemon/command_line_args.h5
-rw-r--r--src/daemon/command_parser_executor.cpp19
-rw-r--r--src/daemon/main.cpp6
-rw-r--r--src/daemon/rpc_command_executor.cpp4
-rw-r--r--src/debug_utilities/cn_deserialize.cpp3
-rw-r--r--src/debug_utilities/object_sizes.cpp5
-rw-r--r--src/device/device_ledger.cpp9
-rw-r--r--src/device/device_ledger.hpp5
-rw-r--r--src/mnemonics/electrum-words.cpp28
-rw-r--r--src/p2p/net_node.h3
-rw-r--r--src/p2p/net_node.inl27
-rw-r--r--src/ringct/rctOps.cpp5
-rw-r--r--src/ringct/rctOps.h1
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h2
-rw-r--r--src/rpc/rpc_args.cpp2
-rw-r--r--src/simplewallet/simplewallet.cpp283
-rw-r--r--src/wallet/api/wallet.cpp88
-rw-r--r--src/wallet/api/wallet.h6
-rw-r--r--src/wallet/api/wallet2_api.h32
-rw-r--r--src/wallet/api/wallet_manager.cpp20
-rw-r--r--src/wallet/api/wallet_manager.h6
-rw-r--r--src/wallet/wallet2.cpp216
-rw-r--r--src/wallet/wallet2.h16
-rw-r--r--src/wallet/wallet_args.cpp8
-rw-r--r--src/wallet/wallet_rpc_server.cpp224
-rw-r--r--src/wallet/wallet_rpc_server.h10
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h97
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h4
45 files changed, 1094 insertions, 244 deletions
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 19ba32340..564016fc9 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -148,6 +148,7 @@ struct txpool_tx_meta_t
uint8_t relayed;
uint8_t do_not_relay;
uint8_t double_spend_seen: 1;
+ uint8_t bf_padding: 7;
uint8_t padding[76]; // till 192 bytes
};
diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp
index 95eb2f73d..52d2938cd 100644
--- a/src/blockchain_utilities/blockchain_blackball.cpp
+++ b/src/blockchain_utilities/blockchain_blackball.cpp
@@ -235,7 +235,7 @@ int main(int argc, char* argv[])
"blackball-db-dir", "Specify blackball database directory",
get_default_db_path(),
{{ &arg_testnet_on, &arg_stagenet_on }},
- [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val) {
+ [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string {
if (testnet_stagenet[0])
return (boost::filesystem::path(val) / "testnet").string();
else if (testnet_stagenet[1])
diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp
index caa549c13..a58257719 100644
--- a/src/blockchain_utilities/blockchain_import.cpp
+++ b/src/blockchain_utilities/blockchain_import.cpp
@@ -593,8 +593,8 @@ int main(int argc, char* argv[])
const command_line::arg_descriptor<std::string> arg_database = {
"database", available_dbs.c_str(), default_db_type
};
- const command_line::arg_descriptor<bool> arg_verify = {"guard-against-pwnage",
- "Verify blocks and transactions during import (only disable if you exported the file yourself)", true};
+ const command_line::arg_descriptor<bool> arg_noverify = {"dangerous-unverified-import",
+ "Blindly trust the import file and use potentially malicious blocks and transactions during import (only enable if you exported the file yourself)", false};
const command_line::arg_descriptor<bool> arg_batch = {"batch",
"Batch transactions for faster import", true};
const command_line::arg_descriptor<bool> arg_resume = {"resume",
@@ -614,7 +614,7 @@ int main(int argc, char* argv[])
// call add_options() directly for these arguments since
// command_line helpers support only boolean switch, not boolean argument
desc_cmd_sett.add_options()
- (arg_verify.name, make_semantic(arg_verify), arg_verify.description)
+ (arg_noverify.name, make_semantic(arg_noverify), arg_noverify.description)
(arg_batch.name, make_semantic(arg_batch), arg_batch.description)
(arg_resume.name, make_semantic(arg_resume), arg_resume.description)
;
@@ -633,7 +633,7 @@ int main(int argc, char* argv[])
if (! r)
return 1;
- opt_verify = command_line::get_arg(vm, arg_verify);
+ opt_verify = !command_line::get_arg(vm, arg_noverify);
opt_batch = command_line::get_arg(vm, arg_batch);
opt_resume = command_line::get_arg(vm, arg_resume);
block_stop = command_line::get_arg(vm, arg_block_stop);
@@ -738,6 +738,18 @@ int main(int argc, char* argv[])
MINFO("bootstrap file path: " << import_file_path);
MINFO("database path: " << m_config_folder);
+ if (!opt_verify)
+ {
+ MCLOG_RED(el::Level::Warning, "global", "\n"
+ "Import is set to proceed WITHOUT VERIFICATION.\n"
+ "This is a DANGEROUS operation: if the file was tampered with in transit, or obtained from a malicious source,\n"
+ "you could end up with a compromised database. It is recommended to NOT use " << arg_noverify.name << ".\n"
+ "*****************************************************************************************\n"
+ "You have 90 seconds to press ^C or terminate this program before unverified import starts\n"
+ "*****************************************************************************************");
+ sleep(90);
+ }
+
cryptonote::cryptonote_protocol_stub pr; //TODO: stub only for this kind of test, make real validation of relayed objects
cryptonote::core core(&pr);
diff --git a/src/common/password.cpp b/src/common/password.cpp
index 9336a14fc..3ce2ba42a 100644
--- a/src/common/password.cpp
+++ b/src/common/password.cpp
@@ -164,7 +164,7 @@ namespace
while (true)
{
if (message)
- std::cout << message <<": ";
+ std::cout << message <<": " << std::flush;
if (!read_from_tty(pass1))
return false;
if (verify)
diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp
index 51e071577..fb238dca7 100644
--- a/src/common/threadpool.cpp
+++ b/src/common/threadpool.cpp
@@ -39,11 +39,11 @@ static __thread int depth = 0;
namespace tools
{
-threadpool::threadpool() : running(true), active(0) {
+threadpool::threadpool(unsigned int max_threads) : running(true), active(0) {
boost::thread::attributes attrs;
attrs.set_stack_size(THREAD_STACK_SIZE);
- max = tools::get_max_concurrency();
- size_t i = max;
+ max = max_threads ? max_threads : tools::get_max_concurrency();
+ unsigned int i = max;
while(i--) {
threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this)));
}
@@ -78,7 +78,7 @@ void threadpool::submit(waiter *obj, std::function<void()> f) {
}
}
-int threadpool::get_max_concurrency() {
+unsigned int threadpool::get_max_concurrency() const {
return max;
}
diff --git a/src/common/threadpool.h b/src/common/threadpool.h
index 34152541c..bf80a87f6 100644
--- a/src/common/threadpool.h
+++ b/src/common/threadpool.h
@@ -46,6 +46,9 @@ public:
static threadpool instance;
return instance;
}
+ static threadpool *getNewForUnitTests(unsigned max_threads = 0) {
+ return new threadpool(max_threads);
+ }
// The waiter lets the caller know when all of its
// tasks are completed.
@@ -66,11 +69,12 @@ public:
// task to finish.
void submit(waiter *waiter, std::function<void()> f);
- int get_max_concurrency();
+ unsigned int get_max_concurrency() const;
+
+ ~threadpool();
private:
- threadpool();
- ~threadpool();
+ threadpool(unsigned int max_threads = 0);
typedef struct entry {
waiter *wo;
std::function<void()> f;
@@ -79,8 +83,8 @@ public:
boost::condition_variable has_work;
boost::mutex mutex;
std::vector<boost::thread> threads;
- int active;
- int max;
+ unsigned int active;
+ unsigned int max;
bool running;
void run();
};
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 008610117..329352e94 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -727,6 +727,13 @@ std::string get_nix_version_display_string()
bool is_local_address(const std::string &address)
{
+ // always assume Tor/I2P addresses to be untrusted by default
+ if (boost::ends_with(address, ".onion") || boost::ends_with(address, ".i2p"))
+ {
+ MDEBUG("Address '" << address << "' is Tor/I2P, non local");
+ return false;
+ }
+
// extract host
epee::net_utils::http::url_content u_c;
if (!epee::net_utils::parse_url(address, u_c))
@@ -820,4 +827,22 @@ std::string get_nix_version_display_string()
return false;
return true;
}
+
+ boost::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str)
+ {
+ auto pos = str.find(":");
+ bool r = pos != std::string::npos;
+ uint32_t major;
+ r = r && epee::string_tools::get_xtype_from_string(major, str.substr(0, pos));
+ uint32_t minor;
+ r = r && epee::string_tools::get_xtype_from_string(minor, str.substr(pos + 1));
+ if (r)
+ {
+ return std::make_pair(major, minor);
+ }
+ else
+ {
+ return {};
+ }
+ }
}
diff --git a/src/common/util.h b/src/common/util.h
index 7caf0e3c5..dc426830b 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -32,6 +32,7 @@
#include <boost/thread/locks.hpp>
#include <boost/thread/mutex.hpp>
+#include <boost/optional.hpp>
#include <system_error>
#include <csignal>
#include <cstdio>
@@ -214,4 +215,6 @@ namespace tools
bool sha256sum(const std::string &filename, crypto::hash &hash);
bool is_hdd(const char *path);
+
+ boost::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str);
}
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index bab991d19..aac6ec22b 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -157,7 +157,7 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_base::create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey)
{
crypto::secret_key fake;
- memset(&fake, 0, sizeof(fake));
+ memset(&unwrap(fake), 0, sizeof(fake));
create_from_keys(address, fake, viewkey);
}
//-----------------------------------------------------------------
diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h
index 5cd1709ab..3f4651565 100644
--- a/src/cryptonote_basic/connection_context.h
+++ b/src/cryptonote_basic/connection_context.h
@@ -67,15 +67,15 @@ namespace cryptonote
switch (s)
{
case cryptonote_connection_context::state_before_handshake:
- return "state_before_handshake";
+ return "before_handshake";
case cryptonote_connection_context::state_synchronizing:
- return "state_synchronizing";
+ return "synchronizing";
case cryptonote_connection_context::state_standby:
- return "state_standby";
+ return "standby";
case cryptonote_connection_context::state_idle:
- return "state_idle";
+ return "idle";
case cryptonote_connection_context::state_normal:
- return "state_normal";
+ return "normal";
default:
return "unknown";
}
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index 2c777f5a2..3a3222f9b 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -328,6 +328,11 @@ namespace cryptonote
LOG_PRINT_L0("Background mining controller thread started" );
}
+ if(get_ignore_battery())
+ {
+ MINFO("Ignoring battery");
+ }
+
return true;
}
//-----------------------------------------------------------------------------------------------------
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index fbb2a2223..78f61766f 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -444,7 +444,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
m_db->block_txn_stop();
uint64_t num_popped_blocks = 0;
- while (true)
+ while (!m_db->is_read_only())
{
const uint64_t top_height = m_db->height() - 1;
const crypto::hash top_id = m_db->top_block_hash();
@@ -827,7 +827,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
// then when the next block difficulty is queried, push the latest height data and
// pop the oldest one from the list. This only requires 1x read per height instead
// of doing 735 (DIFFICULTY_BLOCKS_COUNT).
- if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1))
+ if (m_timestamps_and_difficulties_height != 0 && ((height - m_timestamps_and_difficulties_height) == 1) && m_timestamps.size() >= DIFFICULTY_BLOCKS_COUNT)
{
uint64_t index = height - 1;
m_timestamps.push_back(m_db->get_block_timestamp(index));
@@ -1960,14 +1960,21 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA
res.outs.clear();
res.outs.reserve(req.outputs.size());
- for (const auto &i: req.outputs)
+ try
{
- // get tx_hash, tx_out_index from DB
- const output_data_t od = m_db->get_output_key(i.amount, i.index);
- tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index);
- bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
+ for (const auto &i: req.outputs)
+ {
+ // get tx_hash, tx_out_index from DB
+ const output_data_t od = m_db->get_output_key(i.amount, i.index);
+ tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index);
+ bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
- res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first});
+ res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first});
+ }
+ }
+ catch (const std::exception &e)
+ {
+ return false;
}
return true;
}
@@ -2095,16 +2102,19 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container
{
try
{
- blocks.push_back(std::make_pair(m_db->get_block_blob(block_hash), block()));
- if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second))
+ uint64_t height = 0;
+ if (m_db->block_exists(block_hash, &height))
{
- LOG_ERROR("Invalid block");
- return false;
+ blocks.push_back(std::make_pair(m_db->get_block_blob_from_height(height), block()));
+ if (!parse_and_validate_block_from_blob(blocks.back().first, blocks.back().second))
+ {
+ LOG_ERROR("Invalid block: " << block_hash);
+ blocks.pop_back();
+ missed_bs.push_back(block_hash);
+ }
}
- }
- catch (const BLOCK_DNE& e)
- {
- missed_bs.push_back(block_hash);
+ else
+ missed_bs.push_back(block_hash);
}
catch (const std::exception& e)
{
@@ -2205,7 +2215,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
CRITICAL_REGION_LOCAL(m_blockchain_lock);
bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height);
- resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(m_db->height() - 1);
+ resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1);
return result;
}
@@ -2282,19 +2292,19 @@ bool Blockchain::have_block(const crypto::hash& id) const
if(m_db->block_exists(id))
{
- LOG_PRINT_L3("block exists in main chain");
+ LOG_PRINT_L2("block " << id << " found in main chain");
return true;
}
if(m_alternative_chains.count(id))
{
- LOG_PRINT_L3("block found in m_alternative_chains");
+ LOG_PRINT_L2("block " << id << " found in m_alternative_chains");
return true;
}
if(m_invalid_blocks.count(id))
{
- LOG_PRINT_L3("block found in m_invalid_blocks");
+ LOG_PRINT_L2("block " << id << " found in m_invalid_blocks");
return true;
}
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 7d34415c4..7fc81a87d 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -170,7 +170,6 @@ namespace cryptonote
m_last_dns_checkpoints_update(0),
m_last_json_checkpoints_update(0),
m_disable_dns_checkpoints(false),
- m_threadpool(tools::threadpool::getInstance()),
m_update_download(0),
m_nettype(UNDEFINED)
{
@@ -676,15 +675,17 @@ namespace cryptonote
bool core::handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay)
{
TRY_ENTRY();
+ CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
struct result { bool res; cryptonote::transaction tx; crypto::hash hash; crypto::hash prefix_hash; bool in_txpool; bool in_blockchain; };
std::vector<result> results(tx_blobs.size());
tvc.resize(tx_blobs.size());
+ tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
std::list<blobdata>::const_iterator it = tx_blobs.begin();
for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
- m_threadpool.submit(&waiter, [&, i, it] {
+ tpool.submit(&waiter, [&, i, it] {
try
{
results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay);
@@ -711,7 +712,7 @@ namespace cryptonote
}
else
{
- m_threadpool.submit(&waiter, [&, i, it] {
+ tpool.submit(&waiter, [&, i, it] {
try
{
results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay);
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 567966d48..91bd50729 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -39,7 +39,6 @@
#include "cryptonote_protocol/cryptonote_protocol_handler_common.h"
#include "storages/portable_storage_template_helper.h"
#include "common/download.h"
-#include "common/threadpool.h"
#include "common/command_line.h"
#include "tx_pool.h"
#include "blockchain.h"
@@ -991,8 +990,6 @@ namespace cryptonote
std::unordered_set<crypto::hash> bad_semantics_txes[2];
boost::mutex bad_semantics_txes_lock;
- tools::threadpool& m_threadpool;
-
enum {
UPDATES_DISABLED,
UPDATES_NOTIFY,
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index bf1fe476e..9df3b458b 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -239,6 +239,7 @@ namespace cryptonote
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
+ meta.bf_padding = 0;
memset(meta.padding, 0, sizeof(meta.padding));
try
{
@@ -278,6 +279,7 @@ namespace cryptonote
meta.relayed = relayed;
meta.do_not_relay = do_not_relay;
meta.double_spend_seen = false;
+ meta.bf_padding = 0;
memset(meta.padding, 0, sizeof(meta.padding));
try
@@ -927,8 +929,26 @@ namespace cryptonote
m_transactions_lock.unlock();
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const
+ bool tx_memory_pool::is_transaction_ready_to_go(txpool_tx_meta_t& txd, const cryptonote::blobdata &txblob, transaction &tx) const
{
+ struct transction_parser
+ {
+ transction_parser(const cryptonote::blobdata &txblob, transaction &tx): txblob(txblob), tx(tx), parsed(false) {}
+ cryptonote::transaction &operator()()
+ {
+ if (!parsed)
+ {
+ if (!parse_and_validate_tx_from_blob(txblob, tx))
+ throw std::runtime_error("failed to parse transaction blob");
+ parsed = true;
+ }
+ return tx;
+ }
+ const cryptonote::blobdata &txblob;
+ transaction &tx;
+ bool parsed;
+ } lazy_tx(txblob, tx);
+
//not the best implementation at this time, sorry :(
//check is ring_signature already checked ?
if(txd.max_used_block_id == null_hash)
@@ -938,7 +958,7 @@ namespace cryptonote
return false;//we already sure that this tx is broken for this height
tx_verification_context tvc;
- if(!m_blockchain.check_tx_inputs(tx, txd.max_used_block_height, txd.max_used_block_id, tvc))
+ if(!m_blockchain.check_tx_inputs(lazy_tx(), txd.max_used_block_height, txd.max_used_block_id, tvc))
{
txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
@@ -955,7 +975,7 @@ namespace cryptonote
return false;
//check ring signature again, it is possible (with very small chance) that this transaction become again valid
tx_verification_context tvc;
- if(!m_blockchain.check_tx_inputs(tx, txd.max_used_block_height, txd.max_used_block_id, tvc))
+ if(!m_blockchain.check_tx_inputs(lazy_tx(), txd.max_used_block_height, txd.max_used_block_id, tvc))
{
txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
@@ -964,7 +984,7 @@ namespace cryptonote
}
}
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
- if(m_blockchain.have_tx_keyimges_as_spent(tx))
+ if(m_blockchain.have_tx_keyimges_as_spent(lazy_tx()))
{
txd.double_spend_seen = true;
return false;
@@ -1140,18 +1160,21 @@ namespace cryptonote
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(sorted_it->second);
cryptonote::transaction tx;
- if (!parse_and_validate_tx_from_blob(txblob, tx))
- {
- MERROR("Failed to parse tx from txpool");
- sorted_it++;
- continue;
- }
// Skip transactions that are not ready to be
// included into the blockchain or that are
// missing key images
const cryptonote::txpool_tx_meta_t original_meta = meta;
- bool ready = is_transaction_ready_to_go(meta, tx);
+ bool ready = false;
+ try
+ {
+ ready = is_transaction_ready_to_go(meta, txblob, tx);
+ }
+ catch (const std::exception &e)
+ {
+ MERROR("Failed to check transaction readiness: " << e.what());
+ // continue, not fatal
+ }
if (memcmp(&original_meta, &meta, sizeof(meta)))
{
try
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index 19cd83ed9..4ce2f085d 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -499,10 +499,12 @@ namespace cryptonote
* @brief check if a transaction is a valid candidate for inclusion in a block
*
* @param txd the transaction to check (and info about it)
+ * @param txblob the transaction blob to check
+ * @param tx the parsed transaction, if successful
*
* @return true if the transaction is good to go, otherwise false
*/
- bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const;
+ bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, const cryptonote::blobdata &txblob, transaction &tx) const;
/**
* @brief mark all transactions double spending the one passed
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 2e1df8078..dd34e461e 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -1712,6 +1712,9 @@ skip:
template<class t_core>
void t_cryptonote_protocol_handler<t_core>::drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans)
{
+ LOG_DEBUG_CC(context, "dropping connection id " << context.m_connection_id <<
+ ", add_fail " << add_fail << ", flush_all_spans " << flush_all_spans);
+
if (add_fail)
m_p2p->add_host_fail(context.m_remote_address);
diff --git a/src/daemon/command_line_args.h b/src/daemon/command_line_args.h
index 4673590aa..cba71bf3b 100644
--- a/src/daemon/command_line_args.h
+++ b/src/daemon/command_line_args.h
@@ -72,6 +72,11 @@ namespace daemon_args
, "Specify maximum log file size [B]"
, MAX_LOG_FILE_SIZE
};
+ const command_line::arg_descriptor<std::size_t> arg_max_log_files = {
+ "max-log-files"
+ , "Specify maximum number of rotated log files to be saved (no limit by setting to 0)"
+ , MAX_LOG_FILES
+ };
const command_line::arg_descriptor<std::string> arg_log_level = {
"log-level"
, ""
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index 34d9fb4c8..aa688294d 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -27,6 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "common/dns_utils.h"
+#include "common/command_line.h"
#include "version.h"
#include "daemon/command_parser_executor.h"
@@ -326,12 +327,26 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg
if(args.size() == 4)
{
- ignore_battery = args[3] == "true";
+ if(args[3] == "true" || command_line::is_yes(args[3]) || args[3] == "1")
+ {
+ ignore_battery = true;
+ }
+ else if(args[3] != "false" && !command_line::is_no(args[3]) && args[3] != "0")
+ {
+ return false;
+ }
}
if(args.size() >= 3)
{
- do_background_mining = args[2] == "true";
+ if(args[2] == "true" || command_line::is_yes(args[2]) || args[2] == "1")
+ {
+ do_background_mining = true;
+ }
+ else if(args[2] != "false" && !command_line::is_no(args[2]) && args[2] != "0")
+ {
+ return false;
+ }
}
if(args.size() >= 2)
diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp
index 49494e889..88bb1fd0c 100644
--- a/src/daemon/main.cpp
+++ b/src/daemon/main.cpp
@@ -84,6 +84,7 @@ int main(int argc, char const * argv[])
command_line::add_arg(core_settings, daemon_args::arg_log_file);
command_line::add_arg(core_settings, daemon_args::arg_log_level);
command_line::add_arg(core_settings, daemon_args::arg_max_log_file_size);
+ command_line::add_arg(core_settings, daemon_args::arg_max_log_files);
command_line::add_arg(core_settings, daemon_args::arg_max_concurrency);
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_ip);
command_line::add_arg(core_settings, daemon_args::arg_zmq_rpc_bind_port);
@@ -203,7 +204,7 @@ int main(int argc, char const * argv[])
if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_file))
log_file_path = command_line::get_arg(vm, daemon_args::arg_log_file);
log_file_path = bf::absolute(log_file_path, relative_path_base);
- mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size));
+ mlog_configure(log_file_path.string(), true, command_line::get_arg(vm, daemon_args::arg_max_log_file_size), command_line::get_arg(vm, daemon_args::arg_max_log_files));
// Set log level
if (!command_line::is_arg_defaulted(vm, daemon_args::arg_log_level))
@@ -262,6 +263,9 @@ int main(int argc, char const * argv[])
}
else
{
+#ifdef HAVE_READLINE
+ rdln::suspend_readline pause_readline;
+#endif
std::cerr << "Unknown command: " << command.front() << std::endl;
return 1;
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 487853441..956c84a01 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -976,7 +976,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() {
}
else
{
- memset(&res.pool_stats, 0, sizeof(res.pool_stats));
+ res.pool_stats = {};
if (!m_rpc_server->on_get_transaction_pool_stats(req, res, false) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
@@ -1895,7 +1895,7 @@ bool t_rpc_command_executor::sync_info()
for (const auto &s: res.spans)
if (s.rate > 0.0f && s.connection_id == p.info.connection_id)
nblocks += s.nblocks, size += s.size;
- tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued";
+ tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << epee::string_tools::pad_string(p.info.state, 16) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued";
}
uint64_t total_size = 0;
diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp
index dfbd3b864..6c09b0f18 100644
--- a/src/debug_utilities/cn_deserialize.cpp
+++ b/src/debug_utilities/cn_deserialize.cpp
@@ -27,6 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/filesystem.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/transformed.hpp>
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_core/blockchain.h"
@@ -51,6 +53,7 @@ static void print_extra_fields(const std::vector<cryptonote::tx_extra_field> &fi
else if (typeid(cryptonote::tx_extra_pub_key) == fields[n].type()) std::cout << "extra pub key: " << boost::get<cryptonote::tx_extra_pub_key>(fields[n]).pub_key;
else if (typeid(cryptonote::tx_extra_nonce) == fields[n].type()) std::cout << "extra nonce: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_nonce>(fields[n]).nonce);
else if (typeid(cryptonote::tx_extra_merge_mining_tag) == fields[n].type()) std::cout << "extra merge mining tag: depth " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).depth << ", merkle root " << boost::get<cryptonote::tx_extra_merge_mining_tag>(fields[n]).merkle_root;
+ else if (typeid(cryptonote::tx_extra_additional_pub_keys) == fields[n].type()) std::cout << "additional tx pubkeys: " << boost::join(boost::get<cryptonote::tx_extra_additional_pub_keys>(fields[n]).data | boost::adaptors::transformed([](const crypto::public_key &key){ return epee::string_tools::pod_to_hex(key); }), ", " );
else if (typeid(cryptonote::tx_extra_mysterious_minergate) == fields[n].type()) std::cout << "extra minergate custom: " << epee::string_tools::buff_to_hex_nodelimer(boost::get<cryptonote::tx_extra_mysterious_minergate>(fields[n]).data);
else std::cout << "unknown";
std::cout << std::endl;
diff --git a/src/debug_utilities/object_sizes.cpp b/src/debug_utilities/object_sizes.cpp
index a3d037a59..ab8839636 100644
--- a/src/debug_utilities/object_sizes.cpp
+++ b/src/debug_utilities/object_sizes.cpp
@@ -94,6 +94,11 @@ int main(int argc, char* argv[])
SL(nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>);
SL(nodetool::p2p_connection_context_t<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>::connection_context>);
SL(nodetool::network_address_old);
+ SL(nodetool::peerlist_entry_base<nodetool::network_address_old>);
+
+ SL(nodetool::network_config);
+ SL(nodetool::basic_node_data);
+ SL(cryptonote::CORE_SYNC_DATA);
SL(tools::wallet2::transfer_details);
SL(tools::wallet2::payment_details);
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 641fe74c3..c70422887 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -48,6 +48,15 @@ namespace hw {
/* ===================================================================== */
/* === Debug ==== */
/* ===================================================================== */
+ #ifdef WIN32
+ static char *pcsc_stringify_error(LONG rv) {
+ static __thread char out[20];
+ sprintf_s(out, sizeof(out), "0x%08lX", rv);
+
+ return out;
+ }
+ #endif
+
void set_apdu_verbose(bool verbose) {
apdu_verbose = verbose;
}
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index f1fcaab87..b62bdf959 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -33,8 +33,13 @@
#include <cstddef>
#include <string>
#include "device.hpp"
+#ifdef WIN32
+#include <winscard.h>
+#define MAX_ATR_SIZE 33
+#else
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
+#endif
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp
index 6a2a3e0c4..7dd09ecb9 100644
--- a/src/mnemonics/electrum-words.cpp
+++ b/src/mnemonics/electrum-words.cpp
@@ -67,6 +67,9 @@
#include "language_base.h"
#include "singleton.h"
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "mnemonic"
+
namespace
{
uint32_t create_checksum_index(const std::vector<std::string> &word_list,
@@ -152,6 +155,7 @@ namespace
if (full_match)
{
*language = *it1;
+ MINFO("Full match for language " << (*language)->get_english_language_name());
return true;
}
// Some didn't match. Clear the index array.
@@ -164,9 +168,11 @@ namespace
if (fallback)
{
*language = fallback;
+ MINFO("Fallback match for language " << (*language)->get_english_language_name());
return true;
}
+ MINFO("No match found");
return false;
}
@@ -217,7 +223,9 @@ namespace
checksum;
std::string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) :
last_word;
- return trimmed_checksum == trimmed_last_word;
+ bool ret = trimmed_checksum == trimmed_last_word;
+ MINFO("Checksum is %s" << (ret ? "valid" : "invalid"));
+ return ret;
}
}
@@ -253,7 +261,10 @@ namespace crypto
boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on);
if (len % 4)
+ {
+ MERROR("Invalid seed: not a multiple of 4");
return false;
+ }
bool has_checksum = true;
if (len)
@@ -263,6 +274,7 @@ namespace crypto
if (seed.size() != expected/2 && seed.size() != expected &&
seed.size() != expected + 1)
{
+ MERROR("Invalid seed: unexpected number of words");
return false;
}
@@ -274,6 +286,7 @@ namespace crypto
Language::Base *language;
if (!find_seed_language(seed, has_checksum, matched_indices, &language))
{
+ MERROR("Invalid seed: language not found");
return false;
}
language_name = language->get_language_name();
@@ -284,6 +297,7 @@ namespace crypto
if (!checksum_test(seed, language->get_unique_prefix_length()))
{
// Checksum fail
+ MERROR("Invalid seed: invalid checksum");
return false;
}
seed.pop_back();
@@ -300,7 +314,11 @@ namespace crypto
val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) +
word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length);
- if (!(val % word_list_length == w1)) return false;
+ if (!(val % word_list_length == w1))
+ {
+ MERROR("Invalid seed: mumble mumble");
+ return false;
+ }
dst.append((const char*)&val, 4); // copy 4 bytes to position
}
@@ -332,9 +350,15 @@ namespace crypto
{
std::string s;
if (!words_to_bytes(words, s, sizeof(dst), true, language_name))
+ {
+ MERROR("Invalid seed: failed to convert words to bytes");
return false;
+ }
if (s.size() != sizeof(dst))
+ {
+ MERROR("Invalid seed: wrong output size");
return false;
+ }
dst = *(const crypto::secret_key*)s.data();
return true;
}
diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h
index 4606f66ee..90e2f78b1 100644
--- a/src/p2p/net_node.h
+++ b/src/p2p/net_node.h
@@ -244,6 +244,7 @@ namespace nodetool
bool check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp);
bool gray_peerlist_housekeeping();
+ bool check_incoming_connections();
void kill() { ///< will be called e.g. from deinit()
_info("Killing the net_node");
@@ -307,6 +308,7 @@ namespace nodetool
epee::math_helper::once_a_time_seconds<1> m_connections_maker_interval;
epee::math_helper::once_a_time_seconds<60*30, false> m_peerlist_store_interval;
epee::math_helper::once_a_time_seconds<60> m_gray_peerlist_housekeeping_interval;
+ epee::math_helper::once_a_time_seconds<900, false> m_incoming_connections_interval;
std::string m_bind_ip;
std::string m_port;
@@ -316,6 +318,7 @@ namespace nodetool
std::list<epee::net_utils::network_address> m_priority_peers;
std::vector<epee::net_utils::network_address> m_exclusive_peers;
std::vector<epee::net_utils::network_address> m_seed_nodes;
+ bool m_fallback_seed_nodes_added;
std::list<nodetool::peerlist_entry> m_command_line_peers;
uint64_t m_peer_livetime;
//keep connections to initiate some interactions
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 9b21705ec..5b65ba4d2 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -405,6 +405,7 @@ namespace nodetool
bool res = handle_command_line(vm);
CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line");
+ m_fallback_seed_nodes_added = false;
if (m_nettype == cryptonote::TESTNET)
{
memcpy(&m_network_id, &::config::testnet::NETWORK_ID, 16);
@@ -498,6 +499,7 @@ namespace nodetool
for (const auto &peer: get_seed_nodes(cryptonote::MAINNET))
full_addrs.insert(peer);
+ m_fallback_seed_nodes_added = true;
}
}
}
@@ -1134,7 +1136,6 @@ namespace nodetool
size_t try_count = 0;
size_t current_index = crypto::rand<size_t>()%m_seed_nodes.size();
- bool fallback_nodes_added = false;
while(true)
{
if(m_net_server.is_stop_signal_sent())
@@ -1144,15 +1145,21 @@ namespace nodetool
break;
if(++try_count > m_seed_nodes.size())
{
- if (!fallback_nodes_added)
+ if (!m_fallback_seed_nodes_added)
{
MWARNING("Failed to connect to any of seed peers, trying fallback seeds");
+ current_index = m_seed_nodes.size();
for (const auto &peer: get_seed_nodes(m_nettype))
{
MDEBUG("Fallback seed node: " << peer);
append_net_address(m_seed_nodes, peer);
}
- fallback_nodes_added = true;
+ m_fallback_seed_nodes_added = true;
+ if (current_index == m_seed_nodes.size())
+ {
+ MWARNING("No fallback seeds, continuing without seeds");
+ break;
+ }
// continue for another few cycles
}
else
@@ -1293,6 +1300,20 @@ namespace nodetool
m_connections_maker_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::connections_maker, this));
m_gray_peerlist_housekeeping_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::gray_peerlist_housekeeping, this));
m_peerlist_store_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::store_config, this));
+ m_incoming_connections_interval.do_call(boost::bind(&node_server<t_payload_net_handler>::check_incoming_connections, this));
+ return true;
+ }
+ //-----------------------------------------------------------------------------------
+ template<class t_payload_net_handler>
+ bool node_server<t_payload_net_handler>::check_incoming_connections()
+ {
+ if (m_offline || m_hide_my_port)
+ return true;
+ if (get_incoming_connections_count() == 0)
+ {
+ const el::Level level = el::Level::Warning;
+ MCLOG_RED(level, "global", "No incoming connections - check firewalls/routers allow port " << get_this_peer_port());
+ }
return true;
}
//-----------------------------------------------------------------------------------
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index cc46d0aa7..68cc43128 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -134,12 +134,9 @@ namespace rct {
}
key zeroCommit(xmr_amount amount) {
- key mask = identity();
- mask = scalarmultBase(mask);
key am = d2h(amount);
key bH = scalarmultH(am);
- addKeys(mask, mask, bH);
- return mask;
+ return addKeys(G, bH);
}
key commit(xmr_amount amount, const key &mask) {
diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h
index 3f8f6955c..f8889af5c 100644
--- a/src/ringct/rctOps.h
+++ b/src/ringct/rctOps.h
@@ -62,6 +62,7 @@ namespace rct {
static const key Z = { {0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } };
static const key I = { {0x01, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } };
static const key L = { {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 } };
+ static const key G = { {0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66 } };
//Creates a zero scalar
inline key zero() { return Z; }
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 70e186848..1e624da1b 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -1565,6 +1565,8 @@ namespace cryptonote
std::vector<txpool_histo> histo;
uint32_t num_double_spends;
+ txpool_stats(): bytes_total(0), bytes_min(0), bytes_max(0), bytes_med(0), fee_total(0), oldest(0), txs_total(0), num_failing(0), num_10m(0), num_not_relayed(0), histo_98pc(0), num_double_spends(0) {}
+
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(bytes_total)
KV_SERIALIZE(bytes_min)
diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp
index d4a6138ba..d4044d11b 100644
--- a/src/rpc/rpc_args.cpp
+++ b/src/rpc/rpc_args.cpp
@@ -102,7 +102,7 @@ namespace cryptonote
{
if (!config.login)
{
- LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RFC server password --") << arg.rpc_login.name << tr(" cannot be empty"));
+ LOG_ERROR(arg.rpc_access_control_origins.name << tr(" requires RPC server password --") << arg.rpc_login.name << tr(" cannot be empty"));
return boost::none;
}
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 397614328..24c42a597 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -90,8 +90,6 @@ typedef cryptonote::simple_wallet sw;
#define MIN_RING_SIZE 7 // Used to inform user about min ring size -- does not track actual protocol
-#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
-
#define LOCK_IDLE_SCOPE() \
bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \
m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \
@@ -381,21 +379,10 @@ namespace
boost::optional<std::pair<uint32_t, uint32_t>> parse_subaddress_lookahead(const std::string& str)
{
- auto pos = str.find(":");
- bool r = pos != std::string::npos;
- uint32_t major;
- r = r && epee::string_tools::get_xtype_from_string(major, str.substr(0, pos));
- uint32_t minor;
- r = r && epee::string_tools::get_xtype_from_string(minor, str.substr(pos + 1));
- if (r)
- {
- return std::make_pair(major, minor);
- }
- else
- {
+ auto r = tools::parse_subaddress_lookahead(str);
+ if (!r)
fail_msg_writer() << tr("invalid format for subaddress lookahead; must be <major>:<minor>");
- return {};
- }
+ return r;
}
void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
@@ -508,7 +495,7 @@ namespace
}
if (warn_of_possible_attack)
- fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a tranasction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
+ fail_msg_writer() << tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
}
bool check_file_overwrite(const std::string &filename)
@@ -1371,9 +1358,117 @@ bool simple_wallet::print_ring(const std::vector<std::string> &args)
bool simple_wallet::set_ring(const std::vector<std::string> &args)
{
crypto::key_image key_image;
+
+ // try filename first
+ if (args.size() == 1)
+ {
+ if (!epee::file_io_utils::is_file_exist(args[0]))
+ {
+ fail_msg_writer() << tr("File doesn't exist");
+ return true;
+ }
+
+ char str[4096];
+ std::unique_ptr<FILE, tools::close_file> f(fopen(args[0].c_str(), "r"));
+ if (f)
+ {
+ while (!feof(f.get()))
+ {
+ if (!fgets(str, sizeof(str), f.get()))
+ break;
+ const size_t len = strlen(str);
+ if (len > 0 && str[len - 1] == '\n')
+ str[len - 1] = 0;
+ if (!str[0])
+ continue;
+ char key_image_str[65], type_str[9];
+ int read_after_key_image = 0, read = 0;
+ int fields = sscanf(str, "%64[abcdefABCDEF0123456789] %n%8s %n", key_image_str, &read_after_key_image, type_str, &read);
+ if (fields != 2)
+ {
+ fail_msg_writer() << tr("Invalid ring specification: ") << str;
+ continue;
+ }
+ key_image_str[64] = 0;
+ type_str[8] = 0;
+ crypto::key_image key_image;
+ if (read_after_key_image == 0 || !epee::string_tools::hex_to_pod(key_image_str, key_image))
+ {
+ fail_msg_writer() << tr("Invalid key image: ") << str;
+ continue;
+ }
+ if (read == read_after_key_image+8 || (strcmp(type_str, "absolute") && strcmp(type_str, "relative")))
+ {
+ fail_msg_writer() << tr("Invalid ring type, expected relative or abosolute: ") << str;
+ continue;
+ }
+ bool relative = !strcmp(type_str, "relative");
+ if (read < 0 || (size_t)read > strlen(str))
+ {
+ fail_msg_writer() << tr("Error reading line: ") << str;
+ continue;
+ }
+ bool valid = true;
+ std::vector<uint64_t> ring;
+ const char *ptr = str + read;
+ while (*ptr)
+ {
+ unsigned long offset;
+ int elements = sscanf(ptr, "%lu %n", &offset, &read);
+ if (elements == 0 || read <= 0 || (size_t)read > strlen(str))
+ {
+ fail_msg_writer() << tr("Error reading line: ") << str;
+ valid = false;
+ break;
+ }
+ ring.push_back(offset);
+ ptr += read;
+ MGINFO("read offset: " << offset);
+ }
+ if (!valid)
+ continue;
+ if (ring.empty())
+ {
+ fail_msg_writer() << tr("Invalid ring: ") << str;
+ continue;
+ }
+ if (relative)
+ {
+ for (size_t n = 1; n < ring.size(); ++n)
+ {
+ if (ring[n] <= 0)
+ {
+ fail_msg_writer() << tr("Invalid relative ring: ") << str;
+ valid = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ for (size_t n = 1; n < ring.size(); ++n)
+ {
+ if (ring[n] <= ring[n-1])
+ {
+ fail_msg_writer() << tr("Invalid absolute ring: ") << str;
+ valid = false;
+ break;
+ }
+ }
+ }
+ if (!valid)
+ continue;
+ if (!m_wallet->set_ring(key_image, ring, relative))
+ fail_msg_writer() << tr("Failed to set ring for key image: ") << key_image << ". " << tr("Continuing.");
+ }
+ f.reset();
+ }
+ return true;
+ }
+
if (args.size() < 3)
{
- fail_msg_writer() << tr("usage: set_ring <key_image> absolute|relative <index> [<index>...]");
+ fail_msg_writer() << tr("usage: set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )");
return true;
}
@@ -2028,7 +2123,7 @@ simple_wallet::simple_wallet()
tr("Stop mining in the daemon."));
m_cmd_binder.set_handler("set_daemon",
boost::bind(&simple_wallet::set_daemon, this, _1),
- tr("set_daemon <host>[:<port>]"),
+ tr("set_daemon <host>[:<port>] [trusted|untrusted]"),
tr("Set another daemon to connect to."));
m_cmd_binder.set_handler("save_bc",
boost::bind(&simple_wallet::save_bc, this, _1),
@@ -2082,8 +2177,8 @@ simple_wallet::simple_wallet()
tr("Donate <amount> to the development team (donate.getmonero.org)."));
m_cmd_binder.set_handler("sign_transfer",
boost::bind(&simple_wallet::sign_transfer, this, _1),
- tr("sign_transfer [export]"),
- tr("Sign a transaction from a file."));
+ tr("sign_transfer [export_raw]"),
+ tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported."));
m_cmd_binder.set_handler("submit_transfer",
boost::bind(&simple_wallet::submit_transfer, this, _1),
tr("Submit a signed transaction from a file."));
@@ -2153,7 +2248,7 @@ simple_wallet::simple_wallet()
"refresh-type <full|optimize-coinbase|no-coinbase|default>\n "
" Set the wallet's refresh behaviour.\n "
"priority [0|1|2|3|4]\n "
- " Set the fee too default/unimportant/normal/elevated/priority.\n "
+ " Set the fee to default/unimportant/normal/elevated/priority.\n "
"confirm-missing-payment-id <1|0>\n "
"ask-password <1|0>\n "
"unit <monero|millinero|micronero|nanonero|piconero>\n "
@@ -2325,7 +2420,7 @@ simple_wallet::simple_wallet()
tr("Print the ring(s) used to spend a given key image or transaction (if the ring size is > 1)"));
m_cmd_binder.set_handler("set_ring",
boost::bind(&simple_wallet::set_ring, this, _1),
- tr("set_ring <key_image> absolute|relative <index> [<index>...]"),
+ tr("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"),
tr("Set the ring used for a given key image, so it can be reused in a fork"));
m_cmd_binder.set_handler("save_known_rings",
boost::bind(&simple_wallet::save_known_rings, this, _1),
@@ -2451,8 +2546,24 @@ bool simple_wallet::set_log(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: set_log <log_level_number_0-4> | <categories>");
return true;
}
- if (!args.empty())
- mlog_set_log(args[0].c_str());
+ if(!args.empty())
+ {
+ uint16_t level = 0;
+ if(epee::string_tools::get_xtype_from_string(level, args[0]))
+ {
+ if(4 < level)
+ {
+ fail_msg_writer() << tr("wrong number range, use: set_log <log_level_number_0-4> | <categories>");
+ return true;
+ }
+ mlog_set_log_level(level);
+ }
+ else
+ {
+ mlog_set_log(args[0].c_str());
+ }
+ }
+
success_msg_writer() << "New log categories: " << mlog_get_categories();
return true;
}
@@ -3685,7 +3796,6 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype());
bool ok = true;
- size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2));
size_t arg_size = args.size();
if(arg_size >= 3)
{
@@ -3701,7 +3811,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
{
uint16_t num = 1;
ok = string_tools::get_xtype_from_string(num, args[0]);
- ok = ok && (1 <= num && num <= max_mining_threads_count);
+ ok = ok && 1 <= num;
req.threads_count = num;
}
else
@@ -3711,8 +3821,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args)
if (!ok)
{
- fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>] [do_bg_mining] [ignore_battery], "
- "<number_of_threads> should be from 1 to ") << max_mining_threads_count;
+ fail_msg_writer() << tr("invalid arguments. Please use start_mining [<number_of_threads>] [do_bg_mining] [ignore_battery]");
return true;
}
@@ -3778,6 +3887,33 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args)
}
LOCK_IDLE_SCOPE();
m_wallet->init(daemon_url);
+
+ if (args.size() == 2)
+ {
+ if (args[1] == "trusted")
+ m_trusted_daemon = true;
+ else if (args[1] == "untrusted")
+ m_trusted_daemon = false;
+ else
+ {
+ fail_msg_writer() << tr("Expected trusted or untrusted, got ") << args[1] << ": assuming untrusted";
+ m_trusted_daemon = false;
+ }
+ }
+ else
+ {
+ m_trusted_daemon = false;
+ try
+ {
+ if (tools::is_local_address(m_wallet->get_daemon_address()))
+ {
+ MINFO(tr("Daemon is local, assuming trusted"));
+ m_trusted_daemon = true;
+ }
+ }
+ catch (const std::exception &e) { }
+ }
+ success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (*m_trusted_daemon ? tr("trusted") : tr("untrusted"));
} else {
fail_msg_writer() << tr("This does not seem to be a valid daemon URL.");
}
@@ -3941,6 +4077,8 @@ bool simple_wallet::show_balance_unlocked(bool detailed)
std::string extra;
if (m_wallet->has_multisig_partial_key_images())
extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)");
+ else if (m_wallet->has_unknown_key_images())
+ extra += tr(" (Some owned outputs have missing key images - import_key_images needed)");
success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0});
const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account];
success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag);
@@ -5299,7 +5437,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_)
local_args.push_back(amount_str);
if (!payment_id_str.empty())
local_args.push_back(payment_id_str);
- message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org or "<< MONERO_DONATION_ADDR <<").";
+ message_writer() << (boost::format(tr("Donating %s %s to The Monero Project (donate.getmonero.org or %s).")) % amount_str % cryptonote::get_unit(cryptonote::get_default_decimal_point()) % MONERO_DONATION_ADDR).str();
transfer_new(local_args);
return true;
}
@@ -5469,9 +5607,9 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
fail_msg_writer() << tr("This is a watch only wallet");
return true;
}
- if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export"))
+ if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export_raw"))
{
- fail_msg_writer() << tr("usage: sign_transfer [export]");
+ fail_msg_writer() << tr("usage: sign_transfer [export_raw]");
return true;
}
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
@@ -6016,10 +6154,7 @@ static std::string get_human_readable_timestamp(uint64_t ts)
#endif
uint64_t now = time(NULL);
uint64_t diff = ts > now ? ts - now : now - ts;
- if (diff > 24*3600)
- strftime(buffer, sizeof(buffer), "%Y-%m-%d", &tm);
- else
- strftime(buffer, sizeof(buffer), "%I:%M:%S %p", &tm);
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
return std::string(buffer);
}
//----------------------------------------------------------------------------------------------------
@@ -6131,7 +6266,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(pd.m_tx_hash);
- output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str())));
+ output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%25.25s %20.20s %s %s %d %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str())));
}
}
@@ -6164,7 +6299,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
std::string note = m_wallet->get_tx_note(i->first);
- output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str())));
+ output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%25.25s %20.20s %s %s %14.14s %s %s - %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % print_subaddr_indices(pd.m_subaddr_indices) % note).str())));
}
}
@@ -6190,7 +6325,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
std::string double_spend_note;
if (i->second.m_double_spend_seen)
double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] ");
- message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str();
+ message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str();
}
}
catch (const std::exception& e)
@@ -6213,7 +6348,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
std::string note = m_wallet->get_tx_note(i->first);
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
if ((failed && is_failed) || (!is_failed && pending)) {
- message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str();
+ message_writer() << (boost::format("%8.8s %6.6s %25.25s %20.20s %s %s %14.14s %s - %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % print_subaddr_indices(pd.m_subaddr_indices) % note).str();
}
}
}
@@ -7182,19 +7317,8 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args)
LOCK_IDLE_SCOPE();
try
{
- std::vector<tools::wallet2::transfer_details> outs = m_wallet->export_outputs();
-
- std::stringstream oss;
- boost::archive::portable_binary_oarchive ar(oss);
- ar << outs;
-
- std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
- const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address;
- std::string header;
- header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
- header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
- std::string ciphertext = m_wallet->encrypt_with_view_secret_key(header + oss.str());
- bool r = epee::file_io_utils::save_string_to_file(filename, magic + ciphertext);
+ std::string data = m_wallet->export_outputs_to_str();
+ bool r = epee::file_io_utils::save_string_to_file(filename, data);
if (!r)
{
fail_msg_writer() << tr("failed to save file ") << filename;
@@ -7233,63 +7357,16 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
fail_msg_writer() << tr("failed to read file ") << filename;
return true;
}
- const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
- if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
- {
- fail_msg_writer() << "Bad output export file magic in " << filename;
- return true;
- }
-
- try
- {
- data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen));
- }
- catch (const std::exception &e)
- {
- fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what();
- return true;
- }
-
- const size_t headerlen = 2 * sizeof(crypto::public_key);
- if (data.size() < headerlen)
- {
- fail_msg_writer() << "Bad data size from file " << filename;
- return true;
- }
- const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
- const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
- const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address;
- if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
- {
- fail_msg_writer() << "Outputs from " << filename << " are for a different account";
- return true;
- }
try
{
- std::string body(data, headerlen);
- std::stringstream iss;
- iss << body;
- std::vector<tools::wallet2::transfer_details> outputs;
- try
- {
- boost::archive::portable_binary_iarchive ar(iss);
- ar >> outputs;
- }
- catch (...)
- {
- iss.str("");
- iss << body;
- boost::archive::binary_iarchive ar(iss);
- ar >> outputs;
- }
LOCK_IDLE_SCOPE();
- size_t n_outputs = m_wallet->import_outputs(outputs);
+ size_t n_outputs = m_wallet->import_outputs_from_str(data);
success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported";
}
catch (const std::exception &e)
{
- fail_msg_writer() << "Failed to import outputs: " << e.what();
+ fail_msg_writer() << "Failed to import outputs " << filename << ": " << e.what();
return true;
}
@@ -7529,7 +7606,7 @@ int main(int argc, char* argv[])
std::tie(vm, should_terminate) = wallet_args::main(
argc, argv,
"monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]",
- sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on an another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."),
+ sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."),
desc_params,
positional_options,
[](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; },
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 908e2db2e..c7dbd29e4 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -372,6 +372,7 @@ WalletImpl::WalletImpl(NetworkType nettype)
, m_trustedDaemon(false)
, m_wallet2Callback(nullptr)
, m_recoveringFromSeed(false)
+ , m_recoveringFromDevice(false)
, m_synchronized(false)
, m_rebuildWalletCache(false)
, m_is_connected(false)
@@ -419,6 +420,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co
clearStatus();
m_recoveringFromSeed = false;
+ m_recoveringFromDevice = false;
bool keys_file_exists;
bool wallet_file_exists;
tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists);
@@ -554,18 +556,26 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path,
}
// parse view secret key
+ bool has_viewkey = true;
+ crypto::secret_key viewkey;
if (viewkey_string.empty()) {
- setStatusError(tr("No view key supplied, cancelled"));
- return false;
+ if(has_spendkey) {
+ has_viewkey = false;
+ }
+ else {
+ setStatusError(tr("Neither view key nor spend key supplied, cancelled"));
+ return false;
+ }
}
- 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;
+ 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());
}
- crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data());
-
// check the spend and view keys match the given address
crypto::public_key pkey;
if(has_spendkey) {
@@ -578,26 +588,32 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path,
return false;
}
}
- 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;
+ 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;
+ }
}
try
{
- if (has_spendkey) {
+ if (has_spendkey && has_viewkey) {
m_wallet->generate(path, password, info.address, spendkey, viewkey);
- setSeedLanguage(language);
- LOG_PRINT_L1("Generated new wallet from keys with seed language: " + language);
+ LOG_PRINT_L1("Generated new wallet from spend key and view key");
}
- else {
+ if(!has_spendkey && has_viewkey) {
m_wallet->generate(path, password, info.address, viewkey);
LOG_PRINT_L1("Generated new view only wallet from keys");
}
+ if(has_spendkey && !has_viewkey) {
+ m_wallet->generate(path, password, spendkey, true, false, false);
+ setSeedLanguage(language);
+ LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language);
+ }
}
catch (const std::exception& e) {
@@ -607,11 +623,28 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path,
return true;
}
+bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &password, const std::string &device_name)
+{
+ clearStatus();
+ m_recoveringFromSeed = false;
+ m_recoveringFromDevice = true;
+ try
+ {
+ m_wallet->restore(path, password, device_name);
+ LOG_PRINT_L1("Generated new wallet from device: " + device_name);
+ }
+ catch (const std::exception& e) {
+ setStatusError(string(tr("failed to generate new wallet: ")) + e.what());
+ return false;
+ }
+ return true;
+}
bool WalletImpl::open(const std::string &path, const std::string &password)
{
clearStatus();
m_recoveringFromSeed = false;
+ m_recoveringFromDevice = false;
try {
// TODO: handle "deprecated"
// Check if wallet cache exists
@@ -649,6 +682,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c
}
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)) {
@@ -870,6 +904,16 @@ void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed)
m_recoveringFromSeed = recoveringFromSeed;
}
+void WalletImpl::setRecoveringFromDevice(bool recoveringFromDevice)
+{
+ m_recoveringFromDevice = recoveringFromDevice;
+}
+
+void WalletImpl::setSubaddressLookahead(uint32_t major, uint32_t minor)
+{
+ m_wallet->set_subaddress_lookahead(major, minor);
+}
+
uint64_t WalletImpl::balance(uint32_t accountIndex) const
{
return m_wallet->balance(accountIndex);
@@ -1896,7 +1940,7 @@ void WalletImpl::refreshThreadFunc()
// if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval.
// if not - we wait forever
if (m_refreshIntervalMillis > 0) {
- boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis);
+ boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis.load());
m_refreshCV.timed_wait(lock, wait_for_ms);
} else {
m_refreshCV.wait(lock);
@@ -1982,7 +2026,7 @@ bool WalletImpl::isNewWallet() const
// 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_rebuildWalletCache) && !watchOnly();
+ return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_recoveringFromDevice || m_rebuildWalletCache) && !watchOnly();
}
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 813ca4b30..08232cafd 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -76,6 +76,9 @@ public:
const std::string &address_string,
const std::string &viewkey_string,
const std::string &spendkey_string = "");
+ bool recoverFromDevice(const std::string &path,
+ const std::string &password,
+ const std::string &device_name);
bool close(bool store = true);
std::string seed() const;
std::string getSeedLanguage() const;
@@ -115,6 +118,8 @@ public:
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height);
uint64_t getRefreshFromBlockHeight() const { return m_wallet->get_refresh_from_block_height(); };
void setRecoveringFromSeed(bool recoveringFromSeed);
+ void setRecoveringFromDevice(bool recoveringFromDevice) override;
+ void setSubaddressLookahead(uint32_t major, uint32_t minor) override;
bool watchOnly() const;
bool rescanSpent();
NetworkType nettype() const {return static_cast<NetworkType>(m_wallet->nettype());}
@@ -232,6 +237,7 @@ private:
// so it shouldn't be considered as new and pull blocks (slow-refresh)
// instead of pulling hashes (fast-refresh)
std::atomic<bool> m_recoveringFromSeed;
+ std::atomic<bool> m_recoveringFromDevice;
std::atomic<bool> m_synchronized;
std::atomic<bool> m_rebuildWalletCache;
// cache connection status to avoid unnecessary RPC calls
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index 5b99bd975..f54255e91 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -509,6 +509,21 @@ struct Wallet
*/
virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
+ /*!
+ * \brief setRecoveringFromDevice - set state to recovering from device
+ *
+ * \param recoveringFromDevice - true/false
+ */
+ virtual void setRecoveringFromDevice(bool recoveringFromDevice) = 0;
+
+ /*!
+ * \brief setSubaddressLookahead - set size of subaddress lookahead
+ *
+ * \param major - size fot the major index
+ * \param minor - size fot the minor index
+ */
+ virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0;
+
/**
* @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed
* @return
@@ -1015,6 +1030,23 @@ struct WalletManager
}
/*!
+ * \brief creates wallet using hardware device.
+ * \param path Name of wallet file to be created
+ * \param password Password of wallet file
+ * \param nettype Network type
+ * \param deviceName Device name
+ * \param restoreHeight restore from start height (0 sets to current height)
+ * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value)
+ * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully)
+ */
+ virtual Wallet * createWalletFromDevice(const std::string &path,
+ const std::string &password,
+ NetworkType nettype,
+ const std::string &deviceName,
+ uint64_t restoreHeight = 0,
+ const std::string &subaddressLookahead = "") = 0;
+
+ /*!
* \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted
* \param wallet previously opened / created wallet instance
* \return None
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index a63716576..99eadc82f 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -114,6 +114,26 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path,
return wallet;
}
+Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path,
+ const std::string &password,
+ NetworkType nettype,
+ const std::string &deviceName,
+ uint64_t restoreHeight,
+ const std::string &subaddressLookahead)
+{
+ WalletImpl * wallet = new WalletImpl(nettype);
+ if(restoreHeight > 0){
+ wallet->setRefreshFromBlockHeight(restoreHeight);
+ }
+ auto lookahead = tools::parse_subaddress_lookahead(subaddressLookahead);
+ if (lookahead)
+ {
+ wallet->setSubaddressLookahead(lookahead->first, lookahead->second);
+ }
+ wallet->recoverFromDevice(path, password, deviceName);
+ return wallet;
+}
+
bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store)
{
WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet);
diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h
index 26238b658..19aad9ee3 100644
--- a/src/wallet/api/wallet_manager.h
+++ b/src/wallet/api/wallet_manager.h
@@ -64,6 +64,12 @@ public:
const std::string &addressString,
const std::string &viewKeyString,
const std::string &spendKeyString = "");
+ virtual Wallet * createWalletFromDevice(const std::string &path,
+ const std::string &password,
+ NetworkType nettype,
+ const std::string &deviceName,
+ uint64_t restoreHeight = 0,
+ const std::string &subaddressLookahead = "") override;
virtual bool closeWallet(Wallet *wallet, bool store = true);
bool walletExists(const std::string &path);
bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index d2db45f12..9bd58e561 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -110,6 +110,8 @@ using namespace cryptonote;
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
+#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
+
#define SEGREGATION_FORK_HEIGHT 1546000
#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000
#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000
@@ -3271,6 +3273,12 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
+ if (m_subaddress_lookahead_major == SUBADDRESS_LOOKAHEAD_MAJOR && m_subaddress_lookahead_minor == SUBADDRESS_LOOKAHEAD_MINOR)
+ {
+ // the default lookahead setting (50:200) is clearly too much for hardware wallet
+ m_subaddress_lookahead_major = 5;
+ m_subaddress_lookahead_minor = 20;
+ }
add_subaddress_account(tr("Primary account"));
if (!wallet_.empty()) {
store();
@@ -3622,6 +3630,14 @@ bool wallet2::has_multisig_partial_key_images() const
return false;
}
+bool wallet2::has_unknown_key_images() const
+{
+ for (const auto &td: m_transfers)
+ if (!td.m_key_image_known)
+ return true;
+ return false;
+}
+
/*!
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
* \param wallet_name Name of wallet file (should exist)
@@ -3784,7 +3800,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
{
wallet2::cache_file_data cache_file_data;
std::string buf;
- bool r = epee::file_io_utils::load_file_to_string(m_wallet_file, buf);
+ bool r = epee::file_io_utils::load_file_to_string(m_wallet_file, buf, std::numeric_limits<size_t>::max());
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file);
// try to read it as an encrypted cache
@@ -4665,6 +4681,15 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector)
bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const
{
LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions");
+ std::string ciphertext = dump_tx_to_str(ptx_vector);
+ if (ciphertext.empty())
+ return false;
+ return epee::file_io_utils::save_string_to_file(filename, ciphertext);
+}
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const
+{
+ LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions");
unsigned_tx_set txs;
for (auto &tx: ptx_vector)
{
@@ -4684,11 +4709,11 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
}
catch (...)
{
- return false;
+ return std::string();
}
LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
std::string ciphertext = encrypt_with_view_secret_key(oss.str());
- return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext);
+ return std::string(UNSIGNED_TX_PREFIX) + ciphertext;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const
@@ -4706,10 +4731,17 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
LOG_PRINT_L0("Failed to load from " << unsigned_filename);
return false;
}
+
+ return parse_unsigned_tx_from_str(s, exported_txs);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const
+{
+ std::string s = unsigned_tx_st;
const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen))
{
- LOG_PRINT_L0("Bad magic from " << unsigned_filename);
+ LOG_PRINT_L0("Bad magic from unsigned tx");
return false;
}
s = s.substr(magiclen);
@@ -4725,7 +4757,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
}
catch (...)
{
- LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ LOG_PRINT_L0("Failed to parse data from unsigned tx");
return false;
}
}
@@ -4742,19 +4774,19 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
}
catch (...)
{
- LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
+ LOG_PRINT_L0("Failed to parse data from unsigned tx");
return false;
}
}
catch (const std::exception &e)
{
- LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what());
+ LOG_PRINT_L0("Failed to decrypt unsigned tx: " << e.what());
return false;
}
}
else
{
- LOG_PRINT_L0("Unsupported version in " << unsigned_filename);
+ LOG_PRINT_L0("Unsupported version in unsigned tx");
return false;
}
LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions");
@@ -4775,14 +4807,12 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
}
return sign_tx(exported_txs, signed_filename, txs, export_raw);
}
-
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
+bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes)
{
import_outputs(exported_txs.transfers);
// sign the transactions
- signed_tx_set signed_txes;
for (size_t n = 0; n < exported_txs.txes.size(); ++n)
{
tools::wallet2::tx_construction_data &sd = exported_txs.txes[n];
@@ -4790,11 +4820,10 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size());
signed_txes.ptx.push_back(pending_tx());
tools::wallet2::pending_tx &ptx = signed_txes.ptx.back();
- bool bulletproof = sd.use_rct && !ptx.tx.rct_signatures.p.bulletproofs.empty();
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
rct::multisig_out msout;
- bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, bulletproof, m_multisig ? &msout : NULL);
+ bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, sd.use_bulletproofs, m_multisig ? &msout : NULL);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
// we don't test tx size, because we don't know the current limit, due to not having a blockchain,
// and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway,
@@ -4844,19 +4873,20 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
signed_txes.key_images[i] = m_transfers[i].m_key_image;
}
- // save as binary
- std::ostringstream oss;
- boost::archive::portable_binary_oarchive ar(oss);
- try
- {
- ar << signed_txes;
- }
- catch(...)
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
+{
+ // sign the transactions
+ signed_tx_set signed_txes;
+ std::string ciphertext = sign_tx_dump_to_str(exported_txs, txs, signed_txes);
+ if (ciphertext.empty())
{
+ LOG_PRINT_L0("Failed to sign unsigned_tx_set");
return false;
}
- LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
- std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+
if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext))
{
LOG_PRINT_L0("Failed to save file to " << signed_filename);
@@ -4879,6 +4909,32 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
return true;
}
//----------------------------------------------------------------------------------------------------
+std::string wallet2::sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes)
+{
+ // sign the transactions
+ bool r = sign_tx(exported_txs, ptx, signed_txes);
+ if (!r)
+ {
+ LOG_PRINT_L0("Failed to sign unsigned_tx_set");
+ return std::string();
+ }
+
+ // save as binary
+ std::ostringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ try
+ {
+ ar << signed_txes;
+ }
+ catch(...)
+ {
+ return std::string();
+ }
+ LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
+ std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+ return std::string(SIGNED_TX_PREFIX) + ciphertext;
+}
+//----------------------------------------------------------------------------------------------------
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
{
std::string s;
@@ -4896,10 +4952,20 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
LOG_PRINT_L0("Failed to load from " << signed_filename);
return false;
}
+
+ return parse_tx_from_str(s, ptx, accept_func);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func)
+{
+ std::string s = signed_tx_st;
+ boost::system::error_code errcode;
+ signed_tx_set signed_txs;
+
const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1;
if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen))
{
- LOG_PRINT_L0("Bad magic from " << signed_filename);
+ LOG_PRINT_L0("Bad magic from signed transaction");
return false;
}
s = s.substr(magiclen);
@@ -4915,7 +4981,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
}
catch (...)
{
- LOG_PRINT_L0("Failed to parse data from " << signed_filename);
+ LOG_PRINT_L0("Failed to parse data from signed transaction");
return false;
}
}
@@ -4932,23 +4998,23 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
}
catch (...)
{
- LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename);
+ LOG_PRINT_L0("Failed to parse decrypted data from signed transaction");
return false;
}
}
catch (const std::exception &e)
{
- LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what());
+ LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what());
return false;
}
}
else
{
- LOG_PRINT_L0("Unsupported version in " << signed_filename);
+ LOG_PRINT_L0("Unsupported version in signed transaction");
return false;
}
LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions");
- for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx));
+ for (auto &c_ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx));
if (accept_func && !accept_func(signed_txs))
{
@@ -5170,8 +5236,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
cryptonote::transaction tx;
rct::multisig_out msout = ptx.multisig_sigs.front().msout;
auto sources = sd.sources;
- const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof);
- bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout, false);
+ bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, sd.use_bulletproofs, &msout, false);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype);
THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx),
@@ -6474,6 +6539,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.construction_data.extra = tx.extra;
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
+ ptx.construction_data.use_bulletproofs = false;
ptx.construction_data.dests = dsts;
// record which subaddress indices are being used as inputs
ptx.construction_data.subaddr_account = subaddr_account;
@@ -6729,6 +6795,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.extra = tx.extra;
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
+ ptx.construction_data.use_bulletproofs = !tx.rct_signatures.p.bulletproofs.empty();
ptx.construction_data.dests = dsts;
// record which subaddress indices are being used as inputs
ptx.construction_data.subaddr_account = subaddr_account;
@@ -8430,8 +8497,9 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
}
std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) };
const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size();
- THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len,
- error::wallet_internal_error, "incorrect signature size");
+ if( sig_str.size() != header_len + num_sigs * sig_len ) {
+ return false;
+ }
// decode base58
signatures.clear();
@@ -9161,9 +9229,9 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err)
uint64_t wallet2::get_approximate_blockchain_height() const
{
// time of v2 fork
- const time_t fork_time = m_nettype == TESTNET ? 1448285909 : m_nettype == STAGENET ? (time_t)-1/*TODO*/ : 1458748658;
+ const time_t fork_time = m_nettype == TESTNET ? 1448285909 : m_nettype == STAGENET ? 1520937818 : 1458748658;
// v2 fork block
- const uint64_t fork_block = m_nettype == TESTNET ? 624634 : m_nettype == STAGENET ? (uint64_t)-1/*TODO*/ : 1009827;
+ const uint64_t fork_block = m_nettype == TESTNET ? 624634 : m_nettype == STAGENET ? 32000 : 1009827;
// avg seconds per block
const int seconds_per_block = DIFFICULTY_TARGET_V2;
// Calculated blockchain height
@@ -9814,6 +9882,23 @@ std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
return outs;
}
//----------------------------------------------------------------------------------------------------
+std::string wallet2::export_outputs_to_str() const
+{
+ std::vector<tools::wallet2::transfer_details> outs = export_outputs();
+
+ std::stringstream oss;
+ boost::archive::portable_binary_oarchive ar(oss);
+ ar << outs;
+
+ std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ std::string header;
+ header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
+ header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
+ std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
+ return magic + ciphertext;
+}
+//----------------------------------------------------------------------------------------------------
size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs)
{
m_transfers.clear();
@@ -9846,6 +9931,67 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
return m_transfers.size();
}
//----------------------------------------------------------------------------------------------------
+size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
+{
+ std::string data = outputs_st;
+ const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
+ if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad magic from outputs"));
+ }
+
+ try
+ {
+ data = decrypt_with_view_secret_key(std::string(data, magiclen));
+ }
+ catch (const std::exception &e)
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt outputs: ") + e.what());
+ }
+
+ const size_t headerlen = 2 * sizeof(crypto::public_key);
+ if (data.size() < headerlen)
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad data size for outputs"));
+ }
+ const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
+ const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
+ const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
+ if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Outputs from are for a different account"));
+ }
+
+ size_t imported_outputs = 0;
+ try
+ {
+ std::string body(data, headerlen);
+ std::stringstream iss;
+ iss << body;
+ std::vector<tools::wallet2::transfer_details> outputs;
+ try
+ {
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> outputs;
+ }
+ catch (...)
+ {
+ iss.str("");
+ iss << body;
+ boost::archive::binary_iarchive ar(iss);
+ ar >> outputs;
+ }
+
+ imported_outputs = import_outputs(outputs);
+ }
+ catch (const std::exception &e)
+ {
+ THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what());
+ }
+
+ return imported_outputs;
+}
+//----------------------------------------------------------------------------------------------------
crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
{
crypto::public_key pkey;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 40f6e08d9..7f214be35 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -320,6 +320,7 @@ namespace tools
std::vector<uint8_t> extra;
uint64_t unlock_time;
bool use_rct;
+ bool use_bulletproofs;
std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change
uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer
std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer
@@ -332,6 +333,7 @@ namespace tools
FIELD(extra)
FIELD(unlock_time)
FIELD(use_rct)
+ FIELD(use_bulletproofs)
FIELD(dests)
FIELD(subaddr_account)
FIELD(subaddr_indices)
@@ -655,6 +657,7 @@ namespace tools
bool watch_only() const { return m_watch_only; }
bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const;
bool has_multisig_partial_key_images() const;
+ bool has_unknown_key_images() const;
bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const;
bool key_on_device() const { return m_key_on_device; }
@@ -684,6 +687,7 @@ namespace tools
void commit_tx(pending_tx& ptx_vector);
void commit_tx(std::vector<pending_tx>& ptx_vector);
bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const;
+ std::string dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const;
std::string save_multisig_tx(multisig_tx_set txs);
bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename);
std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector);
@@ -693,9 +697,13 @@ namespace tools
bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false);
// sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI
bool sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, bool export_raw = false);
+ bool sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txs);
+ std::string sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes);
// load unsigned_tx_set from file.
bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const;
+ bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const;
bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
+ bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func);
std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
@@ -987,7 +995,9 @@ namespace tools
// Import/Export wallet data
std::vector<tools::wallet2::transfer_details> export_outputs() const;
+ std::string export_outputs_to_str() const;
size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs);
+ size_t import_outputs_from_str(const std::string &outputs_st);
payment_container export_payments() const;
void import_payments(const payment_container &payments);
void import_payments_out(const std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> &confirmed_payments);
@@ -1264,7 +1274,7 @@ BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
-BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2)
+BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 3)
BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3)
BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 0)
@@ -1611,6 +1621,9 @@ namespace boost
if (ver < 2)
return;
a & x.selected_transfers;
+ if (ver < 3)
+ return;
+ a & x.use_bulletproofs;
}
template <class Archive>
@@ -1896,6 +1909,7 @@ namespace tools
ptx.construction_data.extra = tx.extra;
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
+ ptx.construction_data.use_bulletproofs = false;
ptx.construction_data.dests = dsts;
// record which subaddress indices are being used as inputs
ptx.construction_data.subaddr_account = subaddr_account;
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index 6311e7700..a629eb506 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -101,6 +101,7 @@ namespace wallet_args
const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""};
const command_line::arg_descriptor<std::size_t> arg_max_log_file_size = {"max-log-file-size", "Specify maximum log file size [B]", MAX_LOG_FILE_SIZE};
+ const command_line::arg_descriptor<std::size_t> arg_max_log_files = {"max-log-files", "Specify maximum number of rotated log files to be saved (no limit by setting to 0)", MAX_LOG_FILES};
const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", wallet_args::tr("Max number of threads to use for a parallel job"), DEFAULT_MAX_CONCURRENCY};
const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", wallet_args::tr("Specify log file"), ""};
const command_line::arg_descriptor<std::string> arg_config_file = {"config-file", wallet_args::tr("Config file"), "", true};
@@ -119,6 +120,7 @@ namespace wallet_args
command_line::add_arg(desc_params, arg_log_file);
command_line::add_arg(desc_params, arg_log_level);
command_line::add_arg(desc_params, arg_max_log_file_size);
+ command_line::add_arg(desc_params, arg_max_log_files);
command_line::add_arg(desc_params, arg_max_concurrency);
command_line::add_arg(desc_params, arg_config_file);
@@ -180,11 +182,15 @@ namespace wallet_args
log_path = command_line::get_arg(vm, arg_log_file);
else
log_path = mlog_get_default_log_path(default_log_name);
- mlog_configure(log_path, log_to_console, command_line::get_arg(vm, arg_max_log_file_size));
+ mlog_configure(log_path, log_to_console, command_line::get_arg(vm, arg_max_log_file_size), command_line::get_arg(vm, arg_max_log_files));
if (!command_line::is_arg_defaulted(vm, arg_log_level))
{
mlog_set_log(command_line::get_arg(vm, arg_log_level).c_str());
}
+ else if (!log_to_console)
+ {
+ mlog_set_categories("");
+ }
if (notice)
Print(print) << notice << ENDL;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index dc1beef7b..7f7d33642 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -712,7 +712,7 @@ namespace tools
//------------------------------------------------------------------------------------------------------------------------------
template<typename Ts, typename Tu>
bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
- bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay,
+ bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er)
{
for (const auto & ptx : ptx_vector)
@@ -741,7 +741,16 @@ namespace tools
}
else
{
- if (!do_not_relay)
+ if (m_wallet->watch_only()){
+ unsigned_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->dump_tx_to_str(ptx_vector));
+ if (unsigned_txset.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to save unsigned tx set after creation";
+ return false;
+ }
+ }
+ else if (!do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
@@ -811,7 +820,7 @@ namespace tools
return false;
}
- return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er);
}
catch (const std::exception& e)
@@ -858,7 +867,7 @@ namespace tools
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
LOG_PRINT_L2("on_transfer_split called create_transactions_2");
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er);
}
catch (const std::exception& e)
@@ -869,6 +878,141 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_wallet->restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+ if(m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "command not supported by watch-only wallet";
+ return false;
+ }
+
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+
+ tools::wallet2::unsigned_tx_set exported_txs;
+ if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "cannot load unsigned_txset";
+ return false;
+ }
+
+ std::vector<tools::wallet2::pending_tx> ptxs;
+ try
+ {
+ tools::wallet2::signed_tx_set signed_txs;
+ std::string ciphertext = m_wallet->sign_tx_dump_to_str(exported_txs, ptxs, signed_txs);
+ if (ciphertext.empty())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED;
+ er.message = "Failed to sign unsigned tx";
+ return false;
+ }
+
+ res.signed_txset = epee::string_tools::buff_to_hex_nodelimer(ciphertext);
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED;
+ er.message = std::string("Failed to sign unsigned tx: ") + e.what();
+ return false;
+ }
+
+ for (auto &ptx: ptxs)
+ {
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ }
+
+ if (req.export_raw)
+ {
+ for (auto &ptx: ptxs)
+ {
+ res.tx_raw_list.push_back(epee::string_tools::buff_to_hex_nodelimer(cryptonote::tx_to_blob(ptx.tx)));
+ }
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_wallet->restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.tx_data_hex, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+
+ std::vector<tools::wallet2::pending_tx> ptx_vector;
+ try
+ {
+ bool r = m_wallet->parse_tx_from_str(blob, ptx_vector, NULL);
+ if (!r)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA;
+ er.message = "Failed to parse signed tx data.";
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA;
+ er.message = std::string("Failed to parse signed tx: ") + e.what();
+ return false;
+ }
+
+ try
+ {
+ for (auto &ptx: ptx_vector)
+ {
+ m_wallet->commit_tx(ptx);
+ res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION;
+ er.message = std::string("Failed to submit signed tx: ") + e.what();
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
@@ -883,7 +1027,7 @@ namespace tools
{
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er);
}
catch (const std::exception& e)
@@ -931,7 +1075,7 @@ namespace tools
uint32_t priority = m_wallet->adjust_priority(req.priority);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon);
- return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er);
}
catch (const std::exception& e)
@@ -1007,7 +1151,7 @@ namespace tools
return false;
}
- return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay,
+ return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay,
res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er);
}
catch (const std::exception& e)
@@ -1974,6 +2118,72 @@ namespace tools
return false;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_wallet->restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+
+ try
+ {
+ res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str());
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_wallet->restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+
+ try
+ {
+ res.num_imported = m_wallet->import_outputs_from_str(blob);
+ }
+ catch (const std::exception &e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index cb1a274b6..9cb67c593 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -84,6 +84,8 @@ namespace tools
MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT)
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
+ MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER)
+ MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER)
MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL)
@@ -114,6 +116,8 @@ namespace tools
MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID)
MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN)
MAP_JON_RPC_WE("verify", on_verify, wallet_rpc::COMMAND_RPC_VERIFY)
+ MAP_JON_RPC_WE("export_outputs", on_export_outputs, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS)
+ MAP_JON_RPC_WE("import_outputs", on_import_outputs, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS)
MAP_JON_RPC_WE("export_key_images", on_export_key_images, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES)
MAP_JON_RPC_WE("import_key_images", on_import_key_images, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES)
MAP_JON_RPC_WE("make_uri", on_make_uri, wallet_rpc::COMMAND_RPC_MAKE_URI)
@@ -155,6 +159,8 @@ namespace tools
bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er);
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
+ bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er);
+ bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er);
bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er);
@@ -183,6 +189,8 @@ namespace tools
bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er);
bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er);
bool on_verify(const wallet_rpc::COMMAND_RPC_VERIFY::request& req, wallet_rpc::COMMAND_RPC_VERIFY::response& res, epee::json_rpc::error& er);
+ bool on_export_outputs(const wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_EXPORT_OUTPUTS::response& res, epee::json_rpc::error& er);
+ bool on_import_outputs(const wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::request& req, wallet_rpc::COMMAND_RPC_IMPORT_OUTPUTS::response& res, epee::json_rpc::error& er);
bool on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er);
bool on_import_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er);
bool on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er);
@@ -219,7 +227,7 @@ namespace tools
template<typename Ts, typename Tu>
bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
- bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay,
+ bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay,
Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er);
wallet2 *m_wallet;
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index d44aa459f..96e135f01 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -430,6 +430,7 @@ namespace wallet_rpc
std::string tx_blob;
std::string tx_metadata;
std::string multisig_txset;
+ std::string unsigned_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
@@ -440,6 +441,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata)
KV_SERIALIZE(multisig_txset)
+ KV_SERIALIZE(unsigned_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -495,6 +497,7 @@ namespace wallet_rpc
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
std::string multisig_txset;
+ std::string unsigned_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -504,6 +507,55 @@ namespace wallet_rpc
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
+ KV_SERIALIZE(unsigned_txset)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_SIGN_TRANSFER
+ {
+ struct request
+ {
+ std::string unsigned_txset;
+ bool export_raw;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(unsigned_txset)
+ KV_SERIALIZE_OPT(export_raw, false)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string signed_txset;
+ std::list<std::string> tx_hash_list;
+ std::list<std::string> tx_raw_list;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(signed_txset)
+ KV_SERIALIZE(tx_hash_list)
+ KV_SERIALIZE(tx_raw_list)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_SUBMIT_TRANSFER
+ {
+ struct request
+ {
+ std::string tx_data_hex;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_data_hex)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::list<std::string> tx_hash_list;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_hash_list)
END_KV_SERIALIZE_MAP()
};
};
@@ -543,6 +595,7 @@ namespace wallet_rpc
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
std::string multisig_txset;
+ std::string unsigned_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -552,6 +605,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
+ KV_SERIALIZE(unsigned_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -609,6 +663,7 @@ namespace wallet_rpc
std::list<std::string> tx_blob_list;
std::list<std::string> tx_metadata_list;
std::string multisig_txset;
+ std::string unsigned_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash_list)
@@ -618,6 +673,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_blob_list)
KV_SERIALIZE(tx_metadata_list)
KV_SERIALIZE(multisig_txset)
+ KV_SERIALIZE(unsigned_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -662,6 +718,7 @@ namespace wallet_rpc
std::string tx_blob;
std::string tx_metadata;
std::string multisig_txset;
+ std::string unsigned_txset;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
@@ -671,6 +728,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_blob)
KV_SERIALIZE(tx_metadata)
KV_SERIALIZE(multisig_txset)
+ KV_SERIALIZE(unsigned_txset)
END_KV_SERIALIZE_MAP()
};
};
@@ -1375,6 +1433,45 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_EXPORT_OUTPUTS
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::string outputs_data_hex;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(outputs_data_hex);
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
+ struct COMMAND_RPC_IMPORT_OUTPUTS
+ {
+ struct request
+ {
+ std::string outputs_data_hex;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(outputs_data_hex);
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ uint64_t num_imported;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(num_imported);
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_EXPORT_KEY_IMAGES
{
struct request
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index d47467940..f127ae240 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -69,3 +69,7 @@
#define WALLET_RPC_ERROR_CODE_MULTISIG_SUBMISSION -36
#define WALLET_RPC_ERROR_CODE_NOT_ENOUGH_UNLOCKED_MONEY -37
#define WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION -38
+#define WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA -39
+#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40
+#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41
+#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42