Skip to content

Commit

Permalink
Parachains now also have a database (#1018)
Browse files Browse the repository at this point in the history
* Parachains now also have a database

* PR link
  • Loading branch information
tomaka authored Aug 11, 2023
1 parent 9fc9c65 commit 0a9e9cd
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 72 deletions.
49 changes: 22 additions & 27 deletions light-base/src/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,14 @@ pub use smoldot::trie::Nibble;
pub struct DatabaseContent {
/// Hash of the genesis block, as provided to [`encode_database`].
pub genesis_block_hash: [u8; 32],

/// Information about the finalized chain.
pub chain_information: chain::chain_information::ValidChainInformation,
pub chain_information: Option<chain::chain_information::ValidChainInformation>,

/// List of nodes that were known to be part of the peer-to-peer network when the database
/// was encoded.
pub known_nodes: Vec<(PeerId, Vec<multiaddr::Multiaddr>)>,

/// Known valid Merkle value and storage value combination for the `:code` key.
///
/// Does **not** necessarily match the finalized block found in
Expand All @@ -62,7 +65,7 @@ pub struct DatabaseContent {
}

/// See [`DatabaseContent::runtime_code_hint`].
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct DatabaseContentRuntimeCodeHint {
/// Storage value of the `:code` trie node corresponding to
/// [`DatabaseContentRuntimeCodeHint::code_merkle_value`].
Expand Down Expand Up @@ -93,23 +96,10 @@ pub async fn encode_database<TPlat: platform::PlatformRef>(
// Craft the structure containing all the data that we would like to include.
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 => {
// If the chain information can't be obtained, we just return a dummy value that
// will intentionally fail to decode if passed back.
let dummy_message = "<unknown>";
return if dummy_message.len() > max_size {
String::new()
} else {
dummy_message.to_owned()
};
}
},
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(0) // TODO: hacky chain_index
.await
Expand Down Expand Up @@ -190,13 +180,17 @@ pub fn decode_database(encoded: &str, block_number_bytes: usize) -> Result<Datab
return Err(());
};

let finalized_serialize::Decoded {
chain_information, ..
} = finalized_serialize::decode_chain(
&serde_json::to_string(&decoded.chain).unwrap(),
block_number_bytes,
)
.map_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,
};

// Nodes that fail to decode are simply ignored. This is especially important for
// multiaddresses, as the definition of a valid or invalid multiaddress might change across
Expand Down Expand Up @@ -246,7 +240,8 @@ struct SerdeDatabase {
/// Hexadecimal-encoded hash of the genesis block header. Has no `0x` prefix.
#[serde(rename = "genesisHash")]
genesis_hash: String,
chain: Box<serde_json::value::RawValue>,
#[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",
Expand Down
137 changes: 92 additions & 45 deletions light-base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,48 +385,48 @@ impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
),
) {
// Use the database if it contains a more recent block than the chain spec checkpoint.
(Ok(genesis_ci), checkpoint, Ok(database_content))
if database_content.genesis_block_hash
== genesis_ci
.as_ref()
.finalized_block_header
.hash(chain_spec.block_number_bytes().into())
&& checkpoint.as_ref().and_then(|r| r.as_ref().ok()).map_or(
true,
|cp| {
cp.as_ref().finalized_block_header.number
< database_content
.chain_information
.as_ref()
.finalized_block_header
.number
},
) =>
(
Ok(genesis_ci),
checkpoint,
Ok(database::DatabaseContent {
chain_information: Some(db_ci),
genesis_block_hash: db_genesis_hash,
known_nodes,
runtime_code_hint,
}),
) if db_genesis_hash
== genesis_ci
.as_ref()
.finalized_block_header
.hash(chain_spec.block_number_bytes().into())
&& checkpoint
.as_ref()
.and_then(|r| r.as_ref().ok())
.map_or(true, |cp| {
cp.as_ref().finalized_block_header.number
< db_ci.as_ref().finalized_block_header.number
}) =>
{
let genesis_header = genesis_ci.as_ref().finalized_block_header.clone();
(
database_content.chain_information,
genesis_header.into(),
database_content.known_nodes,
database_content.runtime_code_hint,
)
(db_ci, genesis_header.into(), known_nodes, runtime_code_hint)
}

// Use the database if it contains a more recent block than the chain spec checkpoint.
(
Err(chain_spec::FromGenesisStorageError::UnknownStorageItems),
checkpoint,
Ok(database_content),
Ok(database::DatabaseContent {
chain_information: Some(chain_information),
genesis_block_hash,
known_nodes,
runtime_code_hint,
}),
) if checkpoint
.as_ref()
.and_then(|r| r.as_ref().ok())
.map_or(true, |cp| {
cp.as_ref().finalized_block_header.number
< database_content
.chain_information
.as_ref()
.finalized_block_header
.number
< chain_information.as_ref().finalized_block_header.number
}) =>
{
let genesis_header = header::Header {
Expand All @@ -437,23 +437,18 @@ impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
digest: header::DigestRef::empty().into(),
};

if database_content.genesis_block_hash
if genesis_block_hash
== genesis_header.hash(chain_spec.block_number_bytes().into())
{
(
database_content.chain_information,
chain_information,
genesis_header,
database_content.known_nodes,
database_content.runtime_code_hint,
known_nodes,
runtime_code_hint,
)
} else if let Some(Ok(checkpoint)) = checkpoint {
// Database is incorrect.
(
checkpoint,
genesis_header,
database_content.known_nodes,
None,
)
(checkpoint, genesis_header, known_nodes, None)
} else {
// TODO: we can in theory support chain specs that have neither a checkpoint nor the genesis storage, but it's complicated
// TODO: is this relevant for parachains?
Expand All @@ -470,7 +465,7 @@ impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
(
Err(chain_spec::FromGenesisStorageError::UnknownStorageItems),
Some(Ok(checkpoint)),
_,
database,
) => {
let genesis_header = header::Header {
parent_hash: [0; 32],
Expand All @@ -480,14 +475,49 @@ impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
digest: header::DigestRef::empty().into(),
};

(checkpoint, genesis_header, Default::default(), None)
let mut checkpoint_nodes = Vec::new();
let mut runtime_cache_hint = None;

if let Ok(database) = database {
if database.genesis_block_hash
== genesis_header.hash(chain_spec.block_number_bytes().into())
{
checkpoint_nodes = database.known_nodes.clone();
runtime_cache_hint = database.runtime_code_hint.clone();
}
}

(
checkpoint,
genesis_header,
checkpoint_nodes,
runtime_cache_hint,
)
}

(Err(err), _, _) => return Err(AddChainError::InvalidGenesisStorage(err)),

(Ok(genesis_ci), Some(Ok(checkpoint)), _) => {
(Ok(genesis_ci), Some(Ok(checkpoint)), database) => {
let genesis_header = genesis_ci.as_ref().finalized_block_header.clone();
(checkpoint, genesis_header.into(), Default::default(), None)

let mut checkpoint_nodes = Vec::new();
let mut runtime_cache_hint = None;

if let Ok(database) = database {
if database.genesis_block_hash
== genesis_header.hash(chain_spec.block_number_bytes().into())
{
checkpoint_nodes = database.known_nodes.clone();
runtime_cache_hint = database.runtime_code_hint.clone();
}
}

(
checkpoint,
genesis_header.into(),
checkpoint_nodes,
runtime_cache_hint,
)
}

(
Expand All @@ -496,11 +526,28 @@ impl<TPlat: platform::PlatformRef, TChain> Client<TPlat, TChain> {
| Some(Err(
chain_spec::CheckpointToChainInformationError::GenesisBlockCheckpoint,
)),
_,
database,
) => {
let genesis_header =
header::Header::from(genesis_ci.as_ref().finalized_block_header.clone());
(genesis_ci, genesis_header, Default::default(), None)
let mut checkpoint_nodes = Vec::new();
let mut runtime_cache_hint = None;

if let Ok(database) = database {
if database.genesis_block_hash
== genesis_header.hash(chain_spec.block_number_bytes().into())
{
checkpoint_nodes = database.known_nodes.clone();
runtime_cache_hint = database.runtime_code_hint.clone();
}
}

(
genesis_ci,
genesis_header,
checkpoint_nodes,
runtime_cache_hint,
)
}

(_, Some(Err(err)), _) => {
Expand Down
1 change: 1 addition & 0 deletions wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Changed

- Removed the `chainHead_unstable_genesisHash` JSON-RPC function, in accordance with the latest changes in the JSON-RPC API specification. ([#1010](https://github.com/smol-dot/smoldot/pull/1010))
- The database of a parachain now contains a list of known peer-to-peer nodes and a cache of the runtime code of the parachain, similar to the database of a relay chain. The database of a parachain was previously always empty. ([#1018](https://github.com/smol-dot/smoldot/pull/1018))

### Fixed

Expand Down

0 comments on commit 0a9e9cd

Please sign in to comment.