Migrate spawned-concurrency from 0.4 to 0.5.0-rc#185
Conversation
Replace the GenServer pattern with the new protocol/actor macro API: - Define #[protocol] trait BlockChainProtocol with send-only methods - Replace impl GenServer + CastMessage enum with #[actor] impl + #[send_handler]s - GenServerHandle<T> → ActorRef<T>, send_after takes Context instead of handle - notify_* methods become synchronous (&self) since ActorRef::send() is non-blocking - Remove .await from all P2P call sites accordingly - Remove unused spawned-concurrency and spawned-rt deps from bin/ethlambda
🤖 Kimi Code ReviewReview SummaryThe PR migrates the codebase from Issues Found1. Race Condition in Tick Scheduling (Critical)File: The new
Fix: Calculate the next tick time at the beginning of the tick handler, before processing, to maintain more accurate timing. 2. Unnecessary Async in Notify Methods (Minor)File: The notify methods ( Fix: Remove the
3. Error Handling Inconsistency (Medium)File: The error handling pattern changed from Fix: Update error messages to reflect synchronous nature, e.g., "Failed to send message to blockchain actor". 4. Protocol Trait Visibility (Minor)File: The Fix: Consider if Positive Changes
Security ConsiderationsNo security vulnerabilities introduced. The changes are purely mechanical API updates without affecting consensus logic or cryptographic operations. Performance ImpactMinimal performance impact. The change from async to sync notify methods might slightly reduce overhead, but the difference is negligible for the message volumes expected. Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code Review
No direct correctness regressions found in fork-choice/attestation/state-transition logic in this diff; the Rust changes in crates/blockchain/src/lib.rs look behavior-preserving for message handling/tick scheduling. I couldn’t run Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewNow I have enough context to write a thorough review. Review: Migrate spawned-concurrency from 0.4 to 0.5.0-rcOverall this is a clean, mechanical migration. The behavior is preserved and the new API is used correctly. A few items worth noting: Cargo / DependenciesDual spawned-concurrency versions in the binary
Git tag version mismatch The workspace pins
|
Greptile SummaryThis PR migrates The actor migration is correct: all four message variants ( One stale documentation reference remains in the Confidence Score: 5/5
Sequence DiagramsequenceDiagram
participant P2P as P2P Layer<br/>(gossipsub/req_resp)
participant BC as BlockChain<br/>(ActorRef wrapper)
participant BCS as BlockChainServer<br/>(Actor)
participant Timer as send_after / Timer
Note over P2P,BCS: New block / attestation flow (sync, no .await)
P2P->>BC: notify_new_block(block) [fn &self]
BC->>BCS: handle.new_block(block) → Result<(), ActorError>
BCS-->>BC: Ok(())
BC-->>P2P: (returns immediately)
Note over BCS,Timer: Self-rescheduling tick pattern
Timer->>BCS: handle_tick(_msg: Tick, ctx)
BCS->>BCS: on_tick(timestamp_ms)
BCS->>Timer: send_after(ms_to_next_interval, ctx.clone(), Tick)
Note over BC,BCS: Spawn / initial tick scheduling
BC->>BCS: BlockChainServer { ... }.start() → ActorRef
BC->>Timer: send_after(time_until_genesis, handle.context(), Tick)
|
Motivation
The
spawned-concurrencycrate is releasing v0.5.0-rc with a new protocol/actor macro API that replaces the GenServer pattern. This migration adopts the new API, which provides:#[protocol]traits instead of manual enum matching#[send_handler]instead ofhandle_cast+CastResponse::NoReplyActorRef::send()returnsResult<(), ActorError>immediately (no.await)send_aftertakesContext<A>instead of cloning the handleDescription
Dependency changes
Cargo.toml(workspace)spawned-concurrencyandspawned-rt:"0.4"→ git dep atlambdaclass/spawned.gittagv0.5.0-rcbin/ethlambda/Cargo.tomlspawned-concurrencyandspawned-rt(unused — binary only usesBlockChainfromethlambda_blockchain)Actor migration (
crates/blockchain/src/lib.rs)Before (0.4 — GenServer pattern):
CastMessageenum with 4 variants (NewBlock,NewAttestation,NewAggregatedAttestation,Tick)impl GenServer for BlockChainServerwithhandle_castmatching on the enumGenServerHandle<BlockChainServer>for the wrapperhandle.cast(CastMessage::X).awaitfor sending messagessend_after(duration, handle.clone(), CastMessage::Tick)for timer schedulingAfter (0.5 — Protocol/Actor pattern):
#[protocol] trait BlockChainProtocoldefining 4 send-only methods#[actor(protocol = BlockChainProtocol)] impl BlockChainServerwith individual#[send_handler]methodsActorRef<BlockChainServer>for the wrapperhandle.new_block(block)(sync, returnsResult<(), ActorError>)send_after(duration, ctx.clone(), block_chain_protocol::Tick)for timer schedulingThe
#[protocol]macro generates ablock_chain_protocolmodule with message structs (Tick,NewBlock,NewAttestation,NewAggregatedAttestation) and implements the protocol methods directly onActorRef<BlockChainServer>.Wrapper simplification
notify_*methods onBlockChainchanged fromasync fn(&mut self)tofn(&self):.awaitneeded — sends are non-blocking&selfinstead of&mut self—ActorRef::send()takes&selfP2P call site updates
Removed
.awaitfrom all 4 call sites in the P2P layer:crates/net/p2p/src/gossipsub/handler.rsnotify_new_blockcrates/net/p2p/src/gossipsub/handler.rsnotify_new_aggregated_attestationcrates/net/p2p/src/gossipsub/handler.rsnotify_new_attestationcrates/net/p2p/src/req_resp/handlers.rsnotify_new_blockMigration guide improvements
While migrating, we identified several gaps in the upstream
spawned-concurrencymigration documentation. These are suggestions for thespawnedmaintainers:1.
send_after/send_intervalAPI changes (undocumented)The migration guide doesn't mention that
send_afterchanged from takingGenServerHandle<A>toContext<A>. Every user of timers will hit this.2. Self-rescheduling tick pattern (undocumented)
A very common pattern is an actor that reschedules itself with variable delays. The guide should show this before/after:
3.
ActorRef::context()method (undocumented)The guide shows
ActorRefbut doesn't document thatactor_ref.context()converts anActorRef<A>to aContext<A>. This is needed when scheduling timers from outside a handler (e.g., inspawn()).4.
&mut self→&selffor send callers (undocumented)Switching from
.cast().await(async) to direct protocol method calls (syncResult<(), ActorError>) means callers no longer need&mut self. This can simplify wrapper structs and their call sites significantly.5. Generated module naming convention (incomplete)
The guide mentions generated structs but doesn't explicitly state the naming rule:
#[protocol]ontrait FooBarProtocolgenerates modulefoo_bar_protocol(snake_case)foo_bar_protocol::MethodName(PascalCase of method name)FooBarProtocolRef = Arc<dyn FooBarProtocol>6. Mixed send + request protocols (missing example)
The guide shows send-only and request-only examples separately. A before/after for a protocol with both send and request methods would be helpful.
7.
#[handler]vs#[send_handler]vs#[request_handler](unclear)The "Escape Hatches" section mentions
Handler<M>but doesn't explain the difference between the three handler attributes:#[request_handler]— for request-response methods#[send_handler]— for fire-and-forget methods#[handler]— for manualMessagetypes not in a protocol (?)8. Wrapper pattern migration (missing)
A common pattern is wrapping
ActorRefin a public struct to hide internals. The guide should show this before/after:9.
spawned-rtchanges (undocumented)The migration guide focuses on
spawned-concurrencybut doesn't mention whetherspawned-rthas breaking changes.10.
#[started]panic behavior (unclear)In 0.4,
.start()returned the handle directly. In 0.5,ActorStart::start()also returnsActorRef<A>directly, but what happens if the#[started]hook panics? The guide should clarify whether subsequent sends getActorError::ActorStopped.How to test
All pass locally. For end-to-end verification, run a local devnet:
Verify that: