aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/depends/packages/openssl.mk6
-rw-r--r--contrib/epee/include/net/http_base.h40
-rw-r--r--contrib/epee/include/net/http_client.h100
-rw-r--r--contrib/epee/include/net/http_protocol_handler.inl87
-rw-r--r--src/cryptonote_basic/miner.cpp3
-rw-r--r--src/cryptonote_core/blockchain.cpp53
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp3
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl19
-rw-r--r--src/daemon/rpc_command_executor.cpp10
-rw-r--r--src/net/zmq.cpp6
-rw-r--r--src/net/zmq.h2
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h2
-rw-r--r--src/rpc/zmq_server.cpp4
-rw-r--r--src/wallet/wallet2.cpp203
-rw-r--r--src/wallet/wallet2.h7
-rw-r--r--src/wallet/wallet_errors.h2
-rw-r--r--tests/unit_tests/http.cpp252
17 files changed, 547 insertions, 252 deletions
diff --git a/contrib/depends/packages/openssl.mk b/contrib/depends/packages/openssl.mk
index 77d4b2d11..926b3b180 100644
--- a/contrib/depends/packages/openssl.mk
+++ b/contrib/depends/packages/openssl.mk
@@ -1,8 +1,8 @@
package=openssl
-$(package)_version=3.0.19
-$(package)_download_path=https://www.openssl.org/source
+$(package)_version=3.0.21
+$(package)_download_path=https://github.com/openssl/openssl/releases/download/openssl-$($(package)_version)
$(package)_file_name=$(package)-$($(package)_version).tar.gz
-$(package)_sha256_hash=fa5a4143b8aae18be53ef2f3caf29a2e0747430b8bc74d32d88335b94ab63072
+$(package)_sha256_hash=617e29af8e421f46649484a4937e48c685e47f46488167c982f88bc4ec1d522f
define $(package)_set_vars
$(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)"
diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h
index f32fdd9ae..aec0689a5 100644
--- a/contrib/epee/include/net/http_base.h
+++ b/contrib/epee/include/net/http_base.h
@@ -29,7 +29,7 @@
#pragma once
#include "memwipe.h"
-#include <boost/utility/string_ref.hpp>
+#include <boost/utility/string_view.hpp>
#include <string>
#include <utility>
@@ -70,7 +70,7 @@ namespace net_utils
std::string get_value_from_uri_line(const std::string& param_name, const std::string& uri);
- static inline void add_field(std::string& out, const boost::string_ref name, const boost::string_ref value)
+ static inline void add_field(std::string& out, boost::string_view name, boost::string_view value)
{
out.append(name.data(), name.size()).append(": ");
out.append(value.data(), value.size()).append("\r\n");
@@ -80,6 +80,42 @@ namespace net_utils
add_field(out, field.first, field.second);
}
+ namespace detail
+ {
+ inline bool parse_header_line(boost::string_view line, boost::string_view& name, boost::string_view& value)
+ {
+ if(!line.empty() && line.back() == '\r')
+ line.remove_suffix(1);
+ if(line.empty())
+ return false;
+ if(line.front() == ' ' || line.front() == '\t')
+ return false;
+
+ const size_t colon = line.find(':');
+ if(colon == boost::string_view::npos || colon == 0)
+ return false;
+
+ name = line.substr(0, colon);
+ value = line.substr(colon + 1);
+ while(!value.empty() && (value.front() == ' ' || value.front() == '\t'))
+ value.remove_prefix(1);
+ while(!value.empty() && (value.back() == ' ' || value.back() == '\t'))
+ value.remove_suffix(1);
+
+ for(char c : name)
+ {
+ const bool alnum = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
+ const bool allowed = alnum ||
+ c == '!' || c == '#' || c == '$' || c == '%' || c == '&' ||
+ c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' ||
+ c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
+ if(!allowed)
+ return false;
+ }
+
+ return true;
+ }
+ }
struct http_header_info
{
diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h
index 9ce30b620..949629352 100644
--- a/contrib/epee/include/net/http_client.h
+++ b/contrib/epee/include/net/http_client.h
@@ -414,7 +414,11 @@ namespace net_utils
recv_buff.assign(m_header_cache.begin()+pos+4, m_header_cache.end());
m_header_cache.erase(m_header_cache.begin()+pos+4, m_header_cache.end());
- analize_cached_header_and_invoke_state();
+ if(!analize_cached_header_and_invoke_state())
+ {
+ m_state = reciev_machine_state_error;
+ return false;
+ }
if (!on_header(m_response_info))
{
MDEBUG("Connection cancelled by on_header");
@@ -647,64 +651,46 @@ namespace net_utils
{
MTRACE("http_stream_filter::parse_cached_header(*)");
- const char *ptr = m_cache_to_process.c_str();
- while (ptr[0] != '\r' || ptr[1] != '\n')
+ size_t cur = 0;
+ while(cur < m_cache_to_process.size())
{
- // optional \n
- if (*ptr == '\n')
- ++ptr;
- // an identifier composed of letters or -
- const char *key_pos = ptr;
- while (isalnum(*ptr) || *ptr == '_' || *ptr == '-')
- ++ptr;
- const char *key_end = ptr;
- // optional space (not in RFC, but in previous code)
- if (*ptr == ' ')
- ++ptr;
- CHECK_AND_ASSERT_MES(*ptr == ':', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process);
- ++ptr;
- // optional whitespace, but not newlines - line folding is obsolete, let's ignore it
- while (isblank(*ptr))
- ++ptr;
- const char *value_pos = ptr;
- while (*ptr != '\r' && *ptr != '\n')
- ++ptr;
- const char *value_end = ptr;
- // optional trailing whitespace
- while (value_end > value_pos && isblank(*(value_end-1)))
- --value_end;
- if (*ptr == '\r')
- ++ptr;
- CHECK_AND_ASSERT_MES(*ptr == '\n', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process);
- ++ptr;
+ const size_t line_end = m_cache_to_process.find('\n', cur);
+ CHECK_AND_ASSERT_MES(line_end != std::string::npos, false, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process);
- const std::string key = std::string(key_pos, key_end - key_pos);
- const std::string value = std::string(value_pos, value_end - value_pos);
- if (!key.empty())
- {
- if (!string_tools::compare_no_case(key, "Connection"))
- body_info.m_connection = value;
- else if(!string_tools::compare_no_case(key, "Referrer"))
- body_info.m_referer = value;
- else if(!string_tools::compare_no_case(key, "Content-Length"))
- body_info.m_content_length = value;
- else if(!string_tools::compare_no_case(key, "Content-Type"))
- body_info.m_content_type = value;
- else if(!string_tools::compare_no_case(key, "Transfer-Encoding"))
- body_info.m_transfer_encoding = value;
- else if(!string_tools::compare_no_case(key, "Content-Encoding"))
- body_info.m_content_encoding = value;
- else if(!string_tools::compare_no_case(key, "Host"))
- body_info.m_host = value;
- else if(!string_tools::compare_no_case(key, "Cookie"))
- body_info.m_cookie = value;
- else if(!string_tools::compare_no_case(key, "User-Agent"))
- body_info.m_user_agent = value;
- else if(!string_tools::compare_no_case(key, "Origin"))
- body_info.m_origin = value;
- else
- body_info.m_etc_fields.emplace_back(key, value);
- }
+ boost::string_view line(m_cache_to_process.data() + cur, line_end - cur);
+ cur = line_end + 1;
+
+ if(line == "\r" || line.empty())
+ break;
+
+ boost::string_view name;
+ boost::string_view value;
+ CHECK_AND_ASSERT_MES(detail::parse_header_line(line, name, value), false, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process);
+
+ std::string key(name.data(), name.size());
+ std::string val(value.data(), value.size());
+ if (!string_tools::compare_no_case(key, "Connection"))
+ body_info.m_connection = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Referer"))
+ body_info.m_referer = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Content-Length"))
+ body_info.m_content_length = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Content-Type"))
+ body_info.m_content_type = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Transfer-Encoding"))
+ body_info.m_transfer_encoding = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Content-Encoding"))
+ body_info.m_content_encoding = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Host"))
+ body_info.m_host = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Cookie"))
+ body_info.m_cookie = std::move(val);
+ else if(!string_tools::compare_no_case(key, "User-Agent"))
+ body_info.m_user_agent = std::move(val);
+ else if(!string_tools::compare_no_case(key, "Origin"))
+ body_info.m_origin = std::move(val);
+ else
+ body_info.m_etc_fields.emplace_back(std::move(key), std::move(val));
}
return true;
}
diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl
index 6647d1f15..3e46633b7 100644
--- a/contrib/epee/include/net/http_protocol_handler.inl
+++ b/contrib/epee/include/net/http_protocol_handler.inl
@@ -25,8 +25,9 @@
//
-#include <boost/regex.hpp>
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp>
#include "http_protocol_handler.h"
#include "reg_exp_definer.h"
#include "string_tools.h"
@@ -546,54 +547,52 @@ namespace net_utils
template<class t_connection_context>
bool simple_http_connection_handler<t_connection_context>::parse_cached_header(http_header_info& body_info, const std::string& m_cache_to_process, size_t pos)
{
- STATIC_REGEXP_EXPR_1(rexp_mach_field,
- "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)"
- // 12 3 4 5 6 7 8 9 10 11
- "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]",
- //11 1213 14
- boost::regex::icase | boost::regex::normal);
-
- boost::smatch result;
- std::string::const_iterator it_current_bound = m_cache_to_process.begin();
- std::string::const_iterator it_end_bound = m_cache_to_process.begin()+pos;
-
body_info.clear();
+ if(pos > m_cache_to_process.size() || pos > HTTP_MAX_HEADER_LEN)
+ return false;
+
+ size_t cur = 0;
- //lookup all fields and fill well-known fields
- while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched)
+ while(cur < pos)
{
- const size_t field_val = 14;
- const size_t field_etc_name = 12;
+ const size_t line_end = m_cache_to_process.find('\n', cur);
+ if(line_end == std::string::npos || line_end >= pos)
+ break;
- int i = 2; //start position = 2
- if(result[i++].matched)//"Connection"
- body_info.m_connection = result[field_val];
- else if(result[i++].matched)//"Referer"
- body_info.m_referer = result[field_val];
- else if(result[i++].matched)//"Content-Length"
- body_info.m_content_length = result[field_val];
- else if(result[i++].matched)//"Content-Type"
- body_info.m_content_type = result[field_val];
- else if(result[i++].matched)//"Transfer-Encoding"
- body_info.m_transfer_encoding = result[field_val];
- else if(result[i++].matched)//"Content-Encoding"
- body_info.m_content_encoding = result[field_val];
- else if(result[i++].matched)//"Host"
- body_info.m_host = result[field_val];
- else if(result[i++].matched)//"Cookie"
- body_info.m_cookie = result[field_val];
- else if(result[i++].matched)//"User-Agent"
- body_info.m_user_agent = result[field_val];
- else if(result[i++].matched)//"Origin"
- body_info.m_origin = result[field_val];
- else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!)
- body_info.m_etc_fields.push_back(std::pair<std::string, std::string>(result[field_etc_name], result[field_val]));
- else
- {
- LOG_ERROR_CC(m_conn_context, "simple_http_connection_handler<t_connection_context>::parse_cached_header() not matched last entry in:" << m_cache_to_process);
- }
+ boost::string_view line(m_cache_to_process.data() + cur, line_end - cur);
+ cur = line_end + 1;
+
+ // End of header block.
+ if(line == "\r" || line.empty())
+ break;
- it_current_bound = result[(int)result.size()-1]. first;
+ boost::string_view name;
+ boost::string_view value;
+ if(!detail::parse_header_line(line, name, value))
+ return false;
+
+ if(boost::iequals(name, "Connection"))
+ body_info.m_connection = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Referer"))
+ body_info.m_referer = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Content-Length"))
+ body_info.m_content_length = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Content-Type"))
+ body_info.m_content_type = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Transfer-Encoding"))
+ body_info.m_transfer_encoding = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Content-Encoding"))
+ body_info.m_content_encoding = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Host"))
+ body_info.m_host = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Cookie"))
+ body_info.m_cookie = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "User-Agent"))
+ body_info.m_user_agent = std::string(value.data(), value.size());
+ else if(boost::iequals(name, "Origin"))
+ body_info.m_origin = std::string(value.data(), value.size());
+ else
+ body_info.m_etc_fields.push_back(std::make_pair(std::string(name.data(), name.size()), std::string(value.data(), value.size())));
}
return true;
}
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index 71b8f78cc..fd13ff74d 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -578,7 +578,8 @@ namespace cryptonote
if ((b.major_version >= RX_BLOCK_VERSION) && !rx_set)
{
- crypto::rx_set_miner_thread(th_local_index, tools::get_max_concurrency());
+ // Must be non-zero value because 0 means "not a miner thread, run with secure JIT" in rx-slow-hash.c
+ crypto::rx_set_miner_thread(th_local_index + 1, tools::get_max_concurrency());
rx_set = true;
}
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 1004215d7..fcc92644a 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -5237,7 +5237,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
its = m_scan_table.find(tx_prefix_hash);
assert(its != m_scan_table.end());
- // get all amounts from tx.vin(s)
+ // initialize amount buckets for tx.vin(s)
for (const auto &txin : tx.vin)
{
const txin_to_key &in_to_key = boost::get < txin_to_key > (txin);
@@ -5247,22 +5247,8 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
if (it != its->second.end())
SCAN_TABLE_QUIT("Duplicate key_image found from incoming blocks.");
- amounts.push_back(in_to_key.amount);
- }
-
- // sort and remove duplicate amounts from amounts list
- std::sort(amounts.begin(), amounts.end());
- auto last = std::unique(amounts.begin(), amounts.end());
- amounts.erase(last, amounts.end());
-
- // add amount to the offset_map and tx_map
- for (const uint64_t &amount : amounts)
- {
- if (offset_map.find(amount) == offset_map.end())
- offset_map.emplace(amount, std::vector<uint64_t>());
-
- if (tx_map.find(amount) == tx_map.end())
- tx_map.emplace(amount, std::vector<output_data_t>());
+ offset_map.emplace(in_to_key.amount, std::vector<uint64_t>());
+ tx_map.emplace(in_to_key.amount, std::vector<output_data_t>());
}
// add new absolute_offsets to offset_map
@@ -5279,6 +5265,10 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
++block_index;
}
+ amounts.reserve(offset_map.size());
+ for (const auto &offsets : offset_map)
+ amounts.push_back(offsets.first);
+
// sort and remove duplicate absolute_offsets in offset_map
for (auto &offsets : offset_map)
{
@@ -5337,25 +5327,24 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
const txin_to_key &in_to_key = boost::get < txin_to_key > (txin);
auto needed_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets);
+ const auto offset_it = offset_map.find(in_to_key.amount);
+ const auto tx_it = tx_map.find(in_to_key.amount);
+ if (offset_it == offset_map.end() || tx_it == tx_map.end())
+ SCAN_TABLE_QUIT("Amount not found on scan table from incoming blocks.");
+
+ const std::vector<uint64_t> &offsets_found = offset_it->second;
+ const std::vector<output_data_t> &outputs_found = tx_it->second;
+
std::vector<output_data_t> outputs;
for (const uint64_t & offset_needed : needed_offsets)
{
- size_t pos = 0;
- bool found = false;
-
- for (const uint64_t &offset_found : offset_map[in_to_key.amount])
- {
- if (offset_needed == offset_found)
- {
- found = true;
- break;
- }
-
- ++pos;
- }
+ // offsets_found is sorted above before output_scan_worker populates outputs_found.
+ const auto found_it = std::lower_bound(offsets_found.begin(), offsets_found.end(), offset_needed);
+ const size_t pos = found_it - offsets_found.begin();
+ const bool found = found_it != offsets_found.end() && *found_it == offset_needed;
- if (found && pos < tx_map[in_to_key.amount].size())
- outputs.push_back(tx_map[in_to_key.amount].at(pos));
+ if (found && pos < outputs_found.size())
+ outputs.push_back(outputs_found[pos]);
else
break;
}
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index c350e24c4..7aea5cf50 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -453,11 +453,12 @@ namespace cryptonote
crypto::public_key out_eph_public_key;
crypto::view_tag view_tag;
- hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key,
+ const bool r = hwdev.generate_output_ephemeral_keys(tx.version,sender_account_keys, txkey_pub, tx_key,
dst_entr, change_addr, output_index,
need_additional_txkeys, additional_tx_keys,
additional_tx_public_keys, amount_keys, out_eph_public_key,
use_view_tags, view_tag);
+ CHECK_AND_ASSERT_MES(r, false, "Failed to generate output ephemeral keys");
tx_out out;
cryptonote::set_tx_out(dst_entr.amount, out_eph_public_key, use_view_tags, view_tag, out);
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index 6c36a3b5b..3ad26a3de 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -378,6 +378,11 @@ namespace cryptonote
cnx.ip = cnx.host;
cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port());
}
+ else if (cntxt.m_remote_address.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id())
+ {
+ cnx.ip = cnx.host;
+ cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv6_network_address>().port());
+ }
cnx.rpc_port = cntxt.m_rpc_port;
cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash;
@@ -878,17 +883,23 @@ namespace cryptonote
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context)
{
MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)");
- std::unordered_set<blobdata> seen;
+ std::unordered_set<crypto::hash> seen;
+ seen.reserve(arg.txs.size());
+
for (const auto &blob: arg.txs)
{
MLOGIF_P2P_MESSAGE(cryptonote::transaction tx; crypto::hash hash; bool ret = cryptonote::parse_and_validate_tx_from_blob(blob, tx, hash);, ret, "Including transaction " << hash);
- if (seen.find(blob) != seen.end())
+
+ crypto::hash digest{};
+ if (!blob.empty())
+ tools::sha256sum(reinterpret_cast<const uint8_t*>(blob.data()), blob.size(), digest);
+
+ if (!seen.insert(digest).second)
{
LOG_PRINT_CCONTEXT_L1("Duplicate transaction in notification, dropping connection");
drop_connection(context, false, false);
return 1;
}
- seen.insert(blob);
}
if(context.m_state != cryptonote_connection_context::state_normal)
@@ -2841,6 +2852,8 @@ skip:
{
MINFO("Target height decreasing from " << previous_target << " to " << target);
m_core.set_target_blockchain_height(target);
+ if (target < m_core.get_current_blockchain_height() + 5)
+ m_core.safesyncmode(true);
if (target == 0 && context.m_state > cryptonote_connection_context::state_before_handshake && !m_stopping)
{
MCWARNING("global", "monerod is now disconnected from the network");
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index f0407eece..f6746b8ad 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -644,7 +644,11 @@ bool t_rpc_command_executor::print_connections() {
}
}
- tools::msg_writer() << std::setw(30) << std::left << "Remote Host"
+ int host_field_width = 15;
+ for (const auto &conn : res.connections)
+ host_field_width = std::max(host_field_width, 8 + (int) conn.address.length());
+
+ tools::msg_writer() << std::setw(host_field_width) << std::left << "Remote Host"
<< std::setw(8) << "Type"
<< std::setw(6) << "SSL"
<< std::setw(20) << "Peer id"
@@ -661,11 +665,11 @@ bool t_rpc_command_executor::print_connections() {
for (auto & info : res.connections)
{
std::string address = info.incoming ? "INC " : "OUT ";
- address += info.ip + ":" + info.port;
+ address += info.address;
//std::string in_out = info.incoming ? "INC " : "OUT ";
tools::msg_writer()
//<< std::setw(30) << std::left << in_out
- << std::setw(30) << std::left << address
+ << std::setw(host_field_width) << std::left << address
<< std::setw(8) << (get_address_type_name((epee::net_utils::address_type)info.address_type))
<< std::setw(6) << (info.ssl ? "yes" : "no")
<< std::setw(20) << info.peer_id
diff --git a/src/net/zmq.cpp b/src/net/zmq.cpp
index 2b3ca8376..4a9d3e162 100644
--- a/src/net/zmq.cpp
+++ b/src/net/zmq.cpp
@@ -150,6 +150,12 @@ namespace zmq
if ((last = zmq_msg_recv(part.handle(), socket, flags)) < 0)
return last;
+ if (max_message_size < payload.size() ||
+ max_message_size - payload.size() < part.size())
+ {
+ errno = EMSGSIZE;
+ return -1;
+ }
payload.append(part.data(), part.size());
if (!zmq_msg_more(part.handle()))
break;
diff --git a/src/net/zmq.h b/src/net/zmq.h
index 18bb80c8b..68b6a71be 100644
--- a/src/net/zmq.h
+++ b/src/net/zmq.h
@@ -64,6 +64,8 @@ namespace net
{
namespace zmq
{
+ constexpr std::size_t max_message_size = 10 * 1024 * 1024; // 10 MiB
+
//! \return Category for ZMQ errors.
const std::error_category& error_category() noexcept;
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index 46e52d42f..e6c07ce77 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -88,7 +88,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 3
-#define CORE_RPC_VERSION_MINOR 15
+#define CORE_RPC_VERSION_MINOR 16
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp
index 7ebb6c49f..3f1f38e21 100644
--- a/src/rpc/zmq_server.cpp
+++ b/src/rpc/zmq_server.cpp
@@ -46,7 +46,7 @@ namespace cryptonote
namespace
{
constexpr const int num_zmq_threads = 1;
- constexpr const std::int64_t max_message_size = 10 * 1024 * 1024; // 10 MiB
+ constexpr const std::int64_t max_frame_size = net::zmq::max_message_size;
constexpr const std::chrono::seconds linger_timeout{2}; // wait period for pending out messages
net::zmq::socket init_socket(void* context, int type, epee::span<const std::string> addresses)
@@ -62,7 +62,7 @@ namespace
return nullptr;
}
- if (zmq_setsockopt(out.get(), ZMQ_MAXMSGSIZE, std::addressof(max_message_size), sizeof(max_message_size)) != 0)
+ if (zmq_setsockopt(out.get(), ZMQ_MAXMSGSIZE, std::addressof(max_frame_size), sizeof(max_frame_size)) != 0)
{
MONERO_LOG_ZMQ_ERROR("Failed to set maximum incoming message size");
return nullptr;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 61f49481e..4c83dd890 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -3105,6 +3105,7 @@ void read_pool_txs(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req,
MDEBUG("Reading pool txs");
if (res.txs.size() == req.txs_hashes.size())
{
+ const std::unordered_set<crypto::hash> txid_set(txids.begin(), txids.end());
for (const auto &tx_entry: res.txs)
{
if (tx_entry.in_pool)
@@ -3115,9 +3116,7 @@ void read_pool_txs(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request &req,
if (get_pruned_tx(tx_entry, tx, tx_hash))
{
- const std::vector<crypto::hash>::const_iterator i = std::find_if(txids.begin(), txids.end(),
- [tx_hash](const crypto::hash &e) { return e == tx_hash; });
- if (i != txids.end())
+ if (txid_set.count(tx_hash) > 0)
{
txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen));
}
@@ -3362,11 +3361,14 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
{
THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range");
const cryptonote::transaction& tx = parsed_blocks[i].block.miner_tx;
- const size_t n_vouts = (m_refresh_type == RefreshType::RefreshOptimizeCoinbase && tx.version < 2) ? 1 : tx.vout.size();
- if (parsed_blocks[i].block.major_version >= hf_version_view_tags)
- geniods.push_back(geniod_params{ tx, n_vouts, txidx });
- else
- tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true);
+ const size_t n_vouts = (m_refresh_type == RefreshType::RefreshOptimizeCoinbase && tx.version < 2 && !tx.vout.empty()) ? 1 : tx.vout.size();
+ if (n_vouts > 0)
+ {
+ if (parsed_blocks[i].block.major_version >= hf_version_view_tags)
+ geniods.push_back(geniod_params{ tx, n_vouts, txidx });
+ else
+ tpool.submit(&waiter, [&, n_vouts, txidx](){ geniod(tx, n_vouts, txidx); }, true);
+ }
}
++txidx;
for (size_t j = 0; j < parsed_blocks[i].txes.size(); ++j)
@@ -3566,21 +3568,18 @@ void wallet2::pull_and_parse_next_blocks(bool first, bool try_incremental, uint6
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found)
{
+ remove_obsolete_pool_txs(std::unordered_set<crypto::hash>(tx_hashes.begin(), tx_hashes.end()), remove_if_found);
+}
+
+void wallet2::remove_obsolete_pool_txs(const std::unordered_set<crypto::hash> &tx_hashes, bool remove_if_found)
+{
// remove pool txes to us that aren't in the pool anymore (remove_if_found = false),
// or remove pool txes to us that were reported as removed (remove_if_found = true)
std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin();
while (uit != m_unconfirmed_payments.end())
{
const crypto::hash &txid = uit->second.m_pd.m_tx_hash;
- bool found = false;
- for (const auto &it2: tx_hashes)
- {
- if (it2 == txid)
- {
- found = true;
- break;
- }
- }
+ const bool found = tx_hashes.count(txid) > 0;
auto pit = uit++;
if ((!remove_if_found && !found) || (remove_if_found && found))
{
@@ -3596,17 +3595,9 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe
// Code that is common to 'update_pool_state_by_pool_query' and 'update_pool_state_from_pool_data':
// Check wether a tx in the pool is worthy of processing because we did not see it
// yet or because it is "interesting" out of special circumstances
-bool wallet2::accept_pool_tx_for_processing(const crypto::hash &txid)
+bool wallet2::accept_pool_tx_for_processing(const crypto::hash &txid, const std::unordered_set<crypto::hash> &payments_tx_hashes)
{
- bool txid_found_in_up = false;
- for (const auto &up: m_unconfirmed_payments)
- {
- if (up.second.m_pd.m_tx_hash == txid)
- {
- txid_found_in_up = true;
- break;
- }
- }
+ const bool txid_found_in_up = payments_tx_hashes.count(txid) > 0;
if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
{
// if it's for us, we want to keep track of whether we saw a double spend, so don't bail out
@@ -3619,30 +3610,25 @@ bool wallet2::accept_pool_tx_for_processing(const crypto::hash &txid)
if (!txid_found_in_up)
{
LOG_PRINT_L1("Found new pool tx: " << txid);
- bool found = false;
- for (const auto &i: m_unconfirmed_txs)
+ const auto i = m_unconfirmed_txs.find(txid);
+ bool sent_by_us = i != m_unconfirmed_txs.end();
+ if (sent_by_us)
{
- if (i.first == txid)
+ const unconfirmed_transfer_details& utd = i->second;
+ for (const auto& dst : utd.m_dests)
{
- found = true;
- // if this is a payment to yourself at a different subaddress account, don't skip it
- // so that you can see the incoming pool tx with 'show_transfers' on that receiving subaddress account
- const unconfirmed_transfer_details& utd = i.second;
- for (const auto& dst : utd.m_dests)
+ auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key);
+ if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account)
{
- auto subaddr_index = m_subaddresses.find(dst.addr.m_spend_public_key);
- if (subaddr_index != m_subaddresses.end() && subaddr_index->second.major != utd.m_subaddr_account)
- {
- found = false;
- break;
- }
+ // Payment to ourselves at a different subaddress account:
+ // process it so the receiving account can show the incoming pool tx.
+ sent_by_us = false;
+ break;
}
- break;
}
}
- if (!found)
+ if (!sent_by_us)
{
- // not one of those we sent ourselves
return true;
}
else
@@ -3813,19 +3799,14 @@ void wallet2::update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote:
// remove any pending tx that's not in the pool
const auto now = std::chrono::system_clock::now();
std::unordered_map<crypto::hash, wallet2::unconfirmed_transfer_details>::iterator it = m_unconfirmed_txs.begin();
+
+ const std::unordered_set<crypto::hash> pool_set(res.tx_hashes.begin(), res.tx_hashes.end());
+
while (it != m_unconfirmed_txs.end())
{
const crypto::hash &txid = it->first;
MDEBUG("Checking m_unconfirmed_txs entry " << txid);
- bool found = false;
- for (const auto &it2: res.tx_hashes)
- {
- if (it2 == txid)
- {
- found = true;
- break;
- }
- }
+ const bool found = pool_set.count(txid) > 0;
auto pit = it++;
process_unconfirmed_transfer(false, txid, pit->second, found, now, refreshed);
MDEBUG("New state of that entry: " << pit->second.m_state);
@@ -3837,15 +3818,21 @@ void wallet2::update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote:
// the in transfers list instead (or nowhere if it just
// disappeared without being mined)
if (refreshed)
- remove_obsolete_pool_txs(res.tx_hashes, false);
+ remove_obsolete_pool_txs(pool_set, false);
MTRACE("update_pool_state_by_pool_query done second loop");
+ std::unordered_set<crypto::hash> payments_tx_hashes;
+ payments_tx_hashes.reserve(m_unconfirmed_payments.size());
+ for (const auto &p: m_unconfirmed_payments)
+ payments_tx_hashes.insert(p.second.m_pd.m_tx_hash);
+
// gather txids of new pool txes to us
std::vector<crypto::hash> txids;
+ txids.reserve(res.tx_hashes.size());
for (const auto &txid: res.tx_hashes)
{
- if (accept_pool_tx_for_processing(txid))
+ if (accept_pool_tx_for_processing(txid, payments_tx_hashes))
txids.push_back(txid);
}
@@ -3872,6 +3859,11 @@ void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vect
m_encrypt_keys_after_refresh.reset();
});
+ std::unordered_set<crypto::hash> added_pool_txids;
+ added_pool_txids.reserve(added_pool_txs.size());
+ for (const auto &pool_tx: added_pool_txs)
+ added_pool_txids.insert(std::get<1>(pool_tx));
+
if (refreshed)
{
if (incremental)
@@ -3884,16 +3876,8 @@ void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vect
}
else
{
- // Delete from the list of unconfirmed payments what we don't find anymore in the pool; a bit
- // unfortunate that we have to build a new vector with ids first, but better than copying and
- // modifying the code of 'remove_obsolete_pool_txs' here
- std::vector<crypto::hash> txids;
- txids.reserve(added_pool_txs.size());
- for (const auto &pool_tx: added_pool_txs)
- {
- txids.push_back(std::get<1>(pool_tx));
- }
- remove_obsolete_pool_txs(txids, false);
+ // Delete from the list of unconfirmed payments what we don't find anymore in the pool
+ remove_obsolete_pool_txs(added_pool_txids, false);
}
}
@@ -3904,15 +3888,7 @@ void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vect
{
const crypto::hash &txid = it->first;
MDEBUG("Checking m_unconfirmed_txs entry " << txid);
- bool found = false;
- for (const auto &pool_tx: added_pool_txs)
- {
- if (std::get<1>(pool_tx) == txid)
- {
- found = true;
- break;
- }
- }
+ const bool found = added_pool_txids.count(txid) > 0;
auto pit = it++;
process_unconfirmed_transfer(incremental, txid, pit->second, found, now, refreshed);
MDEBUG("Resulting state of that entry: " << pit->second.m_state);
@@ -3922,9 +3898,13 @@ void wallet2::update_pool_state_from_pool_data(bool incremental, const std::vect
// if we work incrementally and thus see only new pool txs since last time we asked it should
// be rare that we know already about one of those, but check nevertheless
process_txs.clear();
+ std::unordered_set<crypto::hash> payments_tx_hashes;
+ payments_tx_hashes.reserve(m_unconfirmed_payments.size());
+ for (const auto &p: m_unconfirmed_payments)
+ payments_tx_hashes.insert(p.second.m_pd.m_tx_hash);
for (const auto &pool_tx: added_pool_txs)
{
- if (accept_pool_tx_for_processing(std::get<1>(pool_tx)))
+ if (accept_pool_tx_for_processing(std::get<1>(pool_tx), payments_tx_hashes))
{
process_txs.push_back(pool_tx);
}
@@ -8155,14 +8135,6 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs)
{
LOG_PRINT_L0("saving " << txs.m_ptx.size() << " multisig transactions");
- // txes generated, get rid of used k values
- for (size_t n = 0; n < txs.m_ptx.size(); ++n)
- for (size_t idx: txs.m_ptx[n].construction_data.selected_transfers)
- {
- memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0]));
- m_transfers[idx].m_multisig_k.clear();
- }
-
// zero out some data we don't want to share
for (auto &ptx: txs.m_ptx)
{
@@ -8190,6 +8162,11 @@ std::string wallet2::save_multisig_tx(multisig_tx_set txs)
}
LOG_PRINT_L2("Saving multisig unsigned tx data: " << oss.str());
std::string ciphertext = encrypt_with_view_secret_key(oss.str());
+
+ // The transaction creator has already signed, so do not expose the txset
+ // until the corresponding one-time nonce erasure is stored.
+ clear_multisig_k_and_store(txs);
+
return std::string(MULTISIG_UNSIGNED_TX_PREFIX) + ciphertext;
}
//----------------------------------------------------------------------------------------------------
@@ -8348,8 +8325,12 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t
return true;
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids)
+bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs_inout, std::vector<crypto::hash> &txids)
{
+ multisig_tx_set exported_txs = exported_txs_inout;
+ std::vector<crypto::hash> signed_txids;
+ std::vector<std::pair<crypto::hash, size_t>> signed_tx_key_indices;
+
THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found");
const crypto::public_key local_signer = get_multisig_signer_public_key();
@@ -8363,8 +8344,6 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
THROW_WALLET_EXCEPTION_IF(frozen(exported_txs),
error::wallet_internal_error, "Will not sign multisig tx containing frozen outputs")
- txids.clear();
-
// The 'exported_txs' contains a set of different transactions for the multisig group to try to sign. Each of those
// transactions has a set of 'signing attempts' corresponding to all the possible signing groups within the multisig.
// - Here, we will partially sign as many of those signing attempts as possible, for each proposed transaction.
@@ -8474,25 +8453,26 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto
"Unable to finalize the transaction: the ignore sets for these tx attempts seem to be malformed.");
const crypto::hash txid = get_transaction_hash(ptx.tx);
if (store_tx_info())
- {
- m_tx_keys[txid] = ptx.tx_key;
- m_additional_tx_keys[txid] = ptx.additional_tx_keys;
- }
- txids.push_back(txid);
+ signed_tx_key_indices.emplace_back(txid, n);
+ signed_txids.push_back(txid);
}
}
- // signatures generated, get rid of any unused k values (must do export_multisig() to make more tx attempts with the
- // inputs in the transactions worked on here)
- for (size_t n = 0; n < exported_txs.m_ptx.size(); ++n)
- for (size_t idx: exported_txs.m_ptx[n].construction_data.selected_transfers)
- {
- memwipe(m_transfers[idx].m_multisig_k.data(), m_transfers[idx].m_multisig_k.size() * sizeof(m_transfers[idx].m_multisig_k[0]));
- m_transfers[idx].m_multisig_k.clear();
- }
+ exported_txs.m_signers.insert(local_signer);
- exported_txs.m_signers.insert(get_multisig_signer_public_key());
+ // Do not expose signatures until all nonce material for the selected inputs
+ // has been erased from the wallet cache.
+ clear_multisig_k_and_store(exported_txs);
+ for (const auto &entry: signed_tx_key_indices)
+ {
+ const auto &ptx = exported_txs.m_ptx[entry.second];
+ m_tx_keys[entry.first] = ptx.tx_key;
+ m_additional_tx_keys[entry.first] = ptx.additional_tx_keys;
+ }
+
+ exported_txs_inout = std::move(exported_txs);
+ txids = std::move(signed_txids);
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -9285,6 +9265,8 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// check we're clear enough of rct start, to avoid corner cases below
THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE,
error::get_output_distribution, "Not enough rct outputs");
+ THROW_WALLET_EXCEPTION_IF(!std::is_sorted(rct_offsets.begin(), rct_offsets.end()),
+ error::get_output_distribution, "Daemon reports non-monotonic rct output distribution");
THROW_WALLET_EXCEPTION_IF(rct_offsets.back() <= max_rct_index,
error::get_output_distribution, "Daemon reports suspicious number of rct outputs");
}
@@ -15223,6 +15205,27 @@ void wallet2::get_multisig_k(size_t idx, const std::unordered_set<rct::key> &use
THROW_WALLET_EXCEPTION(tools::error::multisig_export_needed);
}
//----------------------------------------------------------------------------------------------------
+void wallet2::clear_multisig_k_and_store(const multisig_tx_set &txs)
+{
+ // Must succeed before any txset produced with these nonces is exposed.
+ bool changed = false;
+ for (const auto &ptx: txs.m_ptx)
+ {
+ for (size_t idx: ptx.construction_data.selected_transfers)
+ {
+ std::vector<rct::key> &multisig_k = m_transfers[idx].m_multisig_k;
+ if (multisig_k.empty())
+ continue;
+ memwipe(multisig_k.data(), multisig_k.size() * sizeof(multisig_k[0]));
+ multisig_k.clear();
+ changed = true;
+ }
+ }
+
+ if (changed)
+ store();
+}
+//----------------------------------------------------------------------------------------------------
rct::multisig_kLRki wallet2::get_multisig_kLRki(size_t n, const rct::key &k) const
{
CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad m_transfers index");
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index cedfecbeb..b18e9d8c6 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -31,6 +31,7 @@
#pragma once
#include <memory>
+#include <unordered_set>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
@@ -1211,7 +1212,7 @@ private:
bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
- bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto::hash> &txids);
+ bool sign_multisig_tx(multisig_tx_set &exported_txs_inout, std::vector<crypto::hash> &txids);
bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids);
std::vector<pending_tx> create_unmixable_sweep_transactions();
void discard_unmixable_outputs();
@@ -1649,6 +1650,7 @@ private:
void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false, bool try_incremental = false);
void process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes, bool remove_if_found);
+ void remove_obsolete_pool_txs(const std::unordered_set<crypto::hash> &tx_hashes, bool remove_if_found);
std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const;
std::string encrypt(const epee::span<char> &span, const crypto::secret_key &skey, bool authenticated = true) const;
@@ -1869,7 +1871,7 @@ private:
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(bool first, bool try_incremental, 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);
- bool accept_pool_tx_for_processing(const crypto::hash &txid);
+ bool accept_pool_tx_for_processing(const crypto::hash &txid, const std::unordered_set<crypto::hash> &payments_tx_hashes);
void process_unconfirmed_transfer(bool incremental, const crypto::hash &txid, wallet2::unconfirmed_transfer_details &tx_details, bool seen_in_pool, std::chrono::system_clock::time_point now, bool refreshed);
void process_pool_info_extent(const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response &res, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed);
void update_pool_state_by_pool_query(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false);
@@ -1908,6 +1910,7 @@ private:
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const std::unordered_set<crypto::public_key> &ignore_set, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const;
void get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L, rct::key &nonce);
+ void clear_multisig_k_and_store(const multisig_tx_set &txs);
void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n);
bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx);
bool add_rings(const cryptonote::transaction_prefix &tx);
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index c54cd3499..e0a767159 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -440,7 +440,7 @@ namespace tools
struct out_of_hashchain_bounds_error : public refresh_error
{
explicit out_of_hashchain_bounds_error(std::string&& loc)
- : refresh_error(std::move(loc), "Index out of bounds of of hashchain")
+ : refresh_error(std::move(loc), "Index out of bounds of hashchain")
{
}
diff --git a/tests/unit_tests/http.cpp b/tests/unit_tests/http.cpp
index 1746ee190..b9f569fda 100644
--- a/tests/unit_tests/http.cpp
+++ b/tests/unit_tests/http.cpp
@@ -28,6 +28,9 @@
#include "gtest/gtest.h"
#include "net/http_auth.h"
+#include "net/http_client.h"
+#include "syncobj.h"
+#include "net/http_protocol_handler.h"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/join.hpp>
@@ -51,6 +54,7 @@
#include <boost/spirit/include/qi_plus.hpp>
#include <boost/spirit/include/qi_sequence.hpp>
#include <boost/spirit/include/qi_string.hpp>
+#include <chrono>
#include <cstdint>
#include <iterator>
#include <openssl/evp.h>
@@ -67,6 +71,111 @@ namespace http = epee::net_utils::http;
using fields = std::unordered_map<std::string, std::string>;
using auth_responses = std::vector<fields>;
+class test_http_endpoint final : public epee::net_utils::i_service_endpoint
+{
+public:
+ bool do_send(epee::byte_slice message) override
+ {
+ sent.append(reinterpret_cast<const char*>(message.data()), message.size());
+ return true;
+ }
+ bool close(const bool) override { return true; }
+ bool send_done() override { return true; }
+ bool call_run_once_service_io() override { return true; }
+ bool request_callback() override { return true; }
+ boost::asio::io_context& get_io_context() override { return io_context; }
+ bool add_ref() override { return true; }
+ bool release() override { return true; }
+
+ boost::asio::io_context io_context;
+ std::string sent;
+};
+
+class dummy_client
+{
+public:
+ bool connect(const std::string&, int, std::chrono::milliseconds, bool = false, const std::string& = "0.0.0.0") { return true; }
+ bool connect(const std::string&, const std::string&, std::chrono::milliseconds, bool = false, const std::string& = "0.0.0.0") { return true; }
+ bool disconnect() { return true; }
+ bool send(const boost::string_ref, std::chrono::milliseconds) { return true; }
+ bool send(const void*, size_t) { return true; }
+ bool recv(std::string& buff, std::chrono::milliseconds)
+ {
+ buff = data;
+ data.clear();
+ return true;
+ }
+ void set_ssl(epee::net_utils::ssl_options_t) { }
+ bool is_connected(bool *ssl = NULL) { return true; }
+ uint64_t get_bytes_sent() const { return 1; }
+ uint64_t get_bytes_received() const { return 1; }
+
+ void set_test_data(const std::string& s) { data = s; }
+
+private:
+ std::string data;
+};
+
+class test_http_client final : public http::http_simple_client_template<dummy_client>
+{
+public:
+ bool on_header(const http::http_response_info& headers) override
+ {
+ ++headers_seen;
+ last_headers = headers;
+ return true;
+ }
+
+ http::http_response_info last_headers;
+ unsigned headers_seen = 0;
+};
+
+class capturing_http_handler final : public http::i_http_server_handler<epee::net_utils::connection_context_base>
+{
+public:
+ bool handle_http_request(
+ const http::http_request_info& query_info,
+ http::http_response_info& response,
+ epee::net_utils::connection_context_base&) override
+ {
+ requests.push_back(query_info);
+ response.m_response_code = 200;
+ response.m_response_comment = "OK";
+ response.m_mime_tipe = "text/plain";
+ return true;
+ }
+
+ std::vector<http::http_request_info> requests;
+};
+
+struct http_request_capture
+{
+ std::vector<bool> results;
+ std::vector<http::http_request_info> requests;
+ std::string sent;
+};
+
+http_request_capture feed_http_request(const std::vector<std::string>& chunks)
+{
+ capturing_http_handler handler;
+ test_http_endpoint endpoint;
+ epee::net_utils::connection_context_base context;
+ http::custum_handler_config<epee::net_utils::connection_context_base> config;
+ config.m_phandler = &handler;
+
+ http::http_custom_handler<epee::net_utils::connection_context_base> connection(&endpoint, config, context);
+ std::vector<bool> results;
+ for (const std::string& chunk : chunks)
+ results.push_back(connection.handle_recv(chunk.data(), chunk.size()));
+
+ return {results, handler.requests, endpoint.sent};
+}
+
+http_request_capture feed_http_request(const std::string& request)
+{
+ return feed_http_request(std::vector<std::string>{request});
+}
+
void rng(size_t len, uint8_t *ptr)
{
crypto::rand(len, ptr);
@@ -701,6 +810,149 @@ TEST(HTTP_Client_Auth, MD5_auth)
}
+TEST(HTTP, Parse_Header_Line)
+{
+ boost::string_view name;
+ boost::string_view value;
+
+ ASSERT_TRUE(http::detail::parse_header_line("Host: example.com\r", name, value));
+ EXPECT_EQ("Host", name);
+ EXPECT_EQ("example.com", value);
+
+ ASSERT_TRUE(http::detail::parse_header_line("Content-Type:\t application/json \t", name, value));
+ EXPECT_EQ("Content-Type", name);
+ EXPECT_EQ("application/json", value);
+
+ ASSERT_TRUE(http::detail::parse_header_line("Host:example.com", name, value));
+ EXPECT_EQ("Host", name);
+ EXPECT_EQ("example.com", value);
+
+ ASSERT_TRUE(http::detail::parse_header_line("X!#$%&'*+-.^_`|~: value", name, value));
+ EXPECT_EQ("X!#$%&'*+-.^_`|~", name);
+ EXPECT_EQ("value", value);
+
+ EXPECT_FALSE(http::detail::parse_header_line("", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line("Host : example.com", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line(" Content-Length: 1", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line("\tContent-Length: 1", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line("Bad Header", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line("Host example.com", name, value));
+ EXPECT_FALSE(http::detail::parse_header_line("GET / HTTP/1.1", name, value));
+}
+
+TEST(HTTP, Server_Parses_Content_Length_First_Header)
+{
+ const std::string body = "0123456789";
+ const auto capture = feed_http_request(
+ "POST /json_rpc HTTP/1.1\r\n"
+ "Content-Length: 10\r\n"
+ "Host: example.com\r\n"
+ "\r\n" + body
+ );
+
+ ASSERT_EQ(1u, capture.results.size());
+ ASSERT_TRUE(capture.results.front());
+ ASSERT_EQ(1u, capture.requests.size());
+ EXPECT_STREQ("10", capture.requests.front().m_header_info.m_content_length.c_str());
+ EXPECT_EQ(body, capture.requests.front().m_body);
+}
+
+TEST(HTTP, Server_Parses_First_Header_After_Split_Request_Line)
+{
+ const std::string body = "0123456789";
+ const auto capture = feed_http_request({
+ "POST /json_rpc HTTP/1.1\r\n",
+ "Content-Length: 10\r\n"
+ "Host: example.com\r\n"
+ "\r\n" + body
+ });
+
+ ASSERT_EQ(2u, capture.results.size());
+ EXPECT_TRUE(capture.results[0]);
+ EXPECT_TRUE(capture.results[1]);
+ ASSERT_EQ(1u, capture.requests.size());
+ EXPECT_STREQ("10", capture.requests.front().m_header_info.m_content_length.c_str());
+ EXPECT_EQ(body, capture.requests.front().m_body);
+}
+
+TEST(HTTP, Server_Rejects_Malformed_First_Header)
+{
+ const auto capture = feed_http_request(
+ "GET / HTTP/1.1\r\n"
+ "Bad Header Without Colon\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ );
+
+ ASSERT_EQ(1u, capture.results.size());
+ EXPECT_FALSE(capture.results.front());
+ EXPECT_TRUE(capture.requests.empty());
+}
+
+TEST(HTTP, Server_Rejects_Malformed_Later_Header)
+{
+ const auto capture = feed_http_request(
+ "GET / HTTP/1.1\r\n"
+ "Host: example.com\r\n"
+ "Bad Header Without Colon\r\n"
+ "\r\n"
+ );
+
+ ASSERT_EQ(1u, capture.results.size());
+ EXPECT_FALSE(capture.results.front());
+ EXPECT_TRUE(capture.requests.empty());
+}
+
+TEST(HTTP, Server_Keeps_Unknown_First_Header)
+{
+ const auto capture = feed_http_request(
+ "GET / HTTP/1.1\r\n"
+ "X-Test: abc\r\n"
+ "Host: example.com\r\n"
+ "\r\n"
+ );
+
+ ASSERT_EQ(1u, capture.results.size());
+ ASSERT_TRUE(capture.results.front());
+ ASSERT_EQ(1u, capture.requests.size());
+ ASSERT_EQ(1u, capture.requests.front().m_header_info.m_etc_fields.size());
+ EXPECT_STREQ("X-Test", capture.requests.front().m_header_info.m_etc_fields.front().first.c_str());
+ EXPECT_STREQ("abc", capture.requests.front().m_header_info.m_etc_fields.front().second.c_str());
+}
+
+TEST(HTTP, Client_Keeps_Unknown_Header)
+{
+ test_http_client client;
+ const bool result = client.test(
+ "HTTP/1.1 200 OK\r\n"
+ "X-Test: abc\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ std::chrono::milliseconds(1000)
+ );
+
+ ASSERT_TRUE(result);
+ EXPECT_EQ(1u, client.headers_seen);
+ ASSERT_EQ(1u, client.last_headers.m_header_info.m_etc_fields.size());
+ EXPECT_STREQ("X-Test", client.last_headers.m_header_info.m_etc_fields.front().first.c_str());
+ EXPECT_STREQ("abc", client.last_headers.m_header_info.m_etc_fields.front().second.c_str());
+}
+
+TEST(HTTP, Client_Rejects_Malformed_Response_Header)
+{
+ test_http_client client;
+ const bool result = client.test(
+ "HTTP/1.1 200 OK\r\n"
+ "Bad Header Without Colon\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",
+ std::chrono::milliseconds(1000)
+ );
+
+ EXPECT_FALSE(result);
+ EXPECT_EQ(0u, client.headers_seen);
+}
+
TEST(HTTP, Add_Field)
{
std::string str{"leading text"};