Skip to content

feature/utils #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .ci/scripts/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ main() {
linux_deb)
echo "=== Detected Linux system with apt"
apt update && apt install -y $LINUX_PACKAGES
if [ -f "$HOME/.cargo/env" ]; then
source "$HOME/.cargo/env"
fi
export PATH="$HOME/.cargo/bin:$PATH"
;;
linux_other)
echo "=== Detected Linux system without apt"
Expand All @@ -31,6 +35,12 @@ main() {
echo "=== Unknown system"
;;
esac

if command -v cargo >/dev/null 2>&1; then
echo "=== Cargo is available: $(cargo --version)"
else
echo "=== Warning: Cargo is not available in PATH"
fi
}

main
Expand Down
26 changes: 25 additions & 1 deletion .github/workflows/build_and_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ on:

env:
USE_CACHE: ${{ github.event.inputs.use_cache || 'true' }}
CACHE_VERSION: v01
CACHE_VERSION: v02
CARGO_HOME: ~/.cargo
RUSTUP_HOME: ~/.rustup
CACHE_PATH: |
~/.cargo
~/.hunter
Expand All @@ -43,6 +45,12 @@ jobs:
submodules: true
fetch-depth: 0

- name: "Set up Rust"
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt, clippy

- name: "Restore cache dependencies"
id: cache-restore
if: ${{ env.USE_CACHE == 'true' }}
Expand All @@ -62,11 +70,27 @@ jobs:
else
./.ci/scripts/init.sh
fi
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
# Ensure Rust is available
source "$HOME/.cargo/env" || true
echo "CARGO_HOME=$HOME/.cargo" >> $GITHUB_ENV
echo "RUSTUP_HOME=$HOME/.rustup" >> $GITHUB_ENV

- name: "Init all dependencies"
run: |
make init_py
make init_vcpkg

- name: "Check Rust toolchain"
run: |
echo "=== Checking Rust toolchain ==="
which rustc || echo "rustc not found"
which cargo || echo "cargo not found"
rustc --version || echo "rustc version check failed"
cargo --version || echo "cargo version check failed"
echo "PATH: $PATH"
echo "CARGO_HOME: $CARGO_HOME"
echo "RUSTUP_HOME: $RUSTUP_HOME"

- name: "Configure"
run: make configure
Expand Down
39 changes: 37 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,42 @@ project(cpp-jam
LANGUAGES CXX
)

if(DEFINED CMAKE_TOOLCHAIN_FILE AND CMAKE_TOOLCHAIN_FILE MATCHES "vcpkg")
if(DEFINED VCPKG_TARGET_TRIPLET AND VCPKG_TARGET_TRIPLET)
set(DETECTED_TRIPLET ${VCPKG_TARGET_TRIPLET})
message(STATUS "Using vcpkg triplet from VCPKG_TARGET_TRIPLET: ${DETECTED_TRIPLET}")
else()
if(WIN32)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(DETECTED_TRIPLET "x64-windows")
else()
set(DETECTED_TRIPLET "x86-windows")
endif()
elseif(APPLE)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(DETECTED_TRIPLET "x64-osx")
else()
set(DETECTED_TRIPLET "arm64-osx")
endif()
else()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(DETECTED_TRIPLET "x64-linux")
else()
set(DETECTED_TRIPLET "x86-linux")
endif()
endif()
message(STATUS "Auto-detected vcpkg triplet: ${DETECTED_TRIPLET}")
endif()

set(CMAKE_INSTALL_LIBDIR "${DETECTED_TRIPLET}/lib")
set(CMAKE_INSTALL_INCLUDEDIR "${DETECTED_TRIPLET}/include")

message(STATUS "CMAKE_INSTALL_LIBDIR: ${CMAKE_INSTALL_LIBDIR}")
message(STATUS "CMAKE_INSTALL_INCLUDEDIR: ${CMAKE_INSTALL_INCLUDEDIR}")
endif()

include(GNUInstallDirs)

message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
message(STATUS "Boost_DIR: ${Boost_DIR}")

Expand All @@ -32,10 +68,9 @@ pkg_check_modules(libb2 REQUIRED IMPORTED_TARGET GLOBAL libb2)
find_package(Boost CONFIG REQUIRED COMPONENTS algorithm outcome program_options)
find_package(fmt CONFIG REQUIRED)
find_package(yaml-cpp CONFIG REQUIRED)
find_package(jam_crust CONFIG REQUIRED)
find_package(qdrvm-crates CONFIG REQUIRED)
find_package(scale CONFIG REQUIRED)
find_package(soralog CONFIG REQUIRED)
find_package(schnorrkel_crust CONFIG REQUIRED)
find_package(Boost.DI CONFIG REQUIRED)
find_package(qtils CONFIG REQUIRED)
find_package(prometheus-cpp CONFIG REQUIRED)
Expand Down
3 changes: 2 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"CMAKE_BUILD_TYPE": "Debug"
"CMAKE_BUILD_TYPE": "Debug",
"VCPKG_OVERLAY_PORTS": "${sourceDir}/vcpkg-overlay"
}
}
]
Expand Down
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ init_vcpkg:

configure:
@echo "=== Configuring..."
export PATH="$$HOME/.cargo/bin:$$PATH" && \
source $$HOME/.cargo/env 2>/dev/null || true && \
VCPKG_ROOT=$(VCPKG) cmake --preset=default -DPython3_EXECUTABLE="$(VENV)/bin/python3" -B $(BUILD) $(PROJECT)

build:
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Build
```cmake
rm -rf build && cmake -B build -DCMAKE_BUILD_TYPE=[Release | Debug] -DTESTING=ON -DCMAKE_TOOLCHAIN_FILE=<path to vcpkg.cmake> -G "Ninja" -DVCPKG_OVERLAY_PORTS=vcpkg-overlay
```
```cmake
cmake --build build
```
4 changes: 2 additions & 2 deletions src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ set(BUILD_VERSION_CPP "${CMAKE_BINARY_DIR}/generated/app/build_version.cpp")
set(GET_VERSION_SCRIPT "${CMAKE_SOURCE_DIR}/scripts/get_version.sh")
add_custom_command(
OUTPUT ${BUILD_VERSION_CPP}
COMMAND echo "// Auto-generated file\\n" > ${BUILD_VERSION_CPP}
COMMAND echo "#include <string>\\n" >> ${BUILD_VERSION_CPP}
COMMAND echo "// Auto-generated file" > ${BUILD_VERSION_CPP}
COMMAND echo "#include <string>" >> ${BUILD_VERSION_CPP}
COMMAND echo "namespace jam {" >> ${BUILD_VERSION_CPP}
COMMAND echo " const std::string &buildVersion() {" >> ${BUILD_VERSION_CPP}
COMMAND printf " static const std::string buildVersion(\"" >> ${BUILD_VERSION_CPP}
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/bandersnatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <memory>
#include <optional>

#include <jam_crust.h>
#include <ark_vrf/ark_vrf.h>
#include <qtils/bytes.hpp>

namespace jam::crypto::bandersnatch {
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/ed25519.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <schnorrkel_crust.h>
#include <schnorrkel/schnorrkel.h>
#include <qtils/bytes.hpp>

namespace jam::crypto::ed25519 {
Expand Down
209 changes: 209 additions & 0 deletions src/se/impl/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/**
* Copyright Quadrivium LLC
* All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <chrono>
#include <condition_variable>
#include <mutex>
#include <shared_mutex>

namespace jam::se::utils {

/**
* @brief Creates a weak_ptr from a shared_ptr
*
* Utility function that simplifies creating a weak_ptr from a shared_ptr.
*
* @tparam T The type of object pointed to
* @param ptr The shared_ptr to create a weak_ptr from
* @return A weak_ptr that shares ownership with the input shared_ptr
*/
template <typename T>
inline std::weak_ptr<T> make_weak(const std::shared_ptr<T> &ptr) noexcept {
return ptr;
}

/**
* @brief Thread-safe wrapper for any object
*
* SafeObject provides exclusive and shared access patterns to an internal
* object with proper synchronization. It uses a mutex to protect the object
* from concurrent access.
*
* @tparam T The type of object to wrap
* @tparam M The mutex type to use for synchronization (defaults to
* std::shared_mutex)
*/
template <typename T, typename M = std::shared_mutex>
struct SafeObject {
using Type = T;

/**
* @brief Constructor that forwards arguments to the wrapped object
*
* @tparam Args Parameter pack of argument types
* @param args Arguments to forward to the wrapped object's constructor
*/
template <typename... Args>
SafeObject(Args &&...args) : t_(std::forward<Args>(args)...) {}

/**
* @brief Provides exclusive (write) access to the wrapped object
*
* Locks the mutex to ensure exclusive access and applies the provided
* function to the wrapped object.
*
* @tparam F Type of the function to apply
* @param f Function to apply to the wrapped object
* @return The result of applying the function to the wrapped object
*/
template <typename F>
inline auto exclusiveAccess(F &&f) {
std::unique_lock lock(cs_);
return std::forward<F>(f)(t_);
}

/**
* @brief Attempts to get exclusive access without blocking
*
* Tries to lock the mutex. If successful, applies the provided function to
* the wrapped object and returns the result. If unsuccessful, returns an
* empty optional.
*
* @tparam F Type of the function to apply
* @param f Function to apply to the wrapped object
* @return An optional containing the result of the function, or empty if
* the lock was not acquired
*/
template <typename F>
inline auto try_exclusiveAccess(F &&f) {
std::unique_lock lock(cs_, std::try_to_lock);
using ResultType = decltype(std::forward<F>(f)(t_));
constexpr bool is_void = std::is_void_v<ResultType>;
using OptionalType = std::conditional_t<is_void,
std::optional<std::monostate>,
std::optional<ResultType>>;

if (lock.owns_lock()) {
if constexpr (is_void) {
std::forward<F>(f)(t_);
return OptionalType(std::in_place);
} else {
return OptionalType(std::forward<F>(f)(t_));
}
} else {
return OptionalType();
}
}

/**
* @brief Provides shared (read) access to the wrapped object
*
* Acquires a shared lock on the mutex and applies the provided function
* to the wrapped object.
*
* @tparam F Type of the function to apply
* @param f Function to apply to the wrapped object
* @return The result of applying the function to the wrapped object
*/
template <typename F>
inline auto sharedAccess(F &&f) const {
std::shared_lock lock(cs_);
return std::forward<F>(f)(t_);
}

private:
T t_; ///< The wrapped object
mutable M cs_; ///< Mutex for synchronization
};

/**
* @brief Alias for SafeObject with a more descriptive name
*
* Provides the same functionality as SafeObject but with a name that better
* describes the read-write access pattern.
*
* @tparam T The type of object to wrap
* @tparam M The mutex type to use for synchronization (defaults to
* std::shared_mutex)
*/
template <typename T, typename M = std::shared_mutex>
using ReadWriteObject = SafeObject<T, M>;

/**
* @brief A synchronization primitive similar to a manual reset event
*
* WaitForSingleObject provides a way to signal and wait for a condition
* between threads. It's similar to a manual reset event, where one thread
* can wait until another thread signals the event.
*/
class WaitForSingleObject final {
std::condition_variable wait_cv_; ///< Condition variable for waiting
std::mutex wait_m_; ///< Mutex for synchronization
bool flag_; ///< Flag that represents the state (true = not signaled, false
///< = signaled)

public:
/**
* @brief Constructor that initializes the object in the not signaled state
*/
WaitForSingleObject() : flag_{true} {}

// Deleted copy and move operations to prevent improper synchronization
WaitForSingleObject(WaitForSingleObject &&) = delete;
WaitForSingleObject(const WaitForSingleObject &) = delete;
WaitForSingleObject &operator=(WaitForSingleObject &&) = delete;
WaitForSingleObject &operator=(const WaitForSingleObject &) = delete;

/**
* @brief Waits for the object to be signaled with a timeout
*
* Blocks the current thread until the object is signaled or the timeout
* expires. The state is automatically reset to not signaled after a
* successful wait.
*
* @param wait_timeout Maximum time to wait
* @return true if the object was signaled, false if the timeout expired
*/
bool wait(std::chrono::microseconds wait_timeout) {
std::unique_lock<std::mutex> _lock(wait_m_);
return wait_cv_.wait_for(_lock, wait_timeout, [&]() {
auto prev = !flag_;
flag_ = true;
return prev;
});
}

/**
* @brief Waits indefinitely for the object to be signaled
*
* Blocks the current thread until the object is signaled.
* The state is automatically reset to not signaled after a successful wait.
*/
void wait() {
std::unique_lock<std::mutex> _lock(wait_m_);
wait_cv_.wait(_lock, [&]() {
auto prev = !flag_;
flag_ = true;
return prev;
});
}

/**
* @brief Signals the object
*
* Sets the object to the signaled state and wakes one waiting thread.
*/
void set() {
{
std::unique_lock<std::mutex> _lock(wait_m_);
flag_ = false;
}
wait_cv_.notify_one();
}
};
} // namespace jam::se::utils
Loading
Loading