mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	mintless-proof-generator
This commit is contained in:
		
							parent
							
								
									e55c132178
								
							
						
					
					
						commit
						1e5d84a9d8
					
				
					 4 changed files with 272 additions and 6 deletions
				
			
		| 
						 | 
				
			
			@ -433,6 +433,14 @@ if (WINGETOPT_FOUND)
 | 
			
		|||
  target_link_libraries_system(pow-miner wingetopt)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_executable(mintless-proof-generator util/mintless-proof-generator.cpp)
 | 
			
		||||
target_link_libraries(mintless-proof-generator PRIVATE ton_crypto ton_block git ${JEMALLOC_LIBRARIES})
 | 
			
		||||
 | 
			
		||||
if (JEMALLOC_FOUND)
 | 
			
		||||
  target_include_directories(mintless-proof-generator PRIVATE ${JEMALLOC_INCLUDE_DIR})
 | 
			
		||||
  target_compile_definitions(mintless-proof-generator PRIVATE -DTON_USE_JEMALLOC=1)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
set(TURN_OFF_LSAN cd .)
 | 
			
		||||
if (TON_USE_ASAN AND NOT WIN32)
 | 
			
		||||
  set(TURN_OFF_LSAN export LSAN_OPTIONS=detect_leaks=0)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										258
									
								
								crypto/util/mintless-proof-generator.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								crypto/util/mintless-proof-generator.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,258 @@
 | 
			
		|||
/* 
 | 
			
		||||
    This file is part of TON Blockchain Library.
 | 
			
		||||
 | 
			
		||||
    TON Blockchain Library is free software: you can redistribute it and/or modify
 | 
			
		||||
    it under the terms of the GNU Lesser General Public License as published by
 | 
			
		||||
    the Free Software Foundation, either version 2 of the License, or
 | 
			
		||||
    (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
    TON Blockchain Library is distributed in the hope that it will be useful,
 | 
			
		||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
    GNU Lesser General Public License for more details.
 | 
			
		||||
 | 
			
		||||
    You should have received a copy of the GNU Lesser General Public License
 | 
			
		||||
    along with TON Blockchain Library.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#include "block-parse.h"
 | 
			
		||||
#include "block.h"
 | 
			
		||||
#include "td/db/utils/BlobView.h"
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include "td/utils/OptionParser.h"
 | 
			
		||||
#include "td/utils/Time.h"
 | 
			
		||||
#include "td/utils/filesystem.h"
 | 
			
		||||
#include "td/utils/logging.h"
 | 
			
		||||
#include "vm/cells/MerkleProof.h"
 | 
			
		||||
#include "vm/db/StaticBagOfCellsDb.h"
 | 
			
		||||
 | 
			
		||||
#include <fstream>
 | 
			
		||||
 | 
			
		||||
void print_help() {
 | 
			
		||||
  std::cerr << "mintless-proof-generator - generates proofs for mintless jettons\n";
 | 
			
		||||
  std::cerr << "Usage:\n";
 | 
			
		||||
  std::cerr << "  mintless-proof-generator generate <input-list> <output-file>\tGenerate a full tree for "
 | 
			
		||||
               "<input-list>, save boc to <output-file>\n";
 | 
			
		||||
  std::cerr << "  mintless-proof-generator make_proof <input-boc> <address> <output-file>\tGenerate a proof for "
 | 
			
		||||
               "address <address> from tree <input-boc>, save boc to file <output-file>\n";
 | 
			
		||||
  std::cerr << "  mintless-proof-generator parse <input-boc> <output-file>\tRead a tree from <input-boc> and output it "
 | 
			
		||||
               "as text to <output-file>\n";
 | 
			
		||||
  exit(2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void log_mem_stat() {
 | 
			
		||||
  auto r_stat = td::mem_stat();
 | 
			
		||||
  if (r_stat.is_error()) {
 | 
			
		||||
    LOG(WARNING) << "Memory: " << r_stat.move_as_error();
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  auto stat = r_stat.move_as_ok();
 | 
			
		||||
  LOG(WARNING) << "Memory: "
 | 
			
		||||
               << "res=" << stat.resident_size_ << " (peak=" << stat.resident_size_peak_
 | 
			
		||||
               << ") virt=" << stat.virtual_size_ << " (peak=" << stat.virtual_size_peak_ << ")";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td::BitArray<3 + 8 + 256> address_to_key(const block::StdAddress &address) {
 | 
			
		||||
  // addr_std$10 anycast:(Maybe Anycast) workchain_id:int8 address:bits256 = MsgAddressInt;
 | 
			
		||||
  vm::CellBuilder cb;
 | 
			
		||||
  cb.store_long(0b100, 3);
 | 
			
		||||
  cb.store_long(address.workchain, 8);
 | 
			
		||||
  cb.store_bits(address.addr.as_bitslice());
 | 
			
		||||
  return cb.data_bits();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Entry {
 | 
			
		||||
  block::StdAddress address;
 | 
			
		||||
  td::RefInt256 amount;
 | 
			
		||||
  td::uint64 start_from = 0, expired_at = 0;
 | 
			
		||||
 | 
			
		||||
  td::BitArray<3 + 8 + 256> get_key() const {
 | 
			
		||||
    return address_to_key(address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  td::Ref<vm::CellSlice> get_value() const {
 | 
			
		||||
    // _ amount:Coins start_from:uint48 expired_at:uint48 = AirdropItem;
 | 
			
		||||
    vm::CellBuilder cb;
 | 
			
		||||
    bool ok = block::tlb::t_Grams.store_integer_value(cb, *amount) && cb.store_ulong_rchk_bool(start_from, 48) &&
 | 
			
		||||
              cb.store_ulong_rchk_bool(expired_at, 48);
 | 
			
		||||
    LOG_CHECK(ok) << "Failed to serialize AirdropItem";
 | 
			
		||||
    return cb.as_cellslice_ref();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Entry parse(td::BitArray<3 + 8 + 256> key, vm::CellSlice value) {
 | 
			
		||||
    Entry e;
 | 
			
		||||
    td::ConstBitPtr ptr = key.bits();
 | 
			
		||||
    LOG_CHECK(ptr.get_uint(3) == 0b100) << "Invalid address";
 | 
			
		||||
    ptr.advance(3);
 | 
			
		||||
    e.address.workchain = (ton::WorkchainId)ptr.get_int(8);
 | 
			
		||||
    ptr.advance(8);
 | 
			
		||||
    e.address.addr = ptr;
 | 
			
		||||
    bool ok = block::tlb::t_Grams.as_integer_skip_to(value, e.amount) && value.fetch_uint_to(48, e.start_from) &&
 | 
			
		||||
              value.fetch_uint_to(48, e.expired_at) && value.empty_ext();
 | 
			
		||||
    LOG_CHECK(ok) << "Failed to parse AirdropItem";
 | 
			
		||||
    return e;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool read_entry(std::istream &f, Entry &entry) {
 | 
			
		||||
  std::string line;
 | 
			
		||||
  while (std::getline(f, line)) {
 | 
			
		||||
    std::vector<std::string> v = td::full_split(line, ' ');
 | 
			
		||||
    if (v.empty()) {
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    auto S = [&]() -> td::Status {
 | 
			
		||||
      if (v.size() != 4) {
 | 
			
		||||
        return td::Status::Error("Invalid line in input");
 | 
			
		||||
      }
 | 
			
		||||
      TRY_RESULT_PREFIX_ASSIGN(entry.address, block::StdAddress::parse(v[0]), "Invalid address in input: ");
 | 
			
		||||
      entry.amount = td::string_to_int256(v[1]);
 | 
			
		||||
      if (entry.amount.is_null() || !entry.amount->is_valid() || entry.amount->sgn() < 0) {
 | 
			
		||||
        return td::Status::Error(PSTRING() << "Invalid amount in input: " << v[1]);
 | 
			
		||||
      }
 | 
			
		||||
      TRY_RESULT_PREFIX_ASSIGN(entry.start_from, td::to_integer_safe<td::uint64>(v[2]),
 | 
			
		||||
                               "Invalid start_from in input: ");
 | 
			
		||||
      TRY_RESULT_PREFIX_ASSIGN(entry.expired_at, td::to_integer_safe<td::uint64>(v[3]),
 | 
			
		||||
                               "Invalid expired_at in input: ");
 | 
			
		||||
      return td::Status::OK();
 | 
			
		||||
    }();
 | 
			
		||||
    S.ensure();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td::Status run_generate(std::string in_filename, std::string out_filename) {
 | 
			
		||||
  LOG(INFO) << "Generating tree from " << in_filename;
 | 
			
		||||
  std::ifstream in_file{in_filename};
 | 
			
		||||
  LOG_CHECK(in_file.is_open()) << "Cannot open file " << in_filename;
 | 
			
		||||
 | 
			
		||||
  Entry entry;
 | 
			
		||||
  vm::Dictionary dict{3 + 8 + 256};
 | 
			
		||||
  td::uint64 count = 0;
 | 
			
		||||
  td::Timestamp log_at = td::Timestamp::in(5.0);
 | 
			
		||||
  while (read_entry(in_file, entry)) {
 | 
			
		||||
    ++count;
 | 
			
		||||
    bool ok = dict.set(entry.get_key(), entry.get_value(), vm::DictionaryBase::SetMode::Add);
 | 
			
		||||
    LOG_CHECK(ok) << "Failed to add entry " << entry.address.rserialize() << " (line #" << count << ")";
 | 
			
		||||
    if (log_at.is_in_past()) {
 | 
			
		||||
      LOG(INFO) << "Added " << count << " entries";
 | 
			
		||||
      log_at = td::Timestamp::in(5.0);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  LOG_CHECK(in_file.eof()) << "Failed to read file " << in_filename;
 | 
			
		||||
  in_file.close();
 | 
			
		||||
 | 
			
		||||
  LOG_CHECK(count != 0) << "Input is empty";
 | 
			
		||||
  td::Ref<vm::Cell> root = dict.get_root_cell();
 | 
			
		||||
  LOG(INFO) << "Total: " << count << " entries, root hash: " << root->get_hash().to_hex();
 | 
			
		||||
  vm::BagOfCells boc;
 | 
			
		||||
  boc.add_root(root);
 | 
			
		||||
  TRY_STATUS(boc.import_cells());
 | 
			
		||||
  LOG(INFO) << "Writing to " << out_filename;
 | 
			
		||||
  TRY_RESULT(fd, td::FileFd::open(out_filename, td::FileFd::Write | td::FileFd::Truncate | td::FileFd::Create));
 | 
			
		||||
  TRY_STATUS(boc.serialize_to_file(fd, 31));
 | 
			
		||||
  TRY_STATUS(fd.sync());
 | 
			
		||||
  fd.close();
 | 
			
		||||
  log_mem_stat();
 | 
			
		||||
  return td::Status::OK();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td::Status run_make_proof(std::string in_filename, std::string s_address, std::string out_filename) {
 | 
			
		||||
  LOG(INFO) << "Generating proof for " << s_address << ", input file is " << in_filename;
 | 
			
		||||
  TRY_RESULT(address, block::StdAddress::parse(s_address));
 | 
			
		||||
 | 
			
		||||
  TRY_RESULT(blob_view, td::FileBlobView::create(in_filename));
 | 
			
		||||
  TRY_RESULT(boc, vm::StaticBagOfCellsDbLazy::create(std::move(blob_view)));
 | 
			
		||||
  TRY_RESULT(root, boc->get_root_cell(0));
 | 
			
		||||
 | 
			
		||||
  vm::MerkleProofBuilder mpb{root};
 | 
			
		||||
  vm::Dictionary dict{mpb.root(), 3 + 8 + 256};
 | 
			
		||||
  auto key = address_to_key(address);
 | 
			
		||||
  td::Ref<vm::CellSlice> value = dict.lookup(key);
 | 
			
		||||
  LOG_CHECK(value.not_null()) << "No entry for address " << s_address;
 | 
			
		||||
  Entry e = Entry::parse(key, *value);
 | 
			
		||||
  LOG(INFO) << "Entry: address=" << e.address.workchain << ":" << e.address.addr.to_hex()
 | 
			
		||||
            << " amount=" << e.amount->to_dec_string() << " start_from=" << e.start_from
 | 
			
		||||
            << " expire_at=" << e.expired_at;
 | 
			
		||||
 | 
			
		||||
  TRY_RESULT(proof, mpb.extract_proof_boc());
 | 
			
		||||
  LOG(INFO) << "Writing proof to " << out_filename << " (" << td::format::as_size(proof.size()) << ")";
 | 
			
		||||
  TRY_STATUS(td::write_file(out_filename, proof));
 | 
			
		||||
  log_mem_stat();
 | 
			
		||||
  return td::Status::OK();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
td::Status run_parse(std::string in_filename, std::string out_filename) {
 | 
			
		||||
  LOG(INFO) << "Parsing " << in_filename;
 | 
			
		||||
  std::ofstream out_file{out_filename};
 | 
			
		||||
  LOG_CHECK(out_file.is_open()) << "Cannot open file " << out_filename;
 | 
			
		||||
 | 
			
		||||
  TRY_RESULT(blob_view, td::FileBlobView::create(in_filename));
 | 
			
		||||
  TRY_RESULT(boc, vm::StaticBagOfCellsDbLazy::create(std::move(blob_view)));
 | 
			
		||||
  TRY_RESULT(root, boc->get_root_cell(0));
 | 
			
		||||
  LOG(INFO) << "Root hash = " << root->get_hash().to_hex();
 | 
			
		||||
  vm::Dictionary dict{root, 3 + 8 + 256};
 | 
			
		||||
  td::Timestamp log_at = td::Timestamp::in(5.0);
 | 
			
		||||
  td::uint64 count = 0;
 | 
			
		||||
  bool ok = dict.check_for_each([&](td::Ref<vm::CellSlice> value, td::ConstBitPtr key, int key_len) {
 | 
			
		||||
    CHECK(key_len == 3 + 8 + 256);
 | 
			
		||||
    Entry e = Entry::parse(key, *value);
 | 
			
		||||
    out_file << e.address.workchain << ":" << e.address.addr.to_hex() << " " << e.amount->to_dec_string() << " "
 | 
			
		||||
             << e.start_from << " " << e.expired_at << "\n";
 | 
			
		||||
    LOG_CHECK(!out_file.fail()) << "Failed to write to " << out_filename;
 | 
			
		||||
    ++count;
 | 
			
		||||
    if (log_at.is_in_past()) {
 | 
			
		||||
      LOG(INFO) << "Parsed " << count << " entries";
 | 
			
		||||
      log_at = td::Timestamp::in(5.0);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  });
 | 
			
		||||
  LOG_CHECK(ok) << "Failed to parse dictionary";
 | 
			
		||||
  out_file.close();
 | 
			
		||||
  LOG_CHECK(!out_file.fail()) << "Failed to write to " << out_filename;
 | 
			
		||||
  LOG(INFO) << "Done: " << count << " entries";
 | 
			
		||||
  log_mem_stat();
 | 
			
		||||
  return td::Status::OK();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char *argv[]) {
 | 
			
		||||
  SET_VERBOSITY_LEVEL(verbosity_INFO);
 | 
			
		||||
  td::set_log_fatal_error_callback([](td::CSlice) { exit(2); });
 | 
			
		||||
  if (argc <= 1) {
 | 
			
		||||
    print_help();
 | 
			
		||||
    return 2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::string command = argv[1];
 | 
			
		||||
  try {
 | 
			
		||||
    if (command == "generate") {
 | 
			
		||||
      if (argc != 4) {
 | 
			
		||||
        print_help();
 | 
			
		||||
      }
 | 
			
		||||
      run_generate(argv[2], argv[3]).ensure();
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if (command == "make_proof") {
 | 
			
		||||
      if (argc != 5) {
 | 
			
		||||
        print_help();
 | 
			
		||||
      }
 | 
			
		||||
      run_make_proof(argv[2], argv[3], argv[4]).ensure();
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
    if (command == "parse") {
 | 
			
		||||
      if (argc != 4) {
 | 
			
		||||
        print_help();
 | 
			
		||||
      }
 | 
			
		||||
      run_parse(argv[2], argv[3]).ensure();
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
  } catch (vm::VmError &e) {
 | 
			
		||||
    LOG(FATAL) << "VM error: " << e.get_msg();
 | 
			
		||||
  } catch (vm::VmVirtError &e) {
 | 
			
		||||
    LOG(FATAL) << "VM error: " << e.get_msg();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  LOG(FATAL) << "Unknown command '" << command << "'";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ td::Result<Ref<Cell>> StaticBagOfCellsDb::create_ext_cell(Cell::LevelMask level_
 | 
			
		|||
//
 | 
			
		||||
class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb {
 | 
			
		||||
 public:
 | 
			
		||||
  StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
 | 
			
		||||
  explicit StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
 | 
			
		||||
  }
 | 
			
		||||
  td::Result<size_t> get_root_count() override {
 | 
			
		||||
    return roots_.size();
 | 
			
		||||
| 
						 | 
				
			
			@ -233,7 +233,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
 | 
			
		|||
    return create_root_cell(std::move(data_cell));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  ~StaticBagOfCellsDbLazyImpl() {
 | 
			
		||||
  ~StaticBagOfCellsDbLazyImpl() override {
 | 
			
		||||
    //LOG(ERROR) << deserialize_cell_cnt_ << " " << deserialize_cell_hash_cnt_;
 | 
			
		||||
    get_thread_safe_counter().add(-1);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -314,11 +314,11 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
 | 
			
		|||
    td::RwMutex::ReadLock guard;
 | 
			
		||||
    if (info_.has_index) {
 | 
			
		||||
      TRY_RESULT(new_offset_view, data_.view(td::MutableSlice(arr, info_.offset_byte_size),
 | 
			
		||||
                                             info_.index_offset + idx * info_.offset_byte_size));
 | 
			
		||||
                                             info_.index_offset + (td::int64)idx * info_.offset_byte_size));
 | 
			
		||||
      offset_view = new_offset_view;
 | 
			
		||||
    } else {
 | 
			
		||||
      guard = index_data_rw_mutex_.lock_read().move_as_ok();
 | 
			
		||||
      offset_view = td::Slice(index_data_).substr(idx * info_.offset_byte_size, info_.offset_byte_size);
 | 
			
		||||
      offset_view = td::Slice(index_data_).substr((td::int64)idx * info_.offset_byte_size, info_.offset_byte_size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CHECK(offset_view.size() == (size_t)info_.offset_byte_size);
 | 
			
		||||
| 
						 | 
				
			
			@ -332,7 +332,7 @@ class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
 | 
			
		|||
    }
 | 
			
		||||
    char arr[8];
 | 
			
		||||
    TRY_RESULT(idx_view, data_.view(td::MutableSlice(arr, info_.ref_byte_size),
 | 
			
		||||
                                    info_.roots_offset + root_i * info_.ref_byte_size));
 | 
			
		||||
                                    info_.roots_offset + (td::int64)root_i * info_.ref_byte_size));
 | 
			
		||||
    CHECK(idx_view.size() == (size_t)info_.ref_byte_size);
 | 
			
		||||
    return info_.read_ref(idx_view.ubegin());
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@
 | 
			
		|||
  TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)
 | 
			
		||||
 | 
			
		||||
#define TRY_RESULT_PREFIX_ASSIGN(name, result, prefix) \
 | 
			
		||||
  TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix)
 | 
			
		||||
  TRY_RESULT_PREFIX_IMPL(TD_CONCAT(r_response, __LINE__), name, result, prefix)
 | 
			
		||||
 | 
			
		||||
#define TRY_RESULT_PROMISE_PREFIX(promise_name, name, result, prefix) \
 | 
			
		||||
  TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue