Veerkracht en zelfherstel

Een gedecentraliseerd netwerk is slechts zo duurzaam als zijn vermogen om te herstellen van gedeeltelijke storingen. Servers gaan offline. Nieuwe servers treden toe en moeten hun gegevens bootstrappen. Netwerkpartities veroorzaken divergentie. Hashiverse pakt dit aan via zelfherstellende protocollen die opereren zonder enige centrale coördinator — gedreven door clients en servers die inconsistentie detecteren en repareren.

Sharding en belastingverdeling

Sociale media volgen machtswetten. Een klein aantal gebruikers en hashtags trekt de overgrote meerderheid van volgers aan en genereert het merendeel van de berichten. In een naïeve DHT zou dit betekenen dat een klein aantal knooppunten het merendeel van de belasting zou dragen — wat hen tot prestatieknelpunten en hoogwaardige DDoS-doelwitten maakt vanwege de inhoud die zij hosten. Hashiverse pakt dit aan op het niveau van het datamodel via op tijd en frequentie gebaseerde sharding.

Op tijdperken gebaseerde bucket-sleutels

De inhoud van een gebruiker of hashtag wordt niet onder één enkele vaste DHT-sleutel opgeslagen. De sleutel is een functie van de publieke ID (of hashtag-tekst) van de gebruiker en een tijd-tijdperk. Dit betekent dat de DHT-locatie van de berichten van een gebruiker in de loop van de tijd verschuift, waardoor de verantwoordelijkheid voor het hosten van zijn inhoud zich over verschillende knooppunten in verschillende tijdperken verspreidt.

Voor gebruikers en hashtags met hoog volume wordt de sleutelruimte verder onderverdeeld: hoe drukker een identiteit is qua berichtvolume, hoe granulairder de bucket-onderverdeling wordt. De inhoud van een productieve poster wordt verdeeld over meer, kleinere buckets — elk gehost door een ander gebied van de DHT-ring. Dit is vastgelegd in het type BucketLocation, dat de identiteit, het tijdperk en de bucket-granulariteit samen codeert.

Het resultaat is dat geen enkel knooppunt permanent verantwoordelijk wordt voor het verkeer van een power user, en geen knooppunt een stabiel doelwit wordt voor een DDoS-aanval gericht op het tot zwijgen brengen van een specifiek account of een specifieke hashtag.

Bron: buckets.rsBucketLocation

Vraaggestuurde caching

Naast healing (dat ontbrekende gegevens achteraf repareert), implementeert Hashiverse vraaggestuurde caching om de belasting op de DHT-dichtstbijzijnde servers voor populaire buckets te verminderen. Tussenliggende servers cachen post-bundles en feedback-bundles namens de knooppunten die het dichtst bij de inhoud staan, bedienen clients direct en beëindigen de Kademlia-walk vroegtijdig. De cache breidt zich naar buiten uit onder aanhoudende vraag en krimpt automatisch wanneer de interesse afneemt — volledig zelfregulerend, geen coördinatie vereist.

Hit-drempel-tokenmechanisme

Elke server onderhoudt een bundle-cache en een feedback-cache (één Moka-gewogen cache per type). Bij elk binnenkomend GetPostBundleV1- of GetPostBundleFeedbackV1-verzoek verhoogt de server een hit-teller voor die bucket-locatie. Zodra de teller de drempel bereikt (momenteel 10 hits binnen het idle-venster), geeft de server een CacheRequestTokenV1 uit — een kortlevend token ondertekend door de server en gebonden aan die bucket-locatie.

De client verzamelt eventuele tokens die tijdens zijn Kademlia-walk worden uitgegeven. Na succesvol ophalen en verifiëren van de bundle van een verantwoordelijke server, uploadt hij de bundle asynchroon naar elke token-uitgevende server via CachePostBundleV1 / CachePostBundleFeedbackV1. De server valideert het token (handtekening, vervaldatum, locatie-overeenkomst), parseert de bundle en slaat hem op in de in-memory-cache. Latere clients die door die server lopen ontvangen de gecachte bundle direct, zonder de walk inwaarts voort te zetten.

Een in-flight deduplicatiecache (gesleuteld op location-ID, TTL die overeenkomt met de token-levensduur) voorkomt dat de server dubbele tokens uitgeeft aan meerdere gelijktijdige walkers voor dezelfde bucket.

Bron: post_bundle_caching.rs, post_bundle_feedback_caching.rs

Live versus verzegelde buckets

De cache maakt onderscheid tussen live en verzegelde data:

Ontdekkingsstraal aan clientzijde

Om gebruik te maken van de uitbreidende cache, onderhoudt elke client een per-bucket ontdekkingsstraal: de XOR-afstand tot de verste server die met succes een bundle teruggaf bij de vorige walk. Bij de volgende walk slaat de PeerIterator servers over die dichterbij zijn dan deze straal en begint vanaf de grens naar buiten te queryen — direct naar waar de cache het laatst werd gevonden, en omzeilt de verantwoordelijke (binnenste) servers volledig.

De straal wordt na elke walk bijgewerkt naar de XOR-afstand van de verste positieve responder. Als buitenste cache-lagen sindsdien zijn verlopen en geen server aan de grens reageert, valt de walk terug op de eerstvolgende dichtere peer en krimpt de straal natuurlijk om de huidige cache-diepte te weerspiegelen. De straal wordt bewaard in een time-to-idle-cache, dus buckets die een tijdje niet zijn bezocht beginnen bij de volgende toegang weer vers vanaf de verantwoordelijke servers.

Cache-verwijdering en -capaciteit

Beide caches worden gewogen op de werkelijke bundle-byte-grootte, ondersteund door Moka's W-TinyLFU-toelatingsbeleid. Plaatshouder-vermeldingen (locaties die zijn bevraagd maar nog niet met een bundle zijn gevuld) gebruiken een klein vast gewicht zodat ze meedoen in de frequentieschets zonder echte capaciteit te verbruiken. Wanneer de byte-hoogwaterlijn wordt bereikt, worden de minst frequent gebruikte locaties als eerste verwijderd. Elke locatie heeft ook een 60-seconden time-to-idle: een locatie die geen verzoeken meer ontvangt wordt stilletjes verwijderd, waarbij zijn hit-teller wordt gereset en de effectieve cache-straal krimpt.

De bundle-cache slaat per locatie tot een configureerbaar aantal originators op (momenteel 5). Als er een nieuwe originator arriveert wanneer de per-locatie-cap vol is, wordt de bundle die het eerst verloopt vervangen. Verzegelde bundles (geen vervaldatum) worden behandeld alsof ze het laatst verlopen en worden alleen verdrongen door andere verzegelde bundles.

Bron: post_bundle_caching_shared.rs, config.rsSERVER_POST_BUNDLE_CACHE_MAX_BYTES e.a.

Healing van post-bundles

Wanneer een client berichten voor een bucket ophaalt, bevraagt hij meerdere servers en verzamelt hun antwoorden. Vervolgens vergelijkt hij: welke server heeft berichten die een andere server mist? Voor elk donor-doelpaar waarbij de donor berichten heeft die het doel mist, regelt de client een twee-fase-heal:

  1. Claim-fase: De client vraagt de doelserver welke van de berichten van de donor hij nodig heeft, met een HealPostBundleClaimV1-verzoek. De server verklaart welke bericht-ID's hij mist.
  2. Commit-fase: De client (of de donor, optredend als proxy) stuurt die specifieke berichtbytes naar het doel via HealPostBundleCommitV1.

Dit draait in achtergrondtaken die worden gespawned nadat de primaire fetch van de client is voltooid. De gebruiker ziet zijn inhoud zonder te wachten op healing; het netwerk repareert zichzelf parallel.

Bron: post_bundle_healing.rs, test_healing_post_bundles.rs

Feedback-healing

Feedbacksignalen — likes, meldingen, dislikes — divergeren ook over servers heen terwijl ze door het netwerk verspreiden. Een melding kan server A bereiken maar server B niet. Feedback-healing pakt dit los van post-healing aan omdat feedback een andere datavorm heeft (het 50-byte EncodedPostFeedbackV1-record) en een andere merge-regel.

De merge-regel voor feedback is: voor elk (post_id, feedback_type)-paar, behoud het signaal met de hoogste PoW over alle servers. De client, na het verzamelen van feedback van meerdere servers, berekent het wereldwijde maximum en identificeert servers met zwakkere signalen dan het wereldwijde maximum. Vervolgens stuurt hij de sterkere signalen naar die servers via HealPostBundleFeedbackV1.

Dit is een eenfase-heal (geen claim-stap): het feedback-record is klein genoeg dat het onvoorwaardelijk versturen ervan goedkoper is dan de heen-en-weer van eerst vragen.

Bron: post_bundle_feedback_healing.rs, test_healing_post_bundle_feedbacks.rs

DDoS-weerstand

Hashiverse heeft vier lagen DDoS-bescherming die aanvallen op progressief eerdere punten in de levenscyclus van de verbinding onderscheppen, van kernel-pakketfiltering tot rate limiting op applicatieniveau.

Kernel-laag: ipset-zwarte lijst

Voor aanhoudende of ernstige aanvallen escaleert de server naar het droppen van pakketten op kernel-niveau via Linux's ipset. Op de zwarte lijst gezette IP's worden toegevoegd aan een hashset met een time-out van vijf minuten; iptables (of nftables) dropt overeenkomende pakketten voordat ze de applicatielaag bereiken, waardoor zelfs de kost van het accepteren en weigeren van de TCP-verbinding wordt geëlimineerd.

ipset create hashiverse_ddos_blacklist hash:ip timeout 300

De Rust-server voegt IP's toe aan deze set met behulp van niet-blokkerende Command::new("ipset")-aanroepen — de kernel-operatie blokkeert de verzoekafhandeling niet. Dit vereist CAP_NET_ADMIN of root in productie, beheerd via Linux Ambient Capabilities.

Pre-TLS: verbindingscap en per-IP-slot-limiet

Elke nieuwe TCP-verbinding wordt gecontroleerd vóór de TLS-handshake begint. Twee bewakers draaien op dit punt:

Beide controles worden uitgevoerd voordat TlsAcceptor::accept wordt aangeroepen, dus een vloed van nieuwe verbindingen vanaf één IP verbruikt geen CPU voor TLS-onderhandeling. De server is voorlopig bewust alleen IPv4; IPv6-ondersteuning vereist tracking op prefix-niveau (/64) om effectief te zijn, wat nog niet is geïmplementeerd.

TLS-handshake-time-out

Zodra een verbinding de pre-TLS-bewakers passeert, wordt de TLS-handshake omhuld door een harde time-out. Een client die een ClientHello stuurt maar de handshake nooit voltooit — een klassieke trage verbinding op TLS-niveau — wordt na een vast aantal seconden gedropt en zijn bad-request-score wordt verhoogd, wat hem in de richting van de applicatielaag-ban beweegt.

Slow Loris-verdediging: header- en body-leesvertijden

Na een succesvolle TLS-handshake bewaken twee verdere time-outs tegen verbindingsvasthoudende aanvallen op de HTTP-laag:

Applicatielaag: IP-reputatiecache

Verzoeken die alle bovenstaande passeren worden per IP-adres bijgehouden met behulp van een Moka in-memory-cache. IP's die de drempel voor het verzoektempo binnen een glijdend venster overschrijden ontvangen een Hashiverse RPC-foutreactie en accumuleren een bad-request-score. Zodra de score een drempel overschrijdt, wordt het IP gepromoveerd naar de ipset-zwarte lijst, waardoor het voor toekomstige verbindingen wordt gedropt naar de kernel-laag.

Bron: hashiverse-server-lib/src/network/transport/, config.rs

TimeProvider-abstractie

De TimeProvider-trait abstraheert over echte tijd, wat ScaledTimeProvider mogelijk maakt — een tijdversnelde simulatieklok die in tests wordt gebruikt. Integratietests die healinggedrag verifiëren over tijdvensters die normaal uren zouden duren, kunnen in seconden draaien door de tijd te schalen. Zo kan de integratietestsuite de volledige healing-levenscyclus — inclusief vervaltijd van oude inhoud — verifiëren zonder vertragingen op de wandklok.

Bron: hashiverse-lib/src/tools/time_provider/