Conversation
📝 WalkthroughWalkthroughAdds sender-type-based payload verification and a wire-encoded TxContext model, dynamic payload verifiers (including Ed25519 test verifier), and a Transaction post-bootstrap hook; updates gateway, verifier registry, mempool/TxContext, EOA mapping, tests, and integration wiring. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Gateway as EthGateway
participant Registry as VerifierRegistry
participant Verifier as SignatureVerifierDyn
participant Mempool
Client->>Gateway: submit raw tx (wire-encoded or envelope)
alt Wire-encoded Custom
Gateway->>Gateway: detect wire magic, decode TxContext -> TxPayload::Custom
Gateway->>Registry: verify_payload(sender_type, TxPayload::Custom)
else EOA Envelope
Gateway->>Gateway: decode TxEnvelope -> TxPayload::Eoa
Gateway->>Registry: verify_payload(EOA_SECP256K1, TxPayload::Eoa)
end
Registry->>Verifier: verify(&payload)
alt Verified
Verifier-->>Registry: Ok(())
Registry-->>Gateway: Ok(verified)
Gateway->>Mempool: submit verified TxContext
Mempool-->>Client: accepted
else Invalid / Unsupported
Verifier-->>Registry: Err
Registry-->>Gateway: Err(GatewayError)
Gateway-->>Client: reject
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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.
Actionable comments posted: 1
🧹 Nitpick comments (13)
docker-compose.testapp.yml (1)
2-26: Consider adding a health check and restart policy.For improved reliability during development and CI testing, you may want to add:
healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8545"] interval: 10s timeout: 5s retries: 3 restart: unless-stoppedThis helps with container orchestration and ensures the service recovers from transient failures.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker-compose.testapp.yml` around lines 2 - 26, Add a container healthcheck and restart policy to the testapp service in docker-compose.testapp.yml: under the service named "testapp" add a healthcheck that uses a CMD curl -f against http://localhost:8545 with interval 10s, timeout 5s and retries 3, and set restart to "unless-stopped" so the container is automatically restarted on transient failures; ensure these keys are placed alongside existing "ports", "environment", and "volumes" entries for the testapp service.bin/testapp/tests/mempool_e2e.rs (4)
577-581: Hardcoded nonce limits test reusability.The nonce is hardcoded to
0inEd25519AuthPayload. While this works for the single-transaction test, consider parameterizing it inEd25519CustomTxBuildInputfor future multi-transaction test scenarios.Suggested enhancement
struct Ed25519CustomTxBuildInput<'a> { tx_template_signer: &'a SigningKey, auth_signer: &'a Ed25519SigningKey, chain_id: u64, token_address: Address, recipient_account_id: AccountId, transfer_amount: u128, sender_account_id: AccountId, + nonce: u64, }Then use
input.nonceinstead of the literal0.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 577 - 581, Ed25519AuthPayload is being constructed with a hardcoded nonce (0); change the code to take the nonce from the transaction builder input instead. Update the builder/input struct (Ed25519CustomTxBuildInput) to include a nonce field if it doesn't exist, pass that input through to the test, and replace the literal 0 with input.nonce when constructing auth_payload in the test where Ed25519AuthPayload and auth_payload are created so multi-transaction tests can supply different nonces.
802-806: Clarify the scope of the local verifier registry.The
SignatureVerifierRegistrycreated here is used only to verify the payload before submission, but it's not integrated into the actual block execution path (the STF). This is fine for testing the verifier logic in isolation, but consider adding a comment clarifying that the production path would need the registry configured in the STF/gateway.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 802 - 806, The local SignatureVerifierRegistry created here (SignatureVerifierRegistry::new(), registry.register_dyn(sender_types::CUSTOM, Ed25519PayloadVerifier), and the call to registry.verify_payload(sender_types::CUSTOM, tx_context.payload())) is only being used to test the verifier logic prior to submission and is not wired into the STF/block execution path; add a concise comment above this block stating that this registry is local to the test and that in production the verifier registry must be configured and injected into the STF/gateway so the block execution path uses the same verifiers.
91-105: Error code collision withEd25519PayloadVerifier.Error codes 0x71-0x74 defined here overlap with those used (or suggested) in
Ed25519PayloadVerifier(lines 59-77). If both components are used in the same error path, distinguishing the failure source becomes difficult.Consider using a separate range for account-level errors vs verifier-level errors, or documenting the error code allocation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 91 - 105, The error codes 0x71–0x74 in the functions err_invalid_auth_payload, err_nonce_mismatch, err_invalid_public_key, and err_invalid_signature collide with those used by Ed25519PayloadVerifier; change these account-level error functions to return codes from a distinct, non-overlapping range (e.g., bump to 0x80–0x83 or define an ACCOUNT_ERROR_BASE constant) and update any tests/comments accordingly so verifier vs account errors are unambiguous, or alternatively add a clear documented allocation comment that reserves 0x71–0x74 for verifier errors and reassign account errors to a different range.
56-79: Consider using distinct error codes for different failure modes.Lines 59, 62, 64, 65, 70, and 71 all return
ErrorCode::new(0x75)for different error conditions (invalid payload type, Borsh decode failures, missing invoke request, encode failure). This makes debugging difficult when errors occur.Suggested distinct error codes
impl SignatureVerifierDyn for Ed25519PayloadVerifier { fn verify(&self, payload: &TxPayload) -> SdkResult<()> { let TxPayload::Custom(bytes) = payload else { - return Err(ErrorCode::new(0x75)); + return Err(ErrorCode::new(0x70)); // Invalid payload type }; let intent: EthIntentPayload = - borsh::from_slice(bytes).map_err(|_| ErrorCode::new(0x75))?; + borsh::from_slice(bytes).map_err(|_| ErrorCode::new(0x71))?; // Intent decode error let decoded: Ed25519EthIntentProof = - borsh::from_slice(&intent.auth_proof).map_err(|_| ErrorCode::new(0x75))?; + borsh::from_slice(&intent.auth_proof).map_err(|_| ErrorCode::new(0x72))?; // Proof decode error let envelope = intent.decode_envelope().map_err(|_| ErrorCode::new(0x75))?; let invoke_request = envelope .to_invoke_requests() .into_iter() .next() - .ok_or_else(|| ErrorCode::new(0x75))?; - let request_digest = keccak256(&invoke_request.encode().map_err(|_| ErrorCode::new(0x75))?); + .ok_or_else(|| ErrorCode::new(0x73))?; // No invoke request + let request_digest = keccak256(&invoke_request.encode().map_err(|_| ErrorCode::new(0x74))?);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 56 - 79, The verify implementation in Ed25519PayloadVerifier (impl SignatureVerifierDyn for Ed25519PayloadVerifier -> fn verify) collapses multiple failure modes into ErrorCode::new(0x75), hindering debugging; update each failure site (the TxPayload match arm for TxPayload::Custom, both borsh::from_slice calls for EthIntentPayload and Ed25519EthIntentProof, intent.decode_envelope(), to_invoke_requests().next() missing-case, and invoke_request.encode()) to return distinct ErrorCode values (e.g., 0x75..0x7A) so callers can distinguish invalid payload type, intent decode error, auth_proof decode error, envelope decode error, missing invoke request, and encode error respectively; change the Err(...) and map_err(...) invocations to use the new unique ErrorCode::new(...) values while keeping existing shapes and mapping locations intact (match arm, borsh::from_slice map_err, decode_envelope map_err, ok_or_else for next, encode map_err).docker/evd/Dockerfile (1)
9-12: The fallbackrustup toolchain installwithout arguments may not work as intended.When
rustup show active-toolchainfails (toolchain not installed), the fallbackrustup toolchain installwithout specifying a toolchain name won't install anything meaningful. The command requires either a toolchain name argument or should rely on rustup's automatic toolchain installation.Consider using
rustup showwhich triggers auto-install fromrust-toolchain.toml:Proposed fix
-RUN rustup show active-toolchain || rustup toolchain install +RUN rustup showAlternatively, if you want explicit control:
RUN rustup toolchain install "$(cat rust-toolchain.toml | grep channel | cut -d'"' -f2)"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docker/evd/Dockerfile` around lines 9 - 12, The fallback RUN line using "rustup show active-toolchain || rustup toolchain install" won't install a specific toolchain; update it to trigger auto-install from rust-toolchain.toml by replacing the fallback with a single "rustup show" (which consults rust-toolchain.toml) or explicitly install the channel parsed from rust-toolchain.toml; locate the COPY rust-toolchain.toml ./rust-toolchain.toml and the RUN rustup show active-toolchain || rustup toolchain install lines and either call rustup show alone or extract the "channel" from rust-toolchain.toml and pass it to rustup toolchain install so a concrete toolchain is installed.crates/app/tx/eth/src/eoa_registry.rs (1)
185-213: Potential inconsistent mapping state when forward mapping exists but reverse is missing.In
ensure_eoa_mapping, when the forward mapping (address -> account_id) exists and matches, but the reverse mapping (account_id -> address) does not exist (line 197 returnsNone), the function falls through to line 205 which checks only the reverse mapping. If the reverse doesn't exist, it creates both mappings viaset_mapping.However, at line 201, if the forward exists and matches, but the reverse also exists and matches, it returns
Ok(())early. This is correct.The issue is at line 202: if
lookup_address_in_envreturnsNone(reverse doesn't exist), the code exits the outerifblock and falls to line 205. This appears to be intentional fall-through behavior, but the nested structure makes it harder to follow.Consider simplifying the logic for clarity:
♻️ Suggested clearer structure
pub fn ensure_eoa_mapping( address: Address, account_id: AccountId, env: &mut dyn Environment, ) -> SdkResult<()> { - if let Some(existing) = lookup_account_id_in_env(address, env)? { - if existing != account_id { - return Err(ERR_ADDRESS_ACCOUNT_CONFLICT); - } - if let Some(existing_addr) = lookup_address_in_env(account_id, env)? { - if existing_addr != address { - return Err(ERR_ADDRESS_ACCOUNT_CONFLICT); - } - return Ok(()); - } - } - - if let Some(existing_addr) = lookup_address_in_env(account_id, env)? { - if existing_addr != address { - return Err(ERR_ADDRESS_ACCOUNT_CONFLICT); - } - return set_mapping(address, account_id, env); - } - - set_mapping(address, account_id, env) + let forward = lookup_account_id_in_env(address, env)?; + let reverse = lookup_address_in_env(account_id, env)?; + + // Validate consistency of any existing mappings + if let Some(existing_id) = forward { + if existing_id != account_id { + return Err(ERR_ADDRESS_ACCOUNT_CONFLICT); + } + } + if let Some(existing_addr) = reverse { + if existing_addr != address { + return Err(ERR_ADDRESS_ACCOUNT_CONFLICT); + } + } + + // If both mappings already exist and are consistent, nothing to do + if forward.is_some() && reverse.is_some() { + return Ok(()); + } + + // Create or complete the mapping + set_mapping(address, account_id, env) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/eoa_registry.rs` around lines 185 - 213, The current branching in ensure_eoa_mapping is confusing and can be simplified: first call lookup_account_id_in_env(address) and if Some(existing) and existing != account_id return ERR_ADDRESS_ACCOUNT_CONFLICT; then call lookup_address_in_env(account_id) and if Some(existing_addr) and existing_addr != address return ERR_ADDRESS_ACCOUNT_CONFLICT; if both lookups returned Some and they match return Ok(()); otherwise call set_mapping(address, account_id, env). Update ensure_eoa_mapping to use lookup_account_id_in_env, lookup_address_in_env, ERR_ADDRESS_ACCOUNT_CONFLICT, and set_mapping in that order to make the logic linear and avoid the nested fall-through.crates/app/tx/eth/src/gateway.rs (2)
163-184: Error mapping may obscure the actual failure reason.When
verify_payloadfails, the error is mapped by inspecting the chain ID post-hoc (lines 170-179). If the signature is invalid and the chain ID is wrong, this returnsInvalidChainIdrather thanInvalidSignature. This could mislead debugging efforts.Consider verifying chain ID before signature verification for clearer error semantics:
♻️ Suggested refactor
pub fn verify_envelope(&self, envelope: TxEnvelope) -> Result<TxContext, GatewayError> { + // Validate chain ID first for clearer error reporting + match envelope.chain_id() { + Some(id) if id != self.chain_id => { + return Err(GatewayError::InvalidChainId { + expected: self.chain_id, + actual: Some(id), + }); + } + None => { + return Err(GatewayError::InvalidChainId { + expected: self.chain_id, + actual: None, + }); + } + _ => {} + } + let payload = TxPayload::Eoa(Box::new(envelope.clone())); - // Verify chain ID and signature self.verifier .verify_payload(sender_type::EOA_SECP256K1, &payload) - .map_err(|_| match envelope.chain_id() { - Some(id) if id != self.chain_id => GatewayError::InvalidChainId { - expected: self.chain_id, - actual: Some(id), - }, - None => GatewayError::InvalidChainId { - expected: self.chain_id, - actual: None, - }, - _ => GatewayError::InvalidSignature, - })?; + .map_err(|_| GatewayError::InvalidSignature)?;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/gateway.rs` around lines 163 - 184, In verify_envelope, check the envelope chain ID against self.chain_id before calling self.verifier.verify_payload so chain-id mismatches return GatewayError::InvalidChainId deterministically; only if the chain ID matches (or is absent if that’s acceptable) proceed to call verify_payload and map its failure to GatewayError::InvalidSignature (or propagate the existing mapping for None/Some mismatches handled earlier); keep the final TxContext::new(envelope, self.base_fee).ok_or(GatewayError::ContractCreationNotSupported) unchanged.
187-218: Consider adding tests for new public API methods.The new
register_payload_verifier,supports_sender_type, andverify_contextmethods lack direct test coverage. While the underlying registry is tested separately, gateway-level integration tests would help ensure the wiring is correct.Would you like me to generate test cases for the new gateway methods?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/gateway.rs` around lines 187 - 218, Add integration tests in the existing tests module for EthGateway that exercise the new public API: call register_payload_verifier on an EthGateway instance to register a mock verifier and assert it is accepted (and errors handled), call supports_sender_type with known supported/unsupported sender types and assert the boolean result, and call verify_context with valid and invalid contexts to assert Ok and Err(GatewayError::VerifyFailed(_)) outcomes; use EthGateway::new or ::with_base_fee to construct, and reference the methods register_payload_verifier, supports_sender_type, and verify_context so the tests validate the gateway-level wiring rather than only the registry unit tests.crates/app/tx/eth/src/mempool.rs (3)
498-501: Envelope decode usesbase_fee=0, affectingeffective_gas_price.When decoding a raw envelope (non-wire format),
base_fee=0is used (line 500). For EIP-1559 transactions, this affects the calculatedeffective_gas_price. If this is intentional (e.g., for replay/recovery where ordering doesn't matter), consider adding a brief comment.+ // Envelope-only decode path uses zero base fee; effective_gas_price + // is recalculated for mempool insertion in decode_and_verify. let envelope = TxEnvelope::decode(bytes)?; TxContext::new(envelope, 0).ok_or(ERR_RECIPIENT_REQUIRED)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/mempool.rs` around lines 498 - 501, The TxEnvelope::decode + TxContext::new call currently constructs the TxContext with base_fee=0 which changes effective_gas_price calculation for EIP-1559 transactions; update the code near TxEnvelope::decode / TxContext::new to either compute/pass the correct base_fee derived from the envelope or (if zero is intentional for replay/recovery) add a concise comment explaining why base_fee=0 is used and the implication for effective_gas_price and ordering; reference TxEnvelope::decode, TxContext::new, effective_gas_price and ERR_RECIPIENT_REQUIRED when making the change so reviewers can quickly find and verify the intent.
330-336:recipient()returnsAccountId::invalid()forNoneresolution.When
recipient_resolutionisNone,recipient()returnsAccountId::invalid(). This sentinel value could propagate silently and cause subtle bugs if callers don't check for it.Consider whether this should panic (like
sender_address()) or whetherAccountId::invalid()is a well-defined sentinel that callers are expected to handle.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/mempool.rs` around lines 330 - 336, The recipient() method currently returns AccountId::invalid() for RecipientResolution::None which can silently propagate; change it to mirror sender_address() behavior and fail loudly: replace the None branch to panic with a clear message (e.g. using panic! or expect on an Option) instead of returning AccountId::invalid(), so callers don't receive a silent sentinel; update the RecipientResolution::None handling in recipient() (and ensure any tests/call sites reflect the new panic semantics) while keeping the Account and EoaAddress branches using the existing account and derive_eth_eoa_account_id(address) logic.
236-243: Panic-based accessors may cause runtime failures.
sender_address()(line 240) andenvelope()(line 272) panic when called on incompatible payload types. While_optvariants exist, panics in library code can be surprising to callers.Consider either:
- Documenting these as
#[doc(hidden)]or internal-only- Returning
Resultinstead of panicking- Adding debug assertions in tests to catch misuse early
The current approach is acceptable if callers are expected to check
sender_type()orpayload()first, but the panic messages should be more actionable.Also applies to: 268-274
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/mempool.rs` around lines 236 - 243, The accessor methods sender_address() and envelope() currently panic on incompatible SenderResolution/Payload types which can cause runtime failures; change them to return Result<T, Error> (e.g., Result<Address, MyError> / Result<EnvelopeType, MyError>) instead of panicking so callers must handle the mismatch, update call sites to propagate or handle the error, and add a clear, actionable error variant/message indicating the expected variant and the actual variant; alternatively, if these are truly internal-only, mark them #[doc(hidden)] and replace the panic text with a more descriptive message and/or add debug_assert! checks (but prefer the Result approach for public APIs).crates/app/tx/eth/src/verifier/registry.rs (1)
90-96: Backward-compatibleverifyallocates unnecessarily.The
verifymethod creates a newBoxallocation for every call viaBox::new(tx.clone()). For high-throughput scenarios, consider whether callers can migrate toverify_payloaddirectly.This is acceptable for a transitional compatibility shim, but the allocation overhead should be documented or the method marked as deprecated if migration is expected.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/verifier/registry.rs` around lines 90 - 96, The compatibility shim register::verify currently allocates by cloning the TxEnvelope into a Box for every call (Box::new(tx.clone()) passed as TxPayload::Eoa) — mark this method as deprecated and document the allocation cost so callers migrate to verify_payload; specifically add a #[deprecated(...)] attribute to the verify function, update its doc comment to state it performs a heap allocation (via TxPayload::Eoa(Box::new(...))) and clearly recommend callers call verify_payload(sender_type::EOA_SECP256K1, &TxPayload::Eoa(...)) directly to avoid the clone/Box allocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/rpc/evnode/src/service.rs`:
- Line 13: The import list in service.rs includes an unused symbol MempoolTx
from evolve_mempool; remove MempoolTx from the use statement (leave Mempool and
SharedMempool) so the compiler warning goes away and imports only used symbols;
update the line containing "use evolve_mempool::{Mempool, MempoolTx,
SharedMempool};" to exclude MempoolTx.
---
Nitpick comments:
In `@bin/testapp/tests/mempool_e2e.rs`:
- Around line 577-581: Ed25519AuthPayload is being constructed with a hardcoded
nonce (0); change the code to take the nonce from the transaction builder input
instead. Update the builder/input struct (Ed25519CustomTxBuildInput) to include
a nonce field if it doesn't exist, pass that input through to the test, and
replace the literal 0 with input.nonce when constructing auth_payload in the
test where Ed25519AuthPayload and auth_payload are created so multi-transaction
tests can supply different nonces.
- Around line 802-806: The local SignatureVerifierRegistry created here
(SignatureVerifierRegistry::new(), registry.register_dyn(sender_types::CUSTOM,
Ed25519PayloadVerifier), and the call to
registry.verify_payload(sender_types::CUSTOM, tx_context.payload())) is only
being used to test the verifier logic prior to submission and is not wired into
the STF/block execution path; add a concise comment above this block stating
that this registry is local to the test and that in production the verifier
registry must be configured and injected into the STF/gateway so the block
execution path uses the same verifiers.
- Around line 91-105: The error codes 0x71–0x74 in the functions
err_invalid_auth_payload, err_nonce_mismatch, err_invalid_public_key, and
err_invalid_signature collide with those used by Ed25519PayloadVerifier; change
these account-level error functions to return codes from a distinct,
non-overlapping range (e.g., bump to 0x80–0x83 or define an ACCOUNT_ERROR_BASE
constant) and update any tests/comments accordingly so verifier vs account
errors are unambiguous, or alternatively add a clear documented allocation
comment that reserves 0x71–0x74 for verifier errors and reassign account errors
to a different range.
- Around line 56-79: The verify implementation in Ed25519PayloadVerifier (impl
SignatureVerifierDyn for Ed25519PayloadVerifier -> fn verify) collapses multiple
failure modes into ErrorCode::new(0x75), hindering debugging; update each
failure site (the TxPayload match arm for TxPayload::Custom, both
borsh::from_slice calls for EthIntentPayload and Ed25519EthIntentProof,
intent.decode_envelope(), to_invoke_requests().next() missing-case, and
invoke_request.encode()) to return distinct ErrorCode values (e.g., 0x75..0x7A)
so callers can distinguish invalid payload type, intent decode error, auth_proof
decode error, envelope decode error, missing invoke request, and encode error
respectively; change the Err(...) and map_err(...) invocations to use the new
unique ErrorCode::new(...) values while keeping existing shapes and mapping
locations intact (match arm, borsh::from_slice map_err, decode_envelope map_err,
ok_or_else for next, encode map_err).
In `@crates/app/tx/eth/src/eoa_registry.rs`:
- Around line 185-213: The current branching in ensure_eoa_mapping is confusing
and can be simplified: first call lookup_account_id_in_env(address) and if
Some(existing) and existing != account_id return ERR_ADDRESS_ACCOUNT_CONFLICT;
then call lookup_address_in_env(account_id) and if Some(existing_addr) and
existing_addr != address return ERR_ADDRESS_ACCOUNT_CONFLICT; if both lookups
returned Some and they match return Ok(()); otherwise call set_mapping(address,
account_id, env). Update ensure_eoa_mapping to use lookup_account_id_in_env,
lookup_address_in_env, ERR_ADDRESS_ACCOUNT_CONFLICT, and set_mapping in that
order to make the logic linear and avoid the nested fall-through.
In `@crates/app/tx/eth/src/gateway.rs`:
- Around line 163-184: In verify_envelope, check the envelope chain ID against
self.chain_id before calling self.verifier.verify_payload so chain-id mismatches
return GatewayError::InvalidChainId deterministically; only if the chain ID
matches (or is absent if that’s acceptable) proceed to call verify_payload and
map its failure to GatewayError::InvalidSignature (or propagate the existing
mapping for None/Some mismatches handled earlier); keep the final
TxContext::new(envelope,
self.base_fee).ok_or(GatewayError::ContractCreationNotSupported) unchanged.
- Around line 187-218: Add integration tests in the existing tests module for
EthGateway that exercise the new public API: call register_payload_verifier on
an EthGateway instance to register a mock verifier and assert it is accepted
(and errors handled), call supports_sender_type with known supported/unsupported
sender types and assert the boolean result, and call verify_context with valid
and invalid contexts to assert Ok and Err(GatewayError::VerifyFailed(_))
outcomes; use EthGateway::new or ::with_base_fee to construct, and reference the
methods register_payload_verifier, supports_sender_type, and verify_context so
the tests validate the gateway-level wiring rather than only the registry unit
tests.
In `@crates/app/tx/eth/src/mempool.rs`:
- Around line 498-501: The TxEnvelope::decode + TxContext::new call currently
constructs the TxContext with base_fee=0 which changes effective_gas_price
calculation for EIP-1559 transactions; update the code near TxEnvelope::decode /
TxContext::new to either compute/pass the correct base_fee derived from the
envelope or (if zero is intentional for replay/recovery) add a concise comment
explaining why base_fee=0 is used and the implication for effective_gas_price
and ordering; reference TxEnvelope::decode, TxContext::new, effective_gas_price
and ERR_RECIPIENT_REQUIRED when making the change so reviewers can quickly find
and verify the intent.
- Around line 330-336: The recipient() method currently returns
AccountId::invalid() for RecipientResolution::None which can silently propagate;
change it to mirror sender_address() behavior and fail loudly: replace the None
branch to panic with a clear message (e.g. using panic! or expect on an Option)
instead of returning AccountId::invalid(), so callers don't receive a silent
sentinel; update the RecipientResolution::None handling in recipient() (and
ensure any tests/call sites reflect the new panic semantics) while keeping the
Account and EoaAddress branches using the existing account and
derive_eth_eoa_account_id(address) logic.
- Around line 236-243: The accessor methods sender_address() and envelope()
currently panic on incompatible SenderResolution/Payload types which can cause
runtime failures; change them to return Result<T, Error> (e.g., Result<Address,
MyError> / Result<EnvelopeType, MyError>) instead of panicking so callers must
handle the mismatch, update call sites to propagate or handle the error, and add
a clear, actionable error variant/message indicating the expected variant and
the actual variant; alternatively, if these are truly internal-only, mark them
#[doc(hidden)] and replace the panic text with a more descriptive message and/or
add debug_assert! checks (but prefer the Result approach for public APIs).
In `@crates/app/tx/eth/src/verifier/registry.rs`:
- Around line 90-96: The compatibility shim register::verify currently allocates
by cloning the TxEnvelope into a Box for every call (Box::new(tx.clone()) passed
as TxPayload::Eoa) — mark this method as deprecated and document the allocation
cost so callers migrate to verify_payload; specifically add a #[deprecated(...)]
attribute to the verify function, update its doc comment to state it performs a
heap allocation (via TxPayload::Eoa(Box::new(...))) and clearly recommend
callers call verify_payload(sender_type::EOA_SECP256K1, &TxPayload::Eoa(...))
directly to avoid the clone/Box allocation.
In `@docker-compose.testapp.yml`:
- Around line 2-26: Add a container healthcheck and restart policy to the
testapp service in docker-compose.testapp.yml: under the service named "testapp"
add a healthcheck that uses a CMD curl -f against http://localhost:8545 with
interval 10s, timeout 5s and retries 3, and set restart to "unless-stopped" so
the container is automatically restarted on transient failures; ensure these
keys are placed alongside existing "ports", "environment", and "volumes" entries
for the testapp service.
In `@docker/evd/Dockerfile`:
- Around line 9-12: The fallback RUN line using "rustup show active-toolchain ||
rustup toolchain install" won't install a specific toolchain; update it to
trigger auto-install from rust-toolchain.toml by replacing the fallback with a
single "rustup show" (which consults rust-toolchain.toml) or explicitly install
the channel parsed from rust-toolchain.toml; locate the COPY rust-toolchain.toml
./rust-toolchain.toml and the RUN rustup show active-toolchain || rustup
toolchain install lines and either call rustup show alone or extract the
"channel" from rust-toolchain.toml and pass it to rustup toolchain install so a
concrete toolchain is installed.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
bin/testapp/Cargo.tomlbin/testapp/tests/mempool_e2e.rscrates/app/sdk/stf_traits/src/lib.rscrates/app/stf/src/lib.rscrates/app/tx/eth/src/eoa_registry.rscrates/app/tx/eth/src/error.rscrates/app/tx/eth/src/gateway.rscrates/app/tx/eth/src/lib.rscrates/app/tx/eth/src/mempool.rscrates/app/tx/eth/src/payload.rscrates/app/tx/eth/src/sender_type.rscrates/app/tx/eth/src/verifier/mod.rscrates/app/tx/eth/src/verifier/registry.rscrates/rpc/chain-index/src/provider.rscrates/rpc/evnode/src/service.rsdocker-compose.testapp.ymldocker/evd/Dockerfile
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
crates/app/tx/eth/src/mempool.rs (2)
268-274: Same concern:envelope()panics for custom payloads.Similar to
sender_address(), this accessor panics when called onCustompayloads. Theenvelope_opt()method provides a safe alternative. Consider consolidating to avoid accidental panics in production code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/mempool.rs` around lines 268 - 274, The envelope() accessor currently panics for TxPayload::Custom just like sender_address() — replace or refactor it to avoid accidental panics by delegating to the safe envelope_opt() or changing envelope() to return Option<&TxEnvelope>; update the function body for envelope() (and any callers) to either return envelope_opt().expect_* only at call sites that truly guarantee Eoa payloads, or change envelope() to simply return envelope_opt() (Option) so callers must handle the None case, referencing the envelope(), envelope_opt(), and sender_address() symbols to locate and adjust the implementation and usages.
236-243: Consider returningResultinstead of panicking.The
sender_address()method panics when called on non-EOA payloads. While the method documentation mentions this limitation, a panic in production code could be problematic. Consider either:
- Making this method return
Option<Address>(similar tosender_address_opt)- Removing the panicking method and keeping only
sender_address_optSince
sender_address_optalready exists, the panicking variant may be unnecessary.♻️ Alternative: Remove panicking accessor
If callers always need to handle the
Nonecase anyway, consider deprecating or removingsender_address()in favor ofsender_address_opt()to prevent accidental panics.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/mempool.rs` around lines 236 - 243, The panicking sender_address() should be made safe: change its signature to return Option<Address> and implement it by delegating to the existing sender_address_opt() (i.e., return self.sender_address_opt()), removing the panic path; update any callers to handle the Option (or, if you prefer removal instead, delete sender_address() and replace all usages with sender_address_opt()). Ensure references to SenderResolution::EoaAddress / SenderResolution::Account remain unchanged.crates/app/tx/eth/src/eoa_registry.rs (2)
185-213: Add in-file tests for the repair and conflict branches.This helper now owns the EOA mapping invariant, but the file still doesn't pin down the forward-only, reverse-only, and conflict cases. Please add unit tests next to this implementation.
As per coding guidelines, "Place unit tests alongside implementation in the same Rust file".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/eoa_registry.rs` around lines 185 - 213, Add inline unit tests in the same Rust file that exercise ensure_eoa_mapping for the forward-only, reverse-only, successful-idempotent, repair (one side missing), and conflict branches: create tests that set up a mock or test Environment, use lookup_account_id_in_env and lookup_address_in_env to assert initial state, call ensure_eoa_mapping with mismatched and matching pairs to trigger ERR_ADDRESS_ACCOUNT_CONFLICT and successful mapping, and verify set_mapping was called (or that both forward and reverse lookups return the expected Address/AccountId) after repair; place tests immediately below the ensure_eoa_mapping implementation and use the same helper functions (lookup_account_id_in_env, lookup_address_in_env, set_mapping) so they run as unit tests alongside the implementation.
188-213: Keep the registry invariant checks in one place.
ensure_eoa_mapping()repeats the same forward/reverse conflict checks thatset_mapping()already does. That duplicates the invariant and adds extra reads before every repair. I’d keep the checks in one place and make the lower-level helper write-only.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/app/tx/eth/src/eoa_registry.rs` around lines 188 - 213, ensure_eoa_mapping duplicates forward/reverse conflict checks that set_mapping already enforces, causing unnecessary reads; remove the lookup_account_id_in_env/lookup_address_in_env calls and the duplicate conflict logic from ensure_eoa_mapping and make it a write-only wrapper that simply calls set_mapping(address, account_id, env) and returns its result, relying on set_mapping to perform and return ERR_ADDRESS_ACCOUNT_CONFLICT when appropriate; if set_mapping currently doesn't perform those checks, move the existing conflict checks into set_mapping (using lookup_account_id_in_env and lookup_address_in_env) and then simplify ensure_eoa_mapping to just call set_mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bin/testapp/tests/mempool_e2e.rs`:
- Around line 231-255: The formatting in init_ed25519_auth_storage (and the
preceding init_eth_auth_storage) is inconsistent with rustfmt; run `cargo fmt`
(or `just quality`) to reformat these functions so spacing/indentation and line
breaks match project style, ensuring expressions like let mut nonce_key =
account_id.as_bytes().to_vec(); nonce_key.push(0u8); data.insert(...); and the
pubkey_key insert lines are reformatted; verify the function signatures and
comment alignment for init_ed25519_auth_storage and the related data.insert
calls are corrected.
---
Nitpick comments:
In `@crates/app/tx/eth/src/eoa_registry.rs`:
- Around line 185-213: Add inline unit tests in the same Rust file that exercise
ensure_eoa_mapping for the forward-only, reverse-only, successful-idempotent,
repair (one side missing), and conflict branches: create tests that set up a
mock or test Environment, use lookup_account_id_in_env and lookup_address_in_env
to assert initial state, call ensure_eoa_mapping with mismatched and matching
pairs to trigger ERR_ADDRESS_ACCOUNT_CONFLICT and successful mapping, and verify
set_mapping was called (or that both forward and reverse lookups return the
expected Address/AccountId) after repair; place tests immediately below the
ensure_eoa_mapping implementation and use the same helper functions
(lookup_account_id_in_env, lookup_address_in_env, set_mapping) so they run as
unit tests alongside the implementation.
- Around line 188-213: ensure_eoa_mapping duplicates forward/reverse conflict
checks that set_mapping already enforces, causing unnecessary reads; remove the
lookup_account_id_in_env/lookup_address_in_env calls and the duplicate conflict
logic from ensure_eoa_mapping and make it a write-only wrapper that simply calls
set_mapping(address, account_id, env) and returns its result, relying on
set_mapping to perform and return ERR_ADDRESS_ACCOUNT_CONFLICT when appropriate;
if set_mapping currently doesn't perform those checks, move the existing
conflict checks into set_mapping (using lookup_account_id_in_env and
lookup_address_in_env) and then simplify ensure_eoa_mapping to just call
set_mapping.
In `@crates/app/tx/eth/src/mempool.rs`:
- Around line 268-274: The envelope() accessor currently panics for
TxPayload::Custom just like sender_address() — replace or refactor it to avoid
accidental panics by delegating to the safe envelope_opt() or changing
envelope() to return Option<&TxEnvelope>; update the function body for
envelope() (and any callers) to either return envelope_opt().expect_* only at
call sites that truly guarantee Eoa payloads, or change envelope() to simply
return envelope_opt() (Option) so callers must handle the None case, referencing
the envelope(), envelope_opt(), and sender_address() symbols to locate and
adjust the implementation and usages.
- Around line 236-243: The panicking sender_address() should be made safe:
change its signature to return Option<Address> and implement it by delegating to
the existing sender_address_opt() (i.e., return self.sender_address_opt()),
removing the panic path; update any callers to handle the Option (or, if you
prefer removal instead, delete sender_address() and replace all usages with
sender_address_opt()). Ensure references to SenderResolution::EoaAddress /
SenderResolution::Account remain unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2a914c1b-6d6a-4e6a-8578-230e1e4e6df0
📒 Files selected for processing (7)
bin/testapp/tests/mempool_e2e.rscrates/app/tx/eth/src/eoa_registry.rscrates/app/tx/eth/src/gateway.rscrates/app/tx/eth/src/mempool.rscrates/rpc/evnode/src/service.rsdocker-compose.testapp.ymldocker/evd/Dockerfile
🚧 Files skipped from review as they are similar to previous changes (1)
- docker-compose.testapp.yml
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
bin/testapp/tests/mempool_e2e.rs (1)
607-654:⚠️ Potential issue | 🟠 MajorThis “E2E” case still bypasses the ingress wiring.
The custom-sender flow manually calls
registry.verify_payload(...)and then pushes a prebuiltTxContextinto the mempool. That means this test stays green even if the real gateway/provider registration forsender_types::CUSTOMbreaks, which is one of the key integrations in this PR. Either route this through the production decode/verify entrypoint or rename/scope it as a post-verification execution test.Also applies to: 798-817
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 607 - 654, The test helper submit_context_and_produce_block currently bypasses the ingress wiring by constructing and inserting a prebuilt TxContext (used by submit_and_produce_block which calls EthGateway::new(...).decode_and_verify(...)), so update the test to exercise the real registration/verification path: replace the manual creation or direct mempool.insert of TxContext with the production ingress decode/verify entrypoint (i.e., call the registry/provider verify_payload flow or the same gateway decode_and_verify used by real ingress) so sender_types::CUSTOM registration is exercised; alternatively, if you intend this to be a post-verification unit scenario, rename the helpers (submit_context_and_produce_block -> submit_verified_context_and_produce_block) and add comments clarifying it does not test registration/ingress, ensuring tests at lines ~798-817 get the same treatment.
🧹 Nitpick comments (2)
bin/testapp/tests/mempool_e2e.rs (2)
57-79: Register these new error codes withdefine_error!.This file adds several ad hoc
ErrorCode::new(...)paths. The repo guideline requiresdefine_error!so the codes are registered at compile time and their ranges stay explicit.As per coding guidelines, "Define errors using the define_error! macro for compile-time error registration with error codes in ranges: 0x00-0x3F (validation), 0x40-0x7F (system), 0x80-0xBF (business logic)".
Also applies to: 92-106
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 57 - 79, The new Ed25519PayloadVerifier::verify method currently uses ad-hoc ErrorCode::new(...) calls (e.g., 0x75–0x7C) which must be declared with the define_error! macro; add a define_error! block that registers named error variants for each used code (matching the repo's ranges, e.g., system 0x40-0x7F) and then replace the inline ErrorCode::new(...) usages in verify (and the other occurrences around lines 92-106) with the corresponding named error variants (or map to them via ErrorCode::from or the defined identifiers) so all error codes are compile-time registered and follow the project's error-range convention.
752-845: Split this test below the 70-line limit.The setup, verifier bootstrap, submission, and state assertions are all in one block now. Pulling the initial-state reads and post-block assertions into helpers would keep the scenario easier to scan and align with the repo rule.
As per coding guidelines, "Keep functions to less than 70 lines to maintain bounded complexity".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/testapp/tests/mempool_e2e.rs` around lines 752 - 845, The test test_custom_sender_ed25519_transfer_e2e exceeds the 70-line guideline by including setup, verifier bootstrap, submission, and assertions in one block; refactor by extracting helper functions: create setup helper (e.g., setup_ed25519_sender_test) that wraps setup_genesis_with_ed25519_sender and returns (handles, genesis_accounts, sender_account_id, bob_account_id, auth_signer, alice_key, bob_key, token_address, sender_nonce_before, sender_balance_before, bob_balance_before), an assertion helper (e.g., assert_post_transfer_state) that reads nonce/balances and performs the three assert_eq! checks, and optionally a small helper for registry bootstrap/verification (e.g., bootstrap_and_verify_custom_sender) to register and verify payloads; replace the inline blocks in test_custom_sender_ed25519_transfer_e2e with calls to these helpers so the test body stays <70 lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bin/testapp/tests/mempool_e2e.rs`:
- Around line 57-79: The verifier currently checks intent.auth_proof against the
invoke-request digest in Ed25519PayloadVerifier::verify but
Ed25519AuthAccount::authenticate trusts payload.message_digest from the caller,
allowing mismatched signatures; fix by making both sides use the same canonical
digest: compute the canonical preimage (e.g., nonce + request_digest + sender
binding) when verifying the intent in Ed25519PayloadVerifier::verify and either
(A) pass that computed digest into Ed25519AuthAccount::authenticate (do not
accept payload.message_digest from caller), or (B) have
Ed25519AuthAccount::authenticate recompute the digest internally from the
intent/envelope (not from authentication_payload) and verify the account
signature against that recomputed digest; ensure the unique symbols involved are
Ed25519PayloadVerifier::verify, intent.auth_proof, request_digest,
Ed25519AuthAccount::authenticate, payload.message_digest and
authentication_payload so both checks validate the same preimage.
---
Outside diff comments:
In `@bin/testapp/tests/mempool_e2e.rs`:
- Around line 607-654: The test helper submit_context_and_produce_block
currently bypasses the ingress wiring by constructing and inserting a prebuilt
TxContext (used by submit_and_produce_block which calls
EthGateway::new(...).decode_and_verify(...)), so update the test to exercise the
real registration/verification path: replace the manual creation or direct
mempool.insert of TxContext with the production ingress decode/verify entrypoint
(i.e., call the registry/provider verify_payload flow or the same gateway
decode_and_verify used by real ingress) so sender_types::CUSTOM registration is
exercised; alternatively, if you intend this to be a post-verification unit
scenario, rename the helpers (submit_context_and_produce_block ->
submit_verified_context_and_produce_block) and add comments clarifying it does
not test registration/ingress, ensuring tests at lines ~798-817 get the same
treatment.
---
Nitpick comments:
In `@bin/testapp/tests/mempool_e2e.rs`:
- Around line 57-79: The new Ed25519PayloadVerifier::verify method currently
uses ad-hoc ErrorCode::new(...) calls (e.g., 0x75–0x7C) which must be declared
with the define_error! macro; add a define_error! block that registers named
error variants for each used code (matching the repo's ranges, e.g., system
0x40-0x7F) and then replace the inline ErrorCode::new(...) usages in verify (and
the other occurrences around lines 92-106) with the corresponding named error
variants (or map to them via ErrorCode::from or the defined identifiers) so all
error codes are compile-time registered and follow the project's error-range
convention.
- Around line 752-845: The test test_custom_sender_ed25519_transfer_e2e exceeds
the 70-line guideline by including setup, verifier bootstrap, submission, and
assertions in one block; refactor by extracting helper functions: create setup
helper (e.g., setup_ed25519_sender_test) that wraps
setup_genesis_with_ed25519_sender and returns (handles, genesis_accounts,
sender_account_id, bob_account_id, auth_signer, alice_key, bob_key,
token_address, sender_nonce_before, sender_balance_before, bob_balance_before),
an assertion helper (e.g., assert_post_transfer_state) that reads nonce/balances
and performs the three assert_eq! checks, and optionally a small helper for
registry bootstrap/verification (e.g., bootstrap_and_verify_custom_sender) to
register and verify payloads; replace the inline blocks in
test_custom_sender_ed25519_transfer_e2e with calls to these helpers so the test
body stays <70 lines.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e1c14bf5-fb37-41e2-8788-b5b32bf45319
📒 Files selected for processing (1)
bin/testapp/tests/mempool_e2e.rs
| impl SignatureVerifierDyn for Ed25519PayloadVerifier { | ||
| fn verify(&self, payload: &TxPayload) -> SdkResult<()> { | ||
| let TxPayload::Custom(bytes) = payload else { | ||
| return Err(ErrorCode::new(0x75)); | ||
| }; | ||
| let intent: EthIntentPayload = | ||
| borsh::from_slice(bytes).map_err(|_| ErrorCode::new(0x76))?; | ||
| let decoded: Ed25519EthIntentProof = | ||
| borsh::from_slice(&intent.auth_proof).map_err(|_| ErrorCode::new(0x77))?; | ||
| let envelope = intent.decode_envelope().map_err(|_| ErrorCode::new(0x78))?; | ||
| let invoke_request = envelope | ||
| .to_invoke_requests() | ||
| .into_iter() | ||
| .next() | ||
| .ok_or_else(|| ErrorCode::new(0x79))?; | ||
| let request_digest = keccak256(&invoke_request.encode().map_err(|_| ErrorCode::new(0x7A))?); | ||
| let public_key = | ||
| VerificationKey::try_from(decoded.public_key).map_err(|_| ErrorCode::new(0x7B))?; | ||
| let signature = ed25519_consensus::Signature::from(decoded.signature); | ||
| public_key | ||
| .verify(&signature, &request_digest) | ||
| .map_err(|_| ErrorCode::new(0x7C)) | ||
| } |
There was a problem hiding this comment.
Bind account auth to the same intent digest the verifier checks.
Ed25519PayloadVerifier proves intent.auth_proof against the invoke-request digest, but Ed25519AuthAccount::authenticate() separately trusts payload.message_digest from authentication_payload. Nothing here enforces that those digests/signatures/public keys describe the same intent, so a crafted context can mix a valid intent proof for tx B with an unrelated account-auth signature and still pass both stages. Make the signed preimage canonical and shared by both checks (at least nonce + intent digest + sender binding), or recompute the digest inside authenticate() instead of accepting it from the caller.
Also applies to: 142-160, 582-590
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bin/testapp/tests/mempool_e2e.rs` around lines 57 - 79, The verifier
currently checks intent.auth_proof against the invoke-request digest in
Ed25519PayloadVerifier::verify but Ed25519AuthAccount::authenticate trusts
payload.message_digest from the caller, allowing mismatched signatures; fix by
making both sides use the same canonical digest: compute the canonical preimage
(e.g., nonce + request_digest + sender binding) when verifying the intent in
Ed25519PayloadVerifier::verify and either (A) pass that computed digest into
Ed25519AuthAccount::authenticate (do not accept payload.message_digest from
caller), or (B) have Ed25519AuthAccount::authenticate recompute the digest
internally from the intent/envelope (not from authentication_payload) and verify
the account signature against that recomputed digest; ensure the unique symbols
involved are Ed25519PayloadVerifier::verify, intent.auth_proof, request_digest,
Ed25519AuthAccount::authenticate, payload.message_digest and
authentication_payload so both checks validate the same preimage.
Overview
Design and implement a multi signature scheme system that allows users to migrate verification schemes of their accounts and chains can add thier own custom schemes
Summary by CodeRabbit
New Features
Refactor
Bug Fixes / Improvements
Chores