Coverage Report

Created: 2026-08-25 19:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/p2p_private_broadcast.cpp
Line
Count
Source
1
// Copyright (c) The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
#include <banman.h>
6
#include <net.h>
7
#include <net_processing.h>
8
#include <protocol.h>
9
#include <sync.h>
10
#include <test/fuzz/FuzzedDataProvider.h>
11
#include <test/fuzz/fuzz.h>
12
#include <test/fuzz/util.h>
13
#include <test/fuzz/util/net.h>
14
#include <test/util/net.h>
15
#include <test/util/setup_common.h>
16
#include <test/util/time.h>
17
#include <test/util/validation.h>
18
#include <util/time.h>
19
#include <validationinterface.h>
20
21
#include <array>
22
#include <ios>
23
#include <memory>
24
#include <vector>
25
26
namespace {
27
TestingSetup* g_setup;
28
29
void initialize()
30
0
{
31
0
    static const auto testing_setup = MakeNoLogFileContext<TestingSetup>(
32
0
        /*chain_type=*/ChainType::REGTEST);
33
0
    g_setup = testing_setup.get();
34
0
}
35
36
// Inbound message types with private broadcast specific handling.
37
// Used as the guided path in the CallOneOf() below.
38
constexpr std::array INBOUND_MSG_TYPES{
39
    NetMsgType::VERSION,
40
    NetMsgType::VERACK,
41
    NetMsgType::GETDATA,
42
    NetMsgType::PONG,
43
};
44
} // namespace
45
46
FUZZ_TARGET(p2p_private_broadcast, .init = ::initialize)
47
3.11k
{
48
3.11k
    SeedRandomStateForTest(SeedRand::ZEROS);
49
3.11k
    FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
50
51
3.11k
    auto& node{g_setup->m_node};
52
3.11k
    auto& connman{static_cast<ConnmanTestMsg&>(*node.connman)};
53
3.11k
    connman.Reset();
54
3.11k
    auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)};
55
56
3.11k
    FakeNodeClock clock_ctx{1610000000s}; // 2021-01-07, arbitrary
57
3.11k
    FakeSteadyClock steady_clock;
58
3.11k
    chainman.ResetIbd();
59
    // Sometimes leave IBD: incoming TX processing (the broadcast-abort path)
60
    // returns early during IBD.
61
3.11k
    if (fuzzed_data_provider.ConsumeBool()) chainman.JumpOutOfIbd();
  Branch (61:9): [True: 2.57k, False: 543]
62
63
    // Reset, so that dangling pointers can be detected by sanitizers.
64
3.11k
    node.banman.reset();
65
3.11k
    node.addrman.reset();
66
3.11k
    node.peerman.reset();
67
3.11k
    node.addrman = std::make_unique<AddrMan>(
68
3.11k
        *node.netgroupman, /*deterministic=*/true, /*consistency_check_ratio=*/0);
69
3.11k
    node.peerman = PeerManager::make(connman, *node.addrman,
70
3.11k
                                     /*banman=*/nullptr, chainman,
71
3.11k
                                     *node.mempool, *node.warnings,
72
3.11k
                                     PeerManager::Options{
73
3.11k
                                         .reconcile_txs = true,
74
3.11k
                                         .deterministic_rng = true,
75
3.11k
                                     });
76
3.11k
    connman.SetMsgProc(node.peerman.get());
77
3.11k
    connman.SetAddrman(*node.addrman);
78
79
    // Seed with 0-3 transactions to test multiple pending broadcasts; zero exercises
80
    // the connected-in-vain disconnect in PushPrivateBroadcastTx().
81
3.11k
    const int num_txs{fuzzed_data_provider.ConsumeIntegralInRange(0, 3)};
82
3.11k
    std::vector<CTransactionRef> seeded_txs;
83
6.70k
    for (int i = 0; i < num_txs; ++i) {
  Branch (83:21): [True: 3.59k, False: 3.11k]
84
3.59k
        auto tx{MakeTransactionRef(ConsumeTransaction(fuzzed_data_provider, /*prevout_txids=*/std::nullopt))};
85
3.59k
        (void)node.peerman->InitiateTxBroadcastPrivate(tx);
86
3.59k
        seeded_txs.push_back(tx);
87
3.59k
    }
88
89
3.11k
    LOCK(NetEventsInterface::g_msgproc_mutex);
90
91
3.11k
    static NodeId node_id{0};
92
    // Create at least one PRIVATE_BROADCAST peer, optionally add others of random types.
93
3.11k
    std::vector<CNode*> peers;
94
95
3.11k
    CNode* pb_node = new CNode(
96
3.11k
        /*id=*/node_id++,
97
3.11k
        /*sock=*/std::make_shared<FuzzedSock>(fuzzed_data_provider, steady_clock),
98
3.11k
        /*addrIn=*/ConsumeAddress(fuzzed_data_provider),
99
3.11k
        /*nKeyedNetGroupIn=*/0,
100
3.11k
        /*nLocalHostNonceIn=*/0,
101
3.11k
        /*addrBindIn=*/CService{},
102
3.11k
        /*addrNameIn=*/"",
103
3.11k
        /*conn_type_in=*/ConnectionType::PRIVATE_BROADCAST,
104
3.11k
        /*inbound_onion=*/false,
105
3.11k
        /*network_key=*/0);
106
107
3.11k
    peers.push_back(pb_node);
108
3.11k
    connman.AddTestNode(*pb_node);
109
    // Capture outbound messages to verify if well formed (and to learn the PING
110
    // nonce), before SocketSendData drains vSendMsg.
111
3.11k
    connman.SetCaptureMessages(true);
112
3.11k
    const auto CaptureMessageOrig = CaptureMessage;
113
3.11k
    const CAddress pb_addr = pb_node->addr;
114
3.11k
    std::optional<uint64_t> pb_ping_nonce;
115
3.11k
    CaptureMessage = [&](const CAddress& addr, const std::string& msg_type,
116
38.4k
                         std::span<const unsigned char> data, bool is_incoming) {
117
38.4k
        if (is_incoming || addr != pb_addr) return;
  Branch (117:13): [True: 0, False: 38.4k]
  Branch (117:28): [True: 30.5k, False: 7.95k]
118
7.95k
        if (msg_type == NetMsgType::PING) {
  Branch (118:13): [True: 316, False: 7.63k]
119
316
            Assert(data.size() == sizeof(uint64_t));
120
316
            uint64_t nonce;
121
316
            SpanReader{data} >> nonce;
122
316
            pb_ping_nonce = nonce;
123
316
            return;
124
316
        }
125
7.63k
        if (msg_type == NetMsgType::VERSION) {
  Branch (125:13): [True: 3.11k, False: 4.52k]
126
3.11k
            SpanReader ds{data};
127
3.11k
            int32_t version;
128
3.11k
            uint64_t my_services, your_services, my_services_dup, nonce;
129
3.11k
            int64_t my_time;
130
3.11k
            CService your_addr, my_addr;
131
3.11k
            std::string user_agent;
132
3.11k
            int32_t height;
133
3.11k
            bool relay;
134
3.11k
            ds >> version >> my_services >> my_time >>
135
3.11k
                your_services >> CNetAddr::V1(your_addr) >>
136
3.11k
                my_services_dup >> CNetAddr::V1(my_addr) >>
137
3.11k
                nonce >> user_agent >> height >> relay;
138
3.11k
            Assert(version == WTXID_RELAY_VERSION);
139
3.11k
            Assert(my_services == NODE_NONE && my_services_dup == NODE_NONE);
140
3.11k
            Assert(my_time == 0);
141
3.11k
            Assert(your_services == NODE_NONE);
142
3.11k
            Assert(your_addr == CService{});
143
3.11k
            Assert(user_agent == "/pynode:0.0.1/");
144
3.11k
            Assert(height == 0);
145
3.11k
            Assert(!relay);
146
3.11k
            return;
147
3.11k
        }
148
4.52k
        if (msg_type != NetMsgType::INV) return;
  Branch (148:13): [True: 2.96k, False: 1.55k]
149
1.55k
        SpanReader ds{data};
150
1.55k
        std::vector<CInv> invs;
151
1.55k
        ds >> invs;
152
1.55k
        Assert(invs.size() == 1);
153
1.55k
        Assert(invs[0].IsMsgTx());
154
1.55k
    };
155
156
    // Complete handshake so PushPrivateBroadcastTx runs.
157
3.11k
    connman.Handshake(
158
3.11k
        /*node=*/*pb_node,
159
3.11k
        /*successfully_connected=*/true,
160
3.11k
        /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS),
161
3.11k
        /*local_services=*/NODE_NONE,
162
3.11k
        /*version=*/PROTOCOL_VERSION,
163
3.11k
        /*relay_txs=*/true);
164
165
    // Optionally add extra peers of random connection types.
166
3.11k
    const int extra_peers{fuzzed_data_provider.ConsumeIntegralInRange(0, 2)};
167
6.07k
    for (int i = 0; i < extra_peers; ++i) {
  Branch (167:21): [True: 2.96k, False: 3.11k]
168
2.96k
        auto extra_peer{ConsumeNodeAsUniquePtr(fuzzed_data_provider, steady_clock, node_id++)};
169
        // An address collision would match the capture hook's filter and fail
170
        // its assertions on this peer's (legitimate) other-typed messages.
171
2.96k
        if (extra_peer->addr == pb_addr) continue;
  Branch (171:13): [True: 4, False: 2.95k]
172
2.95k
        peers.push_back(extra_peer.release());
173
2.95k
        connman.AddTestNode(*peers.back());
174
2.95k
        node.peerman->InitializeNode(
175
2.95k
            *peers.back(),
176
2.95k
            static_cast<ServiceFlags>(fuzzed_data_provider.ConsumeIntegral<uint64_t>()));
177
2.95k
    }
178
179
3.11k
    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
180
57.8k
    {
181
        // Pick any random peer to test interleaved message handling.
182
57.8k
        CNode& p2p_node = *PickValue(fuzzed_data_provider, peers);
183
57.8k
        if (p2p_node.fDisconnect) continue;
  Branch (183:13): [True: 2.64k, False: 55.2k]
184
185
55.2k
        clock_ctx += ConsumeDuration<std::chrono::seconds>(fuzzed_data_provider, 0s, 600s);
186
187
55.2k
        std::optional<CSerializedNetMsg> net_msg;
188
55.2k
        CallOneOf(
189
55.2k
            fuzzed_data_provider,
190
55.2k
            [&] {
191
6.31k
                net_msg.emplace();
192
6.31k
                net_msg->m_type = std::string{PickValue(fuzzed_data_provider, INBOUND_MSG_TYPES)};
193
6.31k
            },
194
55.2k
            [&] {
195
16.9k
                net_msg.emplace();
196
16.9k
                net_msg->m_type = fuzzed_data_provider.ConsumeRandomLengthString(CMessageHeader::MESSAGE_TYPE_SIZE);
197
16.9k
            },
198
55.2k
            [&] {
199
654
                (void)node.peerman->InitiateTxBroadcastPrivate(
200
654
                    MakeTransactionRef(ConsumeTransaction(fuzzed_data_provider, /*prevout_txids=*/std::nullopt)));
201
654
            },
202
55.2k
            [&] {
203
                // Construct a valid GETDATA for a seeded tx to exercise the TX send path.
204
13.8k
                if (p2p_node.IsPrivateBroadcastConn() &&
  Branch (204:21): [True: 874, False: 12.9k]
205
13.8k
                    p2p_node.fSuccessfullyConnected &&
  Branch (205:21): [True: 351, False: 523]
206
13.8k
                    !seeded_txs.empty()) {
  Branch (206:21): [True: 351, False: 0]
207
351
                    const auto& tx{PickValue(fuzzed_data_provider, seeded_txs)};
208
351
                    net_msg.emplace(NetMsg::Make(
209
351
                        NetMsgType::GETDATA,
210
351
                        std::vector<CInv>{{MSG_TX, tx->GetHash().ToUint256()}}));
211
351
                }
212
13.8k
            },
213
55.2k
            [&] {
214
                // Confirm reception of the pushed TX with a PONG matching the captured PING nonce.
215
285
                if (&p2p_node == pb_node && pb_ping_nonce) {
  Branch (215:21): [True: 78, False: 207]
  Branch (215:45): [True: 9, False: 69]
216
9
                    net_msg.emplace(NetMsg::Make(NetMsgType::PONG, *pb_ping_nonce));
217
9
                }
218
285
            },
219
55.2k
            [&] {
220
                // Echo a seeded tx back from a non-private-broadcast peer to exercise
221
                // the received-from-network broadcast-abort path.
222
17.2k
                if (!p2p_node.IsPrivateBroadcastConn() &&
  Branch (222:21): [True: 16.6k, False: 566]
223
17.2k
                    p2p_node.fSuccessfullyConnected &&
  Branch (223:21): [True: 13.7k, False: 2.91k]
224
17.2k
                    !seeded_txs.empty()) {
  Branch (224:21): [True: 10.4k, False: 3.32k]
225
10.4k
                    const auto& tx{PickValue(fuzzed_data_provider, seeded_txs)};
226
10.4k
                    net_msg.emplace(NetMsg::Make(NetMsgType::TX, TX_WITH_WITNESS(*tx)));
227
10.4k
                }
228
17.2k
            });
229
230
55.2k
        if (net_msg) {
  Branch (230:13): [True: 34.0k, False: 21.2k]
231
34.0k
            if (net_msg->data.empty()) {
  Branch (231:17): [True: 23.2k, False: 10.7k]
232
23.2k
                net_msg->data = ConsumeRandomLengthByteVector(fuzzed_data_provider, MAX_PROTOCOL_MESSAGE_LENGTH);
233
23.2k
            }
234
34.0k
            connman.FlushSendBuffer(p2p_node);
235
236
            // ConsumeTransaction() can produce messages larger than the
237
            // maximum payload accepted by the P2P transport.
238
34.0k
            if (net_msg->data.size() > MAX_PROTOCOL_MESSAGE_LENGTH) continue;
  Branch (238:17): [True: 0, False: 34.0k]
239
240
34.0k
            (void)connman.ReceiveMsgFrom(p2p_node, std::move(*net_msg));
241
242
34.0k
            bool more_work{true};
243
70.0k
            while (more_work) {
  Branch (243:20): [True: 36.0k, False: 34.0k]
244
36.0k
                p2p_node.fPauseSend = false;
245
36.0k
                try {
246
36.0k
                    more_work = connman.ProcessMessagesOnce(p2p_node);
247
36.0k
                } catch (const std::ios_base::failure&) {
248
0
                }
249
36.0k
                node.peerman->SendMessages(p2p_node);
250
36.0k
            }
251
34.0k
        }
252
55.2k
    }
253
254
3.11k
    CaptureMessage = CaptureMessageOrig;
255
3.11k
    connman.SetCaptureMessages(false);
256
257
3.11k
    node.connman->StopNodes();
258
3.11k
}