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 }