1717
1818#include " block-parse.h"
1919#include " block.h"
20+ #include " td/actor/core/Actor.h"
2021#include " td/db/utils/BlobView.h"
2122
2223#include < iostream>
2324#include " td/utils/OptionParser.h"
2425#include " td/utils/Time.h"
26+ #include " td/utils/base64.h"
2527#include " td/utils/filesystem.h"
2628#include " td/utils/logging.h"
2729#include " vm/cells/MerkleProof.h"
2830#include " vm/db/StaticBagOfCellsDb.h"
2931
3032#include < fstream>
33+ #include < common/delay.h>
34+
35+ const size_t KEY_LEN = 3 + 8 + 256 ;
3136
3237void print_help () {
33- std::cerr << " mintless-proof-generator - generates proofs for mintless jettons\n " ;
34- std::cerr << " Usage:\n " ;
35- std::cerr << " mintless-proof-generator generate <input-list> <output-file>\t Generate a full tree for "
36- " <input-list>, save boc to <output-file>\n " ;
37- std::cerr << " mintless-proof-generator make_proof <input-boc> <address> <output-file>\t Generate a proof for "
38- " address <address> from tree <input-boc>, save boc to file <output-file>\n " ;
39- std::cerr << " mintless-proof-generator parse <input-boc> <output-file>\t Read a tree from <input-boc> and output it "
40- " as text to <output-file>\n " ;
38+ std::cerr << " mintless-proof-generator - generates proofs for mintless jettons. Usage:\n\n " ;
39+ std::cerr << " mintless-proof-generator generate <input-list> <output-file>\n " ;
40+ std::cerr << " Generate a full tree for <input-list>, save boc to <output-file>.\n " ;
41+ std::cerr << " Input format: each line is <address> <amount> <start_from> <expired_at>.\n\n " ;
42+ std::cerr << " mintless-proof-generator make_proof <input-boc> <address> <output-file>.\n " ;
43+ std::cerr << " Generate a proof for address <address> from tree <input-boc>, save boc to file <output-file>.\n\n " ;
44+ std::cerr << " mintless-proof-generator parse <input-boc> <output-file>\n " ;
45+ std::cerr << " Read a tree from <input-boc> and output it as text to <output-file>.\n " ;
46+ std::cerr << " Output format: same as input for 'generate'.\n\n " ;
47+ std::cerr << " mintless-proof-generator make_all_proofs <input-boc> <output-file> [--threads <threads>]\n " ;
48+ std::cerr << " Read a tree from <input-boc> and output proofs for all accounts to <output-file>.\n " ;
49+ std::cerr << " Output format: <address>,<proof-base64>\n " ;
50+ std::cerr << " Default <threads>: 1\n " ;
4151 exit (2 );
4252}
4353
@@ -53,7 +63,7 @@ void log_mem_stat() {
5363 << " ) virt=" << stat.virtual_size_ << " (peak=" << stat.virtual_size_peak_ << " )" ;
5464}
5565
56- td::BitArray<3 + 8 + 256 > address_to_key (const block::StdAddress &address) {
66+ td::BitArray<KEY_LEN > address_to_key (const block::StdAddress &address) {
5767 // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
5868 vm::CellBuilder cb;
5969 cb.store_long (0b100 , 3 );
@@ -62,12 +72,23 @@ td::BitArray<3 + 8 + 256> address_to_key(const block::StdAddress &address) {
6272 return cb.data_bits ();
6373}
6474
75+ block::StdAddress key_to_address (const td::BitArray<KEY_LEN> &key) {
76+ block::StdAddress addr;
77+ td::ConstBitPtr ptr = key.bits ();
78+ LOG_CHECK (ptr.get_uint (3 ) == 0b100 ) << " Invalid address" ;
79+ ptr.advance (3 );
80+ addr.workchain = (ton::WorkchainId)ptr.get_int (8 );
81+ ptr.advance (8 );
82+ addr.addr = ptr;
83+ return addr;
84+ }
85+
6586struct Entry {
6687 block::StdAddress address;
6788 td::RefInt256 amount;
6889 td::uint64 start_from = 0 , expired_at = 0 ;
6990
70- td::BitArray<3 + 8 + 256 > get_key () const {
91+ td::BitArray<KEY_LEN > get_key () const {
7192 return address_to_key (address);
7293 }
7394
@@ -80,14 +101,9 @@ struct Entry {
80101 return cb.as_cellslice_ref ();
81102 }
82103
83- static Entry parse (td::BitArray<3 + 8 + 256 > key, vm::CellSlice value) {
104+ static Entry parse (const td::BitArray<KEY_LEN> & key, vm::CellSlice value) {
84105 Entry e;
85- td::ConstBitPtr ptr = key.bits ();
86- LOG_CHECK (ptr.get_uint (3 ) == 0b100 ) << " Invalid address" ;
87- ptr.advance (3 );
88- e.address .workchain = (ton::WorkchainId)ptr.get_int (8 );
89- ptr.advance (8 );
90- e.address .addr = ptr;
106+ e.address = key_to_address (key);
91107 bool ok = block::tlb::t_Grams.as_integer_skip_to (value, e.amount ) && value.fetch_uint_to (48 , e.start_from ) &&
92108 value.fetch_uint_to (48 , e.expired_at ) && value.empty_ext ();
93109 LOG_CHECK (ok) << " Failed to parse AirdropItem" ;
@@ -129,7 +145,7 @@ td::Status run_generate(std::string in_filename, std::string out_filename) {
129145 LOG_CHECK (in_file.is_open ()) << " Cannot open file " << in_filename;
130146
131147 Entry entry;
132- vm::Dictionary dict{3 + 8 + 256 };
148+ vm::Dictionary dict{KEY_LEN };
133149 td::uint64 count = 0 ;
134150 td::Timestamp log_at = td::Timestamp::in (5.0 );
135151 while (read_entry (in_file, entry)) {
@@ -168,7 +184,7 @@ td::Status run_make_proof(std::string in_filename, std::string s_address, std::s
168184 TRY_RESULT (root, boc->get_root_cell (0 ));
169185
170186 vm::MerkleProofBuilder mpb{root};
171- vm::Dictionary dict{mpb.root (), 3 + 8 + 256 };
187+ vm::Dictionary dict{mpb.root (), KEY_LEN };
172188 auto key = address_to_key (address);
173189 td::Ref<vm::CellSlice> value = dict.lookup (key);
174190 LOG_CHECK (value.not_null ()) << " No entry for address " << s_address;
@@ -193,11 +209,11 @@ td::Status run_parse(std::string in_filename, std::string out_filename) {
193209 TRY_RESULT (boc, vm::StaticBagOfCellsDbLazy::create (std::move (blob_view)));
194210 TRY_RESULT (root, boc->get_root_cell (0 ));
195211 LOG (INFO) << " Root hash = " << root->get_hash ().to_hex ();
196- vm::Dictionary dict{root, 3 + 8 + 256 };
212+ vm::Dictionary dict{root, KEY_LEN };
197213 td::Timestamp log_at = td::Timestamp::in (5.0 );
198214 td::uint64 count = 0 ;
199215 bool ok = dict.check_for_each ([&](td::Ref<vm::CellSlice> value, td::ConstBitPtr key, int key_len) {
200- CHECK (key_len == 3 + 8 + 256 );
216+ CHECK (key_len == KEY_LEN );
201217 Entry e = Entry::parse (key, *value);
202218 out_file << e.address .workchain << " :" << e.address .addr .to_hex () << " " << e.amount ->to_dec_string () << " "
203219 << e.start_from << " " << e.expired_at << " \n " ;
@@ -212,7 +228,108 @@ td::Status run_parse(std::string in_filename, std::string out_filename) {
212228 LOG_CHECK (ok) << " Failed to parse dictionary" ;
213229 out_file.close ();
214230 LOG_CHECK (!out_file.fail ()) << " Failed to write to " << out_filename;
215- LOG (INFO) << " Done: " << count << " entries" ;
231+ LOG (INFO) << " Written " << count << " entries to " << out_filename;
232+ log_mem_stat ();
233+ return td::Status::OK ();
234+ }
235+
236+ class MakeAllProofsActor : public td ::actor::core::Actor {
237+ public:
238+ MakeAllProofsActor (std::string in_filename, std::string out_filename, int max_workers)
239+ : in_filename_(in_filename), out_filename_(out_filename), max_workers_(max_workers) {
240+ }
241+
242+ void start_up () override {
243+ auto S = [&]() -> td::Status {
244+ out_file_.open (out_filename_);
245+ LOG_CHECK (out_file_.is_open ()) << " Cannot open file " << out_filename_;
246+ LOG (INFO) << " Reading " << in_filename_;
247+ TRY_RESULT (blob_view, td::FileBlobView::create (in_filename_));
248+ TRY_RESULT (boc, vm::StaticBagOfCellsDbLazy::create (std::move (blob_view)));
249+ TRY_RESULT (root, boc->get_root_cell (0 ));
250+ LOG (INFO) << " Root hash = " << root->get_hash ().to_hex ();
251+ dict_ = vm::Dictionary{root, KEY_LEN};
252+ return td::Status::OK ();
253+ }();
254+ S.ensure ();
255+ run ();
256+ alarm_timestamp () = td::Timestamp::in (5.0 );
257+ }
258+
259+ void alarm () override {
260+ alarm_timestamp () = td::Timestamp::in (5.0 );
261+ LOG (INFO) << " Processed " << written_count_ << " entries" ;
262+ }
263+
264+ void run () {
265+ for (auto it = pending_results_.begin (); it != pending_results_.end () && !it->second .empty ();) {
266+ out_file_ << it->second << " \n " ;
267+ LOG_CHECK (!out_file_.fail ()) << " Failed to write to " << out_filename_;
268+ it = pending_results_.erase (it);
269+ ++written_count_;
270+ }
271+ while (active_workers_ < max_workers_ && !eof_) {
272+ td::Ref<vm::CellSlice> value = dict_.lookup_nearest_key (current_key_, true , current_idx_ == 0 );
273+ if (value.is_null ()) {
274+ eof_ = true ;
275+ break ;
276+ }
277+ run_worker (current_key_, current_idx_);
278+ ++current_idx_;
279+ ++active_workers_;
280+ }
281+ if (eof_ && active_workers_ == 0 ) {
282+ out_file_.close ();
283+ LOG_CHECK (!out_file_.fail ()) << " Failed to write to " << out_filename_;
284+ LOG (INFO) << " Written " << written_count_ << " entries to " << out_filename_;
285+ stop ();
286+ td::actor::SchedulerContext::get ()->stop ();
287+ }
288+ }
289+
290+ void run_worker (td::BitArray<KEY_LEN> key, td::uint64 idx) {
291+ pending_results_[idx] = " " ;
292+ ton::delay_action (
293+ [SelfId = actor_id (this ), key, idx, root = dict_.get_root_cell ()]() {
294+ vm::MerkleProofBuilder mpb{root};
295+ CHECK (vm::Dictionary (mpb.root (), KEY_LEN).lookup (key).not_null ());
296+ auto r_proof = mpb.extract_proof_boc ();
297+ r_proof.ensure ();
298+ block::StdAddress addr = key_to_address (key);
299+ std::string result = PSTRING () << addr.workchain << " :" << addr.addr .to_hex () << " ,"
300+ << td::base64_encode (r_proof.move_as_ok ());
301+ td::actor::send_closure (SelfId, &MakeAllProofsActor::on_result, idx, std::move (result));
302+ },
303+ td::Timestamp::now ());
304+ }
305+
306+ void on_result (td::uint64 idx, std::string result) {
307+ pending_results_[idx] = std::move (result);
308+ --active_workers_;
309+ run ();
310+ }
311+
312+ private:
313+ std::string in_filename_, out_filename_;
314+ int max_workers_;
315+
316+ std::ofstream out_file_;
317+ vm::Dictionary dict_{KEY_LEN};
318+ td::BitArray<KEY_LEN> current_key_ = td::BitArray<KEY_LEN>::zero();
319+ td::uint64 current_idx_ = 0 ;
320+ bool eof_ = false ;
321+ int active_workers_ = 0 ;
322+
323+ std::map<td::uint64, std::string> pending_results_;
324+ td::uint64 written_count_ = 0 ;
325+ };
326+
327+ td::Status run_make_all_proofs (std::string in_filename, std::string out_filename, int threads) {
328+ td::actor::Scheduler scheduler ({(size_t )threads});
329+ scheduler.run_in_context (
330+ [&] { td::actor::create_actor<MakeAllProofsActor>(" proofs" , in_filename, out_filename, threads).release (); });
331+ while (scheduler.run (1 )) {
332+ }
216333 log_mem_stat ();
217334 return td::Status::OK ();
218335}
@@ -248,11 +365,31 @@ int main(int argc, char *argv[]) {
248365 run_parse (argv[2 ], argv[3 ]).ensure ();
249366 return 0 ;
250367 }
368+
369+ if (command == " make_all_proofs" ) {
370+ std::vector<std::string> args;
371+ int threads = 1 ;
372+ for (int i = 2 ; i < argc; ++i) {
373+ if (!strcmp (argv[i], " --threads" )) {
374+ ++i;
375+ auto r = td::to_integer_safe<int >(td::as_slice (argv[i]));
376+ LOG_CHECK (r.is_ok () && r.ok () >= 1 && r.ok () <= 127 ) << " <threads> should be in [1..127]" ;
377+ threads = r.move_as_ok ();
378+ } else {
379+ args.push_back (argv[i]);
380+ }
381+ }
382+ if (args.size () != 2 ) {
383+ print_help ();
384+ }
385+ run_make_all_proofs (args[0 ], args[1 ], threads).ensure ();
386+ return 0 ;
387+ }
251388 } catch (vm::VmError &e) {
252389 LOG (FATAL) << " VM error: " << e.get_msg ();
253390 } catch (vm::VmVirtError &e) {
254391 LOG (FATAL) << " VM error: " << e.get_msg ();
255392 }
256393
257394 LOG (FATAL) << " Unknown command '" << command << " '" ;
258- }
395+ }
0 commit comments