aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/CMakeLists.txt65
-rw-r--r--src/wallet/api/address_book.cpp50
-rw-r--r--src/wallet/node_rpc_proxy.cpp16
-rw-r--r--src/wallet/node_rpc_proxy.h1
-rw-r--r--src/wallet/wallet2.cpp120
-rw-r--r--src/wallet/wallet2.h46
-rw-r--r--src/wallet/wallet_rpc_helpers.h2
-rw-r--r--src/wallet/wallet_rpc_server.cpp150
-rw-r--r--src/wallet/wallet_rpc_server.h2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h53
10 files changed, 300 insertions, 205 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 3be1a6f6b..a0a166a93 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -77,42 +77,43 @@ target_link_libraries(wallet
PRIVATE
${EXTRA_LIBRARIES})
-set(wallet_rpc_sources
- wallet_rpc_server.cpp)
+if(NOT IOS)
+ set(wallet_rpc_sources
+ wallet_rpc_server.cpp)
-set(wallet_rpc_headers)
+ set(wallet_rpc_headers)
-set(wallet_rpc_private_headers
- wallet_rpc_server.h)
+ set(wallet_rpc_private_headers
+ wallet_rpc_server.h)
-monero_private_headers(wallet_rpc_server
- ${wallet_rpc_private_headers})
-monero_add_executable(wallet_rpc_server
- ${wallet_rpc_sources}
- ${wallet_rpc_headers}
- ${wallet_rpc_private_headers})
-
-target_link_libraries(wallet_rpc_server
- PRIVATE
- wallet
- rpc_base
- cryptonote_core
- cncrypto
- common
- version
- daemonizer
- ${EPEE_READLINE}
- ${Boost_CHRONO_LIBRARY}
- ${Boost_PROGRAM_OPTIONS_LIBRARY}
- ${Boost_FILESYSTEM_LIBRARY}
- ${Boost_THREAD_LIBRARY}
- ${CMAKE_THREAD_LIBS_INIT}
- ${EXTRA_LIBRARIES})
-set_property(TARGET wallet_rpc_server
- PROPERTY
- OUTPUT_NAME "monero-wallet-rpc")
-install(TARGETS wallet_rpc_server DESTINATION bin)
+ monero_private_headers(wallet_rpc_server
+ ${wallet_rpc_private_headers})
+ monero_add_executable(wallet_rpc_server
+ ${wallet_rpc_sources}
+ ${wallet_rpc_headers}
+ ${wallet_rpc_private_headers})
+ target_link_libraries(wallet_rpc_server
+ PRIVATE
+ wallet
+ rpc_base
+ cryptonote_core
+ cncrypto
+ common
+ version
+ daemonizer
+ ${EPEE_READLINE}
+ ${Boost_CHRONO_LIBRARY}
+ ${Boost_PROGRAM_OPTIONS_LIBRARY}
+ ${Boost_FILESYSTEM_LIBRARY}
+ ${Boost_THREAD_LIBRARY}
+ ${CMAKE_THREAD_LIBS_INIT}
+ ${EXTRA_LIBRARIES})
+ set_property(TARGET wallet_rpc_server
+ PROPERTY
+ OUTPUT_NAME "monero-wallet-rpc")
+ install(TARGETS wallet_rpc_server DESTINATION bin)
+endif()
# build and install libwallet_merged only if we building for GUI
if (BUILD_GUI_DEPS)
diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp
index 7be78bba7..005ddf7ee 100644
--- a/src/wallet/api/address_book.cpp
+++ b/src/wallet/api/address_book.cpp
@@ -55,37 +55,14 @@ bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &pa
return false;
}
- crypto::hash payment_id = crypto::null_hash;
- bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id);
-
- // Short payment id provided
- if(payment_id_str.length() == 16) {
- m_errorString = tr("Invalid payment ID. Short payment ID should only be used in an integrated address");
- m_errorCode = Invalid_Payment_Id;
- return false;
- }
-
- // long payment id provided but not valid
- if(!payment_id_str.empty() && !has_long_pid) {
- m_errorString = tr("Invalid payment ID");
- m_errorCode = Invalid_Payment_Id;
- return false;
- }
-
- // integrated + long payment id provided
- if(has_long_pid && info.has_payment_id) {
- m_errorString = tr("Integrated address and long payment ID can't be used at the same time");
+ if (!payment_id_str.empty())
+ {
+ m_errorString = tr("Payment ID supplied: this is obsolete");
m_errorCode = Invalid_Payment_Id;
return false;
}
- // Pad short pid with zeros
- if (info.has_payment_id)
- {
- memcpy(payment_id.data, info.payment_id.data, 8);
- }
-
- bool r = m_wallet->m_wallet->add_address_book_row(info.address,payment_id,description,info.is_subaddress);
+ bool r = m_wallet->m_wallet->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL,description,info.is_subaddress);
if (r)
refresh();
else
@@ -104,19 +81,12 @@ void AddressBookImpl::refresh()
for (size_t i = 0; i < rows.size(); ++i) {
tools::wallet2::address_book_row * row = &rows.at(i);
- std::string payment_id = (row->m_payment_id == crypto::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id);
- std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->nettype(), row->m_is_subaddress, row->m_address);
- // convert the zero padded short payment id to integrated address
- if (!row->m_is_subaddress && payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) {
- payment_id = payment_id.substr(0,16);
- crypto::hash8 payment_id_short;
- if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) {
- address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->nettype(), row->m_address, payment_id_short);
- // Don't show payment id when integrated address is used
- payment_id = "";
- }
- }
- AddressBookRow * abr = new AddressBookRow(i, address, payment_id, row->m_description);
+ std::string address;
+ if (row->m_has_payment_id)
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->nettype(), row->m_address, row->m_payment_id);
+ else
+ address = get_account_address_as_str(m_wallet->m_wallet->nettype(), row->m_is_subaddress, row->m_address);
+ AddressBookRow * abr = new AddressBookRow(i, address, "", row->m_description);
m_rows.push_back(abr);
}
diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 731896715..f3698b599 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -36,7 +36,7 @@
do { \
CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
- CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); \
+ CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon"); \
/* empty string -> not connection */ \
CHECK_AND_ASSERT_MES(!res.status.empty(), res.status, "No connection to daemon"); \
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, res.status, "Daemon busy"); \
@@ -77,6 +77,11 @@ void NodeRPCProxy::invalidate()
m_rpc_payment_seed_height = 0;
m_rpc_payment_seed_hash = crypto::null_hash;
m_rpc_payment_next_seed_hash = crypto::null_hash;
+ m_height_time = 0;
+ m_rpc_payment_diff = 0;
+ m_rpc_payment_credits_per_hash_found = 0;
+ m_rpc_payment_height = 0;
+ m_rpc_payment_cookie = 0;
}
boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
@@ -101,6 +106,7 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
void NodeRPCProxy::set_height(uint64_t h)
{
m_height = h;
+ m_height_time = time(NULL);
}
boost::optional<std::string> NodeRPCProxy::get_info()
@@ -126,12 +132,20 @@ boost::optional<std::string> NodeRPCProxy::get_info()
m_target_height = resp_t.target_height;
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
m_get_info_time = now;
+ m_height_time = now;
}
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
{
+ const time_t now = time(NULL);
+ if (now < m_height_time + 30) // re-cache every 30 seconds
+ {
+ height = m_height;
+ return boost::optional<std::string>();
+ }
+
auto res = get_info();
if (res)
return res;
diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h
index a9d8167ac..65ca40640 100644
--- a/src/wallet/node_rpc_proxy.h
+++ b/src/wallet/node_rpc_proxy.h
@@ -97,6 +97,7 @@ private:
crypto::hash m_rpc_payment_seed_hash;
crypto::hash m_rpc_payment_next_seed_hash;
uint32_t m_rpc_payment_cookie;
+ time_t m_height_time;
};
}
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 9b3e7e8b4..438c61370 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -1038,10 +1038,15 @@ uint64_t gamma_picker::pick()
return first_rct + crypto::rand_idx(n_rct);
};
+boost::mutex wallet_keys_unlocker::lockers_lock;
+unsigned int wallet_keys_unlocker::lockers = 0;
wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password):
w(w),
locked(password != boost::none)
{
+ boost::lock_guard<boost::mutex> lock(lockers_lock);
+ if (lockers++ > 0)
+ locked = false;
if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only())
{
locked = false;
@@ -1056,6 +1061,9 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee::
w(w),
locked(locked)
{
+ boost::lock_guard<boost::mutex> lock(lockers_lock);
+ if (lockers++ > 0)
+ locked = false;
if (!locked)
return;
w.generate_chacha_key_from_password(password, key);
@@ -1064,9 +1072,19 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee::
wallet_keys_unlocker::~wallet_keys_unlocker()
{
- if (!locked)
- return;
- try { w.encrypt_keys(key); }
+ try
+ {
+ boost::lock_guard<boost::mutex> lock(lockers_lock);
+ if (lockers == 0)
+ {
+ MERROR("There are no lockers in wallet_keys_unlocker dtor");
+ return;
+ }
+ --lockers;
+ if (!locked)
+ return;
+ w.encrypt_keys(key);
+ }
catch (...)
{
MERROR("Failed to re-encrypt wallet keys");
@@ -2519,12 +2537,14 @@ void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::bl
error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices)
+void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t &current_height)
{
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res);
req.block_ids = short_chain_history;
+ MDEBUG("Pulling blocks: start_height " << start_height);
+
req.prune = true;
req.start_height = start_height;
req.no_miner_tx = m_refresh_type == RefreshNoCoinbase;
@@ -2544,6 +2564,10 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height,
blocks_start_height = res.start_height;
blocks = std::move(res.blocks);
o_indices = std::move(res.output_indices);
+ current_height = res.current_height;
+
+ MDEBUG("Pulled blocks: blocks_start_height " << blocks_start_height << ", count " << blocks.size()
+ << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height);
}
//----------------------------------------------------------------------------------------------------
void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes)
@@ -2726,9 +2750,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
refresh(trusted_daemon, start_height, blocks_fetched, received_money);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception)
+void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception)
{
error = false;
+ last = false;
exception = NULL;
try
@@ -2746,7 +2771,8 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
// pull the new blocks
std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices;
- pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices);
+ uint64_t current_height;
+ pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height);
THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices");
tools::threadpool& tpool = tools::threadpool::getInstance();
@@ -2784,6 +2810,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
}
}
waiter.wait(&tpool);
+ last = !blocks.empty() && cryptonote::get_block_height(parsed_blocks.back().block) + 1 == current_height;
}
catch(...)
{
@@ -2820,7 +2847,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
}
//----------------------------------------------------------------------------------------------------
-void wallet2::update_pool_state(bool refreshed)
+void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed)
{
MTRACE("update_pool_state start");
@@ -2968,11 +2995,6 @@ void wallet2::update_pool_state(bool refreshed)
LOG_PRINT_L1("We sent that one");
}
}
- else
- {
- LOG_PRINT_L1("Already saw that one, it's for us");
- txids.push_back({txid, true});
- }
}
// get those txes
@@ -3015,13 +3037,7 @@ void wallet2::update_pool_state(bool refreshed)
[tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
if (i != txids.end())
{
- process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, 0, time(NULL), false, true, tx_entry.double_spend_seen, {});
- m_scanned_pool_txs[0].insert(tx_hash);
- if (m_scanned_pool_txs[0].size() > 5000)
- {
- std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]);
- m_scanned_pool_txs[0].clear();
- }
+ process_txs.push_back(std::make_pair(tx, tx_entry.double_spend_seen));
}
else
{
@@ -3052,6 +3068,24 @@ void wallet2::update_pool_state(bool refreshed)
MTRACE("update_pool_state end");
}
//----------------------------------------------------------------------------------------------------
+void wallet2::process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs)
+{
+ const time_t now = time(NULL);
+ for (const auto &e: txs)
+ {
+ const cryptonote::transaction &tx = e.first;
+ const bool double_spend_seen = e.second;
+ const crypto::hash tx_hash = get_transaction_hash(tx);
+ process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, 0, now, false, true, double_spend_seen, {});
+ m_scanned_pool_txs[0].insert(tx_hash);
+ if (m_scanned_pool_txs[0].size() > 5000)
+ {
+ std::swap(m_scanned_pool_txs[0], m_scanned_pool_txs[1]);
+ m_scanned_pool_txs[0].clear();
+ }
+ }
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force)
{
std::vector<crypto::hash> hashes;
@@ -3117,11 +3151,12 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height,
}
-bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress)
+bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress)
{
wallet2::address_book_row a;
a.m_address = address;
- a.m_payment_id = payment_id;
+ a.m_has_payment_id = !!payment_id;
+ a.m_payment_id = payment_id ? *payment_id : crypto::null_hash8;
a.m_description = description;
a.m_is_subaddress = is_subaddress;
@@ -3132,11 +3167,12 @@ bool wallet2::add_address_book_row(const cryptonote::account_public_address &add
return false;
}
-bool wallet2::set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress)
+bool wallet2::set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress)
{
wallet2::address_book_row a;
a.m_address = address;
- a.m_payment_id = payment_id;
+ a.m_has_payment_id = !!payment_id;
+ a.m_payment_id = payment_id ? *payment_id : crypto::null_hash8;
a.m_description = description;
a.m_is_subaddress = is_subaddress;
@@ -3255,7 +3291,15 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
});
auto scope_exit_handler_hwdev = epee::misc_utils::create_scope_leave_handler([&](){hwdev.computing_key_images(false);});
- bool first = true;
+
+ // get updated pool state first, but do not process those txes just yet,
+ // since that might cause a password prompt, which would introduce a data
+ // leak allowing a passive adversary with traffic analysis capability to
+ // infer when we get an incoming output
+ std::vector<std::pair<cryptonote::transaction, bool>> process_pool_txs;
+ update_pool_state(process_pool_txs, true);
+
+ bool first = true, last = false;
while(m_run.load(std::memory_order_relaxed))
{
uint64_t next_blocks_start_height;
@@ -3277,7 +3321,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
refreshed = true;
break;
}
- tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, error, exception);});
+ if (!last)
+ tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, last, error, exception);});
if (!first)
{
@@ -3384,8 +3429,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
try
{
// If stop() is called we don't need to check pending transactions
- if (check_pool && m_run.load(std::memory_order_relaxed))
- update_pool_state(refreshed);
+ if (check_pool && m_run.load(std::memory_order_relaxed) && !process_pool_txs.empty())
+ process_pool_state(process_pool_txs);
}
catch (...)
{
@@ -5369,6 +5414,7 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
void wallet2::set_offline(bool offline)
{
m_offline = offline;
+ m_node_rpc_proxy.set_offline(offline);
m_http_client.set_auto_connect(!offline);
if (offline)
{
@@ -13187,7 +13233,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
bool wallet2::is_synced()
{
uint64_t height;
- boost::optional<std::string> result = m_node_rpc_proxy.get_target_height(height);
+ boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
if (result && *result != CORE_RPC_STATUS_OK)
return false;
return get_blockchain_current_height() >= height;
@@ -13576,4 +13622,22 @@ std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only)
std::copy(res.gray.begin(), res.gray.end(), std::back_inserter(nodes));
return nodes;
}
+//----------------------------------------------------------------------------------------------------
+std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size)
+{
+ THROW_WALLET_EXCEPTION_IF(n_inputs <= 0, tools::error::wallet_internal_error, "Invalid n_inputs");
+ THROW_WALLET_EXCEPTION_IF(n_outputs < 0, tools::error::wallet_internal_error, "Invalid n_outputs");
+ THROW_WALLET_EXCEPTION_IF(ring_size < 0, tools::error::wallet_internal_error, "Invalid ring size");
+
+ if (ring_size == 0)
+ ring_size = get_min_ring_size();
+ if (n_outputs == 1)
+ n_outputs = 2; // extra dummy output
+
+ const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+ size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof);
+ uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof);
+ return std::make_pair(size, weight);
+}
+//----------------------------------------------------------------------------------------------------
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 640565a4e..1faeb1df8 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -122,6 +122,8 @@ private:
wallet2 &w;
bool locked;
crypto::chacha_key key;
+ static boost::mutex lockers_lock;
+ static unsigned int lockers;
};
class i_wallet2_callback
@@ -544,9 +546,10 @@ private:
struct address_book_row
{
cryptonote::account_public_address m_address;
- crypto::hash m_payment_id;
+ crypto::hash8 m_payment_id;
std::string m_description;
bool m_is_subaddress;
+ bool m_has_payment_id;
};
struct reserve_proof_entry
@@ -1123,8 +1126,8 @@ private:
* \brief GUI Address book get/store
*/
std::vector<address_book_row> get_address_book() const { return m_address_book; }
- bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress);
- bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress);
+ bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress);
+ bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress);
bool delete_address_book_row(std::size_t row_id);
uint64_t get_num_rct_outputs();
@@ -1219,7 +1222,8 @@ private:
bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
- void update_pool_state(bool refreshed = false);
+ void update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed = false);
+ void process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const;
@@ -1251,6 +1255,8 @@ private:
bool is_unattended() const { return m_unattended; }
+ std::pair<size_t, uint64_t> estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size);
+
bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
bool daemon_requires_payment();
bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance);
@@ -1403,10 +1409,10 @@ private:
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
bool clear();
void clear_soft(bool keep_key_images=false);
- void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
+ void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t &current_height);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
- void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception);
+ void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception);
void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const;
bool prepare_file_names(const std::string& file_path);
@@ -1629,7 +1635,7 @@ BOOST_CLASS_VERSION(tools::wallet2::payment_details, 4)
BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6)
-BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
+BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18)
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, 1)
@@ -1949,7 +1955,26 @@ namespace boost
inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver)
{
a & x.m_address;
- a & x.m_payment_id;
+ if (ver < 18)
+ {
+ crypto::hash payment_id;
+ a & payment_id;
+ x.m_has_payment_id = !(payment_id == crypto::null_hash);
+ if (x.m_has_payment_id)
+ {
+ bool is_long = false;
+ for (int i = 8; i < 32; ++i)
+ is_long |= payment_id.data[i];
+ if (is_long)
+ {
+ MWARNING("Long payment ID ignored on address book load");
+ x.m_payment_id = crypto::null_hash8;
+ x.m_has_payment_id = false;
+ }
+ else
+ memcpy(x.m_payment_id.data, payment_id.data, 8);
+ }
+ }
a & x.m_description;
if (ver < 17)
{
@@ -1957,6 +1982,11 @@ namespace boost
return;
}
a & x.m_is_subaddress;
+ if (ver < 18)
+ return;
+ a & x.m_has_payment_id;
+ if (x.m_has_payment_id)
+ a & x.m_payment_id;
}
template <class Archive>
diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h
index 91803ff77..4291a112d 100644
--- a/src/wallet/wallet_rpc_helpers.h
+++ b/src/wallet/wallet_rpc_helpers.h
@@ -65,7 +65,7 @@ namespace tools
rpc_payment_state.credits = post_call_credits;
rpc_payment_state.expected_spent += expected_credits;
- if (pre_call_credits < post_call_credits)
+ if (pre_call_credits <= post_call_credits)
return;
uint64_t cost = pre_call_credits - post_call_credits;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index ec21b2897..cb2ee3549 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -550,9 +550,29 @@ namespace tools
if (!m_wallet) return not_open(er);
try
{
- m_wallet->add_subaddress(req.account_index, req.label);
- res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1;
- res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index});
+ if (req.count < 1 || req.count > 64) {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Count must be between 1 and 64.";
+ return false;
+ }
+
+ std::vector<std::string> addresses;
+ std::vector<uint32_t> address_indices;
+
+ addresses.reserve(req.count);
+ address_indices.reserve(req.count);
+
+ for (uint32_t i = 0; i < req.count; i++) {
+ m_wallet->add_subaddress(req.account_index, req.label);
+ uint32_t new_address_index = m_wallet->get_num_subaddresses(req.account_index) - 1;
+ address_indices.push_back(new_address_index);
+ addresses.push_back(m_wallet->get_subaddress_as_str({req.account_index, new_address_index}));
+ }
+
+ res.address = addresses[0];
+ res.address_index = address_indices[0];
+ res.addresses = addresses;
+ res.address_indices = address_indices;
}
catch (const std::exception& e)
{
@@ -2438,7 +2458,10 @@ namespace tools
if (req.pool)
{
- m_wallet->update_pool_state();
+ std::vector<std::pair<cryptonote::transaction, bool>> process_txs;
+ m_wallet->update_pool_state(process_txs);
+ if (!process_txs.empty())
+ m_wallet->process_pool_state(process_txs);
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
m_wallet->get_unconfirmed_payments(payments, account_index, subaddr_indices);
@@ -2518,7 +2541,10 @@ namespace tools
}
}
- m_wallet->update_pool_state();
+ std::vector<std::pair<cryptonote::transaction, bool>> process_txs;
+ m_wallet->update_pool_state(process_txs);
+ if (!process_txs.empty())
+ m_wallet->process_pool_state(process_txs);
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments, req.account_index);
@@ -2719,7 +2745,14 @@ namespace tools
{
uint64_t idx = 0;
for (const auto &entry: ab)
- res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
+ {
+ std::string address;
+ if (entry.m_has_payment_id)
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.m_address, entry.m_payment_id);
+ else
+ address = get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address);
+ res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, address, entry.m_description});
+ }
}
else
{
@@ -2732,7 +2765,12 @@ namespace tools
return false;
}
const auto &entry = ab[idx];
- res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description});
+ std::string address;
+ if (entry.m_has_payment_id)
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.m_address, entry.m_payment_id);
+ else
+ address = get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address);
+ res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, address, entry.m_description});
}
}
return true;
@@ -2749,7 +2787,6 @@ namespace tools
}
cryptonote::address_parse_info info;
- crypto::hash payment_id = crypto::null_hash;
er.message = "";
if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address,
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
@@ -2771,39 +2808,7 @@ namespace tools
er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address;
return false;
}
- if (info.has_payment_id)
- {
- memcpy(payment_id.data, info.payment_id.data, 8);
- memset(payment_id.data + 8, 0, 24);
- }
- if (!req.payment_id.empty())
- {
- if (info.has_payment_id)
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Separate payment ID given with integrated address";
- return false;
- }
-
- crypto::hash long_payment_id;
-
- if (!wallet2::parse_long_payment_id(req.payment_id, payment_id))
- {
- if (!wallet2::parse_short_payment_id(req.payment_id, info.payment_id))
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string";
- return false;
- }
- else
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address";
- return false;
- }
- }
- }
- if (!m_wallet->add_address_book_row(info.address, payment_id, req.description, info.is_subaddress))
+ if (!m_wallet->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL, req.description, info.is_subaddress))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to add address book entry";
@@ -2834,7 +2839,6 @@ namespace tools
tools::wallet2::address_book_row entry = ab[req.index];
cryptonote::address_parse_info info;
- crypto::hash payment_id = crypto::null_hash;
if (req.set_address)
{
er.message = "";
@@ -2861,52 +2865,13 @@ namespace tools
entry.m_address = info.address;
entry.m_is_subaddress = info.is_subaddress;
if (info.has_payment_id)
- {
- memcpy(entry.m_payment_id.data, info.payment_id.data, 8);
- memset(entry.m_payment_id.data + 8, 0, 24);
- }
- }
-
- if (req.set_payment_id)
- {
- if (req.payment_id.empty())
- {
- payment_id = crypto::null_hash;
- }
- else
- {
- if (req.set_address && info.has_payment_id)
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Separate payment ID given with integrated address";
- return false;
- }
-
- if (!wallet2::parse_long_payment_id(req.payment_id, payment_id))
- {
- crypto::hash8 spid;
- if (!wallet2::parse_short_payment_id(req.payment_id, spid))
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string";
- return false;
- }
- else
- {
- er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID;
- er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address";
- return false;
- }
- }
- }
-
- entry.m_payment_id = payment_id;
+ entry.m_payment_id = info.payment_id;
}
if (req.set_description)
entry.m_description = req.description;
- if (!m_wallet->set_address_book_row(req.index, entry.m_address, entry.m_payment_id, entry.m_description, entry.m_is_subaddress))
+ if (!m_wallet->set_address_book_row(req.index, entry.m_address, req.set_address && entry.m_has_payment_id ? &entry.m_payment_id : NULL, entry.m_description, entry.m_is_subaddress))
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Failed to edit address book entry";
@@ -4285,6 +4250,25 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ if (!m_wallet) return not_open(er);
+ try
+ {
+ size_t extra_size = 34 /* pubkey */ + 10 /* encrypted payment id */; // typical makeup
+ const std::pair<size_t, uint64_t> sw = m_wallet->estimate_tx_size_and_weight(req.rct, req.n_inputs, req.ring_size, req.n_outputs, extra_size);
+ res.size = sw.first;
+ res.weight = sw.second;
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "Failed to determine size and weight";
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
res.version = WALLET_RPC_VERSION;
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index b2b5e7116..41f6879ef 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -154,6 +154,7 @@ namespace tools
MAP_JON_RPC_WE("set_daemon", on_set_daemon, wallet_rpc::COMMAND_RPC_SET_DAEMON)
MAP_JON_RPC_WE("set_log_level", on_set_log_level, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL)
MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES)
+ MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT)
MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION)
END_JSON_RPC_MAP()
END_URI_MAP2()
@@ -240,6 +241,7 @@ namespace tools
bool on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_log_level(const wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
//json rpc v2
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 0c86f404d..078e9778b 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 16
+#define WALLET_RPC_VERSION_MINOR 17
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -182,11 +182,13 @@ namespace wallet_rpc
{
struct request_t
{
- uint32_t account_index;
+ uint32_t account_index;
+ uint32_t count;
std::string label;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(account_index)
+ KV_SERIALIZE_OPT(count, 1U)
KV_SERIALIZE(label)
END_KV_SERIALIZE_MAP()
};
@@ -194,12 +196,16 @@ namespace wallet_rpc
struct response_t
{
- std::string address;
- uint32_t address_index;
+ std::string address;
+ uint32_t address_index;
+ std::vector<std::string> addresses;
+ std::vector<uint32_t> address_indices;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
KV_SERIALIZE(address_index)
+ KV_SERIALIZE(addresses)
+ KV_SERIALIZE(address_indices)
END_KV_SERIALIZE_MAP()
};
typedef epee::misc_utils::struct_init<response_t> response;
@@ -1823,12 +1829,10 @@ namespace wallet_rpc
struct request_t
{
std::string address;
- std::string payment_id;
std::string description;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
- KV_SERIALIZE(payment_id)
KV_SERIALIZE(description)
END_KV_SERIALIZE_MAP()
};
@@ -1852,8 +1856,6 @@ namespace wallet_rpc
uint64_t index;
bool set_address;
std::string address;
- bool set_payment_id;
- std::string payment_id;
bool set_description;
std::string description;
@@ -1861,8 +1863,6 @@ namespace wallet_rpc
KV_SERIALIZE(index)
KV_SERIALIZE(set_address)
KV_SERIALIZE(address)
- KV_SERIALIZE(set_payment_id)
- KV_SERIALIZE(payment_id)
KV_SERIALIZE(set_description)
KV_SERIALIZE(description)
END_KV_SERIALIZE_MAP()
@@ -1893,13 +1893,11 @@ namespace wallet_rpc
{
uint64_t index;
std::string address;
- std::string payment_id;
std::string description;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(index)
KV_SERIALIZE(address)
- KV_SERIALIZE(payment_id)
KV_SERIALIZE(description)
END_KV_SERIALIZE_MAP()
};
@@ -2580,5 +2578,36 @@ namespace wallet_rpc
typedef epee::misc_utils::struct_init<response_t> response;
};
+ struct COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT
+ {
+ struct request_t
+ {
+ uint32_t n_inputs;
+ uint32_t n_outputs;
+ uint32_t ring_size;
+ bool rct;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(n_inputs)
+ KV_SERIALIZE(n_outputs)
+ KV_SERIALIZE_OPT(ring_size, 0u)
+ KV_SERIALIZE_OPT(rct, true)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ uint64_t size;
+ uint64_t weight;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(size)
+ KV_SERIALIZE(weight)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
}
}