Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wallet: reroll fake outs selection on local tx_sanity_check failure #6289

Merged
merged 1 commit into from Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/cryptonote_core/tx_sanity_check.cpp
Expand Up @@ -28,7 +28,7 @@

#include <stdint.h>
#include <vector>
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "blockchain.h"
#include "tx_sanity_check.h"
Expand All @@ -39,7 +39,7 @@
namespace cryptonote
{

bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob)
bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available)
{
cryptonote::transaction tx;

Expand Down Expand Up @@ -70,14 +70,18 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob
n_indices += in_to_key.key_offsets.size();
}

return tx_sanity_check(rct_indices, n_indices, rct_outs_available);
}

bool tx_sanity_check(const std::set<uint64_t> &rct_indices, size_t n_indices, uint64_t rct_outs_available)
{
if (n_indices <= 10)
{
MDEBUG("n_indices is only " << n_indices << ", not checking");
return true;
}

uint64_t n_available = blockchain.get_num_mature_outputs(0);
if (n_available < 10000)
if (rct_outs_available < 10000)
return true;

if (rct_indices.size() < n_indices * 8 / 10)
Expand All @@ -88,9 +92,9 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob

std::vector<uint64_t> offsets(rct_indices.begin(), rct_indices.end());
uint64_t median = epee::misc_utils::median(offsets);
if (median < n_available * 6 / 10)
if (median < rct_outs_available * 6 / 10)
{
MERROR("median offset index is too low (median is " << median << " out of total " << n_available << "offsets). Transactions should contain a higher fraction of recent outputs.");
MERROR("median offset index is too low (median is " << median << " out of total " << rct_outs_available << "offsets). Transactions should contain a higher fraction of recent outputs.");
return false;
}

Expand Down
6 changes: 3 additions & 3 deletions src/cryptonote_core/tx_sanity_check.h
Expand Up @@ -26,11 +26,11 @@
// 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 <set>
#include "cryptonote_basic/blobdatatype.h"

namespace cryptonote
{
class Blockchain;

bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob);
bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available);
bool tx_sanity_check(const std::set<uint64_t> &rct_indices, size_t n_indices, uint64_t rct_outs_available);
}
2 changes: 1 addition & 1 deletion src/rpc/core_rpc_server.cpp
Expand Up @@ -1087,7 +1087,7 @@ namespace cryptonote
return true;
}

if (req.do_sanity_checks && !cryptonote::tx_sanity_check(m_core.get_blockchain_storage(), tx_blob))
if (req.do_sanity_checks && !cryptonote::tx_sanity_check(tx_blob, m_core.get_blockchain_storage().get_num_mature_outputs(0)))
{
res.status = "Failed";
res.reason = "Sanity check failed";
Expand Down
46 changes: 44 additions & 2 deletions src/wallet/wallet2.cpp
Expand Up @@ -44,6 +44,7 @@
using namespace epee;

#include "cryptonote_config.h"
#include "cryptonote_core/tx_sanity_check.h"
#include "wallet_rpc_helpers.h"
#include "wallet2.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
Expand Down Expand Up @@ -7733,7 +7734,49 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
}
}

std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs)
{
std::set<uint64_t> unique;
size_t total = 0;

for (const auto &it : outs)
{
for (const auto &out : it)
{
const uint64_t global_index = std::get<0>(out);
unique.insert(global_index);
}
total += it.size();
}

return std::make_pair(std::move(unique), total);
}

void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count)
{
std::vector<uint64_t> rct_offsets;
for (size_t attempts = 3; attempts > 0; --attempts)
{
get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets);

const auto unique = outs_unique(outs);
if (tx_sanity_check(unique.first, unique.second, rct_offsets.empty() ? 0 : rct_offsets.back()))
{
return;
}

std::vector<crypto::key_image> key_images;
key_images.reserve(selected_transfers.size());
std::for_each(selected_transfers.begin(), selected_transfers.end(), [this, &key_images](size_t index) {
key_images.push_back(m_transfers[index].m_key_image);
});
unset_ring(key_images);
}

THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed"));
}

void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
Expand All @@ -7755,7 +7798,6 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>

// if we have at least one rct out, get the distribution, or fall back to the previous system
uint64_t rct_start_height;
std::vector<uint64_t> rct_offsets;
bool has_rct = false;
uint64_t max_rct_index = 0;
for (size_t idx: selected_transfers)
Expand All @@ -7764,7 +7806,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
has_rct = true;
max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index);
}
const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets);
const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets));
if (has_rct_distribution)
{
// check we're clear enough of rct start, to avoid corner cases below
Expand Down
1 change: 1 addition & 0 deletions src/wallet/wallet2.h
Expand Up @@ -1435,6 +1435,7 @@ namespace tools
bool is_spent(const transfer_details &td, bool strict = true) const;
bool is_spent(size_t idx, bool strict = true) const;
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets);
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
Expand Down