Skip to content

Commit

Permalink
Create ApplicationDescription blob type
Browse files Browse the repository at this point in the history
  • Loading branch information
ndr-ds committed Oct 16, 2024
1 parent cd0f531 commit 7fb263d
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 13 deletions.
100 changes: 92 additions & 8 deletions linera-base/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
time::{Duration, SystemTime},
};
Expand Down Expand Up @@ -804,6 +804,25 @@ pub struct UserApplicationDescription {
pub required_application_ids: Vec<UserApplicationId>,
}

/// 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 operation index of the block this application was created.
pub operation_index: u32,
/// The parameters of the application.
#[serde(with = "serde_bytes")]
#[debug(with = "hex_debug")]
pub parameters: Vec<u8>,
/// Required dependencies.
pub required_application_ids: Vec<BlobUserApplicationId>,
}

impl From<&UserApplicationDescription> for UserApplicationId {
fn from(description: &UserApplicationDescription) -> Self {
UserApplicationId {
Expand All @@ -813,6 +832,24 @@ 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(bcs::to_bytes(self).expect("Deserializing blob bytes should not fail!"))
}
}

/// A WebAssembly module's bytecode.
#[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Bytecode {
Expand Down Expand Up @@ -946,6 +983,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 {
Expand All @@ -954,6 +993,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("operation_index", &description.operation_index)
.finish_non_exhaustive(),
}
}
}
Expand All @@ -969,6 +1015,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!"),
),
}
}

Expand All @@ -987,6 +1036,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 {
Expand All @@ -1005,6 +1061,11 @@ impl BlobContent {
BlobType::ServiceBytecode if matches!(&self, BlobContent::ServiceBytecode(_)) => {
Some(())
}
BlobType::ApplicationDescription
if matches!(&self, BlobContent::ApplicationDescription(_)) =>
{
Some(())
}
_ => None,
}?;

Expand All @@ -1020,13 +1081,24 @@ impl BlobContent {
/// Gets the inner blob's bytes.
pub fn inner_bytes(&self) -> Vec<u8> {
match self {
BlobContent::Data(bytes) => bytes,
BlobContent::Data(bytes) => bytes.clone(),
BlobContent::ContractBytecode(compressed_bytecode) => {
&compressed_bytecode.compressed_bytes
compressed_bytecode.compressed_bytes.clone()
}
BlobContent::ServiceBytecode(compressed_bytecode) => {
&compressed_bytecode.compressed_bytes
compressed_bytecode.compressed_bytes.clone()
}
BlobContent::ApplicationDescription(description) => {
bcs::to_bytes(description).expect("Deserializing blob bytes should not fail!")
}
}
}

/// 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<BlobUserApplicationDescription> {
match self {
BlobContent::ApplicationDescription(description) => Some(description),
_ => None,
}
.clone()
}
Expand All @@ -1044,7 +1116,7 @@ impl From<Blob> for BlobContent {
}

impl From<BlobContent> for Blob {
fn from(content: BlobContent) -> Blob {
fn from(content: BlobContent) -> Self {
Self {
id: BlobId::from_content(&content),
content,
Expand Down Expand Up @@ -1083,6 +1155,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
Expand All @@ -1103,8 +1182,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<BlobUserApplicationDescription> {
self.content.into_inner_application_description()
}

/// Loads data blob content from a file.
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> Result<Self, io::Error> {
Ok(Self::new_data(fs::read(path)?))
}
}
Expand Down
151 changes: 151 additions & 0 deletions linera-base/src/identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ pub enum BlobType {
ContractBytecode,
/// A blob containing service bytecode.
ServiceBytecode,
/// A blob containing an application description.
ApplicationDescription,
}

impl Display for BlobType {
Expand All @@ -196,6 +198,7 @@ impl From<&BlobContent> for BlobType {
BlobContent::Data(_) => BlobType::Data,
BlobContent::ContractBytecode(_) => BlobType::ContractBytecode,
BlobContent::ServiceBytecode(_) => BlobType::ServiceBytecode,
BlobContent::ApplicationDescription(_) => BlobType::ApplicationDescription,
}
}
}
Expand Down Expand Up @@ -299,10 +302,24 @@ pub struct ApplicationId<A = ()> {
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<A = ()> {
/// The hash of the `UserApplicationDescription` this refers to.
pub application_description_hash: CryptoHash,
/// The bytecode to use for the application.
pub bytecode_id: BytecodeId<A>,
}

/// Alias for `ApplicationId`. Use this alias in the core
/// protocol where the distinction with the more general enum `GenericApplicationId` matters.
pub type UserApplicationId<A = ()> = ApplicationId<A>;

/// Alias for `BlobApplicationId`. Use this alias in the core
/// protocol where the distinction with the more general enum `GenericApplicationId` matters.
pub type BlobUserApplicationId<A = ()> = BlobApplicationId<A>;

/// A unique identifier for an application.
#[derive(
Eq,
Expand Down Expand Up @@ -808,6 +825,140 @@ impl<A> ApplicationId<A> {
}
}

// Cannot use #[derive(Clone)] because it requires `A: Clone`.
impl<A> Clone for BlobApplicationId<A> {
fn clone(&self) -> Self {
*self
}
}

impl<A> Copy for BlobApplicationId<A> {}

impl<A: PartialEq> PartialEq for BlobApplicationId<A> {
fn eq(&self, other: &Self) -> bool {
self.application_description_hash == other.application_description_hash
&& self.bytecode_id == other.bytecode_id
}
}

impl<A: Eq> Eq for BlobApplicationId<A> {}

impl<A: PartialOrd> PartialOrd for BlobApplicationId<A> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
(self.application_description_hash, self.bytecode_id)
.partial_cmp(&(other.application_description_hash, other.bytecode_id))
}
}

impl<A: Ord> Ord for BlobApplicationId<A> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
(self.application_description_hash, self.bytecode_id)
.cmp(&(other.application_description_hash, other.bytecode_id))
}
}

impl<A> Hash for BlobApplicationId<A> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.application_description_hash.hash(state);
self.bytecode_id.hash(state);
}
}

impl<A> Debug for BlobApplicationId<A> {
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<A> Serialize for BlobApplicationId<A> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<A> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<A>(self) -> BlobApplicationId<A> {
BlobApplicationId {
application_description_hash: self.application_description_hash,
bytecode_id: self.bytecode_id.with_abi(),
}
}
}

impl<A> BlobApplicationId<A> {
/// 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)
Expand Down
12 changes: 11 additions & 1 deletion linera-base/src/unit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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(
Expand Down
Loading

0 comments on commit 7fb263d

Please sign in to comment.