tests.rs (7308B)
1 use std::collections::BTreeMap; 2 3 use tempfile::tempdir; 4 5 use crate::domain::{GenesisBurn, Ledger, Wallet, run_vdf}; 6 7 use super::{SqliteChainStore, decode_compact_snapshot, encode_compact_snapshot}; 8 9 #[test] 10 fn sqlite_chain_store_roundtrips_snapshot() { 11 let dir = tempdir().unwrap(); 12 let store = SqliteChainStore::open(dir.path().join("nested/chain.sqlite3")).unwrap(); 13 let wallet = Wallet::from_seed("alice"); 14 let mut genesis = BTreeMap::new(); 15 genesis.insert(wallet.address().to_string(), 1); 16 let ledger = 17 Ledger::new_with_genesis_burns(genesis, vec![GenesisBurn::new(wallet.address(), 1)], 1) 18 .unwrap(); 19 20 store.save(&ledger.snapshot()).unwrap(); 21 22 assert_eq!(store.load().unwrap(), Some(ledger.snapshot())); 23 store 24 .with_connection(|connection| { 25 let columns = connection 26 .prepare("PRAGMA table_info(chain_snapshots)")? 27 .query_map([], |row| row.get::<_, String>(1))? 28 .collect::<std::result::Result<Vec<_>, _>>()?; 29 assert!(columns.contains(&"snapshot_blob".to_string())); 30 assert!(!columns.contains(&"snapshot_json".to_string())); 31 Ok(()) 32 }) 33 .unwrap(); 34 } 35 36 #[test] 37 fn sqlite_chain_store_does_not_create_ui_projection_tables() { 38 let dir = tempdir().unwrap(); 39 let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap(); 40 41 store 42 .with_connection(|connection| { 43 for table in [ 44 "block_metrics", 45 "ui_cache_meta", 46 "ui_output_index", 47 "ui_revealed_transactions", 48 "ui_burn_leader_ranks", 49 "ui_burn_leader_rank_blocks", 50 ] { 51 let count = connection.query_row( 52 "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = ?1", 53 [table], 54 |row| row.get::<_, u64>(0), 55 )?; 56 assert_eq!(count, 0, "{table} should live in ui_data.sqlite3"); 57 } 58 Ok(()) 59 }) 60 .unwrap(); 61 } 62 63 #[test] 64 fn sqlite_chain_store_clear_chain_removes_snapshot() { 65 let dir = tempdir().unwrap(); 66 let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap(); 67 let wallet = Wallet::from_seed("clear-chain-alice"); 68 let mut genesis = BTreeMap::new(); 69 genesis.insert(wallet.address().to_string(), 10); 70 let ledger = 71 Ledger::new_with_genesis_burns(genesis, vec![GenesisBurn::new(wallet.address(), 1)], 1) 72 .unwrap(); 73 74 store.save(&ledger.snapshot()).unwrap(); 75 assert!(store.load().unwrap().is_some()); 76 77 store.clear_chain().unwrap(); 78 79 assert!(store.load().unwrap().is_none()); 80 } 81 82 #[test] 83 fn compact_snapshot_roundtrips_and_is_smaller_than_json() { 84 let alice = Wallet::from_seed("compact-alice"); 85 let bob = Wallet::from_seed("compact-bob"); 86 let carol = Wallet::from_seed("compact-carol"); 87 let wallets = [alice.clone(), bob.clone()]; 88 let mut genesis = BTreeMap::new(); 89 genesis.insert(alice.address().to_string(), 10_000_000); 90 genesis.insert(bob.address().to_string(), 10_000_000); 91 genesis.insert(carol.address().to_string(), 10_000_000); 92 let mut ledger = Ledger::new_with_genesis_burns( 93 genesis, 94 vec![ 95 GenesisBurn::new(alice.address(), 1_000_000), 96 GenesisBurn::new(bob.address(), 1_000_000), 97 ], 98 1, 99 ) 100 .unwrap(); 101 let blinded = ledger 102 .build_blinded_burn(&carol, 3, 7, ledger.height() + 4) 103 .unwrap(); 104 ledger 105 .submit_blinded_transaction(blinded.transaction.clone()) 106 .unwrap(); 107 let leader = ledger.expected_leader_for_next_block().unwrap(); 108 let wallet = wallets 109 .iter() 110 .find(|wallet| wallet.address() == leader) 111 .unwrap(); 112 let burn = ledger.build_burn(wallet, 1, 0).unwrap(); 113 ledger.submit_transaction(burn).unwrap(); 114 let block = ledger.mine_next_block(wallet, 1).unwrap(); 115 assert_eq!( 116 block.blinded_transactions, 117 vec![blinded.transaction.clone()] 118 ); 119 ledger.apply_locally_mined_block(block).unwrap(); 120 ledger.submit_blinded_reveal(blinded.reveal).unwrap(); 121 let leader = ledger.expected_leader_for_next_block().unwrap(); 122 let wallet = wallets 123 .iter() 124 .find(|wallet| wallet.address() == leader) 125 .unwrap(); 126 let burn = ledger.build_burn(wallet, 1, 0).unwrap(); 127 ledger.submit_transaction(burn).unwrap(); 128 let bundles = ledger 129 .reveal_committee_for_next_block() 130 .into_iter() 131 .filter_map(|member| { 132 let wallet = wallets 133 .iter() 134 .find(|wallet| wallet.address() == member.owner) 135 .unwrap(); 136 ledger.build_reveal_bundle(wallet).unwrap() 137 }) 138 .collect::<Vec<_>>(); 139 assert!(!bundles.is_empty()); 140 let prepared = ledger 141 .prepare_next_block_with_reveal_bundles(wallet.address(), 2, bundles) 142 .unwrap(); 143 let vdf_output = run_vdf(prepared.vdf_seed(), prepared.vdf_rounds()); 144 let block = prepared.finish(wallet, vdf_output); 145 assert_eq!(block.all_blinded_reveals().len(), 1); 146 ledger.apply_locally_mined_block(block).unwrap(); 147 148 let snapshot = ledger.snapshot(); 149 let compact = encode_compact_snapshot(&snapshot).unwrap(); 150 let json = serde_json::to_vec(&snapshot).unwrap(); 151 152 assert_eq!(decode_compact_snapshot(&compact).unwrap(), snapshot); 153 assert!( 154 compact.len() < json.len(), 155 "compact snapshot should be smaller than JSON: compact={} JSON={}", 156 compact.len(), 157 json.len() 158 ); 159 } 160 161 #[test] 162 fn sqlite_chain_store_overwrites_latest_snapshot() { 163 let dir = tempdir().unwrap(); 164 let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap(); 165 let wallet = Wallet::from_seed("alice"); 166 let mut genesis = BTreeMap::new(); 167 genesis.insert(wallet.address().to_string(), 2); 168 let mut ledger = 169 Ledger::new_with_genesis_burns(genesis, vec![GenesisBurn::new(wallet.address(), 1)], 1) 170 .unwrap(); 171 store.save(&ledger.snapshot()).unwrap(); 172 173 let burn = ledger.build_burn(&wallet, 1, 0).unwrap(); 174 ledger.submit_transaction(burn).unwrap(); 175 let block = ledger.mine_next_block(&wallet, 1_000).unwrap(); 176 ledger.apply_locally_mined_block(block).unwrap(); 177 store.save(&ledger.snapshot()).unwrap(); 178 179 let restored = store.load().unwrap().unwrap(); 180 assert_eq!(restored.blocks.last().unwrap().height, 1); 181 assert_eq!(restored, ledger.snapshot()); 182 } 183 184 #[test] 185 fn sqlite_chain_store_reports_invalid_compact_snapshot() { 186 let dir = tempdir().unwrap(); 187 let store = SqliteChainStore::open(dir.path().join("chain.sqlite3")).unwrap(); 188 store 189 .with_connection(|connection| { 190 connection.execute( 191 r#" 192 INSERT INTO chain_snapshots (id, height, tip_hash, snapshot_blob, updated_at_ms) 193 VALUES (1, 9, 'bad-tip', x'00010203', 0) 194 "#, 195 [], 196 )?; 197 Ok(()) 198 }) 199 .unwrap(); 200 201 let error = store.load().unwrap_err(); 202 203 assert!( 204 format!("{error:#}").contains("failed to parse compact chain snapshot from database"), 205 "{error:#}" 206 ); 207 }