mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-16 13:09:33 +00:00
Merge branch 'marvin/keycard-commands' into marvin/keycard-privacy-commands
This commit is contained in:
commit
a43314a213
BIN
artifacts/test_program_methods/group_pda_spender.bin
Normal file
BIN
artifacts/test_program_methods/group_pda_spender.bin
Normal file
Binary file not shown.
BIN
artifacts/test_program_methods/private_pda_claimer.bin
Normal file
BIN
artifacts/test_program_methods/private_pda_claimer.bin
Normal file
Binary file not shown.
@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "bedrock_client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
common.workspace = true
|
||||
|
||||
reqwest.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio-retry.workspace = true
|
||||
futures.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
humantime-serde.workspace = true
|
||||
logos-blockchain-common-http-client.workspace = true
|
||||
logos-blockchain-core.workspace = true
|
||||
logos-blockchain-chain-broadcast-service.workspace = true
|
||||
logos-blockchain-chain-service.workspace = true
|
||||
@ -1,121 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use common::config::BasicAuth;
|
||||
use futures::{Stream, TryFutureExt as _};
|
||||
#[expect(clippy::single_component_path_imports, reason = "Satisfy machete")]
|
||||
use humantime_serde;
|
||||
use log::{info, warn};
|
||||
pub use logos_blockchain_chain_broadcast_service::BlockInfo;
|
||||
use logos_blockchain_chain_service::CryptarchiaInfo;
|
||||
pub use logos_blockchain_common_http_client::{CommonHttpClient, Error};
|
||||
pub use logos_blockchain_core::{block::Block, header::HeaderId, mantle::SignedMantleTx};
|
||||
use reqwest::{Client, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_retry::Retry;
|
||||
|
||||
/// Fibonacci backoff retry strategy configuration.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct BackoffConfig {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub start_delay: Duration,
|
||||
pub max_retries: usize,
|
||||
}
|
||||
|
||||
impl Default for BackoffConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
start_delay: Duration::from_millis(100),
|
||||
max_retries: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple wrapper
|
||||
/// maybe extend in the future for our purposes
|
||||
/// `Clone` is cheap because `CommonHttpClient` is internally reference counted (`Arc`).
|
||||
#[derive(Clone)]
|
||||
pub struct BedrockClient {
|
||||
http_client: CommonHttpClient,
|
||||
node_url: Url,
|
||||
backoff: BackoffConfig,
|
||||
}
|
||||
|
||||
impl BedrockClient {
|
||||
pub fn new(backoff: BackoffConfig, node_url: Url, auth: Option<BasicAuth>) -> Result<Self> {
|
||||
info!("Creating Bedrock client with node URL {node_url}");
|
||||
let client = Client::builder()
|
||||
//Add more fields if needed
|
||||
.timeout(std::time::Duration::from_mins(1))
|
||||
.build()
|
||||
.context("Failed to build HTTP client")?;
|
||||
|
||||
let auth = auth.map(|a| {
|
||||
logos_blockchain_common_http_client::BasicAuthCredentials::new(a.username, a.password)
|
||||
});
|
||||
|
||||
let http_client = CommonHttpClient::new_with_client(client, auth);
|
||||
Ok(Self {
|
||||
http_client,
|
||||
node_url,
|
||||
backoff,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result<Result<(), Error>, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || async {
|
||||
match self
|
||||
.http_client
|
||||
.post_transaction(self.node_url.clone(), tx.clone())
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(Ok(())),
|
||||
Err(err) => match err {
|
||||
// Retry arm.
|
||||
// Retrying only reqwest errors: mainly connected to http.
|
||||
Error::Request(_) => Err(err),
|
||||
// Returning non-retryable error
|
||||
Error::Server(_) | Error::Client(_) | Error::Url(_) => Ok(Err(err)),
|
||||
},
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_lib_stream(&self) -> Result<impl Stream<Item = BlockInfo>, Error> {
|
||||
self.http_client.get_lib_stream(self.node_url.clone()).await
|
||||
}
|
||||
|
||||
pub async fn get_block_by_id(
|
||||
&self,
|
||||
header_id: HeaderId,
|
||||
) -> Result<Option<Block<SignedMantleTx>>, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || {
|
||||
self.http_client
|
||||
.get_block_by_id(self.node_url.clone(), header_id)
|
||||
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_consensus_info(&self) -> Result<CryptarchiaInfo, Error> {
|
||||
Retry::spawn(self.backoff_strategy(), || {
|
||||
self.http_client
|
||||
.consensus_info(self.node_url.clone())
|
||||
.inspect_err(|err| warn!("Block fetching failed with error: {err:#}"))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn backoff_strategy(&self) -> impl Iterator<Item = Duration> {
|
||||
let start_delay_millis = self
|
||||
.backoff
|
||||
.start_delay
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Start delay must be less than u64::MAX milliseconds");
|
||||
|
||||
tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis)
|
||||
.take(self.backoff.max_retries)
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
[package]
|
||||
edition = "2024"
|
||||
license = { workspace = true }
|
||||
name = "indexer_ffi"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
indexer_service.workspace = true
|
||||
log = { workspace = true }
|
||||
tokio = { features = ["rt-multi-thread"], workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.29"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib", "cdylib", "staticlib"]
|
||||
name = "indexer_ffi"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
"cbindgen",
|
||||
] # machete does not recognize this for build dep and complains.
|
||||
@ -1,12 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
println!("cargo:rerun-if-changed=src/");
|
||||
cbindgen::Builder::new()
|
||||
.with_crate(crate_dir)
|
||||
.with_language(cbindgen::Language::C)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file("indexer_ffi.h");
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
language = "C" # For increased compatibility
|
||||
no_includes = true
|
||||
@ -1,76 +0,0 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef enum OperationStatus {
|
||||
Ok = 0,
|
||||
NullPointer = 1,
|
||||
InitializationError = 2,
|
||||
} OperationStatus;
|
||||
|
||||
typedef struct IndexerServiceFFI {
|
||||
void *indexer_handle;
|
||||
void *runtime;
|
||||
} IndexerServiceFFI;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a pointer to a value or an error.
|
||||
*
|
||||
* Pointer is not guaranteed. You should check the error field before
|
||||
* dereferencing the pointer.
|
||||
*/
|
||||
typedef struct PointerResult_IndexerServiceFFI__OperationStatus {
|
||||
struct IndexerServiceFFI *value;
|
||||
enum OperationStatus error;
|
||||
} PointerResult_IndexerServiceFFI__OperationStatus;
|
||||
|
||||
typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult;
|
||||
|
||||
/**
|
||||
* Creates and starts an indexer based on the provided
|
||||
* configuration file path.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
* - `port`: Number representing a port, on which indexers RPC will start.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* An `InitializedIndexerServiceFFIResult` containing either a pointer to the
|
||||
* initialized `IndexerServiceFFI` or an error code.
|
||||
*/
|
||||
InitializedIndexerServiceFFIResult start_indexer(const char *config_path, uint16_t port);
|
||||
|
||||
/**
|
||||
* Stops and frees the resources associated with the given indexer service.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped.
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* An `OperationStatus` indicating success or failure.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* The caller must ensure that:
|
||||
* - `indexer` is a valid pointer to a `IndexerServiceFFI` instance
|
||||
* - The `IndexerServiceFFI` instance was created by this library
|
||||
* - The pointer will not be used after this function returns
|
||||
*/
|
||||
enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer);
|
||||
|
||||
/**
|
||||
* # Safety
|
||||
* It's up to the caller to pass a proper pointer, if somehow from c/c++ side
|
||||
* this is called with a type which doesn't come from a returned `CString` it
|
||||
* will cause a segfault.
|
||||
*/
|
||||
void free_cstring(char *block);
|
||||
|
||||
bool is_ok(const enum OperationStatus *self);
|
||||
|
||||
bool is_error(const enum OperationStatus *self);
|
||||
@ -1,100 +0,0 @@
|
||||
use std::{ffi::c_char, path::PathBuf};
|
||||
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus};
|
||||
|
||||
pub type InitializedIndexerServiceFFIResult = PointerResult<IndexerServiceFFI, OperationStatus>;
|
||||
|
||||
/// Creates and starts an indexer based on the provided
|
||||
/// configuration file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
/// - `port`: Number representing a port, on which indexers RPC will start.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `InitializedIndexerServiceFFIResult` containing either a pointer to the
|
||||
/// initialized `IndexerServiceFFI` or an error code.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn start_indexer(
|
||||
config_path: *const c_char,
|
||||
port: u16,
|
||||
) -> InitializedIndexerServiceFFIResult {
|
||||
setup_indexer(config_path, port).map_or_else(
|
||||
InitializedIndexerServiceFFIResult::from_error,
|
||||
InitializedIndexerServiceFFIResult::from_value,
|
||||
)
|
||||
}
|
||||
|
||||
/// Initializes and starts an indexer based on the provided
|
||||
/// configuration file path.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `config_path`: A pointer to a string representing the path to the configuration file.
|
||||
/// - `port`: Number representing a port, on which indexers RPC will start.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Result` containing either the initialized `IndexerServiceFFI` or an
|
||||
/// error code.
|
||||
fn setup_indexer(
|
||||
config_path: *const c_char,
|
||||
port: u16,
|
||||
) -> Result<IndexerServiceFFI, OperationStatus> {
|
||||
let user_config_path = PathBuf::from(
|
||||
unsafe { std::ffi::CStr::from_ptr(config_path) }
|
||||
.to_str()
|
||||
.map_err(|e| {
|
||||
log::error!("Could not convert the config path to string: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?,
|
||||
);
|
||||
let config = indexer_service::IndexerConfig::from_path(&user_config_path).map_err(|e| {
|
||||
log::error!("Failed to read config: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?;
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
|
||||
let indexer_handle = rt
|
||||
.block_on(indexer_service::run_server(config, port))
|
||||
.map_err(|e| {
|
||||
log::error!("Could not start indexer service: {e}");
|
||||
OperationStatus::InitializationError
|
||||
})?;
|
||||
|
||||
Ok(IndexerServiceFFI::new(indexer_handle, rt))
|
||||
}
|
||||
|
||||
/// Stops and frees the resources associated with the given indexer service.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `OperationStatus` indicating success or failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance
|
||||
/// - The `IndexerServiceFFI` instance was created by this library
|
||||
/// - The pointer will not be used after this function returns
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn stop_indexer(indexer: *mut IndexerServiceFFI) -> OperationStatus {
|
||||
if indexer.is_null() {
|
||||
log::error!("Attempted to stop a null indexer pointer. This is a bug. Aborting.");
|
||||
return OperationStatus::NullPointer;
|
||||
}
|
||||
|
||||
let indexer = unsafe { Box::from_raw(indexer) };
|
||||
drop(indexer);
|
||||
|
||||
OperationStatus::Ok
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
use std::ffi::{CString, c_char};
|
||||
|
||||
/// # Safety
|
||||
/// It's up to the caller to pass a proper pointer, if somehow from c/c++ side
|
||||
/// this is called with a type which doesn't come from a returned `CString` it
|
||||
/// will cause a segfault.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn free_cstring(block: *mut c_char) {
|
||||
if block.is_null() {
|
||||
log::error!("Trying to free a null pointer. Exiting");
|
||||
return;
|
||||
}
|
||||
drop(unsafe { CString::from_raw(block) });
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
pub use result::PointerResult;
|
||||
|
||||
pub mod lifecycle;
|
||||
pub mod memory;
|
||||
pub mod result;
|
||||
@ -1,29 +0,0 @@
|
||||
/// Simple wrapper around a pointer to a value or an error.
|
||||
///
|
||||
/// Pointer is not guaranteed. You should check the error field before
|
||||
/// dereferencing the pointer.
|
||||
#[repr(C)]
|
||||
pub struct PointerResult<Type, Error> {
|
||||
pub value: *mut Type,
|
||||
pub error: Error,
|
||||
}
|
||||
|
||||
impl<Type, Error: Default> PointerResult<Type, Error> {
|
||||
pub fn from_pointer(pointer: *mut Type) -> Self {
|
||||
Self {
|
||||
value: pointer,
|
||||
error: Error::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: Type) -> Self {
|
||||
Self::from_pointer(Box::into_raw(Box::new(value)))
|
||||
}
|
||||
|
||||
pub const fn from_error(error: Error) -> Self {
|
||||
Self {
|
||||
value: std::ptr::null_mut(),
|
||||
error,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub enum OperationStatus {
|
||||
#[default]
|
||||
Ok = 0x0,
|
||||
NullPointer = 0x1,
|
||||
InitializationError = 0x2,
|
||||
}
|
||||
|
||||
impl OperationStatus {
|
||||
#[must_use]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn is_ok(&self) -> bool {
|
||||
*self == Self::Ok
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn is_error(&self) -> bool {
|
||||
!self.is_ok()
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
use std::{ffi::c_void, net::SocketAddr};
|
||||
|
||||
use indexer_service::IndexerHandle;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IndexerServiceFFI {
|
||||
indexer_handle: *mut c_void,
|
||||
runtime: *mut c_void,
|
||||
}
|
||||
|
||||
impl IndexerServiceFFI {
|
||||
pub fn new(indexer_handle: indexer_service::IndexerHandle, runtime: Runtime) -> Self {
|
||||
Self {
|
||||
// Box the complex types and convert to opaque pointers
|
||||
indexer_handle: Box::into_raw(Box::new(indexer_handle)).cast::<c_void>(),
|
||||
runtime: Box::into_raw(Box::new(runtime)).cast::<c_void>(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to take ownership back.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `self` is a valid object(contains valid pointers in all fields)
|
||||
#[must_use]
|
||||
pub unsafe fn into_parts(self) -> (Box<IndexerHandle>, Box<Runtime>) {
|
||||
let indexer_handle = unsafe { Box::from_raw(self.indexer_handle.cast::<IndexerHandle>()) };
|
||||
let runtime = unsafe { Box::from_raw(self.runtime.cast::<Runtime>()) };
|
||||
(indexer_handle, runtime)
|
||||
}
|
||||
|
||||
/// Helper to get indexer handle addr.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `self` is a valid object(contains valid pointers in all fields)
|
||||
#[must_use]
|
||||
pub const unsafe fn addr(&self) -> SocketAddr {
|
||||
let indexer_handle = unsafe {
|
||||
self.indexer_handle
|
||||
.cast::<IndexerHandle>()
|
||||
.as_ref()
|
||||
.expect("Indexer Handle must be non-null pointer")
|
||||
};
|
||||
|
||||
indexer_handle.addr()
|
||||
}
|
||||
|
||||
/// Helper to get indexer handle addr.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that:
|
||||
/// - `self` is a valid object(contains valid pointers in all fields)
|
||||
#[must_use]
|
||||
pub const unsafe fn handle(&self) -> &IndexerHandle {
|
||||
unsafe {
|
||||
self.indexer_handle
|
||||
.cast::<IndexerHandle>()
|
||||
.as_ref()
|
||||
.expect("Indexer Handle must be non-null pointer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Drop to prevent memory leaks
|
||||
impl Drop for IndexerServiceFFI {
|
||||
fn drop(&mut self) {
|
||||
let Self {
|
||||
indexer_handle,
|
||||
runtime,
|
||||
} = self;
|
||||
|
||||
if indexer_handle.is_null() {
|
||||
log::error!("Attempted to drop a null indexer pointer. This is a bug");
|
||||
}
|
||||
if runtime.is_null() {
|
||||
log::error!("Attempted to drop a null tokio runtime pointer. This is a bug");
|
||||
}
|
||||
drop(unsafe { Box::from_raw(indexer_handle.cast::<IndexerHandle>()) });
|
||||
drop(unsafe { Box::from_raw(runtime.cast::<Runtime>()) });
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#![allow(clippy::undocumented_unsafe_blocks, reason = "It is an FFI")]
|
||||
|
||||
pub use errors::OperationStatus;
|
||||
pub use indexer::IndexerServiceFFI;
|
||||
|
||||
pub mod api;
|
||||
mod errors;
|
||||
mod indexer;
|
||||
Loading…
x
Reference in New Issue
Block a user