Improved DHT publication. Adjust batching, added validation per key

This commit is contained in:
Ultradesu
2026-07-23 16:43:10 +03:00
parent aaaad780f2
commit 512a818a6a
3 changed files with 150 additions and 83 deletions
+33 -43
View File
@@ -426,47 +426,17 @@ impl Node {
}
}
async fn answer_store(&self, request: StoreRecordRequest, sender: &EndpointId) -> bool {
if self.is_shutting_down() {
return false;
}
let key = request.key;
match validate_store(request, &self.config.network_id, now_ms()) {
Ok(record) => {
let artist_id = record.item.id;
let deleted = record.item.deleted;
match self.db.store_dht_record(key, record).await {
Ok(stored) => {
if stored {
debug!(
item = %artist_id,
tombstone = deleted,
from = %sender,
"stored DHT record"
);
}
stored
}
Err(err) => {
warn!(error = %err, "failed to store DHT record");
false
}
}
}
Err(reason) => {
warn!(from = %sender, reason = %reason, "rejected DHT store request");
false
}
}
}
/// Validates and stores every entry of a batch individually: one bad
/// entry never poisons the rest.
/// Validates every entry of a batch individually — one bad entry never
/// poisons the rest — and applies the valid ones in a single write
/// transaction.
async fn answer_store_batch(
&self,
request: StoreBatchRequest,
sender: &EndpointId,
) -> StoreBatchResponse {
if self.is_shutting_down() {
return StoreBatchResponse { stored: 0 };
}
if request.entries.len() > MAX_RECORDS_PER_BATCH {
warn!(
from = %sender,
@@ -475,12 +445,26 @@ impl Node {
);
return StoreBatchResponse { stored: 0 };
}
let mut stored = 0u32;
let received = request.entries.len();
let now = now_ms();
let mut valid = Vec::with_capacity(received);
for entry in request.entries {
if self.answer_store(entry, sender).await {
stored += 1;
let key = entry.key;
match validate_store(entry, &self.config.network_id, now) {
Ok(record) => valid.push((key, record)),
Err(reason) => {
warn!(from = %sender, reason = %reason, "rejected DHT store entry");
}
}
}
let stored = match self.db.store_dht_records(valid).await {
Ok(outcomes) => outcomes.into_iter().filter(|stored| *stored).count() as u32,
Err(err) => {
warn!(error = %err, "failed to store DHT record batch");
0
}
};
debug!(from = %sender, received, stored, "stored DHT record batch");
StoreBatchResponse { stored }
}
@@ -814,6 +798,8 @@ impl Node {
let mut local_replica = false;
// Batches under construction, keyed by the index into `contacts`.
let mut per_peer: HashMap<usize, Vec<StoreRecordRequest>> = HashMap::new();
// Own replicas, applied in one write transaction at the end.
let mut own_replicas: Vec<(DhtKey, StoredRecord)> = Vec::new();
// K-closest target sets are memoized per key: token keys repeat
// heavily across the items of one artist or release.
let mut targets_memo: HashMap<DhtKey, Vec<usize>> = HashMap::new();
@@ -845,10 +831,7 @@ impl Node {
<= distance(contacts[*farthest].node_id.as_bytes(), key.as_bytes())
});
if self_is_close {
match self.db.store_dht_record(key, record.clone()).await {
Ok(_) => local_replica = true,
Err(err) => warn!(error = %err, "failed to store own replica"),
}
own_replicas.push((key, record.clone()));
}
for index in targets.iter() {
@@ -860,6 +843,13 @@ impl Node {
}
}
if !own_replicas.is_empty() {
match self.db.store_dht_records(own_replicas).await {
Ok(_) => local_replica = true,
Err(err) => warn!(error = %err, "failed to store own replicas"),
}
}
// One concurrent pipeline per peer; batches within a pipeline are
// sequential so a slow peer only throttles itself.
let sends = per_peer.into_iter().map(|(index, entries)| {