Skip to content
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

add print_latest_block_transactions toy hint #13

Merged
7 changes: 7 additions & 0 deletions crates/exex/cairo-programs/transaction_hash.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
func main() {
%{
print_latest_block_transactions
%}

ret;
}
99 changes: 99 additions & 0 deletions crates/exex/cairo-programs/transaction_hash.json
Original file line number Diff line number Diff line change
@@ -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": []
}
}
37 changes: 31 additions & 6 deletions crates/exex/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
",
)?;
Expand Down Expand Up @@ -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<Option<SealedBlockWithSenders>> {
// Executes a SQL query to select the block data as a JSON string based on the block number.
let block = self.connection().query_row::<String, _, _>(
Expand All @@ -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.
Expand All @@ -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<Option<SealedBlockWithSenders>> {
// Prepare a SQL query to select the block data as a JSON string with the highest block
// number.
let block = self.connection().query_row::<String, _, _>(
"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,
Expand Down
40 changes: 26 additions & 14 deletions crates/exex/src/exex.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -54,9 +53,8 @@ impl<Node: FullNodeComponents> KakarotRollup<Node> {
}

/// 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<PathBuf>) -> eyre::Result<()> {
// Initialize the Cairo run configuration
let config = CairoRunConfig {
layout: LayoutName::all_cairo,
trace_enabled: true,
Expand All @@ -77,14 +75,25 @@ impl<Node: FullNodeComponents> KakarotRollup<Node> {
// 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,
)?;
}
}
}

Expand Down Expand Up @@ -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]
Expand Down
130 changes: 130 additions & 0 deletions crates/exex/src/hints.rs
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -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<HintFunc>,
}

impl fmt::Debug for Hint {
ClementWalter marked this conversation as resolved.
Show resolved Hide resolved
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<F>(name: String, logic: F) -> Self
where
F: Fn(
&mut VirtualMachine,
&mut ExecutionScopes,
&HashMap<String, HintReference>,
&ApTracking,
&HashMap<String, Felt252>,
) -> 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<String, HintReference>,
_ap_tracking: &ApTracking,
_constants: &HashMap<String, Felt252>|
-> 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(())
},
)
}
1 change: 1 addition & 0 deletions crates/exex/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod db;
pub mod execution;
pub mod exex;
pub mod hints;