1+ /*
2+ This file is part of TON Blockchain Library.
3+
4+ TON Blockchain Library is free software: you can redistribute it and/or modify
5+ it under the terms of the GNU Lesser General Public License as published by
6+ the Free Software Foundation, either version 2 of the License, or
7+ (at your option) any later version.
8+
9+ TON Blockchain Library is distributed in the hope that it will be useful,
10+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ GNU Lesser General Public License for more details.
13+
14+ You should have received a copy of the GNU Lesser General Public License
15+ along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+ #include " block-parse.h"
19+ #include " block.h"
20+ #include " td/db/utils/BlobView.h"
21+
22+ #include < iostream>
23+ #include " td/utils/OptionParser.h"
24+ #include " td/utils/Time.h"
25+ #include " td/utils/filesystem.h"
26+ #include " td/utils/logging.h"
27+ #include " vm/cells/MerkleProof.h"
28+ #include " vm/db/StaticBagOfCellsDb.h"
29+
30+ #include < fstream>
31+
32+ void 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 " ;
41+ exit (2 );
42+ }
43+
44+ void log_mem_stat () {
45+ auto r_stat = td::mem_stat ();
46+ if (r_stat.is_error ()) {
47+ LOG (WARNING) << " Memory: " << r_stat.move_as_error ();
48+ return ;
49+ }
50+ auto stat = r_stat.move_as_ok ();
51+ LOG (WARNING) << " Memory: "
52+ << " res=" << stat.resident_size_ << " (peak=" << stat.resident_size_peak_
53+ << " ) virt=" << stat.virtual_size_ << " (peak=" << stat.virtual_size_peak_ << " )" ;
54+ }
55+
56+ td::BitArray<3 + 8 + 256 > address_to_key (const block::StdAddress &address) {
57+ // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
58+ vm::CellBuilder cb;
59+ cb.store_long (0b100 , 3 );
60+ cb.store_long (address.workchain , 8 );
61+ cb.store_bits (address.addr .as_bitslice ());
62+ return cb.data_bits ();
63+ }
64+
65+ struct Entry {
66+ block::StdAddress address;
67+ td::RefInt256 amount;
68+ td::uint64 start_from = 0 , expired_at = 0 ;
69+
70+ td::BitArray<3 + 8 + 256 > get_key () const {
71+ return address_to_key (address);
72+ }
73+
74+ td::Ref<vm::CellSlice> get_value () const {
75+ // _ amount:Coins start_from:uint48 expired_at:uint48 = AirdropItem;
76+ vm::CellBuilder cb;
77+ bool ok = block::tlb::t_Grams.store_integer_value (cb, *amount) && cb.store_ulong_rchk_bool (start_from, 48 ) &&
78+ cb.store_ulong_rchk_bool (expired_at, 48 );
79+ LOG_CHECK (ok) << " Failed to serialize AirdropItem" ;
80+ return cb.as_cellslice_ref ();
81+ }
82+
83+ static Entry parse (td::BitArray<3 + 8 + 256 > key, vm::CellSlice value) {
84+ 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;
91+ bool ok = block::tlb::t_Grams.as_integer_skip_to (value, e.amount ) && value.fetch_uint_to (48 , e.start_from ) &&
92+ value.fetch_uint_to (48 , e.expired_at ) && value.empty_ext ();
93+ LOG_CHECK (ok) << " Failed to parse AirdropItem" ;
94+ return e;
95+ }
96+ };
97+
98+ bool read_entry (std::istream &f, Entry &entry) {
99+ std::string line;
100+ while (std::getline (f, line)) {
101+ std::vector<std::string> v = td::full_split (line, ' ' );
102+ if (v.empty ()) {
103+ continue ;
104+ }
105+ auto S = [&]() -> td::Status {
106+ if (v.size () != 4 ) {
107+ return td::Status::Error (" Invalid line in input" );
108+ }
109+ TRY_RESULT_PREFIX_ASSIGN (entry.address , block::StdAddress::parse (v[0 ]), " Invalid address in input: " );
110+ entry.amount = td::string_to_int256 (v[1 ]);
111+ if (entry.amount .is_null () || !entry.amount ->is_valid () || entry.amount ->sgn () < 0 ) {
112+ return td::Status::Error (PSTRING () << " Invalid amount in input: " << v[1 ]);
113+ }
114+ TRY_RESULT_PREFIX_ASSIGN (entry.start_from , td::to_integer_safe<td::uint64>(v[2 ]),
115+ " Invalid start_from in input: " );
116+ TRY_RESULT_PREFIX_ASSIGN (entry.expired_at , td::to_integer_safe<td::uint64>(v[3 ]),
117+ " Invalid expired_at in input: " );
118+ return td::Status::OK ();
119+ }();
120+ S.ensure ();
121+ return true ;
122+ }
123+ return false ;
124+ }
125+
126+ td::Status run_generate (std::string in_filename, std::string out_filename) {
127+ LOG (INFO) << " Generating tree from " << in_filename;
128+ std::ifstream in_file{in_filename};
129+ LOG_CHECK (in_file.is_open ()) << " Cannot open file " << in_filename;
130+
131+ Entry entry;
132+ vm::Dictionary dict{3 + 8 + 256 };
133+ td::uint64 count = 0 ;
134+ td::Timestamp log_at = td::Timestamp::in (5.0 );
135+ while (read_entry (in_file, entry)) {
136+ ++count;
137+ bool ok = dict.set (entry.get_key (), entry.get_value (), vm::DictionaryBase::SetMode::Add);
138+ LOG_CHECK (ok) << " Failed to add entry " << entry.address .rserialize () << " (line #" << count << " )" ;
139+ if (log_at.is_in_past ()) {
140+ LOG (INFO) << " Added " << count << " entries" ;
141+ log_at = td::Timestamp::in (5.0 );
142+ }
143+ }
144+ LOG_CHECK (in_file.eof ()) << " Failed to read file " << in_filename;
145+ in_file.close ();
146+
147+ LOG_CHECK (count != 0 ) << " Input is empty" ;
148+ td::Ref<vm::Cell> root = dict.get_root_cell ();
149+ LOG (INFO) << " Total: " << count << " entries, root hash: " << root->get_hash ().to_hex ();
150+ vm::BagOfCells boc;
151+ boc.add_root (root);
152+ TRY_STATUS (boc.import_cells ());
153+ LOG (INFO) << " Writing to " << out_filename;
154+ TRY_RESULT (fd, td::FileFd::open (out_filename, td::FileFd::Write | td::FileFd::Truncate | td::FileFd::Create));
155+ TRY_STATUS (boc.serialize_to_file (fd, 31 ));
156+ TRY_STATUS (fd.sync ());
157+ fd.close ();
158+ log_mem_stat ();
159+ return td::Status::OK ();
160+ }
161+
162+ td::Status run_make_proof (std::string in_filename, std::string s_address, std::string out_filename) {
163+ LOG (INFO) << " Generating proof for " << s_address << " , input file is " << in_filename;
164+ TRY_RESULT (address, block::StdAddress::parse (s_address));
165+
166+ TRY_RESULT (blob_view, td::FileBlobView::create (in_filename));
167+ TRY_RESULT (boc, vm::StaticBagOfCellsDbLazy::create (std::move (blob_view)));
168+ TRY_RESULT (root, boc->get_root_cell (0 ));
169+
170+ vm::MerkleProofBuilder mpb{root};
171+ vm::Dictionary dict{mpb.root (), 3 + 8 + 256 };
172+ auto key = address_to_key (address);
173+ td::Ref<vm::CellSlice> value = dict.lookup (key);
174+ LOG_CHECK (value.not_null ()) << " No entry for address " << s_address;
175+ Entry e = Entry::parse (key, *value);
176+ LOG (INFO) << " Entry: address=" << e.address .workchain << " :" << e.address .addr .to_hex ()
177+ << " amount=" << e.amount ->to_dec_string () << " start_from=" << e.start_from
178+ << " expire_at=" << e.expired_at ;
179+
180+ TRY_RESULT (proof, mpb.extract_proof_boc ());
181+ LOG (INFO) << " Writing proof to " << out_filename << " (" << td::format::as_size (proof.size ()) << " )" ;
182+ TRY_STATUS (td::write_file (out_filename, proof));
183+ log_mem_stat ();
184+ return td::Status::OK ();
185+ }
186+
187+ td::Status run_parse (std::string in_filename, std::string out_filename) {
188+ LOG (INFO) << " Parsing " << in_filename;
189+ std::ofstream out_file{out_filename};
190+ LOG_CHECK (out_file.is_open ()) << " Cannot open file " << out_filename;
191+
192+ TRY_RESULT (blob_view, td::FileBlobView::create (in_filename));
193+ TRY_RESULT (boc, vm::StaticBagOfCellsDbLazy::create (std::move (blob_view)));
194+ TRY_RESULT (root, boc->get_root_cell (0 ));
195+ LOG (INFO) << " Root hash = " << root->get_hash ().to_hex ();
196+ vm::Dictionary dict{root, 3 + 8 + 256 };
197+ td::Timestamp log_at = td::Timestamp::in (5.0 );
198+ td::uint64 count = 0 ;
199+ bool ok = dict.check_for_each ([&](td::Ref<vm::CellSlice> value, td::ConstBitPtr key, int key_len) {
200+ CHECK (key_len == 3 + 8 + 256 );
201+ Entry e = Entry::parse (key, *value);
202+ out_file << e.address .workchain << " :" << e.address .addr .to_hex () << " " << e.amount ->to_dec_string () << " "
203+ << e.start_from << " " << e.expired_at << " \n " ;
204+ LOG_CHECK (!out_file.fail ()) << " Failed to write to " << out_filename;
205+ ++count;
206+ if (log_at.is_in_past ()) {
207+ LOG (INFO) << " Parsed " << count << " entries" ;
208+ log_at = td::Timestamp::in (5.0 );
209+ }
210+ return true ;
211+ });
212+ LOG_CHECK (ok) << " Failed to parse dictionary" ;
213+ out_file.close ();
214+ LOG_CHECK (!out_file.fail ()) << " Failed to write to " << out_filename;
215+ LOG (INFO) << " Done: " << count << " entries" ;
216+ log_mem_stat ();
217+ return td::Status::OK ();
218+ }
219+
220+ int main (int argc, char *argv[]) {
221+ SET_VERBOSITY_LEVEL (verbosity_INFO);
222+ td::set_log_fatal_error_callback ([](td::CSlice) { exit (2 ); });
223+ if (argc <= 1 ) {
224+ print_help ();
225+ return 2 ;
226+ }
227+
228+ std::string command = argv[1 ];
229+ try {
230+ if (command == " generate" ) {
231+ if (argc != 4 ) {
232+ print_help ();
233+ }
234+ run_generate (argv[2 ], argv[3 ]).ensure ();
235+ return 0 ;
236+ }
237+ if (command == " make_proof" ) {
238+ if (argc != 5 ) {
239+ print_help ();
240+ }
241+ run_make_proof (argv[2 ], argv[3 ], argv[4 ]).ensure ();
242+ return 0 ;
243+ }
244+ if (command == " parse" ) {
245+ if (argc != 4 ) {
246+ print_help ();
247+ }
248+ run_parse (argv[2 ], argv[3 ]).ensure ();
249+ return 0 ;
250+ }
251+ } catch (vm::VmError &e) {
252+ LOG (FATAL) << " VM error: " << e.get_msg ();
253+ } catch (vm::VmVirtError &e) {
254+ LOG (FATAL) << " VM error: " << e.get_msg ();
255+ }
256+
257+ LOG (FATAL) << " Unknown command '" << command << " '" ;
258+ }
0 commit comments