compact.rs (17437B)
1 use anyhow::{Context, Result, bail}; 2 3 use crate::domain::{ 4 BlindedReveal, BlindedTransaction, Block, ChainSnapshot, FinalizerMode, LaunchProfile, 5 LeaderProof, MaskedBlindedReveal, OutPoint, RevealBundleSection, RevealBundleSignature, 6 Transaction, TxInput, TxOutput, 7 }; 8 9 const COMPACT_SNAPSHOT_MAGIC: &[u8] = b"IUNA-SNAPSHOT"; 10 const COMPACT_SNAPSHOT_VERSION: u8 = 3; 11 12 pub(super) fn encode_compact_snapshot(snapshot: &ChainSnapshot) -> Result<Vec<u8>> { 13 let mut writer = CompactWriter::default(); 14 writer.bytes(COMPACT_SNAPSHOT_MAGIC); 15 writer.u8(COMPACT_SNAPSHOT_VERSION); 16 writer.varint(snapshot.genesis_allocations.len() as u64); 17 for (address, amount) in &snapshot.genesis_allocations { 18 writer.hex(address)?; 19 writer.varint(*amount); 20 } 21 writer.varint(snapshot.vdf_rounds); 22 encode_launch_profile(&mut writer, &snapshot.launch_profile); 23 writer.varint(snapshot.blocks.len() as u64); 24 let mut expected_prev_hash = "0".repeat(64); 25 for (height, block) in snapshot.blocks.iter().enumerate() { 26 if block.height != height as u64 { 27 bail!( 28 "chain snapshot block height {} does not match compact position {}", 29 block.height, 30 height 31 ); 32 } 33 if block.prev_hash != expected_prev_hash { 34 bail!( 35 "chain snapshot block {} has non-canonical previous hash", 36 height 37 ); 38 } 39 encode_block_body(&mut writer, block)?; 40 expected_prev_hash = block.hash.clone(); 41 } 42 Ok(writer.into_inner()) 43 } 44 45 pub(super) fn decode_compact_snapshot(bytes: &[u8]) -> Result<ChainSnapshot> { 46 let mut reader = CompactReader::new(bytes); 47 reader.magic(COMPACT_SNAPSHOT_MAGIC)?; 48 let version = reader.u8()?; 49 if version != COMPACT_SNAPSHOT_VERSION { 50 bail!("unsupported compact chain snapshot version {version}"); 51 } 52 let genesis_count = reader.usize()?; 53 let mut genesis_allocations = std::collections::BTreeMap::new(); 54 for _ in 0..genesis_count { 55 let address = reader.hex()?; 56 let amount = reader.varint()?; 57 genesis_allocations.insert(address, amount); 58 } 59 let vdf_rounds = reader.varint()?; 60 let launch_profile = decode_launch_profile(&mut reader)?; 61 let block_count = reader.usize()?; 62 let mut blocks = Vec::with_capacity(block_count); 63 let mut prev_hash = "0".repeat(64); 64 for height in 0..block_count { 65 let block = decode_block_body(&mut reader, height as u64, prev_hash)?; 66 prev_hash = block.hash.clone(); 67 blocks.push(block); 68 } 69 reader.finish()?; 70 Ok(ChainSnapshot { 71 genesis_allocations, 72 vdf_rounds, 73 launch_profile, 74 blocks, 75 }) 76 } 77 78 fn encode_launch_profile(writer: &mut CompactWriter, profile: &LaunchProfile) { 79 writer.string(&profile.profile_id); 80 writer.varint(profile.ticket_maturity_delay_heights); 81 writer.varint(profile.ticket_expiry_window_heights); 82 writer.varint(u64::from(profile.mine_difficulty_bits)); 83 writer.varint(profile.max_pending_transactions as u64); 84 writer.varint(profile.max_block_transactions as u64); 85 writer.varint(profile.max_block_bytes as u64); 86 } 87 88 fn decode_launch_profile(reader: &mut CompactReader<'_>) -> Result<LaunchProfile> { 89 Ok(LaunchProfile { 90 profile_id: reader.string()?, 91 ticket_maturity_delay_heights: reader.varint()?, 92 ticket_expiry_window_heights: reader.varint()?, 93 mine_difficulty_bits: reader.u32()?, 94 max_pending_transactions: reader.usize()?, 95 max_block_transactions: reader.usize()?, 96 max_block_bytes: reader.usize()?, 97 }) 98 } 99 100 fn encode_block_body(writer: &mut CompactWriter, block: &Block) -> Result<()> { 101 writer.varint(block.timestamp_ms); 102 writer.hex(&block.miner)?; 103 writer.u8(match block.finalizer_mode { 104 FinalizerMode::Ticket => 0, 105 FinalizerMode::Recovery => 1, 106 }); 107 writer.varint(u64::from(block.finalizer_rank)); 108 writer.varint(block.reward); 109 writer.varint(block.vdf_rounds); 110 writer.string(&block.vdf_output); 111 writer.bool(block.leader_proof.is_some()); 112 if let Some(proof) = &block.leader_proof { 113 writer.hexish(&proof.ticket_id)?; 114 writer.hex(&proof.public_key)?; 115 writer.hex(&proof.signature)?; 116 } 117 writer.varint(block.blinded_transactions.len() as u64); 118 for transaction in &block.blinded_transactions { 119 encode_blinded_transaction(writer, transaction)?; 120 } 121 encode_reveal_bundle_section(writer, &block.reveal_bundle_section)?; 122 writer.varint(block.transactions.len() as u64); 123 for transaction in &block.transactions { 124 encode_transaction(writer, transaction)?; 125 } 126 writer.hex(&block.hash)?; 127 Ok(()) 128 } 129 130 fn decode_block_body( 131 reader: &mut CompactReader<'_>, 132 height: u64, 133 prev_hash: String, 134 ) -> Result<Block> { 135 let timestamp_ms = reader.varint()?; 136 let miner = reader.hex()?; 137 let finalizer_mode = match reader.u8()? { 138 0 => FinalizerMode::Ticket, 139 1 => FinalizerMode::Recovery, 140 other => bail!("invalid finalizer mode tag {other}"), 141 }; 142 let finalizer_rank = reader.u32()?; 143 let reward = reader.varint()?; 144 let vdf_rounds = reader.varint()?; 145 let vdf_output = reader.string()?; 146 let leader_proof = if reader.bool()? { 147 Some(LeaderProof { 148 ticket_id: reader.hexish()?, 149 public_key: reader.hex()?, 150 signature: reader.hex()?, 151 }) 152 } else { 153 None 154 }; 155 let blinded_transactions = decode_vec(reader, decode_blinded_transaction)?; 156 let reveal_bundle_section = decode_reveal_bundle_section(reader)?; 157 let transactions = decode_vec(reader, decode_transaction)?; 158 let hash = reader.hex()?; 159 Ok(Block { 160 height, 161 prev_hash, 162 timestamp_ms, 163 miner, 164 finalizer_mode, 165 finalizer_rank, 166 reward, 167 vdf_rounds, 168 vdf_output, 169 leader_proof, 170 blinded_transactions, 171 reveal_bundle_section, 172 transactions, 173 hash, 174 }) 175 } 176 177 fn encode_blinded_transaction( 178 writer: &mut CompactWriter, 179 transaction: &BlindedTransaction, 180 ) -> Result<()> { 181 writer.hex(&transaction.commitment)?; 182 encode_inputs(writer, &transaction.inputs)?; 183 writer.varint(transaction.fee); 184 writer.varint(u64::from(transaction.encrypted_size)); 185 writer.varint(transaction.expires_at_height); 186 writer.hex(&transaction.nonce)?; 187 writer.hex(&transaction.ciphertext)?; 188 writer.hex(&transaction.payload_hash)?; 189 Ok(()) 190 } 191 192 fn decode_blinded_transaction(reader: &mut CompactReader<'_>) -> Result<BlindedTransaction> { 193 Ok(BlindedTransaction { 194 commitment: reader.hex()?, 195 inputs: decode_inputs(reader)?, 196 fee: reader.varint()?, 197 encrypted_size: reader.u32()?, 198 expires_at_height: reader.varint()?, 199 nonce: reader.hex()?, 200 ciphertext: reader.hex()?, 201 payload_hash: reader.hex()?, 202 }) 203 } 204 205 fn encode_blinded_reveal(writer: &mut CompactWriter, reveal: &BlindedReveal) -> Result<()> { 206 writer.hex(&reveal.commitment)?; 207 writer.hex(&reveal.key)?; 208 Ok(()) 209 } 210 211 fn decode_blinded_reveal(reader: &mut CompactReader<'_>) -> Result<BlindedReveal> { 212 Ok(BlindedReveal { 213 commitment: reader.hex()?, 214 key: reader.hex()?, 215 }) 216 } 217 218 fn encode_reveal_bundle_section( 219 writer: &mut CompactWriter, 220 section: &RevealBundleSection, 221 ) -> Result<()> { 222 writer.varint(section.signatures.len() as u64); 223 for signature in §ion.signatures { 224 writer.varint(u64::from(signature.slot)); 225 writer.hex(&signature.member)?; 226 writer.hex(&signature.signature)?; 227 } 228 writer.varint(section.reveals.len() as u64); 229 for masked in §ion.reveals { 230 encode_blinded_reveal(writer, &masked.reveal)?; 231 writer.u8(masked.bundle_mask); 232 } 233 Ok(()) 234 } 235 236 fn decode_reveal_bundle_section(reader: &mut CompactReader<'_>) -> Result<RevealBundleSection> { 237 let signatures = decode_vec(reader, |reader| { 238 Ok(RevealBundleSignature { 239 slot: u8::try_from(reader.varint()?).context("reveal bundle slot does not fit u8")?, 240 member: reader.hex()?, 241 signature: reader.hex()?, 242 }) 243 })?; 244 let reveals = decode_vec(reader, |reader| { 245 Ok(MaskedBlindedReveal { 246 reveal: decode_blinded_reveal(reader)?, 247 bundle_mask: reader.u8()?, 248 }) 249 })?; 250 Ok(RevealBundleSection { 251 signatures, 252 reveals, 253 }) 254 } 255 256 fn encode_transaction(writer: &mut CompactWriter, transaction: &Transaction) -> Result<()> { 257 match transaction { 258 Transaction::Transfer { 259 inputs, 260 outputs, 261 fee, 262 signature, 263 } => { 264 writer.u8(0); 265 encode_inputs(writer, inputs)?; 266 encode_outputs(writer, outputs)?; 267 writer.varint(*fee); 268 writer.hex(signature)?; 269 } 270 Transaction::Burn { 271 inputs, 272 change, 273 amount, 274 fee, 275 signature, 276 } => { 277 writer.u8(1); 278 encode_inputs(writer, inputs)?; 279 encode_outputs(writer, change)?; 280 writer.varint(*amount); 281 writer.varint(*fee); 282 writer.hexish(signature)?; 283 } 284 Transaction::Mine { 285 recipient, 286 anchor, 287 salt, 288 nonce, 289 difficulty_bits, 290 proof_header, 291 signature, 292 } => { 293 writer.u8(2); 294 writer.hex(recipient)?; 295 writer.hex(anchor)?; 296 writer.varint(*salt); 297 writer.varint(*nonce); 298 writer.varint(u64::from(*difficulty_bits)); 299 writer.bool(proof_header.is_some()); 300 if let Some(proof_header) = proof_header { 301 writer.hex(proof_header)?; 302 } 303 writer.hex(signature)?; 304 } 305 } 306 Ok(()) 307 } 308 309 fn decode_transaction(reader: &mut CompactReader<'_>) -> Result<Transaction> { 310 match reader.u8()? { 311 0 => Ok(Transaction::Transfer { 312 inputs: decode_inputs(reader)?, 313 outputs: decode_outputs(reader)?, 314 fee: reader.varint()?, 315 signature: reader.hex()?, 316 }), 317 1 => Ok(Transaction::Burn { 318 inputs: decode_inputs(reader)?, 319 change: decode_outputs(reader)?, 320 amount: reader.varint()?, 321 fee: reader.varint()?, 322 signature: reader.hexish()?, 323 }), 324 2 => { 325 let recipient = reader.hex()?; 326 let anchor = reader.hex()?; 327 let salt = reader.varint()?; 328 let nonce = reader.varint()?; 329 let difficulty_bits = reader.u32()?; 330 let proof_header = if reader.bool()? { 331 Some(reader.hex()?) 332 } else { 333 None 334 }; 335 let signature = reader.hex()?; 336 Ok(Transaction::Mine { 337 recipient, 338 anchor, 339 salt, 340 nonce, 341 difficulty_bits, 342 proof_header, 343 signature, 344 }) 345 } 346 other => bail!("invalid transaction tag {other}"), 347 } 348 } 349 350 fn encode_inputs(writer: &mut CompactWriter, inputs: &[TxInput]) -> Result<()> { 351 writer.varint(inputs.len() as u64); 352 for input in inputs { 353 writer.hexish(&input.outpoint.txid)?; 354 writer.varint(u64::from(input.outpoint.index)); 355 writer.hex(&input.owner)?; 356 writer.hexish(&input.signature)?; 357 } 358 Ok(()) 359 } 360 361 fn decode_inputs(reader: &mut CompactReader<'_>) -> Result<Vec<TxInput>> { 362 decode_vec(reader, |reader| { 363 Ok(TxInput { 364 outpoint: OutPoint { 365 txid: reader.hexish()?, 366 index: reader.u32()?, 367 }, 368 owner: reader.hex()?, 369 signature: reader.hexish()?, 370 }) 371 }) 372 } 373 374 fn encode_outputs(writer: &mut CompactWriter, outputs: &[TxOutput]) -> Result<()> { 375 writer.varint(outputs.len() as u64); 376 for output in outputs { 377 writer.hex(&output.address)?; 378 writer.varint(output.amount); 379 } 380 Ok(()) 381 } 382 383 fn decode_outputs(reader: &mut CompactReader<'_>) -> Result<Vec<TxOutput>> { 384 decode_vec(reader, |reader| { 385 Ok(TxOutput { 386 address: reader.hex()?, 387 amount: reader.varint()?, 388 }) 389 }) 390 } 391 392 fn decode_vec<T>( 393 reader: &mut CompactReader<'_>, 394 mut decode: impl FnMut(&mut CompactReader<'_>) -> Result<T>, 395 ) -> Result<Vec<T>> { 396 let len = reader.usize()?; 397 let mut values = Vec::with_capacity(len); 398 for _ in 0..len { 399 values.push(decode(reader)?); 400 } 401 Ok(values) 402 } 403 404 #[derive(Default)] 405 struct CompactWriter { 406 bytes: Vec<u8>, 407 } 408 409 impl CompactWriter { 410 fn into_inner(self) -> Vec<u8> { 411 self.bytes 412 } 413 414 fn bytes(&mut self, bytes: &[u8]) { 415 self.bytes.extend_from_slice(bytes); 416 } 417 418 fn u8(&mut self, value: u8) { 419 self.bytes.push(value); 420 } 421 422 fn bool(&mut self, value: bool) { 423 self.u8(u8::from(value)); 424 } 425 426 fn varint(&mut self, mut value: u64) { 427 while value >= 0x80 { 428 self.u8((value as u8) | 0x80); 429 value >>= 7; 430 } 431 self.u8(value as u8); 432 } 433 434 fn string(&mut self, value: &str) { 435 self.varint(value.len() as u64); 436 self.bytes(value.as_bytes()); 437 } 438 439 fn hex(&mut self, value: &str) -> Result<()> { 440 let bytes = decode_hex(value)?; 441 self.varint(bytes.len() as u64); 442 self.bytes(&bytes); 443 Ok(()) 444 } 445 446 fn hexish(&mut self, value: &str) -> Result<()> { 447 if value.len() % 2 == 0 && value.as_bytes().iter().all(|byte| byte.is_ascii_hexdigit()) { 448 self.u8(1); 449 self.hex(value)?; 450 } else { 451 self.u8(0); 452 self.string(value); 453 } 454 Ok(()) 455 } 456 } 457 458 struct CompactReader<'a> { 459 bytes: &'a [u8], 460 offset: usize, 461 } 462 463 impl<'a> CompactReader<'a> { 464 fn new(bytes: &'a [u8]) -> Self { 465 Self { bytes, offset: 0 } 466 } 467 468 fn finish(&self) -> Result<()> { 469 if self.offset != self.bytes.len() { 470 bail!("compact chain snapshot has trailing bytes"); 471 } 472 Ok(()) 473 } 474 475 fn magic(&mut self, magic: &[u8]) -> Result<()> { 476 let bytes = self.take(magic.len())?; 477 if bytes != magic { 478 bail!("invalid compact chain snapshot magic"); 479 } 480 Ok(()) 481 } 482 483 fn take(&mut self, len: usize) -> Result<&'a [u8]> { 484 let end = self 485 .offset 486 .checked_add(len) 487 .context("compact chain snapshot offset overflow")?; 488 if end > self.bytes.len() { 489 bail!("unexpected end of compact chain snapshot"); 490 } 491 let bytes = &self.bytes[self.offset..end]; 492 self.offset = end; 493 Ok(bytes) 494 } 495 496 fn u8(&mut self) -> Result<u8> { 497 Ok(self.take(1)?[0]) 498 } 499 500 fn bool(&mut self) -> Result<bool> { 501 match self.u8()? { 502 0 => Ok(false), 503 1 => Ok(true), 504 other => bail!("invalid compact bool tag {other}"), 505 } 506 } 507 508 fn varint(&mut self) -> Result<u64> { 509 let mut value = 0_u64; 510 let mut shift = 0_u32; 511 loop { 512 let byte = self.u8()?; 513 value |= u64::from(byte & 0x7f) 514 .checked_shl(shift) 515 .context("compact varint shift overflow")?; 516 if byte & 0x80 == 0 { 517 return Ok(value); 518 } 519 shift += 7; 520 if shift >= 64 { 521 bail!("compact varint is too large"); 522 } 523 } 524 } 525 526 fn usize(&mut self) -> Result<usize> { 527 self.varint()? 528 .try_into() 529 .context("compact integer does not fit usize") 530 } 531 532 fn u32(&mut self) -> Result<u32> { 533 self.varint()? 534 .try_into() 535 .context("compact integer does not fit u32") 536 } 537 538 fn string(&mut self) -> Result<String> { 539 let len = self.usize()?; 540 let bytes = self.take(len)?; 541 String::from_utf8(bytes.to_vec()).context("compact string is not valid UTF-8") 542 } 543 544 fn hex(&mut self) -> Result<String> { 545 let len = self.usize()?; 546 Ok(hex_encode(self.take(len)?)) 547 } 548 549 fn hexish(&mut self) -> Result<String> { 550 match self.u8()? { 551 0 => self.string(), 552 1 => self.hex(), 553 other => bail!("invalid compact hexish tag {other}"), 554 } 555 } 556 } 557 558 fn decode_hex(input: &str) -> Result<Vec<u8>> { 559 if input.len() % 2 != 0 { 560 bail!("hex string has odd length"); 561 } 562 let mut bytes = Vec::with_capacity(input.len() / 2); 563 for pair in input.as_bytes().chunks_exact(2) { 564 let high = hex_value(pair[0])?; 565 let low = hex_value(pair[1])?; 566 bytes.push((high << 4) | low); 567 } 568 Ok(bytes) 569 } 570 571 fn hex_value(byte: u8) -> Result<u8> { 572 match byte { 573 b'0'..=b'9' => Ok(byte - b'0'), 574 b'a'..=b'f' => Ok(byte - b'a' + 10), 575 b'A'..=b'F' => Ok(byte - b'A' + 10), 576 _ => bail!("invalid hex character"), 577 } 578 } 579 580 fn hex_encode(bytes: impl AsRef<[u8]>) -> String { 581 bytes 582 .as_ref() 583 .iter() 584 .map(|byte| format!("{byte:02x}")) 585 .collect() 586 }