use alloc::{
borrow::ToOwned as _,
boxed::Box,
string::{String, ToString as _},
vec::Vec,
};
use core::cmp;
use smoldot::{
chain,
database::finalized_serialize,
libp2p::{multiaddr, PeerId},
};
use crate::{network_service, platform, sync_service};
pub struct DatabaseContent {
pub genesis_block_hash: [u8; 32],
pub chain_information: chain::chain_information::ValidChainInformation,
pub known_nodes: Vec<(PeerId, Vec<multiaddr::Multiaddr>)>,
}
pub async fn encode_database<TPlat: platform::PlatformRef>(
network_service: &network_service::NetworkService<TPlat>,
sync_service: &sync_service::SyncService<TPlat>,
genesis_block_hash: &[u8; 32],
max_size: usize,
) -> String {
let mut database_draft = SerdeDatabase {
genesis_hash: hex::encode(genesis_block_hash),
chain: match sync_service.serialize_chain_information().await {
Some(ci) => {
let encoded =
finalized_serialize::encode_chain(&ci, sync_service.block_number_bytes());
serde_json::from_str(&encoded).unwrap()
}
None => {
let dummy_message = "<unknown>";
return if dummy_message.len() > max_size {
String::new()
} else {
dummy_message.to_owned()
};
}
},
nodes: network_service
.discovered_nodes(0) .await
.map(|(peer_id, addrs)| {
(
peer_id.to_base58(),
addrs.map(|a| a.to_string()).collect::<Vec<_>>(),
)
})
.collect(),
};
loop {
let serialized = serde_json::to_string(&database_draft).unwrap();
if serialized.len() <= max_size {
return serialized;
}
if database_draft.nodes.is_empty() {
let dummy_message = "<too-large>";
return if dummy_message.len() > max_size {
String::new()
} else {
dummy_message.to_owned()
};
}
let mut nodes_to_remove = cmp::max(1, database_draft.nodes.len() / 2);
database_draft.nodes.retain(|_, _| {
if nodes_to_remove >= 1 {
nodes_to_remove -= 1;
false
} else {
true
}
});
}
}
pub fn decode_database(encoded: &str, block_number_bytes: usize) -> Result<DatabaseContent, ()> {
let decoded: SerdeDatabase = serde_json::from_str(encoded).map_err(|_| ())?;
let genesis_block_hash = if decoded.genesis_hash.len() == 64 {
<[u8; 32]>::try_from(hex::decode(&decoded.genesis_hash).map_err(|_| ())?).unwrap()
} else {
return Err(());
};
let (chain_information, _) = finalized_serialize::decode_chain(
&serde_json::to_string(&decoded.chain).unwrap(),
block_number_bytes,
)
.map_err(|_| ())?;
let known_nodes = decoded
.nodes
.iter()
.filter_map(|(peer_id, addrs)| {
let addrs = addrs
.iter()
.filter_map(|a| a.parse::<multiaddr::Multiaddr>().ok())
.collect();
Some((peer_id.parse::<PeerId>().ok()?, addrs))
})
.collect::<Vec<_>>();
Ok(DatabaseContent {
genesis_block_hash,
chain_information,
known_nodes,
})
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SerdeDatabase {
#[serde(rename = "genesisHash")]
genesis_hash: String,
chain: Box<serde_json::value::RawValue>,
nodes: hashbrown::HashMap<String, Vec<String>, fnv::FnvBuildHasher>,
}