Skip to content

Commit

Permalink
add print_latest_block_transactions toy hint (#13)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* fix comments

* fix conflicts

---------

Co-authored-by: Clément Walter <[email protected]>
  • Loading branch information
tcoratger and ClementWalter authored Aug 30, 2024
1 parent bb7c013 commit ee5c038
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 20 deletions.
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
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 {
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;

0 comments on commit ee5c038

Please sign in to comment.