fix(mesh): dedup across transports + persistent radio-contact blocklist
Two mesh fixes bundled so the deploy lands them together: Doubled messages (radio + federation): dedup at store_message now runs a third cross-transport check keyed on (sender_seq, plaintext, 120s). The existing (sender_pubkey, sender_seq) match missed the common case where the same envelope arrives via LoRa radio (sender_pubkey looked up from the firmware key) and again via Tor federation (sender_pubkey = archipelago ed25519), because the two lookups disagree. The new cross-transport match closes that gap without loosening legacy paths. Stale contacts after clear-all: meshcore's on-device contact table is persistent and reads back into peers on the next refresh_contacts, so the previous "nuclear" clear wiped app state for a few seconds before the old rows reappeared. New persistent `radio_contact_blocklist` (mesh-ignored-radio-contacts.json) captures the pubkeys present at clear-time; `refresh_contacts` filters them on read and the filter survives restart. Federation-synthetic peers are excluded from the snapshot so the list rebuilds normally on the next gossip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
cd8763f468
commit
7e4fed7967
@@ -233,6 +233,45 @@ impl RpcHandler {
|
||||
let service = self.mesh_service.read().await;
|
||||
if let Some(svc) = service.as_ref() {
|
||||
let state = svc.state();
|
||||
|
||||
// Snapshot the firmware pubkeys we currently know about, then
|
||||
// add them to the radio-contact blocklist. MeshCore's on-device
|
||||
// contact table is persistent and reads back stale rows on the
|
||||
// next refresh_contacts, so without this step `clear-all` only
|
||||
// wipes the app view for a few seconds before the old entries
|
||||
// reappear. The blocklist is also saved to disk so the filter
|
||||
// survives a restart.
|
||||
let firmware_pubkeys: Vec<String> = state
|
||||
.peers
|
||||
.read()
|
||||
.await
|
||||
.values()
|
||||
.filter_map(|p| {
|
||||
// Federation-synthetic peers have their contact_id in the
|
||||
// high half of u32 and carry the archipelago key — those
|
||||
// aren't firmware contacts and must not go on the list.
|
||||
if p.contact_id & 0x8000_0000 != 0 {
|
||||
None
|
||||
} else {
|
||||
p.pubkey_hex.clone()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
{
|
||||
let mut set = state.radio_contact_blocklist.write().await;
|
||||
for pk in &firmware_pubkeys {
|
||||
set.insert(pk.clone());
|
||||
}
|
||||
}
|
||||
let persisted: Vec<String> = state
|
||||
.radio_contact_blocklist
|
||||
.read()
|
||||
.await
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
let _ = crate::mesh::save_ignored_radio_contacts(&data_dir, &persisted).await;
|
||||
|
||||
state.peers.write().await.clear();
|
||||
state.messages.write().await.clear();
|
||||
state.contacts.write().await.clear();
|
||||
|
||||
Reference in New Issue
Block a user