Skip to content

Commit 5351e36

Browse files
Switch the Blob from Box<[u8]> to Arc<Box<[u8]>>. (#4399)
## Motivation We are doing some cloning operations for blobs. This occurs in the `TransactionTracker` with the `previously_created_blobs`. Fixes #2389 ## Proposal The implementation is done with the `serde_with` for the serialization. ## Test Plan The CI. A test has been introduced for this serialization and hash. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links None
1 parent 608d9c2 commit 5351e36

File tree

5 files changed

+55
-19
lines changed

5 files changed

+55
-19
lines changed

linera-base/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ prometheus = { workspace = true, optional = true }
5252
proptest = { workspace = true, optional = true, features = ["alloc"] }
5353
rand.workspace = true
5454
reqwest = { workspace = true, optional = true }
55-
serde.workspace = true
55+
serde = { workspace = true, features = ["rc"] }
5656
serde-name.workspace = true
5757
serde_bytes.workspace = true
5858
serde_json.workspace = true

linera-base/src/data_types.rs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{
1515
ops::{Bound, RangeBounds},
1616
path::Path,
1717
str::FromStr,
18+
sync::Arc,
1819
};
1920

2021
use alloy_primitives::U256;
@@ -1246,22 +1247,28 @@ impl CompressedBytecode {
12461247

12471248
impl BcsHashable<'_> for BlobContent {}
12481249

1250+
use serde_with::*;
1251+
12491252
/// A blob of binary data.
1253+
#[serde_as]
12501254
#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
12511255
pub struct BlobContent {
12521256
/// The type of data represented by the bytes.
12531257
blob_type: BlobType,
12541258
/// The binary data.
1255-
#[serde(with = "serde_bytes")]
12561259
#[debug(skip)]
1257-
bytes: Box<[u8]>,
1260+
#[serde_as(as = "Arc<Bytes>")]
1261+
bytes: Arc<Box<[u8]>>,
12581262
}
12591263

12601264
impl BlobContent {
12611265
/// Creates a new [`BlobContent`] from the provided bytes and [`BlobId`].
12621266
pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
12631267
let bytes = bytes.into();
1264-
BlobContent { blob_type, bytes }
1268+
BlobContent {
1269+
blob_type,
1270+
bytes: Arc::new(bytes),
1271+
}
12651272
}
12661273

12671274
/// Creates a new data [`BlobContent`] from the provided bytes.
@@ -1313,9 +1320,10 @@ impl BlobContent {
13131320
&self.bytes
13141321
}
13151322

1316-
/// Gets the inner blob's bytes, consuming the blob.
1317-
pub fn into_bytes(self) -> Box<[u8]> {
1318-
self.bytes
1323+
/// Convert a BlobContent into `Vec<u8>` without cloning if possible.
1324+
pub fn into_vec_or_clone(self) -> Vec<u8> {
1325+
let bytes = Arc::unwrap_or_clone(self.bytes);
1326+
bytes.into_vec()
13191327
}
13201328

13211329
/// Returns the type of data represented by this blob's bytes.
@@ -1363,11 +1371,12 @@ impl Blob {
13631371

13641372
/// Creates a blob without checking that the hash actually matches the content.
13651373
pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1374+
let bytes = bytes.into();
13661375
Blob {
13671376
hash: blob_id.hash,
13681377
content: BlobContent {
13691378
blob_type: blob_id.blob_type,
1370-
bytes: bytes.into(),
1379+
bytes: Arc::new(bytes),
13711380
},
13721381
}
13731382
}
@@ -1432,11 +1441,6 @@ impl Blob {
14321441
self.content.bytes()
14331442
}
14341443

1435-
/// Gets the inner blob's bytes.
1436-
pub fn into_bytes(self) -> Box<[u8]> {
1437-
self.content.into_bytes()
1438-
}
1439-
14401444
/// Loads data blob from a file.
14411445
pub async fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
14421446
Ok(Self::new_data(fs::read(path)?))
@@ -1590,7 +1594,8 @@ mod metrics {
15901594
mod tests {
15911595
use std::str::FromStr;
15921596

1593-
use super::Amount;
1597+
use super::{Amount, BlobContent};
1598+
use crate::identifiers::BlobType;
15941599

15951600
#[test]
15961601
fn display_amount() {
@@ -1617,4 +1622,35 @@ mod tests {
16171622
format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
16181623
);
16191624
}
1625+
1626+
#[test]
1627+
fn blob_content_serialization_deserialization() {
1628+
let test_data = b"Hello, world!".as_slice();
1629+
let original_blob = BlobContent::new(BlobType::Data, test_data);
1630+
1631+
let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1632+
let deserialized: BlobContent =
1633+
bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1634+
assert_eq!(original_blob, deserialized);
1635+
1636+
let serialized =
1637+
serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1638+
let deserialized: BlobContent =
1639+
serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1640+
assert_eq!(original_blob, deserialized);
1641+
}
1642+
1643+
#[test]
1644+
fn blob_content_hash_consistency() {
1645+
let test_data = b"Hello, world!";
1646+
let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1647+
let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1648+
1649+
// Both should have same hash since they contain the same data
1650+
let hash1 = crate::crypto::CryptoHash::new(&blob1);
1651+
let hash2 = crate::crypto::CryptoHash::new(&blob2);
1652+
1653+
assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1654+
assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1655+
}
16201656
}

linera-execution/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ where
946946
this.transaction_tracker
947947
.replay_oracle_response(OracleResponse::Blob(blob_id))?;
948948
}
949-
Ok(content.into_bytes().into_vec())
949+
Ok(content.into_vec_or_clone())
950950
}
951951

952952
fn assert_data_blob_exists(&mut self, hash: DataBlobHash) -> Result<(), ExecutionError> {

linera-execution/tests/test_execution.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,8 +1237,8 @@ async fn test_open_chain() -> anyhow::Result<()> {
12371237
let new_blob = &txn_outcome.blobs[0];
12381238
assert_eq!(new_blob.id().hash, child_id.0);
12391239
assert_eq!(new_blob.id().blob_type, BlobType::ChainDescription);
1240-
let created_description: ChainDescription = bcs::from_bytes(&new_blob.clone().into_bytes())
1241-
.expect("should deserialize a chain description");
1240+
let created_description: ChainDescription =
1241+
bcs::from_bytes(new_blob.bytes()).expect("should deserialize a chain description");
12421242
assert_eq!(created_description.config().balance, Amount::ONE);
12431243
assert_eq!(created_description.config().ownership, child_ownership);
12441244

linera-storage/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ pub trait Storage: Sized {
276276
.into_content(),
277277
};
278278
let compressed_contract_bytecode = CompressedBytecode {
279-
compressed_bytes: content.into_bytes().into_vec(),
279+
compressed_bytes: content.into_vec_or_clone(),
280280
};
281281
#[cfg_attr(not(any(with_wasm_runtime, with_revm)), allow(unused_variables))]
282282
let contract_bytecode =
@@ -343,7 +343,7 @@ pub trait Storage: Sized {
343343
.into_content(),
344344
};
345345
let compressed_service_bytecode = CompressedBytecode {
346-
compressed_bytes: content.into_bytes().into_vec(),
346+
compressed_bytes: content.into_vec_or_clone(),
347347
};
348348
#[cfg_attr(not(any(with_wasm_runtime, with_revm)), allow(unused_variables))]
349349
let service_bytecode = linera_base::task::Blocking::<linera_base::task::NoInput, _>::spawn(

0 commit comments

Comments
 (0)