From ee5c038ccafa931d930bcaeb83b0c89bd2506662 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:01:00 -0700 Subject: [PATCH] add `print_latest_block_transactions` toy hint (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add print_latest_block_transactions toy hint * clean up * clean up * clean up * add Hint struct * clean up * fix start function * fix * add KakarotBuiltinHintProcessor * add default for KakarotBuiltinHintProcessor * Update crates/exex/src/hints.rs Co-authored-by: Clément Walter * fix comments * fix conflicts --------- Co-authored-by: Clément Walter --- .../cairo-programs/transaction_hash.cairo | 7 + .../exex/cairo-programs/transaction_hash.json | 99 +++++++++++++ crates/exex/src/db.rs | 37 ++++- crates/exex/src/exex.rs | 40 ++++-- crates/exex/src/hints.rs | 130 ++++++++++++++++++ crates/exex/src/lib.rs | 1 + 6 files changed, 294 insertions(+), 20 deletions(-) create mode 100644 crates/exex/cairo-programs/transaction_hash.cairo create mode 100644 crates/exex/cairo-programs/transaction_hash.json create mode 100644 crates/exex/src/hints.rs diff --git a/crates/exex/cairo-programs/transaction_hash.cairo b/crates/exex/cairo-programs/transaction_hash.cairo new file mode 100644 index 000000000..fbe31f9f2 --- /dev/null +++ b/crates/exex/cairo-programs/transaction_hash.cairo @@ -0,0 +1,7 @@ +func main() { + %{ + print_latest_block_transactions + %} + + ret; +} diff --git a/crates/exex/cairo-programs/transaction_hash.json b/crates/exex/cairo-programs/transaction_hash.json new file mode 100644 index 000000000..dafc4085a --- /dev/null +++ b/crates/exex/cairo-programs/transaction_hash.json @@ -0,0 +1,99 @@ +{ + "attributes": [], + "builtins": [], + "compiler_version": "0.12.0", + "data": [ + "0x208b7fff7fff7ffe" + ], + "debug_info": { + "file_contents": {}, + "instruction_locations": { + "0": { + "accessible_scopes": [ + "__main__", + "__main__.main" + ], + "flow_tracking_data": { + "ap_tracking": { + "group": 0, + "offset": 0 + }, + "reference_ids": {} + }, + "hints": [ + { + "location": { + "end_col": 7, + "end_line": 4, + "input_file": { + "filename": "crates/exex/cairo-programs/transaction_hash.cairo" + }, + "start_col": 5, + "start_line": 2 + }, + "n_prefix_newlines": 1 + } + ], + "inst": { + "end_col": 8, + "end_line": 6, + "input_file": { + "filename": "crates/exex/cairo-programs/transaction_hash.cairo" + }, + "start_col": 5, + "start_line": 6 + } + } + } + }, + "hints": { + "0": [ + { + "accessible_scopes": [ + "__main__", + "__main__.main" + ], + "code": "print_latest_block_transactions", + "flow_tracking_data": { + "ap_tracking": { + "group": 0, + "offset": 0 + }, + "reference_ids": {} + } + } + ] + }, + "identifiers": { + "__main__.main": { + "decorators": [], + "pc": 0, + "type": "function" + }, + "__main__.main.Args": { + "full_name": "__main__.main.Args", + "members": {}, + "size": 0, + "type": "struct" + }, + "__main__.main.ImplicitArgs": { + "full_name": "__main__.main.ImplicitArgs", + "members": {}, + "size": 0, + "type": "struct" + }, + "__main__.main.Return": { + "cairo_type": "()", + "type": "type_definition" + }, + "__main__.main.SIZEOF_LOCALS": { + "type": "const", + "value": 0 + } + }, + "main_scope": "__main__", + "prime": "0x800000000000011000000000000000000000000000000000000000000000001", + "reference_manager": { + "references": [] + } +} diff --git a/crates/exex/src/db.rs b/crates/exex/src/db.rs index 6c0cd1671..d67ddaa9f 100644 --- a/crates/exex/src/db.rs +++ b/crates/exex/src/db.rs @@ -66,10 +66,10 @@ impl Database { data TEXT ); CREATE TABLE IF NOT EXISTS trace ( - id INTEGER PRIMARY KEY, - number TEXT UNIQUE, - execution TEXT, - memory TEXT + id INTEGER PRIMARY KEY, + number TEXT, + execution TEXT, + memory TEXT ); ", )?; @@ -118,7 +118,7 @@ impl Database { /// /// This function queries the database for a block with the specified block number. /// If the block is found, it is deserialized from its JSON representation into a - /// `SealedBlockWithSenders` struct. If the block is not found, `None` is returned. + /// [`SealedBlockWithSenders`] struct. If the block is not found, `None` is returned. pub fn block(&self, number: U256) -> eyre::Result> { // Executes a SQL query to select the block data as a JSON string based on the block number. let block = self.connection().query_row::( @@ -129,7 +129,7 @@ impl Database { ); match block { - // If the block is found, deserialize the JSON string into `SealedBlockWithSenders`. + // If the block is found, deserialize the JSON string into [`SealedBlockWithSenders`]. Ok(data) => Ok(Some(serde_json::from_str(&data)?)), // If no rows are returned by the query, it means the block does not exist in the // database. @@ -139,6 +139,31 @@ impl Database { } } + /// Retrieves the latest block from the database. + /// + /// This function queries the database for the block with the highest block number (interpreted + /// as an integer) and returns it as a [`SealedBlockWithSenders`] struct. If an error occurs + /// during the retrieval or deserialization of the block, the function will return an + /// `eyre::Result` containing the error. + pub fn latest_block(&self) -> eyre::Result> { + // Prepare a SQL query to select the block data as a JSON string with the highest block + // number. + let block = self.connection().query_row::( + "SELECT data FROM block ORDER BY CAST(number AS INTEGER) DESC LIMIT 1", + [], + |row| row.get(0), + ); + + match block { + // If the block is found, deserialize the JSON string into `SealedBlockWithSenders`. + Ok(data) => Ok(Some(serde_json::from_str(&data)?)), + // If no rows are returned by the query, it means the table is empty. + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), + // If any other error occurs, convert it into an eyre error and return. + Err(e) => Err(e.into()), + } + } + /// Inserts an execution trace into the database. pub fn insert_execution_trace( &self, diff --git a/crates/exex/src/exex.rs b/crates/exex/src/exex.rs index 787fd3b8f..f63e204fe 100644 --- a/crates/exex/src/exex.rs +++ b/crates/exex/src/exex.rs @@ -1,7 +1,6 @@ -use crate::{db::Database, execution::execute_block}; +use crate::{db::Database, execution::execute_block, hints::KakarotHintProcessor}; use cairo_vm::{ cairo_run::{cairo_run, CairoRunConfig}, - hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, types::layout_name::LayoutName, vm::trace::trace_entry::RelocatedTraceEntry, Felt252, @@ -54,9 +53,8 @@ impl KakarotRollup { } /// Starts processing chain state notifications. - pub async fn start(mut self, path: PathBuf) -> eyre::Result<()> { - // Load the cairo program from the file - let program = std::fs::read(path)?; + pub async fn start(mut self, paths: Vec) -> eyre::Result<()> { + // Initialize the Cairo run configuration let config = CairoRunConfig { layout: LayoutName::all_cairo, trace_enabled: true, @@ -77,14 +75,25 @@ impl KakarotRollup { // The ExEx will not require all earlier blocks which can be pruned. self.ctx.events.send(ExExEvent::FinishedHeight(committed_chain.tip().number))?; - // Run the cairo program - let mut hint_processor = BuiltinHintProcessor::new_empty(); - let res = cairo_run(&program, &config, &mut hint_processor)?; - // Commit the execution traces to the database - let trace = res.relocated_trace.unwrap_or_default(); - let memory = - res.relocated_memory.into_iter().map(|x| x.unwrap_or_default()).collect(); - self.commit_cairo_execution_traces(committed_chain.tip().number, trace, memory)?; + // Build the Kakarot hint processor with the print transaction hint. + let mut hint_processor = KakarotHintProcessor::default().build(); + + // Run the cairo programs corresponding to the paths + for path in &paths { + // Load the cairo program from the file + let program = std::fs::read(path)?; + + let res = cairo_run(&program, &config, &mut hint_processor)?; + // Commit the execution traces to the database + let trace = res.relocated_trace.unwrap_or_default(); + let memory = + res.relocated_memory.into_iter().map(|x| x.unwrap_or_default()).collect(); + self.commit_cairo_execution_traces( + committed_chain.tip().number, + trace, + memory, + )?; + } } } @@ -191,7 +200,10 @@ mod tests { )?; // Create the Kakarot Rollup chain instance and start processing chain state notifications. - Ok(KakarotRollup { ctx, db }.start(PathBuf::from("./cairo-programs/fibonacci.json"))) + Ok(KakarotRollup { ctx, db }.start(vec![ + PathBuf::from("./cairo-programs/transaction_hash.json"), + PathBuf::from("./cairo-programs/fibonacci.json"), + ])) } #[tokio::test] diff --git a/crates/exex/src/hints.rs b/crates/exex/src/hints.rs new file mode 100644 index 000000000..2e87c5a37 --- /dev/null +++ b/crates/exex/src/hints.rs @@ -0,0 +1,130 @@ +use crate::{db::Database, exex::DATABASE_PATH}; +use cairo_vm::{ + hint_processor::{ + builtin_hint_processor::builtin_hint_processor_definition::{ + BuiltinHintProcessor, HintFunc, + }, + hint_processor_definition::HintReference, + }, + serde::deserialize_program::ApTracking, + types::exec_scope::ExecutionScopes, + vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}, + Felt252, +}; +use rusqlite::Connection; +use std::{collections::HashMap, fmt, rc::Rc}; + +/// A wrapper around [`BuiltinHintProcessor`] to manage hint registration. +pub struct KakarotHintProcessor { + /// The underlying [`BuiltinHintProcessor`]. + processor: BuiltinHintProcessor, +} + +/// Implementation of `Debug` for `KakarotHintProcessor`. +impl fmt::Debug for KakarotHintProcessor { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("KakarotHintProcessor") + .field("extra_hints", &self.processor.extra_hints.keys()) + .field("run_resources", &"...") + .finish() + } +} + +impl Default for KakarotHintProcessor { + fn default() -> Self { + Self::new_empty().with_hint(print_tx_hint()) + } +} + +impl KakarotHintProcessor { + /// Creates a new, empty [`KakarotHintProcessor`]. + pub fn new_empty() -> Self { + Self { processor: BuiltinHintProcessor::new_empty() } + } + + /// Adds a hint to the [`KakarotHintProcessor`]. + /// + /// This method allows you to register a hint by providing a [`Hint`] instance. + pub fn with_hint(mut self, hint: Hint) -> Self { + self.processor.add_hint(hint.name.clone(), hint.func.clone()); + self + } + + /// Returns the underlying [`BuiltinHintProcessor`]. + /// + /// This allows the processor to be used elsewhere. + pub fn build(self) -> BuiltinHintProcessor { + self.processor + } +} + +/// A generic structure to encapsulate a hint with a closure that contains the specific logic. +pub struct Hint { + /// The name of the hint. + name: String, + /// The function containing the hint logic. + func: Rc, +} + +impl fmt::Debug for Hint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Hint").field("name", &self.name).field("func", &"...").finish() + } +} + +impl Hint { + /// Creates a new [`Hint`] with the specified name and function logic. + /// + /// The logic is passed as a closure, which will be executed when the hint is triggered. + pub fn new(name: String, logic: F) -> Self + where + F: Fn( + &mut VirtualMachine, + &mut ExecutionScopes, + &HashMap, + &ApTracking, + &HashMap, + ) -> Result<(), HintError> + + 'static + + Sync, + { + Self { name, func: Rc::new(HintFunc(Box::new(logic))) } + } +} + +/// Public function to create the `print_latest_block_transactions` hint. +/// +/// This function returns a new `Hint` instance with the specified name and logic. +pub fn print_tx_hint() -> Hint { + Hint::new( + String::from("print_latest_block_transactions"), + |_vm: &mut VirtualMachine, + _exec_scopes: &mut ExecutionScopes, + _ids_data: &HashMap, + _ap_tracking: &ApTracking, + _constants: &HashMap| + -> Result<(), HintError> { + // Open the SQLite database connection. + let connection = Connection::open(DATABASE_PATH) + .map_err(|e| HintError::CustomHint(e.to_string().into_boxed_str()))?; + + // Initialize the database with the connection. + let db = Database::new(connection) + .map_err(|e| HintError::CustomHint(e.to_string().into_boxed_str()))?; + + // Retrieve the latest block from the database. + let latest_block = db + .latest_block() + .map_err(|e| HintError::CustomHint(e.to_string().into_boxed_str()))?; + + // If a block was found, print each transaction hash. + if let Some(block) = latest_block { + for tx in &block.body { + println!("Block: {}, transaction hash: {}", block.number, tx.hash()); + } + } + + Ok(()) + }, + ) +} diff --git a/crates/exex/src/lib.rs b/crates/exex/src/lib.rs index a7fb3a7d5..ef871971d 100644 --- a/crates/exex/src/lib.rs +++ b/crates/exex/src/lib.rs @@ -1,3 +1,4 @@ pub mod db; pub mod execution; pub mod exex; +pub mod hints;