iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

block.rs (22296B)


      1 use std::collections::BTreeMap;
      2 
      3 use anyhow::{Context, Result};
      4 use serde::{Deserialize, Serialize};
      5 
      6 use super::{
      7     Amount, BURN_COMMITTEE_SIZE, BurnBundleSection, BurnTicket, LaunchProfile, LeaderScore,
      8     Transaction, Wallet, hex_hash, recovery_vdf_seed_for_child, vdf_content_commitment,
      9     vdf_seed_for_child,
     10 };
     11 
     12 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
     13 pub struct Block {
     14     pub height: u64,
     15     pub prev_hash: String,
     16     pub timestamp_ms: u64,
     17     pub miner: String,
     18     #[serde(default, skip_serializing_if = "Option::is_none")]
     19     pub reward_address: Option<String>,
     20     #[serde(default, skip_serializing_if = "Option::is_none")]
     21     pub reward_address_signature: Option<String>,
     22     #[serde(default)]
     23     pub finalizer_mode: FinalizerMode,
     24     #[serde(default)]
     25     pub finalizer_rank: u32,
     26     pub reward: Amount,
     27     pub vdf_rounds: u64,
     28     pub vdf_output: String,
     29     pub leader_proof: Option<LeaderProof>,
     30     #[serde(default)]
     31     pub burn_bundle_section: BurnBundleSection,
     32     pub transactions: Vec<Transaction>,
     33     /// Canonical hex-encoded transaction-v2 wire envelopes.
     34     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     35     pub transactions_v2: Vec<String>,
     36     pub hash: String,
     37 }
     38 
     39 #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
     40 #[serde(rename_all = "snake_case")]
     41 pub enum FinalizerMode {
     42     #[default]
     43     Ticket,
     44     Recovery,
     45 }
     46 
     47 impl Block {
     48     pub fn compute_hash(&self) -> String {
     49         hex_hash(format!(
     50             "block:{}:{}:{}",
     51             self.content_hash(),
     52             self.vdf_seed(),
     53             self.vdf_output,
     54         ))
     55     }
     56 
     57     pub fn vdf_seed(&self) -> String {
     58         let bundle_hashes = self.burn_bundle_hashes();
     59         let content_commitment = self.vdf_content_commitment();
     60         match self.finalizer_mode {
     61             FinalizerMode::Ticket => vdf_seed_for_child(
     62                 &self.prev_hash,
     63                 self.height,
     64                 &bundle_hashes,
     65                 &content_commitment,
     66             ),
     67             FinalizerMode::Recovery => recovery_vdf_seed_for_child(
     68                 &self.prev_hash,
     69                 self.height,
     70                 self.timestamp_ms,
     71                 &bundle_hashes,
     72                 &content_commitment,
     73             ),
     74         }
     75     }
     76 
     77     fn vdf_content_commitment(&self) -> String {
     78         vdf_content_commitment(
     79             self.height,
     80             &self.prev_hash,
     81             &self.miner,
     82             self.reward_address.as_deref(),
     83             self.finalizer_mode,
     84             self.finalizer_rank,
     85             self.reward,
     86             self.vdf_rounds,
     87             self.leader_proof
     88                 .as_ref()
     89                 .map(|proof| proof.ticket_id.as_str()),
     90             &self.burn_bundle_section,
     91             &self.transactions,
     92             &self.transactions_v2,
     93         )
     94     }
     95 
     96     fn content_hash(&self) -> String {
     97         let txs = self
     98             .transactions
     99             .iter()
    100             .map(Transaction::canonical)
    101             .collect::<Vec<_>>()
    102             .join("|");
    103         let txs_v2 = self.transactions_v2.join("|");
    104         let burn_section = self.burn_bundle_section.canonical();
    105         let leader_proof = self
    106             .leader_proof
    107             .as_ref()
    108             .map(|proof| {
    109                 format!(
    110                     "{}:{}:{}",
    111                     proof.ticket_id, proof.public_key, proof.signature
    112                 )
    113             })
    114             .unwrap_or_default();
    115         if let Some(reward_address) = &self.reward_address {
    116             let reward_address_signature = self.reward_address_signature.as_deref().unwrap_or("");
    117             return hex_hash(format!(
    118                 "block-content-v6:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
    119                 self.height,
    120                 self.prev_hash,
    121                 self.timestamp_ms,
    122                 self.miner,
    123                 reward_address,
    124                 reward_address_signature,
    125                 self.finalizer_rank,
    126                 self.reward,
    127                 self.vdf_rounds,
    128                 leader_proof,
    129                 txs,
    130                 txs_v2,
    131                 canonical_burn_block_items(&burn_section)
    132             ));
    133         }
    134         if self.transactions_v2.is_empty() {
    135             return hex_hash(format!(
    136                 "block-content-v4:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
    137                 self.height,
    138                 self.prev_hash,
    139                 self.timestamp_ms,
    140                 self.miner,
    141                 self.finalizer_rank,
    142                 self.reward,
    143                 self.vdf_rounds,
    144                 leader_proof,
    145                 txs,
    146                 canonical_burn_block_items(&burn_section)
    147             ));
    148         }
    149         hex_hash(format!(
    150             "block-content-v5:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
    151             self.height,
    152             self.prev_hash,
    153             self.timestamp_ms,
    154             self.miner,
    155             self.finalizer_rank,
    156             self.reward,
    157             self.vdf_rounds,
    158             leader_proof,
    159             txs,
    160             txs_v2,
    161             canonical_burn_block_items(&burn_section)
    162         ))
    163     }
    164 
    165     pub(super) fn leader_score(&self) -> LeaderScore {
    166         LeaderScore {
    167             finalizer_mode_rank: self.finalizer_mode.fork_choice_rank(),
    168             finalizer_rank: self.finalizer_rank,
    169             proof_rank: self
    170                 .leader_proof
    171                 .as_ref()
    172                 .map(LeaderProof::rank)
    173                 .unwrap_or_else(|| self.hash.clone()),
    174         }
    175     }
    176 
    177     pub fn json_size_bytes(&self) -> Result<usize> {
    178         serde_json::to_vec(self)
    179             .map(|bytes| bytes.len())
    180             .context("failed to serialize block as JSON for size check")
    181     }
    182 
    183     pub fn required_burns(&self) -> Vec<&Transaction> {
    184         self.burn_bundle_section.required_burns()
    185     }
    186 
    187     pub fn burn_bundle_hashes(&self) -> [String; BURN_COMMITTEE_SIZE] {
    188         self.burn_bundle_section
    189             .burn_bundle_hashes(self.height, &self.prev_hash, &self.miner)
    190     }
    191 
    192     pub fn included_burn_bundle_count(&self) -> usize {
    193         self.burn_bundle_section.included_bundle_count()
    194     }
    195 }
    196 
    197 impl FinalizerMode {
    198     fn fork_choice_rank(self) -> u8 {
    199         match self {
    200             Self::Ticket => 0,
    201             Self::Recovery => 1,
    202         }
    203     }
    204 }
    205 
    206 fn canonical_burn_block_items(burn_section: &str) -> String {
    207     format!("burn-section-v1:{burn_section}")
    208 }
    209 
    210 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
    211 pub struct LeaderProof {
    212     pub ticket_id: String,
    213     pub public_key: String,
    214     pub signature: String,
    215 }
    216 
    217 impl LeaderProof {
    218     fn rank(&self) -> String {
    219         hex_hash(format!(
    220             "iuna-leader-rank:{}:{}",
    221             self.ticket_id, self.signature
    222         ))
    223     }
    224 }
    225 
    226 #[derive(Clone, Debug, Eq, PartialEq)]
    227 pub(super) struct LeaderProofPayload {
    228     pub(super) height: u64,
    229     pub(super) prev_hash: String,
    230     pub(super) finalizer_rank: u32,
    231     pub(super) vdf_output: String,
    232     pub(super) ticket_id: String,
    233     pub(super) ticket_amount: Amount,
    234     pub(super) ticket_owner: String,
    235 }
    236 
    237 impl LeaderProofPayload {
    238     pub(super) fn canonical(&self) -> String {
    239         if self.finalizer_rank == 0 {
    240             format!(
    241                 "iuna-leader-proof:{}:{}:{}:{}:{}:{}",
    242                 self.height,
    243                 self.prev_hash,
    244                 self.vdf_output,
    245                 self.ticket_id,
    246                 self.ticket_amount,
    247                 self.ticket_owner
    248             )
    249         } else {
    250             format!(
    251                 "iuna-leader-proof-v2:{}:{}:{}:{}:{}:{}:{}",
    252                 self.height,
    253                 self.prev_hash,
    254                 self.finalizer_rank,
    255                 self.vdf_output,
    256                 self.ticket_id,
    257                 self.ticket_amount,
    258                 self.ticket_owner
    259             )
    260         }
    261     }
    262 }
    263 
    264 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
    265 #[serde(rename_all = "camelCase")]
    266 pub struct BurnLeaderRank {
    267     pub rank: u32,
    268     pub ticket_id: String,
    269     pub owner: String,
    270     pub amount: Amount,
    271     pub eligible_from_height: u64,
    272     pub eligible_until_height: u64,
    273 }
    274 
    275 #[derive(Clone, Debug)]
    276 pub struct PreparedBlock {
    277     pub(super) height: u64,
    278     pub(super) prev_hash: String,
    279     pub(super) timestamp_ms: u64,
    280     pub(super) miner: String,
    281     pub(super) reward_address: Option<String>,
    282     pub(super) finalizer_mode: FinalizerMode,
    283     pub(super) finalizer_rank: u32,
    284     pub(super) reward: Amount,
    285     pub(super) vdf_rounds: u64,
    286     pub(super) vdf_seed: String,
    287     pub(super) leader_ticket: Option<BurnTicket>,
    288     pub(super) burn_bundle_section: BurnBundleSection,
    289     pub(super) transactions: Vec<Transaction>,
    290     pub(super) transactions_v2: Vec<String>,
    291 }
    292 
    293 impl PreparedBlock {
    294     pub fn vdf_seed(&self) -> &str {
    295         &self.vdf_seed
    296     }
    297 
    298     pub fn vdf_rounds(&self) -> u64 {
    299         self.vdf_rounds
    300     }
    301 
    302     pub fn height(&self) -> u64 {
    303         self.height
    304     }
    305 
    306     pub fn prev_hash(&self) -> &str {
    307         &self.prev_hash
    308     }
    309 
    310     pub fn timestamp_ms(&self) -> u64 {
    311         self.timestamp_ms
    312     }
    313 
    314     pub fn finish(self, wallet: &Wallet, vdf_output: String) -> Block {
    315         let timestamp_ms = self.timestamp_ms;
    316         self.finish_with_timestamp(wallet, vdf_output, timestamp_ms)
    317     }
    318 
    319     pub fn finish_at(self, wallet: &Wallet, vdf_output: String, timestamp_ms: u64) -> Block {
    320         let timestamp_ms = match self.finalizer_mode {
    321             FinalizerMode::Ticket => timestamp_ms.max(self.timestamp_ms),
    322             FinalizerMode::Recovery => self.timestamp_ms,
    323         };
    324         self.finish_with_timestamp(wallet, vdf_output, timestamp_ms)
    325     }
    326 
    327     fn finish_with_timestamp(
    328         self,
    329         wallet: &Wallet,
    330         vdf_output: String,
    331         timestamp_ms: u64,
    332     ) -> Block {
    333         let leader_proof = self.leader_ticket.as_ref().map(|leader_ticket| {
    334             let proof_payload = LeaderProofPayload {
    335                 height: self.height,
    336                 prev_hash: self.prev_hash.clone(),
    337                 finalizer_rank: self.finalizer_rank,
    338                 vdf_output: vdf_output.clone(),
    339                 ticket_id: leader_ticket.id.clone(),
    340                 ticket_amount: leader_ticket.amount,
    341                 ticket_owner: leader_ticket.owner.clone(),
    342             };
    343             wallet.leader_proof(&proof_payload)
    344         });
    345         let reward_address_signature = self.reward_address.as_ref().map(|reward_address| {
    346             wallet.sign_payload(&reward_address_payload(
    347                 self.height,
    348                 &self.prev_hash,
    349                 &self.miner,
    350                 reward_address,
    351             ))
    352         });
    353         let mut block = Block {
    354             height: self.height,
    355             prev_hash: self.prev_hash,
    356             timestamp_ms,
    357             miner: self.miner,
    358             reward_address: self.reward_address,
    359             reward_address_signature,
    360             finalizer_mode: self.finalizer_mode,
    361             finalizer_rank: self.finalizer_rank,
    362             reward: self.reward,
    363             vdf_rounds: self.vdf_rounds,
    364             vdf_output,
    365             leader_proof,
    366             burn_bundle_section: self.burn_bundle_section,
    367             transactions: self.transactions,
    368             transactions_v2: self.transactions_v2,
    369             hash: String::new(),
    370         };
    371         block.hash = block.compute_hash();
    372         block
    373     }
    374 }
    375 
    376 pub(super) fn reward_address_payload(
    377     height: u64,
    378     prev_hash: &str,
    379     miner: &str,
    380     reward_address: &str,
    381 ) -> String {
    382     format!("iuna-hybrid-reward-v1:{height}:{prev_hash}:{miner}:{reward_address}")
    383 }
    384 
    385 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
    386 pub struct ChainStatus {
    387     pub height: u64,
    388     pub tip_hash: String,
    389     pub finalized_height: Option<u64>,
    390     pub finalized_hash: Option<String>,
    391     pub next_leader: Option<String>,
    392     pub launch_profile_hash: String,
    393     pub mine_reward: Amount,
    394     pub current_mine_difficulty_bits: u32,
    395     pub balances: BTreeMap<String, Amount>,
    396     pub pending_transactions: usize,
    397 }
    398 
    399 #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
    400 pub struct ChainSnapshot {
    401     pub genesis_allocations: BTreeMap<String, Amount>,
    402     pub vdf_rounds: u64,
    403     pub launch_profile: LaunchProfile,
    404     pub blocks: Vec<Block>,
    405 }
    406 
    407 #[cfg(test)]
    408 mod tests {
    409     use super::{
    410         Block, BurnBundleSection, FinalizerMode, LeaderProofPayload, canonical_burn_block_items,
    411     };
    412     use crate::domain::{
    413         BurnTicket, GRINDING_RESISTANCE_ACTIVATION_HEIGHT, PreparedBlock, Transaction, Wallet,
    414         run_vdf, verify_vdf,
    415     };
    416 
    417     #[test]
    418     fn primary_leader_proof_payload_omits_rank_from_canonical_form() {
    419         let primary = LeaderProofPayload {
    420             height: 1,
    421             prev_hash: "prev".to_string(),
    422             finalizer_rank: 0,
    423             vdf_output: "vdf".to_string(),
    424             ticket_id: "ticket".to_string(),
    425             ticket_amount: 2,
    426             ticket_owner: "owner".to_string(),
    427         };
    428         let fallback = LeaderProofPayload {
    429             finalizer_rank: 1,
    430             ..primary.clone()
    431         };
    432 
    433         assert_eq!(
    434             primary.canonical(),
    435             "iuna-leader-proof:1:prev:vdf:ticket:2:owner"
    436         );
    437         assert_eq!(
    438             fallback.canonical(),
    439             "iuna-leader-proof-v2:1:prev:1:vdf:ticket:2:owner"
    440         );
    441     }
    442 
    443     #[test]
    444     fn burn_block_items_keep_canonical_prefix() {
    445         assert_eq!(
    446             canonical_burn_block_items("section"),
    447             "burn-section-v1:section"
    448         );
    449     }
    450 
    451     #[test]
    452     fn block_compute_hash_sets_content_and_vdf_seed_contract() {
    453         let mut block = Block {
    454             height: 1,
    455             prev_hash: "0".repeat(64),
    456             timestamp_ms: 1,
    457             miner: "miner".to_string(),
    458             reward_address: None,
    459             reward_address_signature: None,
    460             finalizer_mode: FinalizerMode::Ticket,
    461             finalizer_rank: 0,
    462             reward: 0,
    463             vdf_rounds: 1,
    464             vdf_output: "out".to_string(),
    465             leader_proof: None,
    466             burn_bundle_section: BurnBundleSection::default(),
    467             transactions: vec![Transaction::genesis_burn("owner", 1)],
    468             transactions_v2: Vec::new(),
    469             hash: String::new(),
    470         };
    471 
    472         block.hash = block.compute_hash();
    473 
    474         assert_eq!(block.compute_hash(), block.hash);
    475     }
    476 
    477     #[test]
    478     fn post_activation_vdf_binds_transactions_but_allows_ticket_timestamp_completion() {
    479         let mut block = Block {
    480             height: GRINDING_RESISTANCE_ACTIVATION_HEIGHT,
    481             prev_hash: "0".repeat(64),
    482             timestamp_ms: 1,
    483             miner: "1".repeat(64),
    484             reward_address: None,
    485             reward_address_signature: None,
    486             finalizer_mode: FinalizerMode::Ticket,
    487             finalizer_rank: 0,
    488             reward: 1,
    489             vdf_rounds: 16,
    490             vdf_output: String::new(),
    491             leader_proof: None,
    492             burn_bundle_section: BurnBundleSection::default(),
    493             transactions: vec![Transaction::genesis_burn("2".repeat(64), 1)],
    494             transactions_v2: Vec::new(),
    495             hash: String::new(),
    496         };
    497         let original_seed = block.vdf_seed();
    498         let output = run_vdf(&original_seed, block.vdf_rounds);
    499         block.vdf_output = output.clone();
    500         assert!(verify_vdf(&block.vdf_seed(), block.vdf_rounds, &output));
    501 
    502         let mut changed_transactions = block.clone();
    503         changed_transactions.transactions = vec![Transaction::genesis_burn("3".repeat(64), 1)];
    504         assert_ne!(changed_transactions.vdf_seed(), original_seed);
    505         assert!(!verify_vdf(
    506             &changed_transactions.vdf_seed(),
    507             changed_transactions.vdf_rounds,
    508             &output
    509         ));
    510 
    511         let mut completed_later = block.clone();
    512         completed_later.timestamp_ms += 1_000;
    513         assert_eq!(completed_later.vdf_seed(), original_seed);
    514         assert!(verify_vdf(
    515             &completed_later.vdf_seed(),
    516             completed_later.vdf_rounds,
    517             &output
    518         ));
    519     }
    520 
    521     #[test]
    522     fn transaction_v2_envelopes_are_bound_to_vdf_and_block_hash() {
    523         let mut block = Block {
    524             height: GRINDING_RESISTANCE_ACTIVATION_HEIGHT,
    525             prev_hash: "0".repeat(64),
    526             timestamp_ms: 1,
    527             miner: "1".repeat(64),
    528             reward_address: None,
    529             reward_address_signature: None,
    530             finalizer_mode: FinalizerMode::Ticket,
    531             finalizer_rank: 0,
    532             reward: 1,
    533             vdf_rounds: 1,
    534             vdf_output: "output".to_string(),
    535             leader_proof: None,
    536             burn_bundle_section: BurnBundleSection::default(),
    537             transactions: vec![Transaction::genesis_burn("2".repeat(64), 1)],
    538             transactions_v2: vec!["00".to_string()],
    539             hash: String::new(),
    540         };
    541         block.hash = block.compute_hash();
    542         let original_seed = block.vdf_seed();
    543         let original_hash = block.hash.clone();
    544 
    545         block.transactions_v2[0] = "01".to_string();
    546 
    547         assert_ne!(block.vdf_seed(), original_seed);
    548         assert_ne!(block.compute_hash(), original_hash);
    549     }
    550 
    551     #[test]
    552     fn prepared_and_finished_blocks_share_post_activation_vdf_commitment() {
    553         let wallet = Wallet::from_seed("vdf-commitment-finalizer");
    554         let transactions = vec![Transaction::genesis_burn(wallet.address(), 1)];
    555         let mut prepared = PreparedBlock {
    556             height: GRINDING_RESISTANCE_ACTIVATION_HEIGHT,
    557             prev_hash: "0".repeat(64),
    558             timestamp_ms: 10,
    559             miner: wallet.address().to_string(),
    560             reward_address: None,
    561             finalizer_mode: FinalizerMode::Ticket,
    562             finalizer_rank: 0,
    563             reward: 1,
    564             vdf_rounds: 1,
    565             vdf_seed: String::new(),
    566             leader_ticket: Some(BurnTicket {
    567                 id: "4".repeat(64),
    568                 owner: wallet.address().to_string(),
    569                 amount: 1,
    570                 eligible_from_height: GRINDING_RESISTANCE_ACTIVATION_HEIGHT,
    571                 eligible_until_height: GRINDING_RESISTANCE_ACTIVATION_HEIGHT,
    572             }),
    573             burn_bundle_section: BurnBundleSection::default(),
    574             transactions,
    575             transactions_v2: Vec::new(),
    576         };
    577         let bundle_hashes = prepared.burn_bundle_section.burn_bundle_hashes(
    578             prepared.height,
    579             &prepared.prev_hash,
    580             &prepared.miner,
    581         );
    582         let commitment = super::vdf_content_commitment(
    583             prepared.height,
    584             &prepared.prev_hash,
    585             &prepared.miner,
    586             prepared.reward_address.as_deref(),
    587             prepared.finalizer_mode,
    588             prepared.finalizer_rank,
    589             prepared.reward,
    590             prepared.vdf_rounds,
    591             prepared
    592                 .leader_ticket
    593                 .as_ref()
    594                 .map(|ticket| ticket.id.as_str()),
    595             &prepared.burn_bundle_section,
    596             &prepared.transactions,
    597             &prepared.transactions_v2,
    598         );
    599         prepared.vdf_seed = super::vdf_seed_for_child(
    600             &prepared.prev_hash,
    601             prepared.height,
    602             &bundle_hashes,
    603             &commitment,
    604         );
    605         let expected_seed = prepared.vdf_seed.clone();
    606         let output = run_vdf(&expected_seed, prepared.vdf_rounds);
    607 
    608         let block = prepared.finish_at(&wallet, output, 20);
    609 
    610         assert_eq!(block.vdf_seed(), expected_seed);
    611     }
    612 
    613     #[test]
    614     fn json_block_size_uses_canonical_node_representation() {
    615         let compact_wire_json = format!(
    616             r#"{{"height":1,"prev_hash":"{}","timestamp_ms":1,"miner":"{}","reward":0,"vdf_rounds":1,"vdf_output":"out","leader_proof":null,"transactions":[],"hash":"{}"}}"#,
    617             "0".repeat(64),
    618             "1".repeat(64),
    619             "2".repeat(64)
    620         );
    621 
    622         let block: Block = serde_json::from_str(&compact_wire_json).unwrap();
    623         let canonical_json_len = serde_json::to_vec(&block).unwrap().len();
    624 
    625         assert_eq!(block.finalizer_mode, FinalizerMode::Ticket);
    626         assert_eq!(block.finalizer_rank, 0);
    627         assert_eq!(block.burn_bundle_section, BurnBundleSection::default());
    628         assert_eq!(block.json_size_bytes().unwrap(), canonical_json_len);
    629         assert!(canonical_json_len > compact_wire_json.len());
    630     }
    631 
    632     #[test]
    633     fn activated_reward_address_is_authenticated_by_finalizer() {
    634         let wallet = Wallet::from_seed("hybrid-reward-finalizer");
    635         let reward_address = wallet.hybrid_address(crate::domain::AddressNetwork::Mainnet);
    636         let prepared = PreparedBlock {
    637             height: crate::domain::HYBRID_REWARD_ACTIVATION_HEIGHT,
    638             prev_hash: "0".repeat(64),
    639             timestamp_ms: 10,
    640             miner: wallet.address().to_string(),
    641             reward_address: Some(reward_address.clone()),
    642             finalizer_mode: FinalizerMode::Recovery,
    643             finalizer_rank: 0,
    644             reward: 1,
    645             vdf_rounds: 1,
    646             vdf_seed: "seed".to_string(),
    647             leader_ticket: None,
    648             burn_bundle_section: BurnBundleSection::default(),
    649             transactions: Vec::new(),
    650             transactions_v2: Vec::new(),
    651         };
    652 
    653         let block = prepared.finish(&wallet, "vdf".to_string());
    654         let signature = block.reward_address_signature.as_deref().unwrap();
    655         crate::domain::ledger_ops::verify_address_signature(
    656             wallet.address(),
    657             &super::reward_address_payload(
    658                 block.height,
    659                 &block.prev_hash,
    660                 &block.miner,
    661                 &reward_address,
    662             ),
    663             signature,
    664             "reward address",
    665         )
    666         .unwrap();
    667         assert!(
    668             crate::domain::ledger_ops::verify_address_signature(
    669                 wallet.address(),
    670                 &super::reward_address_payload(
    671                     block.height,
    672                     &block.prev_hash,
    673                     &block.miner,
    674                     "iuna1ptampered",
    675                 ),
    676                 signature,
    677                 "reward address",
    678             )
    679             .is_err()
    680         );
    681     }
    682 }