1 /// \file kette.d
2 module tagion.tools.kette;
3 
4 /**
5  * @brief tool for replaying and undoing the dart database
6  */
7 
8 import tagion.tools.Basic;
9 import std.getopt;
10 import tagion.tools.revision;
11 import tagion.dart.DART;
12 import tagion.dart.Recorder;
13 import tagion.basic.Types : FileExtension;
14 import std.path : extension;
15 import tagion.crypto.SecureInterfaceNet : SecureNet;
16 import tagion.crypto.SecureNet;
17 import std.file : copy, exists;
18 import std.range;
19 import tagion.dart.DARTException;
20 import tagion.hibon.HiBONRecord: isRecord;
21 import tagion.hibon.HiBONJSON : toPretty;
22 import tagion.hibon.HiBONtoText;
23 import std.format;
24 
25 
26 
27 import tagion.recorderchain.RecorderChainBlock : RecorderBlock;
28 
29 
30 mixin Main!(_main, "tagionkette");
31 
32 import std.stdio;
33 
34 int _main(string[] args) {
35     immutable program = args[0];
36     bool version_switch;
37 
38     SecureNet secure_net = new StdSecureNet;
39     const hash_net = new StdHashNet;
40     auto factory = RecordFactory(hash_net);
41     string genesis_dart;
42     string dart_file;
43 
44     string passphrase = "verysecret";
45     secure_net.generateKeyPair(passphrase);
46 
47 
48     bool replay;
49     bool replay_and_find;
50     
51     GetoptResult main_args;
52     try {
53         main_args = getopt(args,
54         std.getopt.config.caseSensitive,
55         std.getopt.config.bundling,
56         "version", "display the version", &version_switch,
57         "v|verbose", "Prints more debug information", &__verbose_switch,
58         "genesisdart|g", "Path to genesis dart file", &genesis_dart,
59         "dartfile|d", "Path to dart file", &dart_file,
60         "replay|r", "Replay the recorder", &replay,
61         "findreplay|f", "Find the block and replay from there", &replay_and_find,
62         );
63 
64         if (version_switch) {
65             revision_text.writeln;
66             return 0;
67         }
68         if (main_args.helpWanted) {
69             defaultGetoptPrinter(
70                     [
71                     // format("%s version %s", program, REVNO),
72                     "Documentation: https://tagion.org/",
73                     "",
74                     "Usage:",
75                     "",
76                     "<option>:",
77 
78                     ].join("\n"),
79                     main_args.options);
80             return 0;
81         }
82 
83         if (!genesis_dart.exists || genesis_dart.extension != FileExtension.dart) {
84             error("incorrect genesis dart file");
85             return 1;
86         }
87 
88         if (replay) {
89             // Copy genesis DART as base for DART that is being recovered
90             genesis_dart.copy(dart_file);
91         }
92 
93         DART dart;
94         scope(exit) {
95             dart.close;
96         }
97         try {
98             dart = new DART(secure_net, dart_file);
99         }
100         catch (DARTException e) {
101             error(e);
102             return 1;
103         }
104 
105 
106         
107         import tagion.hibon.HiBONFile : HiBONRange;
108 
109 
110         if (replay) {
111             foreach(inputfilename; args[1 ..$]) {
112                 writefln("going through the blocks of %s", inputfilename);
113                 if (inputfilename.extension != FileExtension.hibon) {
114                     error("File %s not valid (only %(.%s %))",
115                             inputfilename, only(FileExtension.hibon));
116                     return 1;
117 
118                 }
119                 auto fin = File(inputfilename, "r");
120                 scope(exit) {
121                     fin.close;
122                 }
123 
124                 RecorderBlock prev_block;
125                 foreach(no, doc; HiBONRange(fin).enumerate) {
126                     // add the blocks
127                     if (!doc.isRecord!RecorderBlock) {
128                         error("The document in the range was not of type RecorderBlock");
129                         return 1;
130                     }
131                     const _block = RecorderBlock(doc);
132 
133                 
134                     verbose("block %s", _block.toPretty);
135 
136                     if (prev_block !is RecorderBlock.init) {
137                         const hash_of_prev = hash_net.calcHash(prev_block.toDoc);
138                         if (hash_of_prev != _block.previous) {
139                             error("The chain is not valid. fingerprint of previous %s=%s block %s expected %s", prev_block.epoch_number, hash_of_prev.encodeBase64, _block.epoch_number, _block.previous.encodeBase64); 
140 
141                         }
142                     }
143 
144                     const _recorder = factory.recorder(_block.recorder_doc);
145                     verbose("epoch: %s, eye %(%02x%), recorder length %s", _block.epoch_number, _block.bullseye, _recorder.length); 
146                     const new_bullseye = dart.modify(_recorder);
147                     if (_block.bullseye != new_bullseye) {
148                         error(format("ERROR: expected bullseye: %(%02x%) \ngot %(%02x%)", 
149                             _block.bullseye, 
150                             new_bullseye));
151                         return 1;
152                     }
153                     verbose("succesfully added block");
154                     prev_block = _block;
155                 }
156 
157             } 
158             
159         }
160 
161         if (replay_and_find) {
162             bool match;
163 
164             const init_eye = dart.bullseye;
165             verbose("searching for eye %(%02x%)", init_eye);
166             foreach(inputfilename; args[1 ..$]) {
167                 writefln("going through the blocks of %s", inputfilename);
168                 if (inputfilename.extension != FileExtension.hibon) {
169                     error("File %s not valid (only %(.%s %))",
170                             inputfilename, only(FileExtension.hibon));
171                     return 1;
172                 }
173                 auto fin = File(inputfilename, "r");
174                 scope(exit) {
175                     fin.close;
176                 }
177 
178                 RecorderBlock prev_block;
179                 Blocks: foreach(no, doc; HiBONRange(fin).enumerate) {
180                     // add the blocks
181                     if (!doc.isRecord!RecorderBlock) {
182                         error("The document in the range was not of type RecorderBlock");
183                         return 1;
184                     }
185                     const _block = RecorderBlock(doc);
186                     scope(success) {
187                         prev_block = _block;
188                     }
189                     if (prev_block !is RecorderBlock.init) {
190                         const hash_of_prev = hash_net.calcHash(prev_block.toDoc);
191                         if (hash_of_prev != _block.previous) {
192                             error("The chain is not valid. fingerprint of previous %s=%s block %s expected %s", prev_block.epoch_number, hash_of_prev.encodeBase64, _block.epoch_number, _block.previous.encodeBase64); 
193 
194                         }
195                     }
196 
197                     if (!match && _block.bullseye == init_eye) {
198                         verbose("found a MATCH on epoch %d", _block.epoch_number);
199                         match = true;
200                         continue Blocks;
201                     }
202 
203                     if (!match) {
204                         verbose("Bullseye did not match searching next epoch %s", _block.epoch_number);
205                         continue Blocks;
206                     }
207                     verbose("block %s", _block.toPretty);
208 
209 
210                     const _recorder = factory.recorder(_block.recorder_doc);
211                     verbose("epoch: %s, eye %(%02x%), recorder length %s", _block.epoch_number, _block.bullseye, _recorder.length); 
212                     const new_bullseye = dart.modify(_recorder);
213                     if (_block.bullseye != new_bullseye) {
214                         error(format("ERROR: expected bullseye: %(%02x%) \ngot %(%02x%)", 
215                             _block.bullseye, 
216                             new_bullseye));
217                         return 1;
218                     }
219                     verbose("succesfully added block");
220                 }
221 
222             } 
223             
224         }
225 
226     } catch (Exception e) {
227         error(e);
228     }
229 
230 
231 
232 
233     return 0;
234 
235 
236 
237 }
238 
239 
240 
241