19constexpr int MAX_TXHASHES = 16;
20constexpr int MAX_PEERS = 16;
26std::chrono::microseconds DELAYS[256];
32 for (uint8_t txhash = 0; txhash < MAX_TXHASHES; txhash += 1) {
38 DELAYS[i] = std::chrono::microseconds{i};
42 for (; i < 128; ++i) {
43 int diff_bits = ((i - 10) * 2) / 9;
45 DELAYS[i] = DELAYS[i - 1] + std::chrono::microseconds{diff};
48 for (; i < 256; ++i) {
49 DELAYS[i] = -DELAYS[255 - i];
84 uint64_t m_current_sequence{0};
87 std::priority_queue<std::chrono::microseconds, std::vector<std::chrono::microseconds>,
88 std::greater<std::chrono::microseconds>> m_events;
93 std::chrono::microseconds m_time;
95 State m_state{State::NOTHING};
102 Announcement m_announcements[MAX_TXHASHES][MAX_PEERS];
105 std::chrono::microseconds m_now{244466666};
108 void Cleanup(
int txhash)
110 bool all_nothing =
true;
111 for (
int peer = 0; peer < MAX_PEERS; ++peer) {
112 const Announcement& ann = m_announcements[txhash][peer];
113 if (ann.m_state != State::NOTHING) {
114 if (ann.m_state != State::COMPLETED)
return;
118 if (all_nothing)
return;
119 for (
int peer = 0; peer < MAX_PEERS; ++peer) {
120 m_announcements[txhash][peer].m_state = State::NOTHING;
125 int GetSelected(
int txhash)
const
128 uint64_t ret_priority = 0;
129 for (
int peer = 0; peer < MAX_PEERS; ++peer) {
130 const Announcement& ann = m_announcements[txhash][peer];
132 if (ann.m_state == State::REQUESTED)
return -1;
134 if (ann.m_state == State::CANDIDATE && ann.m_time <= m_now) {
135 if (
ret == -1 || ann.m_priority > ret_priority) {
136 std::tie(
ret, ret_priority) = std::tie(peer, ann.m_priority);
144 Tester() : m_tracker(true) {}
146 std::chrono::microseconds
Now()
const {
return m_now; }
148 void AdvanceTime(std::chrono::microseconds offset)
151 while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
154 void AdvanceToEvent()
156 while (!m_events.empty() && m_events.top() <= m_now) m_events.pop();
157 if (!m_events.empty()) {
158 m_now = m_events.top();
163 void DisconnectedPeer(
int peer)
166 for (
int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
167 if (m_announcements[txhash][peer].m_state != State::NOTHING) {
168 m_announcements[txhash][peer].m_state = State::NOTHING;
177 void ForgetTxHash(
int txhash)
180 for (
int peer = 0; peer < MAX_PEERS; ++peer) {
181 m_announcements[txhash][peer].m_state = State::NOTHING;
189 void ReceivedInv(
int peer,
int txhash,
bool is_wtxid,
bool preferred, std::chrono::microseconds reqtime)
193 Announcement& ann = m_announcements[txhash][peer];
194 if (ann.m_state == State::NOTHING) {
195 ann.m_preferred = preferred;
196 ann.m_state = State::CANDIDATE;
197 ann.m_time = reqtime;
198 ann.m_is_wtxid = is_wtxid;
199 ann.m_sequence = m_current_sequence++;
200 ann.m_priority = m_tracker.
ComputePriority(TXHASHES[txhash], peer, ann.m_preferred);
203 if (reqtime > m_now) m_events.push(reqtime);
210 void RequestedTx(
int peer,
int txhash, std::chrono::microseconds exptime)
214 if (m_announcements[txhash][peer].m_state == State::CANDIDATE) {
215 for (
int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
216 if (m_announcements[txhash][peer2].m_state == State::REQUESTED) {
217 m_announcements[txhash][peer2].m_state = State::COMPLETED;
220 m_announcements[txhash][peer].m_state = State::REQUESTED;
221 m_announcements[txhash][peer].m_time = exptime;
225 if (exptime > m_now) m_events.push(exptime);
228 m_tracker.
RequestedTx(peer, TXHASHES[txhash], exptime);
231 void ReceivedResponse(
int peer,
int txhash)
234 if (m_announcements[txhash][peer].m_state != State::NOTHING) {
235 m_announcements[txhash][peer].m_state = State::COMPLETED;
243 void GetRequestable(
int peer)
248 std::vector<std::tuple<uint64_t, int, bool>> result;
249 std::vector<std::pair<NodeId, GenTxid>> expected_expired;
250 for (
int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
252 for (
int peer2 = 0; peer2 < MAX_PEERS; ++peer2) {
253 Announcement& ann2 = m_announcements[txhash][peer2];
254 if (ann2.m_state == State::REQUESTED && ann2.m_time <= m_now) {
256 ann2.m_state = State::COMPLETED;
263 const Announcement& ann = m_announcements[txhash][peer];
264 if (ann.m_state == State::CANDIDATE && GetSelected(txhash) == peer) {
265 result.emplace_back(ann.m_sequence, txhash, ann.m_is_wtxid);
269 std::sort(result.begin(), result.end());
270 std::sort(expected_expired.begin(), expected_expired.end());
273 std::vector<std::pair<NodeId, GenTxid>> expired;
274 const auto actual = m_tracker.
GetRequestable(peer, m_now, &expired);
275 std::sort(expired.begin(), expired.end());
276 assert(expired == expected_expired);
279 assert(result.size() == actual.size());
280 for (
size_t pos = 0; pos < actual.size(); ++pos) {
281 assert(TXHASHES[std::get<1>(result[pos])] == actual[pos].GetHash());
282 assert(std::get<2>(result[pos]) == actual[pos].IsWtxid());
290 for (
int peer = 0; peer < MAX_PEERS; ++peer) {
293 size_t candidates = 0;
294 for (
int txhash = 0; txhash < MAX_TXHASHES; ++txhash) {
295 tracked += m_announcements[txhash][peer].m_state != State::NOTHING;
296 inflight += m_announcements[txhash][peer].m_state == State::REQUESTED;
297 candidates += m_announcements[txhash][peer].m_state == State::CANDIDATE;
319 auto it = buffer.begin();
320 while (it != buffer.end()) {
321 int cmd = *(it++) % 11;
322 int peer, txidnum, delaynum;
325 tester.AdvanceToEvent();
328 delaynum = it == buffer.end() ? 0 : *(it++);
329 tester.AdvanceTime(DELAYS[delaynum]);
332 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
333 tester.GetRequestable(peer);
336 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
337 tester.DisconnectedPeer(peer);
340 txidnum = it == buffer.end() ? 0 : *(it++);
341 tester.ForgetTxHash(txidnum % MAX_TXHASHES);
345 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
346 txidnum = it == buffer.end() ? 0 : *(it++);
347 tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1,
cmd & 1,
348 std::chrono::microseconds::min());
352 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
353 txidnum = it == buffer.end() ? 0 : *(it++);
354 delaynum = it == buffer.end() ? 0 : *(it++);
355 tester.ReceivedInv(peer, txidnum % MAX_TXHASHES, (txidnum / MAX_TXHASHES) & 1,
cmd & 1,
356 tester.Now() + DELAYS[delaynum]);
359 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
360 txidnum = it == buffer.end() ? 0 : *(it++);
361 delaynum = it == buffer.end() ? 0 : *(it++);
362 tester.RequestedTx(peer, txidnum % MAX_TXHASHES, tester.Now() + DELAYS[delaynum]);
365 peer = it == buffer.end() ? 0 : *(it++) % MAX_PEERS;
366 txidnum = it == buffer.end() ? 0 : *(it++);
367 tester.ReceivedResponse(peer, txidnum % MAX_TXHASHES);
A hasher class for SHA-256.
void Finalize(unsigned char hash[OUTPUT_SIZE])
CSHA256 & Write(const unsigned char *data, size_t len)
uint64_t Finalize() const
Compute the 64-bit SipHash-2-4 of the data written so far.
CSipHasher & Write(uint64_t data)
Hash a 64-bit integer worth of data It is treated as if this was the little-endian interpretation of ...
static GenTxid Wtxid(const uint256 &hash)
static GenTxid Txid(const uint256 &hash)
Data structure to keep track of, and schedule, transaction downloads from peers.
void ReceivedInv(NodeId peer, const GenTxid >xid, bool preferred, std::chrono::microseconds reqtime)
Adds a new CANDIDATE announcement.
void SanityCheck() const
Run internal consistency check (testing only).
size_t CountInFlight(NodeId peer) const
Count how many REQUESTED announcements a peer has.
size_t CountCandidates(NodeId peer) const
Count how many CANDIDATE announcements a peer has.
void DisconnectedPeer(NodeId peer)
Deletes all announcements for a given peer.
void ReceivedResponse(NodeId peer, const uint256 &txhash)
Converts a CANDIDATE or REQUESTED announcement to a COMPLETED one.
uint64_t ComputePriority(const uint256 &txhash, NodeId peer, bool preferred) const
Access to the internal priority computation (testing only)
void PostGetRequestableSanityCheck(std::chrono::microseconds now) const
Run a time-dependent internal consistency check (testing only).
void RequestedTx(NodeId peer, const uint256 &txhash, std::chrono::microseconds expiry)
Marks a transaction as requested, with a specified expiry.
size_t Count(NodeId peer) const
Count how many announcements a peer has (REQUESTED, CANDIDATE, and COMPLETED combined).
size_t Size() const
Count how many announcements are being tracked in total across all peers and transaction hashes.
std::vector< GenTxid > GetRequestable(NodeId peer, std::chrono::microseconds now, std::vector< std::pair< NodeId, GenTxid > > *expired=nullptr)
Find the txids to request now from peer.
void ForgetTxHash(const uint256 &txhash)
Deletes all announcements for a given txhash (both txid and wtxid ones).
T Now()
Return the current time point cast to the given precision.