/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 | 0 | { |
48 | 0 | SeedRandomStateForTest(SeedRand::ZEROS); |
49 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
50 | |
|
51 | 0 | auto& node{g_setup->m_node}; |
52 | 0 | auto& connman{static_cast<ConnmanTestMsg&>(*node.connman)}; |
53 | 0 | connman.Reset(); |
54 | 0 | auto& chainman{static_cast<TestChainstateManager&>(*node.chainman)}; |
55 | |
|
56 | 0 | FakeNodeClock clock_ctx{1610000000s}; // 2021-01-07, arbitrary |
57 | 0 | FakeSteadyClock steady_clock; |
58 | 0 | chainman.ResetIbd(); |
59 | | // Sometimes leave IBD: incoming TX processing (the broadcast-abort path) |
60 | | // returns early during IBD. |
61 | 0 | if (fuzzed_data_provider.ConsumeBool()) chainman.JumpOutOfIbd(); Branch (61:9): [True: 0, False: 0]
|
62 | | |
63 | | // Reset, so that dangling pointers can be detected by sanitizers. |
64 | 0 | node.banman.reset(); |
65 | 0 | node.addrman.reset(); |
66 | 0 | node.peerman.reset(); |
67 | 0 | node.addrman = std::make_unique<AddrMan>( |
68 | 0 | *node.netgroupman, /*deterministic=*/true, /*consistency_check_ratio=*/0); |
69 | 0 | node.peerman = PeerManager::make(connman, *node.addrman, |
70 | 0 | /*banman=*/nullptr, chainman, |
71 | 0 | *node.mempool, *node.warnings, |
72 | 0 | PeerManager::Options{ |
73 | 0 | .reconcile_txs = true, |
74 | 0 | .deterministic_rng = true, |
75 | 0 | }); |
76 | 0 | connman.SetMsgProc(node.peerman.get()); |
77 | 0 | 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 | 0 | const int num_txs{fuzzed_data_provider.ConsumeIntegralInRange(0, 3)}; |
82 | 0 | std::vector<CTransactionRef> seeded_txs; |
83 | 0 | for (int i = 0; i < num_txs; ++i) { Branch (83:21): [True: 0, False: 0]
|
84 | 0 | auto tx{MakeTransactionRef(ConsumeTransaction(fuzzed_data_provider, /*prevout_txids=*/std::nullopt))}; |
85 | 0 | (void)node.peerman->InitiateTxBroadcastPrivate(tx); |
86 | 0 | seeded_txs.push_back(tx); |
87 | 0 | } |
88 | |
|
89 | 0 | LOCK(NetEventsInterface::g_msgproc_mutex); |
90 | |
|
91 | 0 | static NodeId node_id{0}; |
92 | | // Create at least one PRIVATE_BROADCAST peer, optionally add others of random types. |
93 | 0 | std::vector<CNode*> peers; |
94 | |
|
95 | 0 | CNode* pb_node = new CNode( |
96 | 0 | /*id=*/node_id++, |
97 | 0 | /*sock=*/std::make_shared<FuzzedSock>(fuzzed_data_provider, steady_clock), |
98 | 0 | /*addrIn=*/ConsumeAddress(fuzzed_data_provider), |
99 | 0 | /*nKeyedNetGroupIn=*/0, |
100 | 0 | /*nLocalHostNonceIn=*/0, |
101 | 0 | /*addrBindIn=*/CService{}, |
102 | 0 | /*addrNameIn=*/"", |
103 | 0 | /*conn_type_in=*/ConnectionType::PRIVATE_BROADCAST, |
104 | 0 | /*inbound_onion=*/false, |
105 | 0 | /*network_key=*/0); |
106 | |
|
107 | 0 | peers.push_back(pb_node); |
108 | 0 | 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 | 0 | connman.SetCaptureMessages(true); |
112 | 0 | const auto CaptureMessageOrig = CaptureMessage; |
113 | 0 | const CAddress pb_addr = pb_node->addr; |
114 | 0 | std::optional<uint64_t> pb_ping_nonce; |
115 | 0 | CaptureMessage = [&](const CAddress& addr, const std::string& msg_type, |
116 | 0 | std::span<const unsigned char> data, bool is_incoming) { |
117 | 0 | if (is_incoming || addr != pb_addr) return; Branch (117:13): [True: 0, False: 0]
Branch (117:28): [True: 0, False: 0]
|
118 | 0 | if (msg_type == NetMsgType::PING) { Branch (118:13): [True: 0, False: 0]
|
119 | 0 | Assert(data.size() == sizeof(uint64_t)); |
120 | 0 | uint64_t nonce; |
121 | 0 | SpanReader{data} >> nonce; |
122 | 0 | pb_ping_nonce = nonce; |
123 | 0 | return; |
124 | 0 | } |
125 | 0 | if (msg_type == NetMsgType::VERSION) { Branch (125:13): [True: 0, False: 0]
|
126 | 0 | SpanReader ds{data}; |
127 | 0 | int32_t version; |
128 | 0 | uint64_t my_services, your_services, my_services_dup, nonce; |
129 | 0 | int64_t my_time; |
130 | 0 | CService your_addr, my_addr; |
131 | 0 | std::string user_agent; |
132 | 0 | int32_t height; |
133 | 0 | bool relay; |
134 | 0 | ds >> version >> my_services >> my_time >> |
135 | 0 | your_services >> CNetAddr::V1(your_addr) >> |
136 | 0 | my_services_dup >> CNetAddr::V1(my_addr) >> |
137 | 0 | nonce >> user_agent >> height >> relay; |
138 | 0 | Assert(version == WTXID_RELAY_VERSION); |
139 | 0 | Assert(my_services == NODE_NONE && my_services_dup == NODE_NONE); |
140 | 0 | Assert(my_time == 0); |
141 | 0 | Assert(your_services == NODE_NONE); |
142 | 0 | Assert(your_addr == CService{}); |
143 | 0 | Assert(user_agent == "/pynode:0.0.1/"); |
144 | 0 | Assert(height == 0); |
145 | 0 | Assert(!relay); |
146 | 0 | return; |
147 | 0 | } |
148 | 0 | if (msg_type != NetMsgType::INV) return; Branch (148:13): [True: 0, False: 0]
|
149 | 0 | SpanReader ds{data}; |
150 | 0 | std::vector<CInv> invs; |
151 | 0 | ds >> invs; |
152 | 0 | Assert(invs.size() == 1); |
153 | 0 | Assert(invs[0].IsMsgTx()); |
154 | 0 | }; |
155 | | |
156 | | // Complete handshake so PushPrivateBroadcastTx runs. |
157 | 0 | connman.Handshake( |
158 | 0 | /*node=*/*pb_node, |
159 | 0 | /*successfully_connected=*/true, |
160 | 0 | /*remote_services=*/ServiceFlags(NODE_NETWORK | NODE_WITNESS), |
161 | 0 | /*local_services=*/NODE_NONE, |
162 | 0 | /*version=*/PROTOCOL_VERSION, |
163 | 0 | /*relay_txs=*/true); |
164 | | |
165 | | // Optionally add extra peers of random connection types. |
166 | 0 | const int extra_peers{fuzzed_data_provider.ConsumeIntegralInRange(0, 2)}; |
167 | 0 | for (int i = 0; i < extra_peers; ++i) { Branch (167:21): [True: 0, False: 0]
|
168 | 0 | 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 | 0 | if (extra_peer->addr == pb_addr) continue; Branch (171:13): [True: 0, False: 0]
|
172 | 0 | peers.push_back(extra_peer.release()); |
173 | 0 | connman.AddTestNode(*peers.back()); |
174 | 0 | node.peerman->InitializeNode( |
175 | 0 | *peers.back(), |
176 | 0 | static_cast<ServiceFlags>(fuzzed_data_provider.ConsumeIntegral<uint64_t>())); |
177 | 0 | } |
178 | |
|
179 | 0 | LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) |
180 | 0 | { |
181 | | // Pick any random peer to test interleaved message handling. |
182 | 0 | CNode& p2p_node = *PickValue(fuzzed_data_provider, peers); |
183 | 0 | if (p2p_node.fDisconnect) continue; Branch (183:13): [True: 0, False: 0]
|
184 | | |
185 | 0 | clock_ctx += ConsumeDuration<std::chrono::seconds>(fuzzed_data_provider, 0s, 600s); |
186 | |
|
187 | 0 | std::optional<CSerializedNetMsg> net_msg; |
188 | 0 | CallOneOf( |
189 | 0 | fuzzed_data_provider, |
190 | 0 | [&] { |
191 | 0 | net_msg.emplace(); |
192 | 0 | net_msg->m_type = std::string{PickValue(fuzzed_data_provider, INBOUND_MSG_TYPES)}; |
193 | 0 | }, |
194 | 0 | [&] { |
195 | 0 | net_msg.emplace(); |
196 | 0 | net_msg->m_type = fuzzed_data_provider.ConsumeRandomLengthString(CMessageHeader::MESSAGE_TYPE_SIZE); |
197 | 0 | }, |
198 | 0 | [&] { |
199 | 0 | (void)node.peerman->InitiateTxBroadcastPrivate( |
200 | 0 | MakeTransactionRef(ConsumeTransaction(fuzzed_data_provider, /*prevout_txids=*/std::nullopt))); |
201 | 0 | }, |
202 | 0 | [&] { |
203 | | // Construct a valid GETDATA for a seeded tx to exercise the TX send path. |
204 | 0 | if (p2p_node.IsPrivateBroadcastConn() && Branch (204:21): [True: 0, False: 0]
|
205 | 0 | p2p_node.fSuccessfullyConnected && Branch (205:21): [True: 0, False: 0]
|
206 | 0 | !seeded_txs.empty()) { Branch (206:21): [True: 0, False: 0]
|
207 | 0 | const auto& tx{PickValue(fuzzed_data_provider, seeded_txs)}; |
208 | 0 | net_msg.emplace(NetMsg::Make( |
209 | 0 | NetMsgType::GETDATA, |
210 | 0 | std::vector<CInv>{{MSG_TX, tx->GetHash().ToUint256()}})); |
211 | 0 | } |
212 | 0 | }, |
213 | 0 | [&] { |
214 | | // Confirm reception of the pushed TX with a PONG matching the captured PING nonce. |
215 | 0 | if (&p2p_node == pb_node && pb_ping_nonce) { Branch (215:21): [True: 0, False: 0]
Branch (215:45): [True: 0, False: 0]
|
216 | 0 | net_msg.emplace(NetMsg::Make(NetMsgType::PONG, *pb_ping_nonce)); |
217 | 0 | } |
218 | 0 | }, |
219 | 0 | [&] { |
220 | | // Echo a seeded tx back from a non-private-broadcast peer to exercise |
221 | | // the received-from-network broadcast-abort path. |
222 | 0 | if (!p2p_node.IsPrivateBroadcastConn() && Branch (222:21): [True: 0, False: 0]
|
223 | 0 | p2p_node.fSuccessfullyConnected && Branch (223:21): [True: 0, False: 0]
|
224 | 0 | !seeded_txs.empty()) { Branch (224:21): [True: 0, False: 0]
|
225 | 0 | const auto& tx{PickValue(fuzzed_data_provider, seeded_txs)}; |
226 | 0 | net_msg.emplace(NetMsg::Make(NetMsgType::TX, TX_WITH_WITNESS(*tx))); |
227 | 0 | } |
228 | 0 | }); |
229 | |
|
230 | 0 | if (net_msg) { Branch (230:13): [True: 0, False: 0]
|
231 | 0 | if (net_msg->data.empty()) { Branch (231:17): [True: 0, False: 0]
|
232 | 0 | net_msg->data = ConsumeRandomLengthByteVector(fuzzed_data_provider, MAX_PROTOCOL_MESSAGE_LENGTH); |
233 | 0 | } |
234 | 0 | connman.FlushSendBuffer(p2p_node); |
235 | | |
236 | | // ConsumeTransaction() can produce messages larger than the |
237 | | // maximum payload accepted by the P2P transport. |
238 | 0 | if (net_msg->data.size() > MAX_PROTOCOL_MESSAGE_LENGTH) continue; Branch (238:17): [True: 0, False: 0]
|
239 | | |
240 | 0 | (void)connman.ReceiveMsgFrom(p2p_node, std::move(*net_msg)); |
241 | |
|
242 | 0 | bool more_work{true}; |
243 | 0 | while (more_work) { Branch (243:20): [True: 0, False: 0]
|
244 | 0 | p2p_node.fPauseSend = false; |
245 | 0 | try { |
246 | 0 | more_work = connman.ProcessMessagesOnce(p2p_node); |
247 | 0 | } catch (const std::ios_base::failure&) { |
248 | 0 | } |
249 | 0 | node.peerman->SendMessages(p2p_node); |
250 | 0 | } |
251 | 0 | } |
252 | 0 | } |
253 | | |
254 | 0 | CaptureMessage = CaptureMessageOrig; |
255 | 0 | connman.SetCaptureMessages(false); |
256 | |
|
257 | 0 | node.connman->StopNodes(); |
258 | 0 | } |