<p>The question is from 2015; some things have changed since then.</p>
<p><code>CNetMessage</code> is a transport protocol agnostic message container. It contains the received message data (<code>DataStream</code>), time of message receipt, payload size, and other information. It is used to decompose messages from the network. See the following code:</p>
<pre class="lang-cpp prettyprint-override"><code>bool CNode::ReceiveMsgBytes(Span&lt;const uint8_t&gt; msg_bytes, bool&amp; complete)
{
    complete = false;
    const auto time = GetTime&lt;std::chrono::microseconds&gt;();
    LOCK(cs_vRecv);
    m_last_recv = std::chrono::duration_cast&lt;std::chrono::seconds&gt;(time);
    nRecvBytes += msg_bytes.size();
    while (msg_bytes.size() &gt; 0) {
        // absorb network data
        if (!m_transport-&gt;ReceivedBytes(msg_bytes)) {
            // Serious transport problem, disconnect from the peer.
            return false;
        }

        if (m_transport-&gt;ReceivedMessageComplete()) {
            // decompose a transport agnostic CNetMessage from the deserializer
            bool reject_message{false};
            CNetMessage msg = m_transport-&gt;GetReceivedMessage(time, reject_message);
            if (reject_message) {
                // Message deserialization failed. Drop the message but don't disconnect the peer.
                // store the size of the corrupt message
                mapRecvBytesPerMsgType.at(NET_MESSAGE_TYPE_OTHER) += msg.m_raw_message_size;
                continue;
            }

            // Store received bytes per message type.
            // To prevent a memory DOS, only allow known message types.
            auto i = mapRecvBytesPerMsgType.find(msg.m_type);
            if (i == mapRecvBytesPerMsgType.end()) {
                i = mapRecvBytesPerMsgType.find(NET_MESSAGE_TYPE_OTHER);
            }
            assert(i != mapRecvBytesPerMsgType.end());
            i-&gt;second += msg.m_raw_message_size;

            // push the message to the process queue,
            vRecvMsg.push_back(std::move(msg));

            complete = true;
        }
    }

    return true;
}
</code></pre>
<p>After receiving the message, we push it to <code>vRecvMsg</code> and then put it in a queue to be processed. In <code>ProcessMessage</code>, we get the message data (<code>DataStream</code>) and process it according to the message type.</p>
<pre class="lang-cpp prettyprint-override"><code>bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic&lt;bool&gt;&amp; interruptMsgProc)
{
    AssertLockHeld(g_msgproc_mutex);

    PeerRef peer = GetPeerRef(pfrom-&gt;GetId());
    if (peer == nullptr) return false;

    {
        LOCK(peer-&gt;m_getdata_requests_mutex);
        if (!peer-&gt;m_getdata_requests.empty()) {
            ProcessGetData(*pfrom, *peer, interruptMsgProc);
        }
    }

    const bool processed_orphan = ProcessOrphanTx(*peer);

    if (pfrom-&gt;fDisconnect)
        return false;

    if (processed_orphan) return true;

    // this maintains the order of responses
    // and prevents m_getdata_requests to grow unbounded
    {
        LOCK(peer-&gt;m_getdata_requests_mutex);
        if (!peer-&gt;m_getdata_requests.empty()) return true;
    }

    // Don't bother if send buffer is too full to respond anyway
    if (pfrom-&gt;fPauseSend) return false;

    auto poll_result{pfrom-&gt;PollMessage()};
    if (!poll_result) {
        // No message to process
        return false;
    }

    CNetMessage&amp; msg{poll_result-&gt;first};
    bool fMoreWork = poll_result-&gt;second;

    TRACE6(net, inbound_message,
        pfrom-&gt;GetId(),
        pfrom-&gt;m_addr_name.c_str(),
        pfrom-&gt;ConnectionTypeAsString().c_str(),
        msg.m_type.c_str(),
        msg.m_recv.size(),
        msg.m_recv.data()
    );

    if (m_opts.capture_messages) {
        CaptureMessage(pfrom-&gt;addr, msg.m_type, MakeUCharSpan(msg.m_recv), /*is_incoming=*/true);
    }

    try {
        ProcessMessage(*pfrom, msg.m_type, msg.m_recv, msg.m_time, interruptMsgProc);
        if (interruptMsgProc) return false;
        {
            LOCK(peer-&gt;m_getdata_requests_mutex);
            if (!peer-&gt;m_getdata_requests.empty()) fMoreWork = true;
        }
        // Does this peer has an orphan ready to reconsider?
        // (Note: we may have provided a parent for an orphan provided
        //  by another peer that was already processed; in that case,
        //  the extra work may not be noticed, possibly resulting in an
        //  unnecessary 100ms delay)
        if (m_orphanage.HaveTxToReconsider(peer-&gt;m_id)) fMoreWork = true;
    } catch (const std::exception&amp; e) {
        LogPrint(BCLog::NET, &quot;%s(%s, %u bytes): Exception '%s' (%s) caught\n&quot;, __func__, SanitizeString(msg.m_type), msg.m_message_size, e.what(), typeid(e).name());
    } catch (...) {
        LogPrint(BCLog::NET, &quot;%s(%s, %u bytes): Unknown exception caught\n&quot;, __func__, SanitizeString(msg.m_type), msg.m_message_size);
    }

    return fMoreWork;
}
</code></pre>
<p>In the case of <code>vSendMsg</code>, this is a vector of <code>CSerializedNetMsg</code>. <code>CSerializedNetMsg</code> structure is simple. It holds the message data (a vector of <code>unsigned char</code>) and its type. As its name indicates, it represents the serialized message. You can notice in the codebase that <code>NetMsg::Make</code> is commonly used to construct it. This function accepts a string parameter representing the message type and any other parameter except that will be used to compose the msg data.</p>
<pre class="lang-cpp prettyprint-override"><code>namespace NetMsg {
    template &lt;typename... Args&gt;
    CSerializedNetMsg Make(std::string msg_type, Args&amp;&amp;... args)
    {
        CSerializedNetMsg msg;
        msg.m_type = std::move(msg_type);
        VectorWriter{msg.data, 0, std::forward&lt;Args&gt;(args)...};
        return msg;
    }
} // namespace NetMsg
</code></pre>
<p>Now, what I understand about the difference between <code>CSerializedNetMsg</code> and <code>CNetMessage</code> is:</p>
<ol>
<li><code>CSerializedNetMsg</code> seems lighter.</li>
<li><code>CNetMessage</code> has additional members (<code>m_time</code>, <code>m_message_size</code>, <code>m_raw_message_size</code>)</li>
<li>Only one <code>CNetMessage</code> object for the same message will exist. <code>CSerializedNetMsg</code> has a specific method for copies.</li>
</ol>
<p>Although they seem similar, they have specific characteristics for their purposes. For example, it is important to deal with received messages with <code>CNetMessage</code>, among other reasons, to know exactly the size of the data we receive to track the process queue size. <code>Copy</code> from <code>CSerializedNetMsg</code> can be useful when sending the same message for more than one node.</p>
<pre class="lang-cpp prettyprint-override"><code>    m_connman.ForEachNode([this, pindex, &amp;lazy_ser, &amp;hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
        AssertLockHeld(::cs_main);

        if (pnode-&gt;GetCommonVersion() &lt; INVALID_CB_NO_BAN_VERSION || pnode-&gt;fDisconnect)
            return;
        ProcessBlockAvailability(pnode-&gt;GetId());
        CNodeState &amp;state = *State(pnode-&gt;GetId());
        // If the peer has, or we announced to them the previous block already,
        // but we don't think they have this one, go ahead and announce it
        if (state.m_requested_hb_cmpctblocks &amp;&amp; !PeerHasHeader(&amp;state, pindex) &amp;&amp; PeerHasHeader(&amp;state, pindex-&gt;pprev)) {

            LogPrint(BCLog::NET, &quot;%s sending header-and-ids %s to peer=%d\n&quot;, &quot;PeerManager::NewPoWValidBlock&quot;,
                    hashBlock.ToString(), pnode-&gt;GetId());

            const CSerializedNetMsg&amp; ser_cmpctblock{lazy_ser.get()};
            PushMessage(*pnode, ser_cmpctblock.Copy());
            state.pindexBestHeaderSent = pindex;
        }
    });
</code></pre>
<hr />
<p>About <code>vRecvGetData</code>, it was removed in <a href="https://github.com/bitcoin/bitcoin/pull/19911" rel="noreferrer">#19911</a>. Now we have <code>m_getdata_requests</code> as a member of <code>Peer</code>, when a node receives a <code>GETDATA</code> message, it stores the INVs into <code>m_getdata_requests</code>. It is used to control what a node requested of us (e.g., a transaction).</p>

CC BY-SA 4.0
2024-02-07T15:17:59.380Z
2024-02-07T15:17:59.380Z
brunoerg
<p>Can any expert help me understand the need for the following members of <code>CNode</code> in <code>src/net.h</code></p>

<pre><code>std::deque&lt;CSerializeData&gt; vSendMsg;
std::deque&lt;CInv&gt; vRecvGetData;
std::deque&lt;CNetMessage&gt; vRecvMsg;
</code></pre>

<p>If I understand the source code correct, vRecvMsg is for storing the input messages and vSendMsg is a queue for output messages.</p>

<ul>
<li>Why do we have different form of queues [i.e. <code>CNetMessage</code> and <code>CSerializeData</code>] for input and output?</li>
<li>What is the purpose of <code>vRevGetData</code>? Can you please help me understand also the concept behind the usage of this ?</li>
</ul>

<bitcoin-core-development><p2p>
A question on CNode class data members
Paarth
# A question on CNode class data members

(Asked by: username_0 on 2015-12-22)

Can any expert help me understand the need for the following members of CNode in src/net.h
std::deque vSendMsg;
std::deque vRecvGetData;
std::deque vRecvMsg;

If I understand the source code correct, vRecvMsg is for storing the input messages and vSendMsg is a queue for output messages.

Why do we have different form of queues [i.e. CNetMessage and CSerializeData] for input and output?
What is the purpose of vRevGetData? Can you please help me understand also the concept behind the usage of this ?

**Tags:** bitcoin-core-development, p2p


---

## 1 answers

### Answer by username_1 on 2024-02-07. Score: 6

The question is from 2015; some things have changed since then.
CNetMessage is a transport protocol agnostic message container. It contains the received message data (DataStream), time of message receipt, payload size, and other information. It is used to decompose messages from the network. See the following code:
bool CNode::ReceiveMsgBytes(Span msg_bytes, bool& complete)
{
    complete = false;
    const auto time = GetTime();
    LOCK(cs_vRecv);
    m_last_recv = std::chrono::duration_cast(time);
    nRecvBytes += msg_bytes.size();
    while (msg_bytes.size() > 0) {
        // absorb network data
        if (!m_transport->ReceivedBytes(msg_bytes)) {
            // Serious transport problem, disconnect from the peer.
            return false;
        }

        if (m_transport->ReceivedMessageComplete()) {
            // decompose a transport agnostic CNetMessage from the deserializer
            bool reject_message{false};
            CNetMessage msg = m_transport->GetReceivedMessage(time, reject_message);
            if (reject_message) {
                // Message deserialization failed. Drop the message but don't disconnect the peer.
                // store the size of the corrupt message
                mapRecvBytesPerMsgType.at(NET_MESSAGE_TYPE_OTHER) += msg.m_raw_message_size;
                continue;
            }

            // Store received bytes per message type.
            // To prevent a memory DOS, only allow known message types.
            auto i = mapRecvBytesPerMsgType.find(msg.m_type);
            if (i == mapRecvBytesPerMsgType.end()) {
                i = mapRecvBytesPerMsgType.find(NET_MESSAGE_TYPE_OTHER);
            }
            assert(i != mapRecvBytesPerMsgType.end());
            i->second += msg.m_raw_message_size;

            // push the message to the process queue,
            vRecvMsg.push_back(std::move(msg));

            complete = true;
        }
    }

    return true;
}

After receiving the message, we push it to vRecvMsg and then put it in a queue to be processed. In ProcessMessage, we get the message data (DataStream) and process it according to the message type.
bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic& interruptMsgProc)
{
    AssertLockHeld(g_msgproc_mutex);

    PeerRef peer = GetPeerRef(pfrom->GetId());
    if (peer == nullptr) return false;

    {
        LOCK(peer->m_getdata_requests_mutex);
        if (!peer->m_getdata_requests.empty()) {
            ProcessGetData(*pfrom, *peer, interruptMsgProc);
        }
    }

    const bool processed_orphan = ProcessOrphanTx(*peer);

    if (pfrom->fDisconnect)
        return false;

    if (processed_orphan) return true;

    // this maintains the order of responses
    // and prevents m_getdata_requests to grow unbounded
    {
        LOCK(peer->m_getdata_requests_mutex);
        if (!peer->m_getdata_requests.empty()) return true;
    }

    // Don't bother if send buffer is too full to respond anyway
    if (pfrom->fPauseSend) return false;

    auto poll_result{pfrom->PollMessage()};
    if (!poll_result) {
        // No message to process
        return false;
    }

    CNetMessage& msg{poll_result->first};
    bool fMoreWork = poll_result->second;

    TRACE6(net, inbound_message,
        pfrom->GetId(),
        pfrom->m_addr_name.c_str(),
        pfrom->ConnectionTypeAsString().c_str(),
        msg.m_type.c_str(),
        msg.m_recv.size(),
        msg.m_recv.data()
    );

    if (m_opts.capture_messages) {
        CaptureMessage(pfrom->addr, msg.m_type, MakeUCharSpan(msg.m_recv), /*is_incoming=*/true);
    }

    try {
        ProcessMessage(*pfrom, msg.m_type, msg.m_recv, msg.m_time, interruptMsgProc);
        if (interruptMsgProc) return false;
        {
            LOCK(peer->m_getdata_requests_mutex);
            if (!peer->m_getdata_requests.empty()) fMoreWork = true;
        }
        // Does this peer has an orphan ready to reconsider?
        // (Note: we may have provided a parent for an orphan provided
        //  by another peer that was already processed; in that case,
        //  the extra work may not be noticed, possibly resulting in an
        //  unnecessary 100ms delay)
        if (m_orphanage.HaveTxToReconsider(peer->m_id)) fMoreWork = true;
    } catch (const std::exception& e) {
        LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' (%s) caught\n", __func__, SanitizeString(msg.m_type), msg.m_message_size, e.what(), typeid(e).name());
    } catch (...) {
        LogPrint(BCLog::NET, "%s(%s, %u bytes): Unknown exception caught\n", __func__, SanitizeString(msg.m_type), msg.m_message_size);
    }

    return fMoreWork;
}

In the case of vSendMsg, this is a vector of CSerializedNetMsg. CSerializedNetMsg structure is simple. It holds the message data (a vector of unsigned char) and its type. As its name indicates, it represents the serialized message. You can notice in the codebase that NetMsg::Make is commonly used to construct it. This function accepts a string parameter representing the message type and any other parameter except that will be used to compose the msg data.
namespace NetMsg {
    template 
    CSerializedNetMsg Make(std::string msg_type, Args&&... args)
    {
        CSerializedNetMsg msg;
        msg.m_type = std::move(msg_type);
        VectorWriter{msg.data, 0, std::forward(args)...};
        return msg;
    }
} // namespace NetMsg

Now, what I understand about the difference between CSerializedNetMsg and CNetMessage is:

CSerializedNetMsg seems lighter.
CNetMessage has additional members (m_time, m_message_size, m_raw_message_size)
Only one CNetMessage object for the same message will exist. CSerializedNetMsg has a specific method for copies.

Although they seem similar, they have specific characteristics for their purposes. For example, it is important to deal with received messages with CNetMessage, among other reasons, to know exactly the size of the data we receive to track the process queue size. Copy from CSerializedNetMsg can be useful when sending the same message for more than one node.
    m_connman.ForEachNode([this, pindex, &lazy_ser, &hashBlock](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
        AssertLockHeld(::cs_main);

        if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect)
            return;
        ProcessBlockAvailability(pnode->GetId());
        CNodeState &state = *State(pnode->GetId());
        // If the peer has, or we announced to them the previous block already,
        // but we don't think they have this one, go ahead and announce it
        if (state.m_requested_hb_cmpctblocks && !PeerHasHeader(&state, pindex) && PeerHasHeader(&state, pindex->pprev)) {

            LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n", "PeerManager::NewPoWValidBlock",
                    hashBlock.ToString(), pnode->GetId());

            const CSerializedNetMsg& ser_cmpctblock{lazy_ser.get()};
            PushMessage(*pnode, ser_cmpctblock.Copy());
            state.pindexBestHeaderSent = pindex;
        }
    });


About vRecvGetData, it was removed in #19911. Now we have m_getdata_requests as a member of Peer, when a node receives a GETDATA message, it stores the INVs into m_getdata_requests. It is used to control what a node requested of us (e.g., a transaction).


---

