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

feat(torii): model upgrades #2637

Merged
merged 38 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b1f3e35
feat(torii): model upgrades
Larkooo Nov 5, 2024
fa71dbd
upgrade processor for event and model
Larkooo Nov 6, 2024
60a870d
Merge branch 'main' into model-upgrade
Larkooo Nov 6, 2024
62750f6
fix processors
Larkooo Nov 6, 2024
6313f44
fix: model upgrade
Larkooo Nov 6, 2024
f7fd6ae
wrap up model upgrade
Larkooo Nov 6, 2024
e85bdd0
ftm
Larkooo Nov 6, 2024
d641ba0
clippy
Larkooo Nov 6, 2024
3336c4a
fmt
Larkooo Nov 6, 2024
3742676
refactor: diff
Larkooo Nov 7, 2024
b3ee109
fmtg
Larkooo Nov 7, 2024
0d26d43
fix: set model cache
Larkooo Nov 7, 2024
cbc39c7
fix add model members
Larkooo Nov 7, 2024
14ffc8e
fix
Larkooo Nov 7, 2024
ddd9921
feat: shared cache between grpc & engine and fix partial deser
Larkooo Nov 8, 2024
cf9c45c
fix: test and fmt
Larkooo Nov 8, 2024
3925cce
Merge remote-tracking branch 'upstream/main' into model-upgrade
Larkooo Nov 10, 2024
ae8bf42
fix
Larkooo Nov 10, 2024
210ac31
primitives not option
Larkooo Nov 12, 2024
d86fcc4
fix: enums
Larkooo Nov 13, 2024
ab855dd
Revert "fix: enums"
Larkooo Nov 13, 2024
5d57e6d
Revert "primitives not option"
Larkooo Nov 13, 2024
b04e848
fix enum sql value
Larkooo Nov 13, 2024
e3d8a7b
Merge remote-tracking branch 'upstream/main' into model-upgrade
Larkooo Nov 13, 2024
b7f3264
main
Larkooo Nov 13, 2024
87857c2
remove prints & format
Larkooo Nov 13, 2024
9b5d624
fix quer ytest
Larkooo Nov 13, 2024
9ac0d21
fix: bool
Larkooo Nov 14, 2024
a8d6737
fix: ararys
Larkooo Nov 14, 2024
d2c7696
fmt
Larkooo Nov 14, 2024
255d4fb
fix: map row to ty
Larkooo Nov 14, 2024
6235080
fix: enum
Larkooo Nov 14, 2024
612fc4b
fix: enum
Larkooo Nov 14, 2024
0dbec7e
fmt
Larkooo Nov 14, 2024
551c6bd
fix: primitive len
Larkooo Nov 14, 2024
cea8102
Revert "fix: primitive len"
Larkooo Nov 14, 2024
155033d
refactotr: dont use modelr eader block
Larkooo Nov 14, 2024
4e921c3
Revert "Revert "fix: primitive len""
Larkooo Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions crates/dojo/types/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,85 @@ impl Ty {
}
Ok(())
}

/// Returns a new Ty containing only the differences between self and other
pub fn diff(&self, other: &Ty) -> Option<Ty> {
match (self, other) {
(Ty::Struct(s1), Ty::Struct(s2)) => {
// Find members that exist in s1 but not in s2, or are different
let diff_children: Vec<Member> = s1
.children
.iter()
.filter(|m1| {
s2.children
.iter()
.find(|m2| m2.name == m1.name)
.map_or(true, |m2| *m1 != m2)
})
.cloned()
.collect();

if diff_children.is_empty() {
None
} else {
Some(Ty::Struct(Struct { name: s1.name.clone(), children: diff_children }))
}
}
(Ty::Enum(e1), Ty::Enum(e2)) => {
// Find options that exist in e1 but not in e2, or are different
let diff_options: Vec<EnumOption> = e1
.options
.iter()
.filter(|o1| {
e2.options.iter().find(|o2| o2.name == o1.name).map_or(true, |o2| *o1 != o2)
})
.cloned()
.collect();

if diff_options.is_empty() {
None
} else {
Some(Ty::Enum(Enum {
name: e1.name.clone(),
option: e1.option,
options: diff_options,
}))
}
}
(Ty::Array(a1), Ty::Array(a2)) => {
if a1 == a2 {
None
} else {
Some(Ty::Array(a1.clone()))
}
}
(Ty::Tuple(t1), Ty::Tuple(t2)) => {
if t1 == t2 {
None
} else {
Some(Ty::Tuple(t1.clone()))
}
}
(Ty::ByteArray(b1), Ty::ByteArray(b2)) => {
if b1 == b2 {
None
} else {
Some(Ty::ByteArray(b1.clone()))
}
}
(Ty::Primitive(p1), Ty::Primitive(p2)) => {
if p1 == p2 {
None
} else {
Some(Ty::Primitive(*p1))
}
}
// Different types entirely - we cannot diff them
_ => {
panic!("Type mismatch between self {:?} and other {:?}", self.name(), other.name())
}
}
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -597,4 +676,77 @@ mod tests {
assert_eq!(format_member(&member), expected);
}
}

#[test]
fn test_ty_diff() {
// Test struct diff
let struct1 = Ty::Struct(Struct {
name: "TestStruct".to_string(),
children: vec![
Member {
name: "field1".to_string(),
ty: Ty::Primitive(Primitive::U32(None)),
key: false,
},
Member {
name: "field2".to_string(),
ty: Ty::Primitive(Primitive::U32(None)),
key: false,
},
Member {
name: "field3".to_string(),
ty: Ty::Primitive(Primitive::U32(None)),
key: false,
},
],
});

let struct2 = Ty::Struct(Struct {
name: "TestStruct".to_string(),
children: vec![Member {
name: "field1".to_string(),
ty: Ty::Primitive(Primitive::U32(None)),
key: false,
}],
});

// Should show only field2 and field3 as differences
let diff = struct1.diff(&struct2).unwrap();
if let Ty::Struct(s) = diff {
assert_eq!(s.children.len(), 2);
assert_eq!(s.children[0].name, "field2");
assert_eq!(s.children[1].name, "field3");
} else {
panic!("Expected Struct diff");
}

// Test enum diff
let enum1 = Ty::Enum(Enum {
name: "TestEnum".to_string(),
option: None,
options: vec![
EnumOption { name: "Option1".to_string(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Option2".to_string(), ty: Ty::Tuple(vec![]) },
],
});

let enum2 = Ty::Enum(Enum {
name: "TestEnum".to_string(),
option: None,
options: vec![EnumOption { name: "Option1".to_string(), ty: Ty::Tuple(vec![]) }],
});

// Should show only Option2 as difference
let diff = enum1.diff(&enum2).unwrap();
if let Ty::Enum(e) = diff {
assert_eq!(e.options.len(), 1);
assert_eq!(e.options[0].name, "Option2");
} else {
panic!("Expected Enum diff");
}

// Test no differences
let same_struct = struct2.diff(&struct2);
assert!(same_struct.is_none());
}
Larkooo marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion crates/dojo/world/src/contracts/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ where
parse_schema(&abigen::model::Ty::Struct(res)).map_err(ModelError::Parse)
}

// For non fixed layouts, packed and unpacked sizes are None.
// For non fixed layouts, packed and unpacked sizes are None.
// Therefore we return 0 in this case.
async fn packed_size(&self) -> Result<u32, ModelError> {
Ok(self.model_reader.packed_size().call().await?.unwrap_or(0))
Expand Down
4 changes: 4 additions & 0 deletions crates/torii/core/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use crate::processors::store_del_record::StoreDelRecordProcessor;
use crate::processors::store_set_record::StoreSetRecordProcessor;
use crate::processors::store_update_member::StoreUpdateMemberProcessor;
use crate::processors::store_update_record::StoreUpdateRecordProcessor;
use crate::processors::upgrade_event::UpgradeEventProcessor;
use crate::processors::upgrade_model::UpgradeModelProcessor;
use crate::processors::{BlockProcessor, EventProcessor, TransactionProcessor};
use crate::sql::{Cursors, Sql};
use crate::types::ContractType;
Expand Down Expand Up @@ -74,6 +76,8 @@ impl<P: Provider + Send + Sync + std::fmt::Debug + 'static> Processors<P> {
vec![
Box::new(RegisterModelProcessor) as Box<dyn EventProcessor<P>>,
Box::new(RegisterEventProcessor) as Box<dyn EventProcessor<P>>,
Box::new(UpgradeModelProcessor) as Box<dyn EventProcessor<P>>,
Box::new(UpgradeEventProcessor) as Box<dyn EventProcessor<P>>,
Box::new(StoreSetRecordProcessor),
Box::new(StoreDelRecordProcessor),
Box::new(StoreUpdateRecordProcessor),
Expand Down
2 changes: 2 additions & 0 deletions crates/torii/core/src/processors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub mod store_set_record;
pub mod store_transaction;
pub mod store_update_member;
pub mod store_update_record;
pub mod upgrade_event;
pub mod upgrade_model;

const MODEL_INDEX: usize = 0;
const ENTITY_ID_INDEX: usize = 1;
Expand Down
5 changes: 3 additions & 2 deletions crates/torii/core/src/processors/register_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,19 @@ where
contract_address = ?event.address,
packed_size = %packed_size,
unpacked_size = %unpacked_size,
"Registered model content."
"Registered event content."
);

db.register_model(
&namespace,
schema,
&schema,
layout,
event.class_hash.into(),
event.address.into(),
packed_size,
unpacked_size,
block_timestamp,
None,
)
.await?;

Expand Down
3 changes: 2 additions & 1 deletion crates/torii/core/src/processors/register_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,14 @@ where

db.register_model(
&namespace,
schema,
&schema,
layout,
event.class_hash.into(),
event.address.into(),
packed_size,
unpacked_size,
block_timestamp,
None,
)
.await?;

Expand Down
113 changes: 113 additions & 0 deletions crates/torii/core/src/processors/upgrade_event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use anyhow::{Error, Ok, Result};
use async_trait::async_trait;
use dojo_world::contracts::abigen::world::Event as WorldEvent;
use dojo_world::contracts::model::ModelReader;
use dojo_world::contracts::world::WorldContractReader;
use starknet::core::types::Event;
use starknet::providers::Provider;
use tracing::{debug, info};

use super::EventProcessor;
use crate::sql::Sql;

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

#[derive(Default, Debug)]
pub struct UpgradeEventProcessor;

#[async_trait]
impl<P> EventProcessor<P> for UpgradeEventProcessor
where
P: Provider + Send + Sync + std::fmt::Debug,
{
fn event_key(&self) -> String {
"EventUpgraded".to_string()
}

// We might not need this anymore, since we don't have fallback and all world events must
// be handled.
fn validate(&self, _event: &Event) -> bool {
true
}

async fn process(
&self,
world: &WorldContractReader<P>,
db: &mut Sql,
_block_number: u64,
block_timestamp: u64,
_event_id: &str,
event: &Event,
) -> Result<(), Error> {
// Torii version is coupled to the world version, so we can expect the event to be well
// formed.
let event = match WorldEvent::try_from(event).unwrap_or_else(|_| {
panic!(
"Expected {} event to be well formed.",
<UpgradeEventProcessor as EventProcessor<P>>::event_key(self)
)
}) {
Larkooo marked this conversation as resolved.
Show resolved Hide resolved
WorldEvent::EventUpgraded(e) => e,
_ => {
unreachable!()
}
Comment on lines +52 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle unexpected event types explicitly

Ohayo, sensei! Instead of using unreachable!(), return an error when an unexpected event type is encountered. This improves code robustness and makes debugging easier.

Apply this diff:

-_ => {
-    unreachable!()
-}
+_ => {
+    return Err(anyhow!("Unexpected event type received in UpgradeEventProcessor."));
+}

Committable suggestion skipped: line range outside the PR's diff.

};
Comment on lines +45 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace panic and unreachable with proper error handling

Ohayo sensei! The current error handling could crash the application. Consider using proper error propagation:

-        let event = match WorldEvent::try_from(event).unwrap_or_else(|_| {
-            panic!(
-                "Expected {} event to be well formed.",
-                <UpgradeEventProcessor as EventProcessor<P>>::event_key(self)
-            )
-        }) {
-            WorldEvent::EventUpgraded(e) => e,
-            _ => {
-                unreachable!()
-            }
+        let event = match WorldEvent::try_from(event) {
+            Ok(WorldEvent::EventUpgraded(e)) => e,
+            Ok(_) => return Err(Error::msg(format!(
+                "Unexpected event type for {}",
+                <UpgradeEventProcessor as EventProcessor<P>>::event_key(self)
+            ))),
+            Err(e) => return Err(Error::msg(format!(
+                "Failed to parse {} event: {}",
+                <UpgradeEventProcessor as EventProcessor<P>>::event_key(self),
+                e
+            )))
         };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let event = match WorldEvent::try_from(event).unwrap_or_else(|_| {
panic!(
"Expected {} event to be well formed.",
<UpgradeEventProcessor as EventProcessor<P>>::event_key(self)
)
}) {
WorldEvent::EventUpgraded(e) => e,
_ => {
unreachable!()
}
};
let event = match WorldEvent::try_from(event) {
Ok(WorldEvent::EventUpgraded(e)) => e,
Ok(_) => return Err(Error::msg(format!(
"Unexpected event type for {}",
<UpgradeEventProcessor as EventProcessor<P>>::event_key(self)
))),
Err(e) => return Err(Error::msg(format!(
"Failed to parse {} event: {}",
<UpgradeEventProcessor as EventProcessor<P>>::event_key(self),
e
)))
};


// Called model here by language, but it's an event. Torii rework will make clear
// distinction.
let model = db.model(event.selector).await?;
let name = model.name;
let namespace = model.namespace;
let prev_schema = model.schema;

let model = world.model_reader(&namespace, &name).await?;
let new_schema = model.schema().await?;
let schema_diff = new_schema.diff(&prev_schema);
// No changes to the schema. This can happen if torii is re-run with a fresh database.
// As the register model fetches the latest schema from the chain.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might rework the model.schema() to accept a block id, which could perhaps be more accurate at some point when replaying history.
But not a blocker for this V1.

if schema_diff.is_none() {
return Ok(());
}

let schema_diff = schema_diff.unwrap();
let layout = model.layout().await?;

// Events are never stored onchain, hence no packing or unpacking.
let unpacked_size: u32 = 0;
let packed_size: u32 = 0;

info!(
target: LOG_TARGET,
namespace = %namespace,
name = %name,
"Upgraded event."
);

debug!(
target: LOG_TARGET,
name,
diff = ?schema_diff,
layout = ?layout,
class_hash = ?event.class_hash,
contract_address = ?event.address,
packed_size = %packed_size,
unpacked_size = %unpacked_size,
"Upgraded event content."
);

db.register_model(
&namespace,
&new_schema,
layout,
event.class_hash.into(),
event.address.into(),
packed_size,
unpacked_size,
block_timestamp,
Some(&schema_diff),
)
.await?;

Ok(())
}
}
Loading
Loading