Skip to content

Commit 6beab06

Browse files
committed
Improve serialization
1 parent 47139ef commit 6beab06

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

crates/Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/batcher/src/types/batch_queue.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,4 +882,196 @@ mod test {
882882
max_fee_1
883883
);
884884
}
885+
886+
#[test]
887+
fn test_batch_size_limit_enforcement_with_real_sp1_proofs() {
888+
use aligned_sdk::communication::serialization::cbor_serialize;
889+
use aligned_sdk::common::types::VerificationData;
890+
use std::fs;
891+
892+
let proof_generator_addr = Address::random();
893+
let payment_service_addr = Address::random();
894+
let chain_id = U256::from(42);
895+
let dummy_signature = Signature {
896+
r: U256::from(1),
897+
s: U256::from(2),
898+
v: 3,
899+
};
900+
901+
// Load actual SP1 proof files
902+
let proof_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.proof";
903+
let elf_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.elf";
904+
let pub_input_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.pub";
905+
906+
let proof_data = match fs::read(proof_path) {
907+
Ok(data) => data,
908+
Err(_) => return, // Skip test if files not found
909+
};
910+
911+
let elf_data = match fs::read(elf_path) {
912+
Ok(data) => data,
913+
Err(_) => return,
914+
};
915+
916+
let pub_input_data = match fs::read(pub_input_path) {
917+
Ok(data) => data,
918+
Err(_) => return,
919+
};
920+
921+
let verification_data = VerificationData {
922+
proving_system: ProvingSystemId::SP1,
923+
proof: proof_data,
924+
pub_input: Some(pub_input_data),
925+
verification_key: None,
926+
vm_program_code: Some(elf_data),
927+
proof_generator_addr,
928+
};
929+
930+
// Create 10 entries using the same SP1 proof data
931+
let mut batch_queue = BatchQueue::new();
932+
let max_fee = U256::from(1_000_000_000_000_000u128);
933+
934+
for i in 0..10 {
935+
let sender_addr = Address::random();
936+
let nonce = U256::from(i + 1);
937+
938+
let nonced_verification_data = NoncedVerificationData::new(
939+
verification_data.clone(),
940+
nonce,
941+
max_fee,
942+
chain_id,
943+
payment_service_addr,
944+
);
945+
946+
let vd_commitment: VerificationDataCommitment = nonced_verification_data.clone().into();
947+
let entry = BatchQueueEntry::new_for_testing(
948+
nonced_verification_data,
949+
vd_commitment,
950+
dummy_signature,
951+
sender_addr,
952+
);
953+
let batch_priority = BatchQueueEntryPriority::new(max_fee, nonce);
954+
batch_queue.push(entry, batch_priority);
955+
}
956+
957+
// Test with a 5MB batch size limit
958+
let batch_size_limit = 5_000_000; // 5MB
959+
let gas_price = U256::from(1);
960+
961+
let finalized_batch = try_build_batch(
962+
batch_queue.clone(),
963+
gas_price,
964+
batch_size_limit,
965+
50, // max proof qty
966+
DEFAULT_CONSTANT_GAS_COST,
967+
).unwrap();
968+
969+
// Verify the finalized batch respects the size limit
970+
let finalized_verification_data: Vec<VerificationData> = finalized_batch
971+
.iter()
972+
.map(|entry| entry.nonced_verification_data.verification_data.clone())
973+
.collect();
974+
975+
let finalized_serialized = cbor_serialize(&finalized_verification_data).unwrap();
976+
let finalized_actual_size = finalized_serialized.len();
977+
978+
// Assert the batch respects the limit
979+
assert!(finalized_actual_size <= batch_size_limit,
980+
"Finalized batch size {} exceeds limit {}", finalized_actual_size, batch_size_limit);
981+
982+
// Verify some entries were included (not empty batch)
983+
assert!(!finalized_batch.is_empty(), "Batch should not be empty");
984+
985+
// Verify not all entries were included (some should be rejected due to size limit)
986+
assert!(finalized_batch.len() < 10, "Batch should not include all entries due to size limit");
987+
}
988+
989+
#[test]
990+
fn test_cbor_size_upper_bound_accuracy() {
991+
use aligned_sdk::communication::serialization::cbor_serialize;
992+
use aligned_sdk::common::types::VerificationData;
993+
use std::fs;
994+
995+
let proof_generator_addr = Address::random();
996+
let payment_service_addr = Address::random();
997+
let chain_id = U256::from(42);
998+
999+
// Load actual SP1 proof files
1000+
let proof_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.proof";
1001+
let elf_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.elf";
1002+
let pub_input_path = "../../scripts/test_files/sp1/sp1_fibonacci_5_0_0.pub";
1003+
1004+
let proof_data = match fs::read(proof_path) {
1005+
Ok(data) => data,
1006+
Err(_) => return, // Skip test if files not found
1007+
};
1008+
1009+
let elf_data = match fs::read(elf_path) {
1010+
Ok(data) => data,
1011+
Err(_) => return,
1012+
};
1013+
1014+
let pub_input_data = match fs::read(pub_input_path) {
1015+
Ok(data) => data,
1016+
Err(_) => return,
1017+
};
1018+
1019+
let verification_data = VerificationData {
1020+
proving_system: ProvingSystemId::SP1,
1021+
proof: proof_data,
1022+
pub_input: Some(pub_input_data),
1023+
verification_key: None,
1024+
vm_program_code: Some(elf_data),
1025+
proof_generator_addr,
1026+
};
1027+
1028+
let nonced_verification_data = NoncedVerificationData::new(
1029+
verification_data.clone(),
1030+
U256::from(1),
1031+
U256::from(1_000_000_000_000_000u128),
1032+
chain_id,
1033+
payment_service_addr,
1034+
);
1035+
1036+
// Test cbor_size_upper_bound() accuracy
1037+
let estimated_size = nonced_verification_data.cbor_size_upper_bound();
1038+
1039+
// Compare with actual CBOR serialization of the inner VerificationData
1040+
let actual_serialized = cbor_serialize(&verification_data).unwrap();
1041+
let actual_size = actual_serialized.len();
1042+
1043+
// Also test serializing the NoncedVerificationData
1044+
let actual_nonced_serialized = cbor_serialize(&nonced_verification_data).unwrap();
1045+
let actual_nonced_size = actual_nonced_serialized.len();
1046+
1047+
// Verify CBOR encodes binary data efficiently (with serde_bytes fix)
1048+
let raw_total = verification_data.proof.len() +
1049+
verification_data.vm_program_code.as_ref().unwrap().len() +
1050+
verification_data.pub_input.as_ref().unwrap().len();
1051+
1052+
let cbor_efficiency_ratio = actual_size as f64 / raw_total as f64;
1053+
1054+
// With serde_bytes, CBOR should be very efficient (close to 1.0x)
1055+
assert!(cbor_efficiency_ratio < 1.1,
1056+
"CBOR serialization should be efficient with serde_bytes. Ratio: {:.3}x",
1057+
cbor_efficiency_ratio);
1058+
1059+
// Verify CBOR uses byte strings, not arrays
1060+
let proof_cbor = cbor_serialize(&verification_data.proof).unwrap();
1061+
let first_byte = proof_cbor[0];
1062+
let major_type = (first_byte >> 5) & 0x07;
1063+
1064+
assert_eq!(major_type, 2, "Proof should be encoded as CBOR byte string (major type 2), got {}", major_type);
1065+
1066+
// The estimation should be an upper bound
1067+
assert!(estimated_size >= actual_size,
1068+
"cbor_size_upper_bound() should be an upper bound. Estimated: {}, Actual: {}",
1069+
estimated_size, actual_size);
1070+
1071+
// The estimation should also be reasonable (not wildly over-estimated)
1072+
let estimation_overhead = estimated_size as f64 / actual_size as f64;
1073+
assert!(estimation_overhead < 2.0,
1074+
"Estimation should be reasonable, not wildly over-estimated. Overhead: {:.3}x",
1075+
estimation_overhead);
1076+
}
8851077
}

crates/sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ tokio = { version = "1.37.0", features = [
1919
] }
2020
lambdaworks-crypto = { git = "https://github.com/lambdaclass/lambdaworks.git", rev = "5f8f2cfcc8a1a22f77e8dff2d581f1166eefb80b", features = ["serde"]}
2121
serde = { version = "1.0.201", features = ["derive"] }
22+
serde_bytes = "0.11"
2223
sha3 = { version = "0.10.8" }
2324
url = "2.5.0"
2425
hex = "0.4.3"

crates/sdk/src/common/types.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,16 @@ impl Display for ProvingSystemId {
6565
#[derive(Debug, Serialize, Deserialize, Clone)]
6666
pub struct VerificationData {
6767
pub proving_system: ProvingSystemId,
68+
#[serde(with = "serde_bytes")]
6869
pub proof: Vec<u8>,
70+
#[serde(default, skip_serializing_if = "Option::is_none")]
71+
#[serde(deserialize_with = "deserialize_option_bytes", serialize_with = "serialize_option_bytes")]
6972
pub pub_input: Option<Vec<u8>>,
73+
#[serde(default, skip_serializing_if = "Option::is_none")]
74+
#[serde(deserialize_with = "deserialize_option_bytes", serialize_with = "serialize_option_bytes")]
7075
pub verification_key: Option<Vec<u8>>,
76+
#[serde(default, skip_serializing_if = "Option::is_none")]
77+
#[serde(deserialize_with = "deserialize_option_bytes", serialize_with = "serialize_option_bytes")]
7178
pub vm_program_code: Option<Vec<u8>>,
7279
pub proof_generator_addr: Address,
7380
}
@@ -502,6 +509,30 @@ impl Network {
502509
}
503510
}
504511

512+
// Helper functions for serializing Option<Vec<u8>> with serde_bytes
513+
fn serialize_option_bytes<S>(
514+
value: &Option<Vec<u8>>,
515+
serializer: S,
516+
) -> Result<S::Ok, S::Error>
517+
where
518+
S: serde::Serializer,
519+
{
520+
match value {
521+
Some(bytes) => serde_bytes::serialize(bytes, serializer),
522+
None => serializer.serialize_none(),
523+
}
524+
}
525+
526+
fn deserialize_option_bytes<'de, D>(
527+
deserializer: D,
528+
) -> Result<Option<Vec<u8>>, D::Error>
529+
where
530+
D: serde::Deserializer<'de>,
531+
{
532+
let opt: Option<serde_bytes::ByteBuf> = Option::deserialize(deserializer)?;
533+
Ok(opt.map(|buf| buf.into_vec()))
534+
}
535+
505536
#[cfg(test)]
506537
mod tests {
507538
use ethers::signers::LocalWallet;

0 commit comments

Comments
 (0)