From ca23cb5ed37e983abf0bfef8395e34fec5dc9d79 Mon Sep 17 00:00:00 2001 From: Andre da Silva Date: Fri, 18 Oct 2024 15:31:16 -0300 Subject: [PATCH 1/2] BlobNotFoundOnRead -> BlobsNotFound --- linera-chain/src/lib.rs | 15 ++++- linera-core/src/client/mod.rs | 66 +++++++++++++----- linera-core/src/local_node.rs | 64 +++++++++--------- linera-core/src/node.rs | 27 ++++---- linera-core/src/unit_tests/test_utils.rs | 4 +- linera-core/src/updater.rs | 4 +- linera-core/src/worker.rs | 36 +++++++--- linera-execution/src/execution_state_actor.rs | 6 +- linera-execution/src/lib.rs | 33 +++++++-- linera-execution/src/system.rs | 67 ++++++++++++------- .../tests/snapshots/format__format.yaml.snap | 6 +- linera-storage/src/db_storage.rs | 2 +- linera-storage/src/lib.rs | 4 +- linera-views/src/views/mod.rs | 6 +- 14 files changed, 215 insertions(+), 125 deletions(-) diff --git a/linera-chain/src/lib.rs b/linera-chain/src/lib.rs index 66f9ab2d9e4..52c9a054ed7 100644 --- a/linera-chain/src/lib.rs +++ b/linera-chain/src/lib.rs @@ -18,7 +18,7 @@ use data_types::{MessageBundle, Origin, PostedMessage}; use linera_base::{ crypto::{CryptoError, CryptoHash}, data_types::{ArithmeticError, BlockHeight, Round, Timestamp}, - identifiers::{ApplicationId, ChainId}, + identifiers::{ApplicationId, BlobId, ChainId}, }; use linera_execution::ExecutionError; use linera_views::views::ViewError; @@ -32,7 +32,7 @@ pub enum ChainError { #[error("Arithmetic error: {0}")] ArithmeticError(#[from] ArithmeticError), #[error("Error in view operation: {0}")] - ViewError(#[from] ViewError), + ViewError(ViewError), #[error("Execution error: {0} during {1:?}")] ExecutionError(ExecutionError, ChainExecutionContext), @@ -150,6 +150,17 @@ pub enum ChainError { expected: CryptoHash, actual: CryptoHash, }, + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), +} + +impl From for ChainError { + fn from(error: ViewError) -> Self { + match error { + ViewError::BlobsNotFound(blob_ids) => ChainError::BlobsNotFound(blob_ids), + error => ChainError::ViewError(error), + } + } } #[derive(Copy, Clone, Debug)] diff --git a/linera-core/src/client/mod.rs b/linera-core/src/client/mod.rs index e4c535d2ee4..ca5b2e31988 100644 --- a/linera-core/src/client/mod.rs +++ b/linera-core/src/client/mod.rs @@ -446,10 +446,10 @@ where #[derive(Debug, Error)] pub enum ChainClientError { #[error("Local node operation failed: {0}")] - LocalNodeError(#[from] LocalNodeError), + LocalNodeError(LocalNodeError), #[error("Remote node operation failed: {0}")] - RemoteNodeError(#[from] NodeError), + RemoteNodeError(NodeError), #[error(transparent)] ArithmeticError(#[from] ArithmeticError), @@ -458,7 +458,7 @@ pub enum ChainClientError { JsonError(#[from] serde_json::Error), #[error("Chain operation failed: {0}")] - ChainError(#[from] ChainError), + ChainError(ChainError), #[error(transparent)] CommunicationError(#[from] CommunicationError), @@ -494,12 +494,51 @@ pub enum ChainClientError { FoundMultipleKeysForChain(ChainId), #[error(transparent)] - ViewError(#[from] ViewError), + ViewError(ViewError), #[error("Blobs not found: {0:?}")] BlobsNotFound(Vec), } +impl From for ChainClientError { + fn from(error: NodeError) -> Self { + match error { + NodeError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids), + error => Self::RemoteNodeError(error), + } + } +} + +impl From for ChainClientError { + fn from(error: ViewError) -> Self { + match error { + ViewError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids), + error => Self::ViewError(error), + } + } +} + +impl From for ChainClientError { + fn from(error: LocalNodeError) -> Self { + match error { + LocalNodeError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids), + error => Self::LocalNodeError(error), + } + } +} + +impl From for ChainClientError { + fn from(error: ChainError) -> Self { + match error { + ChainError::BlobsNotFound(blob_ids) + | ChainError::ExecutionError(ExecutionError::BlobsNotFound(blob_ids), _) => { + Self::BlobsNotFound(blob_ids) + } + error => Self::ChainError(error), + } + } +} + impl From for ChainClientError { fn from(infallible: Infallible) -> Self { match infallible {} @@ -1122,7 +1161,7 @@ where // necessary. if let Err(err) = self.process_certificate(certificate.clone(), vec![]).await { match &err { - LocalNodeError::WorkerError(WorkerError::BlobsNotFound(blob_ids)) => { + LocalNodeError::BlobsNotFound(blob_ids) => { let blobs = LocalNodeClient::::download_blobs(blob_ids, &nodes).await; ensure!(blobs.len() == blob_ids.len(), err); @@ -1623,8 +1662,8 @@ where .handle_block_proposal(*proposal.clone()) .await { - if let Some(blob_ids) = original_err.get_blobs_not_found() { - self.update_local_node_with_blobs_from(blob_ids, remote_node) + if let LocalNodeError::BlobsNotFound(blob_ids) = &original_err { + self.update_local_node_with_blobs_from(blob_ids.clone(), remote_node) .await?; continue; // We found the missing blobs: retry. } @@ -1641,9 +1680,7 @@ where let mut blobs = vec![]; while let Err(original_err) = self.client.handle_certificate(*cert.clone(), blobs).await { - if let LocalNodeError::WorkerError(WorkerError::BlobsNotFound(blob_ids)) = - &original_err - { + if let LocalNodeError::BlobsNotFound(blob_ids) = &original_err { blobs = remote_node .find_missing_blobs(blob_ids.clone(), chain_id) .await?; @@ -1792,11 +1829,10 @@ where .local_node .stage_block_execution(block.clone()) .await; - if let Err(err) = &result { - if let Some(blob_ids) = err.get_blobs_not_found() { - self.receive_certificates_for_blobs(blob_ids).await?; - continue; // We found the missing blob: retry. - } + if let Err(LocalNodeError::BlobsNotFound(blob_ids)) = &result { + self.receive_certificates_for_blobs(blob_ids.clone()) + .await?; + continue; // We found the missing blob: retry. } return Ok(result?); } diff --git a/linera-core/src/local_node.rs b/linera-core/src/local_node.rs index 44df6828f0e..5871a17f5fa 100644 --- a/linera-core/src/local_node.rs +++ b/linera-core/src/local_node.rs @@ -17,9 +17,9 @@ use linera_chain::{ data_types::{ Block, BlockProposal, Certificate, CertificateValue, ExecutedBlock, LiteCertificate, }, - ChainError, ChainStateView, + ChainStateView, }; -use linera_execution::{ExecutionError, Query, Response, SystemExecutionError}; +use linera_execution::{Query, Response}; use linera_storage::Storage; use linera_views::views::ViewError; use rand::{prelude::SliceRandom, thread_rng}; @@ -59,10 +59,10 @@ pub enum LocalNodeError { ArithmeticError(#[from] ArithmeticError), #[error(transparent)] - ViewError(#[from] linera_views::views::ViewError), + ViewError(ViewError), #[error("Local node operation failed: {0}")] - WorkerError(#[from] WorkerError), + WorkerError(WorkerError), #[error( "Failed to download certificates and update local node to the next height \ @@ -83,35 +83,35 @@ pub enum LocalNodeError { InvalidChainInfoResponse, #[error(transparent)] - NodeError(#[from] NodeError), + NodeError(NodeError), + + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), } -impl LocalNodeError { - pub fn get_blobs_not_found(&self) -> Option> { - match self { - LocalNodeError::WorkerError(WorkerError::ChainError(chain_error)) => { - match **chain_error { - ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead( - blob_id, - )), - _, - ) - | ChainError::ExecutionError( - ExecutionError::ViewError(ViewError::BlobNotFoundOnRead(blob_id)), - _, - ) => Some(vec![blob_id]), - _ => None, - } - } - LocalNodeError::WorkerError(WorkerError::BlobsNotFound(blob_ids)) => { - Some(blob_ids.clone()) - } - LocalNodeError::NodeError(NodeError::BlobNotFoundOnRead(blob_id)) => { - Some(vec![*blob_id]) - } - LocalNodeError::NodeError(NodeError::BlobsNotFound(blob_ids)) => Some(blob_ids.clone()), - _ => None, +impl From for LocalNodeError { + fn from(error: WorkerError) -> Self { + match error { + WorkerError::BlobsNotFound(blob_ids) => LocalNodeError::BlobsNotFound(blob_ids), + error => LocalNodeError::WorkerError(error), + } + } +} + +impl From for LocalNodeError { + fn from(error: NodeError) -> Self { + match error { + NodeError::BlobsNotFound(blob_ids) => LocalNodeError::BlobsNotFound(blob_ids), + error => LocalNodeError::NodeError(error), + } + } +} + +impl From for LocalNodeError { + fn from(error: ViewError) -> Self { + match error { + ViewError::BlobsNotFound(blob_ids) => LocalNodeError::BlobsNotFound(blob_ids), + error => LocalNodeError::ViewError(error), } } } @@ -293,7 +293,7 @@ where result = match &result { Err(err) => { - if let Some(blob_ids) = err.get_blobs_not_found() { + if let LocalNodeError::BlobsNotFound(blob_ids) = &err { let blobs = remote_node.try_download_blobs(blob_ids.as_slice()).await; if blobs.len() != blob_ids.len() { result diff --git a/linera-core/src/node.rs b/linera-core/src/node.rs index dd4d99ad145..b2065172ebf 100644 --- a/linera-core/src/node.rs +++ b/linera-core/src/node.rs @@ -18,7 +18,7 @@ use linera_chain::{ }; use linera_execution::{ committee::{Committee, ValidatorName}, - ExecutionError, SystemExecutionError, + ExecutionError, }; use linera_version::VersionInfo; use linera_views::views::ViewError; @@ -178,7 +178,7 @@ pub enum NodeError { height: BlockHeight, }, - #[error("The following blobs are missing: {0:?}.")] + #[error("Blobs not found: {0:?}")] BlobsNotFound(Vec), // This error must be normalized during conversions. @@ -216,8 +216,6 @@ pub enum NodeError { #[error("Failed to make a chain info query on the local node: {error}")] LocalNodeQuery { error: String }, - #[error("Blob not found on storage read: {0}")] - BlobNotFoundOnRead(BlobId), #[error("Node failed to provide a 'last used by' certificate for the blob")] InvalidCertificateForBlob(BlobId), #[error("Local error handling validator response")] @@ -252,8 +250,11 @@ impl CrossChainMessageDelivery { impl From for NodeError { fn from(error: ViewError) -> Self { - Self::ViewError { - error: error.to_string(), + match error { + ViewError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids), + error => Self::ViewError { + error: error.to_string(), + }, } } } @@ -287,14 +288,10 @@ impl From for NodeError { height, }, ChainError::InactiveChain(chain_id) => Self::InactiveChain(chain_id), - ChainError::ExecutionError( - ExecutionError::SystemError(SystemExecutionError::BlobNotFoundOnRead(blob_id)), - _, - ) - | ChainError::ExecutionError( - ExecutionError::ViewError(ViewError::BlobNotFoundOnRead(blob_id)), - _, - ) => Self::BlobNotFoundOnRead(blob_id), + ChainError::BlobsNotFound(blob_ids) + | ChainError::ExecutionError(ExecutionError::BlobsNotFound(blob_ids), _) => { + Self::BlobsNotFound(blob_ids) + } error => Self::ChainError { error: error.to_string(), }, @@ -307,7 +304,7 @@ impl From for NodeError { match error { WorkerError::ChainError(error) => (*error).into(), WorkerError::MissingCertificateValue => Self::MissingCertificateValue, - WorkerError::BlobsNotFound(blob_ids) => NodeError::BlobsNotFound(blob_ids), + WorkerError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids), error => Self::WorkerError { error: error.to_string(), }, diff --git a/linera-core/src/unit_tests/test_utils.rs b/linera-core/src/unit_tests/test_utils.rs index dbaa6fa7ac7..198a40aacd4 100644 --- a/linera-core/src/unit_tests/test_utils.rs +++ b/linera-core/src/unit_tests/test_utils.rs @@ -262,7 +262,7 @@ where let handle_block_proposal_result = Self::handle_block_proposal(proposal, &mut validator).await; let result = match handle_block_proposal_result { - Some(Err(NodeError::BlobsNotFound(_) | NodeError::BlobNotFoundOnRead(_))) => { + Some(Err(NodeError::BlobsNotFound(_))) => { handle_block_proposal_result.expect("handle_block_proposal_result should be Some") } _ => match validator.fault_type { @@ -354,7 +354,7 @@ where let handle_certificate_result = Self::handle_certificate(certificate, validator, blobs).await; match handle_certificate_result { - Some(Err(NodeError::BlobsNotFound(_) | NodeError::BlobNotFoundOnRead(_))) => { + Some(Err(NodeError::BlobsNotFound(_))) => { handle_certificate_result.expect("handle_certificate_result should be Some") } _ => match validator.fault_type { diff --git a/linera-core/src/updater.rs b/linera-core/src/updater.rs index 14536891cc3..f5ed8b91f3b 100644 --- a/linera-core/src/updater.rs +++ b/linera-core/src/updater.rs @@ -274,8 +274,8 @@ where // synchronize them now and retry. self.send_chain_information_for_senders(chain_id).await?; } - Err(NodeError::BlobNotFoundOnRead(_)) if !blob_ids.is_empty() => { - // For `BlobNotFoundOnRead`, we assume that the local node should already be + Err(NodeError::BlobsNotFound(_)) if !blob_ids.is_empty() => { + // For `BlobsNotFound`, we assume that the local node should already be // updated with the needed blobs, so sending the chain information about the // certificates that last used the blobs to the validator node should be enough. let missing_blob_ids = stream::iter(mem::take(&mut blob_ids)) diff --git a/linera-core/src/worker.rs b/linera-core/src/worker.rs index cc34917b4ca..7b311dfba23 100644 --- a/linera-core/src/worker.rs +++ b/linera-core/src/worker.rs @@ -12,7 +12,7 @@ use std::{ #[cfg(with_testing)] use linera_base::crypto::PublicKey; use linera_base::{ - crypto::{CryptoHash, KeyPair}, + crypto::{CryptoError, CryptoHash, KeyPair}, data_types::{ ArithmeticError, Blob, BlockHeight, DecompressionError, Round, UserApplicationDescription, }, @@ -25,10 +25,11 @@ use linera_chain::{ Block, BlockExecutionOutcome, BlockProposal, Certificate, CertificateValue, ExecutedBlock, HashedCertificateValue, LiteCertificate, MessageBundle, Origin, Target, }, - ChainStateView, + ChainError, ChainStateView, }; -use linera_execution::{committee::Epoch, Query, Response}; +use linera_execution::{committee::Epoch, ExecutionError, Query, Response}; use linera_storage::Storage; +use linera_views::views::ViewError; use lru::LruCache; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -140,16 +141,16 @@ pub enum Reason { #[derive(Debug, Error)] pub enum WorkerError { #[error(transparent)] - CryptoError(#[from] linera_base::crypto::CryptoError), + CryptoError(#[from] CryptoError), #[error(transparent)] ArithmeticError(#[from] ArithmeticError), #[error(transparent)] - ViewError(#[from] linera_views::views::ViewError), + ViewError(ViewError), #[error(transparent)] - ChainError(#[from] Box), + ChainError(#[from] Box), // Chain access control #[error("Block was not signed by an authorized owner")] @@ -205,7 +206,7 @@ pub enum WorkerError { MissingExecutedBlockInProposal, #[error("Fast blocks cannot query oracles")] FastBlockUsingOracles, - #[error("The following blobs are missing: {0:?}.")] + #[error("Blobs not found: {0:?}")] BlobsNotFound(Vec), #[error("The block proposal is invalid: {0}")] InvalidBlockProposal(String), @@ -221,10 +222,25 @@ pub enum WorkerError { Decompression(#[from] DecompressionError), } -impl From for WorkerError { +impl From for WorkerError { #[instrument(level = "trace", skip(chain_error))] - fn from(chain_error: linera_chain::ChainError) -> Self { - WorkerError::ChainError(Box::new(chain_error)) + fn from(chain_error: ChainError) -> Self { + match chain_error { + ChainError::BlobsNotFound(blob_ids) + | ChainError::ExecutionError(ExecutionError::BlobsNotFound(blob_ids), _) => { + WorkerError::BlobsNotFound(blob_ids) + } + error => WorkerError::ChainError(Box::new(error)), + } + } +} + +impl From for WorkerError { + fn from(view_error: ViewError) -> Self { + match view_error { + ViewError::BlobsNotFound(blob_ids) => WorkerError::BlobsNotFound(blob_ids), + error => WorkerError::ViewError(error), + } } } diff --git a/linera-execution/src/execution_state_actor.rs b/linera-execution/src/execution_state_actor.rs index d11aba653fe..b8f36683470 100644 --- a/linera-execution/src/execution_state_actor.rs +++ b/linera-execution/src/execution_state_actor.rs @@ -306,11 +306,7 @@ where } ReadBlobContent { blob_id, callback } => { - let blob = self - .system - .read_blob_content(blob_id) - .await - .map_err(|_| SystemExecutionError::BlobNotFoundOnRead(blob_id))?; + let blob = self.system.read_blob_content(blob_id).await?; callback.respond(blob); } diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index a1c419c1b0d..2d2b9662c35 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -190,11 +190,11 @@ const _: () = { #[derive(Error, Debug)] pub enum ExecutionError { #[error(transparent)] - ViewError(#[from] ViewError), + ViewError(ViewError), #[error(transparent)] ArithmeticError(#[from] ArithmeticError), #[error(transparent)] - SystemError(#[from] SystemExecutionError), + SystemError(SystemExecutionError), #[error("User application reported an error: {0}")] UserError(String), #[cfg(any(with_wasmer, with_wasmtime))] @@ -264,6 +264,29 @@ pub enum ExecutionError { // and enforced limits for all oracles. #[error("Unstable oracles are disabled on this network.")] UnstableOracle, + + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), +} + +impl From for ExecutionError { + fn from(error: ViewError) -> Self { + match error { + ViewError::BlobsNotFound(blob_ids) => ExecutionError::BlobsNotFound(blob_ids), + error => ExecutionError::ViewError(error), + } + } +} + +impl From for ExecutionError { + fn from(error: SystemExecutionError) -> Self { + match error { + SystemExecutionError::BlobsNotFound(blob_ids) => { + ExecutionError::BlobsNotFound(blob_ids) + } + error => ExecutionError::SystemError(error), + } + } } /// The public entry points provided by the contract part of an application. @@ -346,7 +369,7 @@ pub trait ExecutionRuntimeContext { description: &UserApplicationDescription, ) -> Result; - async fn get_blob(&self, blob_id: BlobId) -> Result; + async fn get_blob(&self, blob_id: BlobId) -> Result; async fn contains_blob(&self, blob_id: BlobId) -> Result; } @@ -1018,11 +1041,11 @@ impl ExecutionRuntimeContext for TestExecutionRuntimeContext { .clone()) } - async fn get_blob(&self, blob_id: BlobId) -> Result { + async fn get_blob(&self, blob_id: BlobId) -> Result { Ok(self .blobs .get(&blob_id) - .ok_or_else(|| SystemExecutionError::BlobNotFoundOnRead(blob_id))? + .ok_or_else(|| ViewError::BlobsNotFound(vec![blob_id]))? .clone()) } diff --git a/linera-execution/src/system.rs b/linera-execution/src/system.rs index 61ac1254cf8..94cc631b139 100644 --- a/linera-execution/src/system.rs +++ b/linera-execution/src/system.rs @@ -336,7 +336,7 @@ pub enum SystemExecutionError { #[error(transparent)] ArithmeticError(#[from] ArithmeticError), #[error(transparent)] - ViewError(#[from] ViewError), + ViewError(ViewError), #[error("Invalid admin ID in new chain: {0}")] InvalidNewChainAdminId(ChainId), @@ -389,14 +389,23 @@ pub enum SystemExecutionError { #[error("Chain is not active yet.")] InactiveChain, - #[error("Blob not found on storage read: {0}")] - BlobNotFoundOnRead(BlobId), + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), #[error("Oracle response mismatch")] OracleResponseMismatch, #[error("No recorded response for oracle query")] MissingOracleResponse, } +impl From for SystemExecutionError { + fn from(error: ViewError) -> Self { + match error { + ViewError::BlobsNotFound(blob_ids) => SystemExecutionError::BlobsNotFound(blob_ids), + error => SystemExecutionError::ViewError(error), + } + } +} + impl SystemExecutionStateView where C: Context + Clone + Send + Sync + 'static, @@ -966,15 +975,11 @@ where Ok(messages) } - pub async fn read_blob_content( - &mut self, - blob_id: BlobId, - ) -> Result { + pub async fn read_blob_content(&mut self, blob_id: BlobId) -> Result { self.context() .extra() .get_blob(blob_id) .await - .map_err(|_| SystemExecutionError::BlobNotFoundOnRead(blob_id)) .map(Into::into) } @@ -985,7 +990,7 @@ where if self.context().extra().contains_blob(blob_id).await? { Ok(()) } else { - Err(SystemExecutionError::BlobNotFoundOnRead(blob_id)) + Err(SystemExecutionError::BlobsNotFound(vec![blob_id])) } } @@ -996,25 +1001,35 @@ where ) -> Result<(), SystemExecutionError> { let contract_bytecode_blob_id = BlobId::new(bytecode_id.contract_blob_hash, BlobType::ContractBytecode); - ensure!( - self.context() - .extra() - .contains_blob(contract_bytecode_blob_id) - .await?, - SystemExecutionError::BlobNotFoundOnRead(contract_bytecode_blob_id) - ); - txn_tracker.replay_oracle_response(OracleResponse::Blob(contract_bytecode_blob_id))?; + + let mut missing_blobs = Vec::new(); + if !self + .context() + .extra() + .contains_blob(contract_bytecode_blob_id) + .await? + { + missing_blobs.push(contract_bytecode_blob_id); + } + let service_bytecode_blob_id = BlobId::new(bytecode_id.service_blob_hash, BlobType::ServiceBytecode); - ensure!( - self.context() - .extra() - .contains_blob(service_bytecode_blob_id) - .await?, - SystemExecutionError::BlobNotFoundOnRead(service_bytecode_blob_id) - ); - txn_tracker.replay_oracle_response(OracleResponse::Blob(service_bytecode_blob_id))?; - Ok(()) + if !self + .context() + .extra() + .contains_blob(service_bytecode_blob_id) + .await? + { + missing_blobs.push(service_bytecode_blob_id); + } + + if missing_blobs.is_empty() { + txn_tracker.replay_oracle_response(OracleResponse::Blob(contract_bytecode_blob_id))?; + txn_tracker.replay_oracle_response(OracleResponse::Blob(service_bytecode_blob_id))?; + Ok(()) + } else { + Err(SystemExecutionError::BlobsNotFound(missing_blobs)) + } } } diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 032ee89e0e7..1de66ecf376 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -597,14 +597,10 @@ NodeError: STRUCT: - error: STR 20: - BlobNotFoundOnRead: - NEWTYPE: - TYPENAME: BlobId - 21: InvalidCertificateForBlob: NEWTYPE: TYPENAME: BlobId - 22: + 21: LocalError: STRUCT: - error: STR diff --git a/linera-storage/src/db_storage.rs b/linera-storage/src/db_storage.rs index 8968b48dc5e..9d097a718d0 100644 --- a/linera-storage/src/db_storage.rs +++ b/linera-storage/src/db_storage.rs @@ -463,7 +463,7 @@ where let maybe_blob_bytes = self.store.read_value::>(&blob_key).await?; #[cfg(with_metrics)] READ_BLOB_COUNTER.with_label_values(&[]).inc(); - let blob_bytes = maybe_blob_bytes.ok_or_else(|| ViewError::BlobNotFoundOnRead(blob_id))?; + let blob_bytes = maybe_blob_bytes.ok_or_else(|| ViewError::BlobsNotFound(vec![blob_id]))?; Ok(Blob::new_with_id_unchecked(blob_id, blob_bytes)) } diff --git a/linera-storage/src/lib.rs b/linera-storage/src/lib.rs index 1b96ab5e546..0ef8d246139 100644 --- a/linera-storage/src/lib.rs +++ b/linera-storage/src/lib.rs @@ -403,8 +403,8 @@ where } } - async fn get_blob(&self, blob_id: BlobId) -> Result { - Ok(self.storage.read_blob(blob_id).await?) + async fn get_blob(&self, blob_id: BlobId) -> Result { + self.storage.read_blob(blob_id).await } async fn contains_blob(&self, blob_id: BlobId) -> Result { diff --git a/linera-views/src/views/mod.rs b/linera-views/src/views/mod.rs index 8dae9edffca..c7d8b52cfcd 100644 --- a/linera-views/src/views/mod.rs +++ b/linera-views/src/views/mod.rs @@ -155,9 +155,9 @@ pub enum ViewError { #[error("The value is too large for the client")] TooLargeValue, - /// Blob not found when trying to read it. - #[error("Blob not found on storage read: {0}")] - BlobNotFoundOnRead(BlobId), + /// Some blobs were not found. + #[error("Blobs not found: {0:?}")] + BlobsNotFound(Vec), } impl ViewError { From 7abc4b38f6781e609ea54f43ae20a805e18c3aef Mon Sep 17 00:00:00 2001 From: Andre da Silva Date: Sat, 12 Oct 2024 01:56:30 -0300 Subject: [PATCH 2/2] Create ApplicationDescription blob type --- linera-base/src/data_types.rs | 108 +++++++++++-- linera-base/src/identifiers.rs | 151 ++++++++++++++++++ linera-base/src/unit_tests.rs | 12 +- .../chain_worker/state/temporary_changes.rs | 2 +- linera-core/src/client/mod.rs | 10 +- linera-execution/src/lib.rs | 2 +- .../tests/snapshots/format__format.yaml.snap | 25 +++ 7 files changed, 294 insertions(+), 16 deletions(-) diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index c1ce2b7a6a2..d2f3a1ab93c 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -31,11 +31,11 @@ use thiserror::Error; #[cfg(with_metrics)] use crate::prometheus_util::{self, MeasureLatency}; use crate::{ - crypto::BcsHashable, + crypto::{BcsHashable, CryptoHash}, doc_scalar, hex_debug, identifiers::{ - ApplicationId, BlobId, BlobType, BytecodeId, Destination, GenericApplicationId, MessageId, - UserApplicationId, + ApplicationId, BlobId, BlobType, BlobUserApplicationId, BytecodeId, ChainId, Destination, + GenericApplicationId, MessageId, UserApplicationId, }, limited_writer::{LimitedWriter, LimitedWriterError}, time::{Duration, SystemTime}, @@ -805,6 +805,25 @@ pub struct UserApplicationDescription { pub required_application_ids: Vec, } +/// Description of the necessary information to run a user application used within blobs. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize, WitType, WitStore)] +pub struct BlobUserApplicationDescription { + /// The unique ID of the bytecode to use for the application. + pub bytecode_id: BytecodeId, + /// The chain ID that created the application. + pub creator_chain_id: ChainId, + /// Height of the block that created this application. + pub block_height: BlockHeight, + /// At what efffect index of the block this application was created. + pub block_effect_index: u32, + /// The parameters of the application. + #[serde(with = "serde_bytes")] + #[debug(with = "hex_debug")] + pub parameters: Vec, + /// Required dependencies. + pub required_application_ids: Vec, +} + impl From<&UserApplicationDescription> for UserApplicationId { fn from(description: &UserApplicationDescription) -> Self { UserApplicationId { @@ -814,6 +833,29 @@ impl From<&UserApplicationDescription> for UserApplicationId { } } +impl From<&BlobUserApplicationDescription> for BlobUserApplicationId { + fn from(description: &BlobUserApplicationDescription) -> Self { + BlobUserApplicationId::new( + CryptoHash::new(&description.blob_bytes()), + description.bytecode_id, + ) + } +} + +impl BcsHashable for BlobUserApplicationDescription {} + +impl BlobUserApplicationDescription { + /// Gets the `BlobBytes` for this `BlobUserApplicationDescription`. + pub fn blob_bytes(&self) -> BlobBytes { + BlobBytes(self.to_bytes()) + } + + /// Gets the serialized bytes for this `BlobUserApplicationDescription`. + pub fn to_bytes(&self) -> Vec { + bcs::to_bytes(self).expect("Serializing blob bytes should not fail!") + } +} + /// A WebAssembly module's bytecode. #[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct Bytecode { @@ -973,6 +1015,8 @@ pub enum BlobContent { ContractBytecode(CompressedBytecode), /// A blob containing service bytecode. ServiceBytecode(CompressedBytecode), + /// A blob containing an application description. + ApplicationDescription(BlobUserApplicationDescription), } impl fmt::Debug for BlobContent { @@ -981,6 +1025,13 @@ impl fmt::Debug for BlobContent { BlobContent::Data(_) => write!(f, "BlobContent::Data"), BlobContent::ContractBytecode(_) => write!(f, "BlobContent::ContractBytecode"), BlobContent::ServiceBytecode(_) => write!(f, "BlobContent::ServiceBytecode"), + BlobContent::ApplicationDescription(description) => f + .debug_struct("BlobContent::ApplicationDescription") + .field("bytecode_id", &description.bytecode_id) + .field("creator_chain_id", &description.creator_chain_id) + .field("block_height", &description.block_height) + .field("block_effect_index", &description.block_effect_index) + .finish_non_exhaustive(), } } } @@ -996,6 +1047,9 @@ impl BlobContent { BlobType::ServiceBytecode => BlobContent::ServiceBytecode(CompressedBytecode { compressed_bytes: bytes, }), + BlobType::ApplicationDescription => BlobContent::ApplicationDescription( + bcs::from_bytes(&bytes).expect("Deserializing blob bytes should not fail!"), + ), } } @@ -1014,6 +1068,13 @@ impl BlobContent { BlobContent::ServiceBytecode(compressed_bytecode) } + /// Creates a new application description [`BlobContent`] from a [`BlobUserApplicationDescription`]. + pub fn new_application_description( + application_description: BlobUserApplicationDescription, + ) -> Self { + BlobContent::ApplicationDescription(application_description) + } + /// Creates a `Blob` without checking that this is the correct `BlobId`. pub fn with_blob_id_unchecked(self, blob_id: BlobId) -> Blob { Blob { @@ -1032,6 +1093,11 @@ impl BlobContent { BlobType::ServiceBytecode if matches!(&self, BlobContent::ServiceBytecode(_)) => { Some(()) } + BlobType::ApplicationDescription + if matches!(&self, BlobContent::ApplicationDescription(_)) => + { + Some(()) + } _ => None, }?; @@ -1047,13 +1113,20 @@ impl BlobContent { /// Gets the inner blob's bytes. pub fn inner_bytes(&self) -> Vec { match self { - BlobContent::Data(bytes) => bytes, - BlobContent::ContractBytecode(compressed_bytecode) => { - &compressed_bytecode.compressed_bytes - } - BlobContent::ServiceBytecode(compressed_bytecode) => { - &compressed_bytecode.compressed_bytes + BlobContent::Data(bytes) => bytes.clone(), + BlobContent::ContractBytecode(compressed_bytecode) + | BlobContent::ServiceBytecode(compressed_bytecode) => { + compressed_bytecode.compressed_bytes.clone() } + BlobContent::ApplicationDescription(description) => description.to_bytes(), + } + } + + /// Moves ownership of the blob's application description. If the `BlobContent` is of the wrong type, returns `None`. + pub fn into_inner_application_description(self) -> Option { + match self { + BlobContent::ApplicationDescription(description) => Some(description), + _ => None, } .clone() } @@ -1071,6 +1144,7 @@ impl BlobContent { | BlobContent::ServiceBytecode(compressed_bytecode) => { compressed_bytecode.compressed_bytes.len() } + BlobContent::ApplicationDescription(description) => description.to_bytes().len(), } } } @@ -1082,7 +1156,7 @@ impl From for BlobContent { } impl From for Blob { - fn from(content: BlobContent) -> Blob { + fn from(content: BlobContent) -> Self { Self { id: BlobId::from_content(&content), content, @@ -1121,6 +1195,13 @@ impl Blob { BlobContent::new_service_bytecode(compressed_bytecode).into() } + /// Creates a new application description [`Blob`] from a [`BlobUserApplicationDescription`]. + pub fn new_application_description( + application_description: BlobUserApplicationDescription, + ) -> Self { + BlobContent::new_application_description(application_description).into() + } + /// A content-addressed blob ID i.e. the hash of the `Blob`. pub fn id(&self) -> BlobId { self.id @@ -1141,8 +1222,13 @@ impl Blob { self.content.inner_bytes() } + /// Moves ownership of the blob's application description. If the `Blob` is of the wrong type, returns `None`. + pub fn into_inner_application_description(self) -> Option { + self.content.into_inner_application_description() + } + /// Loads data blob content from a file. - pub async fn load_data_blob_from_file(path: impl AsRef) -> io::Result { + pub async fn load_data_blob_from_file(path: impl AsRef) -> Result { Ok(Self::new_data(fs::read(path)?)) } } diff --git a/linera-base/src/identifiers.rs b/linera-base/src/identifiers.rs index d599518f47d..b38cfc02ccc 100644 --- a/linera-base/src/identifiers.rs +++ b/linera-base/src/identifiers.rs @@ -157,6 +157,8 @@ pub enum BlobType { ContractBytecode, /// A blob containing service bytecode. ServiceBytecode, + /// A blob containing an application description. + ApplicationDescription, } impl Display for BlobType { @@ -182,6 +184,7 @@ impl From<&BlobContent> for BlobType { BlobContent::Data(_) => BlobType::Data, BlobContent::ContractBytecode(_) => BlobType::ContractBytecode, BlobContent::ServiceBytecode(_) => BlobType::ServiceBytecode, + BlobContent::ApplicationDescription(_) => BlobType::ApplicationDescription, } } } @@ -310,10 +313,24 @@ pub struct ApplicationId { pub creation: MessageId, } +/// A unique identifier for a user application from a blob. +#[derive(WitLoad, WitStore, WitType)] +#[cfg_attr(with_testing, derive(Default))] +pub struct BlobApplicationId { + /// The hash of the `UserApplicationDescription` this refers to. + pub application_description_hash: CryptoHash, + /// The bytecode to use for the application. + pub bytecode_id: BytecodeId, +} + /// Alias for `ApplicationId`. Use this alias in the core /// protocol where the distinction with the more general enum `GenericApplicationId` matters. pub type UserApplicationId = ApplicationId; +/// Alias for `BlobApplicationId`. Use this alias in the core +/// protocol where the distinction with the more general enum `GenericApplicationId` matters. +pub type BlobUserApplicationId = BlobApplicationId; + /// A unique identifier for an application. #[derive( Eq, @@ -819,6 +836,140 @@ impl ApplicationId { } } +// Cannot use #[derive(Clone)] because it requires `A: Clone`. +impl Clone for BlobApplicationId { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for BlobApplicationId {} + +impl PartialEq for BlobApplicationId { + fn eq(&self, other: &Self) -> bool { + self.application_description_hash == other.application_description_hash + && self.bytecode_id == other.bytecode_id + } +} + +impl Eq for BlobApplicationId {} + +impl PartialOrd for BlobApplicationId { + fn partial_cmp(&self, other: &Self) -> Option { + (self.application_description_hash, self.bytecode_id) + .partial_cmp(&(other.application_description_hash, other.bytecode_id)) + } +} + +impl Ord for BlobApplicationId { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + (self.application_description_hash, self.bytecode_id) + .cmp(&(other.application_description_hash, other.bytecode_id)) + } +} + +impl Hash for BlobApplicationId { + fn hash(&self, state: &mut H) { + self.application_description_hash.hash(state); + self.bytecode_id.hash(state); + } +} + +impl Debug for BlobApplicationId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BlobApplicationId") + .field( + "application_description_hash", + &self.application_description_hash, + ) + .field("bytecode_id", &self.bytecode_id) + .finish() + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename = "BlobApplicationId")] +struct SerializableBlobApplicationId { + pub application_description_hash: CryptoHash, + pub bytecode_id: BytecodeId, +} + +impl Serialize for BlobApplicationId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + if serializer.is_human_readable() { + let bytes = bcs::to_bytes(&SerializableBlobApplicationId { + application_description_hash: self.application_description_hash, + bytecode_id: self.bytecode_id.forget_abi(), + }) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_str(&hex::encode(bytes)) + } else { + SerializableBlobApplicationId::serialize( + &SerializableBlobApplicationId { + application_description_hash: self.application_description_hash, + bytecode_id: self.bytecode_id.forget_abi(), + }, + serializer, + ) + } + } +} + +impl<'de, A> Deserialize<'de> for BlobApplicationId { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + let application_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?; + let application_id: SerializableBlobApplicationId = + bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?; + Ok(BlobApplicationId { + application_description_hash: application_id.application_description_hash, + bytecode_id: application_id.bytecode_id.with_abi(), + }) + } else { + let value = SerializableBlobApplicationId::deserialize(deserializer)?; + Ok(BlobApplicationId { + application_description_hash: value.application_description_hash, + bytecode_id: value.bytecode_id.with_abi(), + }) + } + } +} + +impl BlobApplicationId { + /// Creates an application ID from the application description hash. + pub fn new(application_description_hash: CryptoHash, bytecode_id: BytecodeId) -> Self { + BlobApplicationId { + application_description_hash, + bytecode_id, + } + } + + /// Specializes an application ID for a given ABI. + pub fn with_abi(self) -> BlobApplicationId { + BlobApplicationId { + application_description_hash: self.application_description_hash, + bytecode_id: self.bytecode_id.with_abi(), + } + } +} + +impl BlobApplicationId { + /// Forgets the ABI of a bytecode ID (if any). + pub fn forget_abi(self) -> BlobApplicationId { + BlobApplicationId { + application_description_hash: self.application_description_hash, + bytecode_id: self.bytecode_id.forget_abi(), + } + } +} + impl Display for Owner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) diff --git a/linera-base/src/unit_tests.rs b/linera-base/src/unit_tests.rs index e7e69ece075..748220c67e9 100644 --- a/linera-base/src/unit_tests.rs +++ b/linera-base/src/unit_tests.rs @@ -12,7 +12,8 @@ use crate::{ crypto::{CryptoHash, PublicKey}, data_types::{Amount, BlockHeight, Resources, SendMessageRequest, TimeDelta, Timestamp}, identifiers::{ - Account, ApplicationId, BytecodeId, ChainId, ChannelName, Destination, MessageId, Owner, + Account, ApplicationId, BlobApplicationId, BytecodeId, ChainId, ChannelName, Destination, + MessageId, Owner, }, ownership::{ChainOwnership, TimeoutConfig}, }; @@ -111,6 +112,15 @@ fn application_id_test_case() -> ApplicationId { } } +/// Creates a dummy [`BlobApplicationId`] instance to use for the WIT roundtrip test. +#[allow(dead_code)] +fn blob_application_id_test_case() -> BlobApplicationId { + BlobApplicationId::new( + CryptoHash::test_hash("application description"), + bytecode_id_test_case(), + ) +} + /// Creates a dummy [`BytecodeId`] instance to use for the WIT roundtrip test. fn bytecode_id_test_case() -> BytecodeId { BytecodeId::new( diff --git a/linera-core/src/chain_worker/state/temporary_changes.rs b/linera-core/src/chain_worker/state/temporary_changes.rs index e5b74cbac4e..b76e64f6cd2 100644 --- a/linera-core/src/chain_worker/state/temporary_changes.rs +++ b/linera-core/src/chain_worker/state/temporary_changes.rs @@ -358,7 +358,7 @@ where WorkerError::BytecodeTooLarge ); } - BlobContent::Data(_) => {} + BlobContent::Data(_) | BlobContent::ApplicationDescription(_) => {} } Ok(()) } diff --git a/linera-core/src/client/mod.rs b/linera-core/src/client/mod.rs index ca5b2e31988..d9f75cebce9 100644 --- a/linera-core/src/client/mod.rs +++ b/linera-core/src/client/mod.rs @@ -463,6 +463,9 @@ pub enum ChainClientError { #[error(transparent)] CommunicationError(#[from] CommunicationError), + #[error(transparent)] + BcsError(#[from] bcs::Error), + #[error("Internal error within chain client: {0}")] InternalError(&'static str), @@ -2711,9 +2714,12 @@ where &self, bytes: Vec>, ) -> Result, ChainClientError> { - let blobs = bytes.into_iter().map(Blob::new_data); + let mut blobs = Vec::with_capacity(bytes.len()); + for byte_vec in bytes { + blobs.push(Blob::new_data(byte_vec)); + } let publish_blob_operations = blobs - .clone() + .iter() .map(|blob| { Operation::System(SystemOperation::PublishDataBlob { blob_hash: blob.id().hash, diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index 2d2b9662c35..499c839fa7f 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -247,7 +247,7 @@ pub enum ExecutionError { #[error("Invalid JSON: {}", .0)] Json(#[from] serde_json::Error), #[error(transparent)] - Bcs(#[from] bcs::Error), + BcsError(#[from] bcs::Error), #[error("Recorded response for oracle query has the wrong type")] OracleResponseMismatch, #[error("Assertion failed: local time {local_time} is not earlier than {timestamp}")] diff --git a/linera-rpc/tests/snapshots/format__format.yaml.snap b/linera-rpc/tests/snapshots/format__format.yaml.snap index 1de66ecf376..7f21a5e8e09 100644 --- a/linera-rpc/tests/snapshots/format__format.yaml.snap +++ b/linera-rpc/tests/snapshots/format__format.yaml.snap @@ -43,6 +43,12 @@ ApplicationPermissions: - close_chain: SEQ: TYPENAME: ApplicationId +BlobApplicationId: + STRUCT: + - application_description_hash: + TYPENAME: CryptoHash + - bytecode_id: + TYPENAME: BytecodeId BlobContent: ENUM: 0: @@ -56,6 +62,10 @@ BlobContent: ServiceBytecode: NEWTYPE: TYPENAME: CompressedBytecode + 3: + ApplicationDescription: + NEWTYPE: + TYPENAME: BlobUserApplicationDescription BlobId: STRUCT: - hash: @@ -70,6 +80,21 @@ BlobType: ContractBytecode: UNIT 2: ServiceBytecode: UNIT + 3: + ApplicationDescription: UNIT +BlobUserApplicationDescription: + STRUCT: + - bytecode_id: + TYPENAME: BytecodeId + - creator_chain_id: + TYPENAME: ChainId + - block_height: + TYPENAME: BlockHeight + - block_effect_index: U32 + - parameters: BYTES + - required_application_ids: + SEQ: + TYPENAME: BlobApplicationId Block: STRUCT: - chain_id: