iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 &section.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 &section.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 }