feat(chat): wire the two-variant send transport (Phase 5)
Make composed messages actually reach the wire: broadcastChatMemos sends the header + payload as two 0-value memo outputs to the peer's z-address (header first, the lower memo position), on both variants. - chat_outgoing: chatSendOutputs(memos, utf8Prefix) — the pure, testable memo encoder. Full-node memos get a "utf8:" prefix (dragonxd rejects raw JSON and then UTF-8-encodes on-chain, byte-identical to SilentDragonXLite's Memo::from_str); the payload is NOT double-hex. Lite memos are raw UTF-8 (the backend does Memo::from_str directly). Header is always output 0. - App::broadcastChatMemos: full-node builds a two-recipient z_sendmany array (amount 0, from the spendable reply z-address, default fee) via submitZSendMany with markFeeGapRetry=true — deliberately, to suppress the fee-gap auto-retry, which rebuilds a single-recipient tx and would drop the payload output. Lite routes to broadcastChatMemosLite (two 0-value LiteSendRecipients, raw memos; the backend accepts duplicate addresses + 0-value for exactly this pattern). - Encoding + design established by a four-codebase mapping (wallet, daemon, SDXL, lite backend) and an adversarial review of the wiring (0 confirmed findings). - Tests: chatSendOutputs (utf8:-prefix + header-first for full-node, raw for lite) + on-chain round-trip (strip "utf8:" -> the harvest parser re-pairs and decrypts). Remaining as a LIVE test (cannot be proven from source): that dragonxd returns the memo under memoStr verbatim, that recipient-array order maps to note position, that a 0-value memo-only tx relays, and full SDXL<->DragonX interop. Gated by DRAGONX_ENABLE_CHAT (default OFF). Verified: Linux + Windows(mingw) build with chat ON, ctest 100%, hygiene clean; caches restored to the OFF default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5932,6 +5932,48 @@ void testHushChatOutgoing()
|
||||
EXPECT_EQ(conv2.back().body, std::string("reply!"));
|
||||
}
|
||||
|
||||
// Phase 5: the transport encoding (chatSendOutputs) — header first, "utf8:" for full-node / raw for
|
||||
// lite — and that the on-chain form (after the daemon strips "utf8:") round-trips through receive.
|
||||
void testHushChatTransport()
|
||||
{
|
||||
using namespace dragonx::chat;
|
||||
|
||||
ChatKeyPair alice, bob;
|
||||
ChatIdentityResult ra = deriveChatIdentityFromSecret("tx-alice", alice, true);
|
||||
ChatIdentityResult rb = deriveChatIdentityFromSecret("tx-bob", bob, true);
|
||||
OutgoingChatMemos memos;
|
||||
EXPECT_TRUE(buildOutgoingMessage(alice, ra.public_key_hex, "zs-alice", rb.public_key_hex,
|
||||
"zs-bob", "cid-tx", "over the wire", memos) == ChatComposeStatus::Ok);
|
||||
|
||||
// Full-node: header at index 0, both to the peer, each "utf8:"-prefixed.
|
||||
std::array<ChatSendOutput, 2> fn = chatSendOutputs(memos, /*utf8Prefix=*/true);
|
||||
EXPECT_EQ(fn[0].address, std::string("zs-bob"));
|
||||
EXPECT_EQ(fn[1].address, std::string("zs-bob"));
|
||||
EXPECT_TRUE(fn[0].memo.rfind("utf8:", 0) == 0);
|
||||
EXPECT_TRUE(fn[1].memo.rfind("utf8:", 0) == 0);
|
||||
EXPECT_EQ(fn[0].memo, std::string("utf8:") + memos.headerMemo);
|
||||
EXPECT_EQ(fn[1].memo, std::string("utf8:") + memos.payloadMemo);
|
||||
|
||||
// Lite: raw, no prefix.
|
||||
std::array<ChatSendOutput, 2> lt = chatSendOutputs(memos, /*utf8Prefix=*/false);
|
||||
EXPECT_EQ(lt[0].memo, memos.headerMemo);
|
||||
EXPECT_EQ(lt[1].memo, memos.payloadMemo);
|
||||
|
||||
// On-chain form round-trips: the daemon stores the UTF-8 text (the string after "utf8:"), which
|
||||
// is exactly what the harvest parser reads back (header first / lower position).
|
||||
auto strip = [](const std::string& m) { return m.rfind("utf8:", 0) == 0 ? m.substr(5) : m; };
|
||||
HushChatTransactionInput tx;
|
||||
tx.txid = "txwire";
|
||||
tx.outputs.push_back({0, strip(fn[0].memo)});
|
||||
tx.outputs.push_back({1, strip(fn[1].memo)});
|
||||
auto extracted = extractHushChatTransactionMetadata(tx, true);
|
||||
EXPECT_EQ((int)extracted.metadata.size(), 1);
|
||||
ChatService bobSvc;
|
||||
bobSvc.setIdentity(bob);
|
||||
EXPECT_EQ(bobSvc.ingest(extracted.metadata, {}, 9), 1);
|
||||
EXPECT_EQ(bobSvc.store().conversation("cid-tx")[0].body, std::string("over the wire"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
@@ -6027,6 +6069,7 @@ int main()
|
||||
testHushChatService();
|
||||
testHushChatDatabase();
|
||||
testHushChatOutgoing();
|
||||
testHushChatTransport();
|
||||
testAddressChecksumValidation();
|
||||
testLiteServerProbeLive();
|
||||
testXmrigLiveInstall();
|
||||
|
||||
Reference in New Issue
Block a user