1 /// \file RecorderChain.d 2 module tagion.recorderchain.RecorderChain; 3 4 import tagion.hashchain.HashChain : HashChain; 5 import tagion.hashchain.HashChainFileStorage : HashChainFileStorage; 6 import tagion.hashchain.HashChainStorage : HashChainStorage; 7 import tagion.recorderchain.RecorderChainBlock : RecorderChainBlock; 8 9 /** @brief File contains class RecorderChain 10 */ 11 12 /** 13 * \class RecorderChain 14 * Class stores info and handles local files of recorder chain 15 */ 16 17 alias RecorderChain = HashChain!(RecorderChainBlock); 18 alias RecorderChainStorage = HashChainStorage!RecorderChainBlock; 19 alias RecorderChainFileStorage = HashChainFileStorage!RecorderChainBlock; 20 21 unittest { 22 import std.file; 23 import std.path; 24 import std.range : empty; 25 import tagion.basic.Types : FileExtension; 26 import tagion.basic.basic : fileId, tempfile; 27 import tagion.crypto.SecureInterfaceNet : SecureNet; 28 import tagion.crypto.SecureNet : StdSecureNet; 29 import tagion.crypto.Types : Fingerprint; 30 import tagion.dart.DART : DART; 31 import tagion.dart.Recorder; 32 import tagion.script.TagionCurrency : TGN; 33 import tagion.script.common : TagionBill; 34 import tagion.utils.StdTime; 35 import std.format; 36 const temp_folder = tempfile ~ "/"; 37 38 const dart_filename = fileId!RecorderChain(FileExtension.dart, "dart").fullpath; 39 const dart_recovered_filename = fileId!RecorderChain(FileExtension.dart, "recovered").fullpath; 40 const dart_genesis_filename = fileId!RecorderChain(FileExtension.dart, "genesis").fullpath; 41 42 SecureNet net = new StdSecureNet; 43 auto factory = RecordFactory(net); 44 45 net.generateKeyPair("very secret password"); 46 47 TagionBill[] makeBills(uint epoch) { 48 SecureNet secure_net = new StdSecureNet; 49 { 50 secure_net.generateKeyPair("secure_net secret password"); 51 } 52 53 TagionBill[] bills; 54 // bills ~= TagionBill(1000.TGN, currentTime, securenet); 55 56 bills ~= TagionBill((1000).TGN, currentTime, secure_net.pubkey, null); 57 bills ~= TagionBill((1200).TGN, currentTime, secure_net.derivePubkey("secure_net0"), null); 58 bills ~= TagionBill((3000).TGN, currentTime, secure_net.derivePubkey("secure_net1"), null); 59 bills ~= TagionBill((4300).TGN, currentTime, secure_net.derivePubkey("secure_net2"), null); 60 61 return bills; 62 } 63 64 /// RecorderChain_replay_no_genesis 65 { 66 // Create empty recorder chain 67 RecorderChainStorage storage = new RecorderChainFileStorage(temp_folder, net); 68 auto recorder_chain = new RecorderChain(storage); 69 70 // Create empty DART 71 DART.create(dart_filename, net); 72 Exception dart_exception; 73 auto dart = new DART(net, dart_filename, dart_exception); 74 assert(dart_exception is null); 75 76 // In loop fill DART and Add blocks 77 enum blocks_count = 10; 78 foreach (i; 0 .. blocks_count) { 79 const bills_recorder = factory.recorder(makeBills(i), Archive.Type.ADD); 80 dart.modify(bills_recorder); 81 82 auto last_block = recorder_chain.getLastBlock; 83 auto previous_hash = last_block is null ? Fingerprint.init : last_block.getHash; 84 recorder_chain.append(new RecorderChainBlock(bills_recorder.toDoc, 85 previous_hash, Fingerprint(dart.fingerprint), i, net)); 86 } 87 88 assert(recorder_chain.isValidChain); 89 90 // Find last block with actual DART bullseye 91 { 92 auto block_last_bullseye = recorder_chain.storage.find( 93 (b) => b.bullseye == dart.fingerprint); 94 assert(block_last_bullseye !is null); 95 assert( 96 block_last_bullseye.toDoc.serialize == recorder_chain.getLastBlock.toDoc.serialize); 97 } 98 99 // Create new empty DART for recovery 100 DART.create(dart_recovered_filename, net); 101 auto dart_recovered = new DART(net, dart_recovered_filename, dart_exception); 102 assert(dart_exception is null); 103 104 // Replay blocks 105 { 106 recorder_chain.replay((RecorderChainBlock block) { 107 auto block_recorder = factory.recorder(block.recorder_doc); 108 dart_recovered.modify(block_recorder); 109 110 assert(block.bullseye == dart_recovered.fingerprint); 111 }); 112 } 113 114 // Compare bullseyes of result DART and recovered from blocks 115 assert(dart.fingerprint == dart_recovered.fingerprint); 116 117 rmdirRecurse(temp_folder); 118 remove(dart_filename); 119 remove(dart_recovered_filename); 120 } 121 122 /// RecorderChain_replay_genesis 123 { 124 // Create empty recorder chain 125 RecorderChainStorage storage = new RecorderChainFileStorage(temp_folder, net); 126 auto recorder_chain = new RecorderChain(storage); 127 128 // Create empty DART 129 DART.create(dart_filename, net); 130 Exception dart_exception; 131 auto dart = new DART(net, dart_filename, dart_exception); 132 assert(dart_exception is null); 133 134 // Add something to DART and make genesis 135 const genesis_recorder = factory.recorder(makeBills(11), Archive.Type.ADD); 136 dart.modify(genesis_recorder); 137 dart_filename.copy(dart_genesis_filename); 138 139 // In loop fill DART and Add blocks 140 enum blocks_count = 10; 141 foreach (i; 0 .. blocks_count) { 142 const bills_recorder = factory.recorder(makeBills(i), Archive.Type.ADD); 143 dart.modify(bills_recorder); 144 145 auto last_block = recorder_chain.getLastBlock; 146 auto previous_hash = last_block is null ? Fingerprint.init : last_block.getHash; 147 recorder_chain.append(new RecorderChainBlock(bills_recorder.toDoc, 148 previous_hash, 149 Fingerprint(dart.fingerprint), i, net)); 150 } 151 152 assert(recorder_chain.isValidChain); 153 154 // Find last block with actual DART bullseye 155 { 156 auto block_last_bullseye = recorder_chain.storage.find( 157 (b) => b.bullseye == dart.fingerprint); 158 assert(block_last_bullseye !is null); 159 assert( 160 block_last_bullseye.toDoc.serialize == recorder_chain.getLastBlock.toDoc.serialize); 161 } 162 163 // Create new empty DART for recovery from saved genesis DART 164 dart_genesis_filename.copy(dart_recovered_filename); 165 auto dart_recovered = new DART(net, dart_recovered_filename, dart_exception); 166 assert(dart_exception is null); 167 168 // Replay blocks 169 { 170 recorder_chain.replay((RecorderChainBlock block) { 171 auto block_recorder = factory.recorder(block.recorder_doc); 172 dart_recovered.modify(block_recorder); 173 174 assert(block.bullseye == dart_recovered.fingerprint); 175 }); 176 } 177 178 // Compare bullseyes of result DART and recovered from blocks 179 assert(dart.fingerprint == dart_recovered.fingerprint); 180 181 rmdirRecurse(temp_folder); 182 remove(dart_filename); 183 remove(dart_recovered_filename); 184 remove(dart_genesis_filename); 185 } 186 187 /// RecorderChain_replayFrom 188 { 189 // Create empty recorder chain 190 RecorderChainStorage storage = new RecorderChainFileStorage(temp_folder, net); 191 auto recorder_chain = new RecorderChain(storage); 192 193 // Create empty DART 194 DART.create(dart_filename, net); 195 Exception dart_exception; 196 auto dart = new DART(net, dart_filename, dart_exception); 197 assert(dart_exception is null); 198 199 // In loop fill DART and Add blocks 200 enum blocks_count = 10; 201 enum some_block_index = 4; 202 foreach (i; 0 .. blocks_count) { 203 const bills_recorder = factory.recorder(makeBills(i), Archive.Type.ADD); 204 dart.modify(bills_recorder); 205 206 auto last_block = recorder_chain.getLastBlock; 207 auto previous_hash = last_block is null ? Fingerprint.init : last_block.getHash; 208 recorder_chain.append(new RecorderChainBlock( 209 bills_recorder.toDoc, 210 previous_hash, 211 Fingerprint(dart.fingerprint), 212 i, 213 net)); 214 215 // In the middle of the chain copy dart that will be "outdated" 216 if (i == some_block_index) { 217 dart_filename.copy(dart_recovered_filename); 218 } 219 } 220 221 auto recorder_chain_new = new RecorderChain(storage); 222 223 assert(recorder_chain.isValidChain); 224 assert(recorder_chain_new.isValidChain); 225 226 // Find last block with actual DART bullseye 227 { 228 auto block_last_bullseye = recorder_chain_new.storage.find( 229 (b) => b.bullseye == dart.fingerprint); 230 assert(block_last_bullseye !is null); 231 assert( 232 block_last_bullseye.toDoc.serialize == recorder_chain_new 233 .getLastBlock.toDoc.serialize); 234 } 235 236 // Open outdated DART for recovery 237 auto dart_recovered = new DART(net, dart_recovered_filename, dart_exception); 238 assert(dart_exception is null); 239 240 // Replay blocks from the middle of chain 241 { 242 recorder_chain_new.replayFrom((RecorderChainBlock block) { 243 auto block_recorder = factory.recorder(block.recorder_doc); 244 dart_recovered.modify(block_recorder); 245 246 assert(block.bullseye == dart_recovered.fingerprint); 247 }, 248 (b) => (b.bullseye == dart_recovered.fingerprint)); 249 } 250 251 // Compare bullseyes of result DART and recovered from blocks 252 assert(dart.fingerprint == dart_recovered.fingerprint); 253 254 rmdirRecurse(temp_folder); 255 remove(dart_filename); 256 remove(dart_recovered_filename); 257 } 258 259 /// RecorderChain_invalid_chain 260 { 261 // Create empty recorder chain 262 RecorderChainStorage storage = new RecorderChainFileStorage(temp_folder, net); 263 auto recorder_chain = new RecorderChain(storage); 264 265 // Create empty DART 266 DART.create(dart_filename, net); 267 Exception dart_exception; 268 auto dart = new DART(net, dart_filename, dart_exception); 269 assert(dart_exception is null); 270 271 // In loop fill DART and Add blocks 272 enum blocks_count = 10; 273 foreach (i; 0 .. blocks_count) { 274 const bills_recorder = factory.recorder(makeBills(i), Archive.Type.ADD); 275 dart.modify(bills_recorder); 276 277 auto last_block = recorder_chain.getLastBlock; 278 auto previous_hash = last_block is null ? Fingerprint.init : last_block.getHash; 279 recorder_chain.append(new RecorderChainBlock( 280 bills_recorder.toDoc, 281 previous_hash, 282 Fingerprint(dart.fingerprint), 283 i, 284 net)); 285 } 286 287 assert(recorder_chain.isValidChain); 288 289 // Remove one block from chain 290 { 291 auto some_hash = recorder_chain.storage.getHashes[0]; 292 auto filename = buildPath(temp_folder, format!"%(%02x%)"(some_hash).setExtension( 293 FileExtension.hibon)); 294 remove(filename); 295 296 // Chain shouldn't be valid anymore 297 assert(!recorder_chain.isValidChain); 298 } 299 300 // Create new empty DART for recovery 301 DART.create(dart_recovered_filename, net); 302 auto dart_recovered = new DART(net, dart_recovered_filename, dart_exception); 303 assert(dart_exception is null); 304 305 pragma(msg, "fixme(phr): figure out why recorderchain replay breaks"); 306 version(none) { 307 // Replay blocks 308 { 309 recorder_chain.replay((RecorderChainBlock block) { 310 auto block_recorder = factory.recorder(block.recorder_doc); 311 dart_recovered.modify(block_recorder); 312 }); 313 } 314 // Bullseyes should not be the same 315 assert(dart.fingerprint != dart_recovered.fingerprint); 316 } 317 318 rmdirRecurse(temp_folder); 319 remove(dart_filename); 320 remove(dart_recovered_filename); 321 } 322 }