From 50713cc87b73a276f39679114dda5fd577b115db Mon Sep 17 00:00:00 2001 From: Sami Ahmed Date: Fri, 13 Mar 2026 17:03:51 -0700 Subject: [PATCH] Add smsgbroadcast RPC command for messaging all known peers New command: smsgbroadcast Iterates all public keys in smsgDB and sends an encrypted message to each. Skips the sender's own address. Reports sent/failed counts. Co-Authored-By: Claude Opus 4.6 --- src/qt/messagemodel.cpp | 12 ++++-- src/rpcsmessage.cpp | 91 ++++++++++++++++++++++++++++++++++++++++- src/trianglesrpc.cpp | 1 + src/trianglesrpc.h | 1 + 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/qt/messagemodel.cpp b/src/qt/messagemodel.cpp index 8a715ea..be0aec6 100644 --- a/src/qt/messagemodel.cpp +++ b/src/qt/messagemodel.cpp @@ -47,7 +47,7 @@ public: void refreshMessageTable() { cachedMessageTable.clear(); - + if (parent->getWalletModel()->getEncryptionStatus() == WalletModel::Locked) { // -- messages are stored encrypted, can't load them without the private keys @@ -84,7 +84,7 @@ public: sent_datetime .setTime_t(msg.timestamp); received_datetime.setTime_t(smsgStored.timeReceived); - + memcpy(&vchKey[0], chKey, 18); addMessageEntry(MessageTableEntry(vchKey, @@ -112,7 +112,7 @@ public: sent_datetime .setTime_t(msg.timestamp); received_datetime.setTime_t(smsgStored.timeReceived); - + memcpy(&vchKey[0], chKey, 18); addMessageEntry(MessageTableEntry(vchKey, @@ -124,10 +124,16 @@ public: received_datetime, (char*)&msg.vchMessage[0]), true); + } else + { + printf("refreshMessageTable: outbox decrypt failed (err %d) for addr %s\n", decryptResult, smsgStored.sAddrOutbox.c_str()); } }; delete it; + + printf("refreshMessageTable: inbox %u found, %u decrypted; outbox %u found, %u decrypted\n", + nInbox, nInboxDecrypted, nOutbox, nOutboxDecrypted); } } diff --git a/src/rpcsmessage.cpp b/src/rpcsmessage.cpp index d8cd438..497f91e 100644 --- a/src/rpcsmessage.cpp +++ b/src/rpcsmessage.cpp @@ -889,7 +889,96 @@ Value smsgbuckets(const Array& params, bool fHelp) result.push_back(Pair("result", "Unknown Mode.")); result.push_back(Pair("expected", "[stats|dump].")); }; - + + + return result; +}; + +Value smsgbroadcast(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "smsgbroadcast \n" + "Send an encrypted message to all known public keys in the smsgDB.\n" + "Returns the number of recipients and any failures."); + + if (!fSecMsgEnabled) + throw runtime_error("Secure messaging is disabled."); + + if (pwalletMain->IsLocked()) + throw runtime_error("Wallet is locked."); + + std::string addrFrom = params[0].get_str(); + std::string msg = params[1].get_str(); + + // Validate sender address + CTrianglesAddress coinAddrFrom(addrFrom); + if (!coinAddrFrom.IsValid()) + throw runtime_error("Invalid from address."); + + Object result; + uint32_t nSent = 0; + uint32_t nFailed = 0; + Array failures; + + { + LOCK(cs_smsgDB); + + SecMsgDB dbPub; + if (!dbPub.Open("r")) + throw runtime_error("Could not open smsgDB."); + + // Iterate all "pk" entries + std::string sPrefix("pk"); + leveldb::Iterator* it = dbPub.pdb->NewIterator(leveldb::ReadOptions()); + for (it->Seek(sPrefix); it->Valid(); it->Next()) + { + std::string key = it->key().ToString(); + if (key.size() < 2 || key[0] != 'p' || key[1] != 'k') + break; + + // Deserialize the CKeyID from the key (after "pk" prefix) + CDataStream ssKey(key.data(), key.data() + key.size(), SER_DISK, CLIENT_VERSION); + char prefix[2]; + ssKey >> prefix[0]; + ssKey >> prefix[1]; + CKeyID ckidTo; + ssKey >> ckidTo; + + // Convert CKeyID to address string + CTrianglesAddress addrTo(ckidTo); + if (!addrTo.IsValid()) + continue; + + std::string sAddrTo = addrTo.ToString(); + + // Skip sending to self + if (sAddrTo == addrFrom) + continue; + + std::string sError; + if (SecureMsgSend(addrFrom, sAddrTo, msg, sError) != 0) + { + nFailed++; + Object objFail; + objFail.push_back(Pair("address", sAddrTo)); + objFail.push_back(Pair("error", sError)); + failures.push_back(objFail); + } else + { + nSent++; + } + } + delete it; + } + + char cbuf[256]; + snprintf(cbuf, sizeof(cbuf), "Broadcast sent to %u recipients, %u failed.", nSent, nFailed); + result.push_back(Pair("result", std::string(cbuf))); + result.push_back(Pair("sent", (int)nSent)); + result.push_back(Pair("failed", (int)nFailed)); + if (nFailed > 0) + result.push_back(Pair("failures", failures)); return result; }; diff --git a/src/trianglesrpc.cpp b/src/trianglesrpc.cpp index 12d71d5..c351999 100644 --- a/src/trianglesrpc.cpp +++ b/src/trianglesrpc.cpp @@ -336,6 +336,7 @@ static const CRPCCommand vRPCCommands[] = { "smsginbox", &smsginbox, false, false}, { "smsgoutbox", &smsgoutbox, false, false}, { "smsgbuckets", &smsgbuckets, false, false}, + { "smsgbroadcast", &smsgbroadcast, false, false}, diff --git a/src/trianglesrpc.h b/src/trianglesrpc.h index fb3ddf9..72116a4 100644 --- a/src/trianglesrpc.h +++ b/src/trianglesrpc.h @@ -247,6 +247,7 @@ extern json_spirit::Value smsgsendanon(const json_spirit::Array& params, bool fH extern json_spirit::Value smsginbox(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value smsgoutbox(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value smsgbuckets(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value smsgbroadcast(const json_spirit::Array& params, bool fHelp); #endif