aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/rpc/net.cpp49
-rw-r--r--src/test/fuzz/rpc.cpp1
-rwxr-xr-xtest/functional/p2p_net_deadlock.py37
-rwxr-xr-xtest/functional/rpc_net.py32
-rwxr-xr-xtest/functional/test_runner.py1
6 files changed, 121 insertions, 0 deletions
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 2908c37c1f..0ee3f27761 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -299,6 +299,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnodeaddresses", 0, "count"},
{ "addpeeraddress", 1, "port"},
{ "addpeeraddress", 2, "tried"},
+ { "sendmsgtopeer", 0, "peer_id" },
{ "stop", 0, "wait" },
};
// clang-format on
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index a2a46ef32f..f7b6c68344 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -968,6 +968,54 @@ static RPCHelpMan addpeeraddress()
};
}
+static RPCHelpMan sendmsgtopeer()
+{
+ return RPCHelpMan{
+ "sendmsgtopeer",
+ "Send a p2p message to a peer specified by id.\n"
+ "The message type and body must be provided, the message header will be generated.\n"
+ "This RPC is for testing only.",
+ {
+ {"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to send the message to."},
+ {"msg_type", RPCArg::Type::STR, RPCArg::Optional::NO, strprintf("The message type (maximum length %i)", CMessageHeader::COMMAND_SIZE)},
+ {"msg", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The serialized message body to send, in hex, without a message header"},
+ },
+ RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}},
+ RPCExamples{
+ HelpExampleCli("sendmsgtopeer", "0 \"addr\" \"ffffff\"") + HelpExampleRpc("sendmsgtopeer", "0 \"addr\" \"ffffff\"")},
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ const NodeId peer_id{request.params[0].getInt<int64_t>()};
+ const std::string& msg_type{request.params[1].get_str()};
+ if (msg_type.size() > CMessageHeader::COMMAND_SIZE) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Error: msg_type too long, max length is %i", CMessageHeader::COMMAND_SIZE));
+ }
+ auto msg{TryParseHex<unsigned char>(request.params[2].get_str())};
+ if (!msg.has_value()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Error parsing input for msg");
+ }
+
+ NodeContext& node = EnsureAnyNodeContext(request.context);
+ CConnman& connman = EnsureConnman(node);
+
+ CSerializedNetMsg msg_ser;
+ msg_ser.data = msg.value();
+ msg_ser.m_type = msg_type;
+
+ bool success = connman.ForNode(peer_id, [&](CNode* node) {
+ connman.PushMessage(node, std::move(msg_ser));
+ return true;
+ });
+
+ if (!success) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Error: Could not send message to peer");
+ }
+
+ UniValue ret{UniValue::VOBJ};
+ return ret;
+ },
+ };
+}
+
void RegisterNetRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
@@ -986,6 +1034,7 @@ void RegisterNetRPCCommands(CRPCTable& t)
{"network", &getnodeaddresses},
{"hidden", &addconnection},
{"hidden", &addpeeraddress},
+ {"hidden", &sendmsgtopeer},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp
index 9e76c7be3e..24ec0e4a73 100644
--- a/src/test/fuzz/rpc.cpp
+++ b/src/test/fuzz/rpc.cpp
@@ -158,6 +158,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"reconsiderblock",
"scanblocks",
"scantxoutset",
+ "sendmsgtopeer", // when no peers are connected, no p2p message is sent
"sendrawtransaction",
"setmocktime",
"setnetworkactive",
diff --git a/test/functional/p2p_net_deadlock.py b/test/functional/p2p_net_deadlock.py
new file mode 100755
index 0000000000..f69fe52146
--- /dev/null
+++ b/test/functional/p2p_net_deadlock.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+# Copyright (c) 2023-present The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+import threading
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import random_bytes
+
+
+class NetDeadlockTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 2
+
+ def run_test(self):
+ node0 = self.nodes[0]
+ node1 = self.nodes[1]
+
+ self.log.info("Simultaneously send a large message on both sides")
+ rand_msg = random_bytes(4000000).hex()
+
+ thread0 = threading.Thread(target=node0.sendmsgtopeer, args=(0, "unknown", rand_msg))
+ thread1 = threading.Thread(target=node1.sendmsgtopeer, args=(0, "unknown", rand_msg))
+
+ thread0.start()
+ thread1.start()
+ thread0.join()
+ thread1.join()
+
+ self.log.info("Check whether a deadlock happened")
+ self.generate(node0, 1)
+ self.sync_blocks()
+
+
+if __name__ == '__main__':
+ NetDeadlockTest().main()
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 5fdd5daddf..255f5108a2 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -65,6 +65,7 @@ class NetTest(BitcoinTestFramework):
self.test_service_flags()
self.test_getnodeaddresses()
self.test_addpeeraddress()
+ self.test_sendmsgtopeer()
def test_connection_count(self):
self.log.info("Test getconnectioncount")
@@ -328,6 +329,37 @@ class NetTest(BitcoinTestFramework):
addrs = node.getnodeaddresses(count=0) # getnodeaddresses re-runs the addrman checks
assert_equal(len(addrs), 2)
+ def test_sendmsgtopeer(self):
+ node = self.nodes[0]
+
+ self.restart_node(0)
+ self.connect_nodes(0, 1)
+
+ self.log.info("Test sendmsgtopeer")
+ self.log.debug("Send a valid message")
+ with self.nodes[1].assert_debug_log(expected_msgs=["received: addr"]):
+ node.sendmsgtopeer(peer_id=0, msg_type="addr", msg="FFFFFF")
+
+ self.log.debug("Test error for sending to non-existing peer")
+ assert_raises_rpc_error(-1, "Error: Could not send message to peer", node.sendmsgtopeer, peer_id=100, msg_type="addr", msg="FF")
+
+ self.log.debug("Test that zero-length msg_type is allowed")
+ node.sendmsgtopeer(peer_id=0, msg_type="addr", msg="")
+
+ self.log.debug("Test error for msg_type that is too long")
+ assert_raises_rpc_error(-8, "Error: msg_type too long, max length is 12", node.sendmsgtopeer, peer_id=0, msg_type="long_msg_type", msg="FF")
+
+ self.log.debug("Test that unknown msg_type is allowed")
+ node.sendmsgtopeer(peer_id=0, msg_type="unknown", msg="FF")
+
+ self.log.debug("Test that empty msg is allowed")
+ node.sendmsgtopeer(peer_id=0, msg_type="addr", msg="FF")
+
+ self.log.debug("Test that oversized messages are allowed, but get us disconnected")
+ zero_byte_string = b'\x00' * 4000001
+ node.sendmsgtopeer(peer_id=0, msg_type="addr", msg=zero_byte_string.hex())
+ self.wait_until(lambda: len(self.nodes[0].getpeerinfo()) == 0, timeout=10)
+
if __name__ == '__main__':
NetTest().main()
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 0c19a9ff93..db04bb8bdb 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -268,6 +268,7 @@ BASE_SCRIPTS = [
'p2p_leak_tx.py',
'p2p_eviction.py',
'p2p_ibd_stalling.py',
+ 'p2p_net_deadlock.py',
'wallet_signmessagewithaddress.py',
'rpc_signmessagewithprivkey.py',
'rpc_generate.py',