From b40b67c84d5bdf72589b3b8e7a28106b031e1ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Delabrouille?= Date: Tue, 14 May 2024 10:08:44 +0200 Subject: [PATCH] feat(rpc): store additional ContractClass data in client for read RPC --- .github/workflows/starknet-rpc-tests.yml | 2 +- CHANGELOG.md | 4 + Cargo.lock | 296 +++++++++++------- Cargo.toml | 3 + crates/client/contract-class-data/Cargo.toml | 17 + crates/client/contract-class-data/src/lib.rs | 257 +++++++++++++++ crates/client/db/Cargo.toml | 4 + .../client/db/src/contract_class_data_db.rs | 105 +++++++ crates/client/db/src/da_db.rs | 25 +- crates/client/db/src/error.rs | 8 +- crates/client/db/src/l1_handler_tx_fee.rs | 14 +- crates/client/db/src/lib.rs | 19 +- crates/client/db/src/mapping_db.rs | 16 +- crates/client/mapping-sync/src/sync_blocks.rs | 2 +- crates/client/rpc-core/Cargo.toml | 3 +- crates/client/rpc-core/src/utils.rs | 74 +---- crates/client/rpc/Cargo.toml | 31 +- crates/client/rpc/src/contract_class_v0.rs | 208 ++++++++++++ crates/client/rpc/src/events/mod.rs | 2 + crates/client/rpc/src/lib.rs | 165 +++++++--- crates/client/rpc/src/runtime_api.rs | 2 + crates/client/rpc/src/starknetrpcwrapper.rs | 8 +- crates/client/rpc/src/trace_api.rs | 1 + crates/node/Cargo.toml | 3 + crates/node/src/chain_spec.rs | 2 +- crates/node/src/commands/setup.rs | 1 - crates/node/src/rpc/mod.rs | 5 +- crates/node/src/rpc/starknet.rs | 13 +- crates/node/src/service.rs | 12 + .../src/from_broadcasted_transactions.rs | 240 +++++++++++++- crates/primitives/transactions/src/lib.rs | 44 ++- docs/rpc-contribution.md | 2 +- rust-toolchain.toml | 3 +- starknet-rpc-test/Cargo.toml | 15 +- starknet-rpc-test/README.md | 2 +- starknet-rpc-test/add_declare_transaction.rs | 9 +- starknet-rpc-test/get_class.rs | 217 ++++++++++--- starknet-rpc-test/get_class_at.rs | 112 ------- starknet-rpc-test/src/lib.rs | 2 +- starknet-test-utils/src/lib.rs | 2 +- 40 files changed, 1486 insertions(+), 464 deletions(-) create mode 100644 crates/client/contract-class-data/Cargo.toml create mode 100644 crates/client/contract-class-data/src/lib.rs create mode 100644 crates/client/db/src/contract_class_data_db.rs create mode 100644 crates/client/rpc/src/contract_class_v0.rs delete mode 100644 starknet-rpc-test/get_class_at.rs diff --git a/.github/workflows/starknet-rpc-tests.yml b/.github/workflows/starknet-rpc-tests.yml index fea52a775..c2e00eb47 100644 --- a/.github/workflows/starknet-rpc-tests.yml +++ b/.github/workflows/starknet-rpc-tests.yml @@ -36,7 +36,7 @@ jobs: ./target/production/madara setup --chain=dev --from-local=configs - name: Compile contracts for rpc-tests run: | - cd starknet-rpc-test/contracts && ./generate_declare_contracts.sh 10 + cd starknet-rpc-test/contracts && ./generate_declare_contracts.sh 11 - name: Run rpc test with cache run: |- ./target/production/madara --dev --sealing=manual & diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f97f514..f03cac5d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Next release +- feat(rpc): store data about contract classes which is not needed for the + execution in the client to return it later in read RPCs +- test(rpc): merge `get_class` and `get_class_at` tests together +- build: upgrade toolchain to `nightly-2023-09-18` and remove wasm target - feat(runtime): remove custom checks before tx execution - test: Adding txv3 tests - feat: L1 gas price/fix diff --git a/Cargo.lock b/Cargo.lock index 5e130f9de..3414be170 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,7 +194,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -247,7 +247,7 @@ dependencies = [ "derivative", "hashbrown 0.13.2", "itertools 0.10.5", - "num-traits 0.2.17", + "num-traits 0.2.19", "zeroize", ] @@ -277,7 +277,7 @@ dependencies = [ "digest 0.10.7", "itertools 0.10.5", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "paste", "rustc_version", "zeroize", @@ -300,7 +300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "proc-macro2", "quote", "syn 1.0.109", @@ -399,7 +399,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", "rand 0.8.5", ] @@ -465,7 +465,7 @@ dependencies = [ "asn1-rs-impl", "displaydoc", "nom", - "num-traits 0.2.17", + "num-traits 0.2.19", "rusticata-macros", "thiserror", "time", @@ -558,7 +558,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -703,7 +703,7 @@ checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" dependencies = [ "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "serde", ] @@ -743,7 +743,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -905,7 +905,7 @@ dependencies = [ "num-bigint", "num-integer", "num-rational", - "num-traits 0.2.17", + "num-traits 0.2.19", "once_cell", "parity-scale-codec", "phf", @@ -1073,7 +1073,7 @@ dependencies = [ "lazy_static", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "scale-info", "serde", @@ -1087,7 +1087,7 @@ dependencies = [ "cairo-lang-utils", "indoc", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "scale-info", "serde", @@ -1190,7 +1190,7 @@ dependencies = [ "itertools 0.11.0", "log", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "once_cell", "salsa", "smol_str", @@ -1209,7 +1209,7 @@ dependencies = [ "colored", "itertools 0.11.0", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "salsa", "smol_str", "unescaper", @@ -1240,7 +1240,7 @@ source = "git+https://github.com/bidzyyys/cairo.git?branch=feature/scale-codec#8 dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -1279,7 +1279,7 @@ dependencies = [ "keccak", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "smol_str", "starknet-crypto 0.6.1", "thiserror", @@ -1303,7 +1303,7 @@ dependencies = [ "indoc", "itertools 0.11.0", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "once_cell", "salsa", "smol_str", @@ -1324,7 +1324,7 @@ dependencies = [ "lalrpop", "lalrpop-util", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "regex", "salsa", "serde", @@ -1344,7 +1344,7 @@ dependencies = [ "cairo-lang-sierra-type-size", "cairo-lang-utils", "itertools 0.11.0", - "num-traits 0.2.17", + "num-traits 0.2.19", "thiserror", ] @@ -1358,7 +1358,7 @@ dependencies = [ "cairo-lang-sierra-type-size", "cairo-lang-utils", "itertools 0.11.0", - "num-traits 0.2.17", + "num-traits 0.2.19", "thiserror", ] @@ -1400,7 +1400,7 @@ dependencies = [ "indoc", "itertools 0.11.0", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "thiserror", ] @@ -1457,7 +1457,7 @@ dependencies = [ "itertools 0.11.0", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "once_cell", "parity-scale-codec", "scale-info", @@ -1478,7 +1478,7 @@ dependencies = [ "cairo-lang-filesystem", "cairo-lang-utils", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "salsa", "smol_str", "unescaper", @@ -1502,7 +1502,7 @@ dependencies = [ "indexmap 2.2.5", "itertools 0.11.0", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "scale-info", "schemars", @@ -1532,7 +1532,7 @@ dependencies = [ "num-bigint", "num-integer", "num-prime", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "rand 0.8.5", "scale-info", @@ -1659,7 +1659,7 @@ dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits 0.2.17", + "num-traits 0.2.19", "serde", "wasm-bindgen", "windows-targets 0.48.5", @@ -1740,7 +1740,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -2273,7 +2273,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -2321,7 +2321,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -2343,7 +2343,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -2392,7 +2392,7 @@ dependencies = [ "displaydoc", "nom", "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "rusticata-macros", ] @@ -2552,7 +2552,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -2593,7 +2593,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.39", + "syn 2.0.66", "termcolor", "toml 0.8.8", "walkdir", @@ -2969,7 +2969,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "syn 2.0.39", + "syn 2.0.66", "toml 0.8.8", "walkdir", ] @@ -2986,7 +2986,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3011,7 +3011,7 @@ dependencies = [ "serde", "serde_json", "strum 0.25.0", - "syn 2.0.39", + "syn 2.0.66", "tempfile", "thiserror", "tiny-keccak", @@ -3190,7 +3190,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3279,7 +3279,7 @@ dependencies = [ "futures", "futures-timer", "log", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "parking_lot 0.12.1", "scale-info", @@ -3320,7 +3320,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -3547,7 +3547,7 @@ dependencies = [ "proc-macro2", "quote", "sp-core-hashing", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3559,7 +3559,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3569,7 +3569,7 @@ source = "git+https://github.com/massalabs/polkadot-sdk?branch=release-polkadot- dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3729,7 +3729,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -3811,7 +3811,7 @@ checksum = "d4cf186fea4af17825116f72932fe52cce9a13bae39ff63b4dc0cfdb3fb4bde1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -4481,7 +4481,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -4824,6 +4824,28 @@ dependencies = [ "regex", ] +[[package]] +name = "lambdaworks-crypto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +dependencies = [ + "lambdaworks-math", + "serde", + "sha2 0.10.8", + "sha3", +] + +[[package]] +name = "lambdaworks-math" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -5478,7 +5500,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -5492,7 +5514,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -5503,7 +5525,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -5514,7 +5536,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -5532,6 +5554,7 @@ dependencies = [ "log", "madara-runtime", "mc-commitment-state-diff", + "mc-contract-class-data", "mc-db", "mc-eth-client", "mc-genesis-data-provider", @@ -5582,10 +5605,12 @@ dependencies = [ "sp-state-machine", "sp-statement-store", "sp-timestamp", + "starknet_api", "substrate-build-script-utils", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "thiserror", + "tokio", "try-runtime-cli", "url", ] @@ -5710,17 +5735,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "mc-contract-class-data" +version = "0.8.0" +dependencies = [ + "futures", + "log", + "mc-db", + "pallet-starknet-runtime-api", + "sc-transaction-pool-api", + "sp-api", + "sp-runtime", + "starknet_api", + "tokio", +] + [[package]] name = "mc-db" version = "0.8.0" dependencies = [ "kvdb-rocksdb", "log", + "mp-transactions", "parity-db", "parity-scale-codec", "sc-client-db", + "serde", + "serde_json", "sp-database", "sp-runtime", + "starknet-core", "starknet_api", "thiserror", "uuid 1.8.0", @@ -5801,7 +5845,7 @@ dependencies = [ "mp-digest-log", "mp-hashers", "mp-transactions", - "num-traits 0.2.17", + "num-traits 0.2.19", "pallet-starknet-runtime-api", "sc-client-api", "sp-api", @@ -5814,8 +5858,11 @@ dependencies = [ name = "mc-rpc" version = "0.8.0" dependencies = [ + "anyhow", "blockifier", "cairo-vm", + "futures", + "indexmap 2.2.5", "jsonrpsee", "log", "mc-db", @@ -5846,6 +5893,7 @@ dependencies = [ "starknet-ff 0.3.7", "starknet_api", "thiserror", + "tokio", ] [[package]] @@ -5857,6 +5905,7 @@ dependencies = [ "blockifier", "cairo-lang-starknet-classes", "cairo-lang-utils", + "cairo-vm", "flate2", "indexmap 2.2.5", "jsonrpsee", @@ -6374,7 +6423,7 @@ dependencies = [ "nalgebra-macros", "num-complex 0.4.4", "num-rational", - "num-traits 0.2.17", + "num-traits 0.2.19", "simba", "typenum", ] @@ -6426,7 +6475,7 @@ dependencies = [ "matrixmultiply 0.2.4", "num-complex 0.2.4", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "rawpointer", ] @@ -6543,7 +6592,7 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "rand 0.8.5", "serde", ] @@ -6555,7 +6604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" dependencies = [ "autocfg", - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -6564,7 +6613,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -6584,7 +6633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -6595,7 +6644,7 @@ checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" dependencies = [ "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] @@ -6610,7 +6659,7 @@ dependencies = [ "num-bigint", "num-integer", "num-modular", - "num-traits 0.2.17", + "num-traits 0.2.19", "rand 0.8.5", ] @@ -6623,7 +6672,7 @@ dependencies = [ "autocfg", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "serde", ] @@ -6633,14 +6682,14 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -6674,7 +6723,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -6785,7 +6834,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7196,7 +7245,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7260,7 +7309,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7304,7 +7353,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7465,7 +7514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7533,14 +7582,14 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -7585,7 +7634,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -7596,7 +7645,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bitflags 2.4.1", "lazy_static", - "num-traits 0.2.17", + "num-traits 0.2.19", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", @@ -7726,9 +7775,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -7807,7 +7856,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ - "num-traits 0.2.17", + "num-traits 0.2.19", "rand 0.8.5", ] @@ -7922,7 +7971,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -8204,7 +8253,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.39", + "syn 2.0.66", "unicode-ident", ] @@ -8506,7 +8555,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -8667,7 +8716,7 @@ dependencies = [ "log", "num-bigint", "num-rational", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "parking_lot 0.12.1", "sc-client-api", @@ -9371,7 +9420,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -9635,22 +9684,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -9732,7 +9781,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -9847,7 +9896,7 @@ checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" dependencies = [ "approx", "num-complex 0.4.4", - "num-traits 0.2.17", + "num-traits 0.2.19", "paste", "wide", ] @@ -9859,7 +9908,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", - "num-traits 0.2.17", + "num-traits 0.2.19", "thiserror", "time", ] @@ -10006,7 +10055,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10028,7 +10077,7 @@ version = "16.0.0" source = "git+https://github.com/massalabs/polkadot-sdk?branch=release-polkadot-v1.3.0-std#a41673f8aa3f24d7ed8fb1f246a97551eca58837" dependencies = [ "integer-sqrt", - "num-traits 0.2.17", + "num-traits 0.2.19", "parity-scale-codec", "scale-info", "serde", @@ -10213,7 +10262,7 @@ source = "git+https://github.com/massalabs/polkadot-sdk?branch=release-polkadot- dependencies = [ "quote", "sp-core-hashing", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10232,7 +10281,7 @@ source = "git+https://github.com/massalabs/polkadot-sdk?branch=release-polkadot- dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10429,7 +10478,7 @@ dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10622,7 +10671,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10770,7 +10819,7 @@ dependencies = [ "async-trait", "ethers", "log", - "num-traits 0.2.17", + "num-traits 0.2.19", "starknet-proxy-client", "thiserror", "utils", @@ -10787,7 +10836,7 @@ dependencies = [ "hmac 0.12.1", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "rfc6979", "sha2 0.10.8", "starknet-crypto-codegen 0.3.2", @@ -10807,7 +10856,7 @@ dependencies = [ "hmac 0.12.1", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "rfc6979", "sha2 0.10.8", "starknet-crypto-codegen 0.3.2", @@ -10826,7 +10875,7 @@ dependencies = [ "hmac 0.12.1", "num-bigint", "num-integer", - "num-traits 0.2.17", + "num-traits 0.2.19", "rfc6979", "sha2 0.10.8", "starknet-crypto-codegen 0.3.3", @@ -10843,7 +10892,7 @@ checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" dependencies = [ "starknet-curve 0.4.1", "starknet-ff 0.3.6", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10853,7 +10902,7 @@ source = "git+https://github.com/xJonathanLEI/starknet-rs.git?rev=2d596369116282 dependencies = [ "starknet-curve 0.4.2", "starknet-ff 0.3.7", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -10935,7 +10984,7 @@ dependencies = [ "ethereum-instance", "ethers", "log", - "num-traits 0.2.17", + "num-traits 0.2.19", "serde_json", "thiserror", "utils", @@ -10962,6 +11011,7 @@ dependencies = [ "starknet-providers", "starknet-signers", "starknet-test-utils", + "starknet-types-core", "thiserror", "tokio", "url", @@ -11003,6 +11053,20 @@ dependencies = [ "url", ] +[[package]] +name = "starknet-types-core" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe29a53d28ff630e4c7827788f14b28f9386d27cb9d05186a5f2e73218c34677" +dependencies = [ + "lambdaworks-crypto", + "lambdaworks-math", + "num-bigint", + "num-integer", + "num-traits 0.2.19", + "serde", +] + [[package]] name = "starknet_api" version = "0.8.0" @@ -11118,7 +11182,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -11228,9 +11292,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -11339,7 +11403,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -11350,7 +11414,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", "test-case-core", ] @@ -11371,7 +11435,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -11528,7 +11592,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -11719,7 +11783,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -11930,7 +11994,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "digest 0.10.7", - "rand 0.7.3", + "rand 0.8.5", "static_assertions", ] @@ -12077,7 +12141,7 @@ dependencies = [ "async-trait", "ethers", "log", - "num-traits 0.2.17", + "num-traits 0.2.19", "serde_json", "thiserror", ] @@ -12198,7 +12262,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -12232,7 +12296,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12932,7 +12996,7 @@ checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] @@ -12952,7 +13016,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.66", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ce8fd2788..d7ecce224 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "crates/client/commitment-state-diff", "crates/client/eth-client", "crates/client/starknet-block-import", + "crates/client/contract-class-data", "starknet-rpc-test", "madara-test-runner", "starknet-test-utils", @@ -57,6 +58,7 @@ default-members = [ "crates/client/commitment-state-diff", "crates/client/eth-client", "crates/client/starknet-block-import", + "crates/client/contract-class-data", "starknet-test-utils", ] @@ -188,6 +190,7 @@ mc-l1-messages = { path = "crates/client/l1-messages" } mc-l1-gas-price = { path = "crates/client/l1-gas-price" } mc-eth-client = { path = "crates/client/eth-client" } mc-starknet-block-import = { path = "crates/client/starknet-block-import" } +mc-contract-class-data = { path = "crates/client/contract-class-data" } # Madara runtime madara-runtime = { path = "crates/runtime" } diff --git a/crates/client/contract-class-data/Cargo.toml b/crates/client/contract-class-data/Cargo.toml new file mode 100644 index 000000000..3e57ba63d --- /dev/null +++ b/crates/client/contract-class-data/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mc-contract-class-data" +authors.workspace = true +edition.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +futures = { workspace = true } +log = { workspace = true } +mc-db = { workspace = true } +pallet-starknet-runtime-api = { workspace = true } +sc-transaction-pool-api = { workspace = true } +sp-api = { workspace = true } +sp-runtime = { workspace = true } +starknet_api = { workspace = true } +tokio = { workspace = true } diff --git a/crates/client/contract-class-data/src/lib.rs b/crates/client/contract-class-data/src/lib.rs new file mode 100644 index 000000000..d4ad09a11 --- /dev/null +++ b/crates/client/contract-class-data/src/lib.rs @@ -0,0 +1,257 @@ +//! A worker to manage additional contact class data +//! +//! When receiving an `add_decalre_transaction` we convert the contract class we receive into a +//! struct that is executable by the blockifier. +//! During this process there is a loss of data, but we need to store this data somewhere in order +//! to rebuild the original struct and answer the `get_class_at` rpc. +//! +//! To make sure no data is missing, we store the extra data in some temporary storage before +//! pushing the tx into the TransactionPool, while creating a watcher to this Substrate extrinsinc. +//! Then we wait for the tx to be executed, and it's block to be finalized. Once it's done, we check +//! that the Starknet tx was successful. If yes, we move it to a more perenial storage, if not we +//! remove it altogether from the temporary storage. +//! The logic described above is splited beetween: +//! - mc_rpc::add_declare_transaction +//! - mc_db::ContractClassDataDb +//! - mc_contract_class_data::run_worker (the present crate) +//! +//! The present worker does the following inside an infinite loop: +//! - Poll the channel for new declare transactions and add them to a queue +//! - Iterate over this queue and poll each individual tx watcher of update in the tx status. +//! * If the Substrate tx failed in anyway, remove the contact class data. +//! * Else if it has been finalize check if the Substrate block has been synced in our backend. +//! + If it is not sync yet, moved it to another buffer, that will be checked again on each +//! iteration of the loop. +//! + Else, check if the Starknet transaction was successfully included in the Starknet block. +//! > If yes, move the pending contract class data to the final storage. +//! > Else, remove it from the pending storage altogether. + +#![feature(iter_collect_into)] + +use std::collections::HashMap; +use std::num::NonZeroUsize; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use futures::poll; +use futures::stream::{Stream, StreamExt}; +use log::error; +use mc_db::contract_class_data_db::ContractClassDataDb; +use mc_db::MappingDb; +use sc_transaction_pool_api::{TransactionPool, TransactionStatus, TransactionStatusStreamFor}; +use sp_runtime::traits::Block as BlockT; +use starknet_api::core::ClassHash; +use starknet_api::transaction::TransactionHash; +use tokio::sync::mpsc::UnboundedReceiver; + +// Content of the message sent through the mpsc channel +// It contains everything we need to decide what to do with the ContractClassData +// (hash of the declare transaction, +// hash of the class declared, +// watcher of the matching Substrate transaction status) +type DeclareTransactionStatusStream

= (TransactionHash, ClassHash, Pin>>); + +// Wrapper arround an `UnboundedReceiver` and a buffer. +// It encapsulates the logic for pulling multiple elements out of the channel at the same time. +// +// The buffer must have a capacity > 0. Use `new` to make sure it is conscructed correctly. +pub struct DeclareTransactionJobReceiverFuture +where + P: TransactionPool, + B: BlockT, +{ + receiver: UnboundedReceiver>, + buffer: Vec>, +} + +impl DeclareTransactionJobReceiverFuture +where + P: TransactionPool, + B: BlockT, +{ + #[inline(always)] + pub fn new(receiver: UnboundedReceiver>, limit: NonZeroUsize) -> Self { + Self { receiver, buffer: Vec::with_capacity(limit.into()) } + } + + #[inline(always)] + pub fn buffer_as_mut_ref(&mut self) -> &mut Vec> { + &mut self.buffer + } +} + +// When calling `poll_next`, if new values have been pushed to the channel, stores up to limit of +// them in the buffer, and returns the exact amount. +// +// This whole struct only makes sense if limit > 0. +// We use `buffer.capacity()` as limit, so use the `new` method +// that won't let you pass a non allocated `Vec`. +impl Stream for DeclareTransactionJobReceiverFuture +where + P: TransactionPool, + B: BlockT, +{ + type Item = usize; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let DeclareTransactionJobReceiverFuture { receiver, buffer, .. } = &mut *self; + + match receiver.poll_recv_many(cx, buffer, buffer.capacity()) { + Poll::Pending => Poll::Pending, + // For limit > 0, this means that the channel has been closed + Poll::Ready(0) => Poll::Ready(None), + Poll::Ready(amount) => Poll::Ready(Some(amount)), + } + } +} + +pub async fn run_worker( + contract_class_db: Arc, + mapping_db: Arc>, + job_recv: UnboundedReceiver>, +) where + B: BlockT, + P: TransactionPool, +{ + // We push everything we get from the receiver here, then repetitively iterate over it to poll + // each transaction's status stream and take action when they end up being finalized. + let mut unfinalized_declare_transactions = Vec::new(); + // Stores all the txs that were not yet mapped into our db when they finalized for later processing. + let mut finalized_but_not_mapped_yet = HashMap::>::new(); + // Totally arbitrary limit. + // The job_recv being unbounded, there is not risk of it being full. + // Could be increased up to at most the max amount of declare tx possible to have in a single block. + // More that this would be useless as the "poll receiver" task is woke up at least one (but most + // likely multiple times) during the span of one blocktime. + let streams_buffer_limit = unsafe { NonZeroUsize::new_unchecked(10) }; + let mut jobs_receiver = DeclareTransactionJobReceiverFuture::::new(job_recv, streams_buffer_limit); + + loop { + // Deal with new DeclareTx in the channel + { + // Check if new DeclareTx were created + let opt_amount = match poll!(jobs_receiver.next()) { + Poll::Pending => None, + Poll::Ready(None) => panic!("the ContractClassData sender has been droped"), + Poll::Ready(opt_amount) => opt_amount, + }; + // If yes, add them to the queue + if let Some(amount) = opt_amount { + // Empty the buffer into `unfinalized_declare_transaction` + jobs_receiver.buffer_as_mut_ref().drain(..amount).collect_into(&mut unfinalized_declare_transactions); + } + } + + // Deal with the finalized but not mapped + // As soon as the Substrate block is synced, it will remove this block entry + // and process every transaction in it. + finalized_but_not_mapped_yet.retain(|substrate_block_hash, transactions: &mut Vec<_>| { + let is_synced = mapping_db.is_synced(substrate_block_hash); + // If it is now mapped, we take action + if is_synced { + for (transaction_hash, class_hash) in transactions { + persist_or_remove_pending_contract_class_data( + &contract_class_db, + &mapping_db, + *transaction_hash, + *class_hash, + ); + } + } + + // Don't retain the synced enties + !is_synced + }); + + // We did a bunch of blocking operations at this point, + // let's yield the thread to the tokio runtime. + tokio::task::yield_now().await; + + // Now deal with the transactions that are still not finalized + // We use indexing rather than iterator to be able to remove entries while iterating. + let mut i = 0; + while i < unfinalized_declare_transactions.len() { + let (transaction_hash, class_hash, tx_status_watcher_stream) = &mut unfinalized_declare_transactions[i]; + let transaction_hash = *transaction_hash; + let class_hash = *class_hash; + + match poll!(tx_status_watcher_stream.next()) { + // The Substrate block is final, we can decide what to do with those contract class data + Poll::Ready(Some(TransactionStatus::Finalized((substrate_block_hash, _)))) => { + // Finalized but not synced yet, even if the Substrate tx is part of the Substrate block, + // we cannot know for sure that the matching Starknet tx is part of the Starknet matching block. + // Store it on the side to be processed later. + if !mapping_db.is_synced(&substrate_block_hash) { + finalized_but_not_mapped_yet + .entry(substrate_block_hash) + .and_modify(|v| v.push((transaction_hash, class_hash))) + .or_insert_with(|| vec![(transaction_hash, class_hash)]); + } else { + persist_or_remove_pending_contract_class_data( + &contract_class_db, + &mapping_db, + transaction_hash, + class_hash, + ); + } + + // `swap_remove` for perfs, therefore don't increment `i` + unfinalized_declare_transactions.swap_remove(i); + // We did some blocking work, let's yield + tokio::task::yield_now().await; + } + // Those are all final status, that mean the TX wasn't successfully executed. + // We remove the related extra data we stored. + Poll::Ready(None) + | Poll::Ready(Some(TransactionStatus::Dropped)) + | Poll::Ready(Some(TransactionStatus::Invalid)) + | Poll::Ready(Some(TransactionStatus::Usurped(_))) + | Poll::Ready(Some(TransactionStatus::FinalityTimeout(_))) => { + if let Err(e) = contract_class_db.remove_pending_contract_class_data(class_hash) { + error!( + "failed to remove pending contract class data for class with hashe + {class_hash:?}: {e}" + ) + }; + unfinalized_declare_transactions.swap_remove(i); + tokio::task::yield_now().await; + } + // Here are all the intermediate status (Pending, InBlock, etc) + // Do nothing, just keep iterating + _ => i += 1, + } + } + + tokio::task::yield_now().await; + } +} + +// To avoid false negative, this method should only be called after you made sure the Substrate +// block containing the Starknet transaction with hash `transaction_hash` has been synced int +// the Madara backend. +fn persist_or_remove_pending_contract_class_data( + contract_class_db: &Arc, + mapping_db: &Arc>, + transaction_hash: TransactionHash, + class_hash: ClassHash, +) { + // If the Starknet tx has been mapped, this means it didn't failed. + // Declare being a non-revertible Tx, it wouldn't be in the block otherwise. + #[allow(clippy::collapsible_else_if)] + if mapping_db.transaction_is_mapped(transaction_hash) { + if let Err(e) = contract_class_db.persist_pending_contract_class_data(class_hash) { + error!( + "failed to persist pending contract class data for class with hash + {class_hash:?}: {e}" + ) + } + } else { + if let Err(e) = contract_class_db.remove_pending_contract_class_data(class_hash) { + error!( + "failed to remove pending contract class data for class with hash + {class_hash:?}: {e}" + ) + } + } +} diff --git a/crates/client/db/Cargo.toml b/crates/client/db/Cargo.toml index 46fa2a279..f07f23470 100644 --- a/crates/client/db/Cargo.toml +++ b/crates/client/db/Cargo.toml @@ -18,11 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] kvdb-rocksdb = { version = "0.19.0", optional = true } log = { workspace = true } +mp-transactions = { workspace = true } parity-db = { version = "0.4.12", optional = true } parity-scale-codec = { workspace = true, features = ["derive"] } sc-client-db = { workspace = true, features = ["rocksdb"] } +serde = { workspace = true } +serde_json = { workspace = true } sp-database = { workspace = true } sp-runtime = { workspace = true } +starknet-core = { workspace = true } starknet_api = { workspace = true } thiserror = { workspace = true } uuid = "1.7.0" diff --git a/crates/client/db/src/contract_class_data_db.rs b/crates/client/db/src/contract_class_data_db.rs new file mode 100644 index 000000000..b768fd190 --- /dev/null +++ b/crates/client/db/src/contract_class_data_db.rs @@ -0,0 +1,105 @@ +use std::sync::Arc; + +use mp_transactions::{ContractClassData, V0ContractClassData, V1ContractClassData}; +use parity_scale_codec::Encode; +use sp_database::Database; +use starknet_api::core::ClassHash; + +use crate::{DbError, DbHash}; + +/// Allow interaction with the mapping db +pub struct ContractClassDataDb { + pub(crate) db: Arc>, +} + +impl ContractClassDataDb { + pub fn register_pending_v0_contract_class_data( + &self, + class_hash: ClassHash, + data: V0ContractClassData, + ) -> Result<(), DbError> { + let mut transaction = sp_database::Transaction::new(); + + let data_as_json_vec = { + // Forced to convert to json because `LegacyContractAbiEntry` doesn't impl scale-codec + let mut data_as_json_vec = serde_json::to_vec(&data)?; + // Push the contract class version at the end + data_as_json_vec.push(0); + + data_as_json_vec + }; + transaction.set(crate::columns::PENDING_CONTRACT_CLASS_DATA, &class_hash.encode(), &data_as_json_vec); + + self.db.commit(transaction)?; + + Ok(()) + } + + pub fn register_pending_v1_contract_class_data( + &self, + class_hash: ClassHash, + data: V1ContractClassData, + ) -> Result<(), DbError> { + let mut transaction = sp_database::Transaction::new(); + let data_as_json_vec = { + // Forced to convert to json because `FlattenedSierraClass` doesn't impl scale-codec + let mut data_as_json_vec = serde_json::to_vec(&data)?; + // Push the contract class version at the end + data_as_json_vec.push(1); + + data_as_json_vec + }; + + transaction.set(crate::columns::PENDING_CONTRACT_CLASS_DATA, &class_hash.encode(), &data_as_json_vec); + + self.db.commit(transaction)?; + + Ok(()) + } + + pub fn read_contract_class_data(&self, class_hash: ClassHash) -> Result, DbError> { + let raw_data = match self.db.get(crate::columns::CONTRACT_CLASS_DATA, &class_hash.encode()) { + Some(raw) => raw, + None => return Ok(None), + }; + + let contract_class_data = match raw_data + .last() + .ok_or_else(|| DbError::CorruptedValue(crate::columns::CONTRACT_CLASS_DATA, class_hash.to_string()))? + { + 0 => ContractClassData::V0(serde_json::from_slice(&raw_data[..raw_data.len() - 1])?), + 1 => ContractClassData::V1(serde_json::from_slice(&raw_data[..raw_data.len() - 1])?), + _ => return Err(DbError::CorruptedValue(crate::columns::CONTRACT_CLASS_DATA, class_hash.to_string())), + }; + + Ok(Some(contract_class_data)) + } + + pub fn remove_pending_contract_class_data(&self, class_hash: ClassHash) -> Result<(), DbError> { + let mut transaction = sp_database::Transaction::new(); + + transaction.remove(crate::columns::PENDING_CONTRACT_CLASS_DATA, &class_hash.encode()); + + self.db.commit(transaction)?; + + Ok(()) + } + + pub fn persist_pending_contract_class_data(&self, class_hash: ClassHash) -> Result<(), DbError> { + let encoded_class_hash = class_hash.encode(); + + let encoded_class_hash_data = + self.db.get(crate::columns::PENDING_CONTRACT_CLASS_DATA, &encoded_class_hash).ok_or_else(|| { + DbError::ValueNotInitialized(crate::columns::PENDING_CONTRACT_CLASS_DATA, class_hash.to_string()) + })?; + + let mut transaction = sp_database::Transaction::new(); + + transaction.remove(crate::columns::PENDING_CONTRACT_CLASS_DATA, &encoded_class_hash); + transaction.set(crate::columns::CONTRACT_CLASS_DATA, &encoded_class_hash, &encoded_class_hash_data); + + self.db.commit(transaction)?; + + Ok(()) + } +} diff --git a/crates/client/db/src/da_db.rs b/crates/client/db/src/da_db.rs index 2cf5bbfcb..0758a1ab8 100644 --- a/crates/client/db/src/da_db.rs +++ b/crates/client/db/src/da_db.rs @@ -1,33 +1,24 @@ use std::sync::Arc; -// Substrate use parity_scale_codec::{Decode, Encode}; use sp_database::Database; -// Starknet use starknet_api::block::BlockHash; -use starknet_api::hash::StarkFelt; use crate::{DbError, DbHash}; -// The fact db stores DA facts that need to be written to L1 pub struct DaDb { pub(crate) db: Arc>, } -// TODO: purge old cairo job keys impl DaDb { - pub fn last_proved_block(&self) -> Result { - match self.db.get(crate::columns::DA, crate::static_keys::LAST_PROVED_BLOCK) { - Some(raw) => { - let felt = StarkFelt::decode(&mut &raw[..])?; - Ok(BlockHash(felt)) - } - None => Err(DbError::ValueNotInitialized( - crate::columns::DA, - // Safe coze `LAST_PROVED_BLOCK` is utf8 - unsafe { std::str::from_utf8_unchecked(crate::static_keys::LAST_PROVED_BLOCK) }.to_string(), - )), - } + pub fn last_proved_block(&self) -> Result, DbError> { + let block_hash = self + .db + .get(crate::columns::DA, crate::static_keys::LAST_PROVED_BLOCK) + .map(|raw| BlockHash::decode(&mut &raw[..])) + .transpose()?; + + Ok(block_hash) } pub fn update_last_proved_block(&self, block_hash: &BlockHash) -> Result<(), DbError> { diff --git a/crates/client/db/src/error.rs b/crates/client/db/src/error.rs index 85bb66de2..8978457b9 100644 --- a/crates/client/db/src/error.rs +++ b/crates/client/db/src/error.rs @@ -2,10 +2,14 @@ pub enum DbError { #[error("Failed to commit DB Update: `{0}`")] CommitError(#[from] sp_database::error::DatabaseError), - #[error("Failed to deserialize DB Data: `{0}`")] - DeserializeError(#[from] parity_scale_codec::Error), + #[error("Failed to decode DB Data: `{0}`")] + ScaleCodecError(#[from] parity_scale_codec::Error), #[error("Failed to build Uuid: `{0}`")] Uuid(#[from] uuid::Error), #[error("A value was queryied that was not initialized at column: `{0}` key: `{1}`")] ValueNotInitialized(u32, String), + #[error("The data stored at column `{0}` key: `{1}`, has been corrupted")] + CorruptedValue(u32, String), + #[error(transparent)] + Serde(#[from] serde_json::Error), } diff --git a/crates/client/db/src/l1_handler_tx_fee.rs b/crates/client/db/src/l1_handler_tx_fee.rs index 99ff7c35b..0f2974574 100644 --- a/crates/client/db/src/l1_handler_tx_fee.rs +++ b/crates/client/db/src/l1_handler_tx_fee.rs @@ -24,16 +24,16 @@ impl L1HandlerTxFeeDb { } /// Return the stored fee paid on l1 for a specific L1Handler transaction - pub fn get_fee_paid_for_l1_handler_tx(&self, tx_hash: StarkFelt) -> Result { - if let Some(bytes) = self.db.get(crate::columns::L1_HANDLER_PAID_FEE, &tx_hash.encode()) { + pub fn get_fee_paid_for_l1_handler_tx(&self, tx_hash: StarkFelt) -> Result, DbError> { + let opt_fee = self.db.get(crate::columns::L1_HANDLER_PAID_FEE, &tx_hash.encode()).map(|raw| { let mut buff = [0u8; 16]; - buff.copy_from_slice(&bytes); + buff.copy_from_slice(&raw); let fee = u128::from_le_bytes(buff); - Ok(Fee(fee)) - } else { - Err(DbError::ValueNotInitialized(crate::columns::L1_HANDLER_PAID_FEE, tx_hash.to_string())) - } + Fee(fee) + }); + + Ok(opt_fee) } } diff --git a/crates/client/db/src/lib.rs b/crates/client/db/src/lib.rs index 29089ab93..99324cfd5 100644 --- a/crates/client/db/src/lib.rs +++ b/crates/client/db/src/lib.rs @@ -12,10 +12,11 @@ //! flags. Support for custom databases is possible but not supported yet. mod error; +use contract_class_data_db::ContractClassDataDb; pub use error::DbError; mod mapping_db; -pub use mapping_db::MappingCommitment; +pub use mapping_db::{MappingCommitment, MappingDb}; use sierra_classes_db::SierraClassesDb; use starknet_api::hash::StarkHash; mod da_db; @@ -23,6 +24,7 @@ mod db_opening_utils; mod messaging_db; pub mod sierra_classes_db; pub use messaging_db::LastSyncedEventBlock; +pub mod contract_class_data_db; mod l1_handler_tx_fee; mod meta_db; @@ -32,7 +34,6 @@ use std::sync::Arc; use da_db::DaDb; use l1_handler_tx_fee::L1HandlerTxFeeDb; -use mapping_db::MappingDb; use messaging_db::MessagingDb; use meta_db::MetaDb; use sc_client_db::DatabaseSource; @@ -53,7 +54,7 @@ pub(crate) mod columns { // ===== /!\ =================================================================================== // MUST BE INCREMENTED WHEN A NEW COLUMN IN ADDED // ===== /!\ =================================================================================== - pub const NUM_COLUMNS: u32 = 8; + pub const NUM_COLUMNS: u32 = 10; pub const META: u32 = 0; pub const BLOCK_MAPPING: u32 = 1; @@ -69,6 +70,12 @@ pub(crate) mod columns { /// This column stores the fee paid on l1 for L1Handler transactions pub const L1_HANDLER_PAID_FEE: u32 = 7; + + /// This column stores the additional data about contract classes we need to serve the RPCs + pub const CONTRACT_CLASS_DATA: u32 = 8; + /// This column stores data about classes that have been received by the node but are not yet + /// included in the chain + pub const PENDING_CONTRACT_CLASS_DATA: u32 = 9; } pub mod static_keys { @@ -89,6 +96,7 @@ pub struct Backend { messaging: Arc, sierra_classes: Arc, l1_handler_paid_fee: Arc, + contract_class_data: Arc, } /// Returns the Starknet database directory. @@ -129,6 +137,7 @@ impl Backend { messaging: Arc::new(MessagingDb { db: db.clone() }), sierra_classes: Arc::new(SierraClassesDb { db: db.clone() }), l1_handler_paid_fee: Arc::new(L1HandlerTxFeeDb { db: db.clone() }), + contract_class_data: Arc::new(ContractClassDataDb { db: db.clone() }), }) } @@ -162,6 +171,10 @@ impl Backend { &self.l1_handler_paid_fee } + /// Return contract class data database manager + pub fn contract_class_data(&self) -> &Arc { + &self.contract_class_data + } /// In the future, we will compute the block global state root asynchronously in the client, /// using the Starknet-Bonzai-trie. /// That what replaces it for now :) diff --git a/crates/client/db/src/mapping_db.rs b/crates/client/db/src/mapping_db.rs index f45a14c4c..aa654161f 100644 --- a/crates/client/db/src/mapping_db.rs +++ b/crates/client/db/src/mapping_db.rs @@ -32,11 +32,8 @@ impl MappingDb { } /// Check if the given block hash has already been processed - pub fn is_synced(&self, block_hash: &B::Hash) -> Result { - match self.db.get(crate::columns::SYNCED_MAPPING, &block_hash.encode()) { - Some(raw) => Ok(bool::decode(&mut &raw[..])?), - None => Ok(false), - } + pub fn is_synced(&self, block_hash: &B::Hash) -> bool { + self.db.get(crate::columns::SYNCED_MAPPING, &block_hash.encode()).is_some() } /// Return the hash of the Substrate block wrapping the Starknet block with given hash @@ -56,7 +53,7 @@ impl MappingDb { let mut transaction = sp_database::Transaction::new(); - transaction.set(crate::columns::SYNCED_MAPPING, &block_hash.encode(), &true.encode()); + transaction.set(crate::columns::SYNCED_MAPPING, &block_hash.encode(), &[]); self.db.commit(transaction)?; @@ -89,7 +86,7 @@ impl MappingDb { &substrate_hashes.encode(), ); - transaction.set(crate::columns::SYNCED_MAPPING, &commitment.block_hash.encode(), &true.encode()); + transaction.set(crate::columns::SYNCED_MAPPING, &commitment.block_hash.encode(), &[]); for transaction_hash in commitment.starknet_transaction_hashes.iter() { transaction.set( @@ -121,4 +118,9 @@ impl MappingDb { None => Ok(None), } } + + /// Was the Starknet transaction with this hash mapped to a Substrate block? + pub fn transaction_is_mapped(&self, transaction_hash: TransactionHash) -> bool { + self.db.get(crate::columns::TRANSACTION_MAPPING, &transaction_hash.encode()).is_some() + } } diff --git a/crates/client/mapping-sync/src/sync_blocks.rs b/crates/client/mapping-sync/src/sync_blocks.rs index d2f075378..000071e21 100644 --- a/crates/client/mapping-sync/src/sync_blocks.rs +++ b/crates/client/mapping-sync/src/sync_blocks.rs @@ -211,7 +211,7 @@ fn fetch_header( where BE: HeaderBackend, { - if madara_backend.mapping().is_synced(&checking_tip)? { + if madara_backend.mapping().is_synced(&checking_tip) { return Ok(None); } diff --git a/crates/client/rpc-core/Cargo.toml b/crates/client/rpc-core/Cargo.toml index 74f48c378..75969e7bb 100644 --- a/crates/client/rpc-core/Cargo.toml +++ b/crates/client/rpc-core/Cargo.toml @@ -20,6 +20,7 @@ anyhow = { workspace = true } blockifier = { workspace = true } cairo-lang-starknet-classes = { workspace = true } cairo-lang-utils = { workspace = true } +cairo-vm = { workspace = true } flate2 = { workspace = true } indexmap = { workspace = true } jsonrpsee = { workspace = true, features = ["server", "macros"] } @@ -30,7 +31,7 @@ mp-transactions = { workspace = true, features = ["serde"] } num-bigint = { workspace = true } pallet-starknet = { workspace = true, features = ["genesis-loader"] } serde = { workspace = true } -serde_json = { workspace = true } +serde_json = { workspace = true, features = ["raw_value"] } serde_with = { workspace = true } sp-api = { workspace = true } sp-blockchain = { workspace = true } diff --git a/crates/client/rpc-core/src/utils.rs b/crates/client/rpc-core/src/utils.rs index a7979a02e..858ef732d 100644 --- a/crates/client/rpc-core/src/utils.rs +++ b/crates/client/rpc-core/src/utils.rs @@ -1,8 +1,6 @@ -use std::io::Write; use std::sync::Arc; -use anyhow::{anyhow, Result}; -use blockifier::execution::contract_class::ContractClass as BlockifierContractClass; +use anyhow::Result; use blockifier::state::cached_state::CommitmentStateDiff; use cairo_lang_starknet_classes::casm_contract_class::{ CasmContractClass, CasmContractEntryPoint, CasmContractEntryPoints, StarknetSierraCompilationError, @@ -18,37 +16,13 @@ use mp_felt::Felt252Wrapper; use num_bigint::{BigInt, BigUint, Sign}; use sp_api::{BlockT, HeaderT}; use sp_blockchain::HeaderBackend; -use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; use starknet_api::state::ThinStateDiff; use starknet_core::types::contract::{CompiledClass, CompiledClassEntrypoint, CompiledClassEntrypointList}; use starknet_core::types::{ - CompressedLegacyContractClass, ContractClass, ContractStorageDiffItem, DeclaredClassItem, DeployedContractItem, - EntryPointsByType, FieldElement, FlattenedSierraClass, FromByteArrayError, LegacyContractEntryPoint, - LegacyEntryPointsByType, NonceUpdate, ReplacedClassItem, SierraEntryPoint, StateDiff, StorageEntry, + ContractStorageDiffItem, DeclaredClassItem, DeployedContractItem, EntryPointsByType, FieldElement, + FlattenedSierraClass, NonceUpdate, ReplacedClassItem, SierraEntryPoint, StateDiff, StorageEntry, }; -/// Returns a [`ContractClass`] from a [`BlockifierContractClass`] -pub fn blockifier_to_rpc_contract_class_types(contract_class: BlockifierContractClass) -> Result { - match contract_class { - BlockifierContractClass::V0(contract_class) => { - let entry_points_by_type = to_legacy_entry_points_by_type(&contract_class.entry_points_by_type)?; - let compressed_program = compress(&contract_class.program.serialize()?)?; - Ok(ContractClass::Legacy(CompressedLegacyContractClass { - program: compressed_program, - entry_points_by_type, - // FIXME 723 - abi: None, - })) - } - BlockifierContractClass::V1(_contract_class) => Ok(ContractClass::Sierra(FlattenedSierraClass { - sierra_program: vec![], // FIXME: https://github.com/keep-starknet-strange/madara/issues/775 - contract_class_version: option_env!("COMPILER_VERSION").unwrap_or("0.11.2").into(), - entry_points_by_type: EntryPointsByType { constructor: vec![], external: vec![], l1_handler: vec![] }, /* TODO: add entry_points_by_type */ - abi: String::from("{}"), // FIXME: https://github.com/keep-starknet-strange/madara/issues/790 - })), - } -} - /// Returns a [`StateDiff`] from a [`CommitmentStateDiff`] pub fn blockifier_to_rpc_state_diff_types(commitment_state_diff: CommitmentStateDiff) -> Result { let storage_diffs: Vec = commitment_state_diff @@ -172,48 +146,6 @@ pub fn to_rpc_state_diff(thin_state_diff: ThinStateDiff) -> StateDiff { } } -/// Returns a compressed vector of bytes -pub(crate) fn compress(data: &[u8]) -> Result> { - let mut gzip_encoder = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); - // 2023-08-22: JSON serialization is already done in Blockifier - // https://github.com/keep-starknet-strange/blockifier/blob/no_std-support-7578442/crates/blockifier/src/execution/contract_class.rs#L129 - // https://github.com/keep-starknet-strange/blockifier/blob/no_std-support-7578442/crates/blockifier/src/execution/contract_class.rs#L389 - // serde_json::to_writer(&mut gzip_encoder, data)?; - gzip_encoder.write_all(data)?; - Ok(gzip_encoder.finish()?) -} - -/// Returns a [Result] (starknet-rs type) from a [HashMap>] -fn to_legacy_entry_points_by_type( - entries: &IndexMap>, -) -> Result { - fn collect_entry_points( - entries: &IndexMap>, - entry_point_type: EntryPointType, - ) -> Result> { - Ok(entries - .get(&entry_point_type) - .ok_or(anyhow!("Missing {:?} entry point", entry_point_type))? - .iter() - .map(|e| to_legacy_entry_point(e.clone())) - .collect::, FromByteArrayError>>()?) - } - - let constructor = collect_entry_points(entries, EntryPointType::Constructor)?; - let external = collect_entry_points(entries, EntryPointType::External)?; - let l1_handler = collect_entry_points(entries, EntryPointType::L1Handler)?; - - Ok(LegacyEntryPointsByType { constructor, external, l1_handler }) -} - -/// Returns a [LegacyContractEntryPoint] (starknet-rs) from a [EntryPoint] (starknet-api) -fn to_legacy_entry_point(entry_point: EntryPoint) -> Result { - let selector = FieldElement::from_bytes_be(&entry_point.selector.0.0)?; - let offset = entry_point.offset.0; - Ok(LegacyContractEntryPoint { selector, offset }) -} - /// Returns the current Starknet block from the block header's digest pub fn get_block_by_block_hash(client: &C, block_hash: ::Hash) -> Result where diff --git a/crates/client/rpc/Cargo.toml b/crates/client/rpc/Cargo.toml index a062e8cb9..d709cadef 100644 --- a/crates/client/rpc/Cargo.toml +++ b/crates/client/rpc/Cargo.toml @@ -16,12 +16,20 @@ repository = "https://github.com/keep-starknet-strange/madara" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -# Madara utils +# Madara mc-db = { workspace = true } mc-genesis-data-provider = { workspace = true } mc-rpc-core = { workspace = true } mc-storage = { workspace = true } +mp-block = { workspace = true } +mp-felt = { workspace = true } +mp-hashers = { workspace = true } +mp-simulations = { workspace = true } +mp-transactions = { workspace = true, features = ["client"] } pallet-starknet-runtime-api = { workspace = true } +# Substrate +sc-client-api = { workspace = true } +sc-network-sync = { workspace = true } sc-transaction-pool = { workspace = true } sc-transaction-pool-api = { workspace = true } sp-api = { workspace = true } @@ -30,26 +38,23 @@ sp-blockchain = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-timestamp = { workspace = true } - -# Substrate client -sc-client-api = { workspace = true } -sc-network-sync = { workspace = true } # Starknet blockifier = { workspace = true } cairo-vm = { workspace = true } +starknet-core = { workspace = true } +starknet-ff = { workspace = true } +starknet_api = { workspace = true } +# Others +anyhow = { workspace = true } +futures = { workspace = true } +indexmap = { workspace = true } jsonrpsee = { workspace = true, features = ["server", "macros"] } log = { workspace = true } -mp-block = { workspace = true } -mp-felt = { workspace = true } -mp-hashers = { workspace = true } -mp-simulations = { workspace = true } -mp-transactions = { workspace = true, features = ["client"] } serde = { workspace = true } serde_json = { workspace = true } -starknet-core = { workspace = true } -starknet-ff = { workspace = true } -starknet_api = { workspace = true } thiserror = { workspace = true } +tokio = { workspace = true, features = ["sync"] } + [dev-dependencies] rstest = { workspace = true } diff --git a/crates/client/rpc/src/contract_class_v0.rs b/crates/client/rpc/src/contract_class_v0.rs new file mode 100644 index 000000000..5100e5690 --- /dev/null +++ b/crates/client/rpc/src/contract_class_v0.rs @@ -0,0 +1,208 @@ +use anyhow::{anyhow, Result}; +use blockifier::execution::contract_class::ContractClassV0; +use cairo_vm::serde::deserialize_program::ProgramJson; +use indexmap::IndexMap; +use mp_felt::Felt252Wrapper; +use mp_transactions::V0ContractClassData; +use serde_json::value::RawValue; +use starknet_api::deprecated_contract_class::{EntryPoint, EntryPointType}; +use starknet_core::types::contract::legacy::{ + LegacyApTrackingData, LegacyAttribute, LegacyFlowTrackingData, LegacyHint, LegacyIdentifier, + LegacyIdentifierMember, LegacyProgram, LegacyReference, LegacyReferenceManager, +}; +use starknet_core::types::{ + CompressedLegacyContractClass, ContractClass, LegacyContractEntryPoint, LegacyEntryPointsByType, +}; +use starknet_ff::FromByteArrayError; + +pub fn casm_contract_class_to_compressed_legacy_contract_class( + contract_class: ContractClassV0, + mut v0_contract_class_data: V0ContractClassData, +) -> Result { + let program_json = ProgramJson::from(contract_class.program.clone()); + let legacy_program = LegacyProgram { + // If we stored some accessible_scopes, it means attributes was `Some` + attributes: v0_contract_class_data.accessible_scopes.map(|accessible_scopes| { + program_json + .attributes + .into_iter() + .zip(accessible_scopes) + .map(|(attribute, accessible_scopes)| LegacyAttribute { + accessible_scopes, + end_pc: attribute.end_pc.try_into().expect("the value shoud have fit"), + flow_tracking_data: attribute.flow_tracking_data.map(|ftd| LegacyFlowTrackingData { + ap_tracking: LegacyApTrackingData { + group: ftd.ap_tracking.group.try_into().expect("the value shoud have fit"), + offset: ftd.ap_tracking.offset.try_into().expect("the value shoud have fit"), + }, + reference_ids: ftd + .reference_ids + .into_iter() + .map(|(k, v)| (k, v.try_into().expect("the value should have fit"))) + .collect(), + }), + name: attribute.name, + start_pc: attribute.start_pc.try_into().expect("value should have fit"), + value: attribute.value, + }) + .collect() + }), + builtins: program_json + .builtins + .into_iter() + .map(|bn| { + bn.name().strip_suffix("_builtin").expect("all builtins names end with this suffix atm").to_string() + }) + .collect(), + compiler_version: v0_contract_class_data.compiler_version, + data: program_json + .data + .into_iter() + .map(|mr| match mr { + cairo_vm::types::relocatable::MaybeRelocatable::RelocatableValue(_) => { + panic!("All values should be relocatable at this point") + } + cairo_vm::types::relocatable::MaybeRelocatable::Int(f) => Felt252Wrapper::from(f).into(), + }) + .collect(), + // I'm pretty sure DeclareTransaction are guaranteed to not contain any program debug info + debug_info: None, + hints: program_json + .hints + .into_iter() + .map(|(k, v)| { + ( + k.try_into().expect("the value should have fit"), + v.into_iter() + .map(|hp| LegacyHint { + accessible_scopes: hp.accessible_scopes, + code: hp.code, + flow_tracking_data: LegacyFlowTrackingData { + ap_tracking: LegacyApTrackingData { + group: hp + .flow_tracking_data + .ap_tracking + .group + .try_into() + .expect("value should have fit"), + offset: hp + .flow_tracking_data + .ap_tracking + .offset + .try_into() + .expect("value should have fit"), + }, + reference_ids: hp + .flow_tracking_data + .reference_ids + .into_iter() + .map(|(k, v)| (k, v.try_into().expect("value should have fit"))) + .collect(), + }, + }) + .collect(), + ) + }) + .collect(), + identifiers: program_json + .identifiers + .into_iter() + .map(|(k, identifier)| { + let identifiers_data = + v0_contract_class_data.identifiers_data.remove(&k).expect("there should have been a value there"); + ( + k, + LegacyIdentifier { + cairo_type: identifier.cairo_type, + full_name: identifier.full_name, + members: identifier.members.map(|members| { + members + .into_iter() + .map(|(k, member)| { + ( + k, + LegacyIdentifierMember { + cairo_type: member.cairo_type, + offset: member.offset.try_into().expect("value should have fit"), + }, + ) + }) + .collect() + }), + pc: identifier.pc.map(|pc| pc.try_into().expect("value should have fit")), + r#type: identifier.type_.expect("there should have been a value there"), + value: identifier.value.map(|v| { + RawValue::from_string(v.to_string()).expect("the value should have been convertible") + }), + decorators: identifiers_data.decorators, + destination: identifiers_data.destination, + references: identifiers_data.references, + size: identifiers_data.size, + }, + ) + }) + .collect(), + main_scope: v0_contract_class_data.main_scope, + prime: program_json.prime, + reference_manager: LegacyReferenceManager { + references: program_json + .reference_manager + .references + .into_iter() + .zip(v0_contract_class_data.references_data) + .map(|(reference, reference_data)| { + LegacyReference { + ap_tracking_data: LegacyApTrackingData { + group: reference.ap_tracking_data.group.try_into().expect("value should have fit"), + offset: reference.ap_tracking_data.offset.try_into().expect("value should have fit"), + }, + pc: reference_data.pc, + // We have the value but not the method to recreate the string from it. + // So for now we store it as it is in the rpc, and get it back here. + value: reference_data.value, + } + }) + .collect(), + }, + }; + + let entry_points_by_type = to_legacy_entry_points_by_type(&contract_class.entry_points_by_type)?; + + Ok(ContractClass::Legacy(CompressedLegacyContractClass { + program: legacy_program.compress().unwrap(), + entry_points_by_type, + abi: v0_contract_class_data.abi, + })) +} + +/// Returns a [Result] (starknet-rs type) from a [HashMap>] +fn to_legacy_entry_points_by_type( + entries: &IndexMap>, +) -> Result { + fn collect_entry_points( + entries: &IndexMap>, + entry_point_type: EntryPointType, + ) -> Result> { + Ok(entries + .get(&entry_point_type) + .ok_or(anyhow!("Missing {:?} entry point", entry_point_type))? + .iter() + .map(|e| to_legacy_entry_point(e.clone())) + .collect::, FromByteArrayError>>()?) + } + + let constructor = collect_entry_points(entries, EntryPointType::Constructor)?; + let external = collect_entry_points(entries, EntryPointType::External)?; + let l1_handler = collect_entry_points(entries, EntryPointType::L1Handler)?; + + Ok(LegacyEntryPointsByType { constructor, external, l1_handler }) +} + +/// Returns a [LegacyContractEntryPoint] (starknet-rs) from a [EntryPoint] (starknet-api) +fn to_legacy_entry_point(entry_point: EntryPoint) -> Result { + // let selector = FieldElement::from_bytes_be(&entry_point.selector.0.0)?; + let selector = Felt252Wrapper::from(entry_point.selector).into(); + let offset = entry_point.offset.0; + Ok(LegacyContractEntryPoint { selector, offset }) +} diff --git a/crates/client/rpc/src/events/mod.rs b/crates/client/rpc/src/events/mod.rs index a9683f254..ce0611938 100644 --- a/crates/client/rpc/src/events/mod.rs +++ b/crates/client/rpc/src/events/mod.rs @@ -13,6 +13,7 @@ use pallet_starknet_runtime_api::{ConvertTransactionRuntimeApi, StarknetRuntimeA use sc_client_api::backend::{Backend, StorageProvider}; use sc_client_api::BlockBackend; use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_runtime::traits::Block as BlockT; @@ -32,6 +33,7 @@ where C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, BE: Backend, H: HasherT + Send + Sync + 'static, + P: TransactionPool, { /// Helper function to get Starknet block details /// diff --git a/crates/client/rpc/src/lib.rs b/crates/client/rpc/src/lib.rs index 4065b278a..4e8616bfb 100644 --- a/crates/client/rpc/src/lib.rs +++ b/crates/client/rpc/src/lib.rs @@ -3,6 +3,7 @@ //! It uses the madara client and backend in order to answer queries. mod constants; +mod contract_class_v0; mod errors; mod events; mod madara_backend_client; @@ -13,6 +14,7 @@ mod types; use std::collections::HashMap; use std::marker::PhantomData; +use std::pin::Pin; use std::sync::Arc; use blockifier::transaction::account_transaction::AccountTransaction; @@ -34,25 +36,27 @@ use mp_hashers::HasherT; use mp_simulations::SimulationFlags; use mp_transactions::compute_hash::ComputeTransactionHash; use mp_transactions::from_broadcasted_transactions::{ - try_account_tx_from_broadcasted_tx, try_declare_tx_from_broadcasted_declare_tx, + split_broadcasted_declare_tx_into_declare_tx_and_additional_data, try_account_tx_from_broadcasted_tx, try_deploy_tx_from_broadcasted_deploy_tx, try_invoke_tx_from_broadcasted_invoke_tx, }; use mp_transactions::to_starknet_core_transaction::to_starknet_core_tx; -use mp_transactions::{compute_message_hash, get_transaction_hash, TransactionStatus}; +use mp_transactions::{ + compute_message_hash, get_transaction_hash, ContractClassData, TransactionStatus, V1ContractClassData, +}; use pallet_starknet_runtime_api::{ConvertTransactionRuntimeApi, StarknetRuntimeApi}; use sc_client_api::backend::{Backend, StorageProvider}; use sc_client_api::BlockBackend; use sc_network_sync::SyncingService; use sc_transaction_pool::{ChainApi, Pool}; use sc_transaction_pool_api::error::{Error as PoolError, IntoPoolError}; -use sc_transaction_pool_api::{InPoolTransaction, TransactionPool, TransactionSource}; +use sc_transaction_pool_api::{InPoolTransaction, TransactionPool, TransactionSource, TransactionStatusStreamFor}; use sp_api::ProvideRuntimeApi; use sp_arithmetic::traits::UniqueSaturatedInto; use sp_blockchain::HeaderBackend; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use sp_runtime::transaction_validity::InvalidTransaction; -use starknet_api::core::Nonce; +use starknet_api::core::{ClassHash, Nonce}; use starknet_api::hash::StarkFelt; use starknet_api::transaction::{Calldata, Fee, TransactionHash, TransactionVersion}; use starknet_core::types::{ @@ -72,10 +76,13 @@ use starknet_core::utils::get_selector_from_name; use trace_api::get_previous_block_substrate_hash; use crate::constants::{MAX_EVENTS_CHUNK_SIZE, MAX_EVENTS_KEYS}; +use crate::contract_class_v0::casm_contract_class_to_compressed_legacy_contract_class; use crate::types::RpcEventFilter; +type DeclareTransactionStatusStream

= (TransactionHash, ClassHash, Pin>>); + /// A Starknet RPC server for Madara -pub struct Starknet { +pub struct Starknet, H> { client: Arc, backend: Arc>, overrides: Arc>, @@ -85,6 +92,7 @@ pub struct Starknet { sync_service: Arc>, starting_block: <::Header as HeaderT>::Number, genesis_provider: Arc, + contract_class_data_tx: tokio::sync::mpsc::UnboundedSender>, _marker: PhantomData<(B, BE, H)>, } @@ -100,7 +108,7 @@ pub struct Starknet { // # Returns // * `Self` - The actual Starknet struct #[allow(clippy::too_many_arguments)] -impl Starknet { +impl, H> Starknet { pub fn new( client: Arc, backend: Arc>, @@ -110,6 +118,7 @@ impl Starknet { sync_service: Arc>, starting_block: <::Header as HeaderT>::Number, genesis_provider: Arc, + contract_class_data_tx: tokio::sync::mpsc::UnboundedSender>, ) -> Self { Self { client, @@ -120,6 +129,7 @@ impl Starknet { sync_service, starting_block, genesis_provider, + contract_class_data_tx, _marker: PhantomData, } } @@ -129,6 +139,7 @@ impl Starknet where B: BlockT, C: HeaderBackend + 'static, + P: TransactionPool, { pub fn current_block_number(&self) -> RpcResult { Ok(UniqueSaturatedInto::::unique_saturated_into(self.client.info().best_number)) @@ -139,6 +150,7 @@ impl Starknet where B: BlockT, C: HeaderBackend + 'static, + P: TransactionPool, { pub fn current_spec_version(&self) -> RpcResult { Ok("0.7.0".to_string()) @@ -152,6 +164,7 @@ where C: ProvideRuntimeApi, C::Api: StarknetRuntimeApi, H: HasherT + Send + Sync + 'static, + P: TransactionPool, { pub fn current_block_hash(&self) -> Result { let substrate_block_hash = self.client.info().best_hash; @@ -292,40 +305,51 @@ where ) -> RpcResult { let best_block_hash = self.get_best_block_hash(); - let opt_sierra_contract_class = if let BroadcastedDeclareTransaction::V2(ref tx) = declare_transaction { - Some(flattened_sierra_to_sierra_contract_class(tx.contract_class.clone())) - } else { - None - }; - let chain_id = Felt252Wrapper(self.chain_id()?.0); - let transaction = try_declare_tx_from_broadcasted_declare_tx(declare_transaction, chain_id).map_err(|e| { - error!("Failed to convert BroadcastedDeclareTransaction to DeclareTransaction, error: {e}"); - StarknetRpcApiError::InternalServerError - })?; + let (transaction, contract_class_data) = + split_broadcasted_declare_tx_into_declare_tx_and_additional_data(declare_transaction, chain_id).map_err( + |e| { + error!("Failed to convert BroadcastedDeclareTransaction to DeclareTransaction, error: {e}"); + StarknetRpcApiError::InternalServerError + }, + )?; let (class_hash, tx_hash) = (transaction.class_hash(), transaction.tx_hash()); - let current_block_hash = self.get_best_block_hash(); - let contract_class = self + + if self .overrides .for_block_hash(self.client.as_ref(), current_block_hash) - .contract_class_by_class_hash(current_block_hash, class_hash); - - if let Some(contract_class) = contract_class { - error!("Contract class already exists: {:?}", contract_class); + .contract_class_by_class_hash(current_block_hash, class_hash) + .is_some() + { + error!("Contract class with class hash {:?} already exists", class_hash); return Err(StarknetRpcApiError::ClassAlreadyDeclared.into()); } let extrinsic = self.convert_tx_to_extrinsic(best_block_hash, AccountTransaction::Declare(transaction))?; - submit_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?; - - if let Some(sierra_contract_class) = opt_sierra_contract_class { - if let Some(e) = self.backend.sierra_classes().store_sierra_class(class_hash, sierra_contract_class).err() { - log::error!("Failed to store the sierra contract class for declare tx `{tx_hash}`: {e}") + { + let contract_class_data_db = self.backend.contract_class_data(); + match contract_class_data { + ContractClassData::V0(v0_contract_class_data) => { + contract_class_data_db.register_pending_v0_contract_class_data(class_hash, v0_contract_class_data) + } + ContractClassData::V1(v1_contract_class_data) => { + contract_class_data_db.register_pending_v1_contract_class_data(class_hash, v1_contract_class_data) + } } + .map_err(|e| { + error!("Failed store contract class data: {e}"); + StarknetRpcApiError::InternalServerError + })?; } + let transaction_status_watcher_stream: Pin>> = + submit_and_watch_extrinsic(self.pool.clone(), best_block_hash, extrinsic).await?; + + self.contract_class_data_tx + .send((tx_hash, class_hash, transaction_status_watcher_stream)) + .expect("this should work"); Ok(DeclareTransactionResult { transaction_hash: Felt252Wrapper::from(tx_hash).into(), @@ -648,20 +672,17 @@ where StarknetRpcApiError::BlockNotFound })?; - let contract_address_wrapped = Felt252Wrapper(contract_address).into(); - let contract_class = self + let contract_address = Felt252Wrapper(contract_address).into(); + let class_hash = self .overrides .for_block_hash(self.client.as_ref(), substrate_block_hash) - .contract_class_by_address(substrate_block_hash, contract_address_wrapped) + .contract_class_hash_by_address(substrate_block_hash, contract_address) .ok_or_else(|| { - error!("Failed to retrieve contract class at '{contract_address}'"); + error!("Failed to retrieve class hash at '{contract_address:?}'"); StarknetRpcApiError::ContractNotFound })?; - Ok(blockifier_to_rpc_contract_class_types(contract_class).map_err(|e| { - error!("Failed to convert contract class at '{contract_address}' to RPC contract class: {e}"); - StarknetRpcApiError::InvalidContractClass - })?) + self.get_contract_class_by_hash_at_block(substrate_block_hash, class_hash) } /// Get the contract class hash in the given block for the contract deployed at the given @@ -786,20 +807,7 @@ where })?; let class_hash = Felt252Wrapper(class_hash).into(); - - let contract_class = self - .overrides - .for_block_hash(self.client.as_ref(), substrate_block_hash) - .contract_class_by_class_hash(substrate_block_hash, class_hash) - .ok_or_else(|| { - error!("Failed to retrieve contract class from hash '{class_hash}'"); - StarknetRpcApiError::ClassHashNotFound - })?; - - Ok(blockifier_to_rpc_contract_class_types(contract_class).map_err(|e| { - error!("Failed to convert contract class from hash '{class_hash}' to RPC contract class: {e}"); - StarknetRpcApiError::InternalServerError - })?) + self.get_contract_class_by_hash_at_block(substrate_block_hash, class_hash) } /// Get block information with transaction hashes given the block id. @@ -1682,6 +1690,46 @@ where Ok(MaybePendingTransactionReceipt::PendingReceipt(receipt)) } + + fn get_contract_class_by_hash_at_block( + &self, + substrate_block_hash: B::Hash, + class_hash: ClassHash, + ) -> RpcResult { + let contract_class_data = self + .backend + .contract_class_data() + .read_contract_class_data(class_hash) + .map_err(|e| { + error!("Failed to retrieve contract class data for class_hash '{class_hash:?}': {e}"); + StarknetRpcApiError::InternalServerError + })? + .ok_or(StarknetRpcApiError::ClassHashNotFound)?; + + let contract_class = match contract_class_data { + ContractClassData::V0(v0_contract_class_data) => { + let casm_contract_class = { + let contract_class = self + .overrides + .for_block_hash(self.client.as_ref(), substrate_block_hash) + .contract_class_by_class_hash(substrate_block_hash, class_hash) + .ok_or_else(|| { + error!("Failed to retrieve contract class from hash '{class_hash}'"); + StarknetRpcApiError::ClassHashNotFound + })?; + match contract_class { + blockifier::execution::contract_class::ContractClass::V0(cc) => cc, + _ => unreachable!(), + } + }; + + casm_contract_class_to_compressed_legacy_contract_class(casm_contract_class, v0_contract_class_data)? + } + ContractClassData::V1(V1ContractClassData { sierra_program }) => ContractClass::Sierra(sierra_program), + }; + + Ok(contract_class) + } } async fn submit_extrinsic( @@ -1703,6 +1751,25 @@ where }) } +async fn submit_and_watch_extrinsic( + pool: Arc

, + best_block_hash: ::Hash, + extrinsic: ::Extrinsic, +) -> Result>>, StarknetRpcApiError> +where + P: TransactionPool + 'static, + B: BlockT, + ::Extrinsic: Send + Sync + 'static, +{ + pool.submit_and_watch(best_block_hash, TX_SOURCE, extrinsic).await.map_err(|e| { + error!("Failed to submit extrinsic: {:?}", e); + match e.into_pool_error() { + Ok(PoolError::InvalidTransaction(InvalidTransaction::BadProof)) => StarknetRpcApiError::ValidationFailure, + _ => StarknetRpcApiError::InternalServerError, + } + }) +} + /// The current timestamp in seconds. fn calculate_pending_block_timestamp() -> u64 { let timestamp_in_millisecond = sp_timestamp::InherentDataProvider::from_system_time().as_millis(); diff --git a/crates/client/rpc/src/runtime_api.rs b/crates/client/rpc/src/runtime_api.rs index ca903c170..c263e9c02 100644 --- a/crates/client/rpc/src/runtime_api.rs +++ b/crates/client/rpc/src/runtime_api.rs @@ -14,6 +14,7 @@ use mp_simulations::SimulationFlags; use pallet_starknet_runtime_api::{ConvertTransactionRuntimeApi, StarknetRuntimeApi}; use sc_client_api::backend::Backend; use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::TransactionPool; use sp_api::{ApiError, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_runtime::traits::Block as BlockT; @@ -34,6 +35,7 @@ where C: ProvideRuntimeApi, C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, H: HasherT + Send + Sync + 'static, + P: TransactionPool, { pub fn do_call( &self, diff --git a/crates/client/rpc/src/starknetrpcwrapper.rs b/crates/client/rpc/src/starknetrpcwrapper.rs index a2a778b9c..5ea2bf787 100644 --- a/crates/client/rpc/src/starknetrpcwrapper.rs +++ b/crates/client/rpc/src/starknetrpcwrapper.rs @@ -28,9 +28,13 @@ use starknet_core::types::{ use crate::Starknet; // Newtype Wrapper to escape Arc orphan rules -pub struct StarknetRpcWrapper(pub Arc>); +pub struct StarknetRpcWrapper, H>( + pub Arc>, +); -impl Clone for StarknetRpcWrapper { +impl, H> Clone + for StarknetRpcWrapper +{ fn clone(&self) -> Self { StarknetRpcWrapper(self.0.clone()) } diff --git a/crates/client/rpc/src/trace_api.rs b/crates/client/rpc/src/trace_api.rs index 65d1ba24d..de6a372c6 100644 --- a/crates/client/rpc/src/trace_api.rs +++ b/crates/client/rpc/src/trace_api.rs @@ -484,6 +484,7 @@ where C::Api: StarknetRuntimeApi + ConvertTransactionRuntimeApi, H: HasherT + Send + Sync + 'static, BE: Backend + 'static, + P: TransactionPool, { let starknet_block = get_block_by_block_hash(starknet.client.as_ref(), substrate_block_hash).map_err(|e| { error!("Failed to get block for block hash {substrate_block_hash}: '{e}'"); diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 58948f4c7..e63051570 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -29,6 +29,7 @@ num-bigint = { workspace = true } serde = { workspace = true } sha3 = { workspace = true } thiserror = { workspace = true } +tokio = { workspace = true, features = ["sync"] } frame-system = { workspace = true } sc-basic-authorship = { workspace = true } @@ -78,6 +79,7 @@ frame-benchmarking = { workspace = true } frame-benchmarking-cli = { workspace = true } madara-runtime = { workspace = true, features = ["std"] } mc-commitment-state-diff = { workspace = true } +mc-contract-class-data = { workspace = true } mc-db = { workspace = true } mc-eth-client = { workspace = true } mc-l1-gas-price = { workspace = true } @@ -100,6 +102,7 @@ mp-starknet-inherent = { workspace = true, features = ["client"] } # Starknet blockifier = { workspace = true } +starknet_api = { workspace = true } # CLI-specific dependencies diff --git a/crates/node/src/chain_spec.rs b/crates/node/src/chain_spec.rs index 6a4902e00..25c4597f9 100644 --- a/crates/node/src/chain_spec.rs +++ b/crates/node/src/chain_spec.rs @@ -184,7 +184,7 @@ fn testnet_genesis( authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), _config: Default::default(), }, - /// Starknet Genesis configuration. + // Starknet Genesis configuration. starknet: starknet_genesis_config, } } diff --git a/crates/node/src/commands/setup.rs b/crates/node/src/commands/setup.rs index cd2d5c1e6..61ae0b5e2 100644 --- a/crates/node/src/commands/setup.rs +++ b/crates/node/src/commands/setup.rs @@ -142,7 +142,6 @@ impl ConfigSource { impl SetupCmd { pub fn run(&self) -> Result<()> { - log::info!("setup cmd: {:?}", self); let dest_config_dir_path = { let chain_id = self.chain_id(); let base_path = self.base_path().unwrap_or_else(|| BasePath::from_project("", "", &Cli::executable_name())); diff --git a/crates/node/src/rpc/mod.rs b/crates/node/src/rpc/mod.rs index 913e830a1..d83e7a388 100644 --- a/crates/node/src/rpc/mod.rs +++ b/crates/node/src/rpc/mod.rs @@ -25,7 +25,7 @@ use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; pub use starknet::StarknetDeps; /// Full client dependencies. -pub struct FullDeps { +pub struct FullDeps> { /// The client instance to use. pub client: Arc, /// Transaction pool instance. @@ -37,7 +37,7 @@ pub struct FullDeps { /// Manual seal command sink pub command_sink: Option>>, /// Starknet dependencies - pub starknet: StarknetDeps, + pub starknet: StarknetDeps, } /// Instantiate all full RPC extensions. @@ -82,6 +82,7 @@ where starknet_params.sync_service, starknet_params.starting_block, starknet_params.genesis_provider, + starknet_params.contract_class_data_tx, ))); module.merge(MadaraRpcApiServer::into_rpc(rpc_instance.clone()))?; diff --git a/crates/node/src/rpc/starknet.rs b/crates/node/src/rpc/starknet.rs index 0a98efed4..7d74fe566 100644 --- a/crates/node/src/rpc/starknet.rs +++ b/crates/node/src/rpc/starknet.rs @@ -1,14 +1,20 @@ +use std::pin::Pin; use std::sync::Arc; use mc_db::Backend; use mc_genesis_data_provider::GenesisProvider; use mc_storage::OverrideHandle; use sc_network_sync::SyncingService; +use sc_transaction_pool_api::{TransactionPool, TransactionStatusStreamFor}; use sp_api::BlockT; use sp_runtime::traits::Header as HeaderT; +use starknet_api::core::ClassHash; +use starknet_api::transaction::TransactionHash; + +type DeclareTransactionStatusStream

= (TransactionHash, ClassHash, Pin>>); /// Extra dependencies for Starknet compatibility. -pub struct StarknetDeps { +pub struct StarknetDeps> { /// The client instance to use. pub client: Arc, /// Madara Backend. @@ -21,9 +27,11 @@ pub struct StarknetDeps { pub starting_block: <::Header as HeaderT>::Number, /// The genesis state data provider pub genesis_provider: Arc, + /// The channel used to send newly declared contract classes data to the madara backed + pub contract_class_data_tx: tokio::sync::mpsc::UnboundedSender>, } -impl Clone for StarknetDeps { +impl> Clone for StarknetDeps { fn clone(&self) -> Self { Self { client: self.client.clone(), @@ -32,6 +40,7 @@ impl Clone for StarknetDeps { sync_service: self.sync_service.clone(), starting_block: self.starting_block, genesis_provider: self.genesis_provider.clone(), + contract_class_data_tx: self.contract_class_data_tx.clone(), } } } diff --git a/crates/node/src/service.rs b/crates/node/src/service.rs index f3b8b1124..45b9ad49f 100644 --- a/crates/node/src/service.rs +++ b/crates/node/src/service.rs @@ -263,6 +263,7 @@ pub fn new_full( _ => (None, None), }; + let (contract_class_data_tx, contract_class_data_rx) = tokio::sync::mpsc::unbounded_channel(); let overrides = overrides_handle(client.clone()); let config_dir: PathBuf = config.data_path.clone(); let genesis_data = OnDiskGenesisConfig(config_dir); @@ -273,6 +274,7 @@ pub fn new_full( sync_service: sync_service.clone(), starting_block, genesis_provider: genesis_data.into(), + contract_class_data_tx, }; let rpc_extensions_builder = { @@ -369,6 +371,16 @@ pub fn new_full( } } + task_manager.spawn_essential_handle().spawn( + "mc-contract-class-data-worker", + Some(MADARA_TASK_GROUP), + mc_contract_class_data::run_worker::>( + madara_backend.contract_class_data().clone(), + madara_backend.mapping().clone(), + contract_class_data_rx, + ), + ); + // manual-seal authorship if !sealing.is_default() { log::info!("{} sealing enabled.", sealing); diff --git a/crates/primitives/transactions/src/from_broadcasted_transactions.rs b/crates/primitives/transactions/src/from_broadcasted_transactions.rs index d6ef214ac..b3b8a2735 100644 --- a/crates/primitives/transactions/src/from_broadcasted_transactions.rs +++ b/crates/primitives/transactions/src/from_broadcasted_transactions.rs @@ -41,6 +41,7 @@ use starknet_crypto::FieldElement; use thiserror::Error; use crate::compute_hash::ComputeTransactionHash; +use crate::{ContractClassData, IdentifierData, ReferenceData, V0ContractClassData}; #[derive(Debug, Error)] pub enum BroadcastedTransactionConversionError { @@ -85,8 +86,239 @@ pub fn try_account_tx_from_broadcasted_tx( } } +pub fn split_broadcasted_declare_tx_into_declare_tx_and_additional_data( + broadcasted_declare_tx: BroadcastedDeclareTransaction, + chain_id: Felt252Wrapper, +) -> Result<(DeclareTransaction, ContractClassData), BroadcastedTransactionConversionError> { + fn try_new_declare_transaction( + tx: starknet_api::transaction::DeclareTransaction, + tx_hash: TransactionHash, + class_info: ClassInfo, + is_query: bool, + ) -> Result { + if is_query { + DeclareTransaction::new_for_query(tx, tx_hash, class_info) + } else { + DeclareTransaction::new(tx, tx_hash, class_info) + } + .map_err(|_| BroadcastedTransactionConversionError::InvalidTransactionVersion) + } + + let user_tx = match broadcasted_declare_tx { + BroadcastedDeclareTransaction::V1(BroadcastedDeclareTransactionV1 { + max_fee, + signature, + nonce, + contract_class: compresed_contract_class, + sender_address, + is_query, + }) => { + // Create a GzipDecoder to decompress the bytes + let mut gz = GzDecoder::new(&compresed_contract_class.program[..]); + + // Read the decompressed bytes into a Vec + let mut decompressed_bytes = Vec::new(); + std::io::Read::read_to_end(&mut gz, &mut decompressed_bytes) + .map_err(|_| BroadcastedTransactionConversionError::ProgramDecompressionFailed)?; + + let (class_hash, contract_class_data) = { + let legacy_contract_class = LegacyContractClass { + program: serde_json::from_slice(decompressed_bytes.as_slice()) + .map_err(|_| BroadcastedTransactionConversionError::ProgramDeserializationFailed)?, + abi: match compresed_contract_class.abi.as_ref() { + Some(abi) => abi.iter().cloned().map(|entry| entry.into()).collect::>(), + None => vec![], + }, + entry_points_by_type: to_raw_legacy_entry_points( + compresed_contract_class.entry_points_by_type.clone(), + ), + }; + + let legacy_program = &legacy_contract_class.program; + let v0_contract_class_data = V0ContractClassData { + abi: compresed_contract_class.abi.clone(), + accessible_scopes: legacy_program.attributes.as_ref().map(|attributes| { + attributes.iter().map(|attribute| attribute.accessible_scopes.clone()).collect() + }), + compiler_version: legacy_program.compiler_version.clone(), + identifiers_data: legacy_program + .identifiers + .iter() + .map(|(k, v)| { + ( + k.clone(), + IdentifierData { + decorators: v.decorators.clone(), + destination: v.destination.clone(), + references: v.references.clone(), + size: v.size, + }, + ) + }) + .collect(), + main_scope: legacy_program.main_scope.clone(), + references_data: legacy_program + .reference_manager + .references + .iter() + .map(|reference| ReferenceData { value: reference.value.clone(), pc: reference.pc }) + .collect(), + }; + + let class_hash = legacy_contract_class + .class_hash() + .map_err(|_| BroadcastedTransactionConversionError::ClassHashComputationFailed)?; + + (class_hash, ContractClassData::V0(v0_contract_class_data)) + }; + let abi_length = compresed_contract_class.abi.as_ref().map(|abi| abi.len()).unwrap_or_default(); + let tx = + starknet_api::transaction::DeclareTransaction::V1(starknet_api::transaction::DeclareTransactionV0V1 { + max_fee: Fee(max_fee + .try_into() + .map_err(|_| BroadcastedTransactionConversionError::MaxFeeTooBig)?), + signature: TransactionSignature( + signature.into_iter().map(|v| Felt252Wrapper::from(v).into()).collect(), + ), + nonce: Felt252Wrapper::from(nonce).into(), + class_hash: Felt252Wrapper::from(class_hash).into(), + sender_address: Felt252Wrapper::from(sender_address).into(), + }); + + let contract_class = instantiate_blockifier_contract_class(compresed_contract_class, decompressed_bytes)?; + let tx_hash = tx.compute_hash(chain_id, is_query); + + let declare_transaction = + try_new_declare_transaction(tx, tx_hash, ClassInfo::new(&contract_class, 0, abi_length)?, is_query)?; + + (declare_transaction, contract_class_data) + } + BroadcastedDeclareTransaction::V2(BroadcastedDeclareTransactionV2 { + max_fee, + signature, + nonce, + contract_class: flattened_contract_class, + sender_address, + compiled_class_hash, + is_query, + }) => { + let sierra_contract_class = Felt252Wrapper::from(flattened_contract_class.class_hash()).into(); + let sierra_program_length = flattened_contract_class.sierra_program.len(); + let abi_length = flattened_contract_class.abi.len(); + + let casm_contract_class = flattened_sierra_to_casm_contract_class(flattened_contract_class.clone()) + .map_err(BroadcastedTransactionConversionError::SierraCompilationFailed)?; + // ensure that the user has sign the correct class hash + if get_casm_contract_class_hash(&casm_contract_class) != compiled_class_hash { + return Err(BroadcastedTransactionConversionError::InvalidCompiledClassHash); + } + let tx = + starknet_api::transaction::DeclareTransaction::V2(starknet_api::transaction::DeclareTransactionV2 { + max_fee: Fee(max_fee + .try_into() + .map_err(|_| BroadcastedTransactionConversionError::MaxFeeTooBig)?), + signature: TransactionSignature( + signature.into_iter().map(|v| Felt252Wrapper::from(v).into()).collect(), + ), + nonce: Felt252Wrapper::from(nonce).into(), + class_hash: sierra_contract_class, + sender_address: Felt252Wrapper::from(sender_address).into(), + compiled_class_hash: Felt252Wrapper::from(compiled_class_hash).into(), + }); + + let tx_hash = tx.compute_hash(chain_id, is_query); + let contract_class = ContractClass::V1( + ContractClassV1::try_from(casm_contract_class) + .map_err(|_| BroadcastedTransactionConversionError::CasmContractClassConversionFailed)?, + ); + + let declare_transaction = try_new_declare_transaction( + tx, + tx_hash, + ClassInfo::new(&contract_class, sierra_program_length, abi_length)?, + is_query, + )?; + + ( + declare_transaction, + ContractClassData::V1(crate::V1ContractClassData { + sierra_program: Arc::unwrap_or_clone(flattened_contract_class), + }), + ) + } + BroadcastedDeclareTransaction::V3(BroadcastedDeclareTransactionV3 { + sender_address, + compiled_class_hash, + signature, + nonce, + contract_class: flattened_contract_class, + resource_bounds, + tip, + paymaster_data, + account_deployment_data, + nonce_data_availability_mode, + fee_data_availability_mode, + is_query, + }) => { + let sierra_contract_class = Felt252Wrapper::from(flattened_contract_class.class_hash()).into(); + let sierra_program_length = flattened_contract_class.sierra_program.len(); + let abi_length = flattened_contract_class.abi.len(); + + let casm_contract_class = flattened_sierra_to_casm_contract_class(flattened_contract_class.clone()) + .map_err(BroadcastedTransactionConversionError::SierraCompilationFailed)?; + // ensure that the user has sign the correct class hash + if get_casm_contract_class_hash(&casm_contract_class) != compiled_class_hash { + return Err(BroadcastedTransactionConversionError::InvalidCompiledClassHash); + } + + let tx = + starknet_api::transaction::DeclareTransaction::V3(starknet_api::transaction::DeclareTransactionV3 { + resource_bounds: resource_bounds_mapping_conversion(resource_bounds), + tip: Tip(tip), + signature: TransactionSignature( + signature.into_iter().map(|v| Felt252Wrapper::from(v).into()).collect(), + ), + nonce: Felt252Wrapper::from(nonce).into(), + class_hash: sierra_contract_class, + compiled_class_hash: Felt252Wrapper::from(compiled_class_hash).into(), + sender_address: Felt252Wrapper::from(sender_address).into(), + nonce_data_availability_mode: data_availability_mode_conversion(nonce_data_availability_mode), + fee_data_availability_mode: data_availability_mode_conversion(fee_data_availability_mode), + paymaster_data: PaymasterData( + paymaster_data.into_iter().map(|v| Felt252Wrapper::from(v).into()).collect(), + ), + account_deployment_data: AccountDeploymentData( + account_deployment_data.into_iter().map(|v| Felt252Wrapper::from(v).into()).collect(), + ), + }); + + let tx_hash = tx.compute_hash(chain_id, is_query); + let contract_class = ContractClass::V1( + ContractClassV1::try_from(casm_contract_class) + .map_err(|_| BroadcastedTransactionConversionError::CasmContractClassConversionFailed)?, + ); + + let declare_transaction = try_new_declare_transaction( + tx, + tx_hash, + ClassInfo::new(&contract_class, sierra_program_length, abi_length)?, + is_query, + )?; + + ( + declare_transaction, + ContractClassData::V1(crate::V1ContractClassData { + sierra_program: Arc::unwrap_or_clone(flattened_contract_class), + }), + ) + } + }; + + Ok(user_tx) +} + pub fn try_declare_tx_from_broadcasted_declare_tx( - value: BroadcastedDeclareTransaction, + broadcasted_declare_tx: BroadcastedDeclareTransaction, chain_id: Felt252Wrapper, ) -> Result { fn try_new_declare_transaction( @@ -103,7 +335,7 @@ pub fn try_declare_tx_from_broadcasted_declare_tx( .map_err(|_| BroadcastedTransactionConversionError::InvalidTransactionVersion) } - let user_tx = match value { + let user_tx = match broadcasted_declare_tx { BroadcastedDeclareTransaction::V1(BroadcastedDeclareTransactionV1 { max_fee, signature, @@ -222,6 +454,10 @@ pub fn try_declare_tx_from_broadcasted_declare_tx( let casm_contract_class = flattened_sierra_to_casm_contract_class(flattened_contract_class) .map_err(BroadcastedTransactionConversionError::SierraCompilationFailed)?; + // ensure that the user has sign the correct class hash + if get_casm_contract_class_hash(&casm_contract_class) != compiled_class_hash { + return Err(BroadcastedTransactionConversionError::InvalidCompiledClassHash); + } let tx = starknet_api::transaction::DeclareTransaction::V3(starknet_api::transaction::DeclareTransactionV3 { diff --git a/crates/primitives/transactions/src/lib.rs b/crates/primitives/transactions/src/lib.rs index 28f9b5f60..ec0ddc9ae 100644 --- a/crates/primitives/transactions/src/lib.rs +++ b/crates/primitives/transactions/src/lib.rs @@ -1,6 +1,7 @@ //! Starknet transaction related functionality. #![feature(trait_upcasting)] +#![feature(arc_unwrap_or_clone)] pub mod compute_hash; pub mod execution; @@ -9,13 +10,19 @@ pub mod from_broadcasted_transactions; #[cfg(feature = "client")] pub mod to_starknet_core_transaction; +use std::collections::HashMap; + use blockifier::transaction::account_transaction::AccountTransaction; use blockifier::transaction::transaction_execution::Transaction; +use serde::{Deserialize, Serialize}; use sp_core::H256; use starknet_api::core::{ContractAddress, Nonce, PatriciaKey}; use starknet_api::hash::StarkFelt; use starknet_api::transaction::TransactionHash; -use starknet_core::types::{TransactionExecutionStatus, TransactionFinalityStatus}; +use starknet_core::types::contract::legacy::LegacyReference; +use starknet_core::types::{ + FlattenedSierraClass, LegacyContractAbiEntry, TransactionExecutionStatus, TransactionFinalityStatus, +}; use starknet_ff::FieldElement; const SIMULATE_TX_VERSION_OFFSET: FieldElement = @@ -139,3 +146,38 @@ impl From<&Transaction> for TxType { } } } + +#[derive(Debug)] +pub enum ContractClassData { + V0(V0ContractClassData), + V1(V1ContractClassData), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct V0ContractClassData { + pub abi: Option>, + pub accessible_scopes: Option>>, + pub compiler_version: Option, + pub identifiers_data: HashMap, + pub main_scope: String, + pub references_data: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct IdentifierData { + pub decorators: Option>, + pub destination: Option, + pub references: Option>, + pub size: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ReferenceData { + pub value: String, + pub pc: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct V1ContractClassData { + pub sierra_program: FlattenedSierraClass, +} diff --git a/docs/rpc-contribution.md b/docs/rpc-contribution.md index 2e8d413af..263487b10 100644 --- a/docs/rpc-contribution.md +++ b/docs/rpc-contribution.md @@ -231,7 +231,7 @@ able to target your new endpoint. Prior to running the test, you will need to run the generate_declare_contracts.sh script -`cd starknet-rpc-test/contracts && ./generate_declare_contracts.sh 8` +`cd starknet-rpc-test/contracts && ./generate_declare_contracts.sh 11` To run the tests, simply run `cargo test -p starknet-rpc-test -- test -- --exact --nocapture --test-threads=1`. diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 809561240..c840b7bb5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,4 @@ [toolchain] -channel = "nightly-2023-08-24" +channel = "nightly-2023-09-18" components = ["rustfmt", "clippy", "rust-analyzer"] -targets = ["wasm32-unknown-unknown"] profile = "minimal" diff --git a/starknet-rpc-test/Cargo.toml b/starknet-rpc-test/Cargo.toml index 492e33a0b..03ea297c4 100644 --- a/starknet-rpc-test/Cargo.toml +++ b/starknet-rpc-test/Cargo.toml @@ -27,9 +27,12 @@ thiserror = { workspace = true } tokio = { version = "1.36.0", features = ["rt", "macros", "parking_lot"] } url = "2.4.1" -# [[test]] -# name = "starknet_spec_version" -# path = "spec_version.rs" +[dev-dependencies] +starknet-types-core = "0.1.3" + +[[test]] +name = "starknet_spec_version" +path = "spec_version.rs" [[test]] name = "starknet_get_block_number" @@ -52,13 +55,9 @@ name = "starknet_get_storage_at" path = "get_storage_at.rs" [[test]] -name = "starknet_get_class" +name = "starknet_get_class_and_get_class_at" path = "get_class.rs" -[[test]] -name = "starknet_get_class_at" -path = "get_class_at.rs" - [[test]] name = "starknet_get_class_hash_at" path = "get_class_hash_at.rs" diff --git a/starknet-rpc-test/README.md b/starknet-rpc-test/README.md index 42006bb2e..38f691ff1 100644 --- a/starknet-rpc-test/README.md +++ b/starknet-rpc-test/README.md @@ -26,5 +26,5 @@ The artifacts are used for testing the declare class operation. ```sh cd starknet-rpc-test/contracts -./generate_declare_contracts.sh 10 +./generate_declare_contracts.sh 11 ``` diff --git a/starknet-rpc-test/add_declare_transaction.rs b/starknet-rpc-test/add_declare_transaction.rs index bda00555a..6f379323a 100644 --- a/starknet-rpc-test/add_declare_transaction.rs +++ b/starknet-rpc-test/add_declare_transaction.rs @@ -1,6 +1,7 @@ extern crate starknet_rpc_test; use core::panic; +use std::time::Duration; use std::vec; use assert_matches::assert_matches; @@ -16,6 +17,7 @@ use starknet_rpc_test::utils::{ }; use starknet_rpc_test::{SendTransactionError, Transaction, TransactionResult}; use starknet_test_utils::constants::ETH_FEE_TOKEN_ADDRESS; +use tokio::time::sleep; #[rstest] #[tokio::test] @@ -145,13 +147,14 @@ async fn works_with_storage_change(madara: &ThreadSafeMadaraClient) -> Result<() } _ => panic!("Expected declare transaction result"), } - - assert!(rpc.get_class(BlockId::Number(block_number), expected_class_hash).await.is_ok()); - // included in block let included_txs = rpc.get_block_transaction_count(BlockId::Number(block_number)).await?; assert_eq!(included_txs, 1); + // Wait a bit to let the client sync the block + sleep(Duration::from_secs(3)).await; + assert!(rpc.get_class(BlockId::Number(block_number), expected_class_hash).await.is_ok()); + Ok(()) } diff --git a/starknet-rpc-test/get_class.rs b/starknet-rpc-test/get_class.rs index 1275927b8..3f96a034c 100644 --- a/starknet-rpc-test/get_class.rs +++ b/starknet-rpc-test/get_class.rs @@ -1,17 +1,31 @@ +// Test for both get_class and get_class_at + +// Important: those routes won't work for contracts declared during genesis as the node can't +// collect the additional data needed to return a fully fleshed ContractClass. +// The declaration have to go through the RPC for it to work. + use std::io::Read; +use std::time::Duration; use assert_matches::assert_matches; use flate2::read::GzDecoder; use rstest::rstest; -use starknet_core::types::contract::legacy::LegacyContractClass; +use starknet_accounts::Execution; +use starknet_contract::ContractFactory; +use starknet_core::types::contract::legacy::{LegacyContractClass, LegacyDebugInfo, LegacyProgram}; use starknet_core::types::contract::SierraClass; use starknet_core::types::{BlockId, ContractClass, FlattenedSierraClass, StarknetError}; use starknet_ff::FieldElement; use starknet_providers::Provider; use starknet_providers::ProviderError::StarknetError as StarknetProviderError; -use starknet_rpc_test::constants::{CAIRO_1_ACCOUNT_CONTRACT_CLASS_HASH, TEST_CONTRACT_CLASS_HASH}; +use starknet_rpc_test::constants::{ARGENT_CONTRACT_ADDRESS, SIGNER_PRIVATE, TEST_CONTRACT_CLASS_HASH}; use starknet_rpc_test::fixtures::{madara, ThreadSafeMadaraClient}; -use starknet_rpc_test::LegacyProgramWrapper; +use starknet_rpc_test::utils::{build_single_owner_account, get_contract_address_from_deploy_tx}; +use starknet_rpc_test::Transaction; +use starknet_test_utils::constants::MAX_FEE_OVERRIDE; +use starknet_test_utils::utils::AccountActions; +use starknet_types_core::felt::Felt; +use tokio::time::sleep; #[rstest] #[tokio::test] @@ -47,58 +61,158 @@ async fn fail_non_existing_class_hash(madara: &ThreadSafeMadaraClient) -> Result #[rstest] #[tokio::test] -#[ignore = "Waiting for issue #1585 to be solved"] -async fn work_ok_retrieving_class_for_contract_version_0(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { +async fn fail_non_existing_address(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { let rpc = madara.get_starknet_client().await; - let test_contract_class_hash = - FieldElement::from_hex_be(TEST_CONTRACT_CLASS_HASH).expect("Invalid Contract Class Hash"); - - let test_contract_class_bytes = include_bytes!("../cairo-contracts/build/test.json"); - let test_contract_class: LegacyContractClass = serde_json::from_slice(test_contract_class_bytes).unwrap(); + let unknown_contract_address = FieldElement::from_hex_be("0x4269DEADBEEF").expect("Invalid Contract classh hash"); assert_matches!( - rpc - .get_class( - BlockId::Number(0), - test_contract_class_hash, - ).await?, - ContractClass::Legacy(c) => { - // decompress program - let mut gz = GzDecoder::new(&c.program[..]); - let mut decompressed_bytes = Vec::new(); - gz.read_to_end(&mut decompressed_bytes).unwrap(); - let legacy_program_wrapper: LegacyProgramWrapper = serde_json::from_slice(decompressed_bytes.as_slice())?; - let program = legacy_program_wrapper.legacy_program; - assert_eq!( - program.data.len(), - test_contract_class.program.data.len(), - ); - } + rpc.get_class_at(BlockId::Number(0), unknown_contract_address,).await, + Err(StarknetProviderError(StarknetError::ContractNotFound)) ); Ok(()) } -#[ignore = "conversion between contract class types is incomplete"] #[rstest] #[tokio::test] -async fn work_ok_retrieving_class_for_contract_version_1(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { +async fn work_ok_retrieving_class_for_contract_version_0(madara: &ThreadSafeMadaraClient) { + // Test that the contract class received from the rpc call get_class and get_class_at is the same as + // the one that was used to declare + fn assert_eq_contract_class(received: ContractClass, mut expected: LegacyContractClass) { + assert_matches!( + received, + ContractClass::Legacy(c) => { + + assert_eq!( + serde_json::to_value(expected.abi).unwrap(), + serde_json::to_value(c.abi).unwrap(), + ); + assert_eq!( + serde_json::to_value(expected.entry_points_by_type).unwrap(), + serde_json::to_value(c.entry_points_by_type).unwrap(), + ); + + // decompress program + let mut gz = GzDecoder::new(&c.program[..]); + let mut decompressed_bytes = Vec::new(); + gz.read_to_end(&mut decompressed_bytes).unwrap(); + let mut program: LegacyProgram = serde_json::from_slice(decompressed_bytes.as_slice()).unwrap(); + + // Because of some obscure pathfinder bug, starknet-rs deserialize debug_info as an empty struct rather than none + // It has been fixed, so when https://github.com/xJonathanLEI/starknet-rs/pull/599 is megerd, this will become useless + // In the meantime we take this field out and make sure it is the empty struct + expected.program.debug_info.take(); + let debug_infos = program.debug_info.take(); + assert_eq!( + serde_json::to_value(LegacyDebugInfo { file_contents: Default::default(), instruction_locations: Default::default()}).unwrap(), + serde_json::to_value(debug_infos).unwrap(), + ); + // Because fucking program.identifiers.values are raw string, they can be either a positive or a negative number when deserialized + // In order to make sure those values are trully equal, we need to convert them back to Felt so they can wrap arround + // We take them out so we can latter call `==` on the whole `program.identifiers` + for (a, b) in expected.program.identifiers.iter_mut().zip(program.identifiers.iter_mut()) { + let a = + a.1.value.take().map(|v| Felt::from_dec_str(v.get()).unwrap()); + let b = + b.1.value.take().map(|v| Felt::from_dec_str(v.get()).unwrap()); + assert_eq!( + a, b + ); + } + // Finally compar the fixed program + assert_eq!( + serde_json::to_value(expected.program).unwrap(), + serde_json::to_value(program).unwrap(), + ); + } + ) + } + let rpc = madara.get_starknet_client().await; - let test_contract_class_hash = - FieldElement::from_hex_be(CAIRO_1_ACCOUNT_CONTRACT_CLASS_HASH).expect("Invalid Contract Class Hash"); + let account = build_single_owner_account(&rpc, SIGNER_PRIVATE, ARGENT_CONTRACT_ADDRESS, true); + let (declare_tx, expected_class_hash) = + account.declare_legacy_contract("../cairo-contracts/build/UnauthorizedInnerCallAccount.json"); + let contract_class: LegacyContractClass = serde_json::from_reader( + std::fs::File::open("../cairo-contracts/build/UnauthorizedInnerCallAccount.json").unwrap(), + ) + .unwrap(); + + // Declare the class + let (txs, block_number) = { + let mut madara_write_lock = madara.write().await; + let txs = + madara_write_lock.create_block_with_txs(vec![Transaction::LegacyDeclaration(declare_tx)]).await.unwrap(); + let block_number = rpc.block_number().await.unwrap(); + (txs, block_number) + }; + assert!(txs[0].is_ok(), "declare tx failed"); + + // Wait a bit to let the client sync the block + sleep(Duration::from_secs(3)).await; + + // Check if get_class workded + let received_contract_class = rpc.get_class(BlockId::Number(block_number), expected_class_hash).await.unwrap(); + assert_eq_contract_class(received_contract_class, contract_class.clone()); + + // Now deploy the contract + let contract_factory = ContractFactory::new(expected_class_hash, account.clone()); + let max_fee = FieldElement::from_hex_be(MAX_FEE_OVERRIDE).unwrap(); + let deploy_tx = Execution::from(&contract_factory.deploy(vec![], FieldElement::ZERO, true).max_fee(max_fee)); + + let (mut txs, block_number) = { + let mut madara_write_lock = madara.write().await; + + let txs = madara_write_lock.create_block_with_txs(vec![Transaction::Execution(deploy_tx)]).await.unwrap(); + let block_number = rpc.block_number().await.unwrap(); - let test_contract_class_bytes = include_bytes!("../cairo-contracts/build/cairo_1/NoValidateAccount.sierra.json"); + (txs, block_number) + }; + let deploy_tx_res = txs.pop().unwrap(); + assert!(deploy_tx_res.is_ok(), "deploy tx failed"); + + // And make sure get_class_at also work + let contract_address = get_contract_address_from_deploy_tx(&rpc, deploy_tx_res).await.unwrap(); + let received_contract_class = rpc.get_class_at(BlockId::Number(block_number), contract_address).await.unwrap(); + assert_eq_contract_class(received_contract_class, contract_class); +} + +#[rstest] +#[tokio::test] +async fn work_ok_retrieving_class_for_contract_version_1(madara: &ThreadSafeMadaraClient) { + let rpc = madara.get_starknet_client().await; + + let account = build_single_owner_account(&rpc, SIGNER_PRIVATE, ARGENT_CONTRACT_ADDRESS, true); + let (declare_tx, class_hash, _) = account.declare_contract( + "../starknet-rpc-test/contracts/counter10/counter10.contract_class.json", + "../starknet-rpc-test/contracts/counter10/counter10.compiled_contract_class.json", + None, + ); + + let (txs, block_number) = { + let mut madara_write_lock = madara.write().await; + + let txs = madara_write_lock.create_block_with_txs(vec![Transaction::Declaration(declare_tx)]).await.unwrap(); + let block_number = rpc.block_number().await.unwrap(); + + (txs, block_number) + }; + assert!(txs[0].is_ok(), "declare tx failed"); + + let test_contract_class_bytes = + include_bytes!("../starknet-rpc-test/contracts/counter10/counter10.contract_class.json"); let test_contract_class: SierraClass = serde_json::from_slice(test_contract_class_bytes).unwrap(); let flattened_test_contract_class: FlattenedSierraClass = test_contract_class.flatten().unwrap(); + sleep(Duration::from_secs(3)).await; + assert_matches!( rpc .get_class( - BlockId::Number(0), - test_contract_class_hash - ).await?, + BlockId::Number(block_number), + class_hash + ).await.unwrap(), ContractClass::Sierra(c) => { assert_eq!( c.abi, @@ -111,5 +225,36 @@ async fn work_ok_retrieving_class_for_contract_version_1(madara: &ThreadSafeMada } ); - Ok(()) + // Now deploy the contract + let contract_factory = ContractFactory::new(class_hash, account.clone()); + let max_fee = FieldElement::from_hex_be(MAX_FEE_OVERRIDE).unwrap(); + let deploy_tx = Execution::from(&contract_factory.deploy(vec![], FieldElement::ZERO, true).max_fee(max_fee)); + + let (mut txs, block_number) = { + let mut madara_write_lock = madara.write().await; + + let txs = madara_write_lock.create_block_with_txs(vec![Transaction::Execution(deploy_tx)]).await.unwrap(); + let block_number = rpc.block_number().await.unwrap(); + + (txs, block_number) + }; + let deploy_tx_res = txs.pop().unwrap(); + assert!(deploy_tx_res.is_ok(), "deploy tx failed"); + + // And make sure get_class_at also work + let contract_address = get_contract_address_from_deploy_tx(&rpc, deploy_tx_res).await.unwrap(); + assert_matches!( + rpc.get_class_at( + BlockId::Number(block_number), contract_address).await.unwrap(), + ContractClass::Sierra(c) => { + assert_eq!( + c.abi, + flattened_test_contract_class.abi, + ); + assert_eq!( + c.sierra_program, + flattened_test_contract_class.sierra_program, + ); + } + ); } diff --git a/starknet-rpc-test/get_class_at.rs b/starknet-rpc-test/get_class_at.rs deleted file mode 100644 index 5c5f998e6..000000000 --- a/starknet-rpc-test/get_class_at.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::io::Read; - -use assert_matches::assert_matches; -use flate2::read::GzDecoder; -use rstest::rstest; -use starknet_core::types::contract::legacy::LegacyContractClass; -use starknet_core::types::contract::SierraClass; -use starknet_core::types::{BlockId, ContractClass, FlattenedSierraClass, StarknetError}; -use starknet_ff::FieldElement; -use starknet_providers::Provider; -use starknet_providers::ProviderError::StarknetError as StarknetProviderError; -use starknet_rpc_test::constants::{CAIRO_1_ACCOUNT_CONTRACT_ADDRESS, TEST_CONTRACT_ADDRESS}; -use starknet_rpc_test::fixtures::{madara, ThreadSafeMadaraClient}; -use starknet_rpc_test::LegacyProgramWrapper; - -#[rstest] -#[tokio::test] -async fn fail_non_existing_block(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { - let rpc = madara.get_starknet_client().await; - - let test_contract_address = FieldElement::from_hex_be(TEST_CONTRACT_ADDRESS).expect("Invalid Contract Address"); - - assert_matches!( - rpc.get_class_at(BlockId::Number(100), test_contract_address,).await, - Err(StarknetProviderError(StarknetError::BlockNotFound)) - ); - - Ok(()) -} - -#[rstest] -#[tokio::test] -async fn fail_non_existing_contract(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { - let rpc = madara.get_starknet_client().await; - - let unknown_contract_address = FieldElement::from_hex_be("0x4269DEADBEEF").expect("Invalid Contract Address"); - - assert_matches!( - rpc.get_class_at(BlockId::Number(0), unknown_contract_address,).await, - Err(StarknetProviderError(StarknetError::ContractNotFound)) - ); - - Ok(()) -} - -#[rstest] -#[tokio::test] -#[ignore = "Waiting for issue #1585 to be solved"] -async fn work_ok_retrieving_class_for_contract_version_0(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { - let rpc = madara.get_starknet_client().await; - - let test_contract_address = FieldElement::from_hex_be(TEST_CONTRACT_ADDRESS).expect("Invalid Contract Address"); - - let test_contract_class_bytes = include_bytes!("../cairo-contracts/build/test.json"); - let test_contract_class: LegacyContractClass = serde_json::from_slice(test_contract_class_bytes).unwrap(); - - assert_matches!( - rpc - .get_class_at( - BlockId::Number(0), - test_contract_address - ).await?, - ContractClass::Legacy(c) => { - // decompress program - let mut d = GzDecoder::new(&c.program[..]); - let mut data = String::new(); - d.read_to_string(&mut data).unwrap(); - let legacy_program_wrapper: LegacyProgramWrapper = serde_json::from_str(data.as_str())?; - let program = legacy_program_wrapper.legacy_program; - assert_eq!( - program.data, - test_contract_class.program.data, - ); - } - ); - - Ok(()) -} - -#[rstest] -#[ignore] -#[tokio::test] -async fn work_ok_retrieving_class_for_contract_version_1(madara: &ThreadSafeMadaraClient) -> Result<(), anyhow::Error> { - let rpc = madara.get_starknet_client().await; - - let test_contract_address = - FieldElement::from_hex_be(CAIRO_1_ACCOUNT_CONTRACT_ADDRESS).expect("Invalid Contract Address"); - - let test_contract_class_bytes = include_bytes!("../cairo-contracts/build/cairo_1/NoValidateAccount.sierra.json"); - let test_contract_class: SierraClass = serde_json::from_slice(test_contract_class_bytes).unwrap(); - let flattened_test_contract_class: FlattenedSierraClass = test_contract_class.flatten().unwrap(); - - assert_matches!( - rpc - .get_class_at( - BlockId::Number(0), - test_contract_address - ).await?, - ContractClass::Sierra(c) => { - assert_eq!( - c.abi, - flattened_test_contract_class.abi, - ); - assert_eq!( - c.sierra_program, - flattened_test_contract_class.sierra_program, - ); - } - ); - - Ok(()) -} diff --git a/starknet-rpc-test/src/lib.rs b/starknet-rpc-test/src/lib.rs index 049467828..4072e5887 100644 --- a/starknet-rpc-test/src/lib.rs +++ b/starknet-rpc-test/src/lib.rs @@ -179,7 +179,7 @@ impl MadaraClient { results.push(result); } - self.do_create_block(true, false).await?; + self.do_create_block(true, true).await?; Ok(results) } diff --git a/starknet-test-utils/src/lib.rs b/starknet-test-utils/src/lib.rs index 064c09c17..9a331e972 100644 --- a/starknet-test-utils/src/lib.rs +++ b/starknet-test-utils/src/lib.rs @@ -176,7 +176,7 @@ impl MadaraClient { results.push(result); } - self.do_create_block(true, false).await?; + self.do_create_block(true, true).await?; Ok(results) }