According to [BIP37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki#Extensions_to_existing_messages), `getdata` commands with a request for filtered blocks via type `MSG_FILTERED_BLOCK` in the `inv` submessage are only responded to if a filter is set:

> The getdata command is extended to allow a new type in the inv submessage. The type field can now be MSG_FILTERED_BLOCK (== 3) rather than MSG_BLOCK. **If no filter has been set on the connection, a request for filtered blocks is ignored.** If a filter has been set, a merkleblock message is returned for the requested block hash.

**Expected behavior**

When no BIP37 filter is set and a we request a filtered block, there should be no response from the node.

**Actual behavior**

When no BIP37 filter is set and a we request a filtered block, we get a `merkleblock` message in reply.

**To reproduce**

The behaviour can reproduced by adding the following lines to the functional test `p2p_filter.py` (introduced recently with PR https://github.com/bitcoin/bitcoin/pull/18334, commit https://github.com/bitcoin/bitcoin/pull/18334/commits/fa156999695ddaeb016d8320bee62f8d96679d55), that has already a callback implemented which requests filtered blocks as reply to `inv` messages:
```
    # add this lines after filtered P2P connection is added to the node, but before filter is set
    filter_node.merkleblock_received = False
    self.nodes[0].generatetoaddress(1, self.node[0].getnewaddress())[0]
    filter_node.sync_with_ping()
    assert not filter_node.merkleblock_received
```
The assertion fails, meaning that a merkleblock was indeed received.

**Possible solution**
Looking at the code in `net_processing.cpp`/`net.h`, it is quite obvious why this happens: the condition to answer to a filtered block request is just whether the (unique) pointer `pfrom->m_tx_relay->pfilter` is set, and this condition is always true. Initially the pointer is set in the constructor of the `struct TxRelay`, pointing to a default-constructed instance of `CBloomFilter`:
https://github.com/bitcoin/bitcoin/blob/d52ba21dfff99173abb927bc964ce7ceb711d789/src/net.h#L812
And after receiving a `filterclear` message it is also reset to an empty instance again:
https://github.com/bitcoin/bitcoin/blob/d52ba21dfff99173abb927bc964ce7ceb711d789/src/net_processing.cpp#L3201
In the following code, the boolean variable `sendMerkleBlock` will thus _always_ be set to true:
https://github.com/bitcoin/bitcoin/blob/d52ba21dfff99173abb927bc964ce7ceb711d789/src/net_processing.cpp#L1500-L1508

A simple solution would be to just set `pfilter` to `nullptr` initially and after receiving a `filterclear` message. The question is if its worth the change, as the described behaviour is IMHO not harmful at all (it just doesn't fit the description in the BIP37 document, which could be adapted?) and connection bloom filters are deprecated anway and [not recommended to be used due to privacy concerns](https://en.bitcoin.it/wiki/BIP37_privacy_problems). It is noteworthy though that possibly other parts in `net_processing.cpp` assume the test on `pfilter` means "a bloom filter has been set", and the fact that this always evaluates to `true` could possibly lead to some unintended behaviour. For example, in the following code part, where a `NodeEvictionCandidate` is created, its member `fBloomFilter` will always be set to true:
https://github.com/bitcoin/bitcoin/blob/d52ba21dfff99173abb927bc964ce7ceb711d789/src/net.cpp#L864-L875
} else {
                
                pto->nPingNonceSent = 0;
                connman->PushMessage(pto, msgMaker.Make(NetMsgType::PING));
            }
        }
        TRY_LOCK(cs_main, lockMain);
        if (!lockMain)
            return true;
        if (CheckIfBanned(pto)) return true;
        CNodeState &state = *State(pto->GetId());
        
        int64_t nNow = GetTimeMicros();
        auto current_time = GetTime<std::chrono::microseconds>();
        if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->m_next_local_addr_send < current_time) {
            AdvertiseLocal(pto);
            pto->m_next_local_addr_send = PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
        }
        
        
        
        if (pto->IsAddrRelayPeer() && pto->m_next_addr_send < current_time) {
            pto->m_next_addr_send = PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL);
            std::vector<CAddress> vAddr;
            vAddr.reserve(pto->vAddrToSend.size());
            assert(pto->m_addr_known);
            for (const CAddress& addr : pto->vAddrToSend)
            {
                if (!pto->m_addr_known->contains(addr.GetKey()))
                {