-
Notifications
You must be signed in to change notification settings - Fork 27
Feat: add support for geyser plugins #707
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
base: master
Are you sure you want to change the base?
Conversation
chore: upgrade rust-toolchain to 1.91.1
Manual Deploy AvailableYou can trigger a manual deploy of this PR branch to testnet: Alternative: Comment
Comment updated automatically when the PR is synchronized. |
📝 WalkthroughWalkthroughThis pull request introduces Geyser plugin integration into the aperture module and restructures configuration management. New dependencies support dynamic plugin loading through libloading. Configuration is refactored to move listen settings and add event processor and geyser plugin configuration under a new aperture section. TransactionStatus fields are modified to directly include transaction, metadata, and index rather than separate signature and execution result objects. A new GeyserPluginManager dynamically loads and manages plugin instances. Initialization flow changes from JsonRpcServer::new to a new initialize_aperture function. Related error handling, method signatures, and data access patterns are updated throughout to align with the new structures. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
magicblock-committor-service/src/compute_budget.rs (1)
202-207: Consider applying the same refactor for consistency.The
ComputeBudget::total_budgetmethod could benefit from the samesaturating_addrefactor for consistency with the change on line 63.Apply this diff:
fn total_budget(&self, committee_count: u32) -> u32 { - self.per_committee() - .checked_mul(committee_count) - .and_then(|product| product.checked_add(self.base_budget())) - .unwrap_or(u32::MAX) + self.per_committee() + .saturating_mul(committee_count) + .saturating_add(self.base_budget()) }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (28)
Cargo.toml(2 hunks)config.example.toml(1 hunks)magicblock-accounts-db/src/index.rs(1 hunks)magicblock-accounts-db/src/lib.rs(1 hunks)magicblock-aperture/Cargo.toml(3 hunks)magicblock-aperture/src/error.rs(2 hunks)magicblock-aperture/src/geyser.rs(1 hunks)magicblock-aperture/src/lib.rs(4 hunks)magicblock-aperture/src/processor.rs(6 hunks)magicblock-aperture/src/tests.rs(2 hunks)magicblock-aperture/tests/setup.rs(3 hunks)magicblock-api/src/errors.rs(1 hunks)magicblock-api/src/magic_validator.rs(2 hunks)magicblock-committor-program/src/state/chunks.rs(1 hunks)magicblock-committor-service/src/compute_budget.rs(1 hunks)magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs(1 hunks)magicblock-committor-service/src/intent_executor/task_info_fetcher.rs(2 hunks)magicblock-config/src/config/aperture.rs(1 hunks)magicblock-config/src/config/cli.rs(2 hunks)magicblock-config/src/config/mod.rs(2 hunks)magicblock-config/src/lib.rs(3 hunks)magicblock-config/src/tests.rs(5 hunks)magicblock-config/src/validator.rs(0 hunks)magicblock-ledger/src/database/db.rs(1 hunks)magicblock-ledger/src/database/rocks_db.rs(5 hunks)magicblock-ledger/src/store/api.rs(2 hunks)magicblock-validator/src/main.rs(1 hunks)tools/ledger-stats/src/accounts.rs(1 hunks)
💤 Files with no reviewable changes (1)
- magicblock-config/src/validator.rs
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-10-21T14:00:54.642Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-aperture/src/requests/websocket/account_subscribe.rs:18-27
Timestamp: 2025-10-21T14:00:54.642Z
Learning: In magicblock-aperture account_subscribe handler (src/requests/websocket/account_subscribe.rs), the RpcAccountInfoConfig fields data_slice, commitment, and min_context_slot are currently ignored—only encoding is applied. This is tracked as technical debt in issue #579: https://github.com/magicblock-labs/magicblock-validator/issues/579
Applied to files:
magicblock-validator/src/main.rsmagicblock-accounts-db/src/lib.rsmagicblock-config/src/lib.rsmagicblock-api/src/magic_validator.rsmagicblock-aperture/tests/setup.rs
📚 Learning: 2025-11-18T08:47:39.702Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs:158-165
Timestamp: 2025-11-18T08:47:39.702Z
Learning: In magicblock-chainlink tests involving compressed accounts, `set_remote_slot()` sets the slot of the `AccountSharedData`, while `compressed_account_shared_with_owner_and_slot()` sets the slot of the delegation record. These are two different fields and both calls are necessary.
Applied to files:
magicblock-accounts-db/src/lib.rs
📚 Learning: 2025-11-07T13:20:13.793Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/coordinator.rs:227-238
Timestamp: 2025-11-07T13:20:13.793Z
Learning: In magicblock-processor's ExecutionCoordinator (scheduler/coordinator.rs), the `account_contention` HashMap intentionally does not call `shrink_to_fit()`. Maintaining slack capacity is beneficial for performance by avoiding frequent reallocations during high transaction throughput. As long as empty entries are removed from the map (which `clear_account_contention` does), the capacity overhead is acceptable.
Applied to files:
magicblock-accounts-db/src/lib.rs
📚 Learning: 2025-11-21T10:22:07.520Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 661
File: magicblock-committor-service/src/intent_executor/single_stage_executor.rs:20-28
Timestamp: 2025-11-21T10:22:07.520Z
Learning: In magicblock-committor-service's SingleStageExecutor and TwoStageExecutor (single_stage_executor.rs and two_stage_executor.rs), the fields transaction_strategy, junk, and patched_errors are intentionally public because these executors are designed to be used independently outside of the IntentExecutor scope, and callers need access to these execution reports for cleanup and error handling.
Applied to files:
magicblock-committor-service/src/intent_executor/task_info_fetcher.rsmagicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs
📚 Learning: 2025-11-24T14:21:00.996Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: Cargo.toml:58-58
Timestamp: 2025-11-24T14:21:00.996Z
Learning: In the magicblock-validator codebase, magicblock-api/Cargo.toml intentionally uses borsh = "1.5.3" (instead of the workspace version 0.10.4) because it needs to deserialize types from the magic-domain-program external dependency, which requires borsh 1.5.x compatibility. This is an intentional exception for interoperability with the magic domain program.
Applied to files:
Cargo.tomlmagicblock-aperture/Cargo.toml
📚 Learning: 2025-11-19T09:34:37.917Z
Learnt from: thlorenz
Repo: magicblock-labs/magicblock-validator PR: 621
File: test-integration/test-chainlink/tests/ix_remote_account_provider.rs:62-63
Timestamp: 2025-11-19T09:34:37.917Z
Learning: In test-integration/test-chainlink/tests/ix_remote_account_provider.rs and similar test files, the `_fwd_rx` receiver returned by `init_remote_account_provider()` is intentionally kept alive (but unused) to prevent "receiver dropped" errors on the sender side. The pattern `let (remote_account_provider, _fwd_rx) = init_remote_account_provider().await;` should NOT be changed to `let (remote_account_provider, _) = ...` because dropping the receiver would cause send() operations to fail.
Applied to files:
magicblock-aperture/tests/setup.rs
📚 Learning: 2025-11-04T10:48:00.070Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/mod.rs:217-219
Timestamp: 2025-11-04T10:48:00.070Z
Learning: In magicblock-validator, the codebase uses a pattern where types containing non-Send/non-Sync fields (like Rc<RefCell<...>>) are marked with unsafe impl Send when they are guaranteed to be confined to a single thread through careful API design and thread spawning patterns.
Applied to files:
magicblock-aperture/src/processor.rs
📚 Learning: 2025-11-07T13:09:52.253Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: test-kit/src/lib.rs:275-0
Timestamp: 2025-11-07T13:09:52.253Z
Learning: In test-kit, the transaction scheduler in ExecutionTestEnv is not expected to shut down during tests. Therefore, using `.unwrap()` in test helper methods like `schedule_transaction` is acceptable and will not cause issues in the test environment.
Applied to files:
magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs
🧬 Code graph analysis (11)
magicblock-ledger/src/database/db.rs (1)
magicblock-ledger/src/database/rocks_db.rs (2)
raw_iterator_cf(209-211)batch(213-215)
magicblock-committor-program/src/state/chunks.rs (1)
magicblock-accounts-db/src/storage.rs (1)
offset(184-190)
tools/ledger-stats/src/accounts.rs (1)
magicblock-committor-program/src/state/changeset.rs (1)
owner(119-124)
magicblock-config/src/lib.rs (1)
magicblock-api/src/magic_validator.rs (1)
config(367-374)
magicblock-api/src/magic_validator.rs (1)
magicblock-aperture/src/lib.rs (1)
initialize_aperture(14-25)
magicblock-ledger/src/database/rocks_db.rs (1)
magicblock-ledger/src/database/db.rs (1)
raw_iterator_cf(105-107)
magicblock-aperture/tests/setup.rs (1)
magicblock-aperture/src/lib.rs (1)
initialize_aperture(14-25)
magicblock-api/src/errors.rs (1)
magicblock-aperture/src/error.rs (5)
from(36-38)from(42-44)from(48-50)from(54-56)from(60-62)
magicblock-aperture/src/tests.rs (1)
magicblock-aperture/src/processor.rs (1)
start(91-103)
magicblock-aperture/src/processor.rs (2)
magicblock-core/src/link.rs (1)
link(60-89)magicblock-aperture/src/geyser.rs (2)
notify_slot(102-109)notify_account(73-100)
magicblock-aperture/src/lib.rs (3)
magicblock-aperture/src/processor.rs (2)
start(91-103)new(59-79)magicblock-aperture/tests/setup.rs (1)
new(86-148)magicblock-aperture/src/server/http/mod.rs (1)
new(40-55)
🔇 Additional comments (31)
magicblock-committor-service/src/intent_execution_manager/intent_execution_engine.rs (1)
363-371: HashSet import correctly matches test usageSwitching the import to
collections::HashSetaligns withreceived_idsusage intest_non_blocking_messagesand removes the implicit HashMap dependency. Looks good and keeps the test code clear and minimal.magicblock-committor-service/src/compute_budget.rs (1)
63-63: Excellent improvement—more idiomatic overflow handling.The change from
checked_add(...).unwrap_or(u32::MAX)tosaturating_add(...)is cleaner and more idiomatic while preserving the same overflow behavior.magicblock-committor-service/src/intent_executor/task_info_fetcher.rs (2)
16-16: LGTM! Safe alternative to unsafe code.Replacing
new_uncheckedwithnew().unwrap()is appropriate here since 5 is a compile-time constant and non-zero. The compiler optimizes this to the same code without runtime overhead.
53-53: LGTM! Safe alternative to unsafe code.Similar to line 16, replacing
new_uncheckedwithnew().unwrap()is safe and correct since 1000 is non-zero. This eliminates unnecessary unsafe code without runtime cost.Note: This change appears unrelated to the PR's stated objective (Geyser plugin support). Consider grouping such cleanup changes in a separate PR for clearer change tracking.
magicblock-accounts-db/src/lib.rs (1)
246-274: Modulo guard change is behavior‑preservingThe rewritten condition
if slot % self.snapshot_frequency != 0 {is equivalent to the previous form given theassert_ne!(snapshot_frequency, 0, ...)above, so snapshot triggering semantics are unchanged and remain correct.magicblock-accounts-db/src/index.rs (1)
360-365: Lifetime‑annotated return type is consistent and clearerReturning
AccountsDbResult<AccountOffsetFinder<'_>>aligns withAccountsReader<'db>usingAccountOffsetFinder<'db>and makes the borrow relationship explicit without changing behavior. This looks correct and type‑sound.magicblock-ledger/src/store/api.rs (1)
210-210: LGTM! Explicit lifetime annotations improve clarity.The addition of explicit
'_lifetime annotations toRwLockReadGuardreturn types correctly documents that the returned guards borrow fromself. This change improves code clarity without affecting behavior, as the same lifetimes were previously inferred through elision.Also applies to: 232-232
magicblock-ledger/src/database/db.rs (1)
105-105: LGTM! Lifetime annotations align with underlying RocksDB API.The explicit
'_lifetime annotations onDBRawIteratorandWriteBatchreturn types correctly document that these types borrow fromself. This aligns with the lifetime annotations in the underlyingrocks_db.rsmethods and maintains API consistency.Also applies to: 109-109
magicblock-ledger/src/database/rocks_db.rs (1)
87-87: LGTM! Explicit lifetime annotations improve safety documentation.The addition of explicit
'_lifetime annotations to RocksDB iterator and slice types (DBPinnableSlice,DBIterator,DBRawIterator) correctly documents that these types borrow from the database instance. This makes the borrowing relationships explicit, which:
- Improves code readability and maintainability
- Helps prevent lifetime-related errors
- Aligns with modern Rust best practices
The changes are purely type-level clarifications and do not affect runtime behavior.
Also applies to: 106-106, 176-176, 196-196, 209-209
config.example.toml (2)
89-108: LGTM! Well-structured configuration section with clear documentation.The new
[aperture]section properly consolidates RPC, event processor, and geyser plugin configuration. Documentation is comprehensive with defaults, CLI flags, and environment variable mappings.
109-111: Terminology update noted: "L2 -> L1" changed to "ER -> BASE".This aligns with the project's naming conventions (Ephemeral Runtime → Base chain).
magicblock-config/src/config/mod.rs (1)
2-2: LGTM! Clean module addition following existing conventions.The new
aperturemodule and itsApertureConfigre-export follow the established pattern in this file.Also applies to: 14-14
magicblock-validator/src/main.rs (1)
95-97: LGTM! Configuration access paths updated correctly.The changes align with the new
ApertureConfigstructure wherelistenis nested underaperture.magicblock-api/src/magic_validator.rs (2)
19-21: LGTM! Import updated for new initialization entrypoint.The import change from
JsonRpcServertoinitialize_aperturereflects the architectural shift to a unified aperture initialization function.
261-267: LGTM! Clean refactoring to use the new aperture initialization entrypoint.The change from direct
JsonRpcServer::new()construction toinitialize_aperture()properly encapsulates:
- Event processor task startup
- Geyser plugin loading
- JSON-RPC server creation
This aligns with the
initialize_aperturefunction signature inmagicblock-aperture/src/lib.rs(lines 13-24).magicblock-aperture/src/tests.rs (1)
53-54: LGTM! Test setup updated to use the new configuration-driven initialization.Using
ApertureConfig::default()is appropriate for unit tests - it provides sensible defaults (1 event processor, no geyser plugins) without requiring external configuration. The test now mirrors the production initialization flow throughEventProcessor::start.Also applies to: 76-78
magicblock-api/src/errors.rs (1)
12-13: LGTM! Error variant correctly updated to use the new umbrella error type.The change from
RpcErrortoApertureErrorproperly reflects the expanded scope of the aperture module, which now handles both RPC and Geyser plugin errors. The#[from]attribute ensures seamless error propagation frominitialize_aperturecalls.magicblock-config/src/lib.rs (1)
4-7: LGTM! Import and re-export changes align with the configuration restructuring.The
ApertureConfigimport and removal ofBindAddressfrom re-exports correctly reflect the new configuration hierarchy.Also applies to: 28-28
Cargo.toml (1)
49-49: agave-geyser-plugin-interface version 2.2 is significantly outdated; use 3.1.3 instead.The latest version of
agave-geyser-plugin-interfaceis 3.1.3, not 2.2. Using an outdated version introduces security risks, misses bug fixes, and may cause compatibility issues. Update toagave-geyser-plugin-interface = { version = "3.1" }or pin to"3.1.3"directly.For
libloading, version 0.8 is acceptable, though the latest 0.8.x patch is 0.8.9.⛔ Skipped due to learnings
Learnt from: Dodecahedr0x Repo: magicblock-labs/magicblock-validator PR: 639 File: Cargo.toml:58-58 Timestamp: 2025-11-24T14:21:00.996Z Learning: In the magicblock-validator codebase, magicblock-api/Cargo.toml intentionally uses borsh = "1.5.3" (instead of the workspace version 0.10.4) because it needs to deserialize types from the magic-domain-program external dependency, which requires borsh 1.5.x compatibility. This is an intentional exception for interoperability with the magic domain program.magicblock-aperture/Cargo.toml (1)
19-19: LGTM! Dependencies align well with Geyser plugin support.The added dependencies are appropriate:
libloadingfor dynamic.soplugin loadingagave-geyser-plugin-interfacefor the official Geyser plugin APIthiserrorfor the new error typesAlso applies to: 42-42, 70-70
magicblock-aperture/tests/setup.rs (2)
96-127: LGTM! Server initialization refactored correctly to use new aperture API.The test setup correctly adapts to the new
initialize_apertureentrypoint andApertureConfigstructure while preserving the port-finding loop for parallel test isolation.
131-137: The WebSocket port convention (RPC port + 1) is correct and follows Solana's established pattern.The pubsub URL binding to
port + 1is verified across the codebase:magicblock-aperture/src/lib.rsexplicitly setsaddr.set_port(addr.port() + 1)before binding the WebSocket listener, and this convention is documented inmagicblock-config/src/types/network.rsas "By solana convention, websocket listens on rpc port + 1". The test code correctly implements this pattern.magicblock-config/src/config/aperture.rs (1)
7-20: Well-structured configuration type for the new aperture functionality.The struct appropriately captures the RPC listen address, parallelism settings, and plugin paths with sensible serde attributes (
deny_unknown_fieldsprevents typos,defaultallows partial configs).magicblock-config/src/tests.rs (1)
69-69: LGTM! Tests properly updated to reflect the new aperture configuration structure.The test coverage correctly validates:
- Default values for
aperture.listenandaperture.event_processors- CLI overlay behavior preserving unset fields
- Environment variable mapping with the
MBV_APERTURE__*prefix- Example config parsing
Also applies to: 175-192, 468-471, 502-504, 551-554
magicblock-aperture/src/error.rs (1)
15-21: LGTM! Clean umbrella error type for the aperture crate.The
ApertureErrorenum correctly aggregates RPC and Geyser plugin errors with automaticFromconversions via#[from].magicblock-config/src/config/cli.rs (1)
62-76: LGTM! Clean CLI config structure for aperture settings.The new
CliApertureConfigstruct follows the established patterns in this file with appropriate use of#[command(flatten)]for CLI integration and consistent serialization attributes.magicblock-aperture/src/geyser.rs (1)
16-19: Good: Field ordering ensures correct drop order.The
pluginsfield is declared before_libs, so plugins are dropped before the libraries are unloaded. This prevents use-after-free when plugin destructors run.magicblock-aperture/src/lib.rs (2)
14-25: LGTM! Clean initialization entrypoint.The
initialize_aperturefunction provides a well-structured public API that properly sequences EventProcessor startup before server creation, with appropriate error propagation.
33-54: Good encapsulation by makingnewprivate.Making
JsonRpcServer::newprivate ensures callers useinitialize_aperture, which properly initializes the EventProcessor first. This prevents misuse where a server could be created without the event processing infrastructure.magicblock-aperture/src/processor.rs (2)
116-136: Reasonable error handling for geyser notifications.Logging errors at
warnlevel and continuing is appropriate since geyser plugin failures shouldn't crash the validator. Consider adding rate limiting for error logs if plugins fail continuously, but this is fine for initial implementation.
59-79: Good safety documentation for the FFI boundary.The SAFETY comment clearly explains the risks and responsibilities around loading Geyser plugins. Based on learnings, this pattern of documenting unsafe code rationale is consistent with the codebase conventions.
| #[derive(Serialize, Debug, thiserror::Error)] | ||
| pub struct RpcError { | ||
| code: i16, | ||
| message: String, | ||
| } | ||
|
|
||
| impl Display for RpcError { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| write!( | ||
| f, | ||
| "RPC Error. Code: {}. Message: {}", | ||
| self.code, self.message | ||
| ) | ||
| write!(f, "Code: {}. Message: {}", self.code, self.message) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Custom Display impl overrides thiserror generated one — ensure this is intentional.
When deriving thiserror::Error, a Display impl is typically generated from #[error("...")]. Since RpcError has both a manual Display impl and the derive, the manual one takes precedence. If this custom format ("Code: {code}. Message: {message}") is intentional, consider adding an #[error] attribute instead to make it explicit, or remove the manual impl.
-#[derive(Serialize, Debug, thiserror::Error)]
+#[derive(Serialize, Debug)]
pub struct RpcError {
code: i16,
message: String,
}
+
+impl std::error::Error for RpcError {}Or alternatively, use the #[error] attribute and remove the manual Display:
#[derive(Serialize, Debug, thiserror::Error)]
+#[error("Code: {code}. Message: {message}")]
pub struct RpcError {
code: i16,
message: String,
}
-
-impl Display for RpcError {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "Code: {}. Message: {}", self.code, self.message)
- }
-}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #[derive(Serialize, Debug, thiserror::Error)] | |
| pub struct RpcError { | |
| code: i16, | |
| message: String, | |
| } | |
| impl Display for RpcError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!( | |
| f, | |
| "RPC Error. Code: {}. Message: {}", | |
| self.code, self.message | |
| ) | |
| write!(f, "Code: {}. Message: {}", self.code, self.message) | |
| } | |
| } | |
| #[derive(Serialize, Debug)] | |
| pub struct RpcError { | |
| code: i16, | |
| message: String, | |
| } | |
| impl Display for RpcError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!(f, "Code: {}. Message: {}", self.code, self.message) | |
| } | |
| } | |
| impl std::error::Error for RpcError {} |
| #[derive(Serialize, Debug, thiserror::Error)] | |
| pub struct RpcError { | |
| code: i16, | |
| message: String, | |
| } | |
| impl Display for RpcError { | |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| write!( | |
| f, | |
| "RPC Error. Code: {}. Message: {}", | |
| self.code, self.message | |
| ) | |
| write!(f, "Code: {}. Message: {}", self.code, self.message) | |
| } | |
| } | |
| #[derive(Serialize, Debug, thiserror::Error)] | |
| #[error("Code: {code}. Message: {message}")] | |
| pub struct RpcError { | |
| code: i16, | |
| message: String, | |
| } |
🤖 Prompt for AI Agents
In magicblock-aperture/src/error.rs around lines 23 to 33, the manual Display
impl for RpcError overrides the Display produced by #[derive(thiserror::Error)];
decide and make it explicit: either remove the manual impl and add an
#[error("Code: {code}. Message: {message}")] attribute on the struct so
thiserror generates the Display, or keep the manual impl but remove the derive
for Display by changing the derive to exclude Display (or keep derive and
document intent with a comment/attribute). Update the file accordingly so only
one Display implementation exists and the chosen format is explicit.
| impl GeyserPluginManager { | ||
| pub(crate) unsafe fn new( | ||
| configs: &[PathBuf], | ||
| ) -> Result<Self, GeyserPluginError> { | ||
| let mut plugins = Vec::with_capacity(configs.len()); | ||
| let mut _libs = Vec::with_capacity(configs.len()); | ||
| for file_path in configs { | ||
| let config = fs::read_to_string(file_path)?; | ||
| let config: Value = json::from_str(&config).map_err(|e| { | ||
| GeyserPluginError::ConfigFileReadError { | ||
| msg: format!( | ||
| "Failed to parse plugin configuration file: {e}" | ||
| ), | ||
| } | ||
| })?; | ||
| let path = config | ||
| .get("path") | ||
| .ok_or(GeyserPluginError::ConfigFileReadError { | ||
| msg: | ||
| "Plugin configuration file doesn't contain `path` field" | ||
| .into(), | ||
| })? | ||
| .as_str() | ||
| .ok_or(GeyserPluginError::ConfigFileReadError { | ||
| msg: | ||
| "The `path` field in the configuration must be a string" | ||
| .into(), | ||
| })?; | ||
| let lib = Library::new(path).map_err(|e| { | ||
| GeyserPluginError::ConfigFileReadError { | ||
| msg: format!( | ||
| "Failed to load plugin shared library object file: {e}" | ||
| ), | ||
| } | ||
| })?; | ||
| let create_plugin: Symbol<PluginCreate> = lib.get(ENTRYPOINT_SYMBOL).map_err(|e| { | ||
| GeyserPluginError::ConfigFileReadError { | ||
| msg: format!( | ||
| "Failed to read entry point symbol from plugin object file: {e}" | ||
| ), | ||
| } | ||
| })?; | ||
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | ||
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); | ||
| plugin.on_load(&file_path.to_string_lossy(), false)?; | ||
| plugin.notify_end_of_startup()?; | ||
| plugins.push(plugin); | ||
| _libs.push(lib); | ||
| } | ||
| Ok(Self { plugins, _libs }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider documenting the safety contract for plugin loading.
The new function is correctly marked unsafe, but the safety requirements could be more explicit in documentation. The caller must ensure:
- Config files point to valid, trusted plugin libraries
- Plugins are ABI-compatible with this validator version
+ /// Creates a new GeyserPluginManager by loading plugins from the provided config files.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that:
+ /// - All plugin configuration files point to valid, trusted shared libraries
+ /// - The loaded plugins are ABI-compatible with the current validator version
+ /// - The plugin libraries implement the `_create_plugin` symbol correctly
pub(crate) unsafe fn new(
configs: &[PathBuf],
) -> Result<Self, GeyserPluginError> {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| impl GeyserPluginManager { | |
| pub(crate) unsafe fn new( | |
| configs: &[PathBuf], | |
| ) -> Result<Self, GeyserPluginError> { | |
| let mut plugins = Vec::with_capacity(configs.len()); | |
| let mut _libs = Vec::with_capacity(configs.len()); | |
| for file_path in configs { | |
| let config = fs::read_to_string(file_path)?; | |
| let config: Value = json::from_str(&config).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to parse plugin configuration file: {e}" | |
| ), | |
| } | |
| })?; | |
| let path = config | |
| .get("path") | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "Plugin configuration file doesn't contain `path` field" | |
| .into(), | |
| })? | |
| .as_str() | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "The `path` field in the configuration must be a string" | |
| .into(), | |
| })?; | |
| let lib = Library::new(path).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to load plugin shared library object file: {e}" | |
| ), | |
| } | |
| })?; | |
| let create_plugin: Symbol<PluginCreate> = lib.get(ENTRYPOINT_SYMBOL).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to read entry point symbol from plugin object file: {e}" | |
| ), | |
| } | |
| })?; | |
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | |
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); | |
| plugin.on_load(&file_path.to_string_lossy(), false)?; | |
| plugin.notify_end_of_startup()?; | |
| plugins.push(plugin); | |
| _libs.push(lib); | |
| } | |
| Ok(Self { plugins, _libs }) | |
| } | |
| impl GeyserPluginManager { | |
| /// Creates a new GeyserPluginManager by loading plugins from the provided config files. | |
| /// | |
| /// # Safety | |
| /// | |
| /// The caller must ensure that: | |
| /// - All plugin configuration files point to valid, trusted shared libraries | |
| /// - The loaded plugins are ABI-compatible with the current validator version | |
| /// - The plugin libraries implement the `_create_plugin` symbol correctly | |
| pub(crate) unsafe fn new( | |
| configs: &[PathBuf], | |
| ) -> Result<Self, GeyserPluginError> { | |
| let mut plugins = Vec::with_capacity(configs.len()); | |
| let mut _libs = Vec::with_capacity(configs.len()); | |
| for file_path in configs { | |
| let config = fs::read_to_string(file_path)?; | |
| let config: Value = json::from_str(&config).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to parse plugin configuration file: {e}" | |
| ), | |
| } | |
| })?; | |
| let path = config | |
| .get("path") | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "Plugin configuration file doesn't contain `path` field" | |
| .into(), | |
| })? | |
| .as_str() | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "The `path` field in the configuration must be a string" | |
| .into(), | |
| })?; | |
| let lib = Library::new(path).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to load plugin shared library object file: {e}" | |
| ), | |
| } | |
| })?; | |
| let create_plugin: Symbol<PluginCreate> = lib.get(ENTRYPOINT_SYMBOL).map_err(|e| { | |
| GeyserPluginError::ConfigFileReadError { | |
| msg: format!( | |
| "Failed to read entry point symbol from plugin object file: {e}" | |
| ), | |
| } | |
| })?; | |
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | |
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); | |
| plugin.on_load(&file_path.to_string_lossy(), false)?; | |
| plugin.notify_end_of_startup()?; | |
| plugins.push(plugin); | |
| _libs.push(lib); | |
| } | |
| Ok(Self { plugins, _libs }) | |
| } |
🤖 Prompt for AI Agents
In magicblock-aperture/src/geyser.rs around lines 21 to 71, add a clear doc
comment on the unsafe fn new describing the safety contract: state that the
caller must ensure config paths point to valid, trusted plugin shared libraries,
that the loaded libraries are ABI-compatible with this validator (matching
symbol signatures and expectations), that the ENTRYPOINT_SYMBOL returns a valid
pointer to a GeyserPlugin instance and that ownership/stack/threading rules for
the plugin instance are satisfied (e.g., Box::from_raw is valid and plugin
methods are safe to call from this context); also document any lifetime or
concurrency constraints and examples of correct caller behavior so future
callers understand when using unsafe is valid.
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | ||
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing null check before Box::from_raw — potential null pointer dereference.
If create_plugin() returns a null pointer (e.g., due to plugin initialization failure), Box::from_raw(plugin_raw) will create a box from null, leading to undefined behavior when dereferenced.
let plugin_raw: *mut dyn GeyserPlugin = create_plugin();
+ if plugin_raw.is_null() {
+ return Err(GeyserPluginError::ConfigFileReadError {
+ msg: "Plugin factory returned null pointer".into(),
+ });
+ }
let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | |
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); | |
| let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); | |
| if plugin_raw.is_null() { | |
| return Err(GeyserPluginError::ConfigFileReadError { | |
| msg: "Plugin factory returned null pointer".into(), | |
| }); | |
| } | |
| let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw); |
🤖 Prompt for AI Agents
In magicblock-aperture/src/geyser.rs around lines 63-64, the code calls
Box::from_raw on the pointer returned by create_plugin() without checking for
null; add an explicit null check (if plugin_raw.is_null()) immediately after
create_plugin() and handle the failure path (log an error/return Err or cleanly
abort plugin initialization) instead of calling Box::from_raw on a null pointer,
and only call Box::from_raw when plugin_raw is non-null so you avoid undefined
behavior; ensure any required cleanup or error propagation is performed in the
null branch.
| pub fn notify_slot(&self, slot: u64) -> Result<(), GeyserPluginError> { | ||
| let status = &SlotStatus::Rooted; | ||
| let parent = Some(slot.saturating_sub(1)); | ||
| for plugin in &self.plugins { | ||
| plugin.update_slot_status(slot, parent, status)?; | ||
| } | ||
| Ok(()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edge case: slot 0 would have itself as parent.
slot.saturating_sub(1) returns 0 when slot == 0, making slot 0 its own parent. Consider using slot.checked_sub(1) to return None for slot 0:
pub fn notify_slot(&self, slot: u64) -> Result<(), GeyserPluginError> {
let status = &SlotStatus::Rooted;
- let parent = Some(slot.saturating_sub(1));
+ let parent = slot.checked_sub(1);
for plugin in &self.plugins {
plugin.update_slot_status(slot, parent, status)?;
}
Ok(())
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn notify_slot(&self, slot: u64) -> Result<(), GeyserPluginError> { | |
| let status = &SlotStatus::Rooted; | |
| let parent = Some(slot.saturating_sub(1)); | |
| for plugin in &self.plugins { | |
| plugin.update_slot_status(slot, parent, status)?; | |
| } | |
| Ok(()) | |
| pub fn notify_slot(&self, slot: u64) -> Result<(), GeyserPluginError> { | |
| let status = &SlotStatus::Rooted; | |
| let parent = slot.checked_sub(1); | |
| for plugin in &self.plugins { | |
| plugin.update_slot_status(slot, parent, status)?; | |
| } | |
| Ok(()) | |
| } |
🤖 Prompt for AI Agents
In magicblock-aperture/src/geyser.rs around lines 102 to 108, passing
Some(slot.saturating_sub(1)) makes slot 0 its own parent; change parent to use
slot.checked_sub(1) so it yields None for slot 0 (i.e., let parent =
slot.checked_sub(1);) and pass that Option<u64> to plugin.update_slot_status;
preserve the rest of the loop and error handling.
| pub(crate) fn start( | ||
| config: &ApertureConfig, | ||
| state: &SharedState, | ||
| channels: &DispatchEndpoints, | ||
| instances: usize, | ||
| cancel: CancellationToken, | ||
| ) { | ||
| for id in 0..instances { | ||
| let processor = EventProcessor::new(channels, state); | ||
| ) -> ApertureResult<()> { | ||
| for id in 0..config.event_processors { | ||
| let processor = | ||
| EventProcessor::new(channels, state, &config.geyser_plugins)?; | ||
| tokio::spawn(processor.run(id, cancel.clone())); | ||
| } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each EventProcessor creates its own GeyserPluginManager — plugins loaded multiple times.
The start method creates config.event_processors instances, each calling EventProcessor::new which loads all plugins independently. This means each plugin is loaded and initialized event_processors times, which:
- May cause resource contention or conflicts in plugins expecting singleton behavior
- Wastes memory by duplicating plugin state
- Could cause issues with plugins that open exclusive resources (files, ports, etc.)
Consider creating the GeyserPluginManager once and sharing it via Arc:
pub(crate) fn start(
config: &ApertureConfig,
state: &SharedState,
channels: &DispatchEndpoints,
cancel: CancellationToken,
) -> ApertureResult<()> {
+ // SAFETY: see documentation on GeyserPluginManager::new
+ let geyser: Arc<GeyserPluginManager> =
+ unsafe { GeyserPluginManager::new(&config.geyser_plugins) }?.into();
for id in 0..config.event_processors {
- let processor =
- EventProcessor::new(channels, state, &config.geyser_plugins)?;
+ let processor =
+ EventProcessor::new_with_geyser(channels, state, geyser.clone());
tokio::spawn(processor.run(id, cancel.clone()));
}
Ok(())
}This would require adding a new constructor that accepts an existing Arc<GeyserPluginManager>.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In magicblock-aperture/src/processor.rs around lines 91 to 103, each loop
iteration calls EventProcessor::new which constructs its own GeyserPluginManager
causing plugins to be loaded multiple times; instead, create a single
GeyserPluginManager before the loop, wrap it in an Arc, and pass a cloned Arc
reference into EventProcessor (add a new EventProcessor::with_plugin_manager or
extend the existing constructor to accept Arc<GeyserPluginManager>). Instantiate
the manager once using the same config.geyser_plugins, wrap it in Arc, and
inside the loop call the new constructor with manager.clone() so every spawned
processor shares the same plugin manager and no plugin is initialized more than
once.
| offset: usize, | ||
| ) -> Result<(), ChunksError> { | ||
| if offset % self.chunk_size as usize != 0 { | ||
| if offset % self.chunk_size as usize > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Standardize offset validation checks for consistency.
Line 100 uses offset % self.chunk_size as usize > 0 while the identical validation in is_offset_delivered (line 115) uses offset % self.chunk_size as usize != 0. While these are logically equivalent for unsigned integers, using inconsistent forms reduces code clarity and maintainability.
Recommend standardizing on != 0 across both methods, as it's more idiomatic for this type of zero-check pattern in Rust.
pub fn set_offset_delivered(
&mut self,
offset: usize,
) -> Result<(), ChunksError> {
- if offset % self.chunk_size as usize > 0 {
+ if offset % self.chunk_size as usize != 0 {
Err(ChunksError::InvalidOffsetError(offset, self.chunk_size))
} else {
let idx = offset / self.chunk_size as usize;
self.set_chunk_delivered(idx)?;
Ok(())
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if offset % self.chunk_size as usize > 0 { | |
| pub fn set_offset_delivered( | |
| &mut self, | |
| offset: usize, | |
| ) -> Result<(), ChunksError> { | |
| if offset % self.chunk_size as usize != 0 { | |
| Err(ChunksError::InvalidOffsetError(offset, self.chunk_size)) | |
| } else { | |
| let idx = offset / self.chunk_size as usize; | |
| self.set_chunk_delivered(idx)?; | |
| Ok(()) | |
| } | |
| } |
🤖 Prompt for AI Agents
magicblock-committor-program/src/state/chunks.rs around lines 100 and 115: the
offset validation at line 100 uses `offset % self.chunk_size as usize > 0` while
the same check at line 115 uses `offset % self.chunk_size as usize != 0`;
standardize both to use `!= 0` for clarity and idiomatic zero-checking in Rust
by replacing the `> 0` expression at line 100 with `!= 0`.
| /// Aperture-specific configuration. | ||
| pub aperture: ApertureConfig, | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for any remaining references to config.listen that should be config.aperture.listen
rg -n --type rust '\.listen\.' | grep -v 'aperture\.listen' | grep -v 'prepare_lookup_tables' | grep -v '_test\.' | head -30Repository: magicblock-labs/magicblock-validator
Length of output: 528
🏁 Script executed:
#!/bin/bash
# Check the ValidatorParams structure in lib.rs around lines 50-60
sed -n '50,60p' magicblock-config/src/lib.rsRepository: magicblock-labs/magicblock-validator
Length of output: 406
🏁 Script executed:
#!/bin/bash
# Check the context around the found references
echo "=== magicblock-aperture/src/lib.rs:42 ==="
sed -n '38,46p' magicblock-aperture/src/lib.rs
echo -e "\n=== test-integration/test-tools/src/validator.rs:215 ==="
sed -n '211,220p' test-integration/test-tools/src/validator.rs
echo -e "\n=== test-integration/test-tools/src/validator.rs:258 ==="
sed -n '254,263p' test-integration/test-tools/src/validator.rs
echo -e "\n=== test-integration/test-tools/src/toml_to_args.rs:59 ==="
sed -n '55,65p' test-integration/test-tools/src/toml_to_args.rsRepository: magicblock-labs/magicblock-validator
Length of output: 1872
🏁 Script executed:
#!/bin/bash
# Check the function signature and context of toml_to_args.rs
sed -n '50,65p' test-integration/test-tools/src/toml_to_args.rsRepository: magicblock-labs/magicblock-validator
Length of output: 507
Breaking changes must be addressed before merging: update all .listen references to .aperture.listen
The refactoring to group aperture configuration is sound, but the migration is incomplete. Four locations still reference the old config.listen field and will break at runtime:
magicblock-aperture/src/lib.rs:42—config.listen.0should beconfig.aperture.listen.0test-integration/test-tools/src/validator.rs:215, 258—config.listenassignments and property access should useconfig.aperture.listentest-integration/test-tools/src/toml_to_args.rs:59—config.listenshould beconfig.aperture.listen
🤖 Prompt for AI Agents
In magicblock-config/src/lib.rs around lines 53-55 and in the listed call sites,
update all accesses to the moved listen field to use the new nested path:
replace config.listen with config.aperture.listen; specifically change
magicblock-aperture/src/lib.rs line ~42 from config.listen.0 to
config.aperture.listen.0, update both assignments and property accesses in
test-integration/test-tools/src/validator.rs at lines ~215 and ~258 to use
config.aperture.listen, and change
test-integration/test-tools/src/toml_to_args.rs line ~59 from config.listen to
config.aperture.listen so all references point to the new ApertureConfig.listen
field.
| .filter(|acc| { | ||
| if !owner.map_or(true, |owner| acc.owner.eq(&owner)) { | ||
| if !owner.is_none_or(|owner| acc.owner.eq(&owner)) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Owner filter logic is correct; consider MSRV and readability tweaks
The is_none_or usage is semantically correct and preserves the intended behavior of “no filter when owner is None, exact owner match when Some”. However:
Option::is_none_oris a relatively new std method; ensure your Rust toolchain/MSRV is new enough to support it, otherwise this will break builds.- The closure parameter name
|owner|shadows the outerowner: Option<Pubkey>, which slightly hurts readability.
You might consider one of:
// Keep is_none_or, avoid shadowing
if !owner.is_none_or(|filter_owner| acc.owner == filter_owner) {
return false;
}
// Or fall back to the clearer pattern if MSRV is a concern:
if let Some(filter_owner) = owner {
if acc.owner != filter_owner {
return false;
}
}🤖 Prompt for AI Agents
In tools/ledger-stats/src/accounts.rs around lines 126 to 129, the owner filter
uses Option::is_none_or with a closure that shadows the outer owner variable; to
fix, either rename the closure parameter to avoid shadowing (e.g., filter_owner)
while keeping is_none_or, or replace the is_none_or usage with an explicit if
let Some(filter_owner) = owner check and early-return when acc.owner !=
filter_owner to preserve MSRV compatibility and improve readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (5)
magicblock-committor-program/src/state/chunks.rs (1)
102-102: Inconsistent offset validation remains unresolved.This line continues to use
> 0while the identical validation inis_offset_delivered(line 117) uses!= 0. A previous review already requested standardizing both checks to!= 0for consistency and idiomatic Rust style, but the issue remains unaddressed.🔎 Recommended standardization
- if offset % self.chunk_size as usize > 0 { + if offset % self.chunk_size as usize != 0 { Err(ChunksError::InvalidOffsetError(offset, self.chunk_size))magicblock-aperture/src/geyser.rs (3)
76-77: Missing null check beforeBox::from_raw— potential undefined behavior.If
create_plugin()returns a null pointer (e.g., due to plugin initialization failure),Box::from_raw(plugin_raw)will create a box from null, leading to undefined behavior when dereferenced.🔎 Proposed fix
let plugin_raw: *mut dyn GeyserPlugin = create_plugin(); + if plugin_raw.is_null() { + return Err(GeyserPluginError::ConfigFileReadError { + msg: "Plugin factory returned null pointer".into(), + }); + } let mut plugin: Box<dyn GeyserPlugin> = Box::from_raw(plugin_raw);
115-122: Edge case: slot 0 would have itself as parent.
slot.saturating_sub(1)returns 0 whenslot == 0, making slot 0 its own parent. Useslot.checked_sub(1)to returnNonefor slot 0.🔎 Proposed fix
pub fn notify_slot(&self, slot: u64) -> Result<(), GeyserPluginError> { check_if_enabled!(self); let status = &SlotStatus::Rooted; - let parent = Some(slot.saturating_sub(1)); + let parent = slot.checked_sub(1); for plugin in &self.plugins { plugin.update_slot_status(slot, parent, status)?; } Ok(()) }
153-167:strict_sub(1)will panic ifblock.meta.slotis 0.Unlike
notify_slotwhich usessaturating_sub, this usesstrict_subwhich panics on underflow. If a block with slot 0 is ever processed, this will crash the validator.🔎 Proposed fix
let block = ReplicaBlockInfoV4 { slot: block.meta.slot, - parent_slot: block.meta.slot.strict_sub(1), + parent_slot: block.meta.slot.saturating_sub(1), blockhash: &block.hash.to_string(),magicblock-aperture/src/processor.rs (1)
91-103: Each EventProcessor creates its own GeyserPluginManager — plugins loaded multiple times.The
startmethod spawnsconfig.event_processorsinstances, each callingEventProcessor::newwhich loads all plugins independently. This means each plugin is loaded and initialized multiple times, which:
- May cause resource contention or conflicts in plugins expecting singleton behavior
- Wastes memory by duplicating plugin state
- Could cause issues with plugins that open exclusive resources (files, ports, etc.)
Consider creating the
GeyserPluginManageronce and sharing it viaArc.🔎 Proposed fix
pub(crate) fn start( config: &ApertureConfig, state: &SharedState, channels: &DispatchEndpoints, cancel: CancellationToken, ) -> ApertureResult<()> { + // SAFETY: see documentation on GeyserPluginManager::new + let geyser: Arc<GeyserPluginManager> = + unsafe { GeyserPluginManager::new(&config.geyser_plugins) }?.into(); for id in 0..config.event_processors { - let processor = - EventProcessor::new(channels, state, &config.geyser_plugins)?; + let processor = + EventProcessor::new_with_geyser(channels, state, geyser.clone()); tokio::spawn(processor.run(id, cancel.clone())); } Ok(()) }Add a new constructor that accepts an existing
Arc<GeyserPluginManager>:fn new_with_geyser( channels: &DispatchEndpoints, state: &SharedState, geyser: Arc<GeyserPluginManager>, ) -> Self { Self { subscriptions: state.subscriptions.clone(), transactions: state.transactions.clone(), blocks: state.blocks.clone(), account_update_rx: channels.account_update.clone(), transaction_status_rx: channels.transaction_status.clone(), block_update_rx: channels.block_update.clone(), geyser, } }
🧹 Nitpick comments (1)
magicblock-ledger/src/store/api.rs (1)
1703-1710: Minor: redundant clone in test.The expression
&sanitized_uno.clone()creates an unnecessary clone sincesanitized_unois already owned. Consider using&sanitized_unodirectly.🔎 Suggested simplification
.write_transaction( sig_uno, slot_uno, - &sanitized_uno.clone(), + &sanitized_uno, tx_uno.tx_with_meta.get_status_meta().unwrap(), )Apply similar changes to other test call sites (lines 1733, 2369, 2392).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
Cargo.tomlconfig.example.tomlmagicblock-aperture/Cargo.tomlmagicblock-aperture/src/encoder.rsmagicblock-aperture/src/geyser.rsmagicblock-aperture/src/processor.rsmagicblock-aperture/src/state/subscriptions.rsmagicblock-aperture/src/tests.rsmagicblock-aperture/tests/setup.rsmagicblock-api/src/errors.rsmagicblock-api/src/magic_validator.rsmagicblock-committor-program/src/state/chunks.rsmagicblock-config/src/config/cli.rsmagicblock-config/src/lib.rsmagicblock-config/src/tests.rsmagicblock-config/src/types/mod.rsmagicblock-core/Cargo.tomlmagicblock-core/src/link/transactions.rsmagicblock-ledger/src/store/api.rsmagicblock-ledger/tests/common.rsmagicblock-processor/src/executor/processing.rsmagicblock-processor/tests/execution.rsmagicblock-processor/tests/fees.rsmagicblock-processor/tests/scheduling.rsmagicblock-validator/src/main.rs
🚧 Files skipped from review as they are similar to previous changes (6)
- magicblock-api/src/magic_validator.rs
- magicblock-config/src/lib.rs
- magicblock-config/src/config/cli.rs
- magicblock-aperture/Cargo.toml
- Cargo.toml
- magicblock-aperture/src/tests.rs
🧰 Additional context used
🧠 Learnings (14)
📚 Learning: 2025-11-24T14:21:00.996Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: Cargo.toml:58-58
Timestamp: 2025-11-24T14:21:00.996Z
Learning: In the magicblock-validator codebase, magicblock-api/Cargo.toml intentionally uses borsh = "1.5.3" (instead of the workspace version 0.10.4) because it needs to deserialize types from the magic-domain-program external dependency, which requires borsh 1.5.x compatibility. This is an intentional exception for interoperability with the magic domain program.
Applied to files:
magicblock-core/Cargo.toml
📚 Learning: 2025-12-03T09:33:48.707Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: test-integration/test-committor-service/tests/test_ix_commit_local.rs:867-881
Timestamp: 2025-12-03T09:33:48.707Z
Learning: Repo: magicblock-labs/magicblock-validator PR: 639
Context: test-integration/test-committor-service/tests/test_ix_commit_local.rs (ix_commit_local)
Learning: The PhotonIndexer used for compressed account fetches (get_compressed_account) has built‑in retry logic (defaults to ~10 attempts), so tests should not add separate retry loops around compressed fetches unless there’s a specific need.
Applied to files:
magicblock-processor/tests/execution.rsmagicblock-processor/tests/fees.rsmagicblock-aperture/src/state/subscriptions.rs
📚 Learning: 2025-12-03T09:36:01.527Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: magicblock-chainlink/src/remote_account_provider/mod.rs:1350-1353
Timestamp: 2025-12-03T09:36:01.527Z
Learning: Repo: magicblock-labs/magicblock-validator
File: magicblock-chainlink/src/remote_account_provider/mod.rs
Context: consolidate_fetched_remote_accounts
Learning: For unexpected result counts (>2), the project prefers logging an error and returning an empty Vec over panicking; acceptable during development per maintainer (Dodecahedr0x).
Applied to files:
magicblock-processor/tests/execution.rsmagicblock-config/src/types/mod.rsmagicblock-api/src/errors.rsmagicblock-aperture/src/state/subscriptions.rsmagicblock-aperture/tests/setup.rsmagicblock-aperture/src/processor.rs
📚 Learning: 2025-10-14T09:56:14.047Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 564
File: test-integration/programs/flexi-counter/src/processor/call_handler.rs:122-125
Timestamp: 2025-10-14T09:56:14.047Z
Learning: The file test-integration/programs/flexi-counter/src/processor/call_handler.rs contains a test smart contract used for integration testing, not production code.
Applied to files:
magicblock-processor/tests/execution.rsmagicblock-processor/tests/fees.rs
📚 Learning: 2025-11-07T13:09:52.253Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: test-kit/src/lib.rs:275-0
Timestamp: 2025-11-07T13:09:52.253Z
Learning: In test-kit, the transaction scheduler in ExecutionTestEnv is not expected to shut down during tests. Therefore, using `.unwrap()` in test helper methods like `schedule_transaction` is acceptable and will not cause issues in the test environment.
Applied to files:
magicblock-processor/tests/execution.rsmagicblock-processor/tests/fees.rsmagicblock-processor/tests/scheduling.rs
📚 Learning: 2025-11-21T10:22:07.520Z
Learnt from: taco-paco
Repo: magicblock-labs/magicblock-validator PR: 661
File: magicblock-committor-service/src/intent_executor/single_stage_executor.rs:20-28
Timestamp: 2025-11-21T10:22:07.520Z
Learning: In magicblock-committor-service's SingleStageExecutor and TwoStageExecutor (single_stage_executor.rs and two_stage_executor.rs), the fields transaction_strategy, junk, and patched_errors are intentionally public because these executors are designed to be used independently outside of the IntentExecutor scope, and callers need access to these execution reports for cleanup and error handling.
Applied to files:
magicblock-aperture/src/encoder.rsmagicblock-aperture/src/state/subscriptions.rsmagicblock-processor/src/executor/processing.rs
📚 Learning: 2025-10-21T14:00:54.642Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-aperture/src/requests/websocket/account_subscribe.rs:18-27
Timestamp: 2025-10-21T14:00:54.642Z
Learning: In magicblock-aperture account_subscribe handler (src/requests/websocket/account_subscribe.rs), the RpcAccountInfoConfig fields data_slice, commitment, and min_context_slot are currently ignored—only encoding is applied. This is tracked as technical debt in issue #579: https://github.com/magicblock-labs/magicblock-validator/issues/579
Applied to files:
magicblock-aperture/src/encoder.rsmagicblock-validator/src/main.rsmagicblock-aperture/src/state/subscriptions.rs
📚 Learning: 2025-11-07T14:20:31.457Z
Learnt from: thlorenz
Repo: magicblock-labs/magicblock-validator PR: 621
File: magicblock-chainlink/src/remote_account_provider/chain_pubsub_actor.rs:457-495
Timestamp: 2025-11-07T14:20:31.457Z
Learning: In magicblock-chainlink/src/remote_account_provider/chain_pubsub_client.rs, the unsubscribe closure returned by PubSubConnection::account_subscribe(...) resolves to () (unit), not a Result. Downstream code should not attempt to inspect an unsubscribe result and can optionally wrap it in a timeout to guard against hangs.
Applied to files:
magicblock-aperture/src/state/subscriptions.rsmagicblock-aperture/src/processor.rs
📚 Learning: 2025-12-01T16:02:05.367Z
Learnt from: thlorenz
Repo: magicblock-labs/magicblock-validator PR: 703
File: magicblock-chainlink/src/submux/mod.rs:652-654
Timestamp: 2025-12-01T16:02:05.367Z
Learning: In magicblock-chainlink/src/submux/mod.rs, the subscribe_program method intentionally adds program_id to program_subs before attempting the subscription. This ensures that even if the initial subscription fails or only partially succeeds across clients, the reconnection logic will retry the subscription. This is a deliberate design pattern for resilience in the multi-client architecture and should not be "fixed" to remove entries on failure.
Applied to files:
magicblock-aperture/src/state/subscriptions.rs
📚 Learning: 2025-11-07T13:20:13.793Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/coordinator.rs:227-238
Timestamp: 2025-11-07T13:20:13.793Z
Learning: In magicblock-processor's ExecutionCoordinator (scheduler/coordinator.rs), the `account_contention` HashMap intentionally does not call `shrink_to_fit()`. Maintaining slack capacity is beneficial for performance by avoiding frequent reallocations during high transaction throughput. As long as empty entries are removed from the map (which `clear_account_contention` does), the capacity overhead is acceptable.
Applied to files:
magicblock-aperture/src/state/subscriptions.rs
📚 Learning: 2025-11-18T08:47:39.702Z
Learnt from: Dodecahedr0x
Repo: magicblock-labs/magicblock-validator PR: 639
File: magicblock-chainlink/tests/04_redeleg_other_separate_slots.rs:158-165
Timestamp: 2025-11-18T08:47:39.702Z
Learning: In magicblock-chainlink tests involving compressed accounts, `set_remote_slot()` sets the slot of the `AccountSharedData`, while `compressed_account_shared_with_owner_and_slot()` sets the slot of the delegation record. These are two different fields and both calls are necessary.
Applied to files:
magicblock-aperture/src/state/subscriptions.rs
📚 Learning: 2025-11-04T10:48:00.070Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 589
File: magicblock-processor/src/scheduler/mod.rs:217-219
Timestamp: 2025-11-04T10:48:00.070Z
Learning: In magicblock-validator, the codebase uses a pattern where types containing non-Send/non-Sync fields (like Rc<RefCell<...>>) are marked with unsafe impl Send when they are guaranteed to be confined to a single thread through careful API design and thread spawning patterns.
Applied to files:
magicblock-aperture/src/state/subscriptions.rsmagicblock-aperture/src/processor.rs
📚 Learning: 2025-10-21T11:00:18.396Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 578
File: magicblock-aperture/src/encoder.rs:176-187
Timestamp: 2025-10-21T11:00:18.396Z
Learning: In the magicblock validator, the current slot is always the root slot. The SlotEncoder in magicblock-aperture/src/encoder.rs correctly sets `root: slot` because there is no lag between current and root slots in this architecture.
Applied to files:
magicblock-aperture/src/geyser.rs
📚 Learning: 2025-10-28T13:15:42.706Z
Learnt from: bmuddha
Repo: magicblock-labs/magicblock-validator PR: 596
File: magicblock-processor/src/scheduler.rs:1-1
Timestamp: 2025-10-28T13:15:42.706Z
Learning: In magicblock-processor, transaction indexes were always set to 0 even before the changes in PR #596. The proper transaction indexing within slots will be addressed during the planned ledger rewrite.
Applied to files:
magicblock-ledger/src/store/api.rsmagicblock-processor/src/executor/processing.rs
🧬 Code graph analysis (8)
magicblock-processor/tests/execution.rs (1)
tools/ledger-stats/src/utils.rs (1)
logs(6-16)
magicblock-processor/tests/fees.rs (1)
magicblock-committor-service/src/intent_executor/task_info_fetcher.rs (1)
signature(285-291)
magicblock-committor-program/src/state/chunks.rs (1)
magicblock-accounts-db/src/storage.rs (1)
offset(184-190)
magicblock-validator/src/main.rs (1)
magicblock-api/src/magic_validator.rs (1)
config(373-379)
magicblock-aperture/src/geyser.rs (2)
magicblock-core/src/link.rs (1)
link(60-89)magicblock-aperture/src/processor.rs (1)
new(59-79)
magicblock-aperture/tests/setup.rs (1)
magicblock-aperture/src/lib.rs (1)
initialize_aperture(14-25)
magicblock-aperture/src/processor.rs (1)
magicblock-aperture/src/geyser.rs (5)
new(35-84)notify_slot(115-123)notify_block(148-173)notify_account(86-113)notify_transaction(125-146)
magicblock-processor/src/executor/processing.rs (3)
magicblock-committor-service/src/intent_executor/task_info_fetcher.rs (1)
signature(285-291)test-integration/test-tools/src/integration_test_context.rs (1)
signature(66-68)magicblock-rpc-client/src/lib.rs (1)
error(201-203)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: run_make_ci_format
- GitHub Check: Build Project
- GitHub Check: run_make_ci_lint
- GitHub Check: run_make_ci_test
🔇 Additional comments (42)
magicblock-config/src/types/mod.rs (1)
9-9: LGTM!The expanded re-export of
RemotealongsideBindAddressaligns with the configuration restructuring in this PR, whereRemoteis now used more extensively for networking configuration.magicblock-validator/src/main.rs (1)
78-80: LGTM!The RPC host and port are now correctly sourced from the new
config.aperture.listenpath, consistent with the configuration restructuring in this PR.config.example.toml (2)
94-112: LGTM!The new
[aperture]section is well-documented with clear defaults, CLI arguments, and environment variable mappings. Thegeyser-pluginsfield appropriately omits CLI/Env mappings since complex list configurations are best handled via TOML.
114-116: Terminology update noted.The comment header update from "L2 -> L1" to "ER -> BASE" reflects the project's naming conventions.
magicblock-config/src/tests.rs (6)
68-68: LGTM!The default port assertion correctly uses the new
aperture.listenpath and verifies the expected default of 8899.
174-191: LGTM!Excellent test coverage for the non-destructive overlay behavior. The test properly verifies that:
- CLI-specified fields (
basefee,listen) are updated- TOML-specified fields (
keypair) are preserved- Default fields (
event_processors) remain at their defaults
374-374: LGTM!The assertion correctly validates that the example config's aperture listen port matches the documented value.
429-435: LGTM!Good addition of dedicated aperture section validation in the full coverage test, ensuring the example config properly sets the new configuration fields.
464-466: LGTM!The environment variable guards follow the established naming convention and properly test the new aperture configuration paths.
514-517: LGTM!The assertions correctly validate that the aperture configuration fields are properly populated from environment variables.
magicblock-api/src/errors.rs (1)
12-13: LGTM!The migration from
RpcErrorto the broaderApertureErrorumbrella type properly consolidates RPC and Geyser error handling under a unified variant. All references to the oldApiError::RpcErrorvariant have been successfully updated.magicblock-core/Cargo.toml (1)
24-24: LGTM!The new dependency is correctly added using workspace version and aligns with the
TransactionStatusMetausage inmagicblock-core/src/link/transactions.rsfor richer transaction status handling.magicblock-aperture/src/encoder.rs (2)
150-157: LGTM!The updated data access patterns correctly align with the new
TransactionStatusstructure, properly extracting logs frommeta.log_messagesand account keys fromtxn.message().account_keys().
167-172: LGTM!The signature and error extraction logic correctly adapts to the new
TransactionStatusstructure, properly dereferencing the signature and converting the transaction error to a string representation.magicblock-core/src/link/transactions.rs (2)
12-12: LGTM!Correctly imports
TransactionStatusMetafrom the standard Solana client types crate, ensuring compatibility with the Agave Geyser plugin interface.
59-65: Clean refactor ofTransactionStatusfor Geyser plugin support.The new structure provides the complete data needed by Geyser plugins: the full sanitized transaction (with signature accessible via
txn.signature()), standard metadata, and block-level indexing. This aligns well with Solana's notification patterns.magicblock-ledger/tests/common.rs (1)
34-36: LGTM!Correctly updated to pass the transaction by reference, aligning with the updated
write_transactionAPI signature. The returned index is appropriately ignored in this test helper since it's not needed.magicblock-processor/tests/fees.rs (4)
202-205: LGTM!Test correctly updated to access the transaction status through the new
status.meta.statuspath.
246-249: LGTM!Consistent with the updated access pattern for checking transaction failure status.
290-294: LGTM!Correctly updated to access the signature via
status.txn.signature()with proper dereferencing, and status viastatus.meta.status.
340-344: LGTM!Consistent with the other gasless mode test, correctly accessing signature and status through the new
TransactionStatusstructure.magicblock-processor/tests/scheduling.rs (2)
121-135: LGTM!The updates correctly adapt to the new
TransactionStatusstructure, usingstatus.txn.signature()andstatus.meta.statusinstead of the previous direct fields. The borrow pattern (using&Signaturefor set removal) is appropriate.
173-182: LGTM!The dereferencing of
status.txn.signature()to obtain an ownedSignaturefor the vector is correct. The status check viastatus.meta.statusis consistent with the refactored structure.magicblock-processor/tests/execution.rs (3)
14-14: LGTM!The
SVMMessageimport is correctly added to support callingaccount_keys()on the transaction, which is a method from this trait thatSanitizedTransactionimplements.
94-102: LGTM!The assertions are correctly updated to use the new field paths:
*status.txn.signature()for signature comparisonstatus.meta.log_messagesfor accessing transaction logs
126-143: LGTM!The iteration over transaction account keys is correctly updated to use
status.txn.account_keys().iter(), leveraging theSVMMessagetrait. The skip/take pattern remains unchanged, maintaining the original test logic.magicblock-aperture/src/state/subscriptions.rs (2)
11-14: LGTM!The import is appropriately simplified to bring in
TransactionStatusdirectly, which is now used as the primary type for transaction status updates.
191-202: LGTM!The refactored
send_signature_updatemethod correctly:
- Takes a single
&TransactionStatusparameter instead of separate signature and update objects- Extracts the signature via
update.txn.signature()for the subscription lookup- Sends the notification with
update.meta.status(the transaction result) andupdate.slotThis aligns well with the new
TransactionStatusstructure containingtxn,meta,index, andslotfields.magicblock-aperture/tests/setup.rs (3)
16-24: LGTM!The imports are correctly updated to include
initialize_aperturefrom the aperture crate andApertureConfigfrom the config crate, aligning with the new initialization API.
99-130: LGTM!The test setup correctly adapts to the new aperture initialization flow:
- Creates
ApertureConfigwith the listen address using struct update syntax for defaults- Uses
initialize_aperturewhich handles both event processor and server startup- Preserves the port-retry loop for test isolation in parallel test runs
134-140: WebSocket port offset convention is correct.The test setup properly uses
port + 1for the WebSocket endpoint, matching the server's actual binding behavior inJsonRpcServer::new()whereaddr.set_port(addr.port() + 1)is called before binding the WebSocket listener.magicblock-ledger/src/store/api.rs (2)
861-888: LGTM!The
write_transactionsignature improvements are well-designed:
- Taking
&SanitizedTransactionavoids unnecessary cloning at call sites- Returning
u32(the transaction slot index) enables callers to properly constructTransactionStatuswith the correct indexThe implementation correctly propagates the index from
write_transaction_status.
991-1043: LGTM!The
write_transaction_statusmethod now correctly returns the computedtransaction_slot_index. This enables the caller (write_transaction) to surface this value to higher-level code for inclusion inTransactionStatus.magicblock-aperture/src/geyser.rs (2)
86-113: LGTM!The
notify_accountmethod correctly constructsReplicaAccountInfoV3, checksaccount_data_notifications_enabled()before notifying each plugin, and handles the conversion properly. The TODO comment acknowledges the architectural limitation with transaction syncing.
125-146: LGTM!The
notify_transactionmethod correctly buildsReplicaTransactionInfoV2fromTransactionStatus, checkstransaction_notifications_enabled()per plugin, and properly wraps in versioned types before calling the plugin interface.magicblock-aperture/src/processor.rs (3)
116-126: LGTM!The geyser notifications for slot and block updates are correctly integrated. Notifying the slot before the block is logical ordering. Error handling via
inspect_errwith warning logs appropriately prevents plugin failures from blocking core event processing.
134-137: LGTM!Geyser account notifications are correctly placed after subscription notifications, maintaining the existing notification flow while adding plugin support.
148-158: LGTM!The transaction notification integration correctly forwards the status to geyser plugins before updating the local cache, which ensures plugins receive data promptly. The updated
SignatureResultconstruction usingstatus.meta.statusand*status.txn.signature()aligns with the newTransactionStatusstructure.magicblock-processor/src/executor/processing.rs (4)
6-7: LGTM!Import reorganization correctly reflects the new types used:
TransactionSimulationResultreplaces the previous result type, andTxnSimulationResultTxis retained for simulation channel communication.
223-244: LGTM!The refactored
commit_transactionmethod correctly:
- Handles ledger write errors early with appropriate logging
- Constructs
TransactionStatuswith the new structure (slot,index,txn,meta)- Sends the status only after successful ledger write
The TODO on
meta.clone()is acknowledged and appropriately deferred to the ledger rewrite.
260-268: LGTM!The signature change to pass
&txninstead of consumingtxnis consistent withcommit_transactionand aligns with the updated ledger API.
131-146: LGTM!The
TransactionSimulationResultconstruction correctly extracts all execution details (result, units consumed, logs, return data, inner instructions) for both successful and failed paths, providing the complete data needed forsimulateTransactionRPC responses.
| let path = config | ||
| .get("libpath") | ||
| .ok_or(GeyserPluginError::ConfigFileReadError { | ||
| msg: | ||
| "Plugin configuration file doesn't contain `path` field" | ||
| .into(), | ||
| })? | ||
| .as_str() | ||
| .ok_or(GeyserPluginError::ConfigFileReadError { | ||
| msg: | ||
| "The `path` field in the configuration must be a string" | ||
| .into(), | ||
| })?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message inconsistent with actual field name.
The code retrieves the "libpath" field but the error message refers to "path". This mismatch will confuse operators debugging configuration issues.
🔎 Proposed fix
let path = config
.get("libpath")
.ok_or(GeyserPluginError::ConfigFileReadError {
msg:
- "Plugin configuration file doesn't contain `path` field"
+ "Plugin configuration file doesn't contain `libpath` field"
.into(),
})?
.as_str()
.ok_or(GeyserPluginError::ConfigFileReadError {
msg:
- "The `path` field in the configuration must be a string"
+ "The `libpath` field in the configuration must be a string"
.into(),
})?;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let path = config | |
| .get("libpath") | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "Plugin configuration file doesn't contain `path` field" | |
| .into(), | |
| })? | |
| .as_str() | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "The `path` field in the configuration must be a string" | |
| .into(), | |
| })?; | |
| let path = config | |
| .get("libpath") | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "Plugin configuration file doesn't contain `libpath` field" | |
| .into(), | |
| })? | |
| .as_str() | |
| .ok_or(GeyserPluginError::ConfigFileReadError { | |
| msg: | |
| "The `libpath` field in the configuration must be a string" | |
| .into(), | |
| })?; |
This PR introduces a mechanism to load and use agave geyser plugins from shared libraries. The implementation simply hooks into aperture event processing and invokes loaded plugins from there.
For now only account and slot updates are supported, with transaction and block updates are scheduled to be added after magicblock-ledger crate has been rewritten.
Summary by CodeRabbit
Release Notes
New Features
Improvements
[aperture]section for centralized RPC and event processor settings.✏️ Tip: You can customize this high-level summary in your review settings.