diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a9b2bda5..78a22c4a4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ git # Madara Changelog ## Next release +- fix(L1): fix l1 thread with battle tested implementation + removed l1-l2 - fix: update and store ConfigFetch in l2 sync(), chainId rpc call - fix: get_events paging with continuation_token - fux(getStorageAt): #28 diff --git a/Cargo.lock b/Cargo.lock index 96fc6024a7..735b3ac932 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1347,7 +1347,7 @@ source = "git+https://github.com/keep-starknet-strange/cairo.git?branch=no_std-s dependencies = [ "cairo-lang-utils", "const-fnv1a-hash", - "convert_case 0.6.0", + "convert_case", "derivative", "itertools 0.10.5", "lalrpop", @@ -1468,7 +1468,7 @@ dependencies = [ "cairo-lang-sierra-to-casm", "cairo-lang-syntax", "cairo-lang-utils", - "convert_case 0.6.0", + "convert_case", "genco", "indent", "indoc", @@ -2022,12 +2022,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -2518,10 +2512,8 @@ dependencies = [ "mc-db", "mc-deoxys", "mc-genesis-data-provider", - "mc-l1-messages", "mc-mapping-sync", "mc-rpc", - "mc-settlement", "mc-storage", "mockito", "mp-block", @@ -2642,10 +2634,8 @@ version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version", "syn 1.0.109", ] @@ -3091,8 +3081,8 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -3106,8 +3096,8 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "ethers-core", "once_cell", @@ -3117,8 +3107,8 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -3135,8 +3125,8 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "Inflector", "const-hex", @@ -3158,8 +3148,8 @@ dependencies = [ [[package]] name = "ethers-contract-derive" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "Inflector", "const-hex", @@ -3173,8 +3163,8 @@ dependencies = [ [[package]] name = "ethers-core" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "arrayvec 0.7.4", "bytes", @@ -3202,8 +3192,8 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "chrono", "ethers-core", @@ -3217,8 +3207,8 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "async-trait", "auto_impl", @@ -3243,8 +3233,8 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "async-trait", "auto_impl", @@ -3279,8 +3269,8 @@ dependencies = [ [[package]] name = "ethers-signers" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "async-trait", "coins-bip32", @@ -3297,8 +3287,8 @@ dependencies = [ [[package]] name = "ethers-solc" -version = "2.0.11" -source = "git+https://github.com/gakonst/ethers-rs?rev=f0e5b194f09c533feb10d1a686ddb9e5946ec107#f0e5b194f09c533feb10d1a686ddb9e5946ec107" +version = "2.0.13" +source = "git+https://github.com/gakonst/ethers-rs#1354179f3c1586b1359c21a3a7e935edf1343ec8" dependencies = [ "cfg-if", "const-hex", @@ -4236,30 +4226,6 @@ dependencies = [ "hashbrown 0.14.3", ] -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.5", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.3.3" @@ -4523,16 +4489,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -4854,21 +4810,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures", - "futures-executor", - "futures-util", - "log", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "jsonrpsee" version = "0.16.3" @@ -6007,6 +5948,7 @@ dependencies = [ "blockifier", "bonsai-trie", "env_logger", + "ethers", "futures", "hex", "indexmap 2.0.0-pre", @@ -6028,6 +5970,7 @@ dependencies = [ "mp-storage", "mp-transactions", "parity-scale-codec", + "primitive-types", "rand 0.8.5", "reqwest", "rodio", @@ -6047,7 +5990,6 @@ dependencies = [ "tokio-tungstenite", "url", "validator", - "web3", ] [[package]] @@ -6064,30 +6006,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mc-l1-messages" -version = "0.1.0" -dependencies = [ - "ethers", - "log", - "mc-db", - "mp-felt", - "mp-transactions", - "pallet-starknet-runtime-api", - "rustc-hex", - "sc-client-api", - "sc-transaction-pool-api", - "serde", - "serde_json", - "sp-api", - "sp-core", - "sp-runtime", - "starknet-core-contract-client", - "starknet_api", - "thiserror", - "url", -] - [[package]] name = "mc-mapping-sync" version = "0.1.0" @@ -6199,42 +6117,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mc-settlement" -version = "0.1.0" -dependencies = [ - "async-trait", - "clap", - "ethers", - "futures", - "futures-timer", - "log", - "mc-db", - "mp-block", - "mp-digest-log", - "mp-felt", - "mp-hashers", - "mp-messages", - "mp-snos-output", - "mp-transactions", - "pallet-starknet-runtime-api", - "rustc-hex", - "sc-client-api", - "serde", - "serde_json", - "sp-api", - "sp-arithmetic", - "sp-blockchain", - "sp-core", - "sp-io", - "sp-runtime", - "starknet-core-contract-client", - "starknet-crypto 0.6.1 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", - "starknet_api", - "thiserror", - "url", -] - [[package]] name = "mc-storage" version = "0.1.0" @@ -10108,16 +9990,7 @@ version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ - "secp256k1-sys 0.6.1", -] - -[[package]] -name = "secp256k1" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" -dependencies = [ - "secp256k1-sys 0.8.1", + "secp256k1-sys", ] [[package]] @@ -10129,15 +10002,6 @@ dependencies = [ "cc", ] -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "secrecy" version = "0.8.0" @@ -10743,7 +10607,7 @@ dependencies = [ "regex", "scale-info", "schnorrkel", - "secp256k1 0.24.3", + "secp256k1", "secrecy", "serde", "sp-core-hashing", @@ -10850,7 +10714,7 @@ dependencies = [ "log", "parity-scale-codec", "rustversion", - "secp256k1 0.24.3", + "secp256k1", "sp-core", "sp-externalities", "sp-keystore", @@ -11292,7 +11156,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "starknet-core" version = "0.8.0" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "base64 0.21.5", "flate2", @@ -11306,22 +11170,10 @@ dependencies = [ "starknet-ff 0.3.5 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", ] -[[package]] -name = "starknet-core-contract-client" -version = "0.1.0" -source = "git+https://github.com/keep-starknet-strange/zaun#9fe421790165166b714c4ef95c79ad49a959a05a" -dependencies = [ - "async-trait", - "ethers", - "log", - "num-traits 0.2.17", - "thiserror", -] - [[package]] name = "starknet-crypto" version = "0.6.1" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "crypto-bigint", "hex", @@ -11377,7 +11229,7 @@ dependencies = [ [[package]] name = "starknet-crypto-codegen" version = "0.3.2" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "starknet-curve 0.4.0 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", "starknet-ff 0.3.5 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", @@ -11407,7 +11259,7 @@ dependencies = [ [[package]] name = "starknet-curve" version = "0.4.0" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "starknet-ff 0.3.5 (git+https://github.com/jbcaron/starknet-rs.git?branch=classes)", ] @@ -11431,7 +11283,7 @@ dependencies = [ [[package]] name = "starknet-ff" version = "0.3.5" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "ark-ff", "bigdecimal", @@ -11466,7 +11318,7 @@ dependencies = [ [[package]] name = "starknet-providers" version = "0.8.0" -source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#5bce1aae4f4bee76359540bc368d76b10d11b337" +source = "git+https://github.com/jbcaron/starknet-rs.git?branch=classes#629140c15e257bb3927a74e4c461246c30a748eb" dependencies = [ "async-trait", "auto_impl", @@ -13137,54 +12989,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web3" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5388522c899d1e1c96a4c307e3797e0f697ba7c77dd8e0e625ecba9dd0342937" -dependencies = [ - "arrayvec 0.7.4", - "base64 0.21.5", - "bytes", - "derive_more", - "ethabi", - "ethereum-types", - "futures", - "futures-timer", - "headers", - "hex", - "idna 0.4.0", - "jsonrpc-core", - "log", - "once_cell", - "parking_lot 0.12.1", - "pin-project", - "reqwest", - "rlp", - "secp256k1 0.27.0", - "serde", - "serde_json", - "soketto", - "tiny-keccak", - "tokio", - "tokio-stream", - "tokio-util", - "url", - "web3-async-native-tls", -] - -[[package]] -name = "web3-async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f6d8d1636b2627fe63518d5a9b38a569405d9c9bc665c43c9c341de57227ebb" -dependencies = [ - "native-tls", - "thiserror", - "tokio", - "url", -] - [[package]] name = "webpki" version = "0.22.4" diff --git a/Cargo.toml b/Cargo.toml index e1bf23be00..073bac039f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "crates/client/mapping-sync", "crates/client/rpc", "crates/client/rpc-core", - "crates/client/settlement", "crates/client/storage", "crates/node", "crates/pallets/starknet", @@ -41,7 +40,6 @@ default-members = [ "crates/client/mapping-sync", "crates/client/rpc", "crates/client/rpc-core", - "crates/client/settlement", "crates/client/storage", "crates/node", "crates/pallets/starknet", @@ -248,11 +246,9 @@ mp-transactions = { path = "crates/primitives/transactions", default-features = mc-commitment-state-diff = { path = "crates/client/commitment-state-diff" } mc-db = { path = "crates/client/db" } mc-genesis-data-provider = { path = "crates/client/genesis-data-provider" } -mc-l1-messages = { path = "crates/client/l1-messages" } mc-mapping-sync = { path = "crates/client/mapping-sync" } mc-rpc = { path = "crates/client/rpc" } mc-rpc-core = { path = "crates/client/rpc-core" } -mc-settlement = { path = "crates/client/settlement" } mc-storage = { path = "crates/client/storage" } # Madara runtime @@ -301,8 +297,7 @@ cairo-lang-utils = { git = "https://github.com/keep-starknet-strange/cairo.git", ] } # Ethers: using the same versions as in Anvil -ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "f0e5b194f09c533feb10d1a686ddb9e5946ec107" } +ethers = { git = "https://github.com/gakonst/ethers-rs"} # Zaun starknet-core-contract-client = { git = "https://github.com/keep-starknet-strange/zaun" } diff --git a/crates/client/deoxys/Cargo.toml b/crates/client/deoxys/Cargo.toml index 57d095456e..92f745bc89 100644 --- a/crates/client/deoxys/Cargo.toml +++ b/crates/client/deoxys/Cargo.toml @@ -22,7 +22,8 @@ lazy_static = "1.4.0" reqwest = "0.11" serde_json = "1" tokio-tungstenite = "0.20.1" -web3 = "0.19.0" +ethers = { workspace = true } + blockifier = { workspace = true, default-features = false, features = [ "testing", @@ -35,6 +36,7 @@ itertools = { workspace = true } log = { version = "0.4.14" } mockito = { workspace = true } rand = { version = "0.8.5" } +primitive-types = { version = "0.12.2"} rodio = { version = "0.17", optional = true } serde = { workspace = true, default-features = true } tokio = { workspace = true, features = ["macros", "parking_lot", "test-util"] } diff --git a/crates/client/deoxys/src/l1.rs b/crates/client/deoxys/src/l1.rs index 3e56311a8d..5ac38c5565 100644 --- a/crates/client/deoxys/src/l1.rs +++ b/crates/client/deoxys/src/l1.rs @@ -1,233 +1,192 @@ //! Contains the necessaries to perform an L1 verification of the state -use std::error::Error; +use std::str::FromStr; use std::sync::{Arc, Mutex}; use anyhow::Result; -use futures::{SinkExt, StreamExt}; -use hex::encode; +use ethers::contract::{abigen, EthEvent}; +use ethers::providers::{Http, Middleware, Provider}; +use ethers::types::transaction::eip2718::TypedTransaction; +use ethers::types::{Address, BlockNumber as EthBlockNumber, Filter, TransactionRequest, I256, U64}; +use ethers::utils::hex::decode; +use futures::stream::StreamExt; use lazy_static::lazy_static; -use mp_commitments::StateCommitment; -use mp_felt::{Felt252Wrapper, Felt252WrapperError}; +use mp_felt::Felt252Wrapper; +use primitive_types::{H256, U256}; use reqwest::Url; use serde::Deserialize; use serde_json::Value; -use starknet_api::block::{BlockHash, BlockNumber}; -use tokio::sync::mpsc::{self, Sender}; -use tokio_tungstenite::connect_async; -use tokio_tungstenite::tungstenite::Message; +use starknet_api::hash::StarkHash; use crate::l2::STARKNET_STATE_UPDATE; -use crate::utility::{format_address, get_state_update_at}; - -pub mod starknet_core_address { - pub const MAINNET: &str = "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4"; - pub const GOERLI_TESTNET: &str = "0xde29d060D45901Fb19ED6C6e959EB22d8626708e"; - pub const GOERLI_INTEGRATION: &str = "0xd5c325D183C592C94998000C5e0EED9e6655c020"; - pub const SEPOLIA_TESTNET: &str = "0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057"; - pub const SEPOLIA_INTEGRATION: &str = "0x4737c0c1B4D5b1A687B42610DdabEE781152359c"; -} +use crate::utility::{event_to_l1_state_update, get_state_update_at}; +use crate::utils::constant::{starknet_core_address, LOG_STATE_UPDTATE_TOPIC}; lazy_static! { /// Shared latest L2 state update verified on L1 pub static ref ETHEREUM_STATE_UPDATE: Arc> = Arc::new(Mutex::new(L1StateUpdate { - global_root: StateCommitment::default(), - block_number: BlockNumber::default(), - block_hash: BlockHash::default(), + block_number: u64::default(), + global_root: StarkHash::default(), + block_hash: StarkHash::default(), })); } /// Contains the Starknet verified state on L1 #[derive(Debug, Clone, Deserialize)] pub struct L1StateUpdate { - pub global_root: StateCommitment, - pub block_number: BlockNumber, - pub block_hash: BlockHash, + pub block_number: u64, + pub global_root: StarkHash, + pub block_hash: StarkHash, +} + +/// Starknet core LogStateUpdate event +#[derive(Clone, Debug, EthEvent, Deserialize)] +pub struct LogStateUpdate { + #[ethevent(indexed)] + pub global_root: U256, + pub block_number: I256, + pub block_hash: U256, } +/// Ethereum client to interact with L1 #[derive(Clone)] pub struct EthereumClient { - _http: reqwest::Client, + provider: Arc>, url: Url, } /// Implementation of the Ethereum client to interact with L1 impl EthereumClient { /// Create a new EthereumClient instance with the given RPC URL - pub fn new(url: Url) -> Result { - Ok(Self { _http: reqwest::Client::new(), url }) - } - - /// Get current RPC URL - pub fn get_url(&self) -> Url { - self.get_wss().unwrap() + pub async fn new(url: Url) -> Result { + let provider = Provider::::try_from(url.as_str())?; + Ok(Self { provider: Arc::new(provider), url }) } /// Get current RPC URL - pub fn get_wss(&self) -> Result> { - let mut wss_url = self.url.clone(); - - let new_scheme = match wss_url.scheme() { - "http" => "ws", - "https" => "wss", - "ws" | "wss" => return Ok(wss_url), - _ => return Err("Unsupported URL scheme".into()), - }; - - wss_url.set_scheme(new_scheme).unwrap(); - Ok(wss_url) + pub fn get_url(&self) -> String { + self.url.as_str().to_string() } /// Call the Ethereum RPC endpoint with the given JSON-RPC payload - async fn call_ethereum(&self, value: Value) -> Result { - let (mut socket, _) = - connect_async(&self.get_url()).await.map_err(|e| anyhow::anyhow!("WebSocket connect error: {}", e))?; - - let request = serde_json::to_string(&value)?; - socket.send(Message::Text(request)).await.map_err(|e| anyhow::anyhow!("WebSocket send error: {}", e))?; - - if let Some(message) = socket.next().await { - let message = message.map_err(|e| anyhow::anyhow!("WebSocket message error: {}", e))?; - if let Message::Text(text) = message { - let response: Value = serde_json::from_str(&text)?; - return Ok(response["result"].clone()); - } - } - - Err(anyhow::anyhow!("No response received from WebSocket")) + pub async fn call_ethereum(&self, method: &str, params: Vec) -> Result> { + let response: Value = self.provider.request(method, params).await?; + Ok(response) } - /// Subscribes to a specific event from the Starknet core contract - async fn get_eth_subscribe(&self, topics: Vec) -> Result { - let address = starknet_core_address::MAINNET; - - let params = serde_json::json!({ - "address": address, - "topics": topics - }); - - let payload = serde_json::json!({ - "jsonrpc": "2.0", - "method": "eth_subscribe", - "params": ["logs", params], - "id": 1 - }); - - let response = match self.call_ethereum(payload).await { - Ok(response) => response, - Err(_e) => return Err(Felt252WrapperError::InvalidCharacter), - }; - - Ok(response.to_string()) + /// Retrieves the latest Ethereum block number + pub async fn get_latest_block_number(&self) -> Result> { + let block_number = self.provider.get_block_number().await?; + Ok(block_number.as_u64().into()) } - pub async fn listen_and_update_state( - wss_url: Url, - subscription_id: &str, - tx: Sender, - ) -> Result<(), Box> { - let (ws_stream, _) = connect_async(wss_url).await?; - let mut ws_stream = ws_stream; - - while let Some(message) = futures::StreamExt::next(&mut ws_stream).await { - let message = message?; - - if message.is_text() || message.is_binary() { - let data = message.into_text()?; - let event = serde_json::from_str::(&data)?; - println!("ethereum: {:?}", data); - if subscription_id != subscription_id { - tx.send(event.clone()).await.unwrap(); - } - } + /// Get the block number of the last occurrence of a given event. + pub async fn get_last_event_block_number(&self) -> Result> { + let topic = H256::from_slice(&hex::decode(&LOG_STATE_UPDTATE_TOPIC[2..])?); + let address = + Address::from_str(starknet_core_address::MAINNET).map_err(|e| format!("Failed to parse address: {}", e))?; + let latest_block = self.get_latest_block_number().await.expect("Failed to retrieve latest block number"); + + // Assuming an avg Block time of 15sec we check for a LogStateUpdate occurence in the last ~24h + let filter = Filter::new() + .from_block(latest_block - 6000) + .to_block(EthBlockNumber::Latest) + .address(vec![address]) + .topic0(topic); + + let logs = self.provider.get_logs(&filter).await?; + + if let Some(last_log) = logs.last() { + let last_block = last_log.block_number.ok_or("No block number in log")?; + Ok(last_block.as_u64()) + } else { + Err("No events found".into()) } - - Ok(()) } - /// Generates a specific eth_call to the Starknet core contract - async fn get_eth_call(&self, data: &str) -> Result { - let payload = serde_json::json!({ - "jsonrpc": "2.0", - "method": "eth_call", - "params": [ - { - "to": starknet_core_address::MAINNET, - "data": data - }, - "latest" - ], - "id": 2 - }); - - let response = match self.call_ethereum(payload).await { - Ok(response) => response, - Err(_e) => return Err(Felt252WrapperError::InvalidCharacter), - }; - - let hex_str = match response.as_str() { - Some(hex) => hex, - None => return Err(Felt252WrapperError::InvalidCharacter), - }; - - let hex_str = hex_str.trim_start_matches("0x"); - - Felt252Wrapper::from_hex_be(hex_str) + /// Get the last Starknet block number verified on L1 + pub async fn get_last_block_number(&self) -> Result { + let data = decode("35befa5d")?; + let to: Address = starknet_core_address::MAINNET.parse()?; + let tx_request = TransactionRequest::new().to(to).data(data); + let tx = TypedTransaction::Legacy(tx_request); + let result = self.provider.call(&tx, None).await.expect("Failed to get last block number"); + let result_str = result.to_string(); + let hex_str = result_str.trim_start_matches("Bytes(0x").trim_end_matches(")").trim_start_matches("0x"); + + let block_number = u64::from_str_radix(hex_str, 16).expect("Failed to parse block number"); + Ok(block_number) } /// Get the last Starknet state root verified on L1 - pub async fn get_last_state_root(&self) -> Result { - let data = "0x9588eca2"; - let state_commitment = self.get_eth_call(data).await?; - Ok(state_commitment.into()) - } - - /// Get the last Starknet block number verified on L1 - pub async fn get_last_block_number(&self) -> Result { - let data = "0x35befa5d"; - let block_number_result = self.get_eth_call(data).await; - let block_number = block_number_result?; - - match u64::try_from(block_number) { - Ok(val) => Ok(BlockNumber(val)), - Err(_) => Err(Felt252WrapperError::FromArrayError.into()), - } + pub async fn get_last_state_root(&self) -> Result { + let data = decode("9588eca2")?; + let to: Address = starknet_core_address::MAINNET.parse()?; + let tx_request = TransactionRequest::new().to(to).data(data); + let tx = TypedTransaction::Legacy(tx_request); + let result = self.provider.call(&tx, None).await.expect("Failed to get last state root"); + Ok(StarkHash::from(Felt252Wrapper::from_hex_be(&result.to_string()).expect("Failed to parse state root"))) } /// Get the last Starknet block hash verified on L1 - pub async fn get_last_block_hash(&self) -> Result { - let data = "0x382d83e3"; - - // Use `?` to propagate the error if `get_generic_call` results in an Err - let block_hash_result = self.get_eth_call(data).await; - let block_hash = block_hash_result?; - - // Now we have a block hash and can try to convert it - match Felt252Wrapper::try_from(block_hash) { - Ok(val) => Ok(BlockHash(val.into())), - Err(_) => Err(Felt252WrapperError::FromArrayError.into()), - } + pub async fn get_last_block_hash(&self) -> Result { + let data = decode("0x382d83e3")?; + let to: Address = starknet_core_address::MAINNET.parse()?; + let tx_request = TransactionRequest::new().to(to).data(data); + let tx = TypedTransaction::Legacy(tx_request); + let result = self.provider.call(&tx, None).await.expect("Failed to get last block hash"); + Ok(StarkHash::from(Felt252Wrapper::from_hex_be(&result.to_string()).expect("Failed to parse block hash"))) } /// Get the last Starknet state update verified on the L1 pub async fn get_initial_state(client: &EthereumClient) -> Result { - let global_root = client.get_last_state_root().await.map_err(|e| { - log::error!("Failed to get last state root: {}", e); - () - })?; - let block_number = client.get_last_block_number().await.map_err(|e| { log::error!("Failed to get last block number: {}", e); () })?; - let block_hash = client.get_last_block_hash().await.map_err(|e| { log::error!("Failed to get last block hash: {}", e); () })?; + let global_root = client.get_last_state_root().await.map_err(|e| { + log::error!("Failed to get last state root: {}", e); + () + })?; Ok(L1StateUpdate { global_root, block_number, block_hash }) } + + /// Subscribes to the LogStateUpdate event from the Starknet core contract and store latest + /// verified state + pub async fn listen_and_update_state(&self, start_block: u64) -> Result<(), Box> { + let client = self.provider.clone(); + let address: Address = starknet_core_address::MAINNET.parse().expect("Failed to parse Starknet core address"); + abigen!( + StarknetCore, + "crates/client/deoxys/src/utils/abis/starknet_core.json", + event_derives(serde::Deserialize, serde::Serialize) + ); + let contract = StarknetCore::new(address, client); + + let event_filter = contract.event::().from_block(start_block).to_block(EthBlockNumber::Latest); + + let mut event_stream = event_filter.stream().await.expect("Failed to initiate event stream"); + + while let Some(event_result) = event_stream.next().await { + match event_result { + Ok(log) => { + println!("Log event in log format: {:?}", log.clone()); + let format_event = + event_to_l1_state_update(log.clone()).expect("Failed to format event into an L1StateUpdate"); + println!("LogStateUpdate event: {:?}, format event: {:?}", log, format_event.clone()); + update_l1(format_event); + } + Err(e) => println!("Error while listening for events: {:?}", e), + } + } + + Ok(()) + } } /// Update the L1 state with the latest data @@ -235,8 +194,8 @@ pub fn update_l1(state_update: L1StateUpdate) { log::info!( "🔄 Updated L1 head: Number: #{}, Hash: {}, Root: {}", state_update.block_number, - format_address(&state_update.block_hash.to_string()), - format_address(&encode(state_update.global_root.0.to_bytes_be())) + state_update.block_hash, + state_update.global_root ); { @@ -248,11 +207,8 @@ pub fn update_l1(state_update: L1StateUpdate) { /// Verify the L1 state with the latest data pub async fn verify_l1(state_update: L1StateUpdate, rpc_port: u16) -> Result<(), String> { - // Minimize the scope of the lock - let starknet_state_block_number = { - let starknet_state = STARKNET_STATE_UPDATE.lock().map_err(|e| e.to_string())?; - starknet_state.block_number - }; + let starknet_state = STARKNET_STATE_UPDATE.lock().map_err(|e| e.to_string())?; + let starknet_state_block_number = starknet_state.block_number; // Check if the node reached the latest verified state on Ethereum if state_update.block_number > starknet_state_block_number { @@ -260,62 +216,143 @@ pub async fn verify_l1(state_update: L1StateUpdate, rpc_port: u16) -> Result<(), } if state_update.block_number <= starknet_state_block_number { - let current_state_update = get_state_update_at(rpc_port, state_update.block_number.0) + let current_state_update = get_state_update_at(rpc_port, state_update.block_number) .await .map_err(|e| format!("Error retrieving state update: {}", e))?; - // Verifying Block Hash and State Root against L2 - match ( - current_state_update.global_root == state_update.global_root, - current_state_update.block_hash == state_update.block_hash, - ) { - (false, _) => Err("🚨 L1 state verification failed: State root does not match".into()), - (_, false) => Err("🚨 L1 state verification failed: Block hash does not match".into()), - (true, true) => { - log::info!( - "✅ Verified L2 state via L1: #{}, Hash: {}, Root: {}", - state_update.block_number, - format_address(&state_update.block_hash.to_string()), - format_address(&encode(state_update.global_root.0.to_bytes_be())) - ); - Ok(()) - } + // Verifying Block Number, Block Hash and State Root against L2 + if current_state_update.block_number != state_update.block_number + || current_state_update.global_root != state_update.global_root + || current_state_update.block_hash != state_update.block_hash + { + return Err("🚨 L1 state verification failed: Verification mismatch".into()); } - } else { - Ok(()) + + log::info!( + "✅ Verified L2 state via L1: #{}, Hash: {}, Root: {}", + state_update.block_number, + state_update.block_hash, + state_update.global_root + ); } + + Ok(()) } /// Syncronize with the L1 latest state updates -pub async fn sync(l1_url: Url, rpc_port: u16) { - let (tx, mut rx) = mpsc::channel(32); - - let client = match EthereumClient::new(l1_url) { - Ok(client) => client, - Err(e) => { - log::error!("Failed to create EthereumClient: {}", e); - return; - } - }; +pub async fn sync(l1_url: Url) { + let client = EthereumClient::new(l1_url).await.expect("Failed to create EthereumClient"); log::info!("🚀 Subscribed to L1 state verification"); - // Get and store the latest state + // Get and store the latest verified state let initial_state = match EthereumClient::get_initial_state(&client).await { Ok(state) => state, Err(_) => return, }; - update_l1(initial_state); // Listen to LogStateUpdate (0x77552641) update and send changes continusly - let wss_url = client.get_wss().unwrap(); - let subscription_id = client.get_eth_subscribe(vec!["0x77552641".to_string()]).await.unwrap(); - EthereumClient::listen_and_update_state(wss_url, &subscription_id, tx).await.unwrap(); - - // Verify the latest state roots and block against L2 - while let Some(new_state_update) = rx.recv().await { - let _ = verify_l1(new_state_update.clone(), rpc_port).await; - update_l1(new_state_update); + let start_block = + EthereumClient::get_last_event_block_number(&client).await.expect("Failed to retrieve last event block number"); + EthereumClient::listen_and_update_state(&client, start_block).await.unwrap(); +} + +#[cfg(test)] +mod l1_sync_tests { + use ethers::contract::EthEvent; + use ethers::core::types::*; + use ethers::prelude::*; + use ethers::providers::Provider; + use tokio; + use url::Url; + + use super::*; + use crate::l1::EthereumClient; + + #[derive(Clone, Debug, EthEvent)] + pub struct Transfer { + #[ethevent(indexed)] + pub from: Address, + #[ethevent(indexed)] + pub to: Address, + pub tokens: U256, + } + + pub mod eth_rpc { + pub const MAINNET: &str = ""; + } + + #[tokio::test] + async fn test_starting_block() { + let url = Url::parse(eth_rpc::MAINNET).expect("Failed to parse URL"); + let client = EthereumClient::new(url).await.expect("Failed to create EthereumClient"); + + let start_block = + EthereumClient::get_last_event_block_number(&client).await.expect("Failed to get last event block number"); + println!("The latest emission of the LogStateUpdate event was on block: {:?}", start_block); + } + + #[tokio::test] + async fn test_initial_state() { + let url = Url::parse(eth_rpc::MAINNET).expect("Failed to parse URL"); + let client = EthereumClient::new(url).await.expect("Failed to create EthereumClient"); + + let initial_state = EthereumClient::get_initial_state(&client).await.expect("Failed to get initial state"); + assert!(!initial_state.global_root.0.is_empty(), "Global root should not be empty"); + assert!(!initial_state.block_number > 0, "Block number should be greater than 0"); + assert!(!initial_state.block_hash.0.is_empty(), "Block hash should not be empty"); + } + + #[tokio::test] + async fn test_event_subscription() -> Result<(), Box> { + abigen!( + IERC20, + r#"[ + function totalSupply() external view returns (uint256) + function balanceOf(address account) external view returns (uint256) + function transfer(address recipient, uint256 amount) external returns (bool) + function allowance(address owner, address spender) external view returns (uint256) + function approve(address spender, uint256 amount) external returns (bool) + function transferFrom( address sender, address recipient, uint256 amount) external returns (bool) + event Transfer(address indexed from, address indexed to, uint256 value) + event Approval(address indexed owner, address indexed spender, uint256 value) + ]"#, + ); + + const WETH_ADDRESS: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; + + let provider = Provider::::try_from(eth_rpc::MAINNET)?; + let client = Arc::new(provider); + let address: Address = WETH_ADDRESS.parse()?; + let contract = IERC20::new(address, client); + + let event = contract.event::().from_block(0).to_block(EthBlockNumber::Latest); + + let mut event_stream = event.stream().await?; + + while let Some(event_result) = event_stream.next().await { + match event_result { + Ok(log) => { + println!("Transfer event: {:?}", log); + } + Err(e) => println!("Error while listening for events: {:?}", e), + } + } + + Ok(()) + } + + #[tokio::test] + async fn listen_and_update_state() -> Result<(), Box> { + let client = EthereumClient::new(Url::parse(eth_rpc::MAINNET).expect("Failed to parse rpc url")) + .await + .expect("Failed to create EthereumClient"); + let start_block = EthereumClient::get_last_event_block_number(&client) + .await + .expect("Failed to retrieve last event block number"); + EthereumClient::listen_and_update_state(&client, start_block).await.unwrap(); + + Ok(()) } } diff --git a/crates/client/deoxys/src/l2.rs b/crates/client/deoxys/src/l2.rs index e42654f113..c250f33d86 100644 --- a/crates/client/deoxys/src/l2.rs +++ b/crates/client/deoxys/src/l2.rs @@ -6,7 +6,6 @@ use std::time::Duration; use itertools::Itertools; use mc_storage::OverrideHandle; use mp_block::state_update::StateUpdateWrapper; -use mp_commitments::StateCommitment; use mp_contract::class::{ClassUpdateWrapper, ContractClassData, ContractClassWrapper}; use mp_felt::Felt252Wrapper; use mp_storage::StarknetStorageSchemaVersion; @@ -17,7 +16,7 @@ use sp_runtime::generic::{Block, Header}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT}; use sp_runtime::OpaqueExtrinsic; use starknet_api::api_core::ClassHash; -use starknet_api::block::{BlockHash, BlockNumber}; +use starknet_api::hash::StarkHash; use starknet_core::types::BlockId as BlockIdCore; use starknet_ff::FieldElement; use starknet_providers::sequencer::models::state_update::{DeclaredContract, DeployedContract}; @@ -32,17 +31,17 @@ use crate::CommandSink; /// Contains the Starknet verified state on L2 #[derive(Debug, Clone, Deserialize)] pub struct L2StateUpdate { - pub global_root: StateCommitment, - pub block_number: BlockNumber, - pub block_hash: BlockHash, + pub block_number: u64, + pub global_root: StarkHash, + pub block_hash: StarkHash, } lazy_static! { /// Shared latest L2 state update verified on L2 pub static ref STARKNET_STATE_UPDATE: Arc> = Arc::new(Mutex::new(L2StateUpdate { - global_root: StateCommitment::default(), - block_number: BlockNumber::default(), - block_hash: BlockHash::default(), + block_number: u64::default(), + global_root: StarkHash::default(), + block_hash: StarkHash::default(), })); } diff --git a/crates/client/deoxys/src/lib.rs b/crates/client/deoxys/src/lib.rs index 5a329543ad..c9d5b77d1c 100644 --- a/crates/client/deoxys/src/lib.rs +++ b/crates/client/deoxys/src/lib.rs @@ -34,7 +34,7 @@ pub mod starknet_sync_worker { let first_block = utility::get_last_synced_block(rpc_port).await + 1; let _ = tokio::join!( - l1::sync(l1_url.clone(), rpc_port), + l1::sync(l1_url.clone()), l2::sync(sender_config, fetch_config.clone(), first_block, rpc_port, backend.clone()) ); } diff --git a/crates/client/deoxys/src/utils/abis/starknet_core.json b/crates/client/deoxys/src/utils/abis/starknet_core.json new file mode 100644 index 0000000000..7d11de1021 --- /dev/null +++ b/crates/client/deoxys/src/utils/abis/starknet_core.json @@ -0,0 +1,658 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "changedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldConfigHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newConfigHash", + "type": "uint256" + } + ], + "name": "ConfigHashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "ConsumedMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "ConsumedMessageToL2", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "Finalized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "fromAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + } + ], + "name": "LogMessageToL1", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "LogMessageToL2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "acceptedGovernor", + "type": "address" + } + ], + "name": "LogNewGovernorAccepted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "nominatedGovernor", + "type": "address" + } + ], + "name": "LogNominatedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "LogNominationCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "LogOperatorRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "removedGovernor", + "type": "address" + } + ], + "name": "LogRemovedGovernor", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "stateTransitionFact", + "type": "bytes32" + } + ], + "name": "LogStateTransitionFact", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "globalRoot", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "blockNumber", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "blockHash", + "type": "uint256" + } + ], + "name": "LogStateUpdate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2Canceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "fromAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "toAddress", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "selector", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "payload", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + } + ], + "name": "MessageToL2CancellationStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "changedBy", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldProgramHash", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newProgramHash", + "type": "uint256" + } + ], + "name": "ProgramHashChanged", + "type": "event" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "toAddress", "type": "uint256" }, + { "internalType": "uint256", "name": "selector", "type": "uint256" }, + { "internalType": "uint256[]", "name": "payload", "type": "uint256[]" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" } + ], + "name": "cancelL1ToL2Message", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "configHash", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "fromAddress", "type": "uint256" }, + { "internalType": "uint256[]", "name": "payload", "type": "uint256[]" } + ], + "name": "consumeMessageFromL2", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getMaxL1MsgFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "identify", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes", "name": "data", "type": "bytes" }], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isFinalized", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isFrozen", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "isOperator", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "msgHash", "type": "bytes32" } + ], + "name": "l1ToL2MessageCancellations", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1ToL2MessageNonce", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "msgHash", "type": "bytes32" } + ], + "name": "l1ToL2Messages", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "msgHash", "type": "bytes32" } + ], + "name": "l2ToL1Messages", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "messageCancellationDelay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "programHash", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newOperator", "type": "address" } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "toAddress", "type": "uint256" }, + { "internalType": "uint256", "name": "selector", "type": "uint256" }, + { "internalType": "uint256[]", "name": "payload", "type": "uint256[]" } + ], + "name": "sendMessageToL2", + "outputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newConfigHash", "type": "uint256" } + ], + "name": "setConfigHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "delayInSeconds", "type": "uint256" } + ], + "name": "setMessageCancellationDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newProgramHash", "type": "uint256" } + ], + "name": "setProgramHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetAcceptGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "starknetCancelNomination", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "starknetIsGovernor", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "newGovernor", "type": "address" } + ], + "name": "starknetNominateNewGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "governorForRemoval", + "type": "address" + } + ], + "name": "starknetRemoveGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "toAddress", "type": "uint256" }, + { "internalType": "uint256", "name": "selector", "type": "uint256" }, + { "internalType": "uint256[]", "name": "payload", "type": "uint256[]" }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" } + ], + "name": "startL1ToL2MessageCancellation", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockHash", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateBlockNumber", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stateRoot", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "removedOperator", + "type": "address" + } + ], + "name": "unregisterOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "programOutput", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "onchainDataHash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "onchainDataSize", + "type": "uint256" + } + ], + "name": "updateState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/crates/client/deoxys/src/utils/abis/usdc.json b/crates/client/deoxys/src/utils/abis/usdc.json new file mode 100644 index 0000000000..81829eebae --- /dev/null +++ b/crates/client/deoxys/src/utils/abis/usdc.json @@ -0,0 +1,74 @@ +[ + { + "constant": false, + "inputs": [{ "name": "newImplementation", "type": "address" }], + "name": "upgradeTo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "newImplementation", "type": "address" }, + { "name": "data", "type": "bytes" } + ], + "name": "upgradeToAndCall", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "implementation", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [{ "name": "newAdmin", "type": "address" }], + "name": "changeAdmin", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "admin", + "outputs": [{ "name": "", "type": "address" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "name": "_implementation", "type": "address" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "payable": true, "stateMutability": "payable", "type": "fallback" }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "name": "previousAdmin", "type": "address" }, + { "indexed": false, "name": "newAdmin", "type": "address" } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "name": "implementation", "type": "address" } + ], + "name": "Upgraded", + "type": "event" + } +] diff --git a/crates/client/deoxys/src/utils/constant.rs b/crates/client/deoxys/src/utils/constant.rs new file mode 100644 index 0000000000..d4d3e7b348 --- /dev/null +++ b/crates/client/deoxys/src/utils/constant.rs @@ -0,0 +1,11 @@ +//! Deoxys constants. + +pub mod starknet_core_address { + pub const MAINNET: &str = "0xc662c410C0ECf747543f5bA90660f6ABeBD9C8c4"; + pub const GOERLI_TESTNET: &str = "0xde29d060D45901Fb19ED6C6e959EB22d8626708e"; + pub const GOERLI_INTEGRATION: &str = "0xd5c325D183C592C94998000C5e0EED9e6655c020"; + pub const SEPOLIA_TESTNET: &str = "0xE2Bb56ee936fd6433DC0F6e7e3b8365C906AA057"; + pub const SEPOLIA_INTEGRATION: &str = "0x4737c0c1B4D5b1A687B42610DdabEE781152359c"; +} + +pub const LOG_STATE_UPDTATE_TOPIC: &str = "0xd342ddf7a308dec111745b00315c14b7efb2bdae570a6856e088ed0c65a3576c"; diff --git a/crates/client/deoxys/src/utils/mod.rs b/crates/client/deoxys/src/utils/mod.rs index 98e289524a..2ab007a115 100644 --- a/crates/client/deoxys/src/utils/mod.rs +++ b/crates/client/deoxys/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod constant; pub mod convert; #[cfg(feature = "m")] pub mod m; diff --git a/crates/client/deoxys/src/utils/utility.rs b/crates/client/deoxys/src/utils/utility.rs index 219fa8fcc9..7b52485658 100644 --- a/crates/client/deoxys/src/utils/utility.rs +++ b/crates/client/deoxys/src/utils/utility.rs @@ -1,16 +1,20 @@ -//! Utility functions. +//! Utility functions for Deoxys. + use std::error::Error; use std::thread::sleep; use std::time::Duration; +use ethers::types::I256; use rand::seq::SliceRandom; use rand::thread_rng; use reqwest::header; use serde_json::{json, Value}; +use starknet_api::hash::StarkFelt; use starknet_ff::FieldElement; use starknet_providers::sequencer::models::BlockId; use starknet_providers::SequencerGatewayProvider; +use crate::l1::{L1StateUpdate, LogStateUpdate}; use crate::l2::{L2StateUpdate, STARKNET_HIGHEST_BLOCK_HASH_AND_NUMBER}; // TODO: secure the auto calls here @@ -185,3 +189,25 @@ pub fn format_address(address: &str) -> String { formatted_address } } + +pub fn event_to_l1_state_update(log_state_update: LogStateUpdate) -> Result { + let block_number_u64 = if log_state_update.block_number >= I256::from(0) { + log_state_update.block_number.low_u64() + } else { + return Err("Block number is negative"); + }; + + let global_root_u128 = log_state_update.global_root.low_u128(); + let block_hash_u128 = log_state_update.block_hash.low_u128(); + + if global_root_u128 as u128 != log_state_update.global_root.low_u128() + || block_hash_u128 as u128 != log_state_update.block_hash.low_u128() + { + return Err("Conversion from U256 to u128 resulted in data loss"); + } + + let global_root = StarkFelt::from(global_root_u128); + let block_hash = StarkFelt::from(block_hash_u128); + + Ok(L1StateUpdate { block_number: block_number_u64, global_root, block_hash }) +} diff --git a/crates/client/l1-messages/Cargo.toml b/crates/client/l1-messages/Cargo.toml deleted file mode 100644 index 2e967808d2..0000000000 --- a/crates/client/l1-messages/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "mc-l1-messages" -version = "0.1.0" -description = "L1 Messages processing library." -homepage = "https://github.com/keep-starknet-strange/madara" -edition = "2021" -license = "MIT" -publish = false -repository = "https://github.com/keep-starknet-strange/madara" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -log = { workspace = true } -rustc-hex = "2.1.0" -url = "2.5.0" - -# Madara RuntimeApi -pallet-starknet-runtime-api = { workspace = true, default-features = true } - -# Starknet dependencies -starknet_api = { workspace = true, default-features = true } - -# Starknet -mc-db = { workspace = true, default-features = true } - -# Madara Primitives -mp-felt = { workspace = true, default-features = true } -mp-transactions = { workspace = true, default-features = true } - -# Substrate Primitives -sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } - -# Substrate client dependencies -sc-client-api = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } - -# Zaun -starknet-core-contract-client = { workspace = true } - -# Other third party dependencies -ethers = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } diff --git a/crates/client/l1-messages/src/config.rs b/crates/client/l1-messages/src/config.rs deleted file mode 100644 index e2434a2a0e..0000000000 --- a/crates/client/l1-messages/src/config.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::fs::File; -use std::path::PathBuf; - -use ethers::types::Address; -use rustc_hex::FromHexError; -use serde::{Deserialize, Deserializer}; -use url::Url; - -#[derive(thiserror::Error, Debug)] -pub enum L1MessagesWorkerConfigError { - #[error("File with L1 Messages Worker config not found: {0}")] - FileNotFound(#[from] std::io::Error), - #[error("Failed to deserialize L1 Messages Worker Config from config file: {0}")] - InvalidFile(#[from] serde_json::Error), - #[error("Invalid Ethereum Provided Url: {0}")] - InvalidProviderUrl(#[from] url::ParseError), - #[error("Invalid L1 Contract Address: {0}")] - InvalidContractAddress(#[from] FromHexError), -} - -#[derive(Clone, PartialEq, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct L1MessagesWorkerConfig { - #[serde(deserialize_with = "deserialize_url")] - http_provider: Url, - contract_address: Address, -} - -fn deserialize_url<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let endpoint: String = String::deserialize(deserializer)?; - - Url::parse(&endpoint).map_err(serde::de::Error::custom) -} - -impl L1MessagesWorkerConfig { - pub fn new(http_provider: Url, contract_address: Address) -> Self { - Self { http_provider, contract_address } - } - - pub fn new_from_file(path: &PathBuf) -> Result { - let file = File::open(path)?; - let config = serde_json::from_reader(file)?; - Ok(config) - } - - pub fn new_from_params(provider_url: &str, contract_address: &str) -> Result { - let http_provider = Url::parse(provider_url)?; - let contract_address = contract_address.parse()?; - Ok(Self { http_provider, contract_address }) - } - - pub fn provider(&self) -> &Url { - &self.http_provider - } - - pub fn contract_address(&self) -> &Address { - &self.contract_address - } -} diff --git a/crates/client/l1-messages/src/contract.rs b/crates/client/l1-messages/src/contract.rs deleted file mode 100644 index dbc3129511..0000000000 --- a/crates/client/l1-messages/src/contract.rs +++ /dev/null @@ -1,43 +0,0 @@ -use mp_felt::{Felt252Wrapper, Felt252WrapperError}; -use mp_transactions::HandleL1MessageTransaction; -use starknet_core_contract_client::interfaces::LogMessageToL2Filter; - -#[derive(thiserror::Error, Debug, PartialEq)] -#[allow(clippy::enum_variant_names)] -pub enum L1EventToTransactionError { - #[error("Failed to convert Calldata param from L1 Event: `{0}`")] - InvalidCalldata(Felt252WrapperError), - #[error("Failed to convert Contract Address from L1 Event: `{0}`")] - InvalidContractAddress(Felt252WrapperError), - #[error("Failed to convert Entrypoint Selector from L1 Event: `{0}`")] - InvalidEntryPointSelector(Felt252WrapperError), - #[error("Failed to convert Nonce param from L1 Event: `{0}`")] - InvalidNonce(Felt252WrapperError), -} - -pub fn parse_handle_l1_message_transaction( - event: LogMessageToL2Filter, -) -> Result { - // L2 contract to call. - let contract_address = Felt252Wrapper::try_from(sp_core::U256(event.to_address.0)) - .map_err(L1EventToTransactionError::InvalidContractAddress)?; - - // Function of the contract to call. - let entry_point_selector = Felt252Wrapper::try_from(sp_core::U256(event.selector.0)) - .map_err(L1EventToTransactionError::InvalidEntryPointSelector)?; - - // L1 message nonce. - let nonce: u64 = Felt252Wrapper::try_from(sp_core::U256(event.nonce.0)) - .map_err(L1EventToTransactionError::InvalidNonce)? - .try_into() - .map_err(L1EventToTransactionError::InvalidNonce)?; - - let calldata: Vec = event - .payload - .iter() - .map(|param| Felt252Wrapper::try_from(sp_core::U256(param.0))) - .collect::, Felt252WrapperError>>() - .map_err(L1EventToTransactionError::InvalidCalldata)?; - - Ok(HandleL1MessageTransaction { nonce, contract_address, entry_point_selector, calldata }) -} diff --git a/crates/client/l1-messages/src/error.rs b/crates/client/l1-messages/src/error.rs deleted file mode 100644 index c77fadef55..0000000000 --- a/crates/client/l1-messages/src/error.rs +++ /dev/null @@ -1,25 +0,0 @@ -use mc_db::DbError; -use sp_api::ApiError; -use url::ParseError; - -use crate::contract::L1EventToTransactionError; - -#[derive(thiserror::Error, Debug)] -pub enum L1MessagesWorkerError { - #[error("Failed to initialize L1 Messages Worker based on provided Config: `{0}`")] - ConfigError(#[from] ParseError), - #[error("Failed to convert transaction via Runtime API: `{0}`")] - ConvertTransactionRuntimeApiError(ApiError), - #[error("Madara Messaging DB Error: `{0}`")] - DatabaseError(#[from] DbError), - #[error("Message from L1 has been already processed, nonce: `{0}`")] - L1MessageAlreadyProcessed(u64), - #[error("Failed to use Runtime API: `{0}`")] - RuntimeApiError(ApiError), - #[error("Failed to submit transaction into Transaction Pool")] - SubmitTxError(#[source] PE), - #[error("Failed to convert L1 Message into Fee")] - ToFeeError, - #[error("Failed to convert L1 Message into L2 Transaction: `{0}`")] - ToTransactionError(#[from] L1EventToTransactionError), -} diff --git a/crates/client/l1-messages/src/lib.rs b/crates/client/l1-messages/src/lib.rs deleted file mode 100644 index fd0f1b4f9d..0000000000 --- a/crates/client/l1-messages/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod config; -pub mod error; -pub mod worker; - -mod contract; diff --git a/crates/client/l1-messages/src/worker.rs b/crates/client/l1-messages/src/worker.rs deleted file mode 100644 index 44ba9c352a..0000000000 --- a/crates/client/l1-messages/src/worker.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::sync::Arc; - -use ethers::providers::{Http, Provider, StreamExt}; -use ethers::types::U256; -use mp_transactions::HandleL1MessageTransaction; -use pallet_starknet_runtime_api::{ConvertTransactionRuntimeApi, StarknetRuntimeApi}; -use sc_client_api::HeaderBackend; -use sc_transaction_pool_api::{TransactionPool, TransactionSource}; -use sp_api::ProvideRuntimeApi; -use sp_runtime::traits::Block as BlockT; -use starknet_api::api_core::Nonce; -use starknet_api::hash::StarkFelt; -use starknet_api::transaction::Fee; -use starknet_core_contract_client::interfaces::{LogMessageToL2Filter, StarknetMessagingEvents}; - -use crate::config::L1MessagesWorkerConfig; -use crate::contract::parse_handle_l1_message_transaction; -use crate::error::L1MessagesWorkerError; - -const TX_SOURCE: TransactionSource = TransactionSource::External; - -pub async fn run_worker( - config: L1MessagesWorkerConfig, - client: Arc, - pool: Arc

, - backend: Arc>, -) where - B: BlockT, - C: ProvideRuntimeApi + HeaderBackend, - C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, - P: TransactionPool + 'static, -{ - log::info!("⟠ Starting L1 Messages Worker with settings: {:?}", config); - - let event_listener = StarknetMessagingEvents::new( - *config.contract_address(), - Arc::new(Provider::new(Http::new(config.provider().clone()))), - ); - - let last_synced_event_block = match backend.messaging().last_synced_l1_block_with_event() { - Ok(blknum) => blknum, - Err(e) => { - log::error!("⟠ Madara Messaging DB unavailable: {:?}", e); - return; - } - }; - - let events = event_listener.event::().from_block(last_synced_event_block.block_number); - let mut event_stream = match events.stream_with_meta().await { - Ok(stream) => stream, - Err(e) => { - log::error!("⟠ Unexpected error with L1 event stream: {:?}", e); - return; - } - }; - - while let Some(event_res) = event_stream.next().await { - if let Ok((event, meta)) = event_res { - log::info!( - "⟠ Processing L1 Message from block: {:?}, transaction_hash: {:?}, log_index: {:?}", - meta.block_number, - meta.transaction_hash, - meta.log_index - ); - - match process_l1_message( - event, - &client, - &pool, - &backend, - &meta.block_number.as_u64(), - &meta.log_index.as_u64(), - ) - .await - { - Ok(Some(tx_hash)) => { - log::info!( - "⟠ L1 Message from block: {:?}, transaction_hash: {:?}, log_index: {:?} submitted, \ - transaction hash on L2: {:?}", - meta.block_number, - meta.transaction_hash, - meta.log_index, - tx_hash - ); - } - Ok(None) => {} - Err(e) => { - log::error!( - "⟠ Unexpected error while processing L1 Message from block: {:?}, transaction_hash: {:?}, \ - log_index: {:?}, error: {:?}", - meta.block_number, - meta.transaction_hash, - meta.log_index, - e - ) - } - } - } - } -} - -async fn process_l1_message( - event: LogMessageToL2Filter, - client: &Arc, - pool: &Arc

, - backend: &Arc>, - l1_block_number: &u64, - event_index: &u64, -) -> Result, L1MessagesWorkerError> -where - B: BlockT, - C: ProvideRuntimeApi + HeaderBackend, - C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, - P: TransactionPool + 'static, - PE: std::error::Error, -{ - // Check against panic - // https://docs.rs/ethers/latest/ethers/types/struct.U256.html#method.as_u128 - let fee: Fee = if event.fee > U256::from_big_endian(&(u128::MAX.to_be_bytes())) { - return Err(L1MessagesWorkerError::ToFeeError); - } else { - Fee(event.fee.as_u128()) - }; - let transaction: HandleL1MessageTransaction = parse_handle_l1_message_transaction(event)?; - - let best_block_hash = client.info().best_hash; - - match client.runtime_api().l1_nonce_unused(best_block_hash, Nonce(StarkFelt::from(transaction.nonce))) { - Ok(true) => Ok(()), - Ok(false) => { - log::debug!("⟠ Event already processed: {:?}", transaction); - return Ok(None); - } - Err(e) => { - log::error!("⟠ Unexpected Runtime Api error: {:?}", e); - Err(L1MessagesWorkerError::RuntimeApiError(e)) - } - }?; - - let extrinsic = client.runtime_api().convert_l1_transaction(best_block_hash, transaction, fee).map_err(|e| { - log::error!("⟠ Failed to convert L1 Transaction via Runtime Api: {:?}", e); - L1MessagesWorkerError::ConvertTransactionRuntimeApiError(e) - })?; - - let tx_hash = pool.submit_one(best_block_hash, TX_SOURCE, extrinsic).await.map_err(|e| { - log::error!("⟠ Failed to submit transaction with L1 Message: {:?}", e); - L1MessagesWorkerError::SubmitTxError(e) - })?; - - backend - .messaging() - .update_last_synced_l1_block_with_event(&mc_db::LastSyncedEventBlock::new( - l1_block_number.to_owned(), - event_index.to_owned(), - )) - .map_err(|e| { - log::error!("⟠ Failed to save last L1 synced block: {:?}", e); - L1MessagesWorkerError::DatabaseError(e) - })?; - - Ok(Some(tx_hash)) -} diff --git a/crates/client/rpc/src/lib.rs b/crates/client/rpc/src/lib.rs index ad0e856065..eddc4a1739 100644 --- a/crates/client/rpc/src/lib.rs +++ b/crates/client/rpc/src/lib.rs @@ -892,7 +892,7 @@ where let block_hash = starknet_block.header().hash::(); let actual_status = if starknet_block.header().block_number - <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number.0 + <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number { BlockStatus::AcceptedOnL1.into() } else { @@ -1179,7 +1179,7 @@ where let starknet_version = starknet_block.header().protocol_version; let actual_status = if starknet_block.header().block_number - <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number.0 + <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number { BlockStatus::AcceptedOnL1.into() } else { @@ -1540,7 +1540,7 @@ where let actual_fee = FieldElement::ZERO; let actual_status = if starknet_block.header().block_number - <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number.0 + <= mc_deoxys::l1::ETHEREUM_STATE_UPDATE.lock().unwrap().block_number { TransactionFinalityStatus::AcceptedOnL1.into() } else { diff --git a/crates/client/settlement/Cargo.toml b/crates/client/settlement/Cargo.toml deleted file mode 100644 index 5a779ead28..0000000000 --- a/crates/client/settlement/Cargo.toml +++ /dev/null @@ -1,65 +0,0 @@ -[package] -name = "mc-settlement" -version = "0.1.0" -description = "Starknet chain settlement." -homepage = "https://github.com/keep-starknet-strange/madara" -edition = "2021" -license = "MIT" -publish = false -repository = "https://github.com/keep-starknet-strange/madara" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -# Async -async-trait = { workspace = true } -futures = { workspace = true } -futures-timer = { workspace = true } - -# Serde -serde = { workspace = true } -serde_json = { workspace = true } - -# Substrate -sc-client-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-arithmetic = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } - -# Starknet -mc-db = { workspace = true, default-features = true } -mp-block = { workspace = true, default-features = true } -mp-digest-log = { workspace = true, features = ["std"] } -mp-felt = { workspace = true, default-features = true } -mp-hashers = { workspace = true, default-features = true } -mp-messages = { workspace = true, default-features = true } -mp-snos-output = { workspace = true, default-features = true } -mp-transactions = { workspace = true, default-features = true } -starknet-crypto = { workspace = true, default-features = true } -starknet_api = { workspace = true, default-features = true } - -# Madara -pallet-starknet-runtime-api = { workspace = true, default-features = true } - -# Ethereum -ethers = { workspace = true } - -# Zaun -starknet-core-contract-client = { workspace = true } - -# Others -log = { workspace = true } -rustc-hex = { workspace = true } -thiserror = { workspace = true } -url = { workspace = true } - -# Optional -clap = { workspace = true, optional = true, features = ["std", "derive"] } - -[features] -default = [] -clap = ["dep:clap"] diff --git a/crates/client/settlement/src/errors.rs b/crates/client/settlement/src/errors.rs deleted file mode 100644 index fab59e6409..0000000000 --- a/crates/client/settlement/src/errors.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::time::Duration; - -use sp_runtime::traits::Block; -use starknet_api::hash::StarkHash; - -use crate::{ethereum, RetryStrategy}; - -/// Settlement error type. -#[derive(thiserror::Error, Debug)] -#[allow(missing_docs)] -pub enum Error { - #[error("Blockchain error: {0}")] - Blockchain(#[from] sp_blockchain::Error), - - #[error("Starknet API error: {0}")] - StarknetApi(#[from] starknet_api::StarknetApiError), - - #[error("Failed to find Madara log: {0}")] - DigestLog(#[from] mp_digest_log::FindLogError), - - #[error("Runtime API error: {0}")] - RuntimeApi(#[from] sp_api::ApiError), - - #[error("Ethereum client error: {0}")] - EthereumClient(#[from] ethereum::errors::Error), - - #[error("Failed to find Substrate block hash for Starknet block #{0}")] - UnknownStarknetBlock(u64), - - #[error("Failed to find Substrate block header for hash: {0}")] - UnknownSubstrateBlock(B::Hash), - - #[error("Unexpected global state root for block #{height}: expected {expected}, got {actual}")] - StateRootMismatch { height: u64, expected: StarkHash, actual: StarkHash }, - - #[error("Unexpected Starknet OS program hash: expected {expected}, got {actual}")] - ProgramHashMismatch { expected: StarkHash, actual: StarkHash }, - - #[error("Unexpected Starknet OS config hash: expected {expected}, got {actual}")] - ConfigHashMismatch { expected: StarkHash, actual: StarkHash }, - - #[error("Starknet state is not initialized yet")] - StateNotInitialized, -} - -pub type Result = std::result::Result>; - -pub struct RetryOnRecoverableErrors { - pub delay: Duration, -} - -impl RetryStrategy for RetryOnRecoverableErrors { - fn can_retry(&self, error: &Error) -> Option { - match error { - // List of non-recoverable errors - Error::StateRootMismatch { .. } => None, - Error::ConfigHashMismatch { .. } => None, - Error::ProgramHashMismatch { .. } => None, - // Otherwise we can continue after some delay - _ => Some(self.delay), - } - } -} diff --git a/crates/client/settlement/src/ethereum/client.rs b/crates/client/settlement/src/ethereum/client.rs deleted file mode 100644 index 00e8c467bc..0000000000 --- a/crates/client/settlement/src/ethereum/client.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::sync::Arc; - -use ethers::types::{Address, TransactionReceipt, I256, U256}; -use starknet_core_contract_client::interfaces::StarknetSovereignContract; -use starknet_core_contract_client::LocalWalletSignerMiddleware; - -use crate::ethereum::errors::{Error, Result}; - -// Starknet core contract is responsible for advancing the rollup state and l1<>l2 messaging. -// Check out https://l2beat.com/scaling/projects/starknet#contracts to get a big picture. -// -// In this scope we work with a subset of methods responsible for querying and updating chain state. -// Starknet state is basically block number + state root hash. -// In order to update the state we need to provide the output of the Starknet OS program, consisting -// of: -// 1. Main part: previous/next state root, block number/hash, config hash, list of l1<>l2 -// messages -// 2. Data availability part: hash and size of the DA blob (the actual data is submitted -// onchain separately) -// -// NOTE that currently we are using a "validium" version of the core contract which does not -// require the DA part. -// -// Starknet OS program is a Cairo program run by the SHARP to prove Starknet state transition. -// SNOS program hash is registered on the Starknet core contract to lock the version: -// * SNOS program sources: https://github.com/starkware-libs/cairo-lang/tree/27a157d761ae49b242026bcbe5fca6e60c1e98bd/src/starkware/starknet/core/os -// * You can calculate program hash by running: cairo-hash-program --program -// build/os_latest.json -// -// SNOS config consists of: -// 1. Config version -// 2. Starknet chain ID -// 3. Fee token address -// -// Read this great overview to learn more about SNOS: -// https://hackmd.io/@pragma/ByP-iux1T - -pub struct StarknetContractClient { - contract: StarknetSovereignContract, -} - -impl StarknetContractClient { - pub fn new(address: Address, client: Arc) -> Self { - Self { contract: StarknetSovereignContract::new(address, client) } - } - - pub async fn state_block_number(&self) -> Result { - self.contract.state_block_number().call().await.map_err(Into::into) - } - - pub async fn state_root(&self) -> Result { - self.contract.state_root().call().await.map_err(Into::into) - } - - pub async fn config_hash(&self) -> Result { - self.contract.config_hash().call().await.map_err(Into::into) - } - - pub async fn program_hash(&self) -> Result { - self.contract.program_hash().call().await.map_err(Into::into) - } - - pub async fn update_state(&self, program_output: Vec) -> Result { - self.contract - .update_state(program_output) - .send() - .await? - .inspect(|s| log::debug!("[ethereum client] pending update_state transaction: {:?}", **s)) - .await? - .ok_or_else(|| Error::MissingTransactionRecepit) - } -} diff --git a/crates/client/settlement/src/ethereum/errors.rs b/crates/client/settlement/src/ethereum/errors.rs deleted file mode 100644 index cd882d9ef0..0000000000 --- a/crates/client/settlement/src/ethereum/errors.rs +++ /dev/null @@ -1,26 +0,0 @@ -use starknet_core_contract_client::LocalWalletSignerMiddleware; - -/// Ethereum client error type. -#[derive(thiserror::Error, Debug)] -#[allow(missing_docs)] -pub enum Error { - #[error("Failed to parse HTTP provider URL: {0}")] - UrlParser(#[from] url::ParseError), - - #[error("Failed to initialize local wallet from private key: {0}")] - LocalWallet(#[from] ethers::signers::WalletError), - - #[error("Failed to parse contract address: {0}")] - HexParser(#[from] rustc_hex::FromHexError), - - #[error("Error while interacting with contract: {0}")] - Contract(#[from] ethers::contract::ContractError), - - #[error("HTTP provider error: {0}")] - Provider(#[from] ethers::providers::ProviderError), - - #[error("Failed to get transaction receipt")] - MissingTransactionRecepit, -} - -pub type Result = std::result::Result; diff --git a/crates/client/settlement/src/ethereum/mod.rs b/crates/client/settlement/src/ethereum/mod.rs deleted file mode 100644 index 7734095c4b..0000000000 --- a/crates/client/settlement/src/ethereum/mod.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub mod client; -pub mod errors; - -use async_trait::async_trait; -pub use client::StarknetContractClient; -use ethers::types::U256; -use mp_snos_output::{SnosCodec, StarknetOsOutput}; -use sp_runtime::traits::Block; -use starknet_api::hash::StarkFelt; - -use crate::{Result, SettlementProvider, StarknetSpec, StarknetState}; - -pub fn convert_u256_to_felt(word: U256) -> Result { - let mut bytes = [0u8; 32]; - word.to_big_endian(bytes.as_mut_slice()); - Ok(StarkFelt::new(bytes)?) -} - -pub fn convert_felt_to_u256(felt: StarkFelt) -> U256 { - U256::from_big_endian(felt.bytes()) -} - -#[async_trait] -impl SettlementProvider for StarknetContractClient { - async fn is_initialized(&self) -> Result { - Ok(U256::zero() != self.program_hash().await?) - } - - async fn get_chain_spec(&self) -> Result { - Ok(StarknetSpec { - program_hash: convert_u256_to_felt(self.program_hash().await?)?, - config_hash: convert_u256_to_felt(self.config_hash().await?)?, - }) - } - - async fn get_state(&self) -> Result { - Ok(StarknetState { - block_number: convert_u256_to_felt(self.state_block_number().await?.into_raw())?, - state_root: convert_u256_to_felt(self.state_root().await?)?, - }) - } - - async fn update_state(&self, program_output: StarknetOsOutput) -> Result<(), B> { - let program_output: Vec = - program_output.into_encoded_vec().into_iter().map(convert_felt_to_u256).collect(); - - let tx_receipt = self.update_state(program_output).await?; - log::trace!("[settlement] State was successfully updated: {:#?}", tx_receipt); - - Ok(()) - } -} diff --git a/crates/client/settlement/src/lib.rs b/crates/client/settlement/src/lib.rs deleted file mode 100644 index 70315e4adc..0000000000 --- a/crates/client/settlement/src/lib.rs +++ /dev/null @@ -1,62 +0,0 @@ -pub mod errors; -pub mod ethereum; -mod sync_state; - -use std::marker::PhantomData; -use std::time::Duration; - -use async_trait::async_trait; -use mp_snos_output::StarknetOsOutput; -use serde::{Deserialize, Serialize}; -use sp_runtime::traits::Block; -use starknet_api::hash::{StarkFelt, StarkHash}; - -use crate::errors::{Error, Result}; - -pub struct SettlementWorker(PhantomData<(B, H, SC)>); - -#[derive(Debug, Copy, Clone, PartialEq)] -#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] -pub enum SettlementLayer { - /// Use Ethereum core contract - Ethereum, -} - -#[async_trait] -pub trait SettlementProvider: Send + Sync { - async fn is_initialized(&self) -> Result; - async fn get_chain_spec(&self) -> Result; - async fn get_state(&self) -> Result; - async fn update_state(&self, program_output: StarknetOsOutput) -> Result<(), B>; -} - -/// Starknet chain identity, contains OS config & program hashes -/// -/// How to calculate program hash: -/// 1. Install Cairo https://docs.cairo-lang.org/quickstart.html -/// 2. Get latest Starknet OS sources (e.g. https://github.com/keep-starknet-strange/snos) -/// 3. Run `cairo-hash-program --program .json` -/// -/// How to calculate config hash: -/// 1. Get Starknet chain ID, which is a string reinterpreted as big number -/// 2. Get Starknet fee token address -/// 3. Calculate Pedersen hash of [CONFIG_HASH_VERSION; chain_id; fee_token_address] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct StarknetSpec { - /// Starknet OS config hash - pub config_hash: StarkHash, - /// Starknet OS program hash - pub program_hash: StarkHash, -} - -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] -pub struct StarknetState { - /// The state commitment after last settled block. - pub state_root: StarkHash, - /// The number (height) of last settled block. - pub block_number: StarkFelt, -} - -pub trait RetryStrategy: Send + Sync { - fn can_retry(&self, error: &Error) -> Option; -} diff --git a/crates/client/settlement/src/sync_state.rs b/crates/client/settlement/src/sync_state.rs deleted file mode 100644 index 316bd7d437..0000000000 --- a/crates/client/settlement/src/sync_state.rs +++ /dev/null @@ -1,285 +0,0 @@ -use std::sync::Arc; - -use futures::StreamExt; -use futures_timer::Delay; -use mp_block::Block as StarknetBlock; -use mp_hashers::HasherT; -use mp_messages::{MessageL1ToL2, MessageL2ToL1}; -use mp_snos_output::StarknetOsOutput; -use mp_transactions::compute_hash::ComputeTransactionHash; -use mp_transactions::Transaction; -use pallet_starknet_runtime_api::StarknetRuntimeApi; -use sc_client_api::BlockchainEvents; -use sp_api::{HeaderT, ProvideRuntimeApi}; -use sp_arithmetic::traits::UniqueSaturatedInto; -use sp_blockchain::HeaderBackend; -use sp_runtime::traits::Block as BlockT; -use starknet_api::hash::StarkHash; -use starknet_api::transaction::TransactionHash; - -use crate::errors::Error; -use crate::{Result, RetryStrategy, SettlementProvider, SettlementWorker, StarknetSpec, StarknetState}; - -impl SettlementWorker -where - B: BlockT, - H: HasherT, - SC: ProvideRuntimeApi + HeaderBackend + BlockchainEvents, - SC::Api: StarknetRuntimeApi, -{ - /// A thread responsible for updating (progressing) Starknet state on the settlement layer. - /// For now we use a simplified setup without validity proofs & DA, but in the future - /// Starknet state contract will also validate state transition against the fact registry. - /// That means we will need to publish state diffs and STARK proof before state update. - /// - /// This is an external loop that is responsible for handling temporary (recoverable) errors. - pub async fn sync_state( - substrate_client: Arc, - settlement_provider: Box>, - madara_backend: Arc>, - retry_strategy: Box>, - ) { - loop { - match Self::sync_state_loop(&substrate_client, settlement_provider.as_ref(), &madara_backend).await { - Ok(()) => { - return; - } - Err(err) => { - log::error!("[settlement] {err}"); - match retry_strategy.can_retry(&err) { - Some(dur) => { - log::info!("[settlement] Retrying after {} ms", dur.as_millis()); - Delay::new(dur).await; - continue; - } - None => panic!("Unrecoverable error in settlement thread: {}", err), - } - } - } - } - } - - /// This is an internal loop that listens to the new finalized blocks - /// and attempts to settle the state. - /// - /// It works as follows: - /// - /// 1. First of all it retrieves the latest settled state - /// 2. Then it starts to listen for new finality notifications - /// 3. For all incoming blocks with height lower than the settled one it checks the state root - /// validity. - /// Inconsistent state root means we have a fatal error, which cannot be resolved automatically. - /// - /// 4. Once it gets up to the tip of the chain it starts to apply new state updates. - /// It is possible that there is a need to apply multiple state updates. - /// - /// Sync state loop operates as long as there are new blocks being finalized. - /// In case chain is stuck it won't update the state, even if there are pending blocks. - /// It is ok, since it's not a normal condition, and generally we expect that the chain will - /// advance indefinitely. - async fn sync_state_loop( - substrate_client: &SC, - settlement_provider: &SP, - madara_backend: &mc_db::Backend, - ) -> Result<(), B> - where - SP: ?Sized + SettlementProvider, - { - if !settlement_provider.is_initialized().await? { - return Err(Error::StateNotInitialized); - } - - let starknet_spec = settlement_provider.get_chain_spec().await?; - log::debug!("[settlement] Starknet chain spec {:?}", starknet_spec); - - // We need to make sure that we are on the same page with the settlement contract. - Self::verify_starknet_spec(substrate_client, &starknet_spec)?; - - let mut last_settled_state = settlement_provider.get_state().await?; - log::debug!("[settlement] Last settled state {:?}", last_settled_state); - - // If we haven't reached the settled level yet (e.g. syncing from scratch) this check will pass. - // But we need to run it again once we are up to speed. - Self::verify_starknet_state(substrate_client, &last_settled_state, madara_backend)?; - - let mut finality_notifications = substrate_client.finality_notification_stream(); - let mut sync_from: u64 = last_settled_state.block_number.try_into()?; - - while let Some(notification) = finality_notifications.next().await { - let block = mp_digest_log::find_starknet_block(notification.header.digest())?; - let sync_to = block.header().block_number; - - if sync_from > sync_to { - log::info!("[settlement] Skipping block {} (already settled)", sync_to); - continue; - } - - if sync_from == sync_to { - log::info!("[settlement] Verifying state root for block {}", sync_to); - Self::verify_starknet_state(substrate_client, &last_settled_state, madara_backend)?; - continue; - } - - log::info!("[settlement] Syncing state for blocks {} -> {}", sync_from, sync_to); - while sync_from < sync_to { - let (next_block, substrate_block_hash) = if sync_from + 1 == sync_to { - // This is a typical scenario when we are up to speed with the chain - (block.clone(), notification.hash) - } else { - Self::get_starknet_block(substrate_client, sync_from + 1)? - }; - - let new_state = Self::update_starknet_state( - substrate_client, - settlement_provider, - &last_settled_state, - &next_block, - substrate_block_hash, - starknet_spec.config_hash, - madara_backend, - ) - .await?; - - log::debug!("[settlement] State transitioned to {:?}", new_state); - last_settled_state = new_state; - sync_from += 1; - } - } - - Ok(()) - } - - /// Returns Starknet block given it's height (level, number). - /// The trick here is that Starknet blocks are embedded into Substrate blocks. - /// - /// Firstly, we need to get Substrate block hash by the Starknet block height. - /// This mapping is kept in a separate storage and there is a dedicated thread which maintains - /// it. There might be situations when we cannot resolve the query (e.g. our node is out of - /// sync), but eventually it will be ok. - /// - /// Secondly, we try to find a corresponding Substrate block (header) by its hash. - /// Lastly, we extract Starknet block from the Substrate block digest. - fn get_starknet_block(substrate_client: &SC, block_number: u64) -> Result<(StarknetBlock, B::Hash), B> { - let substrate_block_hash = substrate_client - .hash(UniqueSaturatedInto::unique_saturated_into(block_number))? - .ok_or_else(|| Error::UnknownStarknetBlock(block_number))?; - - let substrate_block_header = substrate_client - .header(substrate_block_hash)? - .ok_or_else(|| Error::UnknownSubstrateBlock(substrate_block_hash))?; - - let starknet_block = mp_digest_log::find_starknet_block(substrate_block_header.digest())?; - - Ok((starknet_block, substrate_block_hash)) - } - - /// Checks that settlement contract is initialized with the same program & config hash as - /// Madara. - fn verify_starknet_spec(substrate_client: &SC, starknet_spec: &StarknetSpec) -> Result<(), B> { - let substrate_hash = substrate_client.info().best_hash; - let program_hash: StarkHash = substrate_client.runtime_api().program_hash(substrate_hash)?.into(); - - if starknet_spec.program_hash != program_hash { - return Err(Error::ProgramHashMismatch { expected: program_hash, actual: starknet_spec.program_hash }); - } - - let config_hash = substrate_client.runtime_api().config_hash(substrate_hash)?; - - if starknet_spec.config_hash != config_hash { - return Err(Error::ConfigHashMismatch { expected: config_hash, actual: starknet_spec.config_hash }); - } - - Ok(()) - } - - /// Tries to verify that the state root for the specified block in Madara storage - /// is the same as in the given state. - /// - /// If Madara chain haven't reached the given block level yet, it returns OK, assuming that as - /// soon as it catches up - this check will be done again. - fn verify_starknet_state( - substrate_client: &SC, - state: &StarknetState, - madara_backend: &mc_db::Backend, - ) -> Result<(), B> { - let height: u64 = state.block_number.try_into()?; - - match Self::get_starknet_block(substrate_client, height) { - Ok((_block, _)) => { - let state_root = madara_backend.temporary_global_state_root_getter(); - // Verify that current onchain state is consistent with corresponding Madara block - if state.state_root != state_root { - return Err(Error::StateRootMismatch { height, expected: state_root, actual: state.state_root }); - } - Ok(()) - } - Err(Error::UnknownStarknetBlock(_)) => Ok(()), - Err(err) => Err(err), - } - } - - /// Aggregates Starknet OS output from a given Starknet block and tries to settle it using a - /// particular provider. - /// - /// "Main part" of Starknet OS program output consists of: - /// - previous state root (at the beginning of the block) - /// - next state root (at the end of the block) - /// - block number - /// - config hash - /// - list of messages transferred between L1 and L2 - /// - /// We construct it using fast execution results, without producing the execution trace which is - /// used for STARK proof. Still it must match the output got from the respective circuit, - /// otherwise the settlement will fail. - async fn update_starknet_state( - substrate_client: &SC, - settlement_provider: &SP, - prev_state: &StarknetState, - next_block: &StarknetBlock, - substrate_block_hash: B::Hash, - config_hash: StarkHash, - madara_backend: &mc_db::Backend, - ) -> Result - where - SP: ?Sized + SettlementProvider, - { - let next_state = StarknetState { - block_number: next_block.header().block_number.into(), - state_root: madara_backend.temporary_global_state_root_getter(), - }; - - let mut messages_to_l1: Vec = Vec::new(); - let mut messages_to_l2: Vec = Vec::new(); - - let chain_id = substrate_client.runtime_api().chain_id(substrate_block_hash)?; - - for tx in next_block.transactions() { - if let Transaction::L1Handler(l1_handler) = tx { - messages_to_l2.push(l1_handler.clone().into()); - } - let tx_hash = - TransactionHash(tx.compute_hash::(chain_id, false, Some(next_block.header().block_number)).into()); - substrate_client - .runtime_api() - .get_tx_messages_to_l1(substrate_block_hash, tx_hash)? - .into_iter() - .for_each(|msg| messages_to_l1.push(msg.into())); - } - - // See https://github.com/starkware-libs/cairo-lang/blob/27a157d761ae49b242026bcbe5fca6e60c1e98bd/src/starkware/starknet/core/os/output.cairo - let program_output = StarknetOsOutput { - prev_state_root: prev_state.state_root, - new_state_root: next_state.state_root, - block_number: next_state.block_number, - block_hash: next_block.header().hash::().into(), - config_hash, - messages_to_l1, - messages_to_l2, - }; - log::trace!("{:#?}", program_output); - - settlement_provider.update_state(program_output).await?; - - Ok(next_state) - } -} diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 77c7782e62..d14692da2c 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -87,10 +87,8 @@ hex = { workspace = true } madara-runtime = { workspace = true } mc-commitment-state-diff = { workspace = true } mc-db = { workspace = true } -mc-l1-messages = { workspace = true } mc-mapping-sync = { workspace = true } mc-rpc = { workspace = true } -mc-settlement = { workspace = true, features = ["clap"] } mc-storage = { workspace = true } pallet-starknet = { workspace = true } pallet-starknet-runtime-api = { workspace = true } diff --git a/crates/node/src/commands/run.rs b/crates/node/src/commands/run.rs index 347c3c6424..68f93da8e1 100644 --- a/crates/node/src/commands/run.rs +++ b/crates/node/src/commands/run.rs @@ -1,9 +1,7 @@ use std::path::PathBuf; use std::result::Result as StdResult; -use clap::ValueHint::FilePath; use madara_runtime::SealingMode; -use mc_settlement::SettlementLayer; use reqwest::Url; use sc_cli::{Result, RpcMethods, RunCmd, SubstrateCli}; use serde::{Deserialize, Serialize}; @@ -130,14 +128,6 @@ pub struct ExtendedRunCmd { #[clap(long, short, default_value = "integration")] pub network: NetworkType, - /// Choose a supported settlement layer - #[clap(long, ignore_case = true, requires = "settlement_conf")] - pub settlement: Option, - - /// Path to a file containing the settlement configuration - #[clap(long, value_hint = FilePath, requires = "settlement")] - pub settlement_conf: Option, - /// When enabled, more information about the blocks and their transaction is cached and stored /// in the database. /// diff --git a/crates/primitives/transactions/src/from_broadcasted_transactions.rs b/crates/primitives/transactions/src/from_broadcasted_transactions.rs index 51cdc2b09c..162624db5c 100644 --- a/crates/primitives/transactions/src/from_broadcasted_transactions.rs +++ b/crates/primitives/transactions/src/from_broadcasted_transactions.rs @@ -114,8 +114,8 @@ impl TryFrom for UserTransaction { program: serde_json::from_slice(decompressed_bytes.as_slice()) .map_err(|_| BroadcastedTransactionConversionError::ProgramDeserializationFailed)?, abi: match contract_class.abi.as_ref() { - Some(abi) => abi.iter().cloned().map(|entry| entry.into()).collect::>(), - None => vec![], + Some(abi) => Some(abi.iter().cloned().map(|entry| entry.into()).collect::>()), + None => vec![].into(), }, entry_points_by_type: to_raw_legacy_entry_points(contract_class.entry_points_by_type.clone()), };