From ccb996afc6cf650f55ef406eb378e8960a6a4a2d Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 12 Apr 2019 20:20:20 +0000 Subject: [PATCH] rpc: new sanity check on relayed transactions This will weed out some transactions with silly rings --- src/cryptonote_core/CMakeLists.txt | 2 + src/cryptonote_core/tx_sanity_check.cpp | 100 ++++++++++++++++++++++++ src/cryptonote_core/tx_sanity_check.h | 36 +++++++++ src/rpc/core_rpc_server.cpp | 9 +++ src/rpc/core_rpc_server_commands_defs.h | 4 + src/wallet/wallet2.cpp | 1 + utils/python-rpc/framework/daemon.py | 3 +- 7 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/cryptonote_core/tx_sanity_check.cpp create mode 100644 src/cryptonote_core/tx_sanity_check.h diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt index fb96de226..2cbe89b01 100644 --- a/src/cryptonote_core/CMakeLists.txt +++ b/src/cryptonote_core/CMakeLists.txt @@ -30,6 +30,7 @@ set(cryptonote_core_sources blockchain.cpp cryptonote_core.cpp tx_pool.cpp + tx_sanity_check.cpp cryptonote_tx_utils.cpp) set(cryptonote_core_headers) @@ -39,6 +40,7 @@ set(cryptonote_core_private_headers blockchain.h cryptonote_core.h tx_pool.h + tx_sanity_check.h cryptonote_tx_utils.h) monero_private_headers(cryptonote_core diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp new file mode 100644 index 000000000..d3b225f1c --- /dev/null +++ b/src/cryptonote_core/tx_sanity_check.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "blockchain.h" +#include "tx_sanity_check.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "txsanity" + +namespace cryptonote +{ + +bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob) +{ + cryptonote::transaction tx; + + if (!cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)) + { + MERROR("Failed to parse transaction"); + return false; + } + + if (cryptonote::is_coinbase(tx)) + { + MERROR("Transaction is coinbase"); + return false; + } + std::set rct_indices; + size_t n_indices = 0; + + for (const auto &txin : tx.vin) + { + if (txin.type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key &in_to_key = boost::get(txin); + if (in_to_key.amount != 0) + continue; + const std::vector absolute = cryptonote::relative_output_offsets_to_absolute(in_to_key.key_offsets); + for (uint64_t offset: absolute) + rct_indices.insert(offset); + n_indices += in_to_key.key_offsets.size(); + } + + if (n_indices <= 10) + { + MERROR("n_indices is only " << n_indices); + return true; + } + + uint64_t n_available = blockchain.get_num_mature_outputs(0); + if (n_available < 10000) + return true; + + if (rct_indices.size() < n_indices * 9 / 10) + { + MERROR("unique indices is only " << rct_indices.size() << "/" << n_indices); + return false; + } + + std::vector offsets(rct_indices.begin(), rct_indices.end()); + uint64_t median = epee::misc_utils::median(offsets); + if (median < n_available * 9 / 10) + { + MERROR("median is " << median << "/" << n_available); + return false; + } + + return true; +} + +} diff --git a/src/cryptonote_core/tx_sanity_check.h b/src/cryptonote_core/tx_sanity_check.h new file mode 100644 index 000000000..c12d1b0b1 --- /dev/null +++ b/src/cryptonote_core/tx_sanity_check.h @@ -0,0 +1,36 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_basic/blobdatatype.h" + +namespace cryptonote +{ + class Blockchain; + + bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob); +} diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 71bfcc950..97c5015ca 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -41,6 +41,7 @@ using namespace epee; #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_core/tx_sanity_check.h" #include "misc_language.h" #include "net/parse.h" #include "storages/http_abstract_invoke.h" @@ -844,6 +845,14 @@ namespace cryptonote return true; } + if (req.do_sanity_checks && !cryptonote::tx_sanity_check(m_core.get_blockchain_storage(), tx_blob)) + { + res.status = "Failed"; + res.reason = "Sanity check failed"; + res.sanity_check_failed = true; + return true; + } + cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index d2aba8d67..a1e2fdf8d 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -577,10 +577,12 @@ namespace cryptonote { std::string tx_as_hex; bool do_not_relay; + bool do_sanity_checks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_as_hex) KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(do_sanity_checks, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init request; @@ -599,6 +601,7 @@ namespace cryptonote bool overspend; bool fee_too_low; bool not_rct; + bool sanity_check_failed; bool untrusted; BEGIN_KV_SERIALIZE_MAP() @@ -613,6 +616,7 @@ namespace cryptonote KV_SERIALIZE(overspend) KV_SERIALIZE(fee_too_low) KV_SERIALIZE(not_rct) + KV_SERIALIZE(sanity_check_failed) KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index dfcc61426..b74742c6a 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5988,6 +5988,7 @@ void wallet2::commit_tx(pending_tx& ptx) COMMAND_RPC_SEND_RAW_TX::request req; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); req.do_not_relay = false; + req.do_sanity_checks = true; COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout); diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index f4d5e90f0..04fc5b5cf 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -50,10 +50,11 @@ class Daemon(object): } return self.rpc.send_json_rpc_request(getblocktemplate) - def send_raw_transaction(self, tx_as_hex, do_not_relay = False): + def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True): send_raw_transaction = { 'tx_as_hex': tx_as_hex, 'do_not_relay': do_not_relay, + 'do_sanity_checks': do_sanity_checks, } return self.rpc.send_request("/send_raw_transaction", send_raw_transaction)