Skip to content

Commit

Permalink
opt(torii): avoid calculating poseidon hash where possible
Browse files Browse the repository at this point in the history
commit-id:3b4d856a
  • Loading branch information
lambda-0x committed Sep 7, 2024
1 parent f8784c4 commit 23df7be
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 113 deletions.
12 changes: 8 additions & 4 deletions crates/torii/core/src/processors/store_set_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use dojo_world::contracts::world::WorldContractReader;
use num_traits::ToPrimitive;
use starknet::core::types::Event;
use starknet::providers::Provider;
use starknet_crypto::poseidon_hash_many;
use tracing::info;

use super::EventProcessor;
use crate::processors::{MODEL_INDEX, NUM_KEYS_INDEX};
use crate::sql::Sql;
use crate::sql::{felts_sql_string, Sql};

pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_set_record";

Expand Down Expand Up @@ -46,9 +47,9 @@ where
event_id: &str,
event: &Event,
) -> Result<(), Error> {
let selector = event.data[MODEL_INDEX];
let model_id = event.data[MODEL_INDEX];

let model = db.model(selector).await?;
let model = db.model(model_id).await?;

info!(
target: LOG_TARGET,
Expand All @@ -60,6 +61,7 @@ where
let keys_end: usize =
keys_start + event.data[NUM_KEYS_INDEX].to_usize().context("invalid usize")?;
let keys = event.data[keys_start..keys_end].to_vec();
let keys_str = felts_sql_string(&keys);

// keys_end is already the length of the values array.

Expand All @@ -68,12 +70,14 @@ where
values_start + event.data[keys_end].to_usize().context("invalid usize")?;

let values = event.data[values_start..values_end].to_vec();
let entity_id = poseidon_hash_many(&keys);

let mut keys_and_unpacked = [keys, values].concat();

let mut entity = model.schema;
entity.deserialize(&mut keys_and_unpacked)?;

db.set_entity(entity, event_id, block_timestamp).await?;
db.set_entity(entity, event_id, block_timestamp, entity_id, model_id, &keys_str).await?;
Ok(())
}
}
10 changes: 6 additions & 4 deletions crates/torii/core/src/processors/store_update_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::info;

use super::EventProcessor;
use crate::processors::{ENTITY_ID_INDEX, MODEL_INDEX};
use crate::sql::Sql;
use crate::sql::{felts_sql_string, Sql};

pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_record";

Expand Down Expand Up @@ -47,10 +47,10 @@ where
event_id: &str,
event: &Event,
) -> Result<(), Error> {
let selector = event.data[MODEL_INDEX];
let model_id = event.data[MODEL_INDEX];

Check warning on line 50 in crates/torii/core/src/processors/store_update_record.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/processors/store_update_record.rs#L50

Added line #L50 was not covered by tests
let entity_id = event.data[ENTITY_ID_INDEX];

let model = db.model(selector).await?;
let model = db.model(model_id).await?;

Check warning on line 53 in crates/torii/core/src/processors/store_update_record.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/processors/store_update_record.rs#L53

Added line #L53 was not covered by tests

info!(
target: LOG_TARGET,
Expand All @@ -71,12 +71,14 @@ where
// Keys are read from the db, since we don't have access to them when only
// the entity id is passed.
let keys = db.get_entity_keys(entity_id, &tag).await?;

let keys_str = felts_sql_string(&keys);

Check warning on line 75 in crates/torii/core/src/processors/store_update_record.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/processors/store_update_record.rs#L74-L75

Added lines #L74 - L75 were not covered by tests
let mut keys_and_unpacked = [keys, values].concat();

let mut entity = model.schema;
entity.deserialize(&mut keys_and_unpacked)?;

db.set_entity(entity, event_id, block_timestamp).await?;
db.set_entity(entity, event_id, block_timestamp, entity_id, model_id, &keys_str).await?;

Check warning on line 81 in crates/torii/core/src/processors/store_update_record.rs

View check run for this annotation

Codecov / codecov/patch

crates/torii/core/src/processors/store_update_record.rs#L81

Added line #L81 was not covered by tests
Ok(())
}
}
23 changes: 7 additions & 16 deletions crates/torii/core/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,37 +165,28 @@ impl Sql {
entity: Ty,
event_id: &str,
block_timestamp: u64,
entity_id: Felt,
model_id: Felt,
keys_str: &str,
) -> Result<()> {
let keys = if let Ty::Struct(s) = &entity {
let mut keys = Vec::new();
for m in s.keys() {
keys.extend(m.serialize()?);
}
keys
} else {
return Err(anyhow!("Entity is not a struct"));
};

let namespaced_name = entity.name();
let (model_namespace, model_name) = namespaced_name.split_once('-').unwrap();

let entity_id = format!("{:#x}", poseidon_hash_many(&keys));
let model_id = format!("{:#x}", compute_selector_from_names(model_namespace, model_name));
let entity_id = format!("{:#x}", entity_id);
let model_id = format!("{:#x}", model_id);

self.query_queue.enqueue(
"INSERT INTO entity_model (entity_id, model_id) VALUES (?, ?) ON CONFLICT(entity_id, \
model_id) DO NOTHING",
vec![Argument::String(entity_id.clone()), Argument::String(model_id.clone())],
);

let keys_str = felts_sql_string(&keys);
let insert_entities = "INSERT INTO entities (id, keys, event_id, executed_at) VALUES (?, \
?, ?, ?) ON CONFLICT(id) DO UPDATE SET \
updated_at=CURRENT_TIMESTAMP, executed_at=EXCLUDED.executed_at, \
event_id=EXCLUDED.event_id RETURNING *";
let mut entity_updated: EntityUpdated = sqlx::query_as(insert_entities)
.bind(&entity_id)
.bind(&keys_str)
.bind(keys_str)
.bind(event_id)
.bind(utc_dt_string_from_timestamp(block_timestamp))
.fetch_one(&self.pool)
Expand Down Expand Up @@ -1184,7 +1175,7 @@ impl Sql {
}
}

fn felts_sql_string(felts: &[Felt]) -> String {
pub fn felts_sql_string(felts: &[Felt]) -> String {
felts.iter().map(|k| format!("{:#x}", k)).collect::<Vec<String>>().join(FELT_DELIMITER)
+ FELT_DELIMITER
}
211 changes: 124 additions & 87 deletions crates/torii/graphql/src/tests/subscription_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ mod tests {
use dojo_types::primitive::Primitive;
use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty};
use dojo_world::contracts::abi::model::Layout;
use dojo_world::contracts::naming::compute_selector_from_names;
use dojo_world::contracts::naming::{compute_selector_from_names, compute_selector_from_tag};
use serial_test::serial;
use sqlx::SqlitePool;
use starknet::core::types::{Event, Felt};
use starknet_crypto::poseidon_hash_many;
use starknet::core::types::Event;
use starknet_crypto::{poseidon_hash_many, Felt};
use tokio::sync::mpsc;
use torii_core::sql::Sql;
use torii_core::sql::{felts_sql_string, Sql};

use crate::tests::{model_fixtures, run_graphql_subscription};
use crate::utils;
Expand Down Expand Up @@ -54,60 +54,68 @@ mod tests {
tokio::spawn(async move {
// 1. Open process and sleep.Go to execute subscription
tokio::time::sleep(Duration::from_secs(1)).await;
let ty = Ty::Struct(Struct {
name: utils::struct_name_from_names(&namespace, &model_name),
children: vec![
Member {
name: "depth".to_string(),
key: false,
ty: Ty::Enum(Enum {
name: "Depth".to_string(),
option: Some(0),
options: vec![
EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) },
],
}),
},
Member {
name: "record_id".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U8(Some(0))),
},
Member {
name: "typeU16".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U16(Some(1))),
},
Member {
name: "type_u64".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U64(Some(1))),
},
Member {
name: "typeBool".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Bool(Some(true))),
},
Member {
name: "type_felt".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Felt252(Some(Felt::from(1u128)))),
},
Member {
name: "typeContractAddress".to_string(),
key: true,
ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))),
},
],
});
let keys = keys_from_ty(&ty).unwrap();
let keys_str = felts_sql_string(&keys);
let entity_id = poseidon_hash_many(&keys);
let model_id = model_id_from_ty(&ty);

// Set entity with one Record model
db.set_entity(
Ty::Struct(Struct {
name: utils::struct_name_from_names(&namespace, &model_name),
children: vec![
Member {
name: "depth".to_string(),
key: false,
ty: Ty::Enum(Enum {
name: "Depth".to_string(),
option: Some(0),
options: vec![
EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) },
],
}),
},
Member {
name: "record_id".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U8(Some(0))),
},
Member {
name: "typeU16".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U16(Some(1))),
},
Member {
name: "type_u64".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U64(Some(1))),
},
Member {
name: "typeBool".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Bool(Some(true))),
},
Member {
name: "type_felt".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Felt252(Some(Felt::from(1u128)))),
},
Member {
name: "typeContractAddress".to_string(),
key: true,
ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))),
},
],
}),
ty,
&format!("0x{:064x}:0x{:04x}:0x{:04x}", 0, 0, 0),
block_timestamp,
entity_id,
model_id,
&keys_str,
)
.await
.unwrap();
Expand Down Expand Up @@ -178,45 +186,54 @@ mod tests {
tokio::spawn(async move {
// 1. Open process and sleep.Go to execute subscription
tokio::time::sleep(Duration::from_secs(1)).await;
let ty = Ty::Struct(Struct {
name: utils::struct_name_from_names(&namespace, &model_name),
children: vec![
Member {
name: "depth".to_string(),
key: false,
ty: Ty::Enum(Enum {
name: "Depth".to_string(),
option: Some(0),
options: vec![
EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) },
],
}),
},
Member {
name: "record_id".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U32(Some(0))),
},
Member {
name: "type_felt".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Felt252(Some(Felt::from(1u128)))),
},
Member {
name: "typeContractAddress".to_string(),
key: true,
ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))),
},
],
});

let keys = keys_from_ty(&ty).unwrap();
let keys_str = felts_sql_string(&keys);
let entity_id = poseidon_hash_many(&keys);
let model_id = model_id_from_ty(&ty);

// Set entity with one Record model
db.set_entity(
Ty::Struct(Struct {
name: utils::struct_name_from_names(&namespace, &model_name),
children: vec![
Member {
name: "depth".to_string(),
key: false,
ty: Ty::Enum(Enum {
name: "Depth".to_string(),
option: Some(0),
options: vec![
EnumOption { name: "Zero".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "One".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Two".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Three".to_string(), ty: Ty::Tuple(vec![]) },
],
}),
},
Member {
name: "record_id".to_string(),
key: false,
ty: Ty::Primitive(Primitive::U32(Some(0))),
},
Member {
name: "type_felt".to_string(),
key: false,
ty: Ty::Primitive(Primitive::Felt252(Some(Felt::from(1u128)))),
},
Member {
name: "typeContractAddress".to_string(),
key: true,
ty: Ty::Primitive(Primitive::ContractAddress(Some(Felt::ONE))),
},
],
}),
ty,
&format!("0x{:064x}:0x{:04x}:0x{:04x}", 0, 0, 0),
block_timestamp,
entity_id,
model_id,
&keys_str,
)
.await
.unwrap();
Expand Down Expand Up @@ -442,4 +459,24 @@ mod tests {
assert_eq!(response_value, expected_value);
rx.recv().await.unwrap();
}

fn keys_from_ty(ty: &Ty) -> anyhow::Result<Vec<Felt>> {
if let Ty::Struct(s) = &ty {
let mut keys = Vec::new();
for m in s.keys() {
keys.extend(
m.serialize().map_err(|_| anyhow::anyhow!("Failed to serialize model key"))?,
);
}
Ok(keys)
} else {
anyhow::bail!("Entity is not a struct")
}
}

fn model_id_from_ty(ty: &Ty) -> Felt {
let namespaced_name = ty.name();

compute_selector_from_tag(&namespaced_name)
}
}
Loading

0 comments on commit 23df7be

Please sign in to comment.