Files
wownero/tests/unit_tests/request_manager.cpp
T
+3 a9145f9d38 FCMP++/Carrot mega commit [9/14/26]
Co-authored-by: jeffro256 <jeffro256@tutanota.com>
Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
Co-authored-by: Lee *!* Clagett <code@leeclagett.com>
Co-authored-by: SyntheticBird45 <someoneelse.is_on.github.rio7x@simplelogin.com>
Co-authored-by: jeetrex17 <jeetrex@tutamail.com>
Co-authored-by: UkoeHB <ukoehb@users.noreply.github.com>
Co-authored-by: MrCyjaneK
Co-authored-by: tobtoht
Co-authored-by: selsta
Co-authored-by: Thomas <thomas.giudici@proton.me>
Co-authored-by: navidR
Co-authored-by: Boog900 <boog900@tutanota.com>
2026-09-17 18:33:24 -07:00

256 lines
10 KiB
C++

// Copyright (c) 2014, 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 "gtest/gtest.h"
#include "cryptonote_protocol/request_manager.h"
#include <boost/uuid/uuid.hpp>
#include <unistd.h>
static boost::uuids::uuid uuid_from_char(char a)
{
boost::uuids::uuid u;
memset(&u, a, 16);
return u;
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
TEST(request_manager, basic_usage)
{
const uint8_t MAX_IN_FLIGHT = 3;
request_manager req_manager(MAX_IN_FLIGHT);
const boost::uuids::uuid peer{};
// 1. Fill up the request manager
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
{
// Success case
ASSERT_EQ(req_manager.enqueue_requests({crypto::hash{i}}, peer).tx_hashes.size(), 1);
// Can't re-add
ASSERT_EQ(req_manager.enqueue_requests({crypto::hash{i}}, peer).tx_hashes.size(), 0);
}
// 2. Queue is full and can't let any others fly. Queue this one
ASSERT_EQ(req_manager.enqueue_requests({crypto::hash{MAX_IN_FLIGHT}}, peer).tx_hashes.size(), 0);
// 3. Remove 1 from the queue to make room for one queued above
ASSERT_TRUE(req_manager.remove_request(crypto::hash{0}));
ASSERT_FALSE(req_manager.remove_request(crypto::hash{0}));
// 4. Make sure the one queued above enters flight
const auto tx_reqs = req_manager.fly_available_requests(peer).tx_hashes;
ASSERT_EQ(tx_reqs.size(), 1);
ASSERT_EQ(tx_reqs.front(), crypto::hash{MAX_IN_FLIGHT});
// 5. Remove
ASSERT_TRUE(req_manager.remove_request(crypto::hash{1}));
ASSERT_TRUE(req_manager.remove_request(crypto::hash{2}));
ASSERT_TRUE(req_manager.remove_request(crypto::hash{MAX_IN_FLIGHT}));
req_manager.remove_peer(peer);
}
//----------------------------------------------------------------------------------------------------------------------
TEST(request_manager, multiple_peers)
{
const uint8_t MAX_IN_FLIGHT = 4;
const int64_t TIMEOUT_MS = 10;
request_manager req_manager(MAX_IN_FLIGHT, TIMEOUT_MS);
// 1. Add 1 for each peer
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
{
const crypto::hash hash{i};
const boost::uuids::uuid peer = uuid_from_char(i);
// Success case
ASSERT_EQ(req_manager.enqueue_requests({hash}, peer).tx_hashes.size(), 1);
// Can't re-add
ASSERT_EQ(req_manager.enqueue_requests({hash}, peer).tx_hashes.size(), 0);
}
// 2. No stale requests and none available
{
const auto drop_peers = req_manager.remove_stale_requests();
ASSERT_TRUE(drop_peers.empty());
for (uint8_t i = 0; i < MAX_IN_FLIGHT; ++i)
{
const boost::uuids::uuid peer = uuid_from_char(i);
const auto tx_hashes = req_manager.fly_available_requests(peer).tx_hashes;
ASSERT_TRUE(tx_hashes.empty());
}
}
// 3. Sleep to let requests timeout
MINFO("Sleeping for 20ms to let requests timeout");
usleep(TIMEOUT_MS * 1000 * 2);
// 4. Re-add the same ones for each peer, but in reverse. The hashes
// should get queued for each peer
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
{
const crypto::hash hash{i};
const boost::uuids::uuid peer = uuid_from_char(MAX_IN_FLIGHT - i - 1);
ASSERT_EQ(req_manager.enqueue_requests({hash}, peer).tx_hashes.size(), 0);
}
// 5. Remove stale requests
const auto drop_peers = req_manager.remove_stale_requests();
ASSERT_TRUE(drop_peers.empty());
// 6. Available reqs should be all the tx hashes from step 4
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
{
const crypto::hash hash{i};
const boost::uuids::uuid peer = uuid_from_char(MAX_IN_FLIGHT - i - 1);
const auto tx_hashes = req_manager.fly_available_requests(peer).tx_hashes;
ASSERT_EQ(tx_hashes.size(), 1);
ASSERT_EQ(tx_hashes.front(), hash);
}
// 7. Remove peers
for (uint8_t i = 0; i < MAX_IN_FLIGHT; ++i)
{
const boost::uuids::uuid peer = uuid_from_char(i);
req_manager.remove_peer(peer);
}
}
//----------------------------------------------------------------------------------------------------------------------
TEST(request_manager, drop_peers)
{
const uint8_t MAX_IN_FLIGHT = P2P_MIN_SAMPLE_SIZE_FOR_DROPPING;
const int64_t TIMEOUT_MS = 10;
request_manager req_manager(MAX_IN_FLIGHT, TIMEOUT_MS);
const boost::uuids::uuid peer{};
// 1. Add many requests
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
ASSERT_EQ(req_manager.enqueue_requests({crypto::hash{i}}, peer).tx_hashes.size(), 1);
// 2. Sleep to let requests timeout
MINFO("Sleeping for 20ms to let requests timeout");
usleep(TIMEOUT_MS * 1000 * 2);
// 3. Remove stale requests
const auto drop_peers = req_manager.remove_stale_requests();
ASSERT_EQ(drop_peers.size(), 1);
ASSERT_EQ(drop_peers.count(peer), 1);
// 4. Remove peers
req_manager.remove_peer(peer);
}
//----------------------------------------------------------------------------------------------------------------------
TEST(request_manager, dont_rm_processing_txs)
{
const uint8_t MAX_IN_FLIGHT = P2P_MIN_SAMPLE_SIZE_FOR_DROPPING;
const int64_t TIMEOUT_MS = 10;
request_manager req_manager(MAX_IN_FLIGHT, TIMEOUT_MS);
const boost::uuids::uuid peer{};
// 1. Add many requests
std::vector<crypto::hash> init_hashes;
init_hashes.reserve(MAX_IN_FLIGHT);
for (char i = 0; i < MAX_IN_FLIGHT; ++i)
init_hashes.emplace_back(crypto::hash{i});
const auto tx_req = req_manager.enqueue_requests(init_hashes, peer);
ASSERT_EQ(tx_req.tx_hashes, init_hashes);
ASSERT_EQ(tx_req.nonce, 1);
// 2. Indicate that we're processing all of the tx requests
// Note: the last one is explicitly not included in the processing_hashes, which technically is supposed to mean
// the peer didn't have the tx and it can be removed from the request queue.
const std::vector<crypto::hash> processing_hashes(init_hashes.begin(), init_hashes.end() - 1);
ASSERT_TRUE(req_manager.processing_txs(peer, tx_req.nonce, processing_hashes));
ASSERT_FALSE(req_manager.remove_request(init_hashes.back()));
// 3. Sleep to let requests timeout
MINFO("Sleeping for 20ms to let requests timeout");
usleep(TIMEOUT_MS * 1000 * 2);
// 4. There should be no stale requests.
const auto drop_peers = req_manager.remove_stale_requests();
ASSERT_EQ(drop_peers.size(), 0);
// 5. Remove peers
req_manager.remove_peer(peer);
}
//----------------------------------------------------------------------------------------------------------------------
TEST(request_manager, dont_rm_processing_txs_enqueue_overage)
{
const uint8_t MAX_IN_FLIGHT = 2;
const int64_t TIMEOUT_MS = 10;
request_manager req_manager(MAX_IN_FLIGHT, TIMEOUT_MS);
const boost::uuids::uuid peer{};
// 1. Enqueue 1 more request than max allowed in flight
const uint8_t TOTAL_TXS = MAX_IN_FLIGHT + 1;
std::vector<crypto::hash> init_hashes;
init_hashes.reserve(TOTAL_TXS);
for (char i = 0; i < TOTAL_TXS; ++i)
init_hashes.emplace_back(crypto::hash{i});
const auto tx_req = req_manager.enqueue_requests(init_hashes, peer);
ASSERT_EQ(tx_req.tx_hashes, std::vector<crypto::hash>(init_hashes.begin(), init_hashes.begin() + MAX_IN_FLIGHT));
ASSERT_EQ(tx_req.nonce, 1);
// 2. Remove 1 req to make room for the last one
ASSERT_TRUE(req_manager.remove_request(init_hashes.front()));
// 3. Now fly the last one
const auto tx_req2 = req_manager.fly_available_requests(peer);
ASSERT_EQ(tx_req2.tx_hashes, std::vector<crypto::hash>{init_hashes.back()});
ASSERT_EQ(tx_req2.nonce, 2);
// 4. Indicate that we're processing Step 3's tx
ASSERT_FALSE(req_manager.processing_txs(peer, tx_req2.nonce, tx_req2.tx_hashes));
// 5. Sleep to let requests timeout
MINFO("Sleeping for 20ms to let requests timeout");
usleep(TIMEOUT_MS * 1000 * 2);
// 6. The processing tx from step 4 should still be present, since it's processing and shouldn't have been rm'd.
// init_hashes.at(1) should have timed out and rm'd. Since this is only the first stale tx req rm'd, the peer
// is not expected to drop.
static_assert(P2P_MIN_SAMPLE_SIZE_FOR_DROPPING > 1);
const auto drop_peers = req_manager.remove_stale_requests();
ASSERT_EQ(drop_peers.size(), 0);
ASSERT_FALSE(req_manager.remove_request(init_hashes.at(1)));
ASSERT_TRUE(req_manager.remove_request(init_hashes.back()));
// 7. Remove peer
req_manager.remove_peer(peer);
}
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------