<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<CSerializeData> vSendMsg;
std::deque<CInv> vRecvGetData;
std::deque<CNetMessage> 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:

```cpp
bool CNode::ReceiveMsgBytes(Span<const uint8_t> msg_bytes, bool& complete)
{
    complete = false;
    const auto time = GetTime<std::chrono::microseconds>();
    LOCK(cs_vRecv);
    m_last_recv = std::chrono::duration_cast<std::chrono::seconds>(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.

```cpp
bool PeerManagerImpl::ProcessMessages(CNode* pfrom, std::atomic<bool>& 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.

```cpp
namespace NetMsg {
    template <typename... Args>
    CSerializedNetMsg Make(std::string msg_type, Args&&... args)
    {
        CSerializedNetMsg msg;
        msg.m_type = std::move(msg_type);
        VectorWriter{msg.data, 0, std::forward<Args>(args)...};
        return msg;
    }
} // namespace NetMsg

```

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

1. `CSerializedNetMsg` seems lighter.
2. `CNetMessage` has additional members (`m_time`, `m_message_size`, `m_raw_message_size`)
3. 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.

```cpp
    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](https://github.com/bitcoin/bitcoin/pull/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).


---

