DA: Nomos Cli dispersal tests (#711)

* Dissemination app in tests module

* Expose remaining executor settings

* Configure peer ids and addresses for cli tests

* Add BlobInfo to mempool via api

* Expose columns number args for nomos cli

* Spawn all nodes in nodes config
This commit is contained in:
gusto 2024-08-30 13:02:02 +03:00 committed by GitHub
parent 0cb039d806
commit 4ad9ebec46
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 196 additions and 15 deletions

View File

@ -1,7 +1,8 @@
// std
use std::error::Error; use std::error::Error;
use std::ops::Range; use std::ops::Range;
use std::{fmt::Debug, hash::Hash}; use std::{fmt::Debug, hash::Hash};
// crates
use axum::{ use axum::{
extract::{Query, State}, extract::{Query, State},
http::HeaderValue, http::HeaderValue,
@ -35,6 +36,7 @@ use tower_http::{
}; };
use utoipa::OpenApi; use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;
// internal
/// Configuration for the Http Server /// Configuration for the Http Server
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
@ -178,6 +180,7 @@ where
.route("/network/info", routing::get(libp2p_info)) .route("/network/info", routing::get(libp2p_info))
.route("/storage/block", routing::post(block::<S, T>)) .route("/storage/block", routing::post(block::<S, T>))
.route("/mempool/add/tx", routing::post(add_tx::<T>)) .route("/mempool/add/tx", routing::post(add_tx::<T>))
.route("/mempool/add/blobinfo", routing::post(add_blob_info::<V>))
.route("/metrics", routing::get(get_metrics)) .route("/metrics", routing::get(get_metrics))
.with_state(handle); .with_state(handle);
@ -450,6 +453,38 @@ where
>(&handle, tx, Transaction::hash)) >(&handle, tx, Transaction::hash))
} }
#[utoipa::path(
post,
path = "/mempool/add/blobinfo",
responses(
(status = 200, description = "Add blob info to the mempool"),
(status = 500, description = "Internal server error", body = String),
)
)]
async fn add_blob_info<B>(
State(handle): State<OverwatchHandle>,
Json(blob_info): Json<B>,
) -> Response
where
B: DispersedBlobInfo
+ Clone
+ Debug
+ Hash
+ Serialize
+ DeserializeOwned
+ Send
+ Sync
+ 'static,
<B as DispersedBlobInfo>::BlobId: std::cmp::Ord + Clone + Debug + Hash + Send + Sync + 'static,
{
make_request_and_return_response!(mempool::add_blob_info::<
NetworkBackend,
MempoolNetworkAdapter<B, <B as DispersedBlobInfo>::BlobId>,
B,
<B as DispersedBlobInfo>::BlobId,
>(&handle, blob_info, DispersedBlobInfo::blob_id))
}
#[utoipa::path( #[utoipa::path(
get, get,
path = "/metrics", path = "/metrics",

View File

@ -6,7 +6,7 @@ pub async fn send_blob_info<I>(node: &Url, info: &I) -> Result<Response, Error>
where where
I: Serialize, I: Serialize,
{ {
const NODE_CERT_PATH: &str = "mempool/add/cert"; const NODE_CERT_PATH: &str = "mempool/add/blobinfo";
CLIENT CLIENT
.post(node.join(NODE_CERT_PATH).unwrap()) .post(node.join(NODE_CERT_PATH).unwrap())
.json(info) .json(info)

View File

@ -29,10 +29,18 @@ pub struct Disseminate {
/// for block inclusion, if present. /// for block inclusion, if present.
#[clap(long)] #[clap(long)]
pub node_addr: Option<Url>, pub node_addr: Option<Url>,
// Application ID for dispersed data.
#[clap(long)] #[clap(long)]
pub app_id: String, pub app_id: String,
// Index for the Blob associated with Application ID.
#[clap(long)] #[clap(long)]
pub index: u64, pub index: u64,
// Use Kzg RS cache.
#[clap(long)]
pub with_cache: bool,
// Number of columns to use for encoding.
#[clap(long, default_value = "4096")]
pub columns: usize,
/// File to write the certificate to, if present. /// File to write the certificate to, if present.
#[clap(long)] #[clap(long)]
pub output: Option<PathBuf>, pub output: Option<PathBuf>,
@ -51,14 +59,18 @@ impl Disseminate {
>(std::fs::File::open(&self.network_config)?)?; >(std::fs::File::open(&self.network_config)?)?;
let (status_updates, rx) = std::sync::mpsc::channel(); let (status_updates, rx) = std::sync::mpsc::channel();
let bytes: Box<[u8]> = if let Some(data) = &self.data { let mut bytes: Vec<u8> = if let Some(data) = &self.data {
data.clone().as_bytes().into() data.clone().into_bytes()
} else { } else {
let file_path = self.file.as_ref().unwrap(); let file_path = self.file.as_ref().unwrap();
let file_bytes = std::fs::read(file_path)?; std::fs::read(file_path)?
file_bytes.into_boxed_slice()
}; };
let remainder = bytes.len() % 31;
if remainder != 0 {
bytes.resize(bytes.len() + (31 - remainder), 0);
}
let app_id: [u8; 32] = hex::decode(&self.app_id)? let app_id: [u8; 32] = hex::decode(&self.app_id)?
.try_into() .try_into()
.map_err(|_| "Invalid app_id")?; .map_err(|_| "Invalid app_id")?;
@ -66,8 +78,10 @@ impl Disseminate {
let timeout = Duration::from_secs(self.timeout); let timeout = Duration::from_secs(self.timeout);
let node_addr = self.node_addr.clone(); let node_addr = self.node_addr.clone();
let output = self.output.clone(); let output = self.output.clone();
let num_columns = self.columns;
let with_cache = self.with_cache;
let (payload_sender, payload_rx) = tokio::sync::mpsc::unbounded_channel(); let (payload_sender, payload_rx) = tokio::sync::mpsc::unbounded_channel();
payload_sender.send(bytes).unwrap(); payload_sender.send(bytes.into_boxed_slice()).unwrap();
std::thread::spawn(move || { std::thread::spawn(move || {
OverwatchRunner::<DisseminateApp>::run( OverwatchRunner::<DisseminateApp>::run(
DisseminateAppServiceSettings { DisseminateAppServiceSettings {
@ -75,7 +89,10 @@ impl Disseminate {
send_blob: Settings { send_blob: Settings {
payload: Arc::new(Mutex::new(payload_rx)), payload: Arc::new(Mutex::new(payload_rx)),
timeout, timeout,
kzgrs_settings: KzgrsSettings::default(), kzgrs_settings: KzgrsSettings {
num_columns,
with_cache,
},
metadata, metadata,
status_updates, status_updates,
node_addr, node_addr,

View File

@ -75,7 +75,7 @@ where
if !res.status().is_success() { if !res.status().is_success() {
tracing::error!("ERROR: {:?}", res); tracing::error!("ERROR: {:?}", res);
return Err(format!("Failed to send certificate to node: {}", res.status()).into()); return Err(format!("Failed to send blob info to node: {}", res.status()).into());
} }
} }
@ -210,8 +210,8 @@ impl ServiceCore for DisseminateService {
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct KzgrsSettings { pub struct KzgrsSettings {
num_columns: usize, pub num_columns: usize,
with_cache: bool, pub with_cache: bool,
} }
impl Default for KzgrsSettings { impl Default for KzgrsSettings {

View File

@ -57,8 +57,8 @@ pub struct ExecutorBackendSettings<Membership> {
#[serde(with = "secret_key_serde", default = "ed25519::SecretKey::generate")] #[serde(with = "secret_key_serde", default = "ed25519::SecretKey::generate")]
pub node_key: ed25519::SecretKey, pub node_key: ed25519::SecretKey,
/// Membership of DA network PoV set /// Membership of DA network PoV set
membership: Membership, pub membership: Membership,
node_addrs: HashMap<PeerId, Multiaddr>, pub node_addrs: HashMap<PeerId, Multiaddr>,
} }
impl<Membership> ExecutorBackend<Membership> { impl<Membership> ExecutorBackend<Membership> {

View File

@ -41,7 +41,7 @@ where
} }
} }
pub async fn add_cert<N, A, V, Item, Key>( pub async fn add_blob_info<N, A, Item, Key>(
handle: &overwatch_rs::overwatch::handle::OverwatchHandle, handle: &overwatch_rs::overwatch::handle::OverwatchHandle,
item: A::Payload, item: A::Payload,
converter: impl Fn(&A::Payload) -> Key, converter: impl Fn(&A::Payload) -> Key,

View File

@ -42,6 +42,10 @@ time = "0.3"
name = "test_cryptarchia_happy_path" name = "test_cryptarchia_happy_path"
path = "src/tests/cryptarchia/happy.rs" path = "src/tests/cryptarchia/happy.rs"
[[test]]
name = "test_cli"
path = "src/tests/cli.rs"
[features] [features]
mixnet = ["nomos-network/mixnet"] mixnet = ["nomos-network/mixnet"]
metrics = ["nomos-node/metrics"] metrics = ["nomos-node/metrics"]

View File

@ -1,5 +1,5 @@
pub mod nodes; pub mod nodes;
// pub use nodes::NomosNode; pub use nodes::NomosNode;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::env; use std::env;

125
tests/src/tests/cli.rs Normal file
View File

@ -0,0 +1,125 @@
use nomos_cli::cmds::disseminate::Disseminate;
use nomos_cli::da::network::backend::ExecutorBackend;
use nomos_cli::da::network::backend::ExecutorBackendSettings;
use nomos_da_network_service::NetworkConfig;
use nomos_libp2p::ed25519;
use nomos_libp2p::libp2p;
use nomos_libp2p::libp2p::multiaddr::multiaddr;
use nomos_libp2p::Multiaddr;
use nomos_libp2p::PeerId;
use std::collections::HashMap;
use std::io::Write;
use subnetworks_assignations::versions::v1::FillFromNodeList;
use tempfile::NamedTempFile;
use tests::nodes::NomosNode;
use tests::Node;
use tests::SpawnConfig;
const CLI_BIN: &str = "../target/debug/nomos-cli";
use std::process::Command;
fn run_disseminate(disseminate: &Disseminate) {
let mut binding = Command::new(CLI_BIN);
let c = binding
.args(["disseminate", "--network-config"])
.arg(disseminate.network_config.as_os_str())
.arg("--app-id")
.arg(&disseminate.app_id)
.arg("--index")
.arg(disseminate.index.to_string())
.arg("--columns")
.arg(disseminate.columns.to_string())
.arg("--node-addr")
.arg(disseminate.node_addr.as_ref().unwrap().as_str());
match (&disseminate.data, &disseminate.file) {
(Some(data), None) => c.args(["--data", &data]),
(None, Some(file)) => c.args(["--file", file.as_os_str().to_str().unwrap()]),
(_, _) => panic!("Either data or file needs to be provided, but not both"),
};
c.status().expect("failed to execute nomos cli");
}
async fn disseminate(config: &mut Disseminate) {
let nodes = NomosNode::spawn_nodes(SpawnConfig::star_happy(2)).await;
let node_addrs: HashMap<PeerId, Multiaddr> = nodes
.iter()
.map(|n| {
let libp2p_config = &n.config().network.backend.inner;
let keypair = libp2p::identity::Keypair::from(ed25519::Keypair::from(
libp2p_config.node_key.clone(),
));
let peer_id = PeerId::from(keypair.public());
let address = multiaddr!(Ip4(libp2p_config.host), Udp(libp2p_config.port), QuicV1);
(peer_id, address)
})
.collect();
let peer_ids: Vec<nomos_libp2p::PeerId> = node_addrs.keys().cloned().collect();
let da_network_config: NetworkConfig<ExecutorBackend<FillFromNodeList>> = NetworkConfig {
backend: ExecutorBackendSettings {
node_key: ed25519::SecretKey::generate(),
membership: FillFromNodeList::new(&peer_ids, 2, 1),
node_addrs,
},
};
let mut file = NamedTempFile::new().unwrap();
let config_path = file.path().to_owned();
serde_yaml::to_writer(&mut file, &da_network_config).unwrap();
config.timeout = 20;
config.network_config = config_path;
config.node_addr = Some(
format!(
"http://{}",
nodes[0].config().http.backend_settings.address.clone()
)
.parse()
.unwrap(),
);
config.app_id = "fd3384e132ad02a56c78f45547ee40038dc79002b90d29ed90e08eee762ae715".to_string();
config.index = 0;
config.columns = 32;
run_disseminate(&config);
}
#[tokio::test]
async fn disseminate_blob() {
let mut config = Disseminate {
data: Some("hello world".to_string()),
..Default::default()
};
disseminate(&mut config).await;
}
#[tokio::test]
async fn disseminate_big_blob() {
const MSG_SIZE: usize = 1024;
let mut config = Disseminate {
data: std::iter::repeat(String::from("X"))
.take(MSG_SIZE)
.collect::<Vec<_>>()
.join("")
.into(),
..Default::default()
};
disseminate(&mut config).await;
}
#[tokio::test]
async fn disseminate_blob_from_file() {
let mut file = NamedTempFile::new().unwrap();
file.write_all("hello world".as_bytes()).unwrap();
let mut config = Disseminate {
file: Some(file.path().to_path_buf()),
..Default::default()
};
disseminate(&mut config).await;
}