use alloc::{
borrow::ToOwned as _,
boxed::Box,
format,
string::{String, ToString as _},
vec::Vec,
};
use core::cmp;
use smoldot::{
chain,
database::finalized_serialize,
libp2p::{PeerId, multiaddr},
};
use crate::{network_service, platform, runtime_service, sync_service};
pub use smoldot::trie::Nibble;
pub struct DatabaseContent {
pub genesis_block_hash: [u8; 32],
pub chain_information: Option<chain::chain_information::ValidChainInformation>,
pub known_nodes: Vec<(PeerId, Vec<multiaddr::Multiaddr>)>,
pub runtime_code_hint: Option<DatabaseContentRuntimeCodeHint>,
}
#[derive(Debug, Clone)]
pub struct DatabaseContentRuntimeCodeHint {
pub code: Vec<u8>,
pub code_merkle_value: Vec<u8>,
pub closest_ancestor_excluding: Vec<Nibble>,
}
pub async fn encode_database<TPlat: platform::PlatformRef>(
network_service: &network_service::NetworkServiceChain<TPlat>,
sync_service: &sync_service::SyncService<TPlat>,
runtime_service: &runtime_service::RuntimeService<TPlat>,
genesis_block_hash: &[u8; 32],
max_size: usize,
) -> String {
let (code_storage_value, code_merkle_value, code_closest_ancestor_excluding) = runtime_service
.finalized_runtime_storage_merkle_values()
.await
.unwrap_or((None, None, None));
let mut database_draft = SerdeDatabase {
genesis_hash: hex::encode(genesis_block_hash),
chain: sync_service.serialize_chain_information().await.map(|ci| {
let encoded = finalized_serialize::encode_chain(&ci, sync_service.block_number_bytes());
serde_json::from_str(&encoded).unwrap()
}),
nodes: network_service
.discovered_nodes()
.await
.map(|(peer_id, addrs)| {
(
peer_id.to_base58(),
addrs.map(|a| a.to_string()).collect::<Vec<_>>(),
)
})
.collect(),
code_merkle_value: code_merkle_value.map(hex::encode),
code_storage_value: code_storage_value.map(|data| {
base64::Engine::encode(&base64::engine::general_purpose::STANDARD_NO_PAD, data)
}),
code_closest_ancestor_excluding: code_closest_ancestor_excluding.map(|key| {
key.iter()
.map(|nibble| format!("{:x}", nibble))
.collect::<String>()
}),
};
loop {
let serialized = serde_json::to_string(&database_draft).unwrap();
if serialized.len() <= max_size {
return serialized;
}
if database_draft.code_merkle_value.is_some() || database_draft.code_storage_value.is_some()
{
database_draft.code_merkle_value = None;
database_draft.code_storage_value = None;
continue;
}
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 = match &decoded.chain {
Some(chain) => Some(
finalized_serialize::decode_chain(
&serde_json::to_string(chain).unwrap(),
block_number_bytes,
)
.map_err(|_| ())?
.chain_information,
),
None => None,
};
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<_>>();
let runtime_code_hint = match (
decoded.code_merkle_value,
decoded.code_storage_value,
decoded.code_closest_ancestor_excluding,
) {
(Some(mv), Some(sv), Some(an)) => Some(DatabaseContentRuntimeCodeHint {
code: base64::Engine::decode(&base64::engine::general_purpose::STANDARD_NO_PAD, sv)
.map_err(|_| ())?,
code_merkle_value: hex::decode(mv).map_err(|_| ())?,
closest_ancestor_excluding: an
.as_bytes()
.iter()
.map(|char| Nibble::from_ascii_hex_digit(*char).ok_or(()))
.collect::<Result<Vec<Nibble>, ()>>()?,
}),
_ => None,
};
Ok(DatabaseContent {
genesis_block_hash,
chain_information,
known_nodes,
runtime_code_hint,
})
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SerdeDatabase {
#[serde(rename = "genesisHash")]
genesis_hash: String,
#[serde(default = "Default::default", skip_serializing_if = "Option::is_none")]
chain: Option<Box<serde_json::value::RawValue>>,
nodes: hashbrown::HashMap<String, Vec<String>, fnv::FnvBuildHasher>,
#[serde(
rename = "runtimeCode",
default = "Default::default",
skip_serializing_if = "Option::is_none"
)]
code_storage_value: Option<String>,
#[serde(
rename = "codeMerkleValue",
default = "Default::default",
skip_serializing_if = "Option::is_none"
)]
code_merkle_value: Option<String>,
#[serde(
rename = "codeClosestAncestor",
default = "Default::default",
skip_serializing_if = "Option::is_none"
)]
code_closest_ancestor_excluding: Option<String>,
}