mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 12:49:23 +00:00
Localize Tributary HashMaps, offering flexibility and removing contention
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
use core::time::Duration;
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{Ciphersuite, Ristretto};
|
||||
|
||||
use tokio::{sync::RwLock, time::sleep};
|
||||
use tokio::{sync::broadcast, time::sleep};
|
||||
|
||||
use serai_db::MemDb;
|
||||
|
||||
@@ -27,18 +27,18 @@ async fn handle_p2p_test() {
|
||||
|
||||
let mut tributaries = new_tributaries(&keys, &spec).await;
|
||||
|
||||
let mut tributary_senders = vec![];
|
||||
let mut tributary_arcs = vec![];
|
||||
for (i, (p2p, tributary)) in tributaries.drain(..).enumerate() {
|
||||
let tributary = Arc::new(RwLock::new(tributary));
|
||||
let tributary = Arc::new(tributary);
|
||||
tributary_arcs.push(tributary.clone());
|
||||
tokio::spawn(handle_p2p(
|
||||
Ristretto::generator() * *keys[i],
|
||||
p2p,
|
||||
Arc::new(RwLock::new(HashMap::from([(
|
||||
spec.genesis(),
|
||||
ActiveTributary { spec: spec.clone(), tributary },
|
||||
)]))),
|
||||
));
|
||||
let (new_tributary_send, new_tributary_recv) = broadcast::channel(5);
|
||||
tokio::spawn(handle_p2p(Ristretto::generator() * *keys[i], p2p, new_tributary_recv));
|
||||
new_tributary_send
|
||||
.send(ActiveTributary { spec: spec.clone(), tributary })
|
||||
.map_err(|_| "failed to send ActiveTributary")
|
||||
.unwrap();
|
||||
tributary_senders.push(new_tributary_send);
|
||||
}
|
||||
let tributaries = tributary_arcs;
|
||||
|
||||
@@ -46,22 +46,22 @@ async fn handle_p2p_test() {
|
||||
// We don't wait one block of time as we may have missed the chance for this block
|
||||
sleep(Duration::from_secs((2 * Tributary::<MemDb, Transaction, LocalP2p>::block_time()).into()))
|
||||
.await;
|
||||
let tip = tributaries[0].read().await.tip().await;
|
||||
let tip = tributaries[0].tip().await;
|
||||
assert!(tip != spec.genesis());
|
||||
|
||||
// Sleep one second to make sure this block propagates
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
// Make sure every tributary has it
|
||||
for tributary in &tributaries {
|
||||
assert!(tributary.read().await.reader().block(&tip).is_some());
|
||||
assert!(tributary.reader().block(&tip).is_some());
|
||||
}
|
||||
|
||||
// Then after another block of time, we should have yet another new block
|
||||
sleep(Duration::from_secs(Tributary::<MemDb, Transaction, LocalP2p>::block_time().into())).await;
|
||||
let new_tip = tributaries[0].read().await.tip().await;
|
||||
let new_tip = tributaries[0].tip().await;
|
||||
assert!(new_tip != tip);
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
for tributary in tributaries {
|
||||
assert!(tributary.read().await.reader().block(&new_tip).is_some());
|
||||
assert!(tributary.reader().block(&new_tip).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
use core::time::Duration;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
collections::{HashSet, HashMap},
|
||||
};
|
||||
use std::{sync::Arc, collections::HashSet};
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||
|
||||
use tokio::{sync::RwLock, time::sleep};
|
||||
use tokio::{sync::broadcast, time::sleep};
|
||||
|
||||
use serai_db::MemDb;
|
||||
|
||||
@@ -37,19 +34,20 @@ async fn sync_test() {
|
||||
let (syncer_p2p, syncer_tributary) = tributaries.pop().unwrap();
|
||||
|
||||
// Have the rest form a P2P net
|
||||
let mut tributary_senders = vec![];
|
||||
let mut tributary_arcs = vec![];
|
||||
let mut p2p_threads = vec![];
|
||||
for (i, (p2p, tributary)) in tributaries.drain(..).enumerate() {
|
||||
let tributary = Arc::new(RwLock::new(tributary));
|
||||
let tributary = Arc::new(tributary);
|
||||
tributary_arcs.push(tributary.clone());
|
||||
let thread = tokio::spawn(handle_p2p(
|
||||
Ristretto::generator() * *keys[i],
|
||||
p2p,
|
||||
Arc::new(RwLock::new(HashMap::from([(
|
||||
spec.genesis(),
|
||||
ActiveTributary { spec: spec.clone(), tributary },
|
||||
)]))),
|
||||
));
|
||||
let (new_tributary_send, new_tributary_recv) = broadcast::channel(5);
|
||||
let thread =
|
||||
tokio::spawn(handle_p2p(Ristretto::generator() * *keys[i], p2p, new_tributary_recv));
|
||||
new_tributary_send
|
||||
.send(ActiveTributary { spec: spec.clone(), tributary })
|
||||
.map_err(|_| "failed to send ActiveTributary")
|
||||
.unwrap();
|
||||
tributary_senders.push(new_tributary_send);
|
||||
p2p_threads.push(thread);
|
||||
}
|
||||
let tributaries = tributary_arcs;
|
||||
@@ -60,14 +58,14 @@ async fn sync_test() {
|
||||
// propose by our 'offline' validator
|
||||
let block_time = u64::from(Tributary::<MemDb, Transaction, LocalP2p>::block_time());
|
||||
sleep(Duration::from_secs(3 * block_time)).await;
|
||||
let tip = tributaries[0].read().await.tip().await;
|
||||
let tip = tributaries[0].tip().await;
|
||||
assert!(tip != spec.genesis());
|
||||
|
||||
// Sleep one second to make sure this block propagates
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
// Make sure every tributary has it
|
||||
for tributary in &tributaries {
|
||||
assert!(tributary.read().await.reader().block(&tip).is_some());
|
||||
assert!(tributary.reader().block(&tip).is_some());
|
||||
}
|
||||
|
||||
// Now that we've confirmed the other tributaries formed a net without issue, drop the syncer's
|
||||
@@ -76,31 +74,36 @@ async fn sync_test() {
|
||||
|
||||
// Have it join the net
|
||||
let syncer_key = Ristretto::generator() * *syncer_key;
|
||||
let syncer_tributary = Arc::new(RwLock::new(syncer_tributary));
|
||||
let syncer_tributaries = Arc::new(RwLock::new(HashMap::from([(
|
||||
spec.genesis(),
|
||||
ActiveTributary { spec: spec.clone(), tributary: syncer_tributary.clone() },
|
||||
)])));
|
||||
tokio::spawn(handle_p2p(syncer_key, syncer_p2p.clone(), syncer_tributaries.clone()));
|
||||
let syncer_tributary = Arc::new(syncer_tributary);
|
||||
let (syncer_tributary_send, syncer_tributary_recv) = broadcast::channel(5);
|
||||
tokio::spawn(handle_p2p(syncer_key, syncer_p2p.clone(), syncer_tributary_recv));
|
||||
syncer_tributary_send
|
||||
.send(ActiveTributary { spec: spec.clone(), tributary: syncer_tributary.clone() })
|
||||
.map_err(|_| "failed to send ActiveTributary to syncer")
|
||||
.unwrap();
|
||||
|
||||
// It shouldn't automatically catch up. If it somehow was, our test would be broken
|
||||
// Sanity check this
|
||||
let tip = tributaries[0].read().await.tip().await;
|
||||
let tip = tributaries[0].tip().await;
|
||||
sleep(Duration::from_secs(2 * block_time)).await;
|
||||
assert!(tributaries[0].read().await.tip().await != tip);
|
||||
assert_eq!(syncer_tributary.read().await.tip().await, spec.genesis());
|
||||
assert!(tributaries[0].tip().await != tip);
|
||||
assert_eq!(syncer_tributary.tip().await, spec.genesis());
|
||||
|
||||
// Start the heartbeat protocol
|
||||
tokio::spawn(heartbeat_tributaries(syncer_p2p, syncer_tributaries));
|
||||
let (syncer_heartbeat_tributary_send, syncer_heartbeat_tributary_recv) = broadcast::channel(5);
|
||||
tokio::spawn(heartbeat_tributaries(syncer_p2p, syncer_heartbeat_tributary_recv));
|
||||
syncer_heartbeat_tributary_send
|
||||
.send(ActiveTributary { spec: spec.clone(), tributary: syncer_tributary.clone() })
|
||||
.map_err(|_| "failed to send ActiveTributary to heartbeat")
|
||||
.unwrap();
|
||||
|
||||
// The heartbeat is once every 10 blocks
|
||||
sleep(Duration::from_secs(10 * block_time)).await;
|
||||
assert!(syncer_tributary.read().await.tip().await != spec.genesis());
|
||||
assert!(syncer_tributary.tip().await != spec.genesis());
|
||||
|
||||
// Verify it synced to the tip
|
||||
let syncer_tip = {
|
||||
let tributary = tributaries[0].write().await;
|
||||
let syncer_tributary = syncer_tributary.write().await;
|
||||
let tributary = &tributaries[0];
|
||||
|
||||
let tip = tributary.tip().await;
|
||||
let syncer_tip = syncer_tributary.tip().await;
|
||||
@@ -114,7 +117,7 @@ async fn sync_test() {
|
||||
sleep(Duration::from_secs(block_time)).await;
|
||||
|
||||
// Verify it's now keeping up
|
||||
assert!(syncer_tributary.read().await.tip().await != syncer_tip);
|
||||
assert!(syncer_tributary.tip().await != syncer_tip);
|
||||
|
||||
// Verify it's now participating in consensus
|
||||
// Because only `t` validators are used in a commit, take n - t nodes offline
|
||||
@@ -128,7 +131,6 @@ async fn sync_test() {
|
||||
// wait for a block
|
||||
sleep(Duration::from_secs(block_time)).await;
|
||||
|
||||
let syncer_tributary = syncer_tributary.read().await;
|
||||
if syncer_tributary
|
||||
.reader()
|
||||
.parsed_commit(&syncer_tributary.tip().await)
|
||||
|
||||
Reference in New Issue
Block a user