Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create ApplicationDescription blob type #2612

Open
wants to merge 2 commits into
base: graphite-base/2612
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 97 additions & 11 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,
},
limited_writer::{LimitedWriter, LimitedWriterError},
time::{Duration, SystemTime},
Expand Down Expand Up @@ -805,6 +805,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 efffect index of the block this application was created.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// At what efffect index of the block this application was created.
/// At what effect 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<u8>,
/// Required dependencies.
pub required_application_ids: Vec<BlobUserApplicationId>,
}

impl From<&UserApplicationDescription> for UserApplicationId {
fn from(description: &UserApplicationDescription) -> Self {
UserApplicationId {
Expand All @@ -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<u8> {
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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
}
}
}
Expand All @@ -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!"),
),
}
}

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

Expand All @@ -1047,13 +1113,20 @@ impl BlobContent {
/// Gets the inner blob's bytes.
pub fn inner_bytes(&self) -> Vec<u8> {
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<BlobUserApplicationDescription> {
match self {
BlobContent::ApplicationDescription(description) => Some(description),
_ => None,
}
.clone()
}
Expand All @@ -1071,6 +1144,7 @@ impl BlobContent {
| BlobContent::ServiceBytecode(compressed_bytecode) => {
compressed_bytecode.compressed_bytes.len()
}
BlobContent::ApplicationDescription(description) => description.to_bytes().len(),
}
}
}
Expand All @@ -1082,7 +1156,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 @@ -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
Expand All @@ -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<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> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Why?)

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 @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -310,10 +313,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 @@ -819,6 +836,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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we destructure in these methods, the compiler will warn us in case we add any additional fields.

.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
Loading
Loading