Skip to content

Commit 1e5d84a

Browse files
committed
mintless-proof-generator
1 parent e55c132 commit 1e5d84a

File tree

4 files changed

+272
-6
lines changed

4 files changed

+272
-6
lines changed

crypto/CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,14 @@ if (WINGETOPT_FOUND)
433433
target_link_libraries_system(pow-miner wingetopt)
434434
endif()
435435

436+
add_executable(mintless-proof-generator util/mintless-proof-generator.cpp)
437+
target_link_libraries(mintless-proof-generator PRIVATE ton_crypto ton_block git ${JEMALLOC_LIBRARIES})
438+
439+
if (JEMALLOC_FOUND)
440+
target_include_directories(mintless-proof-generator PRIVATE ${JEMALLOC_INCLUDE_DIR})
441+
target_compile_definitions(mintless-proof-generator PRIVATE -DTON_USE_JEMALLOC=1)
442+
endif()
443+
436444
set(TURN_OFF_LSAN cd .)
437445
if (TON_USE_ASAN AND NOT WIN32)
438446
set(TURN_OFF_LSAN export LSAN_OPTIONS=detect_leaks=0)
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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>\tGenerate 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>\tGenerate 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>\tRead 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+
}

crypto/vm/db/StaticBagOfCellsDb.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ td::Result<Ref<Cell>> StaticBagOfCellsDb::create_ext_cell(Cell::LevelMask level_
167167
//
168168
class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb {
169169
public:
170-
StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
170+
explicit StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
171171
}
172172
td::Result<size_t> get_root_count() override {
173173
return roots_.size();
@@ -233,7 +233,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
233233
return create_root_cell(std::move(data_cell));
234234
};
235235

236-
~StaticBagOfCellsDbLazyImpl() {
236+
~StaticBagOfCellsDbLazyImpl() override {
237237
//LOG(ERROR) << deserialize_cell_cnt_ << " " << deserialize_cell_hash_cnt_;
238238
get_thread_safe_counter().add(-1);
239239
}
@@ -314,11 +314,11 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
314314
td::RwMutex::ReadLock guard;
315315
if (info_.has_index) {
316316
TRY_RESULT(new_offset_view, data_.view(td::MutableSlice(arr, info_.offset_byte_size),
317-
info_.index_offset + idx * info_.offset_byte_size));
317+
info_.index_offset + (td::int64)idx * info_.offset_byte_size));
318318
offset_view = new_offset_view;
319319
} else {
320320
guard = index_data_rw_mutex_.lock_read().move_as_ok();
321-
offset_view = td::Slice(index_data_).substr(idx * info_.offset_byte_size, info_.offset_byte_size);
321+
offset_view = td::Slice(index_data_).substr((td::int64)idx * info_.offset_byte_size, info_.offset_byte_size);
322322
}
323323

324324
CHECK(offset_view.size() == (size_t)info_.offset_byte_size);
@@ -332,7 +332,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
332332
}
333333
char arr[8];
334334
TRY_RESULT(idx_view, data_.view(td::MutableSlice(arr, info_.ref_byte_size),
335-
info_.roots_offset + root_i * info_.ref_byte_size));
335+
info_.roots_offset + (td::int64)root_i * info_.ref_byte_size));
336336
CHECK(idx_view.size() == (size_t)info_.ref_byte_size);
337337
return info_.read_ref(idx_view.ubegin());
338338
}

tdutils/td/utils/Status.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)
8181

8282
#define TRY_RESULT_PREFIX_ASSIGN(name, result, prefix) \
83-
TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix)
83+
TRY_RESULT_PREFIX_IMPL(TD_CONCAT(r_response, __LINE__), name, result, prefix)
8484

8585
#define TRY_RESULT_PROMISE_PREFIX(promise_name, name, result, prefix) \
8686
TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)

0 commit comments

Comments
 (0)