Skip to content
Open
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
35 changes: 34 additions & 1 deletion .github/workflows/build_bdk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0

- name: Set up Python 3
Expand Down Expand Up @@ -133,6 +132,28 @@ jobs:
cmake --build build --parallel 4
ctest --output-on-failure --test-dir build

- name: Build GoBDK shared library for purego
if: runner.os != 'Windows'
run: |
cmake --build build --target GoBDK --parallel 4

- name: Test GoBDK with purego
if: runner.os != 'Windows'
env:
GOFLAGS: ""
run: |
if [ "$(uname)" = "Darwin" ]; then
LIB_EXT="dylib"
else
LIB_EXT="so"
fi
LIB_PATH=${{ github.workspace }}/module/gobdk/bdkpurego/lib/libGoBDK_${{ env.OS_ARCH }}.${LIB_EXT}
echo "Library path: ${LIB_PATH}"
ls -lh "${LIB_PATH}" || echo "WARNING: shared library not found"
cd test/golang
BDK_LIB_PATH=${LIB_PATH} \
CGO_ENABLED=0 go test -tags bdk_purego -mod=mod -v ./... || echo "::warning::purego tests failed (non-blocking)"

- name: Check GoBDK has changed
id: gobdk_change
if: runner.os != 'Windows' && github.event_name == 'workflow_dispatch'
Expand All @@ -156,6 +177,18 @@ jobs:
retention-days: 1
overwrite: true

- name: Upload GoBDK shared libraries to artifact UNIX
if: runner.os != 'Windows' && github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: libGoBDK_shared_${{ env.OS_ARCH }}
path: |
./module/gobdk/bdkpurego/lib/libGoBDK_${{ env.OS_ARCH }}.dylib
./module/gobdk/bdkpurego/lib/libGoBDK_${{ env.OS_ARCH }}.so
retention-days: 1
overwrite: true
if-no-files-found: ignore

- name: Prepare Environment and Dependencies for Windows
if: runner.os == 'Windows'
run: |
Expand Down
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# IDE
.idea/

# CMake build output
build/
build-*/

# Shared libraries for purego are committed (same as static .a files in bdkcgo/)
# Only ignore intermediate build artifacts, not the final libs
# module/gobdk/bdkpurego/lib/ -- tracked, not ignored

# Go vendor directory (test module)
test/golang/vendor/
16 changes: 14 additions & 2 deletions cmake/BDKBuildSetting.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,22 @@ macro(bdkSetCompilationOptions)## This has to be macro instead of a function bec
endif()

if(UNIX)
set(CMAKE_CXX_FLAGS "-fPIC -fvisibility=default" CACHE STRING "compilation flags for C++" FORCE)
# Use the correct native-tuning flag for each architecture.
# When cross-compiling on macOS (CMAKE_OSX_ARCHITECTURES set to a different arch),
# skip native tuning since the host CPU differs from the target.
if(APPLE AND CMAKE_OSX_ARCHITECTURES AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL CMAKE_SYSTEM_PROCESSOR)
set(_BDK_NATIVE_FLAG "")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")
set(_BDK_NATIVE_FLAG "-march=native")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(_BDK_NATIVE_FLAG "-mcpu=native")
else()
set(_BDK_NATIVE_FLAG "")
endif()
set(CMAKE_CXX_FLAGS "-fPIC -fvisibility=default ${_BDK_NATIVE_FLAG}" CACHE STRING "compilation flags for C++" FORCE)
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 " CACHE STRING "flags for C++ debug version" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -g0 -DNDEBUG" CACHE STRING "flags for C++ release version" FORCE)
set(CMAKE_C_FLAGS "-fPIC" CACHE STRING "flags for C" FORCE)
set(CMAKE_C_FLAGS "-fPIC ${_BDK_NATIVE_FLAG}" CACHE STRING "flags for C" FORCE)
set(CMAKE_C_FLAGS_DEBUG "-O0 -g3" CACHE STRING "flags for C debug version" FORCE)
set(CMAKE_C_FLAGS_RELEASE "-O3 -g0 -DNDEBUG" CACHE STRING "flags for C release version" FORCE)
endif()
Expand Down
73 changes: 73 additions & 0 deletions core/scriptengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,79 @@ std::vector<ScriptError> bsv::CScriptEngine::VerifyScriptBatch(const VerifyBatch
return results;
}

void bsv::CScriptEngine::ensureThreadPool() const
{
std::call_once(threadPoolInit, [this]() {
size_t n = std::thread::hardware_concurrency();
if (n == 0) n = 1;
threadPool = std::make_unique<ThreadPool>(n);
});
}

std::vector<ScriptError> bsv::CScriptEngine::VerifyScriptBatchParallel(const VerifyBatch& batch, size_t numThreads) const
{
const size_t batchSize = batch.size();
if (batchSize == 0) return {};

ensureThreadPool();

if (numThreads == 0) {
numThreads = threadPool->size();
}

numThreads = std::min(numThreads, batchSize);

std::vector<ScriptError> results(batchSize, SCRIPT_ERR_OK);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default result should be SCRIPT_ERR_UNKNOWN_ERROR, i.e in case something unknown happens, the error is relevant.


if (numThreads == 1) {
size_t i = 0;
for (const auto& elem : batch) {
try {
results[i] = VerifyScript(elem.extendedTX, elem.utxoHeights, elem.blockHeight, elem.consensus, elem.customFlags);
} catch (...) {
results[i] = SCRIPT_ERR_UNKNOWN_ERROR;
}
++i;
}
return results;
}

auto worker = [&](size_t start, size_t end) {
auto it = batch.begin();
std::advance(it, start);
for (size_t i = start; i < end; ++i, ++it) {
try {
results[i] = VerifyScript(it->extendedTX, it->utxoHeights, it->blockHeight, it->consensus, it->customFlags);
} catch (...) {
results[i] = SCRIPT_ERR_UNKNOWN_ERROR;
}
}
};

const size_t chunkSize = batchSize / numThreads;
const size_t remainder = batchSize % numThreads;

std::vector<std::future<void>> futures;
futures.reserve(numThreads - 1);

size_t start = 0;
for (size_t t = 0; t < numThreads; ++t) {
size_t end = start + chunkSize + (t < remainder ? 1 : 0);
if (t == numThreads - 1) {
worker(start, end); // Main thread handles last chunk
} else {
futures.push_back(threadPool->submit([&worker, start, end]() { worker(start, end); }));
}
start = end;
}

for (auto& f : futures) {
f.get();
}

return results;
}

ScriptError bsv::CScriptEngine::verifyImpl(
const CScript& unlocking_script,
const CScript& locking_script,
Expand Down
14 changes: 14 additions & 0 deletions core/scriptengine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
#include <cstdint>
#include <span>
#include <memory>
#include <mutex>
#include <optional>
#include <utility>
#include <vector>
#include <thread>

#include <configscriptpolicy.h>
#include <chainparams.h>
#include <taskcancellation.h>
#include <script/script.h>
#include <script/interpreter.h>
#include <verifyarg.hpp>
#include <thread_pool.hpp>

namespace bsv
{
Expand Down Expand Up @@ -96,11 +99,22 @@ class CScriptEngine {
// Returns a vector of ScriptError results, one for each VerifyArg in the input
std::vector<ScriptError> VerifyScriptBatch(const VerifyBatch& batch) const;

// VerifyScriptBatchParallel processes multiple script verifications in parallel
// numThreads: number of threads to use (0 = std::thread::hardware_concurrency())
// Divides the batch into chunks and processes each chunk on a separate thread
// Per-item exceptions are caught and reported as SCRIPT_ERR_UNKNOWN_ERROR
std::vector<ScriptError> VerifyScriptBatchParallel(const VerifyBatch& batch, size_t numThreads = 0) const;

private :
ConfigScriptPolicy policySettings;
std::unique_ptr<CChainParams> chainParams;
std::shared_ptr<task::CCancellationSource> source;

// Lazy-initialized thread pool for VerifyScriptBatchParallel
mutable std::unique_ptr<ThreadPool> threadPool;
mutable std::once_flag threadPoolInit;
void ensureThreadPool() const;

ScriptError verifyImpl(
const CScript& unlocking_script,
const CScript& locking_script,
Expand Down
24 changes: 24 additions & 0 deletions core/setting-secp256k1.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@ endif()
set(SECP256K1_BUILD_TESTS OFF)
set(SECP256K1_BUILD_EXHAUSTIVE_TESTS OFF)

# Enable architecture-specific optimizations for secp256k1:
# - x86_64: hand-optimized assembly for field/scalar ops + -march=native (AVX2, etc.)
# - ARM64: generic C with __int128 + -mcpu=native (Apple M-series/Neoverse tuning)
# Also override secp256k1's default -O2 with -O3 for maximum throughput.
# When cross-compiling on macOS (CMAKE_OSX_ARCHITECTURES differs from CMAKE_SYSTEM_PROCESSOR),
# use the target architecture but skip -march/mcpu=native since the host CPU differs.
if(APPLE AND CMAKE_OSX_ARCHITECTURES AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL CMAKE_SYSTEM_PROCESSOR)
# Cross-compilation: use the target arch for ASM selection but no native tuning
if(CMAKE_OSX_ARCHITECTURES MATCHES "^(x86_64|AMD64)$")
set(SECP256K1_ASM "x86_64" CACHE STRING "" FORCE)
else()
set(SECP256K1_ASM "OFF" CACHE STRING "" FORCE)
endif()
set(SECP256K1_APPEND_CFLAGS "-O3" CACHE STRING "" FORCE)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")
set(SECP256K1_ASM "x86_64" CACHE STRING "" FORCE)
set(SECP256K1_APPEND_CFLAGS "-march=native -O3" CACHE STRING "" FORCE)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
set(SECP256K1_ASM "OFF" CACHE STRING "" FORCE)
set(SECP256K1_APPEND_CFLAGS "-mcpu=native -O3" CACHE STRING "" FORCE)
else()
set(SECP256K1_APPEND_CFLAGS "-O3" CACHE STRING "" FORCE)
endif()

add_subdirectory("${BDK_BSV_ROOT_DIR}/src/secp256k1" ${CMAKE_CURRENT_BINARY_DIR}/secp256k1)

## Set the IDE Folder to the created targets for secp256k1 to the right place #########
Expand Down
79 changes: 79 additions & 0 deletions core/thread_pool.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#ifndef __THREAD_POOL_HPP__
#define __THREAD_POOL_HPP__

#include <condition_variable>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>

namespace bsv
{

class ThreadPool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying hard to make the bdk core not depending to thread. The reason was to plan for wasm build, that don't like threading.

Threading implementation would be in client code, i.e module level (go module, py module ...), or at higher level (client code)

public:
explicit ThreadPool(size_t numThreads)
{
workers.reserve(numThreads);
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) return;
task = std::move(tasks.front());
tasks.pop();
}
task();
}
});
}
}

~ThreadPool()
{
{
std::lock_guard<std::mutex> lock(mtx);
stop = true;
}
cv.notify_all();
for (auto& w : workers) {
w.join();
}
}

// Non-copyable, non-movable
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;

template<typename F>
auto submit(F&& f) -> std::future<decltype(f())>
{
using ReturnType = decltype(f());
auto task = std::make_shared<std::packaged_task<ReturnType()>>(std::forward<F>(f));
auto future = task->get_future();
{
std::lock_guard<std::mutex> lock(mtx);
tasks.emplace([task]() { (*task)(); });
}
cv.notify_one();
return future;
}

size_t size() const { return workers.size(); }

private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable cv;
bool stop = false;
};

} // namespace bsv

#endif /* __THREAD_POOL_HPP__ */
Loading
Loading