Skip to content

Commit c3fad1f

Browse files
committed
net: add have_next_message argument to Transport::GetBytesToSend()
Before this commit, there are only two possibly outcomes for the "more" prediction in Transport::GetBytesToSend(): * true: the transport itself has more to send, so the answer is certainly yes. * false: the transport has nothing further to send, but if vSendMsg has more message(s) left, that still will result in more wire bytes after the next SetMessageToSend(). For the BIP324 v2 transport, there will arguably be a third state: * definitely not: the transport has nothing further to send, but even if vSendMsg has more messages left, they can't be sent (right now). This happens before the handshake is complete. To implement this, we move the entire decision logic to the Transport, by adding a boolean to GetBytesToSend(), called have_next_message, which informs the transport whether more messages are available. The return values are still true and false, but they mean "definitely yes" and "definitely no", rather than "yes" and "maybe".
1 parent 8e0d979 commit c3fad1f

File tree

5 files changed

+85
-37
lines changed

5 files changed

+85
-37
lines changed

src/net.cpp

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -867,20 +867,22 @@ bool V1Transport::SetMessageToSend(CSerializedNetMsg& msg) noexcept
867867
return true;
868868
}
869869

870-
Transport::BytesToSend V1Transport::GetBytesToSend() const noexcept
870+
Transport::BytesToSend V1Transport::GetBytesToSend(bool have_next_message) const noexcept
871871
{
872872
AssertLockNotHeld(m_send_mutex);
873873
LOCK(m_send_mutex);
874874
if (m_sending_header) {
875875
return {Span{m_header_to_send}.subspan(m_bytes_sent),
876-
// We have more to send after the header if the message has payload.
877-
!m_message_to_send.data.empty(),
876+
// We have more to send after the header if the message has payload, or if there
877+
// is a next message after that.
878+
have_next_message || !m_message_to_send.data.empty(),
878879
m_message_to_send.m_type
879880
};
880881
} else {
881882
return {Span{m_message_to_send.data}.subspan(m_bytes_sent),
882-
// We never have more to send after this message's payload.
883-
false,
883+
// We only have more to send after this message's payload if there is another
884+
// message.
885+
have_next_message,
884886
m_message_to_send.m_type
885887
};
886888
}
@@ -916,6 +918,7 @@ std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
916918
auto it = node.vSendMsg.begin();
917919
size_t nSentSize = 0;
918920
bool data_left{false}; //!< second return value (whether unsent data remains)
921+
std::optional<bool> expected_more;
919922

920923
while (true) {
921924
if (it != node.vSendMsg.end()) {
@@ -928,7 +931,12 @@ std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
928931
++it;
929932
}
930933
}
931-
const auto& [data, more, msg_type] = node.m_transport->GetBytesToSend();
934+
const auto& [data, more, msg_type] = node.m_transport->GetBytesToSend(it != node.vSendMsg.end());
935+
// We rely on the 'more' value returned by GetBytesToSend to correctly predict whether more
936+
// bytes are still to be sent, to correctly set the MSG_MORE flag. As a sanity check,
937+
// verify that the previously returned 'more' was correct.
938+
if (expected_more.has_value()) Assume(!data.empty() == *expected_more);
939+
expected_more = more;
932940
data_left = !data.empty(); // will be overwritten on next loop if all of data gets sent
933941
int nBytes = 0;
934942
if (!data.empty()) {
@@ -941,9 +949,7 @@ std::pair<size_t, bool> CConnman::SocketSendData(CNode& node) const
941949
}
942950
int flags = MSG_NOSIGNAL | MSG_DONTWAIT;
943951
#ifdef MSG_MORE
944-
// We have more to send if either the transport itself has more, or if we have more
945-
// messages to send.
946-
if (more || it != node.vSendMsg.end()) {
952+
if (more) {
947953
flags |= MSG_MORE;
948954
}
949955
#endif
@@ -1323,9 +1329,10 @@ Sock::EventsPerSock CConnman::GenerateWaitSockets(Span<CNode* const> nodes)
13231329
{
13241330
LOCK(pnode->cs_vSend);
13251331
// Sending is possible if either there are bytes to send right now, or if there will be
1326-
// once a potential message from vSendMsg is handed to the transport.
1327-
const auto& [to_send, _more, _msg_type] = pnode->m_transport->GetBytesToSend();
1328-
select_send = !to_send.empty() || !pnode->vSendMsg.empty();
1332+
// once a potential message from vSendMsg is handed to the transport. GetBytesToSend
1333+
// determines both of these in a single call.
1334+
const auto& [to_send, more, _msg_type] = pnode->m_transport->GetBytesToSend(!pnode->vSendMsg.empty());
1335+
select_send = !to_send.empty() || more;
13291336
}
13301337
if (!select_recv && !select_send) continue;
13311338

@@ -3007,7 +3014,10 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
30073014
size_t nBytesSent = 0;
30083015
{
30093016
LOCK(pnode->cs_vSend);
3010-
const auto& [to_send, _more, _msg_type] = pnode->m_transport->GetBytesToSend();
3017+
// Check if the transport still has unsent bytes, and indicate to it that we're about to
3018+
// give it a message to send.
3019+
const auto& [to_send, more, _msg_type] =
3020+
pnode->m_transport->GetBytesToSend(/*have_next_message=*/true);
30113021
const bool queue_was_empty{to_send.empty() && pnode->vSendMsg.empty()};
30123022

30133023
// Update memory usage of send buffer.
@@ -3016,10 +3026,13 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg)
30163026
// Move message to vSendMsg queue.
30173027
pnode->vSendMsg.push_back(std::move(msg));
30183028

3019-
// If there was nothing to send before, attempt "optimistic write":
3029+
// If there was nothing to send before, and there is now (predicted by the "more" value
3030+
// returned by the GetBytesToSend call above), attempt "optimistic write":
30203031
// because the poll/select loop may pause for SELECT_TIMEOUT_MILLISECONDS before actually
30213032
// doing a send, try sending from the calling thread if the queue was empty before.
3022-
if (queue_was_empty) {
3033+
// With a V1Transport, more will always be true here, because adding a message always
3034+
// results in sendable bytes there.
3035+
if (queue_was_empty && more) {
30233036
std::tie(nBytesSent, std::ignore) = SocketSendData(*pnode);
30243037
}
30253038
}

src/net.h

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,19 +308,40 @@ class Transport {
308308
const std::string& /*m_type*/
309309
>;
310310

311-
/** Get bytes to send on the wire.
311+
/** Get bytes to send on the wire, if any, along with other information about it.
312312
*
313313
* As a const function, it does not modify the transport's observable state, and is thus safe
314314
* to be called multiple times.
315315
*
316-
* The bytes returned by this function act as a stream which can only be appended to. This
317-
* means that with the exception of MarkBytesSent, operations on the transport can only append
318-
* to what is being returned.
316+
* @param[in] have_next_message If true, the "more" return value reports whether more will
317+
* be sendable after a SetMessageToSend call. It is set by the caller when they know
318+
* they have another message ready to send, and only care about what happens
319+
* after that. The have_next_message argument only affects this "more" return value
320+
* and nothing else.
319321
*
320-
* Note that m_type and to_send refer to data that is internal to the transport, and calling
321-
* any non-const function on this object may invalidate them.
322+
* Effectively, there are three possible outcomes about whether there are more bytes
323+
* to send:
324+
* - Yes: the transport itself has more bytes to send later. For example, for
325+
* V1Transport this happens during the sending of the header of a
326+
* message, when there is a non-empty payload that follows.
327+
* - No: the transport itself has no more bytes to send, but will have bytes to
328+
* send if handed a message through SetMessageToSend. In V1Transport this
329+
* happens when sending the payload of a message.
330+
* - Blocked: the transport itself has no more bytes to send, and is also incapable
331+
* of sending anything more at all now, if it were handed another
332+
* message to send.
333+
*
334+
* The boolean 'more' is true for Yes, false for Blocked, and have_next_message
335+
* controls what is returned for No.
336+
*
337+
* @return a BytesToSend object. The to_send member returned acts as a stream which is only
338+
* ever appended to. This means that with the exception of MarkBytesSent (which pops
339+
* bytes off the front of later to_sends), operations on the transport can only append
340+
* to what is being returned. Also note that m_type and to_send refer to data that is
341+
* internal to the transport, and calling any non-const function on this object may
342+
* invalidate them.
322343
*/
323-
virtual BytesToSend GetBytesToSend() const noexcept = 0;
344+
virtual BytesToSend GetBytesToSend(bool have_next_message) const noexcept = 0;
324345

325346
/** Report how many bytes returned by the last GetBytesToSend() have been sent.
326347
*
@@ -416,7 +437,7 @@ class V1Transport final : public Transport
416437
CNetMessage GetReceivedMessage(std::chrono::microseconds time, bool& reject_message) override EXCLUSIVE_LOCKS_REQUIRED(!m_recv_mutex);
417438

418439
bool SetMessageToSend(CSerializedNetMsg& msg) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
419-
BytesToSend GetBytesToSend() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
440+
BytesToSend GetBytesToSend(bool have_next_message) const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
420441
void MarkBytesSent(size_t bytes_sent) noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
421442
size_t GetSendMemoryUsage() const noexcept override EXCLUSIVE_LOCKS_REQUIRED(!m_send_mutex);
422443
};

src/test/denialofservice_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
8686

8787
{
8888
LOCK(dummyNode1.cs_vSend);
89-
const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend();
89+
const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend(false);
9090
BOOST_CHECK(!to_send.empty());
9191
}
9292
connman.FlushSendBuffer(dummyNode1);
@@ -97,7 +97,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction)
9797
BOOST_CHECK(peerman.SendMessages(&dummyNode1)); // should result in getheaders
9898
{
9999
LOCK(dummyNode1.cs_vSend);
100-
const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend();
100+
const auto& [to_send, _more, _msg_type] = dummyNode1.m_transport->GetBytesToSend(false);
101101
BOOST_CHECK(!to_send.empty());
102102
}
103103
// Wait 3 more minutes

src/test/fuzz/p2p_transport_serialization.cpp

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ FUZZ_TARGET(p2p_transport_serialization, .init = initialize_p2p_transport_serial
9292
assert(queued);
9393
std::optional<bool> known_more;
9494
while (true) {
95-
const auto& [to_send, more, _msg_type] = send_transport.GetBytesToSend();
95+
const auto& [to_send, more, _msg_type] = send_transport.GetBytesToSend(false);
9696
if (known_more) assert(!to_send.empty() == *known_more);
9797
if (to_send.empty()) break;
9898
send_transport.MarkBytesSent(to_send.size());
@@ -124,11 +124,13 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
124124
// Vectors with bytes last returned by GetBytesToSend() on transport[i].
125125
std::array<std::vector<uint8_t>, 2> to_send;
126126

127-
// Last returned 'more' values (if still relevant) by transport[i]->GetBytesToSend().
128-
std::array<std::optional<bool>, 2> last_more;
127+
// Last returned 'more' values (if still relevant) by transport[i]->GetBytesToSend(), for
128+
// both have_next_message false and true.
129+
std::array<std::optional<bool>, 2> last_more, last_more_next;
129130

130-
// Whether more bytes to be sent are expected on transport[i].
131-
std::array<std::optional<bool>, 2> expect_more;
131+
// Whether more bytes to be sent are expected on transport[i], before and after
132+
// SetMessageToSend().
133+
std::array<std::optional<bool>, 2> expect_more, expect_more_next;
132134

133135
// Function to consume a message type.
134136
auto msg_type_fn = [&]() {
@@ -177,18 +179,27 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
177179

178180
// Wrapper around transport[i]->GetBytesToSend() that performs sanity checks.
179181
auto bytes_to_send_fn = [&](int side) -> Transport::BytesToSend {
180-
const auto& [bytes, more, msg_type] = transports[side]->GetBytesToSend();
182+
// Invoke GetBytesToSend twice (for have_next_message = {false, true}). This function does
183+
// not modify state (it's const), and only the "more" return value should differ between
184+
// the calls.
185+
const auto& [bytes, more_nonext, msg_type] = transports[side]->GetBytesToSend(false);
186+
const auto& [bytes_next, more_next, msg_type_next] = transports[side]->GetBytesToSend(true);
181187
// Compare with expected more.
182188
if (expect_more[side].has_value()) assert(!bytes.empty() == *expect_more[side]);
189+
// Verify consistency between the two results.
190+
assert(bytes == bytes_next);
191+
assert(msg_type == msg_type_next);
192+
if (more_nonext) assert(more_next);
183193
// Compare with previously reported output.
184194
assert(to_send[side].size() <= bytes.size());
185195
assert(to_send[side] == Span{bytes}.first(to_send[side].size()));
186196
to_send[side].resize(bytes.size());
187197
std::copy(bytes.begin(), bytes.end(), to_send[side].begin());
188-
// Remember 'more' result.
189-
last_more[side] = {more};
198+
// Remember 'more' results.
199+
last_more[side] = {more_nonext};
200+
last_more_next[side] = {more_next};
190201
// Return.
191-
return {bytes, more, msg_type};
202+
return {bytes, more_nonext, msg_type};
192203
};
193204

194205
// Function to make side send a new message.
@@ -199,7 +210,8 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
199210
CSerializedNetMsg msg = next_msg[side].Copy();
200211
bool queued = transports[side]->SetMessageToSend(msg);
201212
// Update expected more data.
202-
expect_more[side] = std::nullopt;
213+
expect_more[side] = expect_more_next[side];
214+
expect_more_next[side] = std::nullopt;
203215
// Verify consistency of GetBytesToSend after SetMessageToSend
204216
bytes_to_send_fn(/*side=*/side);
205217
if (queued) {
@@ -223,6 +235,7 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
223235
// If all to-be-sent bytes were sent, move last_more data to expect_more data.
224236
if (send_now == bytes.size()) {
225237
expect_more[side] = last_more[side];
238+
expect_more_next[side] = last_more_next[side];
226239
}
227240
// Remove the bytes from the last reported to-be-sent vector.
228241
assert(to_send[side].size() >= send_now);
@@ -251,6 +264,7 @@ void SimulationTest(Transport& initiator, Transport& responder, R& rng, FuzzedDa
251264
// Clear cached expected 'more' information: if certainly no more data was to be sent
252265
// before, receiving bytes makes this uncertain.
253266
if (expect_more[!side] == false) expect_more[!side] = std::nullopt;
267+
if (expect_more_next[!side] == false) expect_more_next[!side] = std::nullopt;
254268
// Verify consistency of GetBytesToSend after ReceivedBytes
255269
bytes_to_send_fn(/*side=*/!side);
256270
bool progress = to_recv.size() < old_len;

src/test/util/net.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void ConnmanTestMsg::FlushSendBuffer(CNode& node) const
7878
node.vSendMsg.clear();
7979
node.m_send_memusage = 0;
8080
while (true) {
81-
const auto& [to_send, _more, _msg_type] = node.m_transport->GetBytesToSend();
81+
const auto& [to_send, _more, _msg_type] = node.m_transport->GetBytesToSend(false);
8282
if (to_send.empty()) break;
8383
node.m_transport->MarkBytesSent(to_send.size());
8484
}
@@ -90,7 +90,7 @@ bool ConnmanTestMsg::ReceiveMsgFrom(CNode& node, CSerializedNetMsg&& ser_msg) co
9090
assert(queued);
9191
bool complete{false};
9292
while (true) {
93-
const auto& [to_send, _more, _msg_type] = node.m_transport->GetBytesToSend();
93+
const auto& [to_send, _more, _msg_type] = node.m_transport->GetBytesToSend(false);
9494
if (to_send.empty()) break;
9595
NodeReceiveMsgBytes(node, to_send, complete);
9696
node.m_transport->MarkBytesSent(to_send.size());

0 commit comments

Comments
 (0)