diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -230,9 +230,9 @@ [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -359,6 +359,7 @@ "chronik-db", "chronik-http", "chronik-indexer", + "chronik-plugin", "chronik-util", "cxx", "cxx-build", @@ -366,6 +367,16 @@ "tokio", ] +[[package]] +name = "chronik-plugin" +version = "0.1.0" +dependencies = [ + "abc-rust-error", + "abc-rust-lint", + "chronik-util", + "pyo3", +] + [[package]] name = "chronik-proto" version = "0.1.0" @@ -954,6 +965,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -1027,6 +1047,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "smallvec", + "windows-targets", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1197,6 +1240,40 @@ "prost", ] +[[package]] +name = "pyo3" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a89dc7a5850d0e983be1ec2a463a171d20990487c3cfcd68b5363f1ee3d6fe0" +dependencies = [ + "cfg-if", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07426f0d8fe5a601f26293f300afd1a7b1ed5e78b2a705870c5f30893c5163be" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb7dec17e17766b46bca4f1a4215a85006b4c2ecde122076c562dd058da6cf1" +dependencies = [ + "libc", + "pyo3-build-config", +] + [[package]] name = "quote" version = "1.0.33" @@ -1282,6 +1359,15 @@ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.9.3" @@ -1499,6 +1585,12 @@ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "socket2" version = "0.4.9" @@ -1573,6 +1665,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "target-lexicon" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae" + [[package]] name = "tempdir" version = "0.3.7" @@ -1591,7 +1689,7 @@ dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] diff --git a/Cargo.toml b/Cargo.toml --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ "chronik/chronik-http", "chronik/chronik-indexer", "chronik/chronik-lib", + "chronik/chronik-plugin", "chronik/chronik-proto", "chronik/chronik-util", ] diff --git a/chronik/CMakeLists.txt b/chronik/CMakeLists.txt --- a/chronik/CMakeLists.txt +++ b/chronik/CMakeLists.txt @@ -3,6 +3,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(BUILD_BITCOIN_CHRONIK_PLUGINS "Activate the plugin system for Chronik" OFF) + find_package(Corrosion 0.3.0 REQUIRED) set(REQUIRED_RUST_VERSION "1.72.0") @@ -29,6 +31,10 @@ set(CHRONIK_CARGO_FLAGS --locked) +if(BUILD_BITCOIN_CHRONIK_PLUGINS) + set(CHRONIK_FEATURE_FLAGS --features plugins) +endif() + function(add_cargo_custom_target TARGET) add_custom_target(${TARGET} COMMAND @@ -80,7 +86,9 @@ # Compile Rust, generates chronik-lib-static corrosion_import_crate( MANIFEST_PATH "chronik-lib/Cargo.toml" - FLAGS ${CHRONIK_CARGO_FLAGS} + FLAGS + ${CHRONIK_CARGO_FLAGS} + ${CHRONIK_FEATURE_FLAGS} ) # cxx crate generates some source files at this location @@ -131,6 +139,13 @@ chronik-lib-static ) +# Plugins require us to link agains libpython +if(BUILD_BITCOIN_CHRONIK_PLUGINS) + find_package(Python COMPONENTS Interpreter Development) + message("Adding Python_LIBRARIES: ${Python_LIBRARIES}") + target_link_libraries(chronik ${Python_LIBRARIES}) +endif() + if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") # mio crate (dependency of tokio) requires winternl.h, found in ntdll find_package(NTDLL REQUIRED) diff --git a/chronik/chronik-lib/Cargo.toml b/chronik/chronik-lib/Cargo.toml --- a/chronik/chronik-lib/Cargo.toml +++ b/chronik/chronik-lib/Cargo.toml @@ -11,6 +11,9 @@ # Compile to C-ABI crate-type = ["staticlib"] +[features] +plugins = ["chronik-plugin"] + [dependencies] abc-rust-lint = { path = "../abc-rust-lint" } abc-rust-error = { path = "../abc-rust-error" } @@ -32,6 +35,10 @@ # Async runtime tokio = { version = "1.32", features = ["sync", "rt", "rt-multi-thread"] } +[dependencies.chronik-plugin] +path = "../chronik-plugin" +optional = true + [build-dependencies] # Build scripts for the cxx crate cxx-build = "1.0" diff --git a/chronik/chronik-lib/src/bridge.rs b/chronik/chronik-lib/src/bridge.rs --- a/chronik/chronik-lib/src/bridge.rs +++ b/chronik/chronik-lib/src/bridge.rs @@ -21,6 +21,8 @@ indexer::{ChronikIndexer, ChronikIndexerParams, Node}, pause::Pause, }; +#[cfg(feature = "plugins")] +use chronik_plugin::context::PluginContext; use chronik_util::{log, log_chronik, mount_loggers, Loggers}; use thiserror::Error; use tokio::sync::RwLock; @@ -70,6 +72,8 @@ .into_iter() .map(|host| parse_socket_addr(host, params.default_port)) .collect::>>()?; + #[cfg(feature = "plugins")] + PluginContext::setup()?; log!("Starting Chronik bound to {:?}\n", hosts); let bridge = chronik_bridge::ffi::make_bridge(config, node_context); let bridge_ref = expect_unique_ptr("make_bridge", &bridge); diff --git a/chronik/chronik-plugin/Cargo.toml b/chronik/chronik-plugin/Cargo.toml new file mode 100644 --- /dev/null +++ b/chronik/chronik-plugin/Cargo.toml @@ -0,0 +1,15 @@ +# Copyright (c) 2024 The Bitcoin developers + +[package] +name = "chronik-plugin" +version = "0.1.0" +edition = "2021" +rust-version.workspace = true + +[dependencies] +abc-rust-lint = { path = "../abc-rust-lint" } +abc-rust-error = { path = "../abc-rust-error" } + +chronik-util = { path = "../chronik-util" } + +pyo3 = { version = "0.20", default-features = false } diff --git a/chronik/chronik-plugin/src/context.rs b/chronik/chronik-plugin/src/context.rs new file mode 100644 --- /dev/null +++ b/chronik/chronik-plugin/src/context.rs @@ -0,0 +1,36 @@ +// Copyright (c) 2024 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Module for [`PluginContext`]. + +use abc_rust_error::Result; +use chronik_util::log; +use pyo3::{types::PyModule, Python}; + +/// Struct managing all things relating to Chronik plugins. +#[derive(Debug, Default)] +pub struct PluginContext; + +impl PluginContext { + /// Setup a plugin context, i.e. setting up an embedded Python instance and + /// loading plugins. + pub fn setup() -> Result { + pyo3::prepare_freethreaded_python(); + Python::with_gil(|py| -> Result<_> { + let module = PyModule::from_code( + py, + " +import platform +version = platform.python_version() + ", + "get_version.py", + "get_version", + )?; + let version = module.getattr("version")?; + let version = version.extract::()?; + log!("Plugin context initialized Python {}\n", version); + Ok(PluginContext) + }) + } +} diff --git a/chronik/chronik-plugin/src/lib.rs b/chronik/chronik-plugin/src/lib.rs new file mode 100644 --- /dev/null +++ b/chronik/chronik-plugin/src/lib.rs @@ -0,0 +1,12 @@ +// Copyright (c) 2024 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Plugin interface using Python for Chronik. +//! +//! Allows users to specify scripts in a `plugins` folder for indexing novel +//! protocols. + +abc_rust_lint::lint! { + pub mod context; +} diff --git a/contrib/teamcity/build-configurations.yml b/contrib/teamcity/build-configurations.yml --- a/contrib/teamcity/build-configurations.yml +++ b/contrib/teamcity/build-configurations.yml @@ -214,6 +214,19 @@ - - check - check-functional + build-chronik-plugins: + runOnDiffRegex: + - chronik/ + cmake_flags: + - '-DBUILD_BITCOIN_CHRONIK=ON' + - '-DBUILD_BITCOIN_CHRONIK_PLUGINS=ON' + targets: + - - check-crates + - - all + - install + - - check + - check-functional + build-clang: Werror: true clang: true