Skip to content

Commit

Permalink
TON Validation Contest: Reference solution and grader
Browse files Browse the repository at this point in the history
  • Loading branch information
SpyCheese committed Jan 14, 2025
1 parent 1ee3e5d commit d8e29f8
Show file tree
Hide file tree
Showing 13 changed files with 6,066 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,9 @@ add_subdirectory(dht-server)
add_subdirectory(utils)
add_subdirectory(http)
add_subdirectory(rldp-http-proxy)

add_subdirectory(contest/solution)
add_subdirectory(contest/grader)
endif()
#END internal

Expand Down
5 changes: 5 additions & 0 deletions contest/grader/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

add_executable(contest-grader grader.cpp)
target_include_directories(contest-grader PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../..>)
target_link_libraries(contest-grader contest-solution tdutils tdactor ton_block ton_crypto tl-utils tl_api git terminal)
265 changes: 265 additions & 0 deletions contest/grader/grader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
#include "td/utils/OptionParser.h"
#include <fstream>
#include "overlay/overlays.h"

#include "adnl/adnl-ext-client.h"

#include <algorithm>
#include <list>
#include "td/utils/JsonBuilder.h"
#include "mc-config.h"
#include "td/utils/filesystem.h"
#include "td/utils/port/path.h"
#include "terminal/terminal.h"
#include "vm/cells/MerkleProof.h"
#include "ton/ton-tl.hpp"
#include "block-auto.h"
#include "contest/solution/solution.hpp"
#include "td/utils/PathView.h"
#include "td/utils/port/signals.h"
#include "vm/vm.h"
#include "vm/cells/MerkleUpdate.h"

#include <sys/resource.h>

using namespace ton;

static constexpr td::uint64 CPU_USAGE_PER_SEC = 1000000;

static td::uint64 get_cpu_usage() {
rusage usage;
CHECK(getrusage(RUSAGE_SELF, &usage) == 0);
return (td::uint64)usage.ru_utime.tv_sec * 1000000 + (td::uint64)usage.ru_utime.tv_usec;
}

class ContestGrader : public td::actor::Actor {
public:
explicit ContestGrader(std::string tests_dir) : tests_dir_(tests_dir) {
}

void start_up() override {
vm::init_vm().ensure();
scan_tests_dir();
run_next_test();
}

void scan_tests_dir() {
auto walk_status = td::WalkPath::run(tests_dir_, [&](td::CSlice name, td::WalkPath::Type type) {
if (type == td::WalkPath::Type::NotDir && td::ends_with(name, ".bin")) {
test_files_.push_back(td::PathView::relative(name.str(), tests_dir_).str());
}
return td::WalkPath::Action::Continue;
});
walk_status.ensure();
LOG_CHECK(!test_files_.empty()) << "No tests found";
std::sort(test_files_.begin(), test_files_.end());

test_name_column_width_ = 4;
for (const std::string& s : test_files_) {
test_name_column_width_ = std::max(test_name_column_width_, s.size());
}
test_idx_column_width_ = 1;
size_t threshold = 10;
while (test_files_.size() >= threshold) {
++test_idx_column_width_;
threshold *= 10;
}
separator_length_ = test_idx_column_width_ + test_name_column_width_ + 60;

printf("Executing %lu tests\n", test_files_.size());
printf("%s\n", std::string(separator_length_, '=').c_str());
printf("%*s %-*s Time CPU Status Comment\n", (int)test_idx_column_width_, "#",
(int)test_name_column_width_, "Name");
printf("%s\n", std::string(separator_length_, '=').c_str());
}

void run_next_test() {
if (test_idx_ == test_files_.size()) {
finish();
return;
}

auto r_test = read_test_file();
if (r_test.is_error()) {
printf("%*lu %-*s %8.5f %8.5f FATAL %s\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), 0.0, 0.0,
r_test.error().to_string().c_str());
fflush(stdout);
++cnt_fatal_;
++test_idx_;
run_next_test();
return;
}

auto test = r_test.move_as_ok();
BlockIdExt block_id = create_block_id(test->block_id_);
td::BufferSlice block_data = std::move(test->block_data_);
td::BufferSlice collated_data = std::move(test->collated_data_);
bool valid = test->valid_;

td::Ref<vm::Cell> original_merkle_update;
auto S = [&]() -> td::Status {
TRY_RESULT(root, vm::std_boc_deserialize(block_data));
block::gen::Block::Record rec;
if (!block::gen::t_Block.cell_unpack(root, rec)) {
return td::Status::Error("Failed to parse block root");
}
vm::CellSlice cs{rec.state_update->load_cell().move_as_ok()};
if (cs.special_type() != vm::CellTraits::SpecialType::MerkleUpdate) {
return td::Status::Error("Invalid Merkle Update in block");
}
original_merkle_update = rec.state_update;
rec.state_update = vm::CellBuilder{}.finalize_novm();
if (!block::gen::pack_cell(root, rec)) {
return td::Status::Error("Failed to pack new block root");
}
TRY_RESULT_ASSIGN(block_data, vm::std_boc_serialize(root, 31));
return td::Status::OK();
}();
if (S.is_error()) {
printf("%*lu %-*s %8.5f %8.5f FATAL %s\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), 0.0, 0.0, S.to_string().c_str());
fflush(stdout);
++cnt_fatal_;
++test_idx_;
run_next_test();
return;
}

run_contest_solution(
block_id, std::move(block_data), std::move(collated_data),
[=, SelfId = actor_id(this), timer = td::Timer{}, start_cpu = get_cpu_usage()](td::Result<td::BufferSlice> R) {
td::actor::send_closure(SelfId, &ContestGrader::got_solution_result, std::move(R), valid,
original_merkle_update, timer.elapsed(),
(double)(get_cpu_usage() - start_cpu) / CPU_USAGE_PER_SEC);
});
}

td::Result<tl_object_ptr<ton_api::contest_test>> read_test_file() {
TRY_RESULT(data, td::read_file(tests_dir_ + "/" + test_files_[test_idx_]));
return ton::fetch_tl_object<ton_api::contest_test>(data, true);
}

void got_solution_result(td::Result<td::BufferSlice> res, bool valid, td::Ref<vm::Cell> original_merkle_update,
double elapsed, double cpu_time) {
bool got_valid = res.is_ok();
if (got_valid != valid) {
printf("%*lu %-*s %8.5f %8.5f ERROR expected %s, found %s\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), elapsed, cpu_time,
(valid ? "VALID" : "INVALID"), (got_valid ? "VALID" : "INVALID"));
fflush(stdout);
++cnt_fail_;
++test_idx_;
run_next_test();
return;
}
if (!valid) {
printf("%*lu %-*s %8.5f %8.5f OK block is INVALID\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), elapsed, cpu_time);
fflush(stdout);
++cnt_ok_;
++test_idx_;
run_next_test();
return;
}
auto S = check_merkle_update(res.move_as_ok(), original_merkle_update);
if (S.is_error()) {
printf("%*lu %-*s %8.5f %8.5f ERROR invalid Merkle update %s\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), elapsed, cpu_time, S.to_string().c_str());
fflush(stdout);
++cnt_fail_;
++test_idx_;
run_next_test();
return;
}

printf("%*lu %-*s %8.5f %8.5f OK block is VALID\n", (int)test_idx_column_width_, test_idx_ + 1,
(int)test_name_column_width_, test_files_[test_idx_].c_str(), elapsed, cpu_time);
fflush(stdout);
total_time_ += elapsed;
total_cpu_time_ += cpu_time;
++cnt_ok_;
++test_idx_;
run_next_test();
}

td::Status check_merkle_update(td::Slice data, td::Ref<vm::Cell> original_merkle_update) {
TRY_RESULT(new_merkle_update, vm::std_boc_deserialize(data));
TRY_STATUS(vm::MerkleUpdate::validate(new_merkle_update));

vm::CellSlice new_cs{new_merkle_update->load_cell().move_as_ok()};
vm::CellSlice old_cs{original_merkle_update->load_cell().move_as_ok()};
if (new_cs.lex_cmp(old_cs)) { // compare hashes in Merkle update roots
return td::Status::Error("Merkle Update does not match the original Merkle Update");
}
return td::Status::OK();
}

void finish() {
printf("%s\n", std::string(separator_length_, '=').c_str());
printf("Passed %lu/%lu tests\n", cnt_ok_, test_files_.size());
printf("Total time (only passed valid tests): %.5f\n", total_time_);
printf("Total CPU time (only passed valid tests): %.5f\n", total_cpu_time_);
if (cnt_fail_ > 0) {
printf("Failed %lu/%lu tests\n", cnt_fail_, test_files_.size());
}
if (cnt_fatal_ > 0) {
printf("FATAL ERROR %lu/%lu tests\n", cnt_fatal_, test_files_.size());
}
exit(0);
}

private:
std::string tests_dir_;
std::vector<std::string> test_files_;
size_t test_idx_ = 0;
size_t cnt_ok_ = 0, cnt_fail_ = 0, cnt_fatal_ = 0;

size_t test_idx_column_width_ = 0;
size_t test_name_column_width_ = 0;
size_t separator_length_ = 0;

double total_time_ = 0.0;
double total_cpu_time_ = 0.0;
};

int main(int argc, char* argv[]) {
SET_VERBOSITY_LEVEL(verbosity_ERROR);

td::actor::ActorOwn<ContestGrader> x;
td::unique_ptr<td::LogInterface> logger_;
SCOPE_EXIT {
td::log_interface = td::default_log_interface;
};

td::OptionParser p;
p.set_description("Block validation contest");
p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
int v = VERBOSITY_NAME(FATAL) + (td::to_integer<int>(arg));
SET_VERBOSITY_LEVEL(v);
});
p.add_option('h', "help", "prints a help message", [&]() {
char b[10240];
td::StringBuilder sb(td::MutableSlice{b, 10000});
sb << p;
std::cout << sb.as_cslice().c_str();
std::exit(2);
});
std::string tests_dir = "tests/";
p.add_option('d', "tests", "directory with tests (default: tests/)",
[&](td::Slice arg) { tests_dir = arg.str() + "/"; });
td::uint32 threads = 8;
p.add_checked_option('t', "threads", "number of threads (default: 8)", [&](td::Slice arg) {
TRY_RESULT_ASSIGN(threads, td::to_integer_safe<td::uint32>(arg));
return td::Status::OK();
});

p.run(argc, argv).ensure();
td::actor::Scheduler scheduler({threads});

scheduler.run_in_context([&] { x = td::actor::create_actor<ContestGrader>("grader", tests_dir); });
while (scheduler.run(1)) {
}

return 0;
}
6 changes: 6 additions & 0 deletions contest/solution/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

add_library(contest-solution STATIC solution.hpp solution.cpp contest-validate-query.cpp contest-validate-query.hpp)
target_include_directories(contest-solution PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../..>)
target_include_directories(contest-solution PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../../validator>)
target_link_libraries(contest-solution PRIVATE tdactor tdutils ton_block ton_validator validator ton_crypto)
Loading

0 comments on commit d8e29f8

Please sign in to comment.