Skip to content
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

capi: export RpcDaemon configuration settings #1968

Merged
merged 9 commits into from
Apr 19, 2024
Merged
Changes from 1 commit
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
Next Next commit
capi: export RpcDaemon configuration settings
rpcdaemon: extract and reuse some default values
  • Loading branch information
canepat committed Apr 15, 2024
commit c62344273cff1feb584e3062a1ea6ba4f55086d7
3 changes: 2 additions & 1 deletion cmd/capi/execute.cpp
Original file line number Diff line number Diff line change
@@ -396,7 +396,8 @@ int start_rpcdaemon(SilkwormHandle handle, const rpc::DaemonSettings& /*settings
.exclusive = true};
::mdbx::env_managed env{silkworm::db::open_env(config)};

const int status_code{silkworm_start_rpcdaemon(handle, &*env)};
SilkwormRpcSettings settings{};
const int status_code{silkworm_start_rpcdaemon(handle, &*env, &settings)};
if (status_code != SILKWORM_OK) {
SILK_ERROR << "silkworm_start_rpcdaemon failed [code=" << std::to_string(status_code) << "]";
}
26 changes: 26 additions & 0 deletions silkworm/capi/common.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright 2024 The Silkworm Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include "common.hpp"

#include <cstring>

std::filesystem::path make_path(const char data_dir_path[SILKWORM_PATH_SIZE]) {
// Treat as char8_t so that filesystem::path assumes UTF-8 encoding of the input path
auto begin = reinterpret_cast<const char8_t*>(data_dir_path);
size_t len = strnlen(data_dir_path, SILKWORM_PATH_SIZE);
return std::filesystem::path{begin, begin + len};
}
24 changes: 24 additions & 0 deletions silkworm/capi/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright 2024 The Silkworm Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#pragma once

#include <filesystem>

#include "silkworm.h"

//! Build a file system path from its C null-terminated upper-bounded representation
std::filesystem::path make_path(const char path[SILKWORM_PATH_SIZE]);
2 changes: 2 additions & 0 deletions silkworm/capi/instance.hpp
Original file line number Diff line number Diff line change
@@ -24,9 +24,11 @@

#include <silkworm/db/snapshots/repository.hpp>
#include <silkworm/infra/concurrency/context_pool_settings.hpp>
#include <silkworm/infra/common/log.hpp>
#include <silkworm/rpc/daemon.hpp>

struct SilkwormInstance {
silkworm::log::Settings log_settings;
silkworm::concurrency::ContextPoolSettings context_pool_settings;
std::filesystem::path data_dir_path;
std::unique_ptr<silkworm::snapshots::SnapshotRepository> snapshot_repository;
145 changes: 145 additions & 0 deletions silkworm/capi/rpcdaemon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
Copyright 2024 The Silkworm Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

#include <silkworm/infra/common/log.hpp>
#include <silkworm/rpc/settings.hpp>

#include "common.hpp"
#include "instance.hpp"
#include "silkworm.h"

using namespace silkworm;
using namespace silkworm::rpc;
using silkworm::concurrency::ContextPoolSettings;

//! Build interface log settings for ETH JSON-RPC from their C representation
static InterfaceLogSettings make_eth_ifc_log_settings(const struct SilkwormRpcInterfaceLogSettings* settings) {
InterfaceLogSettings eth_ifc_log_settings{.ifc_name = "eth_rpc_api"};
if (settings) {
eth_ifc_log_settings.enabled = settings->enabled;
eth_ifc_log_settings.container_folder = make_path(settings->container_folder);
eth_ifc_log_settings.max_file_size_mb = settings->max_file_size_mb;
eth_ifc_log_settings.max_files = settings->max_files;
eth_ifc_log_settings.dump_response = settings->dump_response;
}
return eth_ifc_log_settings;
}

//! Build JSON-RPC endpoint from C settings
static std::string parse_end_point(const char (&c_host)[SILKWORM_RPC_SETTINGS_HOST_SIZE], int port, const std::string& default_end_point) {
auto host = std::string{c_host};
if (host.empty() && port == 0) {
return kDefaultEth1EndPoint;
}
const auto semicolon_position{default_end_point.find(':')};
SILKWORM_ASSERT(semicolon_position != std::string::npos);
if (host.empty()) {
host = default_end_point.substr(0, semicolon_position);
}
if (port == 0) {
port = std::stoi(default_end_point.substr(semicolon_position + 1));
}
std::string eth_end_point{host + ":" + std::to_string(port)};
return eth_end_point;
}

//! Build list of CORS domains from their C representation
static std::vector<std::string> parse_cors_domains(
const char (&c_cors_domains)[SILKWORM_RPC_SETTINGS_CORS_DOMAINS_MAX][SILKWORM_RPC_SETTINGS_CORS_DOMAIN_SIZE]) {
std::vector<std::string> cors_domains;
for (const auto& c_domain : c_cors_domains) {
std::string_view domain_str = c_domain;
if (domain_str.empty()) break;
cors_domains.emplace_back(domain_str);
}
return cors_domains;
}

//! Build whole RPC daemon settings from their C representation
static DaemonSettings make_daemon_settings(SilkwormHandle handle, const struct SilkwormRpcSettings& settings) {
const auto jwt_path{make_path(settings.jwt_file_path)};
return {
.log_settings = handle->log_settings,
.eth_ifc_log_settings = make_eth_ifc_log_settings(settings.eth_if_log_settings),
.context_pool_settings = handle->context_pool_settings,
.eth_end_point = parse_end_point(settings.eth_api_host, settings.eth_api_port, kDefaultEth1EndPoint),
.engine_end_point = "", // disable end-point for Engine RPC API
.eth_api_spec = std::string{settings.eth_api_spec},
.num_workers = settings.num_workers > 0 ? settings.num_workers : kDefaultNumWorkers,
.cors_domain = parse_cors_domains(settings.cors_domains),
.jwt_secret_file = jwt_path.empty() ? std::nullopt : std::make_optional(jwt_path.string()),
.erigon_json_rpc_compatibility = settings.erigon_json_rpc_compatibility,
.use_websocket = settings.ws_enabled,
.ws_compression = settings.ws_compression,
.http_compression = settings.http_compression,
};
}

SILKWORM_EXPORT int silkworm_start_rpcdaemon(SilkwormHandle handle, MDBX_env* env, const struct SilkwormRpcSettings* settings) SILKWORM_NOEXCEPT {
if (!handle) {
return SILKWORM_INVALID_HANDLE;
}
if (handle->rpcdaemon) {
return SILKWORM_SERVICE_ALREADY_STARTED;
}
if (!env) {
return SILKWORM_INVALID_MDBX_ENV;
}
if (!settings) {
return SILKWORM_INVALID_SETTINGS;
}

auto daemon_settings = make_daemon_settings(handle, *settings);
db::EnvUnmanaged unmanaged_env{env};

// Create the one-and-only Silkrpc daemon
handle->rpcdaemon = std::make_unique<rpc::Daemon>(daemon_settings, std::make_optional<mdbx::env>(unmanaged_env));

// Check protocol version compatibility with Core Services
if (!daemon_settings.skip_protocol_check) {
SILK_INFO << "[Silkworm RPC] Checking protocol version compatibility with core services...";

const auto checklist = handle->rpcdaemon->run_checklist();
for (const auto& protocol_check : checklist.protocol_checklist) {
SILK_INFO << protocol_check.result;
}
checklist.success_or_throw();
} else {
SILK_TRACE << "[Silkworm RPC] Skip protocol version compatibility check with core services";
}

SILK_INFO << "[Silkworm RPC] Starting ETH API at " << daemon_settings.eth_end_point;
handle->rpcdaemon->start();

return SILKWORM_OK;
}

SILKWORM_EXPORT int silkworm_stop_rpcdaemon(SilkwormHandle handle) SILKWORM_NOEXCEPT {
if (!handle) {
return SILKWORM_INVALID_HANDLE;
}
if (!handle->rpcdaemon) {
return SILKWORM_OK;
}

handle->rpcdaemon->stop();
SILK_INFO << "[Silkworm RPC] Exiting...";
handle->rpcdaemon->join();
SILK_INFO << "[Silkworm RPC] Stopped";
handle->rpcdaemon.reset();

return SILKWORM_OK;
}
128 changes: 52 additions & 76 deletions silkworm/capi/silkworm.cpp
Original file line number Diff line number Diff line change
@@ -44,24 +44,59 @@
#include <silkworm/infra/common/directories.hpp>
#include <silkworm/infra/common/log.hpp>
#include <silkworm/infra/common/stopwatch.hpp>
#include <silkworm/infra/concurrency/context_pool_settings.hpp>
#include <silkworm/infra/concurrency/signal_handler.hpp>
#include <silkworm/infra/concurrency/thread_pool.hpp>

#include "common.hpp"
#include "instance.hpp"

using namespace std::chrono_literals;
using namespace silkworm;

//! Create Silkworm log level from its C representation
static log::Level make_log_level(const SilkwormLogLevel c_log_level) {
log::Level verbosity{};
switch (c_log_level) {
case SilkwormLogLevel::NONE:
verbosity = log::Level::kNone;
break;
case SilkwormLogLevel::CRITICAL:
verbosity = log::Level::kCritical;
break;
case SilkwormLogLevel::ERROR:
verbosity = log::Level::kError;
break;
case SilkwormLogLevel::WARNING:
verbosity = log::Level::kWarning;
break;
case SilkwormLogLevel::INFO:
verbosity = log::Level::kInfo;
break;
case SilkwormLogLevel::DEBUG:
verbosity = log::Level::kDebug;
break;
case SilkwormLogLevel::TRACE:
verbosity = log::Level::kTrace;
break;
}
return verbosity;
}

//! Build log configuration matching Erigon log format w/ custom verbosity level
static log::Settings make_log_settings(const SilkwormLogLevel c_log_level) {
return {
.log_utc = false, // display local time
.log_timezone = false, // no timezone ID
.log_trim = true, // compact rendering (i.e. no whitespaces)
.log_verbosity = make_log_level(c_log_level),
};
}

static MemoryMappedRegion make_region(const SilkwormMemoryMappedFile& mmf) {
return {mmf.memory_address, mmf.memory_length};
}

//! Log configuration matching Erigon log format
static log::Settings kLogSettingsLikeErigon{
.log_utc = false, // display local time
.log_timezone = false, // no timezone ID
.log_trim = true, // compact rendering (i.e. no whitespaces)
};
static constexpr size_t kMaxBlockBufferSize{100};
static constexpr size_t kAnalysisCacheSize{5'000};
static constexpr size_t kMaxPrefetchedBlocks{10'240};
@@ -140,13 +175,6 @@ static log::Args log_args_for_exec_commit(StopWatch::Duration elapsed, const std
std::to_string(Directory{db_path}.size())};
}

static std::filesystem::path make_path(const char data_dir_path[SILKWORM_PATH_SIZE]) {
// treat as char8_t so that filesystem::path assumes UTF-8 encoding of the input path
auto begin = reinterpret_cast<const char8_t*>(data_dir_path);
size_t len = strnlen(data_dir_path, SILKWORM_PATH_SIZE);
return std::filesystem::path{begin, begin + len};
}

//! Generate log arguments for execution progress at specified block
static log::Args log_args_for_exec_progress(ExecutionProgress& progress, uint64_t current_block) {
static auto float_to_string = [](float f) -> std::string {
@@ -212,20 +240,24 @@ SILKWORM_EXPORT int silkworm_init(SilkwormHandle* handle, const struct SilkwormS

is_initialized = true;

log::init(kLogSettingsLikeErigon);
log::Settings log_settings{make_log_settings(settings->log_verbosity)};
log::init(log_settings);
log::Info{"Silkworm build info", log_args_for_version()}; // NOLINT(*-unused-raii)

auto snapshot_repository = std::make_unique<snapshots::SnapshotRepository>();
db::DataModel::set_snapshot_repository(snapshot_repository.get());

// NOLINTNEXTLINE(bugprone-unhandled-exception-at-new)
*handle = new SilkwormInstance{
{}, // context_pool_settings
make_path(settings->data_dir_path),
std::move(snapshot_repository),
{}, // rpcdaemon unique_ptr
{}, // sentry_thread unique_ptr
{}, // sentry_stop_signal
.log_settings = std::move(log_settings),
.context_pool_settings = {
.num_contexts = settings->num_contexts > 0 ? settings->num_contexts : concurrency::kDefaultNumContexts,
},
.data_dir_path = make_path(settings->data_dir_path),
.snapshot_repository = std::move(snapshot_repository),
.rpcdaemon = {},
.sentry_thread = {},
.sentry_stop_signal = {},
};
return SILKWORM_OK;
}
@@ -390,62 +422,6 @@ SILKWORM_EXPORT const char* silkworm_libmdbx_version() SILKWORM_NOEXCEPT {
return ::mdbx::get_version().git.describe;
}

SILKWORM_EXPORT int silkworm_start_rpcdaemon(SilkwormHandle handle, MDBX_env* env) SILKWORM_NOEXCEPT {
if (!handle) {
return SILKWORM_INVALID_HANDLE;
}
if (handle->rpcdaemon) {
return SILKWORM_SERVICE_ALREADY_STARTED;
}

db::EnvUnmanaged unmanaged_env{env};

// TODO(canepat) add RPC options in API and convert them
rpc::DaemonSettings settings{
.engine_end_point = "", // disable end-point for Engine RPC API
.skip_protocol_check = true,
.erigon_json_rpc_compatibility = true,
};

// Create the one-and-only Silkrpc daemon
handle->rpcdaemon = std::make_unique<rpc::Daemon>(settings, std::make_optional<mdbx::env>(unmanaged_env));

// Check protocol version compatibility with Core Services
if (!settings.skip_protocol_check) {
SILK_INFO << "[Silkworm RPC] Checking protocol version compatibility with core services...";

const auto checklist = handle->rpcdaemon->run_checklist();
for (const auto& protocol_check : checklist.protocol_checklist) {
SILK_INFO << protocol_check.result;
}
checklist.success_or_throw();
} else {
SILK_TRACE << "[Silkworm RPC] Skip protocol version compatibility check with core services";
}

SILK_INFO << "[Silkworm RPC] Starting ETH API at " << settings.eth_end_point;
handle->rpcdaemon->start();

return SILKWORM_OK;
}

SILKWORM_EXPORT int silkworm_stop_rpcdaemon(SilkwormHandle handle) SILKWORM_NOEXCEPT {
if (!handle) {
return SILKWORM_INVALID_HANDLE;
}
if (!handle->rpcdaemon) {
return SILKWORM_OK;
}

handle->rpcdaemon->stop();
SILK_INFO << "[Silkworm RPC] Exiting...";
handle->rpcdaemon->join();
SILK_INFO << "[Silkworm RPC] Stopped";
handle->rpcdaemon.reset();

return SILKWORM_OK;
}

class BlockProvider {
static constexpr size_t kTxnRefreshThreshold{100};

Loading