Skip to content

Commit a9a1d69

Browse files
committed
rpc: add test-only sendmsgtopeer rpc
This rpc can be used when we want a node to send a message, but cannot use a python P2P object, for example for testing of low-level net transport behavior.
1 parent 6ce5e8f commit a9a1d69

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
299299
{ "getnodeaddresses", 0, "count"},
300300
{ "addpeeraddress", 1, "port"},
301301
{ "addpeeraddress", 2, "tried"},
302+
{ "sendmsgtopeer", 0, "peer_id" },
302303
{ "stop", 0, "wait" },
303304
};
304305
// clang-format on

src/rpc/net.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,54 @@ static RPCHelpMan addpeeraddress()
968968
};
969969
}
970970

971+
static RPCHelpMan sendmsgtopeer()
972+
{
973+
return RPCHelpMan{
974+
"sendmsgtopeer",
975+
"Send a p2p message to a peer specified by id.\n"
976+
"The message type and body must be provided, the message header will be generated.\n"
977+
"This RPC is for testing only.",
978+
{
979+
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO, "The peer to send the message to."},
980+
{"msg_type", RPCArg::Type::STR, RPCArg::Optional::NO, strprintf("The message type (maximum length %i)", CMessageHeader::COMMAND_SIZE)},
981+
{"msg", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The serialized message body to send, in hex, without a message header"},
982+
},
983+
RPCResult{RPCResult::Type::OBJ, "", "", std::vector<RPCResult>{}},
984+
RPCExamples{
985+
HelpExampleCli("sendmsgtopeer", "0 \"addr\" \"ffffff\"") + HelpExampleRpc("sendmsgtopeer", "0 \"addr\" \"ffffff\"")},
986+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
987+
const NodeId peer_id{request.params[0].getInt<int64_t>()};
988+
const std::string& msg_type{request.params[1].get_str()};
989+
if (msg_type.size() > CMessageHeader::COMMAND_SIZE) {
990+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Error: msg_type too long, max length is %i", CMessageHeader::COMMAND_SIZE));
991+
}
992+
auto msg{TryParseHex<unsigned char>(request.params[2].get_str())};
993+
if (!msg.has_value()) {
994+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Error parsing input for msg");
995+
}
996+
997+
NodeContext& node = EnsureAnyNodeContext(request.context);
998+
CConnman& connman = EnsureConnman(node);
999+
1000+
CSerializedNetMsg msg_ser;
1001+
msg_ser.data = msg.value();
1002+
msg_ser.m_type = msg_type;
1003+
1004+
bool success = connman.ForNode(peer_id, [&](CNode* node) {
1005+
connman.PushMessage(node, std::move(msg_ser));
1006+
return true;
1007+
});
1008+
1009+
if (!success) {
1010+
throw JSONRPCError(RPC_MISC_ERROR, "Error: Could not send message to peer");
1011+
}
1012+
1013+
UniValue ret{UniValue::VOBJ};
1014+
return ret;
1015+
},
1016+
};
1017+
}
1018+
9711019
void RegisterNetRPCCommands(CRPCTable& t)
9721020
{
9731021
static const CRPCCommand commands[]{
@@ -986,6 +1034,7 @@ void RegisterNetRPCCommands(CRPCTable& t)
9861034
{"network", &getnodeaddresses},
9871035
{"hidden", &addconnection},
9881036
{"hidden", &addpeeraddress},
1037+
{"hidden", &sendmsgtopeer},
9891038
};
9901039
for (const auto& c : commands) {
9911040
t.appendCommand(c.name, &c);

src/test/fuzz/rpc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
158158
"reconsiderblock",
159159
"scanblocks",
160160
"scantxoutset",
161+
"sendmsgtopeer", // when no peers are connected, no p2p message is sent
161162
"sendrawtransaction",
162163
"setmocktime",
163164
"setnetworkactive",

0 commit comments

Comments
 (0)