diff --git a/.dockerignore b/.dockerignore index 9aa3fc33..a19aecc8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,6 @@ LICENSE remappings.txt **/*.ptau nightfall_bindings/build.rs +.venv +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6e8a6e54..10db6a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67031be093311a96afdd146fb5de209ceaf0f347f2284ed71902368cd15a77ff" +checksum = "b17c19591d57add4f0c47922877a48aae1f47074e3433436545f8948353b3bbb" dependencies = [ "alloy-consensus", "alloy-contract", @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd9d29a6a0bb8d4832ff7685dcbb430011b832f2ccec1af9571a0e75c1f7e9c" +checksum = "6a0dd3ed764953a6b20458b2b7abbfdc93d20d14b38babe1a70fe631a443a9f1" dependencies = [ "alloy-eips", "alloy-primitives", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce038cb325f9a85a10fb026fb1b70cb8c62a004d85d22f8516e5d173e3eec612" +checksum = "9556182afa73cddffa91e64a5aa9508d5e8c912b3a15f26998d2388a824d2c7b" dependencies = [ "alloy-consensus", "alloy-eips", @@ -167,9 +167,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a376305e5c3b3285e84a553fa3f9aee4f5f0e1b0aad4944191b843cd8228788d" +checksum = "b19d7092c96defc3d132ee0d8969ca1b79ef512b5eda5c66e3065266b253adf2" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bfec530782b30151e2564edf3c900f1fa6852128b7a993e458e8e3815d8b915" +checksum = "305fa99b538ca7006b0c03cfed24ec6d82beda67aac857ef4714be24231d15e6" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956e6a23eb880dd93123e8ebea028584325b9af22f991eec2c499c54c277c073" +checksum = "a272533715aefc900f89d51db00c96e6fd4f517ea081a12fea482a352c8c815c" dependencies = [ "alloy-eips", "alloy-primitives", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be436893c0d1f7a57d1d8f1b6b9af9db04174468410b7e6e1d1893e78110a3bc" +checksum = "d91676d242c0ced99c0dd6d0096d7337babe9457cc43407d26aa6367fcf90553" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18959e1a1b40e05578e7a705f65ff4e6b354e38335da4b33ccbee876bde7c26" +checksum = "77f82150116b30ba92f588b87f08fa97a46a1bd5ffc0d0597efdf0843d36bfda" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1da0037ac546c0cae2eb776bed53687b7bbf776f4e7aa2fea0b8b89e734c319b" +checksum = "223612259a080160ce839a4e5df0125ca403a1d5e7206cc911cea54af5d769aa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -371,9 +371,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd89c9e72e62d95b51be0b92468282526b37d3d1015f5e31069a35c2e020872f" +checksum = "3652a65bacfba0a169755090d4ecd7d3c63fa534b21d09b8e604dc2609760da6" dependencies = [ "alloy-genesis", "alloy-hardforks", @@ -419,9 +419,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca97e31bc05bd6d4780254fbb60b16d33b3548d1c657a879fffb0e7ebb642e9" +checksum = "f7283b81b6f136100b152e699171bc7ed8184a58802accbc91a7df4ebb944445" dependencies = [ "alloy-chains", "alloy-consensus", @@ -465,9 +465,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7bb37096e97de25133cf904e08df2aa72168af64f429e3c43a112649e131930" +checksum = "eee7e3d343814ec0dfea69bd1820042a133a9d0b9ac5faf1e6eb133b43366315" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -509,9 +509,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbeeeffa0bb7e95cb79f2b4b46b591763afeccfa9a797183c1b192377ffb6fac" +checksum = "1154b12d470bef59951c62676e106f4ce5de73b987d86b9faa935acebb138ded" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -535,9 +535,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21fe4c370b9e733d884ffd953eb6d654d053b1b22e26ffd591ef597a9e2bc49" +checksum = "47ab76bf97648a1c6ad8fb00f0d594618942b5a9e008afbfb5c8a8fca800d574" dependencies = [ "alloy-primitives", "alloy-rpc-types-anvil", @@ -552,9 +552,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb44412ed075c19d37698f33213b83f0bf8ccc2d4e928527f2622555a31723de" +checksum = "456cfc2c1677260edbd7ce3eddb7de419cb46de0e9826c43401f42b0286a779a" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -564,9 +564,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65423baf6af0ff356e254d7824b3824aa34d8ca9bd857a4e298f74795cc4b69d" +checksum = "23cc57ee0c1ac9fb14854195fc249494da7416591dc4a4d981ddfd5dd93b9bce" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27eaa6c63f551e35f835638397ce5c66d2ba14d0b17ce3bb286842e815b0fc94" +checksum = "4a0ac29dd005c33e3f7e09087accc80843315303685c3f7a1b888002cd27785b" dependencies = [ "alloy-primitives", "derive_more 2.0.1", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dc8a9ba66f1a654d935584200fcd0b7fd34dac0ca19df024911899066b0583" +checksum = "1d9d173854879bcf26c7d71c1c3911972a3314df526f4349ffe488e676af577d" dependencies = [ "alloy-consensus", "alloy-eips", @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848f8ea4063bed834443081d77f840f31075f68d0d49723027f5a209615150bf" +checksum = "6d7d47bca1a2a1541e4404aa38b7e262bb4dffd9ac23b4f178729a4ddc5a5caa" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -625,9 +625,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c632e12fb9bbde97eb2a0f5145f0fe6e0ed1b3927de29d8463ab468905d9843" +checksum = "c331c8e48665607682e8a9549a2347c13674d4fbcbdc342e7032834eba2424f4" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -639,9 +639,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cbe0913689d8e3939a5da4bfb7a898309d87419cf98f8e670332130340b3e7" +checksum = "5e2f66afe1e76ca4485e593980056f061b2bdae2055486a062fca050ff111a52" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -651,9 +651,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3835bdc128f2f3418f5d6c76aec63a245d72973e0eaacc9720aa0787225c5" +checksum = "6a8468f1a7f9ee3bae73c24eead0239abea720dbf7779384b9c7e20d51bfb6b0" dependencies = [ "alloy-primitives", "serde", @@ -662,9 +662,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42084a7b455ef0b94ed201b7494392a759c3e20faac2d00ded5d5762fcf71dee" +checksum = "33387c90b0a5021f45a5a77c2ce6c49b8f6980e66a318181468fb24cea771670" dependencies = [ "alloy-primitives", "async-trait", @@ -677,9 +677,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6312ccc048a4a88aed7311fc448a2e23da55c60c2b3b6dcdb794f759d02e49d7" +checksum = "b55d9e795c85e36dcea08786d2e7ae9b73cb554b6bea6ac4c212def24e1b4d03" dependencies = [ "alloy-consensus", "alloy-network", @@ -766,9 +766,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f77fa71f6dad3aa9b97ab6f6e90f257089fb9eaa959892d153a1011618e2d6" +checksum = "702002659778d89a94cd4ff2044f6b505460df6c162e2f47d1857573845b0ace" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -790,9 +790,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1a5d0f5dd5e07187a4170bdcb7ceaff18b1133cd6b8585bc316ab442cd78a" +checksum = "0d6bdc0830e5e8f08a4c70a4c791d400a86679c694a3b4b986caf26fad680438" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -805,9 +805,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62764e672967d7f8a890c3d28c9c9a9fc781fba59e5d869898b08073c9deae3a" +checksum = "87ce41d99a32346f354725fe62eadd271cdbae45fe6b3cc40cb054e0bf763112" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -825,9 +825,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a21442472bad4494cfb1f11d975ae83059882a11cdda6a3aa8c0d2eb444beb6" +checksum = "686219dcef201655763bd3d4eabe42388d9368bfbf6f1c8016d14e739ec53aac" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -859,9 +859,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc79013f9ac3a8ddeb60234d43da09e6d6abfc1c9dd29d3fe97adfbece3f4a08" +checksum = "7bf39928a5e70c9755d6811a2928131b53ba785ad37c8bf85c90175b5d43b818" dependencies = [ "alloy-primitives", "darling 0.21.3", diff --git a/configuration/Cargo.toml b/configuration/Cargo.toml index 7ae669d9..f6089b6a 100644 --- a/configuration/Cargo.toml +++ b/configuration/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] config = "0.13.4" -alloy = { version = "1.0.23", features = ["transport-ws"] } +alloy = { version = "1.0.38", features = ["transport-ws"] } rand = "0.8" serde = { version = "1.0.219", features = ["derive"] } serial_test = "3.2.0" diff --git a/doc/plume_testnet_client_playbook.md b/doc/plume_testnet_client_playbook.md new file mode 100644 index 00000000..988e26f3 --- /dev/null +++ b/doc/plume_testnet_client_playbook.md @@ -0,0 +1,85 @@ +# Nightfall Client Playbook — Plume Testnet + +## 1) Get the source + +```bash +git clone https://github.com/EYBlockchain/nightfall_4_CE.git +cd nightfall_4_CE +git checkout plume_testnet_client +``` + +Make sure you pulled the latest changes. + +--- + +## 2) Stop & clean previous Docker state + +```bash +# DANGER: removes commitment db +docker compose --profile indie-client down -v +# DANGER: removes images, containers, networks, and volumes +docker system prune -a --volumes +``` + +--- + +## 3) Wallet setup: MetaMask + Plume testnet + +1. Install the MetaMask browser extension: [https://metamask.io/en-GB](https://metamask.io/en-GB) +2. Create a **new network** in MetaMask for **Plume testnet** using the parameters published here: [https://thirdweb.com/plume-testnet](https://thirdweb.com/plume-testnet) +3. Import or create an account and ensure it has **≥ 10 PLUME** for fees (test funds). + +--- + +## 4) Create `local.env` + +Create a file named `local.env` in the repo root with the following content. Replace placeholders (`0x....`) with your values where indicated. + +```bash +CLIENT_SIGNING_KEY="0x......." # your private key +CLIENT2_SIGNING_KEY= +CLIENT_ADDRESS="0x......." # your public address +CLIENT2_ADDRESS="0xf2Fca7419389fB8f5Db220cdEe9039AD2FFb03b5" # keep this as test +PROPOSER_SIGNING_KEY="0x745e9fb463ee15a748b2245e08e798dc5f6388870f4d38c4a7d33f9def590723" # keep this as test +PROPOSER_2_SIGNING_KEY= +DEPLOYER_SIGNING_KEY="0x....." # same as your private key +NIGHTFALL_ADDRESS="0xf86806f5eb3ae6cb08fa2e5ad23bf1ba7b2d7ce3" +NF4_SIGNING_KEY="0x..." # same as your private key +WEBHOOK_URL= +AZURE_VAULT_URL= +DEPLOYER_SIGNING_KEY_NAME= +PROPOSER_SIGNING_KEY_NAME= +PROPOSER_2_SIGNING_KEY_NAME= +CLIENT_SIGNING_KEY_NAME= +CLIENT2_SIGNING_KEY_NAME= +AZURE_CLIENT_ID= +AZURE_CLIENT_SECRET= +AZURE_TENANT_ID= +``` +If you host a online webhook, please put you url in `WEBHOOK_URL`, if you want to host a local webhook, you can follow nf_4.md about how to host a local webhook and replace the webhook url. For easier start, run `python3 webhook.py` in seperate terminal to start a testing local webhook. Webhook is essential to de-escrow fund back to host chain. +Please remove all comments (beginning with `#`) in your local.env file. + +--- + +## 5) Build and run the Nightfall client + +From the repo root: + +```bash +docker compose --profile indie-client build + +docker compose --profile indie-client --env-file local.env up +``` + +## 6) Deployment script + +You can also deploy your own ERC-20/721/1155/3525 contracts using the following script: `blockchain_assets/script/mock_deployment.s.sol` + +Or, you can use mock ERC deployments to play with. + +1. `forge build` +2. `export $(grep -v '^#' local.env | xargs)` +3. `forge script blockchain_assets/script/mock_deployment.s.sol:MockDeployer --rpc-url https://testnet-rpc.plume.org --broadcast --legacy --slow` + + +You can then interact with those contract using the APIs: https://github.com/EYBlockchain/nightfall_4_CE/blob/master/doc/nf_4.md#client-apis# Nightfall Client Playbook — Plume Testnet diff --git a/docker-compose.yml b/docker-compose.yml index c440f0a0..6e8c7d66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -74,13 +74,13 @@ services: condition: service_healthy tty: true # required for logs to print in colour environment: - - NF4_RUN_MODE=${NF4_RUN_MODE:-base_sepolia} + - NF4_RUN_MODE=${NF4_RUN_MODE:-plume_testnet} - RUST_BACKTRACE=${RUST_BACKTRACE:-0} - NF4_SIGNING_KEY=${CLIENT_SIGNING_KEY} - NF4_NIGHTFALL_CLIENT__DB_URL=mongodb://nf4_db_client:27017 - NF4_ETHEREUM_CLIENT_URL - NF4_MOCK_PROVER=${NF4_MOCK_PROVER:-false} - - NF4_NIGHTFALL_PROPOSER__URL= ${NF4_NIGHTFALL_PROPOSER__URL:-localhost:3001} + - NF4_NIGHTFALL_PROPOSER__URL= ${NF4_NIGHTFALL_PROPOSER__URL:-http://35.225.105.10:3001} client2: container_name: nf4_client2 @@ -221,7 +221,6 @@ services: - sync_test build: dockerfile: nightfall_proposer/Dockerfile - context: . platform: linux/amd64 # Required for building on M1 Macs ports: - "3003:3000" @@ -280,8 +279,8 @@ services: - "27017:27017" networks: - nightfall_network - # volumes: - # - mongodb_client_data:/data/db + volumes: + - mongodb_client_data:/data/db environment: - NF4_RUN_MODE=${NF4_RUN_MODE:-development} @@ -553,7 +552,7 @@ services: condition: service_healthy volumes: - # mongodb_client_data: + mongodb_client_data: # mongodb_client2_data: # mongodb_proposer_data: address_data: diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 25045ecb..958d651a 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -15,7 +15,7 @@ dotenv = "0.15.0" x509-parser = "0.16.0" openssl = "0.10.72" k256 = "0.13.4" -alloy = { version = "1.0.23", features = ["full", "json-rpc"] } +alloy = { version = "1.0.38", features = ["full", "json-rpc"] } tokio = { version = "1.45.0", features = ["full"] } configuration = { path = "../configuration" } nightfall_bindings = {path = "../nightfall_bindings"} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 1e7e5641..e1f31cef 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -10,9 +10,11 @@ pub mod validate_certificate; pub mod wallets; pub mod initialisation { - use crate::{blockchain_client::BlockchainClientConnection, wallets::LocalWsClient}; + use crate::{blockchain_client::BlockchainClientConnection, error::BlockchainClientConnectionError, wallets::LocalWsClient}; + use log::info; // Import the `info!` macro use configuration::settings::get_settings; use tokio::sync::{OnceCell, RwLock}; + use url::Url; /// This function is used to provide a singleton blockchain client connection across the entire application. pub async fn get_blockchain_client_connection() -> &'static RwLock { @@ -20,11 +22,21 @@ pub mod initialisation { OnceCell::const_new(); BLOCKCHAIN_CLIENT_CONNECTION .get_or_init(|| async { - RwLock::new( + + let settings = get_settings(); + let url = match Url::parse(&settings.ethereum_client_url) { + Ok(parsed_url) => parsed_url, + Err(e) => { + panic!("Failed to parse Ethereum client URL: {}", e); + } + }; + let client = LocalWsClient::try_from_settings(get_settings()) - .await - .expect("Could not create blockchain client connection"), - ) + .await.expect("Failed to create blockchain client from settings"); + let spwan = client.clone().spawn_reconnect_task(url.clone()); + // Spawn a periodic background reconnection loop + info!("Blockchain client initialized and reconnect task started."); + RwLock::new(client) }) .await } diff --git a/lib/src/validate_certificate.rs b/lib/src/validate_certificate.rs index 67479c2f..f18570b8 100644 --- a/lib/src/validate_certificate.rs +++ b/lib/src/validate_certificate.rs @@ -191,15 +191,11 @@ async fn validate_certificate( oid_group: u32, sender_address: Address, ) -> Result<(), Box> { - let read_connection = get_blockchain_client_connection() - .await - .read() - .await; + let read_connection = get_blockchain_client_connection().await.read().await; let provider = read_connection.get_client(); let blockchain_client = provider.root(); let caller = read_connection.get_address(); - let x509_instance = X509::new(x509_contract_address, blockchain_client); let compute_result = x509_instance diff --git a/lib/src/wallets.rs b/lib/src/wallets.rs index c27d9af7..70f6be1a 100644 --- a/lib/src/wallets.rs +++ b/lib/src/wallets.rs @@ -7,9 +7,10 @@ use alloy::signers::local::PrivateKeySigner; use alloy::transports::ws::WsConnect; use async_trait::async_trait; use azure_security_keyvault::SecretClient; -use log::{debug, info}; +use log::{debug, info, warn, error}; use std::{error::Error, sync::Arc}; use url::Url; +use tokio::time::{sleep, Duration}; #[derive(Clone, Debug)] pub enum WalletType { @@ -50,6 +51,82 @@ pub struct LocalWsClient { signer: PrivateKeySigner, } +impl LocalWsClient { + /// Create a new WebSocket provider connection. + async fn connect_provider( + url: Url, + signer: PrivateKeySigner, + ) -> Result, BlockchainClientConnectionError> { + let ws = WsConnect::new(url); + let provider = ProviderBuilder::new() + .wallet(signer.clone()) + .connect_ws(ws) + .await + .map_err(|e| BlockchainClientConnectionError::ProviderError(e.to_string()))?; + Ok(Arc::new(provider)) + } + + /// Attempt to reconnect to the WebSocket RPC, retrying up to `max_retries` times. + async fn reconnect( + &mut self, + url: Url, + max_retries: usize, + ) -> Result<(), BlockchainClientConnectionError> { + let mut retries = 0; + loop { + retries += 1; + match Self::connect_provider(url.clone(), self.signer.clone()).await { + Ok(p) => { + self.provider = p; + warn!("Reconnected to WebSocket after {} attempt(s)", retries); + return Ok(()); + } + Err(e) => { + if retries >= max_retries { + error!("Failed to reconnect after {} retries: {}", retries, e); + return Err(e); + } + warn!( + "WebSocket reconnect attempt {}/{} failed: {}. Retrying in 5s...", + retries, + max_retries, + e + ); + sleep(Duration::from_secs(5)).await; + } + } + } + } + + /// Periodically check if the provider is still connected and reconnect if needed. + pub fn spawn_reconnect_task(self, url: Url) -> Arc { + let client = Arc::new(self); + let client_clone = client.clone(); + let client_for_task = client.clone(); + println!("Spawning reconnect task for WebSocket client..."); + + tokio::spawn(async move { + loop { + let is_connected = client_clone.is_connected().await; + println!("connection is alive: {}", is_connected); + if !client_clone.is_connected().await { + warn!("WebSocket connection lost, attempting to reconnect..."); + if let Err(e) = Arc::get_mut(&mut client_for_task.clone()) + .unwrap() + .reconnect(url.clone(), 5) + .await + { + error!("Reconnection failed: {}", e); + } + } + sleep(Duration::from_secs(3)).await; + } + }); + + client + } +} + #[async_trait] impl BlockchainClientConnection for LocalWsClient { type W = PrivateKeySigner; @@ -117,17 +194,32 @@ impl BlockchainClientConnection for LocalWsClient { info!("Created signer with address: {:?}", local_signer.address()); debug!("And chain id: {}", settings.network.chain_id); debug!("And Ethereum client url: {}", settings.ethereum_client_url); + let url = Url::parse(&settings.ethereum_client_url) + .map_err(|e| BlockchainClientConnectionError::ProviderError(e.to_string()))?; // create provider - let ws = WsConnect::new(settings.ethereum_client_url.clone()); - let provider = ProviderBuilder::new() - .wallet(local_signer.clone()) - .connect_ws(ws) - .await - .map_err(|e| BlockchainClientConnectionError::ProviderError(e.to_string()))?; + // Attempt to connect with retry logic at startup + let mut retries = 0; + let provider: Arc = loop { + match LocalWsClient::connect_provider(url.clone(), local_signer.clone()).await { + Ok(p) => break p, + Err(e) => { + retries += 1; + if retries > 5 { + return Err(e); + } + warn!( + "Initial WebSocket connection failed (attempt {}), retrying in 5s: {}", + retries, + e + ); + sleep(Duration::from_secs(5)).await; + } + } + }; Ok(Self { - provider: Arc::new(provider), + provider, signer: local_signer, }) } diff --git a/nightfall.toml b/nightfall.toml index 66da1bb1..786cb8b9 100644 --- a/nightfall.toml +++ b/nightfall.toml @@ -23,7 +23,7 @@ log_level = "info" wallet_type = "local" db_url = "mongodb://nf4_db_client:27017" max_event_listener_attempts = 10 -webhook_url = "http://172.18.0.250:8080/webhook" # The webhook URL for the client to send notifications to the propose +webhook_url = "http://host.docker.internal:8080/webhook" # The webhook URL for the client to send notifications to the propose max_queue_size = 1000 [development.nightfall_proposer] @@ -111,6 +111,7 @@ log_level = "info" [development.contracts] assets = "./blockchain_assets/artifacts" +rust_bindings = "./nightfall_bindings" addresses_file = "configuration/toml/addresses.toml" deployment_file = "./blockchain_assets/logs/deployer.s.sol" deploy_contracts = true @@ -152,7 +153,7 @@ log_level = "info" wallet_type = "local" db_url = "mongodb://nf4_db_client:27017" max_event_listener_attempts = 10 -webhook_url = "http://172.18.0.250:8080/webhook" # The webhook URL for the client to send notifications to the propose +webhook_url = "http://host.docker.internal:8080/webhook" # The webhook URL for the client to send notifications to the propose max_queue_size = 1000 [base_sepolia.nightfall_proposer] @@ -182,6 +183,7 @@ log_level = "info" [base_sepolia.contracts] assets = "./blockchain_assets/artifacts" +rust_bindings = "./nightfall_bindings" addresses_file = "configuration/toml/addresses.toml" deployment_file = "./blockchain_assets/logs/deployer.s.sol" deploy_contracts = false @@ -203,6 +205,88 @@ certificate_policies = [ "0x06032d0607000000000000000000000000000000000000000000000000000000", ] + +[plume_testnet] +# This is the plume_testnet environment - add an appropriate configuration here, using development as a template +signing_key = "Key not set" #key (0) +azure_vault_url = "vault url is not set" +azure_key_name = "key name not set" +log_app_only = true +test_x509_certificates = true +mock_prover = false +genesis_block = 0 +ethereum_client_url = "wss://testnet-rpc.plume.org" +configuration_url = "http://35.225.105.10:8080" # The name of the proposer config to use can be overridden by env var NF4_PROPOSER_CONFIG + +[plume_testnet.network] +chain_id = 98867 + +[plume_testnet.nightfall_client] +url = "http://client:3000" +log_level = "info" +wallet_type = "local" +db_url = "mongodb://nf4_db_client:27017" +max_event_listener_attempts = 10 +webhook_url = "http://host.docker.internal:8080/webhook" # The webhook URL for the client to send notifications to the propose +max_queue_size = 1000 + +[plume_testnet.nightfall_proposer] +url = "http://35.225.105.10:3001" +log_level = "info" +wallet_type = "local" +db_url = "mongodb://nf4_db_proposer:27017" +block_assembly_max_wait_secs = 120 +block_assembly_target_fill_ratio = 0.25 +block_assembly_initial_interval_secs = 15 +max_event_listener_attempts = 10 +block_size = 64 + + +[plume_testnet.nightfall_deployer] +log_level = "info" +default_proposer_address = "0xd1Fe296737888f2310EDD943bc6342a41d8Da409" +default_proposer_url = "http://35.225.105.10:3001" +proposer_stake = 4 +proposer_ding = 3 # how much to fine a proposer for not making a block +proposer_exit_penalty = 2 # how much to fine a proposer for deregister during its turn +proposer_cooling_blocks = 4 # how many blocks before a de-registered proposer can register again +proposer_rotation_blocks = 4 # how many blocks before we rotate proposers + +[plume_testnet.nightfall_test] +log_level = "info" + +[plume_testnet.owners] +vk_provider_owner = "0x32fE75423eFaC83B636E41115152A0Fe6D14C7c2" +x509_owner = "0x32fE75423eFaC83B636E41115152A0Fe6D14C7c2" +verifier_owner = "0x32fE75423eFaC83B636E41115152A0Fe6D14C7c2" +round_robin_owner = "0x32fE75423eFaC83B636E41115152A0Fe6D14C7c2" +nightfall_owner = "0x32fE75423eFaC83B636E41115152A0Fe6D14C7c2" + +[plume_testnet.contracts] +assets = "./blockchain_assets/artifacts" +rust_bindings = "./nightfall_bindings" +addresses_file = "configuration/toml/addresses.toml" +deployment_file = "./blockchain_assets/logs/deployer.s.sol" +deploy_contracts = true + +[plume_testnet.contracts.contract_addresses] # Contract addresses for the development network. These are used if deploy_contracts is false and they are not empty +nightfall = "" +round_robin = "" +x509 = "" + +[plume_testnet.certificates] +authority_key_identifier = "0xA469FF28BFAB9C4DB09220B24038D6F18EA57F75" +modulus = "0x009DEA9DCA80BFA87C29232B18D6C0072898922A7E7E224A7FF638F61851B5F36392E7FBFDBFF3A0AE409763E2A04CDD3DC692A6DE447391FFE6722456957DD7F031B8D9A7999579F6F4258490AE6E9D629BC40815F689C58037C03B46502243BFD29B02116454453810D160DE1D8C8DDD624B30A25A011185E60BCA9BF71181DD3256112F1EFDBECF19E77AF9640EDE4DB8FF51855E6B490424FC4F5631DD9551D7CD762420E3AFA0B20E6B403A0CB71FA16861F8C591B2BD7BDD564EC6D5A17A932E310876D1D65AF3F3F213D1C49086F32C7C8A0F53750127DF8709F6035688E02E613F1C57A525A21DD83FA27D0622FC0EFC76ABA114194A7FDA1B0879013D0790F3B8D387ACA238FC37135F9BA6BB0C87A972143568B010B62EE8BA71C78202858170F292596AD95DD4FA2DC8E9ABA359B8F511B5F3894906F3FD0A22CA3DEB2E67B2A97CD2B847AC73BE28F69996A4CF51B6FD87B9F932F6049F886AC5A7725755693842DF00795A9D00C76E2C4446BDDA5E595CBE8CDF51E050632DB110D155343188A57F273B4334E5DA5EC556AD3CADC3327268DC0C528FE41F837A393B5B2F76E476CFA64A2A24BA71F5F7078F5360EBF316D4275AB292B031B9CF8787ACB009D3DC5DCD5038C05E1B2225909E596DFE2E968CFAE077FDFF540E3F78FC464966BB19E280DE34F81079B9DCA111904CDC7C5B6FD5CD44A215B0B5A6A9" +exponent = 65537 +extended_key_usages = [ + "0x06082b0601050507030400000000000000000000000000000000000000000000", + "0x06082b0601050507030800000000000000000000000000000000000000000000", +] +certificate_policies = [ + "0x06032d0607000000000000000000000000000000000000000000000000000000", +] + + [production] # This is the production environment - add an appropriate configuration here, using development as a template diff --git a/nightfall_bindings/Cargo.toml b/nightfall_bindings/Cargo.toml index 84c4aa46..e3f24969 100644 --- a/nightfall_bindings/Cargo.toml +++ b/nightfall_bindings/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -alloy = { version = "1.0.23", features = ["full"] } +alloy = { version = "1.0.38", features = ["full"] } configuration = { path = "../configuration" } log = "0.4.27" diff --git a/nightfall_client/Cargo.toml b/nightfall_client/Cargo.toml index f3e0d526..b4f310c5 100644 --- a/nightfall_client/Cargo.toml +++ b/nightfall_client/Cargo.toml @@ -19,7 +19,7 @@ rand_core = { version = "0.6.4", features = ["getrandom"] } ark-serialize = "0.4.2" sha3 = "0.10.8" sha2 = "0.10" -alloy = { version = "1.0.23", features = ["transport-ws", "json-rpc", "serde"] } +alloy = { version = "1.0.38", features = ["transport-ws", "json-rpc", "serde"] } warp = "0.3.7" serde = { version = "1.0.219", features = ["derive"] } tokio = { version = "1.45.0", features = ["full"] } diff --git a/nightfall_client/Dockerfile b/nightfall_client/Dockerfile index b6bda3c8..a0e4be79 100644 --- a/nightfall_client/Dockerfile +++ b/nightfall_client/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1-labs -FROM rust:1.88.0 AS builder +FROM rust:1.84.1 AS builder # install additional ca-certificates e.g. zscaler certificate (can be removed if not needed) COPY configuration/trust/* /usr/local/share/ca-certificates/ RUN chmod 644 /usr/local/share/ca-certificates/* && update-ca-certificates @@ -8,7 +8,7 @@ RUN chmod 644 /usr/local/share/ca-certificates/* && update-ca-certificates WORKDIR /app RUN mkdir bin COPY --exclude=configuration/bin/* . . -COPY configuration/bin/proving_key configuration/bin/deposit_proving_key configuration/bin/ +# COPY configuration/bin/proving_key configuration/bin/ RUN --mount=type=cache,target=/usr/local/cargo/registry \ --mount=type=cache,target=/app/target \ --mount=type=cache,target=/usr/local/cargo/git \ @@ -17,7 +17,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \ FROM debian:bookworm-slim WORKDIR /app COPY --from=builder /app/bin/client bin/ -COPY configuration/bin/proving_key configuration/bin/deposit_proving_key configuration/bin/ +# COPY configuration/bin/proving_key configuration/bin/ # COPY configuration/toml/addresses.toml configuration/toml/ COPY .env *.env nightfall.toml . RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/nightfall_client/src/driven/contract_functions/nightfall_contract.rs b/nightfall_client/src/driven/contract_functions/nightfall_contract.rs index 5c67eb0c..afc2bc3c 100644 --- a/nightfall_client/src/driven/contract_functions/nightfall_contract.rs +++ b/nightfall_client/src/driven/contract_functions/nightfall_contract.rs @@ -19,12 +19,12 @@ use alloy::{ use ark_bn254::Fr as Fr254; use ark_ff::BigInteger256; use ark_std::Zero; -use configuration::addresses::get_addresses; +use configuration::{addresses::get_addresses, settings::get_settings}; use lib::{ blockchain_client::BlockchainClientConnection, initialisation::get_blockchain_client_connection, }; use log::{debug, info}; -use nightfall_bindings::artifacts::{ERC20Mock, Nightfall, IERC3525}; +use nightfall_bindings::artifacts::{Nightfall, IERC3525}; use num::BigUint; impl NightfallContract for Nightfall::NightfallCalls { @@ -88,6 +88,23 @@ impl NightfallContract for Nightfall::NightfallCalls { // token_type.into(), // ) // .value(Uint256::from(total_fee).0) + // .from(signer.address()); + + // // Send transaction directly through Alloy + // let receipt = call + // .send() + // .await + // .map_err(|e| { + // if e.as_revert_data().is_some() { + // format!( + // "Revert when calling escrow: {:?}", + // e.as_decoded_error::() + // ) + // } else { + // format!("Contract error: {e}") + // } + // }) + // .expect("Error sending transaction") // .nonce(nonce) // .gas(gas_limit) // .max_fee_per_gas(max_fee_per_gas) @@ -108,6 +125,19 @@ impl NightfallContract for Nightfall::NightfallCalls { // .map_err(|e| { // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) // })?; + let nonce = client + .get_transaction_count(signer.address()) + .await + .map_err(|e| { + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 5000000u64; + let call = contract .escrow_funds( solidity_fee.0, @@ -118,23 +148,23 @@ impl NightfallContract for Nightfall::NightfallCalls { token_type.into(), ) .value(Uint256::from(total_fee).0) - .from(signer.address()); + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) + .await + .map_err(|e| { + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; - // Send transaction directly through Alloy - let receipt = call - .send() + let receipt = client + .send_raw_transaction(&call) .await .map_err(|e| { - if e.as_revert_data().is_some() { - format!( - "Revert when calling escrow: {:?}", - e.as_decoded_error::() - ) - } else { - format!("Contract error: {e}") - } - }) - .expect("Error sending transaction") + NightfallContractError::EscrowError(format!("Error getting receipt: {e}")) + })? .get_receipt() .await .map_err(|e| { @@ -203,6 +233,9 @@ impl NightfallContract for Nightfall::NightfallCalls { let contract = Nightfall::new(get_addresses().nightfall(), client.clone()); + // let call = contract + // .descrow_funds(decode_data, token_type.into()) + // .from(signer.address()); /* If your chain doesn't support signing transactions locally, and need to be signed at the client and need to use send_raw_tansaction uncomment the code below */ // let nonce = client.get_transaction_count(signer.address()).await.map_err(|e| { @@ -237,23 +270,59 @@ impl NightfallContract for Nightfall::NightfallCalls { // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) // })?; + // let call = contract + // .descrow_funds(decode_data, token_type.into()) + // .from(signer.address()); + + // let receipt = call + // .send() + // .await + // .map_err(|e| { + // if e.as_revert_data().is_some() { + // format!( + // "Revert when calling escrow: {:?}", + // e.as_decoded_error::() + // ) + // } else { + // format!("Contract error: {e}") + // } + // }) + // .map_err(|e| { + // NightfallContractError::EscrowError(format!("Error getting receipt: {e}")) + // })? + // .get_receipt() + // .await + // .map_err(|e| { + // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + // })?; + let nonce = client + .get_transaction_count(signer.address()) + .await + .map_err(|e| { + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 500000000u64; let call = contract .descrow_funds(decode_data, token_type.into()) - .from(signer.address()); - - let receipt = call - .send() + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) .await .map_err(|e| { - if e.as_revert_data().is_some() { - format!( - "Revert when calling escrow: {:?}", - e.as_decoded_error::() - ) - } else { - format!("Contract error: {e}") - } - }) + NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) + })?; + + let receipt = client + .send_raw_transaction(&call) + .await .map_err(|e| { NightfallContractError::EscrowError(format!("Error getting receipt: {e}")) })? diff --git a/nightfall_client/src/driven/contract_functions/token_contracts.rs b/nightfall_client/src/driven/contract_functions/token_contracts.rs index a27f24ef..05468604 100644 --- a/nightfall_client/src/driven/contract_functions/token_contracts.rs +++ b/nightfall_client/src/driven/contract_functions/token_contracts.rs @@ -2,10 +2,11 @@ use super::contract_type_conversions::{Addr, Uint256}; use crate::{domain::error::TokenContractError, ports::contracts::TokenContract}; +use alloy::providers::Provider; use ark_bn254::Fr as Fr254; use ark_ff::{BigInteger, BigInteger256}; use ark_std::Zero; -use configuration::addresses::get_addresses; +use configuration::{addresses::get_addresses, settings::get_settings}; use lib::{ blockchain_client::BlockchainClientConnection, error::BlockchainClientConnectionError, initialisation::get_blockchain_client_connection, @@ -37,67 +38,68 @@ impl TokenContract for IERC20::IERC20Calls { let provider = read.get_client(); let client = provider.root(); let caller = read.get_address(); - - /* If your chain doesn't support signing transactions locally, and need to be signed at the client level and need to use send_raw_tansaction uncomment the code below */ - - // let nonce = client.get_transaction_count(caller).await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let gas_price = client.get_gas_price().await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let max_fee_per_gas = gas_price * 2; - // let max_priority_fee_per_gas = gas_price; - // let gas_limit = 5000000u64; - - // let raw_tx = IERC20::new(solidity_erc_address.0, client.clone()) - // .approve(spender, amount.0) - // .nonce(nonce) - // .gas(gas_limit) - // .max_fee_per_gas(max_fee_per_gas) - // .max_priority_fee_per_gas(max_priority_fee_per_gas) - // .chain_id(get_settings().network.chain_id) // Linea testnet chain ID - // .build_raw_transaction(caller).await - // .map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - - // let tx_receipt = client.send_raw_transaction(&raw_tx).await - // .map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })? - // .get_receipt() - // .await; - - // Send the transaction with explicit `from` - // ERC-20 approve(spender, amount) never requires token ownership or balance. It just sets the allowance for the caller itself (owner = msg.sender) - let tx_receipt = IERC20::new(solidity_erc_address.0, client.clone()) + let signer = get_blockchain_client_connection() + .await + .read() + .await + .get_signer(); + + let nonce = client.get_transaction_count(caller).await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 5000000u64; + + let raw_tx = IERC20::new(solidity_erc_address.0, client.clone()) .approve(spender, amount.0) - .from(caller) - .send() + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) + .await + .map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + + let tx_receipt = client + .send_raw_transaction(&raw_tx) .await .map_err(|e| { BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) })? .get_receipt() - .await - .map_err(|_| { - BlockchainClientConnectionError::ProviderError( - "Failed to get transaction receipt".to_string(), - ) - })?; + .await; + + // Send the transaction with explicit `from` + // ERC-20 approve(spender, amount) never requires token ownership or balance. It just sets the allowance for the caller itself (owner = msg.sender) + // let tx_receipt = IERC20::new(solidity_erc_address.0, client.clone()) + // .approve(spender, amount.0) + // .from(caller) + // .send() + // .await + // .map_err(|e| { + // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + // })? + // .get_receipt() + // .await + // .map_err(|_| { + // BlockchainClientConnectionError::ProviderError( + // "Failed to get transaction receipt".to_string(), + // ) + // })?; - debug!("ERC20 approval tx mined, from: {:?}", tx_receipt.from); + debug!( + "ERC20 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); - if !tx_receipt.status() { + if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( "ERC20 SetApproval Transaction reverted (status=0)".to_string(), ) @@ -132,61 +134,66 @@ impl TokenContract for IERC721::IERC721Calls { let provider = read.get_client(); let client = provider.root(); let caller = read.get_address(); + let signer = get_blockchain_client_connection() + .await + .read() + .await + .get_signer(); - // let nonce = client.get_transaction_count(caller).await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let gas_price = client.get_gas_price().await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let max_fee_per_gas = gas_price * 2; - // let max_priority_fee_per_gas = gas_price; - // let gas_limit = 500000000u64; - // let raw_tx = IERC721::new(solidity_erc_address.0, client.clone()) + // // Send the transaction with explicit `from` + // let tx_receipt = IERC721::new(solidity_erc_address.0, client.clone()) // .approve(spender, token_id_u256.0) - // .nonce(nonce) - // .gas(gas_limit) - // .max_fee_per_gas(max_fee_per_gas) - // .max_priority_fee_per_gas(max_priority_fee_per_gas) - // .chain_id(get_settings().network.chain_id) // Linea testnet chain ID - // .build_raw_transaction(caller).await - // .map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) - // })?; - - - // let tx_receipt = client.send_raw_transaction(&raw_tx).await + // .from(caller) + // .send() + // .await // .map_err(|e| { // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) // })? // .get_receipt() - // .await; - + // .await + // .map_err(|_| { + // BlockchainClientConnectionError::ProviderError( + // "Failed to get transaction receipt".to_string(), + // ) + // })?; - // Send the transaction with explicit `from` - let tx_receipt = IERC721::new(solidity_erc_address.0, client.clone()) + let nonce = client.get_transaction_count(caller).await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 500000000u64; + let raw_tx = IERC721::new(solidity_erc_address.0, client.clone()) .approve(spender, token_id_u256.0) - .from(caller) - .send() + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) + .await + .map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + + let tx_receipt = client + .send_raw_transaction(&raw_tx) .await .map_err(|e| { BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) })? .get_receipt() - .await - .map_err(|_| { - BlockchainClientConnectionError::ProviderError( - "Failed to get transaction receipt".to_string(), - ) - })?; + .await; - debug!("ERC721 approval tx mined, from: {:?}", tx_receipt.from); + debug!( + "ERC721 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); - if !tx_receipt.status() { + if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( "ERC721 SetApproval Transaction reverted (status=0)".to_string(), ) @@ -203,7 +210,6 @@ impl TokenContract for IERC1155::IERC1155Calls { value: Fr254, token_id: BigInteger256, ) -> Result<(), TokenContractError> { - if value.is_zero() & token_id.is_zero() { return Err(TokenContractError::TokenTypeError( "ERC1155 approvals should have one of value or token ID non-zero".to_string(), @@ -220,61 +226,70 @@ impl TokenContract for IERC1155::IERC1155Calls { let provider = read.get_client(); let client = provider.root(); let caller = read.get_address(); - - // let nonce = client.get_transaction_count(caller).await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let gas_price = client.get_gas_price().await.map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!( - // "Contract error: {e}" - // )) - // })?; - // let max_fee_per_gas = gas_price * 2; - // let max_priority_fee_per_gas = gas_price; - // let gas_limit = 500000000u64; - // let raw_tx = IERC1155::new(solidity_erc_address.0, client.clone()) - // .setApprovalForAll(operator, true) - // .nonce(nonce) - // .gas(gas_limit) - // .max_fee_per_gas(max_fee_per_gas) - // .max_priority_fee_per_gas(max_priority_fee_per_gas) - // .chain_id(get_settings().network.chain_id) // Linea testnet chain ID - // .build_raw_transaction(caller).await - // .map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) - // })?; - - // let tx_receipt = client.send_raw_transaction(&raw_tx).await - // .map_err(|e| { - // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) - // })? - // .get_receipt() - // .await; - - // Send the transaction with explicit `from` - // setApprovalForAll(operator, approved) is per-caller, not per tokenId or value. - // Any address can toggle operator approval for itself; there is no ownership-of-a-specific-token check and no balance requirement. - let tx_receipt = IERC1155::new(solidity_erc_address.0, client.clone()) + let signer = get_blockchain_client_connection() + .await + .read() + .await + .get_signer(); + + let erc1155 = IERC1155::new(solidity_erc_address.0, client.clone()); + + let nonce = client.get_transaction_count(caller).await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 500000000u64; + let raw_tx = erc1155 .setApprovalForAll(operator, true) - .from(caller) - .send() + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) + .await + .map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + + let tx_receipt = client + .send_raw_transaction(&raw_tx) .await .map_err(|e| { BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) })? .get_receipt() - .await - .map_err(|_| { - BlockchainClientConnectionError::ProviderError( - "Failed to get transaction receipt".to_string(), - ) - })?; + .await; + + // Send the transaction with explicit `from` + // setApprovalForAll(operator, approved) is per-caller, not per tokenId or value. + // Any address can toggle operator approval for itself; there is no ownership-of-a-specific-token check and no balance requirement. + // let tx_receipt = IERC1155::new(solidity_erc_address.0, client.clone()) + // .setApprovalForAll(operator, true) + // .from(caller) + // .send() + // .await + // .map_err(|e| { + // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + // })? + // .get_receipt() + // .await + // .map_err(|_| { + // BlockchainClientConnectionError::ProviderError( + // "Failed to get transaction receipt".to_string(), + // ) + // })?; - debug!("ERC1155 approval tx mined, from: {:?}", tx_receipt.from); + debug!( + "ERC1155 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); - if !tx_receipt.status() { + if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( "ERC1155 SetApproval Transaction reverted (status=0)".to_string(), ) @@ -302,6 +317,11 @@ impl TokenContract for IERC3525::IERC3525Calls { let provider = read.get_client(); let client = provider.root(); let caller = read.get_address(); + let signer = get_blockchain_client_connection() + .await + .read() + .await + .get_signer(); debug!("ERC3525 caller: {caller:?}"); @@ -330,7 +350,6 @@ impl TokenContract for IERC3525::IERC3525Calls { // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) // })?; - // let tx_receipt = client.send_raw_transaction(&raw_tx).await // .map_err(|e| { // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) @@ -338,31 +357,64 @@ impl TokenContract for IERC3525::IERC3525Calls { // .get_receipt() // .await; - // NOTE: IERC3525 has overloaded approve functions in many implementations. // Here we use the 2-arg overload approve(address to, uint256 tokenId), // which bindings expose as `approve_0`. - // Send the transaction with explicit `from` - let tx_receipt = IERC3525::new(solidity_erc_address.0, client.clone()) + // // Send the transaction with explicit `from` + // let tx_receipt = IERC3525::new(solidity_erc_address.0, client.clone()) + // .approve_0(spender, token_id_u256.0) + // .from(caller) + // .send() + // .await + // .map_err(|e| { + // BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + // })? + // .get_receipt() + // .await + // .map_err(|_| { + // BlockchainClientConnectionError::ProviderError( + // "Failed to get transaction receipt".to_string(), + // ) + // })?; + + let nonce = client.get_transaction_count(caller).await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let gas_price = client.get_gas_price().await.map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + let max_fee_per_gas = gas_price * 2; + let max_priority_fee_per_gas = gas_price; + let gas_limit = 500000000u64; + let raw_tx = IERC3525::new(solidity_erc_address.0, client.clone()) .approve_0(spender, token_id_u256.0) - .from(caller) - .send() + .nonce(nonce) + .gas(gas_limit) + .max_fee_per_gas(max_fee_per_gas) + .max_priority_fee_per_gas(max_priority_fee_per_gas) + .chain_id(get_settings().network.chain_id) // Linea testnet chain ID + .build_raw_transaction(signer) + .await + .map_err(|e| { + BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) + })?; + + let tx_receipt = client + .send_raw_transaction(&raw_tx) .await .map_err(|e| { BlockchainClientConnectionError::ProviderError(format!("Contract error: {e}")) })? .get_receipt() - .await - .map_err(|_| { - BlockchainClientConnectionError::ProviderError( - "Failed to get transaction receipt".to_string(), - ) - })?; + .await; - debug!("ERC3525 approval tx mined, from: {:?}", tx_receipt.from); + debug!( + "ERC3525 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); - if !tx_receipt.status() { + if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( "ERC3525 SetApproval Transaction reverted (status=0)".to_string(), ) diff --git a/nightfall_client/src/driven/queue.rs b/nightfall_client/src/driven/queue.rs index 049e9efc..d1b2e08e 100644 --- a/nightfall_client/src/driven/queue.rs +++ b/nightfall_client/src/driven/queue.rs @@ -2,8 +2,8 @@ use crate::{ domain::entities::{RequestStatus, SynchronisationPhase::Desynchronized}, driven::notifier::webhook_notifier::WebhookNotifier, drivers::{ - blockchain::nightfall_event_listener::{ - get_synchronisation_status, restart_event_listener, + blockchain::{ + event_listener_manager::restart, nightfall_event_listener::get_synchronisation_status, }, rest::{ client_nf_3::handle_request, @@ -27,7 +27,6 @@ use tokio::{ }; /// This module implements a queue of received requests. Requests can be added to the queue /// asynchronously but are executed with a concurrency of 1. -/// pub struct QueuedRequest { pub transaction_request: TransactionRequest, pub uuid: String, @@ -71,10 +70,8 @@ where } }; if sync_state == Desynchronized { - warn!("Client is not synchronised with the blockchain, restarting event listener"); - tokio::spawn(async { - restart_event_listener::(0).await; - }); + warn!("Client is not synchronised with the blockchain, restarting event listener on thread {:?}", std::thread::current().id()); + restart::().await; } while let Some(request) = { let mut queue = get_queue().await.write().await; diff --git a/nightfall_client/src/drivers/blockchain/event_listener_manager.rs b/nightfall_client/src/drivers/blockchain/event_listener_manager.rs index 148c01ba..55d06737 100644 --- a/nightfall_client/src/drivers/blockchain/event_listener_manager.rs +++ b/nightfall_client/src/drivers/blockchain/event_listener_manager.rs @@ -1,20 +1,34 @@ -use crate::drivers::blockchain::nightfall_event_listener::start_event_listener; use crate::ports::contracts::NightfallContract; +use crate::ports::trees::CommitmentTree; +use crate::{ + drivers::blockchain::nightfall_event_listener::start_event_listener, + initialisation::get_db_connection, +}; +use crate::domain::entities::SynchronisationPhase::Synchronized; +use crate::drivers::blockchain::nightfall_event_listener::get_synchronisation_status; +use ark_bn254::Fr as Fr254; use configuration::settings::get_settings; -use log::{info, warn}; +use log::{debug, info, warn}; +use mongodb::Client as MongoClient; use tokio::{ - sync::{OnceCell, RwLock}, + sync::{Mutex, OnceCell, RwLock}, task::JoinHandle, time::{sleep, Duration}, }; // The sole place that holds the listener handle. static LISTENER: OnceCell>>> = OnceCell::const_new(); +// Add a restart lock to prevent concurrent restarts +static RESTART_LOCK: OnceCell> = OnceCell::const_new(); + async fn listener_lock() -> &'static RwLock>> { // Tokio's OnceCell requires an async initializer. LISTENER.get_or_init(|| async { RwLock::new(None) }).await } +async fn restart_lock() -> &'static Mutex<()> { + RESTART_LOCK.get_or_init(|| async { Mutex::new(()) }).await +} // Spawns the actual listener; logs errors; returns JoinHandle<()>. async fn spawn_listener() -> JoinHandle<()> { @@ -33,22 +47,73 @@ pub async fn ensure_running() { let mut guard = lock.write().await; if guard.is_none() { *guard = Some(spawn_listener::().await); - info!("Event listener started."); + info!("Event listener started"); } } /// Abort current (if any) and respawn. pub async fn restart() { - let lock = listener_lock().await; + // Acquire restart lock to prevent concurrent restarts + let _restart_guard = restart_lock().await.lock().await; + + let lock: &'static RwLock>> = listener_lock().await; let mut guard = lock.write().await; if let Some(handle) = guard.take() { warn!("Restarting event listener: aborting current task…"); handle.abort(); + // Wait for the task to actually terminate + let _ = handle.await; // small grace to allow sockets/cursors to unwind - sleep(Duration::from_millis(50)).await; + sleep(Duration::from_millis(100)).await; + } else { + warn!("No event listener running to restart"); + return; + } + + // Clean database and reset trees + { + let db = get_db_connection().await; + let _ = >::reset_tree(db).await; } + // Spawn new listener *guard = Some(spawn_listener::().await); - info!("Event listener restarted."); + + // Drop the listener guard to allow the listener to run + drop(guard); + + // NOW WAIT FOR SYNCHRONIZATION WHILE HOLDING THE RESTART LOCK + let max_wait_time = Duration::from_secs(300); // 5 minutes max + let start_time = std::time::Instant::now(); + + loop { + sleep(Duration::from_secs(5)).await; // Check every 5 seconds + debug!("Checking synchronization status..."); + match get_synchronisation_status::().await { + Ok(status) => { + if status.phase() == Synchronized { + info!("Event listener synchronized successfully"); + break; + } else { + debug!("Still synchronizing... ({:?})", status.phase()); + } + } + Err(e) => { + warn!("Error checking sync status during restart: {e:?}",); + } + } + + // Timeout check + if start_time.elapsed() > max_wait_time { + warn!( + "Restart timeout - releasing lock after {} seconds", + max_wait_time.as_secs() + ); + break; + } + } + + debug!("Restart lock released after synchronization check"); + // Lock is automatically released when _restart_guard goes out of scope } diff --git a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs index a639a12d..89c5d14c 100644 --- a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs +++ b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs @@ -3,11 +3,13 @@ use crate::{ entities::{OnChainTransaction, SynchronisationPhase, SynchronisationStatus}, error::EventHandlerError, }, - driven::db::mongo::{BlockStorageDB, StoredBlock}, - driven::event_handlers::nightfall_event::get_expected_layer2_blocknumber, - drivers::blockchain::nightfall_event_listener::SynchronisationPhase::Synchronized, + driven::{ + db::mongo::{BlockStorageDB, StoredBlock}, + event_handlers::nightfall_event::get_expected_layer2_blocknumber, + }, + drivers::blockchain::event_listener_manager::restart, initialisation::get_db_connection, - ports::{contracts::NightfallContract, trees::CommitmentTree}, + ports::contracts::NightfallContract, services::process_events::process_events, }; use alloy::{ @@ -15,16 +17,14 @@ use alloy::{ rpc::types::Filter, sol_types::{SolEvent, SolEventInterface}, }; -use ark_bn254::Fr as Fr254; -use configuration::{addresses::get_addresses, settings::get_settings}; +use configuration::addresses::get_addresses; use futures::StreamExt; use futures::{future::BoxFuture, FutureExt}; use lib::{ blockchain_client::BlockchainClientConnection, hex_conversion::HexConvertible, initialisation::get_blockchain_client_connection, }; -use log::{debug, warn}; -use mongodb::Client as MongoClient; +use log::{debug, info, warn}; use nightfall_bindings::artifacts::Nightfall; use std::{panic, time::Duration}; use tokio::time::sleep; @@ -43,6 +43,7 @@ pub fn start_event_listener( loop { attempts += 1; log::info!("Client event listener (attempt {attempts})..."); + println!("inside loop to call listen for events on thread"); let result = listen_for_events::(start_block).await; match result { Ok(_) => { @@ -87,14 +88,10 @@ pub async fn listen_for_events( .read() .await .get_client(); - log::info!( + info!( "Listening for events on the Nightfall contract at address: {}", get_addresses().nightfall() ); - - // get the events from the Nightfall contract from the specified start block - - // Subscribe to the combined events filter let events_filter = Filter::new() .address(get_addresses().nightfall()) .event_signature(vec![ @@ -106,15 +103,59 @@ pub async fn listen_for_events( Nightfall::OwnershipTransferred::SIGNATURE_HASH, ]) .from_block(start_block as u64); - - // Subscribe to the combined events filter let events_subscription = blockchain_client - .subscribe_logs(&events_filter) - .await - .map_err(|_| EventHandlerError::NoEventStream)?; + .subscribe_logs(&events_filter) + .await + .map_err(|_| EventHandlerError::NoEventStream)?; let mut events_stream = events_subscription.into_stream(); + info!("Subscribed to events."); + + { + let latest_block = blockchain_client + .get_block_number() + .await + .expect("could not get latest block number"); + + if latest_block >= start_block as u64 { + let past_events = blockchain_client + .get_logs(&events_filter.clone().to_block(latest_block)) + .await + .expect("could not get past events"); + log::info!("Found {} past events to process", past_events.len()); + for evt in past_events { + let event = match Nightfall::NightfallEvents::decode_log(&evt.inner) { + Ok(e) => e, + Err(e) => { + warn!("Failed to decode log: {e:?}"); + continue; // Skip malformed events + } + }; + let result = process_events::(event.data, evt).await; + match result { + Ok(_) => continue, + Err(e) => { + match e { + // we're missing blocks, so we need to re-synchronise + EventHandlerError::MissingBlocks(n) => { + warn!("Missing blocks. Last contiguous block was {n}. Restarting event listener"); + restart::().await; + return Err(EventHandlerError::StreamTerminated); + } + _ => panic!("Error processing event: {e:?}"), + } + } + } + } + } else { + info!( + "Start block {start_block} is greater than latest block {latest_block}. No past events to process.", + ); + } + } + // Subscribe to the combined events filter + while let Some(evt) = events_stream.next().await { // process each event in the stream and handle any errors let event = match Nightfall::NightfallEvents::decode_log(&evt.inner) { @@ -132,7 +173,7 @@ pub async fn listen_for_events( // we're missing blocks, so we need to re-synchronise EventHandlerError::MissingBlocks(n) => { warn!("Missing blocks. Last contiguous block was {n}. Restarting event listener"); - restart_event_listener::(start_block).await; + restart::().await; return Err(EventHandlerError::StreamTerminated); } _ => panic!("Error processing event: {e:?}"), @@ -144,38 +185,6 @@ pub async fn listen_for_events( Err(EventHandlerError::StreamTerminated) } -// We might need to restart the event listener if we fall out of sync and lose blocks -// This does not erase aleady synchronised data -pub async fn restart_event_listener(start_block: usize) -where - N: NightfallContract, -{ - // if we're restarting the event lister, we definitely shouldn't be in sync, so check that's the case - let sync_state = get_synchronisation_status::() - .await - .expect("Could not check synchronisation state") - .phase(); - if sync_state == Synchronized { - panic!("Restarting event listener while synchronised. This should not happen"); - } - let settings = get_settings(); - let max_attempts = settings - .nightfall_client - .max_event_listener_attempts - .unwrap_or(10); - - // clean the database and reset the trees - // this is a bit of a hack, but we need to reset the trees to get them back in sync - // with the blockchain. We should probably do this in a more elegant way, but this works for now - // and we can improve it later - { - let db = get_db_connection().await; - let _ = >::reset_tree(db).await; - } - - start_event_listener::(start_block, max_attempts).await; -} - pub async fn get_synchronisation_status( ) -> Result { let expected_block_number = get_expected_layer2_blocknumber().lock().await; diff --git a/nightfall_client/src/drivers/derive_key.rs b/nightfall_client/src/drivers/derive_key.rs index 97437000..79cec015 100644 --- a/nightfall_client/src/drivers/derive_key.rs +++ b/nightfall_client/src/drivers/derive_key.rs @@ -1,4 +1,4 @@ -use crate::{get_zkp_keys, ports::keys::KeySpending}; +use crate::{ports::keys::KeySpending}; use ark_bn254::Fr as Fr254; use ark_ec::twisted_edwards::Affine as TEAffine; @@ -121,7 +121,7 @@ impl ZKPKeys { .hash(&[root_key, prefix]) .map_err(|_| KeyError::HashError) }); - + let nullifier_key: Fr254 = nullifier_key_bytes?; let zkp_private_key = BJJScalar::from_be_bytes_mod_order( @@ -137,9 +137,6 @@ impl ZKPKeys { zkp_public_key, nullifier_key, }; - // we'll update the lazy static storage of the ZKPKeys here. Eventually, we'll add a URL to enable updates on the fly. - let mut zkpk = get_zkp_keys().lock().expect("Poisoned lock"); - *zkpk = zkp_keys; // this is consumed by the event listener. Ok(zkp_keys) } @@ -150,8 +147,10 @@ impl ZKPKeys { path: &DerivationPath, ) -> Result { let seed = mnemonic.to_seed(""); + println!("Mnemonic seed: {}", hex::encode(&seed)); let root_key_bytes: [u8; 32] = XPrv::derive_from_path(seed, path)?.to_bytes(); let root_key: Fr254 = Fr254::from_be_bytes_mod_order(&root_key_bytes); + println!("Derived root key: {}", root_key); ZKPKeys::new(root_key) } diff --git a/nightfall_client/src/drivers/rest/keys.rs b/nightfall_client/src/drivers/rest/keys.rs index 7b5c48bc..803f020d 100644 --- a/nightfall_client/src/drivers/rest/keys.rs +++ b/nightfall_client/src/drivers/rest/keys.rs @@ -1,7 +1,10 @@ -use bip32::DerivationPath; -use warp::{hyper::StatusCode, path, reject, reply, Filter, Reply}; +use bip32::{DerivationPath, Mnemonic}; +use warp::{hyper::StatusCode, path, reject, reply, Filter, Reply, Rejection}; -use crate::drivers::derive_key::ZKPKeys; +use crate::{ + drivers::derive_key::ZKPKeys, + get_zkp_keys, +}; use super::models::KeyRequest; @@ -15,22 +18,33 @@ pub fn derive_key_mnemonic( ) -> impl Filter + Clone { path!("v1" / "deriveKey") .and(warp::post()) - .and(warp::body::json()) + // make body optional + .and(warp::body::json().map(Some).or_else(|_| async { Ok::<(Option,), Rejection>((None,)) })) .and_then(handle_derive_key) } -pub async fn handle_derive_key(key_request: KeyRequest) -> Result { - let valid_mnemonic = bip32::Mnemonic::new(key_request.mnemonic, Default::default()) - .map_err(|_| reject::custom(BadKeyRequest))?; - let valid_derivation_path: DerivationPath = key_request - .child_path - .parse() - .map_err(|_| reject::custom(BadKeyRequest))?; +pub async fn handle_derive_key(key_request: Option) -> Result { + if let Some(req) = key_request { + // validate mnemonic and path + let valid_mnemonic = Mnemonic::new(req.mnemonic, Default::default()) + .map_err(|_| reject::custom(BadKeyRequest))?; + let valid_derivation_path: DerivationPath = req + .child_path + .parse() + .map_err(|_| reject::custom(BadKeyRequest))?; - if let Ok(key) = ZKPKeys::derive_from_mnemonic(&valid_mnemonic, &valid_derivation_path) { - Ok(reply::with_status(reply::json(&key), StatusCode::OK)) + if let Ok(key) = ZKPKeys::derive_from_mnemonic(&valid_mnemonic, &valid_derivation_path) { + // update the static + let mut zkpk = get_zkp_keys().lock().expect("Poisoned lock"); + *zkpk = key.clone(); // store derived key + Ok(reply::with_status(reply::json(&key), StatusCode::OK)) + } else { + Err(reject::not_found()) + } } else { - Err(reject::not_found()) + // no body -> return existing static keys + let zkpk = get_zkp_keys().lock().expect("Poisoned lock"); + Ok(reply::with_status(reply::json(&*zkpk), StatusCode::OK)) } } diff --git a/nightfall_client/src/lib.rs b/nightfall_client/src/lib.rs index 5c79bf06..111b002d 100644 --- a/nightfall_client/src/lib.rs +++ b/nightfall_client/src/lib.rs @@ -21,13 +21,23 @@ use std::{ path::Path, sync::{Arc, Mutex, OnceLock}, }; +use bip32::{Mnemonic}; +use bip32::DerivationPath; + /// This function is used to retrieve the zkp keys pub fn get_zkp_keys() -> &'static Mutex { static ZKP_KEYS: OnceLock> = OnceLock::new(); - ZKP_KEYS.get_or_init(|| Mutex::new(Default::default())) + ZKP_KEYS.get_or_init(|| + { + let rng = ark_std::rand::thread_rng(); + let mnemonic = Mnemonic::random(rng, Default::default()); + let path: DerivationPath = "m/44'/60'/0'/0/0".parse().expect("failed to parse path"); + let zkp_keys = ZKPKeys::derive_from_mnemonic(&mnemonic, &path).expect("Could not derive ZKP keys from mnemonic"); + Mutex::new(zkp_keys) +} + ) } - /// This function gets the fee token ID based on the current deployment. /// Fee token ID is the keccak256 hash of the zero address and zero, right shifted by 4 bits. pub fn get_fee_token_id() -> Fr254 { diff --git a/nightfall_deployer/Cargo.toml b/nightfall_deployer/Cargo.toml index bfcea3e7..a3156f27 100644 --- a/nightfall_deployer/Cargo.toml +++ b/nightfall_deployer/Cargo.toml @@ -18,8 +18,8 @@ anyhow = "1.0.99" regex = "1.11.2" itertools = "0.10.5" dotenv = "0.15.0" -alloy = { version = "1.0.23", features = ["full"] } -alloy-node-bindings = "1.0.23" +alloy = { version = "1.0.38", features = ["full"] } +alloy-node-bindings = "1.0.38" tokio = { version = "1.45.0", features = ["full"] } ethers-solc = "2.0.14" configuration = { path = "../configuration" } diff --git a/nightfall_proposer/Cargo.toml b/nightfall_proposer/Cargo.toml index 59e335c8..95f293a7 100644 --- a/nightfall_proposer/Cargo.toml +++ b/nightfall_proposer/Cargo.toml @@ -13,7 +13,7 @@ ark-ed-on-bn254 = "0.4.0" ark-ec = { version = "0.4.2", features = ["parallel"] } nightfall_client = { path = "../nightfall_client" } nightfall_bindings = {path = "../nightfall_bindings"} -alloy = { version = "1.0.23", features = ["full"] } +alloy = { version = "1.0.38", features = ["full"] } ark-serialize = "0.4.2" serde = { version = "1.0.219", features = ["derive"] } tokio-stream = "0.1.17" diff --git a/nightfall_proposer/src/drivers/rest/proposers.rs b/nightfall_proposer/src/drivers/rest/proposers.rs index c7256dc9..9ce53f6d 100644 --- a/nightfall_proposer/src/drivers/rest/proposers.rs +++ b/nightfall_proposer/src/drivers/rest/proposers.rs @@ -60,10 +60,7 @@ pub fn add_proposer() -> impl Filter Result { // get a ManageProposers instance - let read_connection = get_blockchain_client_connection() - .await - .read() - .await; + let read_connection = get_blockchain_client_connection().await.read().await; let blockchain_client = read_connection.get_client(); let caller = read_connection.get_address(); let client = blockchain_client.root(); @@ -104,10 +101,7 @@ pub fn remove_proposer() -> impl Filter Result { // get a ManageProposers instance - let read_connection = get_blockchain_client_connection() - .await - .read() - .await; + let read_connection = get_blockchain_client_connection().await.read().await; let blockchain_client = read_connection.get_client(); let signer_address = read_connection.get_address(); let client = blockchain_client.root(); @@ -170,10 +164,7 @@ pub fn withdraw() -> impl Filter Result { // get a ManageProposers instance - let read_connection = get_blockchain_client_connection() - .await - .read() - .await; + let read_connection = get_blockchain_client_connection().await.read().await; let blockchain_client = read_connection.get_client(); let caller = read_connection.get_address(); let proposer_manager = RoundRobin::new(get_addresses().round_robin, blockchain_client.root()); diff --git a/nightfall_test/Cargo.toml b/nightfall_test/Cargo.toml index 42a3b240..728802c7 100644 --- a/nightfall_test/Cargo.toml +++ b/nightfall_test/Cargo.toml @@ -22,13 +22,13 @@ url = "2.5.4" ark-ff = { version = "0.4.2", features = ["parallel"] } serde_json = "1.0.140" k256 = "0.13.4" -alloy-node-bindings = { version = "1.0.23" } +alloy-node-bindings = { version = "1.0.38" } tokio = { version = "1.45.0", features = ["full"] } ark-serialize = "0.4.2" ark-std = { version = "0.4.0", default-features = false } arkworks-utils = "1.0.1" ark-bn254 = "0.4.0" -alloy = { version = "1.0.23", features = ["full"] } +alloy = { version = "1.0.38", features = ["full"] } hex = "0.4.3" log = "0.4.27" sha2 = "0.10.9" diff --git a/nightfall_test/src/main.rs b/nightfall_test/src/main.rs index 474ff62c..8ac5fb0e 100644 --- a/nightfall_test/src/main.rs +++ b/nightfall_test/src/main.rs @@ -49,10 +49,10 @@ async fn main() -> Result<(), JoinError> { let result = tasks.join_next().await; // wait for any task to finish match result { - Some(Ok(_)) => { + Some(Ok(_)) => { info!("Nightfall tests completed successfully."); return Ok(()); - }, + } Some(Err(e)) => { error!("Nightfall tests failed with error: {e:?}"); return Err(e); diff --git a/webhook.py b/webhook.py new file mode 100644 index 00000000..04a86a98 --- /dev/null +++ b/webhook.py @@ -0,0 +1,15 @@ +from flask import Flask, request +from datetime import datetime +app = Flask(__name__) +@app.route("/webhook", methods=["POST"]) +def webhook(): + data = request.get_json(silent=True) + # Heuristics to classify payload + kind = "TransactionEvent" if isinstance(data, dict) and "uuid" in data else \ + "BlockchainEvent" if isinstance(data, dict) and "l1_txn_hash" in data else \ + "Unknown" + print(f"[{datetime.utcnow().isoformat()}Z] {kind}: {data}") + return "", 200 +if __name__ == "__main__": + # Bind to 0.0.0.0 so Docker containers can reach it + app.run(host="0.0.0.0", port=8080)