diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d13a246bff..7cf0aea2e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,7 @@ jobs: toolchain: stable - uses: Swatinem/rust-cache@v2 - run: > - cargo test --test '*' -- --include-ignored; + cargo test --test '*' -- --include-ignored --skip test_gw_integration_testnet; cargo run -p papyrus_node --bin central_source_integration_test rustfmt: diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml new file mode 100644 index 0000000000..86c4b390a2 --- /dev/null +++ b/.github/workflows/nightly-tests.yml @@ -0,0 +1,37 @@ +name: nightly-tests + +on: + schedule: + - cron: "30 0 * * *" + - cron: "30 3 * * *" + workflow_dispatch: + push: + branches: + - tzahi/gw_integration_test_basic + +env: + OS: ${{ github.event.schedule == "30 3 * * *" && 'macos-latest' || 'ubuntu-latest' }} + +jobs: + GW-integration-test: + runs-on: ${{ env.OS }} + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + # Workflow steps exit upon failure of a subcommand (running `set -e` implicitly before the + # run. As we want to keep running this step after a test failure we can either start with + # `set +e` to suppress all errors, or, as done below, append `|| retVal=$?` to the command + # which makes it successful while storing the potential erroneous code. + - run: > + sudo apt update; sudo apt -y install libclang-dev; + INTEGRATION_TESTNET_NODE_URL=${{ secrets.INTEGRATION_TESTNET_NODE_URL }} + SENDER_PRIVATE_KEY=${{ secrets.INTEGRATION_TESTNET_SENDER_PRIVATE_KEY }} + cargo test --test gateway_integration_test -p starknet_client test_gw_integration_testnet + -- --ignored || retVal=$?; + if [ $retVal -ne 0 ]; then + echo "Integration test failed with exit code $retVal"; + fi; + exit $retVal diff --git a/Cargo.lock b/Cargo.lock index 87dbdcb391..2714e4ffd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -498,6 +498,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits 0.2.16", + "serde", +] + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -633,7 +645,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "starknet-crypto", + "starknet-crypto 0.5.1", "starknet_api", "strum 0.24.1", "strum_macros 0.24.3", @@ -738,7 +750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b48814962d2fd604c50d2b9433c2a41a0ab567779ee2c02f7fba6eca1221f082" dependencies = [ "cached_proc_macro_types", - "darling", + "darling 0.14.4", "proc-macro2", "quote", "syn 1.0.109", @@ -1246,7 +1258,7 @@ dependencies = [ "serde_json", "sha2", "sha3", - "starknet-crypto", + "starknet-crypto 0.5.1", "thiserror-no-std", ] @@ -1323,6 +1335,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits 0.2.16", + "serde", "wasm-bindgen", "windows-targets 0.48.5", ] @@ -1692,8 +1705,18 @@ version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core 0.20.3", + "darling_macro 0.20.3", ] [[package]] @@ -1710,17 +1733,42 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.37", +] + [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ - "darling_core", + "darling_core 0.14.4", "quote", "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core 0.20.3", + "quote", + "syn 2.0.37", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -4315,7 +4363,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "starknet-crypto", + "starknet-crypto 0.5.1", "starknet_api", "test_utils", ] @@ -5661,6 +5709,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_pythonic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62212da9872ca2a0cad0093191ee33753eddff9266cbbc1b4a602d13a3a768db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_path_to_error" version = "0.1.14" @@ -5692,6 +5751,33 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling 0.20.3", + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -5934,6 +6020,24 @@ dependencies = [ "num-traits 0.1.43", ] +[[package]] +name = "starknet-core" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b796a32a7400f7d85e95d3900b5cee7a392b2adbf7ad16093ed45ec6f8d85de6" +dependencies = [ + "base64 0.21.4", + "flate2", + "hex", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with", + "sha3", + "starknet-crypto 0.6.0", + "starknet-ff", +] + [[package]] name = "starknet-crypto" version = "0.5.1" @@ -5954,6 +6058,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "starknet-crypto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbb308033b5c60c5677645f7ba3b012b4e3e81f773480d27fb5f342d50621e6" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits 0.2.16", + "rfc6979", + "sha2", + "starknet-crypto-codegen", + "starknet-curve 0.4.0", + "starknet-ff", + "zeroize", +] + [[package]] name = "starknet-crypto-codegen" version = "0.3.2" @@ -5990,9 +6114,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2cb1d9c0a50380cddab99cb202c6bfb3332728a2769bd0ca2ee80b0b390dd4" dependencies = [ "ark-ff", + "bigdecimal", "crypto-bigint", "getrandom", "hex", + "serde", ] [[package]] @@ -6008,7 +6134,7 @@ dependencies = [ "primitive-types", "serde", "serde_json", - "starknet-crypto", + "starknet-crypto 0.5.1", "thiserror", ] @@ -6023,17 +6149,20 @@ dependencies = [ "enum-iterator", "http", "indexmap 1.9.3", + "jsonrpsee", "mockall", "mockito", "os_info", "papyrus_common", "papyrus_config", + "papyrus_rpc", "pretty_assertions", "rand", "rand_chacha", "reqwest", "serde", "serde_json", + "starknet-core", "starknet_api", "test_utils", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index fcaf509ee4..11d8a6f9a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ serde_yaml = "0.9.16" sha3 = "0.10.8" simple_logger = "4.0.0" starknet_api = "0.5.0-rc1" +starknet-core = "0.6.0" starknet-crypto = "0.5.1" strum = "0.25.0" strum_macros = "0.25.2" diff --git a/crates/papyrus_rpc/src/lib.rs b/crates/papyrus_rpc/src/lib.rs index 1e5925b1b7..83a561bc13 100644 --- a/crates/papyrus_rpc/src/lib.rs +++ b/crates/papyrus_rpc/src/lib.rs @@ -47,6 +47,8 @@ use validator::Validate; use crate::api::get_methods_from_supported_apis; use crate::middleware::{deny_requests_with_unsupported_path, proxy_rpc_request}; use crate::syncing_state::get_last_synced_block; +pub use crate::v0_4_0::transaction::{InvokeTransaction, InvokeTransactionV1}; +pub use crate::v0_4_0::write_api_result::AddInvokeOkResult; /// Maximum size of a supported transaction body - 10MB. pub const SERVER_MAX_BODY_SIZE: u32 = 10 * 1024 * 1024; diff --git a/crates/starknet_client/Cargo.toml b/crates/starknet_client/Cargo.toml index 0c914f3b47..1804af2f71 100644 --- a/crates/starknet_client/Cargo.toml +++ b/crates/starknet_client/Cargo.toml @@ -42,10 +42,14 @@ url.workspace = true assert.workspace = true assert_matches.workspace = true enum-iterator.workspace = true +jsonrpsee = { workspace = true, features = ["full"] } mockall.workspace = true mockito.workspace = true rand.workspace = true rand_chacha.workspace = true +papyrus_common = { path = "../papyrus_common"} +papyrus_rpc = { path = "../papyrus_rpc"} pretty_assertions.workspace = true starknet_api = { workspace = true, features = ["testing"] } +starknet-core.workspace = true test_utils = { path = "../test_utils" } diff --git a/crates/starknet_client/tests/gateway_integration_test.rs b/crates/starknet_client/tests/gateway_integration_test.rs new file mode 100644 index 0000000000..7dd2faab35 --- /dev/null +++ b/crates/starknet_client/tests/gateway_integration_test.rs @@ -0,0 +1,130 @@ +use std::env; + +use jsonrpsee::core::client::ClientT; +use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; +use jsonrpsee::rpc_params; +use papyrus_common::transaction_hash::get_transaction_hash; +use papyrus_rpc::{ + AddInvokeOkResult, + InvokeTransaction as SNRpcInvokeTransaction, + InvokeTransactionV1 as SNRpcInvokeTransactionV1, +}; +use starknet_api::core::{ChainId, ContractAddress, EntryPointSelector, Nonce, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::transaction::{ + Calldata, + Fee, + Transaction, + TransactionSignature, + TransactionVersion, +}; +use starknet_api::{calldata, contract_address, patricia_key, stark_felt}; +use starknet_client::writer::objects::transaction::InvokeTransaction; +use starknet_core::crypto::ecdsa_sign; +use starknet_core::types::FieldElement; + +const ETH_TO_WEI: u128 = u128::pow(10, 18); +const MAX_FEE: u128 = ETH_TO_WEI / 1000; +const INSUFFICIENT_FUNDS_STATUS_CODE: i32 = 2; +const L2_ETH_CONTRACT_ADDRESS: &str = + "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7"; +const BALANCE_OF_ENTRY_POINT_SELECTOR: &str = + "0x2e4263afad30923c891518314c3c95dbe830a16874e8abc5777a9a20b54c76e"; +const TRANSFER_ENTRY_POINT_SELECTOR: &str = + "0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e"; +const USER_A_ADDRESS: &str = "0x2eda087f4edf190224eac3fdf7f762d83052f7c83fdda674e6e97e1f596a819"; +const USER_B_ADDRESS: &str = "0x02d23bb72da2a2c7cce1577a013c3139b4f51d2b32be2ee7825f33428f572a9d"; + +// Returns the eth balance for the given account via the given node client. +async fn get_eth_balance(client: &HttpClient, account: ContractAddress) -> StarkFelt { + let balance = client + .request::, _>( + "starknet_call", + rpc_params!( + L2_ETH_CONTRACT_ADDRESS, + EntryPointSelector(stark_felt!(BALANCE_OF_ENTRY_POINT_SELECTOR)), + calldata![*account.0.key()], + "latest" + ), + ) + .await + .expect("Call to balanceOf failed."); + balance[0] +} + +#[tokio::test] +#[ignore] +// Sends a 'transfer of eth from user A to user B' transaction to a node instance synced with +// Starknet integration testnet. The node is expected to resend the transaction to Starknet +// successfully. +async fn test_gw_integration_testnet() { + let node_url = env::var("INTEGRATION_TESTNET_NODE_URL") + .expect("Node url must be given in INTEGRATION_TESTNET_NODE_URL environment variable."); + let client = + HttpClientBuilder::default().build(format!("https://{}:443/rpc/v0_4", node_url)).unwrap(); + let sender_address = contract_address!(USER_A_ADDRESS); + // Sender balance sufficient balance should be maintained outside of this test. + let sender_balance = get_eth_balance(&client, sender_address).await; + if sender_balance <= MAX_FEE.into() { + println!("Sender balance is too low. Please fund account {}.", USER_A_ADDRESS); + std::process::exit(INSUFFICIENT_FUNDS_STATUS_CODE); + } + + // TODO(tzahi): Switch to "pending" once supported and add an assertion for the status of the + // sent tx and balance update in the end of this test. + let nonce = client + .request::("starknet_getNonce", rpc_params!["latest", sender_address]) + .await + .unwrap(); + let receiver_address = contract_address!(USER_B_ADDRESS); + + // Create an invoke transaction for Eth transfer with a signature placeholder. + let mut invoke_tx = SNRpcInvokeTransactionV1 { + max_fee: Fee(MAX_FEE), + signature: TransactionSignature::default(), + nonce, + sender_address, + version: TransactionVersion(stark_felt!(1_u8)), + calldata: calldata![ + stark_felt!(1_u8), // OpenZeppelin call array len (number of calls in this tx). + // Call Array (4 elements per array struct element). + stark_felt!(L2_ETH_CONTRACT_ADDRESS), // to + EntryPointSelector(stark_felt!(TRANSFER_ENTRY_POINT_SELECTOR)).0, // selector. + stark_felt!(0_u8), // data offset (in the calldata array) + stark_felt!(3_u8), /* data len (of this call in the entire + * calldata array) */ + // Call data. + stark_felt!(3_u8), // Call data len. + // calldata for transfer - receiver and amount (uint256 = 2 felts). + *receiver_address.0.key(), + stark_felt![1_u8], // LSB + stark_felt![0_u8] + ], + }; + + // Update the signature. + let hash = get_transaction_hash( + &Transaction::Invoke(SNRpcInvokeTransaction::Version1(invoke_tx.clone()).into()), + &ChainId("SN_GOERLI".to_string()), + ) + .unwrap(); + let signature = ecdsa_sign( + &FieldElement::from_hex_be(&env::var("SENDER_PRIVATE_KEY").expect( + "Sender private key must be given in SENDER_PRIVATE_KEY environment variable.", + )) + .unwrap(), + &hash.0.into(), + ) + .unwrap(); + invoke_tx.signature = TransactionSignature(vec![signature.r.into(), signature.s.into()]); + + let invoke_res = client + .request::( + "starknet_addInvokeTransaction", + rpc_params!(InvokeTransaction::from(invoke_tx)), + ) + .await + .unwrap(); + + println!("Invoke Tx result: {:?}", invoke_res); +}