Changeset View
Changeset View
Standalone View
Standalone View
src/invrequest.h
// Copyright (c) 2020 The Bitcoin Core developers | // Copyright (c) 2020 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#ifndef BITCOIN_INVREQUEST_H | #ifndef BITCOIN_INVREQUEST_H | ||||
#define BITCOIN_INVREQUEST_H | #define BITCOIN_INVREQUEST_H | ||||
#include <crypto/siphash.h> | #include <crypto/siphash.h> | ||||
#include <net.h> // For NodeId | #include <net.h> // For NodeId | ||||
#include <primitives/transaction.h> | |||||
#include <random.h> | #include <random.h> | ||||
#include <uint256.h> | #include <uint256.h> | ||||
#include <boost/multi_index/ordered_index.hpp> | #include <boost/multi_index/ordered_index.hpp> | ||||
#include <boost/multi_index_container.hpp> | #include <boost/multi_index_container.hpp> | ||||
#include <chrono> | #include <chrono> | ||||
#include <cstdint> | #include <cstdint> | ||||
#include <unordered_map> | #include <unordered_map> | ||||
#include <utility> | |||||
#include <vector> | #include <vector> | ||||
/** | /** | ||||
* Data structure to keep track of, and schedule, inventory downloads from | * Data structure to keep track of, and schedule, inventory downloads from | ||||
* peers. | * peers. | ||||
* | * | ||||
* === Specification === | * === Specification === | ||||
* | * | ||||
▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | |||||
* Complexity: | * Complexity: | ||||
* - Memory usage is proportional to the total number of tracked announcements | * - Memory usage is proportional to the total number of tracked announcements | ||||
* (Size()) plus the number of peers with a nonzero number of tracked | * (Size()) plus the number of peers with a nonzero number of tracked | ||||
* announcements. | * announcements. | ||||
* - CPU usage is generally logarithmic in the total number of tracked | * - CPU usage is generally logarithmic in the total number of tracked | ||||
* announcements, plus the number of announcements affected by an operation | * announcements, plus the number of announcements affected by an operation | ||||
* (amortized O(1) per announcement). | * (amortized O(1) per announcement). | ||||
*/ | */ | ||||
class InvRequestTracker { | template <class InvId> class InvRequestTracker { | ||||
/* | |||||
* Only uint256-based InvId types are supported for now. | |||||
* FIXME: use a template constraint when C++20 is available. | |||||
*/ | |||||
static_assert(std::is_base_of<uint256, InvId>::value, | |||||
"InvRequestTracker inv id type should be uint256 or derived"); | |||||
/** | /** | ||||
* The various states a (invid, peer) pair can be in. | * The various states a (invid, peer) pair can be in. | ||||
* | * | ||||
* Note that CANDIDATE is split up into 3 substates (DELAYED, BEST, READY), | * Note that CANDIDATE is split up into 3 substates (DELAYED, BEST, READY), | ||||
* allowing more efficient implementation. Also note that the sorting order | * allowing more efficient implementation. Also note that the sorting order | ||||
* of ByInvIdView relies on the specific order of values in this enum. | * of ByInvIdView relies on the specific order of values in this enum. | ||||
* | * | ||||
* Expected behaviour is: | * Expected behaviour is: | ||||
Show All 34 Lines | template <class InvId> class InvRequestTracker { | ||||
using SequenceNumber = uint64_t; | using SequenceNumber = uint64_t; | ||||
/** | /** | ||||
* An announcement. This is the data we track for each invid that is | * An announcement. This is the data we track for each invid that is | ||||
* announced to us by each peer. | * announced to us by each peer. | ||||
*/ | */ | ||||
struct Announcement { | struct Announcement { | ||||
/** InvId that was announced. */ | /** InvId that was announced. */ | ||||
const TxId m_invid; | const InvId m_invid; | ||||
/** | /** | ||||
* For CANDIDATE_{DELAYED,BEST,READY} the reqtime; for REQUESTED the | * For CANDIDATE_{DELAYED,BEST,READY} the reqtime; for REQUESTED the | ||||
* expiry. | * expiry. | ||||
*/ | */ | ||||
std::chrono::microseconds m_time; | std::chrono::microseconds m_time; | ||||
/** What peer the request was from. */ | /** What peer the request was from. */ | ||||
const NodeId m_peer; | const NodeId m_peer; | ||||
/** What sequence number this announcement has. */ | /** What sequence number this announcement has. */ | ||||
Show All 38 Lines | struct Announcement { | ||||
return GetState() == State::CANDIDATE_READY || | return GetState() == State::CANDIDATE_READY || | ||||
GetState() == State::CANDIDATE_BEST; | GetState() == State::CANDIDATE_BEST; | ||||
} | } | ||||
/** | /** | ||||
* Construct a new announcement from scratch, initially in | * Construct a new announcement from scratch, initially in | ||||
* CANDIDATE_DELAYED state. | * CANDIDATE_DELAYED state. | ||||
*/ | */ | ||||
Announcement(const TxId &invid, NodeId peer, bool preferred, | Announcement(const InvId &invid, NodeId peer, bool preferred, | ||||
std::chrono::microseconds reqtime, SequenceNumber sequence) | std::chrono::microseconds reqtime, SequenceNumber sequence) | ||||
: m_invid(invid), m_time(reqtime), m_peer(peer), | : m_invid(invid), m_time(reqtime), m_peer(peer), | ||||
m_sequence(sequence), m_preferred(preferred), | m_sequence(sequence), m_preferred(preferred), | ||||
m_state(static_cast<uint8_t>(State::CANDIDATE_DELAYED)) {} | m_state(static_cast<uint8_t>(State::CANDIDATE_DELAYED)) {} | ||||
}; | }; | ||||
//! Type alias for priorities. | //! Type alias for priorities. | ||||
using Priority = uint64_t; | using Priority = uint64_t; | ||||
/** | /** | ||||
* A functor with embedded salt that computes priority of an announcement. | * A functor with embedded salt that computes priority of an announcement. | ||||
* | * | ||||
* Higher priorities are selected first. | * Higher priorities are selected first. | ||||
*/ | */ | ||||
class PriorityComputer { | class PriorityComputer { | ||||
const uint64_t m_k0, m_k1; | const uint64_t m_k0, m_k1; | ||||
public: | public: | ||||
explicit PriorityComputer(bool deterministic) | explicit PriorityComputer(bool deterministic) | ||||
: m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)}, | : m_k0{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)}, | ||||
m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {} | m_k1{deterministic ? 0 : GetRand(0xFFFFFFFFFFFFFFFF)} {} | ||||
Priority operator()(const TxId &invid, NodeId peer, | Priority operator()(const InvId &invid, NodeId peer, | ||||
bool preferred) const { | bool preferred) const { | ||||
uint64_t low_bits = CSipHasher(m_k0, m_k1) | uint64_t low_bits = CSipHasher(m_k0, m_k1) | ||||
.Write(invid.begin(), invid.size()) | .Write(invid.begin(), invid.size()) | ||||
.Write(peer) | .Write(peer) | ||||
.Finalize() >> | .Finalize() >> | ||||
1; | 1; | ||||
return low_bits | uint64_t{preferred} << 63; | return low_bits | uint64_t{preferred} << 63; | ||||
} | } | ||||
Show All 16 Lines | template <class InvId> class InvRequestTracker { | ||||
// | // | ||||
// Uses: | // Uses: | ||||
// * Looking up existing announcements by peer/invid, by checking both | // * Looking up existing announcements by peer/invid, by checking both | ||||
// (peer, | // (peer, | ||||
// false, invid) and (peer, true, invid). | // false, invid) and (peer, true, invid). | ||||
// * Finding all CANDIDATE_BEST announcements for a given peer in | // * Finding all CANDIDATE_BEST announcements for a given peer in | ||||
// GetRequestable. | // GetRequestable. | ||||
struct ByPeer {}; | struct ByPeer {}; | ||||
using ByPeerView = std::tuple<NodeId, bool, const TxId &>; | using ByPeerView = std::tuple<NodeId, bool, const InvId &>; | ||||
struct ByPeerViewExtractor { | struct ByPeerViewExtractor { | ||||
using result_type = ByPeerView; | using result_type = ByPeerView; | ||||
result_type operator()(const Announcement &ann) const { | result_type operator()(const Announcement &ann) const { | ||||
return ByPeerView{ann.m_peer, | return ByPeerView{ann.m_peer, | ||||
ann.GetState() == State::CANDIDATE_BEST, | ann.GetState() == State::CANDIDATE_BEST, | ||||
ann.m_invid}; | ann.m_invid}; | ||||
} | } | ||||
}; | }; | ||||
// The ByInvId index is sorted by (invid, state, priority). | // The ByInvId index is sorted by (invid, state, priority). | ||||
// | // | ||||
// Note: priority == 0 whenever state != CANDIDATE_READY. | // Note: priority == 0 whenever state != CANDIDATE_READY. | ||||
// | // | ||||
// Uses: | // Uses: | ||||
// * Deleting all announcements with a given invid in ForgetInvId. | // * Deleting all announcements with a given invid in ForgetInvId. | ||||
// * Finding the best CANDIDATE_READY to convert to CANDIDATE_BEST, when no | // * Finding the best CANDIDATE_READY to convert to CANDIDATE_BEST, when no | ||||
// other CANDIDATE_READY or REQUESTED announcement exists for that invid. | // other CANDIDATE_READY or REQUESTED announcement exists for that invid. | ||||
// * Determining when no more non-COMPLETED announcements for a given invid | // * Determining when no more non-COMPLETED announcements for a given invid | ||||
// exist, so the COMPLETED ones can be deleted. | // exist, so the COMPLETED ones can be deleted. | ||||
struct ByInvId {}; | struct ByInvId {}; | ||||
using ByInvIdView = std::tuple<const TxId &, State, Priority>; | using ByInvIdView = std::tuple<const InvId &, State, Priority>; | ||||
class ByInvIdViewExtractor { | class ByInvIdViewExtractor { | ||||
const PriorityComputer &m_computer; | const PriorityComputer &m_computer; | ||||
public: | public: | ||||
ByInvIdViewExtractor(const PriorityComputer &computer) | ByInvIdViewExtractor(const PriorityComputer &computer) | ||||
: m_computer(computer) {} | : m_computer(computer) {} | ||||
using result_type = ByInvIdView; | using result_type = ByInvIdView; | ||||
result_type operator()(const Announcement &ann) const { | result_type operator()(const Announcement &ann) const { | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | using Index = boost::multi_index_container< | ||||
ByPeerViewExtractor>, | ByPeerViewExtractor>, | ||||
boost::multi_index::ordered_non_unique< | boost::multi_index::ordered_non_unique< | ||||
boost::multi_index::tag<ByInvId>, ByInvIdViewExtractor>, | boost::multi_index::tag<ByInvId>, ByInvIdViewExtractor>, | ||||
boost::multi_index::ordered_non_unique< | boost::multi_index::ordered_non_unique< | ||||
boost::multi_index::tag<ByTime>, ByTimeViewExtractor>>>; | boost::multi_index::tag<ByTime>, ByTimeViewExtractor>>>; | ||||
/** Helper type to simplify syntax of iterator types. */ | /** Helper type to simplify syntax of iterator types. */ | ||||
template <typename Tag> | template <typename Tag> | ||||
using Iter = typename Index::index<Tag>::type::iterator; | using Iter = typename Index::template index<Tag>::type::iterator; | ||||
/** Per-peer statistics object. */ | /** Per-peer statistics object. */ | ||||
struct PeerInfo { | struct PeerInfo { | ||||
//! Total number of announcements for this peer. | //! Total number of announcements for this peer. | ||||
size_t m_total = 0; | size_t m_total = 0; | ||||
//! Number of COMPLETED announcements for this peer. | //! Number of COMPLETED announcements for this peer. | ||||
size_t m_completed = 0; | size_t m_completed = 0; | ||||
//! Number of REQUESTED announcements for this peer. | //! Number of REQUESTED announcements for this peer. | ||||
▲ Show 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | RecomputePeerInfo(const Index &index) { | ||||
++info.m_total; | ++info.m_total; | ||||
info.m_requested += (ann.GetState() == State::REQUESTED); | info.m_requested += (ann.GetState() == State::REQUESTED); | ||||
info.m_completed += (ann.GetState() == State::COMPLETED); | info.m_completed += (ann.GetState() == State::COMPLETED); | ||||
} | } | ||||
return ret; | return ret; | ||||
} | } | ||||
/** Compute the InvIdInfo map. Only used for sanity checking. */ | /** Compute the InvIdInfo map. Only used for sanity checking. */ | ||||
static std::map<TxId, InvIdInfo> | static std::map<InvId, InvIdInfo> | ||||
ComputeInvIdInfo(const Index &index, const PriorityComputer &computer) { | ComputeInvIdInfo(const Index &index, const PriorityComputer &computer) { | ||||
std::map<TxId, InvIdInfo> ret; | std::map<InvId, InvIdInfo> ret; | ||||
for (const Announcement &ann : index) { | for (const Announcement &ann : index) { | ||||
InvIdInfo &info = ret[ann.m_invid]; | InvIdInfo &info = ret[ann.m_invid]; | ||||
// Classify how many announcements of each state we have for this | // Classify how many announcements of each state we have for this | ||||
// invid. | // invid. | ||||
info.m_candidate_delayed += | info.m_candidate_delayed += | ||||
(ann.GetState() == State::CANDIDATE_DELAYED); | (ann.GetState() == State::CANDIDATE_DELAYED); | ||||
info.m_candidate_ready += | info.m_candidate_ready += | ||||
(ann.GetState() == State::CANDIDATE_READY); | (ann.GetState() == State::CANDIDATE_READY); | ||||
Show All 18 Lines | template <class InvId> class InvRequestTracker { | ||||
//! Wrapper around Index::...::erase that keeps m_peerinfo up to date. | //! Wrapper around Index::...::erase that keeps m_peerinfo up to date. | ||||
template <typename Tag> Iter<Tag> Erase(Iter<Tag> it) { | template <typename Tag> Iter<Tag> Erase(Iter<Tag> it) { | ||||
auto peerit = m_peerinfo.find(it->m_peer); | auto peerit = m_peerinfo.find(it->m_peer); | ||||
peerit->second.m_completed -= it->GetState() == State::COMPLETED; | peerit->second.m_completed -= it->GetState() == State::COMPLETED; | ||||
peerit->second.m_requested -= it->GetState() == State::REQUESTED; | peerit->second.m_requested -= it->GetState() == State::REQUESTED; | ||||
if (--peerit->second.m_total == 0) { | if (--peerit->second.m_total == 0) { | ||||
m_peerinfo.erase(peerit); | m_peerinfo.erase(peerit); | ||||
} | } | ||||
return m_index.get<Tag>().erase(it); | return m_index.template get<Tag>().erase(it); | ||||
deadalnix: What is this?
I'm not saying it's wrong, but if you could point me to an example of what that… | |||||
} | } | ||||
//! Wrapper around Index::...::modify that keeps m_peerinfo up to date. | //! Wrapper around Index::...::modify that keeps m_peerinfo up to date. | ||||
template <typename Tag, typename Modifier> | template <typename Tag, typename Modifier> | ||||
void Modify(Iter<Tag> it, Modifier modifier) { | void Modify(Iter<Tag> it, Modifier modifier) { | ||||
auto peerit = m_peerinfo.find(it->m_peer); | auto peerit = m_peerinfo.find(it->m_peer); | ||||
peerit->second.m_completed -= it->GetState() == State::COMPLETED; | peerit->second.m_completed -= it->GetState() == State::COMPLETED; | ||||
peerit->second.m_requested -= it->GetState() == State::REQUESTED; | peerit->second.m_requested -= it->GetState() == State::REQUESTED; | ||||
m_index.get<Tag>().modify(it, std::move(modifier)); | m_index.template get<Tag>().modify(it, std::move(modifier)); | ||||
peerit->second.m_completed += it->GetState() == State::COMPLETED; | peerit->second.m_completed += it->GetState() == State::COMPLETED; | ||||
peerit->second.m_requested += it->GetState() == State::REQUESTED; | peerit->second.m_requested += it->GetState() == State::REQUESTED; | ||||
} | } | ||||
//! Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY. If this | //! Convert a CANDIDATE_DELAYED announcement into a CANDIDATE_READY. If this | ||||
//! makes it the new best CANDIDATE_READY (and no REQUESTED exists) and | //! makes it the new best CANDIDATE_READY (and no REQUESTED exists) and | ||||
//! better than the CANDIDATE_BEST (if any), it becomes the new | //! better than the CANDIDATE_BEST (if any), it becomes the new | ||||
//! CANDIDATE_BEST. | //! CANDIDATE_BEST. | ||||
void PromoteCandidateReady(Iter<ByInvId> it) { | void PromoteCandidateReady(Iter<ByInvId> it) { | ||||
assert(it != m_index.get<ByInvId>().end()); | assert(it != m_index.template get<ByInvId>().end()); | ||||
assert(it->GetState() == State::CANDIDATE_DELAYED); | assert(it->GetState() == State::CANDIDATE_DELAYED); | ||||
// Convert CANDIDATE_DELAYED to CANDIDATE_READY first. | // Convert CANDIDATE_DELAYED to CANDIDATE_READY first. | ||||
Modify<ByInvId>(it, [](Announcement &ann) { | Modify<ByInvId>(it, [](Announcement &ann) { | ||||
ann.SetState(State::CANDIDATE_READY); | ann.SetState(State::CANDIDATE_READY); | ||||
}); | }); | ||||
// The following code relies on the fact that the ByInvId is sorted by | // The following code relies on the fact that the ByInvId is sorted by | ||||
// invid, and then by state (first _DELAYED, then _READY, then | // invid, and then by state (first _DELAYED, then _READY, then | ||||
// _BEST/REQUESTED). Within the _READY announcements, the best one | // _BEST/REQUESTED). Within the _READY announcements, the best one | ||||
// (highest priority) comes last. Thus, if an existing _BEST exists for | // (highest priority) comes last. Thus, if an existing _BEST exists for | ||||
// the same invid that this announcement may be preferred over, it must | // the same invid that this announcement may be preferred over, it must | ||||
// immediately follow the newly created _READY. | // immediately follow the newly created _READY. | ||||
auto it_next = std::next(it); | auto it_next = std::next(it); | ||||
if (it_next == m_index.get<ByInvId>().end() || | if (it_next == m_index.template get<ByInvId>().end() || | ||||
it_next->m_invid != it->m_invid || | it_next->m_invid != it->m_invid || | ||||
it_next->GetState() == State::COMPLETED) { | it_next->GetState() == State::COMPLETED) { | ||||
// This is the new best CANDIDATE_READY, and there is no | // This is the new best CANDIDATE_READY, and there is no | ||||
// IsSelected() announcement for this invid already. | // IsSelected() announcement for this invid already. | ||||
Modify<ByInvId>(it, [](Announcement &ann) { | Modify<ByInvId>(it, [](Announcement &ann) { | ||||
ann.SetState(State::CANDIDATE_BEST); | ann.SetState(State::CANDIDATE_BEST); | ||||
}); | }); | ||||
} else if (it_next->GetState() == State::CANDIDATE_BEST) { | } else if (it_next->GetState() == State::CANDIDATE_BEST) { | ||||
Show All 13 Lines | template <class InvId> class InvRequestTracker { | ||||
} | } | ||||
//! Change the state of an announcement to something non-IsSelected(). If it | //! Change the state of an announcement to something non-IsSelected(). If it | ||||
//! was IsSelected(), the next best announcement will be marked | //! was IsSelected(), the next best announcement will be marked | ||||
//! CANDIDATE_BEST. | //! CANDIDATE_BEST. | ||||
void ChangeAndReselect(Iter<ByInvId> it, State new_state) { | void ChangeAndReselect(Iter<ByInvId> it, State new_state) { | ||||
assert(new_state == State::COMPLETED || | assert(new_state == State::COMPLETED || | ||||
new_state == State::CANDIDATE_DELAYED); | new_state == State::CANDIDATE_DELAYED); | ||||
assert(it != m_index.get<ByInvId>().end()); | assert(it != m_index.template get<ByInvId>().end()); | ||||
if (it->IsSelected() && it != m_index.get<ByInvId>().begin()) { | if (it->IsSelected() && it != m_index.template get<ByInvId>().begin()) { | ||||
auto it_prev = std::prev(it); | auto it_prev = std::prev(it); | ||||
// The next best CANDIDATE_READY, if any, immediately precedes the | // The next best CANDIDATE_READY, if any, immediately precedes the | ||||
// REQUESTED or CANDIDATE_BEST announcement in the ByInvId index. | // REQUESTED or CANDIDATE_BEST announcement in the ByInvId index. | ||||
if (it_prev->m_invid == it->m_invid && | if (it_prev->m_invid == it->m_invid && | ||||
it_prev->GetState() == State::CANDIDATE_READY) { | it_prev->GetState() == State::CANDIDATE_READY) { | ||||
// If one such CANDIDATE_READY exists (for this invid), convert | // If one such CANDIDATE_READY exists (for this invid), convert | ||||
// it to CANDIDATE_BEST. | // it to CANDIDATE_BEST. | ||||
Modify<ByInvId>(it_prev, [](Announcement &ann) { | Modify<ByInvId>(it_prev, [](Announcement &ann) { | ||||
ann.SetState(State::CANDIDATE_BEST); | ann.SetState(State::CANDIDATE_BEST); | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
Modify<ByInvId>( | Modify<ByInvId>( | ||||
it, [new_state](Announcement &ann) { ann.SetState(new_state); }); | it, [new_state](Announcement &ann) { ann.SetState(new_state); }); | ||||
} | } | ||||
//! Check if 'it' is the only announcement for a given invid that isn't | //! Check if 'it' is the only announcement for a given invid that isn't | ||||
//! COMPLETED. | //! COMPLETED. | ||||
bool IsOnlyNonCompleted(Iter<ByInvId> it) { | bool IsOnlyNonCompleted(Iter<ByInvId> it) { | ||||
assert(it != m_index.get<ByInvId>().end()); | assert(it != m_index.template get<ByInvId>().end()); | ||||
// Not allowed to call this on COMPLETED announcements. | // Not allowed to call this on COMPLETED announcements. | ||||
assert(it->GetState() != State::COMPLETED); | assert(it->GetState() != State::COMPLETED); | ||||
// This announcement has a predecessor that belongs to the same invid. | // This announcement has a predecessor that belongs to the same invid. | ||||
// Due to ordering, and the fact that 'it' is not COMPLETED, its | // Due to ordering, and the fact that 'it' is not COMPLETED, its | ||||
// predecessor cannot be COMPLETED here. | // predecessor cannot be COMPLETED here. | ||||
if (it != m_index.get<ByInvId>().begin() && | if (it != m_index.template get<ByInvId>().begin() && | ||||
std::prev(it)->m_invid == it->m_invid) { | std::prev(it)->m_invid == it->m_invid) { | ||||
return false; | return false; | ||||
} | } | ||||
// This announcement has a successor that belongs to the same invid, | // This announcement has a successor that belongs to the same invid, | ||||
// and is not COMPLETED. | // and is not COMPLETED. | ||||
if (std::next(it) != m_index.get<ByInvId>().end() && | if (std::next(it) != m_index.template get<ByInvId>().end() && | ||||
std::next(it)->m_invid == it->m_invid && | std::next(it)->m_invid == it->m_invid && | ||||
std::next(it)->GetState() != State::COMPLETED) { | std::next(it)->GetState() != State::COMPLETED) { | ||||
return false; | return false; | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
/** | /** | ||||
* Convert any announcement to a COMPLETED one. If there are no | * Convert any announcement to a COMPLETED one. If there are no | ||||
* non-COMPLETED announcements left for this invid, they are deleted. If | * non-COMPLETED announcements left for this invid, they are deleted. If | ||||
* this was a REQUESTED announcement, and there are other CANDIDATEs left, | * this was a REQUESTED announcement, and there are other CANDIDATEs left, | ||||
* the best one is made CANDIDATE_BEST. Returns whether the announcement | * the best one is made CANDIDATE_BEST. Returns whether the announcement | ||||
* still exists. | * still exists. | ||||
*/ | */ | ||||
bool MakeCompleted(Iter<ByInvId> it) { | bool MakeCompleted(Iter<ByInvId> it) { | ||||
assert(it != m_index.get<ByInvId>().end()); | assert(it != m_index.template get<ByInvId>().end()); | ||||
// Nothing to be done if it's already COMPLETED. | // Nothing to be done if it's already COMPLETED. | ||||
if (it->GetState() == State::COMPLETED) { | if (it->GetState() == State::COMPLETED) { | ||||
return true; | return true; | ||||
} | } | ||||
if (IsOnlyNonCompleted(it)) { | if (IsOnlyNonCompleted(it)) { | ||||
// This is the last non-COMPLETED announcement for this invid. | // This is the last non-COMPLETED announcement for this invid. | ||||
// Delete all. | // Delete all. | ||||
TxId invid = it->m_invid; | InvId invid = it->m_invid; | ||||
do { | do { | ||||
it = Erase<ByInvId>(it); | it = Erase<ByInvId>(it); | ||||
} while (it != m_index.get<ByInvId>().end() && | } while (it != m_index.template get<ByInvId>().end() && | ||||
it->m_invid == invid); | it->m_invid == invid); | ||||
return false; | return false; | ||||
} | } | ||||
// Mark the announcement COMPLETED, and select the next best | // Mark the announcement COMPLETED, and select the next best | ||||
// announcement (the first CANDIDATE_READY) if needed. | // announcement (the first CANDIDATE_READY) if needed. | ||||
ChangeAndReselect(it, State::COMPLETED); | ChangeAndReselect(it, State::COMPLETED); | ||||
return true; | return true; | ||||
} | } | ||||
//! Make the data structure consistent with a given point in time: | //! Make the data structure consistent with a given point in time: | ||||
//! - REQUESTED annoucements with expiry <= now are turned into COMPLETED. | //! - REQUESTED annoucements with expiry <= now are turned into COMPLETED. | ||||
//! - CANDIDATE_DELAYED announcements with reqtime <= now are turned into | //! - CANDIDATE_DELAYED announcements with reqtime <= now are turned into | ||||
//! CANDIDATE_{READY,BEST}. | //! CANDIDATE_{READY,BEST}. | ||||
//! - CANDIDATE_{READY,BEST} announcements with reqtime > now are turned | //! - CANDIDATE_{READY,BEST} announcements with reqtime > now are turned | ||||
//! into CANDIDATE_DELAYED. | //! into CANDIDATE_DELAYED. | ||||
void SetTimePoint(std::chrono::microseconds now, | void SetTimePoint(std::chrono::microseconds now, | ||||
std::vector<std::pair<NodeId, TxId>> *expired) { | std::vector<std::pair<NodeId, InvId>> *expired) { | ||||
if (expired) { | if (expired) { | ||||
expired->clear(); | expired->clear(); | ||||
} | } | ||||
// Iterate over all CANDIDATE_DELAYED and REQUESTED from old to new, as | // Iterate over all CANDIDATE_DELAYED and REQUESTED from old to new, as | ||||
// long as they're in the past, and convert them to CANDIDATE_READY and | // long as they're in the past, and convert them to CANDIDATE_READY and | ||||
// COMPLETED respectively. | // COMPLETED respectively. | ||||
while (!m_index.empty()) { | while (!m_index.empty()) { | ||||
auto it = m_index.get<ByTime>().begin(); | auto it = m_index.template get<ByTime>().begin(); | ||||
if (it->GetState() == State::CANDIDATE_DELAYED && | if (it->GetState() == State::CANDIDATE_DELAYED && | ||||
it->m_time <= now) { | it->m_time <= now) { | ||||
PromoteCandidateReady(m_index.project<ByInvId>(it)); | PromoteCandidateReady(m_index.template project<ByInvId>(it)); | ||||
} else if (it->GetState() == State::REQUESTED && | } else if (it->GetState() == State::REQUESTED && | ||||
it->m_time <= now) { | it->m_time <= now) { | ||||
if (expired) { | if (expired) { | ||||
expired->emplace_back(it->m_peer, it->m_invid); | expired->emplace_back(it->m_peer, it->m_invid); | ||||
} | } | ||||
MakeCompleted(m_index.project<ByInvId>(it)); | MakeCompleted(m_index.template project<ByInvId>(it)); | ||||
} else { | } else { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
while (!m_index.empty()) { | while (!m_index.empty()) { | ||||
// If time went backwards, we may need to demote CANDIDATE_BEST and | // If time went backwards, we may need to demote CANDIDATE_BEST and | ||||
// CANDIDATE_READY announcements back to CANDIDATE_DELAYED. This is | // CANDIDATE_READY announcements back to CANDIDATE_DELAYED. This is | ||||
// an unusual edge case, and unlikely to matter in production. | // an unusual edge case, and unlikely to matter in production. | ||||
// However, it makes it much easier to specify and test | // However, it makes it much easier to specify and test | ||||
// InvRequestTracker::Impl's behaviour. | // InvRequestTracker::Impl's behaviour. | ||||
auto it = std::prev(m_index.get<ByTime>().end()); | auto it = std::prev(m_index.template get<ByTime>().end()); | ||||
if (it->IsSelectable() && it->m_time > now) { | if (it->IsSelectable() && it->m_time > now) { | ||||
ChangeAndReselect(m_index.project<ByInvId>(it), | ChangeAndReselect(m_index.template project<ByInvId>(it), | ||||
State::CANDIDATE_DELAYED); | State::CANDIDATE_DELAYED); | ||||
} else { | } else { | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
//! The current sequence number. Increases for every announcement. This is | //! The current sequence number. Increases for every announcement. This is | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | public: | ||||
// The operations below manipulate the data structure. | // The operations below manipulate the data structure. | ||||
/** | /** | ||||
* Adds a new CANDIDATE announcement. | * Adds a new CANDIDATE announcement. | ||||
* | * | ||||
* Does nothing if one already exists for that (invid, peer) combination | * Does nothing if one already exists for that (invid, peer) combination | ||||
* (whether it's CANDIDATE, REQUESTED, or COMPLETED). | * (whether it's CANDIDATE, REQUESTED, or COMPLETED). | ||||
*/ | */ | ||||
void ReceivedInv(NodeId peer, const TxId &invid, bool preferred, | void ReceivedInv(NodeId peer, const InvId &invid, bool preferred, | ||||
std::chrono::microseconds reqtime) { | std::chrono::microseconds reqtime) { | ||||
// Bail out if we already have a CANDIDATE_BEST announcement for this | // Bail out if we already have a CANDIDATE_BEST announcement for this | ||||
// (invid, peer) combination. The case where there is a | // (invid, peer) combination. The case where there is a | ||||
// non-CANDIDATE_BEST announcement already will be caught by the | // non-CANDIDATE_BEST announcement already will be caught by the | ||||
// uniqueness property of the ByPeer index when we try to emplace the | // uniqueness property of the ByPeer index when we try to emplace the | ||||
// new object below. | // new object below. | ||||
if (m_index.get<ByPeer>().count(ByPeerView{peer, true, invid})) { | if (m_index.template get<ByPeer>().count( | ||||
ByPeerView{peer, true, invid})) { | |||||
return; | return; | ||||
} | } | ||||
// Try creating the announcement with CANDIDATE_DELAYED state (which | // Try creating the announcement with CANDIDATE_DELAYED state (which | ||||
// will fail due to the uniqueness of the ByPeer index if a | // will fail due to the uniqueness of the ByPeer index if a | ||||
// non-CANDIDATE_BEST announcement already exists with the same invid | // non-CANDIDATE_BEST announcement already exists with the same invid | ||||
// and peer). Bail out in that case. | // and peer). Bail out in that case. | ||||
auto ret = m_index.get<ByPeer>().emplace(invid, peer, preferred, | auto ret = m_index.template get<ByPeer>().emplace( | ||||
reqtime, m_current_sequence); | invid, peer, preferred, reqtime, m_current_sequence); | ||||
if (!ret.second) { | if (!ret.second) { | ||||
return; | return; | ||||
} | } | ||||
// Update accounting metadata. | // Update accounting metadata. | ||||
++m_peerinfo[peer].m_total; | ++m_peerinfo[peer].m_total; | ||||
++m_current_sequence; | ++m_current_sequence; | ||||
} | } | ||||
/** | /** | ||||
* Deletes all announcements for a given peer. | * Deletes all announcements for a given peer. | ||||
* | * | ||||
* It should be called when a peer goes offline. | * It should be called when a peer goes offline. | ||||
*/ | */ | ||||
void DisconnectedPeer(NodeId peer) { | void DisconnectedPeer(NodeId peer) { | ||||
auto &index = m_index.get<ByPeer>(); | auto &index = m_index.template get<ByPeer>(); | ||||
auto it = | auto it = | ||||
index.lower_bound(ByPeerView{peer, false, TxId(uint256::ZERO)}); | index.lower_bound(ByPeerView{peer, false, InvId(uint256::ZERO)}); | ||||
while (it != index.end() && it->m_peer == peer) { | while (it != index.end() && it->m_peer == peer) { | ||||
// Check what to continue with after this iteration. 'it' will be | // Check what to continue with after this iteration. 'it' will be | ||||
// deleted in what follows, so we need to decide what to continue | // deleted in what follows, so we need to decide what to continue | ||||
// with afterwards. There are a number of cases to consider: | // with afterwards. There are a number of cases to consider: | ||||
// - std::next(it) is end() or belongs to a different peer. In that | // - std::next(it) is end() or belongs to a different peer. In that | ||||
// case, this is the last iteration of the loop (denote this by | // case, this is the last iteration of the loop (denote this by | ||||
// setting it_next to end()). | // setting it_next to end()). | ||||
// - 'it' is not the only non-COMPLETED announcement for its invid. | // - 'it' is not the only non-COMPLETED announcement for its invid. | ||||
Show All 13 Lines | void DisconnectedPeer(NodeId peer) { | ||||
auto it_next = | auto it_next = | ||||
(std::next(it) == index.end() || std::next(it)->m_peer != peer) | (std::next(it) == index.end() || std::next(it)->m_peer != peer) | ||||
? index.end() | ? index.end() | ||||
: std::next(it); | : std::next(it); | ||||
// If the announcement isn't already COMPLETED, first make it | // If the announcement isn't already COMPLETED, first make it | ||||
// COMPLETED (which will mark other CANDIDATEs as CANDIDATE_BEST, or | // COMPLETED (which will mark other CANDIDATEs as CANDIDATE_BEST, or | ||||
// delete all of an invid's announcements if no non-COMPLETED ones | // delete all of an invid's announcements if no non-COMPLETED ones | ||||
// are left). | // are left). | ||||
if (MakeCompleted(m_index.project<ByInvId>(it))) { | if (MakeCompleted(m_index.template project<ByInvId>(it))) { | ||||
// Then actually delete the announcement (unless it was already | // Then actually delete the announcement (unless it was already | ||||
// deleted by MakeCompleted). | // deleted by MakeCompleted). | ||||
Erase<ByPeer>(it); | Erase<ByPeer>(it); | ||||
} | } | ||||
it = it_next; | it = it_next; | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Deletes all announcements for a given invid. | * Deletes all announcements for a given invid. | ||||
* | * | ||||
* This should be called when an inventory is no longer needed. The caller | * This should be called when an inventory is no longer needed. The caller | ||||
* should ensure that new announcements for the same invid will not trigger | * should ensure that new announcements for the same invid will not trigger | ||||
* new ReceivedInv calls, at least in the short term after this call. | * new ReceivedInv calls, at least in the short term after this call. | ||||
*/ | */ | ||||
void ForgetInvId(const TxId &invid) { | void ForgetInvId(const InvId &invid) { | ||||
auto it = m_index.get<ByInvId>().lower_bound( | auto it = m_index.template get<ByInvId>().lower_bound( | ||||
ByInvIdView{invid, State::CANDIDATE_DELAYED, 0}); | ByInvIdView{invid, State::CANDIDATE_DELAYED, 0}); | ||||
while (it != m_index.get<ByInvId>().end() && it->m_invid == invid) { | while (it != m_index.template get<ByInvId>().end() && | ||||
it->m_invid == invid) { | |||||
it = Erase<ByInvId>(it); | it = Erase<ByInvId>(it); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Find the invids to request now from peer. | * Find the invids to request now from peer. | ||||
* | * | ||||
* It does the following: | * It does the following: | ||||
Show All 10 Lines | public: | ||||
* - The selected announcements are returned in announcement order (even | * - The selected announcements are returned in announcement order (even | ||||
* if multiple were added at the same time, or when the clock went | * if multiple were added at the same time, or when the clock went | ||||
* backwards while they were being added). This is done to minimize | * backwards while they were being added). This is done to minimize | ||||
* disruption from dependent inventories being requested out of order: | * disruption from dependent inventories being requested out of order: | ||||
* if multiple dependent inventories are announced simultaneously by one | * if multiple dependent inventories are announced simultaneously by one | ||||
* peer, and end up being requested from them, the requests will happen | * peer, and end up being requested from them, the requests will happen | ||||
* in announcement order. | * in announcement order. | ||||
*/ | */ | ||||
std::vector<TxId> | std::vector<InvId> | ||||
GetRequestable(NodeId peer, std::chrono::microseconds now, | GetRequestable(NodeId peer, std::chrono::microseconds now, | ||||
std::vector<std::pair<NodeId, TxId>> *expired) { | std::vector<std::pair<NodeId, InvId>> *expired) { | ||||
// Move time. | // Move time. | ||||
SetTimePoint(now, expired); | SetTimePoint(now, expired); | ||||
// Find all CANDIDATE_BEST announcements for this peer. | // Find all CANDIDATE_BEST announcements for this peer. | ||||
std::vector<const Announcement *> selected; | std::vector<const Announcement *> selected; | ||||
auto it_peer = m_index.get<ByPeer>().lower_bound( | auto it_peer = m_index.template get<ByPeer>().lower_bound( | ||||
ByPeerView{peer, true, TxId(uint256::ZERO)}); | ByPeerView{peer, true, InvId(uint256::ZERO)}); | ||||
while (it_peer != m_index.get<ByPeer>().end() && | while (it_peer != m_index.template get<ByPeer>().end() && | ||||
it_peer->m_peer == peer && | it_peer->m_peer == peer && | ||||
it_peer->GetState() == State::CANDIDATE_BEST) { | it_peer->GetState() == State::CANDIDATE_BEST) { | ||||
selected.emplace_back(&*it_peer); | selected.emplace_back(&*it_peer); | ||||
++it_peer; | ++it_peer; | ||||
} | } | ||||
// Sort by sequence number. | // Sort by sequence number. | ||||
std::sort(selected.begin(), selected.end(), | std::sort(selected.begin(), selected.end(), | ||||
[](const Announcement *a, const Announcement *b) { | [](const Announcement *a, const Announcement *b) { | ||||
return a->m_sequence < b->m_sequence; | return a->m_sequence < b->m_sequence; | ||||
}); | }); | ||||
// Convert to InvId and return. | // Convert to InvId and return. | ||||
std::vector<TxId> ret; | std::vector<InvId> ret; | ||||
ret.reserve(selected.size()); | ret.reserve(selected.size()); | ||||
std::transform(selected.begin(), selected.end(), | std::transform(selected.begin(), selected.end(), | ||||
std::back_inserter(ret), | std::back_inserter(ret), | ||||
[](const Announcement *ann) { return ann->m_invid; }); | [](const Announcement *ann) { return ann->m_invid; }); | ||||
return ret; | return ret; | ||||
} | } | ||||
/** | /** | ||||
* Marks an inventory as requested, with a specified expiry. | * Marks an inventory as requested, with a specified expiry. | ||||
* | * | ||||
* If no CANDIDATE announcement for the provided peer and invid exists, this | * If no CANDIDATE announcement for the provided peer and invid exists, this | ||||
* call has no effect. Otherwise: | * call has no effect. Otherwise: | ||||
* - That announcement is converted to REQUESTED. | * - That announcement is converted to REQUESTED. | ||||
* - If any other REQUESTED announcement for the same invid already | * - If any other REQUESTED announcement for the same invid already | ||||
* existed, it means an unexpected request was made (GetRequestable will | * existed, it means an unexpected request was made (GetRequestable will | ||||
* never advise doing so). In this case it is converted to COMPLETED, as | * never advise doing so). In this case it is converted to COMPLETED, as | ||||
* we're no longer waiting for a response to it. | * we're no longer waiting for a response to it. | ||||
*/ | */ | ||||
void RequestedData(NodeId peer, const TxId &invid, | void RequestedData(NodeId peer, const InvId &invid, | ||||
std::chrono::microseconds expiry) { | std::chrono::microseconds expiry) { | ||||
auto it = m_index.get<ByPeer>().find(ByPeerView{peer, true, invid}); | auto it = | ||||
if (it == m_index.get<ByPeer>().end()) { | m_index.template get<ByPeer>().find(ByPeerView{peer, true, invid}); | ||||
if (it == m_index.template get<ByPeer>().end()) { | |||||
// There is no CANDIDATE_BEST announcement, look for a _READY or | // There is no CANDIDATE_BEST announcement, look for a _READY or | ||||
// _DELAYED instead. If the caller only ever invokes RequestedData | // _DELAYED instead. If the caller only ever invokes RequestedData | ||||
// with the values returned by GetRequestable, and no other | // with the values returned by GetRequestable, and no other | ||||
// non-const functions other than ForgetInvId and GetRequestable in | // non-const functions other than ForgetInvId and GetRequestable in | ||||
// between, this branch will never execute (as invids returned by | // between, this branch will never execute (as invids returned by | ||||
// GetRequestable always correspond to CANDIDATE_BEST | // GetRequestable always correspond to CANDIDATE_BEST | ||||
// announcements). | // announcements). | ||||
it = m_index.get<ByPeer>().find(ByPeerView{peer, false, invid}); | it = m_index.template get<ByPeer>().find( | ||||
if (it == m_index.get<ByPeer>().end() || | ByPeerView{peer, false, invid}); | ||||
if (it == m_index.template get<ByPeer>().end() || | |||||
(it->GetState() != State::CANDIDATE_DELAYED && | (it->GetState() != State::CANDIDATE_DELAYED && | ||||
it->GetState() != State::CANDIDATE_READY)) { | it->GetState() != State::CANDIDATE_READY)) { | ||||
// There is no CANDIDATE announcement tracked for this peer, so | // There is no CANDIDATE announcement tracked for this peer, so | ||||
// we have nothing to do. Either this invid wasn't tracked at | // we have nothing to do. Either this invid wasn't tracked at | ||||
// all (and the caller should have called ReceivedInv), or it | // all (and the caller should have called ReceivedInv), or it | ||||
// was already requested and/or completed for other reasons and | // was already requested and/or completed for other reasons and | ||||
// this is just a superfluous RequestedData call. | // this is just a superfluous RequestedData call. | ||||
return; | return; | ||||
} | } | ||||
// Look for an existing CANDIDATE_BEST or REQUESTED with the same | // Look for an existing CANDIDATE_BEST or REQUESTED with the same | ||||
// invid. We only need to do this if the found announcement had a | // invid. We only need to do this if the found announcement had a | ||||
// different state than CANDIDATE_BEST. If it did, invariants | // different state than CANDIDATE_BEST. If it did, invariants | ||||
// guarantee that no other CANDIDATE_BEST or REQUESTED can exist. | // guarantee that no other CANDIDATE_BEST or REQUESTED can exist. | ||||
auto it_old = m_index.get<ByInvId>().lower_bound( | auto it_old = m_index.template get<ByInvId>().lower_bound( | ||||
ByInvIdView{invid, State::CANDIDATE_BEST, 0}); | ByInvIdView{invid, State::CANDIDATE_BEST, 0}); | ||||
if (it_old != m_index.get<ByInvId>().end() && | if (it_old != m_index.template get<ByInvId>().end() && | ||||
it_old->m_invid == invid) { | it_old->m_invid == invid) { | ||||
if (it_old->GetState() == State::CANDIDATE_BEST) { | if (it_old->GetState() == State::CANDIDATE_BEST) { | ||||
// The data structure's invariants require that there can be | // The data structure's invariants require that there can be | ||||
// at most one CANDIDATE_BEST or one REQUESTED announcement | // at most one CANDIDATE_BEST or one REQUESTED announcement | ||||
// per invid (but not both simultaneously), so we have to | // per invid (but not both simultaneously), so we have to | ||||
// convert any existing CANDIDATE_BEST to another | // convert any existing CANDIDATE_BEST to another | ||||
// CANDIDATE_* when constructing another REQUESTED. It | // CANDIDATE_* when constructing another REQUESTED. It | ||||
// doesn't matter whether we pick CANDIDATE_READY or | // doesn't matter whether we pick CANDIDATE_READY or | ||||
Show All 25 Lines | public: | ||||
* Converts a CANDIDATE or REQUESTED announcement to a COMPLETED one. If no | * Converts a CANDIDATE or REQUESTED announcement to a COMPLETED one. If no | ||||
* such announcement exists for the provided peer and invid, nothing | * such announcement exists for the provided peer and invid, nothing | ||||
* happens. | * happens. | ||||
* | * | ||||
* It should be called whenever an inventory or NOTFOUND was received from | * It should be called whenever an inventory or NOTFOUND was received from | ||||
* a peer. When the inventory is not needed entirely anymore, ForgetInvId | * a peer. When the inventory is not needed entirely anymore, ForgetInvId | ||||
* should be called instead of, or in addition to, this call. | * should be called instead of, or in addition to, this call. | ||||
*/ | */ | ||||
void ReceivedResponse(NodeId peer, const TxId &invid) { | void ReceivedResponse(NodeId peer, const InvId &invid) { | ||||
// We need to search the ByPeer index for both (peer, false, invid) and | // We need to search the ByPeer index for both (peer, false, invid) and | ||||
// (peer, true, invid). | // (peer, true, invid). | ||||
auto it = m_index.get<ByPeer>().find(ByPeerView{peer, false, invid}); | auto it = | ||||
if (it == m_index.get<ByPeer>().end()) { | m_index.template get<ByPeer>().find(ByPeerView{peer, false, invid}); | ||||
it = m_index.get<ByPeer>().find(ByPeerView{peer, true, invid}); | if (it == m_index.template get<ByPeer>().end()) { | ||||
it = m_index.template get<ByPeer>().find( | |||||
ByPeerView{peer, true, invid}); | |||||
} | } | ||||
if (it != m_index.get<ByPeer>().end()) { | if (it != m_index.template get<ByPeer>().end()) { | ||||
MakeCompleted(m_index.project<ByInvId>(it)); | MakeCompleted(m_index.template project<ByInvId>(it)); | ||||
} | } | ||||
} | } | ||||
// The operations below inspect the data structure. | // The operations below inspect the data structure. | ||||
/** Count how many REQUESTED announcements a peer has. */ | /** Count how many REQUESTED announcements a peer has. */ | ||||
size_t CountInFlight(NodeId peer) const { | size_t CountInFlight(NodeId peer) const { | ||||
auto it = m_peerinfo.find(peer); | auto it = m_peerinfo.find(peer); | ||||
Show All 27 Lines | public: | ||||
/** | /** | ||||
* Count how many announcements are being tracked in total across all peers | * Count how many announcements are being tracked in total across all peers | ||||
* and inventory ids. | * and inventory ids. | ||||
*/ | */ | ||||
size_t Size() const { return m_index.size(); } | size_t Size() const { return m_index.size(); } | ||||
/** Access to the internal priority computation (testing only) */ | /** Access to the internal priority computation (testing only) */ | ||||
uint64_t ComputePriority(const TxId &invid, NodeId peer, | uint64_t ComputePriority(const InvId &invid, NodeId peer, | ||||
bool preferred) const { | bool preferred) const { | ||||
// Return Priority as a uint64_t as Priority is internal. | // Return Priority as a uint64_t as Priority is internal. | ||||
return uint64_t{m_computer(invid, peer, preferred)}; | return uint64_t{m_computer(invid, peer, preferred)}; | ||||
} | } | ||||
/** Run internal consistency check (testing only). */ | /** Run internal consistency check (testing only). */ | ||||
void SanityCheck() const { | void SanityCheck() const { | ||||
// Recompute m_peerdata from m_index. This verifies the data in it as it | // Recompute m_peerdata from m_index. This verifies the data in it as it | ||||
▲ Show 20 Lines • Show All 65 Lines • Show Last 20 Lines |
What is this?
I'm not saying it's wrong, but if you could point me to an example of what that achieve, that would be fantastic.