glove/tests/container/receipt_producer_test.cpp

433 lines
17 KiB
C++

#include "glove/container/receipt_producer.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include <atomic>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <vector>
namespace {
#define REQUIRE(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "REQUIRE failed: %s @ %s:%d\n", #cond, __FILE__, __LINE__); \
return 1; \
} \
} while (0)
constexpr std::string_view audit_key =
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
constexpr std::string_view controller_plan_digest =
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
constexpr std::string_view profile_digest =
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
class temporary_directory {
public:
temporary_directory() {
std::string pattern = "/tmp/glove-receipt-producer-test-XXXXXX";
char* created = ::mkdtemp(pattern.data());
if (created != nullptr) {
root_ = created;
}
}
temporary_directory(const temporary_directory&) = delete;
auto operator=(const temporary_directory&) -> temporary_directory& = delete;
~temporary_directory() {
std::error_code error;
std::filesystem::remove_all(root_, error);
}
[[nodiscard]] auto root() const -> const std::filesystem::path& { return root_; }
private:
std::filesystem::path root_;
};
auto write_key(const std::filesystem::path& path, std::string_view value, mode_t mode = 0600)
-> bool {
std::ofstream output{path, std::ios::binary | std::ios::trunc};
output << value << '\n';
output.flush();
return output.good() && ::chmod(path.c_str(), mode) == 0;
}
auto append_byte(const std::filesystem::path& path) -> bool {
std::ofstream output{path, std::ios::binary | std::ios::app};
output.put('x');
output.flush();
return output.good();
}
auto receipt(std::uint64_t cpu_time_ms = 500) -> glove::container::resource_enforcement_receipt {
return {
.schema_version = 1,
.profile_digest = std::string{profile_digest},
.backend = glove::container::sandbox_backend::linux_production,
.backend_id = "linux-production:cgroup-v2-v1",
.configured_limits =
{
.cpu_time_ms = 60'000,
.memory_bytes = std::uint64_t{512} * 1024U * 1024U,
.pids = 128,
.wall_time_ms = 120'000,
.disk_bytes = std::uint64_t{1024} * 1024U * 1024U,
.terminal_output_bytes = std::uint64_t{16} * 1024U * 1024U,
},
.mechanisms =
{
.cpu_time = glove::container::enforcement_mechanism::cgroup_v2,
.memory = glove::container::enforcement_mechanism::cgroup_v2,
.pids = glove::container::enforcement_mechanism::cgroup_v2,
.wall_time = glove::container::enforcement_mechanism::watchdog,
.disk = glove::container::enforcement_mechanism::filesystem_quota,
.terminal_output = glove::container::enforcement_mechanism::byte_counter,
.receipt_schema_version = 1,
},
.observed =
{
.cpu_time_ms = cpu_time_ms,
.peak_memory_bytes = std::uint64_t{16} * 1024U * 1024U,
.peak_pids = 2,
.wall_time_ms = 750,
.disk_bytes = 4096,
.terminal_output_bytes = 1024,
},
.termination_cause = glove::container::resource_termination_cause::exited,
.exit_code = 0,
.started_at_ms = 1'000,
.finished_at_ms = 1'750,
.library_projections = {},
.retained_changes = {},
};
}
auto bootstrap_reconciliation_and_durable_commit() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
const auto journal_path = temp.root() / "receipts.journal";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = journal_path,
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
const auto genesis = (*producer)->anchor();
REQUIRE(genesis.sequence == 0);
REQUIRE(!(*producer)->bootstrap_reconciled());
REQUIRE(!(*producer)->reserve_terminal().has_value());
auto forged = genesis;
forged.head_hmac[0] = '1';
REQUIRE(!(*producer)->acknowledge_bootstrap(forged).has_value());
REQUIRE(!(*producer)->bootstrap_reconciled());
REQUIRE((*producer)->acknowledge_bootstrap(genesis).has_value());
REQUIRE((*producer)->bootstrap_reconciled());
REQUIRE(!glove::container::receipt_audit_producer::recover(config, genesis).has_value());
auto reservation = (*producer)->reserve_terminal();
REQUIRE(reservation.has_value());
auto first = (*producer)->commit_terminal(
std::move(*reservation), "session-1", controller_plan_digest, receipt()
);
REQUIRE(first.has_value());
REQUIRE(first->sequence == 1);
const auto first_anchor = (*producer)->anchor();
auto page = (*producer)->page_after(genesis, 10);
REQUIRE(page.has_value());
REQUIRE(page->envelopes.size() == 1);
REQUIRE(page->envelopes[0] == *first);
producer->reset();
auto recovered = glove::container::receipt_audit_producer::recover(config, genesis);
REQUIRE(recovered.has_value());
REQUIRE(!(*recovered)->bootstrap_reconciled());
REQUIRE(!(*recovered)->reserve_terminal().has_value());
auto pending = (*recovered)->page_after(genesis, 10);
REQUIRE(pending.has_value());
REQUIRE(pending->envelopes.size() == 1);
REQUIRE((*recovered)->acknowledge_bootstrap(first_anchor).has_value());
REQUIRE((*recovered)->bootstrap_reconciled());
auto second_reservation = (*recovered)->reserve_terminal();
REQUIRE(second_reservation.has_value());
auto second =
(*recovered)
->commit_terminal(
std::move(*second_reservation), "session-2", controller_plan_digest, receipt(501)
);
REQUIRE(second.has_value());
const auto second_anchor = (*recovered)->anchor();
recovered->reset();
auto caught_up = glove::container::receipt_audit_producer::recover(config, second_anchor);
REQUIRE(caught_up.has_value());
REQUIRE((*caught_up)->bootstrap_reconciled());
auto caught_up_page = (*caught_up)->page_after(second_anchor, 10);
REQUIRE(caught_up_page.has_value());
REQUIRE(caught_up_page->envelopes.empty());
caught_up->reset();
auto ahead = second_anchor;
++ahead.sequence;
REQUIRE(!glove::container::receipt_audit_producer::recover(config, ahead).has_value());
auto divergent = second_anchor;
divergent.head_hmac[0] = divergent.head_hmac[0] == 'a' ? 'b' : 'a';
REQUIRE(!glove::container::receipt_audit_producer::recover(config, divergent).has_value());
return 0;
}
auto concurrent_terminal_commits_are_serialized() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = temp.root() / "receipts.journal",
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
REQUIRE((*producer)->acknowledge_bootstrap((*producer)->anchor()).has_value());
std::atomic<std::uint64_t> committed = 0;
{
std::vector<std::thread> workers;
workers.reserve(8);
for (std::uint64_t index = 0; index < 8; ++index) {
workers.emplace_back([&, index] {
auto reservation = (*producer)->reserve_terminal();
if (!reservation) {
return;
}
const auto session = "session-" + std::to_string(index);
if ((*producer)->commit_terminal(
std::move(*reservation),
session,
controller_plan_digest,
receipt(500U + index)
)) {
committed.fetch_add(1, std::memory_order_relaxed);
}
});
}
for (auto& worker : workers) {
worker.join();
}
}
REQUIRE(committed.load(std::memory_order_relaxed) == 8);
REQUIRE((*producer)->anchor().sequence == 8);
return 0;
}
auto capacity_is_reserved_before_a_session() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = temp.root() / "receipts.journal",
.max_journal_bytes = glove::container::max_receipt_journal_record_bytes,
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
REQUIRE((*producer)->acknowledge_bootstrap((*producer)->anchor()).has_value());
REQUIRE(!(*producer)->reserve_terminal().has_value());
REQUIRE((*producer)->anchor().sequence == 0);
return 0;
}
auto abandoned_capacity_reservation_is_released() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = temp.root() / "receipts.journal",
.max_journal_bytes = glove::container::max_receipt_journal_record_bytes + 128U,
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
REQUIRE((*producer)->acknowledge_bootstrap((*producer)->anchor()).has_value());
{
auto reservation = (*producer)->reserve_terminal();
REQUIRE(reservation.has_value());
REQUIRE(!(*producer)->reserve_terminal().has_value());
}
REQUIRE((*producer)->reserve_terminal().has_value());
return 0;
}
auto execution_bound_reservation_is_single_profile_authority() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = temp.root() / "receipts.journal",
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
const auto genesis = (*producer)->anchor();
REQUIRE((*producer)->acknowledge_bootstrap(genesis).has_value());
auto reservation =
(*producer)->reserve_terminal("session-bound", controller_plan_digest, profile_digest);
REQUIRE(reservation.has_value());
REQUIRE(
reservation->matches_execution("session-bound", controller_plan_digest, profile_digest)
);
REQUIRE(
!reservation->matches_execution("session-other", controller_plan_digest, profile_digest)
);
REQUIRE(!(*producer)
->commit_terminal(
std::move(*reservation), "session-other", controller_plan_digest, receipt()
)
.has_value());
REQUIRE((*producer)->anchor() == genesis);
auto retry =
(*producer)->reserve_terminal("session-bound", controller_plan_digest, profile_digest);
REQUIRE(retry.has_value());
auto committed = (*producer)->commit_terminal(
std::move(*retry), "session-bound", controller_plan_digest, receipt()
);
REQUIRE(committed.has_value());
REQUIRE(committed->session_id == "session-bound");
REQUIRE(committed->receipt.profile_digest == profile_digest);
auto confirmed = (*producer)->confirms_terminal(*committed);
REQUIRE(confirmed.has_value());
REQUIRE(*confirmed);
auto lookup = (*producer)->terminal_for_execution(
"session-bound", controller_plan_digest, profile_digest
);
REQUIRE(lookup.has_value());
REQUIRE(lookup->has_value());
REQUIRE(**lookup == *committed);
auto missing = (*producer)->terminal_for_execution(
"session-missing", controller_plan_digest, profile_digest
);
REQUIRE(missing.has_value());
REQUIRE(!missing->has_value());
auto tampered = *committed;
tampered.receipt_digest = std::string(64, 'f');
auto tampered_confirmation = (*producer)->confirms_terminal(tampered);
REQUIRE(tampered_confirmation.has_value());
REQUIRE(!*tampered_confirmation);
const auto committed_anchor = (*producer)->anchor();
producer->reset();
auto recovered = glove::container::receipt_audit_producer::recover(config, committed_anchor);
REQUIRE(recovered.has_value());
auto recovered_confirmation = (*recovered)->confirms_terminal(*committed);
REQUIRE(recovered_confirmation.has_value());
REQUIRE(*recovered_confirmation);
auto recovered_lookup =
(*recovered)
->terminal_for_execution("session-bound", controller_plan_digest, profile_digest);
REQUIRE(recovered_lookup.has_value());
REQUIRE(recovered_lookup->has_value());
REQUIRE(**recovered_lookup == *committed);
return 0;
}
auto append_failure_never_acknowledges_terminal_success() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
const auto journal_path = temp.root() / "receipts.journal";
REQUIRE(write_key(key_path, audit_key));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = journal_path,
};
auto producer = glove::container::receipt_audit_producer::initialize(config);
REQUIRE(producer.has_value());
const auto genesis = (*producer)->anchor();
REQUIRE((*producer)->acknowledge_bootstrap(genesis).has_value());
auto reservation = (*producer)->reserve_terminal();
REQUIRE(reservation.has_value());
REQUIRE(append_byte(journal_path));
REQUIRE(!(*producer)
->commit_terminal(
std::move(*reservation), "session-1", controller_plan_digest, receipt()
)
.has_value());
REQUIRE((*producer)->anchor() == genesis);
producer->reset();
auto repaired = glove::container::receipt_audit_producer::recover(config, genesis);
REQUIRE(repaired.has_value());
REQUIRE((*repaired)->anchor() == genesis);
return 0;
}
auto key_file_is_fail_closed() -> int {
temporary_directory temp;
REQUIRE(!temp.root().empty());
const auto key_path = temp.root() / "audit.key";
const auto journal_path = temp.root() / "receipts.journal";
REQUIRE(write_key(key_path, audit_key, 0644));
const glove::container::receipt_audit_producer_config config{
.key_path = key_path,
.journal_path = journal_path,
};
REQUIRE(!glove::container::receipt_audit_producer::initialize(config).has_value());
REQUIRE(!std::filesystem::exists(journal_path));
REQUIRE(::chmod(key_path.c_str(), 0600) == 0);
const auto symlink_path = temp.root() / "audit-link.key";
std::filesystem::create_symlink(key_path, symlink_path);
auto symlink_config = config;
symlink_config.key_path = symlink_path;
REQUIRE(!glove::container::receipt_audit_producer::initialize(symlink_config).has_value());
const auto hardlink_path = temp.root() / "audit-hardlink.key";
std::filesystem::create_hard_link(key_path, hardlink_path);
REQUIRE(!glove::container::receipt_audit_producer::initialize(config).has_value());
auto hardlink_config = config;
hardlink_config.key_path = hardlink_path;
REQUIRE(!glove::container::receipt_audit_producer::initialize(hardlink_config).has_value());
const auto short_key_path = temp.root() / "short.key";
REQUIRE(write_key(short_key_path, "abcd"));
auto short_config = config;
short_config.key_path = short_key_path;
REQUIRE(!glove::container::receipt_audit_producer::initialize(short_config).has_value());
REQUIRE(!std::filesystem::exists(journal_path));
return 0;
}
auto run() -> int {
REQUIRE(bootstrap_reconciliation_and_durable_commit() == 0);
REQUIRE(concurrent_terminal_commits_are_serialized() == 0);
REQUIRE(capacity_is_reserved_before_a_session() == 0);
REQUIRE(abandoned_capacity_reservation_is_released() == 0);
REQUIRE(execution_bound_reservation_is_single_profile_authority() == 0);
REQUIRE(append_failure_never_acknowledges_terminal_success() == 0);
REQUIRE(key_file_is_fail_closed() == 0);
return 0;
}
} // namespace
auto main() -> int {
return run();
}