-
Notifications
You must be signed in to change notification settings - Fork 174
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
Persistent mode for saya using mock of SNOS #2253
Conversation
bin/saya/src/args/shard.rs
Outdated
pub struct SayaModeArg(pub SayaMode); | ||
|
||
#[derive(Debug, Args, Clone)] | ||
pub struct ShardOptions { |
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.
We should think how the terminology will be separated, as shard
is mostly for ephemeral execution and persistent execution is done in a different manner at some point.
Or the name shouldn't be piltover
but settlement_contract
which can be the world for shards and piltover for persistency.
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.
Sure, that makes sense.
bin/saya/src/args/shard.rs
Outdated
&[ | ||
SayaModeArg(SayaMode::Ephemeral), | ||
SayaModeArg(SayaMode::Persistent), | ||
SayaModeArg(SayaMode::PersistentMerging), |
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.
What is this one? sovereign
?
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.
So the one using the merger program recursively should be soverein
?
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.
Hum, then no. It's something unrelated. I wasn't sure why we had a persistent and persistent merging. Seems to be only at provable program layer, right?
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.
It changes the program flow in both merging and updating state. It is a different program, so it produces a different output. I think at this point PersistentMerging
should be treated as a sort of legacy program, as it is still useful, but Persistent
is a newer, "sounder" way to prove state.
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.
Let's remove PersistentMerging
if it's not used anymore?
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.
Removed
@@ -23,7 +22,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | |||
saya.start().await?; | |||
|
|||
// Wait until Ctrl + C is pressed, then shutdown | |||
ctrl_c().await?; | |||
// ctrl_c().await?; |
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.
not required anymore to be able to kill the program? Or it's handled by the .start()
function?
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.
Not required, as the ephemeral shard will shut itself down after reaching the shutdown event.
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.
Yeah but we may want to kill saya. :) In this case it would be better keeping it to ensure graceful shutdown in case of manual interruption.
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.
It is the opposite, this line simply prevents saya
from closing until ctrl+c
is pressed. Now we do the same thing by simply not returning from the start
function until all the blocks are prover (for ephemeral shard).
A graceful shutdown would have to be implemented there.
use crate::{SayaStarknetAccount, LOG_TARGET}; | ||
|
||
#[derive(Debug, Serialize)] | ||
pub struct PiltoverCalldata { |
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.
I'm not sure if the cainome bindgen is already integrated to piltover. But if yes, we should use it to already have concrete types in rust that are automatically serializable in cairo.
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.
Looks to me like it is integrated, will use it as a dependcy.
By the way those are automatically serializable, as they use serde_felt
from this PR: cartridge-gg/cairo-proof-parser#10.
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.
It doesn't seem to be working at the moment. Create a bug report here: keep-starknet-strange/piltover#39
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.
Thanks for that, I'll have a look at it to ensure it works as it will be better to rely on generated code.
Are we using a fork of piltover or the original contract?
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.
I'm using this one, https://github.com/matzayonc/piltover/tree/feat/fact-registry.
crates/saya/core/src/lib.rs
Outdated
to: c.to, | ||
selector: c.selector, | ||
calldata: c.calldata, | ||
starknet_messages: Vec::new(), // TODO: Fill messages. |
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.
This should be done in a subsequent PR, or could be done in this one? The messages are or L1HandlerTransaction
or available into the receipts.
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.
Should be trivial to implement, but might be less so to test it, that why I left it for now to avoid a false positive.
This PR is quite big already so I would leave it for the following out, unless you think otherwise.
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.
Sounds great in a follow up PR. 👍
) -> anyhow::Result<(String, Felt)> { | ||
if serialized_proof.len() > 2000 { |
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.
isn't 4000
the limit?
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.
I exprienced that some transactions failed when greater than 3000, but can test further now that it's in a more mature state.
let mut nonce = account.get_nonce().await?; | ||
let mut hashes = Vec::new(); | ||
|
||
for fragment in serialized_proof.into_iter().chunks(2000).into_iter() { |
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.
If a fragment upload fail, do we have a way that saya can make a call on the chain to resume from where it was?
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.
Not at the moment, but should be pretty straight forward to implement.
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.
Same then let's keep that in the track for a follow up PR to be implemented to ensure more reliable settlement.
3580386
to
5841d9b
Compare
9cf9984
to
d861f1f
Compare
f405916
to
3513cfe
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2253 +/- ##
==========================================
+ Coverage 67.73% 68.54% +0.80%
==========================================
Files 388 387 -1
Lines 50421 49902 -519
==========================================
+ Hits 34153 34203 +50
+ Misses 16268 15699 -569 ☔ View full report in Codecov by Sentry. |
Co-authored-by: glihm <dev@glihm.net>
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: 4
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
⛔ Files ignored due to path filters (1)
Cargo.lock
is excluded by!**/*.lock
📒 Files selected for processing (2)
- crates/saya/core/Cargo.toml (1 hunks)
- crates/saya/core/src/macros.rs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/saya/core/Cargo.toml
🧰 Additional context used
#[macro_export] | ||
macro_rules! retry { | ||
// The macro takes an async block as an input | ||
($func:expr) => {{ |
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
Ohayo, sensei! Consider parameterizing retry values for greater flexibility.
The macro currently uses hardcoded values for MAX_RETRIES
and RETRY_DELAY_MS
. To make it more versatile, consider allowing these to be specified as parameters.
Here's a suggested improvement:
macro_rules! retry {
($func:expr, $max_retries:expr, $retry_delay_ms:expr) => {{
let max_retries = $max_retries;
let retry_delay_ms = $retry_delay_ms;
// ... rest of the macro implementation ...
}};
}
This change would allow users to customize the retry behavior based on their specific needs.
const MAX_RETRIES: usize = 20; | ||
|
||
// Set the delay between retries in milliseconds (adjust as needed) | ||
const RETRY_DELAY_MS: u64 = 1000; |
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.
Ohayo, sensei! Be wary of constant declarations inside macros.
Defining MAX_RETRIES
and RETRY_DELAY_MS
as constants inside the macro could lead to name conflicts if the macro is invoked multiple times within the same scope.
Consider using let
bindings instead:
let max_retries: usize = 20;
let retry_delay_ms: u64 = 1000;
This approach avoids potential name conflicts and aligns with the parameterization suggestion from the previous comment.
loop { | ||
match $func.await { | ||
Ok(result) => break Ok(result), // If the function succeeds, break the loop and | ||
// return the result | ||
Err(err) => { | ||
tracing::warn!("Error: {}", err); | ||
|
||
// Check if the maximum number of retries has been reached | ||
if retry_count >= MAX_RETRIES { | ||
break Err(err); | ||
} | ||
|
||
// Increment the retry count | ||
retry_count += 1; | ||
tracing::info!("Retrying... ({}/{})", retry_count, MAX_RETRIES); | ||
// Wait before retrying | ||
tokio::time::sleep(tokio::time::Duration::from_millis(RETRY_DELAY_MS)).await; | ||
} | ||
} | ||
} |
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
Ohayo, sensei! Consider enhancing the retry strategy for more robustness.
The current implementation is solid, but there's room for improvement to make the macro more powerful and flexible.
Consider these enhancements:
- Implement different backoff strategies (e.g., exponential backoff).
- Allow users to provide a custom condition for retrying.
Here's a conceptual example:
macro_rules! retry {
($func:expr, $max_retries:expr, $backoff_strategy:expr, $should_retry:expr) => {{
let mut retry_count = 0;
loop {
match $func.await {
Ok(result) => break Ok(result),
Err(err) => {
if !$should_retry(&err) || retry_count >= $max_retries {
break Err(err);
}
retry_count += 1;
let delay = $backoff_strategy(retry_count);
tokio::time::sleep(tokio::time::Duration::from_millis(delay)).await;
}
}
}
}};
}
This version allows users to specify a backoff strategy and a custom retry condition, providing greater control over the retry behavior.
Ok(result) => break Ok(result), // If the function succeeds, break the loop and | ||
// return the result | ||
Err(err) => { | ||
tracing::warn!("Error: {}", err); |
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
Ohayo, sensei! Consider enhancing error handling and logging.
The current implementation uses tracing
effectively for logging errors and retry attempts. However, there's room for improvement in error handling and the information provided in logs.
Consider these enhancements:
- Implement structured error handling to categorize different types of errors.
- Include more context in error logs, such as the function name or a unique identifier.
- Add debug logging for successful attempts to aid in troubleshooting.
Here's an example of how you might implement these suggestions:
use std::fmt::Debug;
macro_rules! retry {
($func:expr, $max_retries:expr, $retry_delay_ms:expr) => {{
let max_retries = $max_retries;
let retry_delay_ms = $retry_delay_ms;
let func_name = stringify!($func);
let mut retry_count = 0;
loop {
match $func.await {
Ok(result) => {
tracing::debug!("Function {} succeeded after {} attempts", func_name, retry_count + 1);
break Ok(result)
},
Err(err) => {
tracing::warn!("Error in {}: {:?}", func_name, err);
if retry_count >= max_retries {
tracing::error!("Max retries reached for {}", func_name);
break Err(err);
}
retry_count += 1;
tracing::info!("Retrying {} ({}/{})", func_name, retry_count, max_retries);
tokio::time::sleep(tokio::time::Duration::from_millis(retry_delay_ms)).await;
}
}
}
}};
}
This version provides more context in logs and adds debug logging for successful attempts, which can be valuable for troubleshooting and monitoring.
Also applies to: 27-27
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: 2
🧹 Outside diff range and nitpick comments (4)
crates/saya/core/src/lib.rs (4)
14-14
: Ohayo, sensei! The new imports look great!The additions from
itertools
,prover
, andstarknet
crates align well with the new functionality. The public re-export ofProverAccessKey
is a nice touch for API usability.Consider grouping related imports together for better readability. For example, you could group all
starknet
-related imports.Also applies to: 19-22, 26-32
60-63
: Ohayo, sensei! TheSayaConfig
struct has evolved nicely!The new
mode
andsettlement_contract
fields, along with the more flexibleblock_range
, align well with the PR objectives. These changes enhance the configurability of the Saya system.Consider adding documentation comments for the new fields to explain their purpose and expected values. This will help users understand how to configure Saya correctly.
93-96
: Ohayo, sensei! TheSaya
struct has gained some new superpowers!The addition of
prover_identifier
,prev_commitment
, andprev_height
fields enhances Saya's capabilities for proving and state tracking. The initialization in thenew
method looks good.Consider adding comments to explain the purpose of
prev_commitment
andprev_height
, as their roles might not be immediately clear to newcomers.Also applies to: 126-139
Line range hint
151-279
: Ohayo, sensei! Thestart
method has become a true master of ceremonies!The refactoring to support different modes of operation and batch processing is impressive. The error handling and logging improvements are also great additions.
However, the method has grown quite complex. Consider breaking it down into smaller, more focused methods to improve readability and maintainability. For example:
- Extract the mode-specific logic into separate methods.
- Create a helper method for block fetching and processing.
- Move the proof generation and processing logic into a separate method.
This refactoring would make the
start
method easier to understand and maintain.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (1)
- crates/saya/core/src/lib.rs (13 hunks)
🧰 Additional context used
🔇 Additional comments (1)
crates/saya/core/src/lib.rs (1)
Line range hint
1-573
: Ohayo, sensei! Overall, the changes to the Saya core library are impressive!The introduction of different operational modes, enhanced StarkNet integration, and improved block processing align well with the PR objectives. The code structure and new functionality demonstrate a significant step forward for the Saya system.
However, there are a few areas that could benefit from further attention:
- The
start
method has become quite complex and could be refactored for better maintainability.- There are some TODOs and commented-out code that need to be addressed.
- The
StarknetAccountData
struct could be improved for better security.- Some methods could benefit from additional error handling and documentation.
Addressing these points will further enhance the quality and robustness of the codebase. Great work on this substantial update, sensei!
// felt.to_bytes()).collect::<Vec<_>>()); | ||
let (commitment, height) = if self.config.mode != SayaMode::Ephemeral { | ||
da.publish_checkpoint(checkpoint).await? | ||
} else if self.config.skip_publishing_proof { | ||
da.publish_state_diff_felts(&state_diff).await? | ||
} else { | ||
da.publish_state_diff_and_proof_felts(&world_da, &serialized_proof).await?; | ||
} | ||
da.publish_state_diff_and_proof_felts(&state_diff, &serialized_proof).await? | ||
}; | ||
self.prev_commitment = Some(commitment); | ||
self.prev_height = Some(height); | ||
|
||
info!(target: LOG_TARGET,"commitment: {:?}, height: {:?}", commitment.0, height); | ||
} | ||
|
||
let program_hash = proof.program_hash; | ||
let program_output_hash = proof.program_output_hash; | ||
let program_output = proof.program_output; | ||
|
||
let program_hash_string = program_hash; | ||
let program_output_hash_string = program_output_hash; | ||
|
||
info!(target: LOG_TARGET,"Extracted program hash and output hash. {:?} {:?}", program_hash_string, program_output_hash_string); | ||
let expected_fact = poseidon_hash_many(&[program_hash, program_output_hash]).to_string(); | ||
let program = program_hash.to_string(); | ||
info!(target: LOG_TARGET, expected_fact, program, "Expected fact."); | ||
|
||
let starknet_account = self.config.starknet_account.get_starknet_account()?; | ||
|
||
// Verify the proof and register fact. | ||
trace!(target: LOG_TARGET, last_block, "Verifying block."); | ||
let (transaction_hash, nonce_after) = verifier::verify( | ||
let (transaction_hash, _nonce) = verifier::verify( | ||
VerifierIdentifier::HerodotusStarknetSepolia(self.config.fact_registry_address), | ||
serialized_proof, | ||
self.config.starknet_account.clone(), | ||
&starknet_account, | ||
self.config.mode.to_program().cairo_version(), | ||
) | ||
.await?; | ||
info!(target: LOG_TARGET, last_block, transaction_hash, "Block verified."); | ||
|
||
let ExtractProgramResult { program: _, program_hash } = extract_program(&proof)?; | ||
let ExtractOutputResult { program_output, program_output_hash } = extract_output(&proof)?; | ||
let expected_fact = poseidon_hash_many(&[program_hash, program_output_hash]).to_string(); | ||
info!(target: LOG_TARGET, expected_fact, "Expected fact."); | ||
|
||
// When not waiting for couple of second `apply_diffs` will sometimes fail due to reliance | ||
// on registered fact | ||
tokio::time::sleep(std::time::Duration::from_secs(2)).await; | ||
|
||
trace!(target: LOG_TARGET, last_block, "Applying diffs."); | ||
let transaction_hash = dojo_os::starknet_apply_diffs( | ||
self.config.world_address, | ||
world_da, | ||
program_output, | ||
program_hash, | ||
nonce_after + Felt::ONE, | ||
self.config.starknet_account.clone(), | ||
) | ||
.await?; | ||
info!(target: LOG_TARGET, last_block, transaction_hash, "Diffs applied."); | ||
|
||
// Apply the diffs to the world state. | ||
match self.config.mode { | ||
SayaMode::Ephemeral => { | ||
// Needs checker program to be verified, and set as the upgrade_state authority | ||
todo!("Ephemeral mode does not support publishing updated state yet."); | ||
} | ||
SayaMode::Persistent => { | ||
let serialized_output = program_output.iter().copied().collect_vec(); | ||
println!("serialized_output: {:?}", serialized_output); | ||
|
||
// todo!("Persistent mode does not support publishing updated state with SNOS | ||
// yet."); | ||
|
||
let deduplicated_output = | ||
serialized_output[1..serialized_output.len() / 2].to_vec(); | ||
let batcher_output = from_felts::<StarknetOsOutput>(&deduplicated_output).unwrap(); | ||
let piltover_calldata = PiltoverCalldata { | ||
program_output: serialized_output, | ||
// onchain_data_hash: batcher_output.new_state_root, | ||
onchain_data_hash: batcher_output.new_block_hash, | ||
onchain_data_size: (Felt::ZERO, Felt::ZERO), | ||
}; | ||
|
||
let expected_state_root = batcher_output.new_block_hash.to_string(); | ||
let expected_block_number = | ||
(batcher_output.new_block_number - Felt::ONE).to_string(); | ||
info!(target: LOG_TARGET, last_block, expected_state_root, expected_block_number, "Applying snos to piltover."); | ||
|
||
starknet_apply_piltover( | ||
piltover_calldata, | ||
self.config.settlement_contract, | ||
&starknet_account, | ||
) | ||
.await?; | ||
} | ||
} |
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.
Ohayo, sensei! The process_proven
method is a true proof master!
The implementation for publishing state differences and verifying proofs looks solid. Great job on supporting different modes of operation!
However, there are a few areas that need attention:
- There's a TODO comment for the Ephemeral mode (line 491). This needs to be implemented.
- There's a commented-out line for
onchain_data_hash
(line 505). Decide whether to use this or remove it. - The error handling for
from_felts
(line 502) usesunwrap()
, which could lead to panics. Consider using?
or proper error handling.
Please address these issues to ensure the robustness of the process_proven
method.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] | ||
pub struct StarknetAccountData { | ||
pub starknet_url: Url, | ||
#[serde(deserialize_with = "felt_string_deserializer")] | ||
pub chain_id: Felt, | ||
pub signer_address: Felt, | ||
pub signer_key: Felt, | ||
} | ||
|
||
impl StarknetAccountData { | ||
pub fn get_starknet_account(&self) -> anyhow::Result<SayaStarknetAccount> { | ||
let provider = JsonRpcClient::new(HttpTransport::new(self.starknet_url.clone())); | ||
let signer = LocalWallet::from(SigningKey::from_secret_scalar(self.signer_key)); | ||
|
||
let mut account = SingleOwnerAccount::new( | ||
provider, | ||
signer, | ||
self.signer_address, | ||
self.chain_id, | ||
ExecutionEncoding::New, | ||
); | ||
|
||
account.set_block_id(BlockId::Tag(BlockTag::Pending)); | ||
Ok(account) | ||
} | ||
} |
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.
Ohayo, sensei! The StarknetAccountData
struct is a nice addition, but let's talk security!
The implementation of StarknetAccountData
and its get_starknet_account
method improves the handling of StarkNet account information. However, there's a potential security concern:
Storing the signer_key
directly in the struct might expose sensitive information. Consider using a more secure method for handling the signer key, such as:
- Using a separate, encrypted storage for the key.
- Implementing a method to securely retrieve the key when needed, possibly requiring user input for decryption.
Here's a conceptual example:
pub struct StarknetAccountData {
pub starknet_url: Url,
#[serde(deserialize_with = "felt_string_deserializer")]
pub chain_id: Felt,
pub signer_address: Felt,
// Remove the signer_key field
}
impl StarknetAccountData {
pub fn get_starknet_account(&self, key_provider: &dyn SecureKeyProvider) -> anyhow::Result<SayaStarknetAccount> {
let provider = JsonRpcClient::new(HttpTransport::new(self.starknet_url.clone()));
let signer_key = key_provider.get_signer_key()?; // Securely retrieve the key
let signer = LocalWallet::from(SigningKey::from_secret_scalar(signer_key));
// ... rest of the method ...
}
}
This approach would enhance the security of the sensitive signer key.
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
🧹 Outside diff range and nitpick comments (4)
bin/saya/src/args/mod.rs (4)
49-50
: Ohayo! TheSayaArgs
struct is leveling up, sensei!The new
end_block
field and thesettlement
options are great additions that provide more flexibility in controlling block processing and settlement. The conflict betweenbatch_size
andend_block
is a smart move to prevent inconsistencies.One small suggestion:
Consider adding a comment explaining why
batch_size
andend_block
are mutually exclusive. This will help future developers understand the reasoning behind this constraint./// The number of blocks to be merged into a single proof. /// This option conflicts with `end_block` to ensure consistent batch processing. #[arg(conflicts_with = "end_block")] pub batch_size: usize,Also applies to: 54-60
100-193
: Ohayo, sensei! Thetry_from
implementation is looking sharp!The new logic for handling
DataAvailabilityConfig
, private keys, and settlement options is well-implemented. The validation forbatch_size
in persistent mode is a crucial addition to ensure correct behavior.However, I have a small suggestion to improve error handling consistency:
Consider using a custom error type instead of
Box<dyn std::error::Error>
for more specific error handling. This would make it easier to handle different error cases and provide more informative error messages to users.For example:
#[derive(thiserror::Error, Debug)] enum SayaConfigError { #[error("Celestia config: {0}")] CelestiaConfig(String), #[error("Invalid input: {0}")] InvalidInput(String), // Add more error variants as needed } impl TryFrom<SayaArgs> for SayaConfig { type Error = SayaConfigError; fn try_from(args: SayaArgs) -> Result<Self, Self::Error> { // Use the custom error type in your implementation // For example: // Err(SayaConfigError::CelestiaConfig("Node url is required".to_string())) } }This approach would make error handling more robust and easier to maintain as the codebase grows.
Line range hint
199-297
: Ohayo! The test case is looking good, sensei!The updates to the test case accurately reflect the changes in the structs and implementation. Good job on maintaining test coverage!
To make your tests even more robust, consider adding these additional test cases:
- Test the validation of
batch_size
in persistent mode:#[test] fn test_batch_size_validation_in_persistent_mode() { let args = SayaArgs { // ... other fields ... batch_size: 2, settlement: SettlementOptions { saya_mode: settlement::SayaModeArg(SayaMode::Persistent), // ... other fields ... }, // ... other fields ... }; let result: Result<SayaConfig, _> = args.try_into(); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Batch size must be 1 for persistent mode")); }
- Test the mutual exclusivity of
batch_size
andend_block
:#[test] fn test_batch_size_and_end_block_conflict() { let args = SayaArgs { // ... other fields ... batch_size: 10, end_block: Some(100), // ... other fields ... }; let result: Result<SayaConfig, _> = args.try_into(); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("The argument '--batch-size <BATCH_SIZE>' cannot be used with '--end-block <END_BLOCK>'")); }These additional tests will help ensure that the new constraints and validations are working as expected.
169-177
: Ohayo, sensei! Thesettlement_contract
validation is on point!The addition of the
settlement_contract
field and its validation for persistent mode is crucial. Great job on ensuring this important requirement is met!To make the error message even more helpful:
Consider updating the error message to be more specific about the requirement:
return Err(SayaConfigError::InvalidInput( "A `settlement_contract` must be specified when using persistent mode.".to_string() ));This more detailed message will help users understand exactly what they need to provide when using persistent mode.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (1)
- bin/saya/src/args/mod.rs (6 hunks)
🧰 Additional context used
🔇 Additional comments (2)
bin/saya/src/args/mod.rs (2)
6-7
: Ohayo, sensei! The new imports and module look good!The addition of
SayaMode
andSettlementOptions
imports, along with the newsettlement
module, aligns well with the introduction of settlement-related functionality. Nice work on keeping the code structure organized!Also applies to: 21-21
Line range hint
1-297
: Ohayo, sensei! Your code is evolving beautifully!Overall, the changes to this file significantly enhance the Saya configuration process. The new settlement options, block range controls, and data availability configurations provide great flexibility for users. The added validations, especially for the persistent mode and
settlement_contract
, help prevent misconfigurations.Here's a summary of the improvements:
- Enhanced
SayaArgs
struct with new options- Improved
try_from
implementation with robust error checking- Updated test cases to cover new functionality
To take your code to the next level, consider:
- Implementing a custom error type for more specific error handling
- Adding a few more test cases to cover edge cases
- Improving some error messages for clarity
Great work on evolving the Saya tool, sensei! These changes will definitely improve the user experience and make the tool more powerful.
#[arg(short, long)] | ||
pub end_block: Option<u64>, |
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.
Ohayo, sensei! Let's add a quick check for end_block
!
The addition of the end_block
field is a great improvement for flexibility. However, to prevent potential issues, we should ensure that end_block
is greater than or equal to start_block
when it's provided.
Consider adding this validation in the try_from
implementation:
if let Some(end_block) = args.end_block {
if end_block < args.start_block {
return Err(SayaConfigError::InvalidInput(
"`end_block` must be greater than or equal to `start_block`".to_string()
));
}
}
This check will help prevent invalid block ranges and potential runtime errors.
Also applies to: 184-184
Description
I added a Cairo 1 program simulating SNOS, committing to piltover.
Related issue
Tests
The full development flow is in this script:
bin/saya/saya.sh
.Added to documentation?
Checklist
scripts/prettier.sh
,scripts/rust_fmt.sh
,scripts/cairo_fmt.sh
)scripts/clippy.sh
,scripts/docs.sh
)Summary by CodeRabbit
New Features
Bug Fixes
world_address
, enhancing deployment instructions.Documentation
Chores