From 5f7f7b68c65f102ccad94af05c7764f689543f07 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Wed, 1 Oct 2025 10:22:13 +0100 Subject: [PATCH 01/11] setup plume client --- doc/plume_testnet_client_playbook.md | 104 +++++++ docker-compose.yml | 11 +- lib/src/validate_certificate.rs | 11 +- nightfall.toml | 88 +++++- nightfall_client/Dockerfile | 6 +- .../contract_functions/nightfall_contract.rs | 136 +++++++-- .../contract_functions/token_contracts.rs | 264 ++++++++++++++---- nightfall_test/src/main.rs | 4 +- 8 files changed, 521 insertions(+), 103 deletions(-) create mode 100644 doc/plume_testnet_client_playbook.md diff --git a/doc/plume_testnet_client_playbook.md b/doc/plume_testnet_client_playbook.md new file mode 100644 index 00000000..b31a30a9 --- /dev/null +++ b/doc/plume_testnet_client_playbook.md @@ -0,0 +1,104 @@ +# 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) Specify Nightfall contract addresses + +Create the TOML config folder: + +```bash +mkdir -p configuration/toml +``` + +Create `configuration/toml/addresses.toml` with the following content: + +```toml +nightfall = "0xe407c6d6D86178Dd3Bba8596bf554f0C2624A2Ab" +round_robin = "0x955F325CBd3C664333d12cceaAE714089fdF7a84" +x509 = "0xF12b0578237Ea2479Ec97BB5bbd69a52D828F451" +``` +These are pre-deployed contracts addresses on plume testnet. + +--- + +## 4) 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). + +--- + +## 5) 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="0xe407c6d6D86178Dd3Bba8596bf554f0C2624A2Ab" +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. Webhook is essential to de-escrow fund back to host chain. +Please remove all comments (beginning with `#`) in your local.env file. + +--- + +## 6) 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 +``` + +## 7) 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 48200996..33ed0ba9 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} @@ -550,7 +549,7 @@ services: condition: service_healthy volumes: - # mongodb_client_data: + mongodb_client_data: # mongodb_client2_data: # mongodb_proposer_data: address_data: diff --git a/lib/src/validate_certificate.rs b/lib/src/validate_certificate.rs index 4f0ebab2..e02f1ff1 100644 --- a/lib/src/validate_certificate.rs +++ b/lib/src/validate_certificate.rs @@ -191,12 +191,10 @@ async fn validate_certificate( oid_group: u32, sender_address: Address, ) -> Result<(), Box> { - let client = get_blockchain_client_connection() - .await - .read() - .await - .get_client(); - let blockchain_client = client.root(); + let real_connection = get_blockchain_client_connection().await.read().await; + let provider = real_connection.get_client(); + let blockchain_client = provider.root(); + let caller = real_connection.get_address(); let x509_instance = X509::new(x509_contract_address, blockchain_client); @@ -218,6 +216,7 @@ async fn validate_certificate( let tx_receipt = x509_instance .validateCertificate(certificate_args) + .from(caller) .send() .await .map_err(|e| { 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_client/Dockerfile b/nightfall_client/Dockerfile index b6bda3c8..6864fc75 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 4a16545b..4b905f17 100644 --- a/nightfall_client/src/driven/contract_functions/nightfall_contract.rs +++ b/nightfall_client/src/driven/contract_functions/nightfall_contract.rs @@ -19,7 +19,7 @@ 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, }; @@ -68,6 +68,50 @@ impl NightfallContract for Nightfall::NightfallCalls { } else { fee + fee + deposit_fee }; + // let call = contract + // .escrow_funds( + // solidity_fee.0, + // solidity_token_address.0, + // solidity_token_id.0, + // solidity_value.0, + // solidity_secret_hash.0, + // 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") + // .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 = 5000000u64; let call = contract .escrow_funds( solidity_fee.0, @@ -78,23 +122,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| { @@ -163,23 +207,59 @@ 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()); + + // 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 71d8e4d5..2759917c 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::BigInteger256; +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,28 +38,65 @@ impl TokenContract for IERC20::IERC20Calls { 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(); - // 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 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(), ) @@ -93,27 +131,63 @@ 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(); - // Send the transaction with explicit `from` - let tx_receipt = 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) + // .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 = 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(), ) @@ -127,10 +201,14 @@ impl TokenContract for IERC721::IERC721Calls { impl TokenContract for IERC1155::IERC1155Calls { async fn set_approval( erc_address: Fr254, - _value: Fr254, - _token_id: BigInteger256, + value: Fr254, + token_id: BigInteger256, ) -> Result<(), TokenContractError> { - // For ERC-1155 we use setApprovalForAll; value/token_id are not relevant to this call. + 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(), + )); + } // Type conversions let solidity_erc_address = Addr::try_from(erc_address)?; @@ -142,29 +220,67 @@ impl TokenContract for IERC1155::IERC1155Calls { 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(); - // 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 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; - debug!("ERC1155 approval tx mined, from: {:?}", tx_receipt.from); + // 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.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(), ) @@ -192,6 +308,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:?}"); @@ -199,26 +320,57 @@ impl TokenContract for IERC3525::IERC3525Calls { // 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_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); From 22e6a816ef86883646e48c0c153e2596746e5a27 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Wed, 1 Oct 2025 10:46:27 +0100 Subject: [PATCH 02/11] fix merge conflicts --- lib/src/validate_certificate.rs | 6 +---- .../contract_functions/nightfall_contract.rs | 21 +++++------------- .../contract_functions/token_contracts.rs | 22 ++++++++++++++----- .../src/drivers/rest/proposers.rs | 15 +++---------- 4 files changed, 25 insertions(+), 39 deletions(-) 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/nightfall_client/src/driven/contract_functions/nightfall_contract.rs b/nightfall_client/src/driven/contract_functions/nightfall_contract.rs index 779924a6..afc2bc3c 100644 --- a/nightfall_client/src/driven/contract_functions/nightfall_contract.rs +++ b/nightfall_client/src/driven/contract_functions/nightfall_contract.rs @@ -24,7 +24,7 @@ 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 { @@ -68,8 +68,6 @@ impl NightfallContract for Nightfall::NightfallCalls { } else { fee + fee + deposit_fee }; -<<<<<<< HEAD -======= /* 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| { // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) @@ -80,7 +78,6 @@ impl NightfallContract for Nightfall::NightfallCalls { // let max_fee_per_gas = gas_price * 2; // let max_priority_fee_per_gas = gas_price; // let gas_limit = 5000000u64; ->>>>>>> origin/master // let call = contract // .escrow_funds( // solidity_fee.0, @@ -91,7 +88,6 @@ impl NightfallContract for Nightfall::NightfallCalls { // token_type.into(), // ) // .value(Uint256::from(total_fee).0) -<<<<<<< HEAD // .from(signer.address()); // // Send transaction directly through Alloy @@ -109,7 +105,6 @@ impl NightfallContract for Nightfall::NightfallCalls { // } // }) // .expect("Error sending transaction") -======= // .nonce(nonce) // .gas(gas_limit) // .max_fee_per_gas(max_fee_per_gas) @@ -125,13 +120,11 @@ impl NightfallContract for Nightfall::NightfallCalls { // .map_err(|e| { // NightfallContractError::EscrowError(format!("Error getting receipt: {e}")) // })? ->>>>>>> origin/master // .get_receipt() // .await // .map_err(|e| { // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) // })?; -<<<<<<< HEAD let nonce = client .get_transaction_count(signer.address()) .await @@ -144,8 +137,7 @@ impl NightfallContract for Nightfall::NightfallCalls { let max_fee_per_gas = gas_price * 2; let max_priority_fee_per_gas = gas_price; let gas_limit = 5000000u64; -======= ->>>>>>> origin/master + let call = contract .escrow_funds( solidity_fee.0, @@ -241,11 +233,9 @@ impl NightfallContract for Nightfall::NightfallCalls { let contract = Nightfall::new(get_addresses().nightfall(), client.clone()); -<<<<<<< HEAD // 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| { @@ -280,10 +270,9 @@ impl NightfallContract for Nightfall::NightfallCalls { // NightfallContractError::EscrowError(format!("Transaction unsuccesful: {e}")) // })?; - let call = contract - .descrow_funds(decode_data, token_type.into()) - .from(signer.address()); ->>>>>>> origin/master + // let call = contract + // .descrow_funds(decode_data, token_type.into()) + // .from(signer.address()); // let receipt = call // .send() diff --git a/nightfall_client/src/driven/contract_functions/token_contracts.rs b/nightfall_client/src/driven/contract_functions/token_contracts.rs index 82b6e013..05468604 100644 --- a/nightfall_client/src/driven/contract_functions/token_contracts.rs +++ b/nightfall_client/src/driven/contract_functions/token_contracts.rs @@ -94,7 +94,10 @@ impl TokenContract for IERC20::IERC20Calls { // ) // })?; - debug!("ERC20 approval tx mined, from: {:?}", tx_receipt.as_ref().unwrap().from); + debug!( + "ERC20 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( @@ -185,7 +188,10 @@ impl TokenContract for IERC721::IERC721Calls { .get_receipt() .await; - debug!("ERC721 approval tx mined, from: {:?}", tx_receipt.as_ref().unwrap().from); + debug!( + "ERC721 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( @@ -278,7 +284,10 @@ impl TokenContract for IERC1155::IERC1155Calls { // ) // })?; - debug!("ERC1155 approval tx mined, from: {:?}", tx_receipt.as_ref().unwrap().from); + debug!( + "ERC1155 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( @@ -341,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}")) @@ -349,7 +357,6 @@ 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`. @@ -402,7 +409,10 @@ impl TokenContract for IERC3525::IERC3525Calls { .get_receipt() .await; - debug!("ERC3525 approval tx mined, from: {:?}", tx_receipt.as_ref().unwrap().from); + debug!( + "ERC3525 approval tx mined, from: {:?}", + tx_receipt.as_ref().unwrap().from + ); if !tx_receipt.unwrap().status() { return Err(BlockchainClientConnectionError::ProviderError( 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()); From e5e326ed2e63799f8189818ddebb5011bd48665b Mon Sep 17 00:00:00 2001 From: jiajieey Date: Wed, 1 Oct 2025 13:12:39 +0100 Subject: [PATCH 03/11] update (dockerfile): comment copying proving_key --- nightfall_client/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nightfall_client/Dockerfile b/nightfall_client/Dockerfile index 6864fc75..a0e4be79 100644 --- a/nightfall_client/Dockerfile +++ b/nightfall_client/Dockerfile @@ -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/ +# 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 \ From 09ba16d4384f8d18f987c524a65864e59c95af6d Mon Sep 17 00:00:00 2001 From: jiajieey Date: Wed, 1 Oct 2025 13:28:45 +0100 Subject: [PATCH 04/11] update(client playbook): nno need to add addresses.toml --- doc/plume_testnet_client_playbook.md | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/doc/plume_testnet_client_playbook.md b/doc/plume_testnet_client_playbook.md index b31a30a9..05b58099 100644 --- a/doc/plume_testnet_client_playbook.md +++ b/doc/plume_testnet_client_playbook.md @@ -23,26 +23,7 @@ docker system prune -a --volumes --- -## 3) Specify Nightfall contract addresses - -Create the TOML config folder: - -```bash -mkdir -p configuration/toml -``` - -Create `configuration/toml/addresses.toml` with the following content: - -```toml -nightfall = "0xe407c6d6D86178Dd3Bba8596bf554f0C2624A2Ab" -round_robin = "0x955F325CBd3C664333d12cceaAE714089fdF7a84" -x509 = "0xF12b0578237Ea2479Ec97BB5bbd69a52D828F451" -``` -These are pre-deployed contracts addresses on plume testnet. - ---- - -## 4) Wallet setup: MetaMask + Plume testnet +## 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) @@ -50,7 +31,7 @@ These are pre-deployed contracts addresses on plume testnet. --- -## 5) Create `local.env` +## 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. @@ -80,7 +61,7 @@ Please remove all comments (beginning with `#`) in your local.env file. --- -## 6) Build and run the Nightfall client +## 5) Build and run the Nightfall client From the repo root: @@ -90,7 +71,7 @@ docker compose --profile indie-client build docker compose --profile indie-client --env-file local.env up ``` -## 7) Deployment script +## 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` From 39c715ca4a3df38088212d1c6fb9f1626798c567 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Wed, 1 Oct 2025 13:53:24 +0100 Subject: [PATCH 05/11] add local webhook --- doc/plume_testnet_client_playbook.md | 2 +- webhook.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 webhook.py diff --git a/doc/plume_testnet_client_playbook.md b/doc/plume_testnet_client_playbook.md index 05b58099..b09a074f 100644 --- a/doc/plume_testnet_client_playbook.md +++ b/doc/plume_testnet_client_playbook.md @@ -56,7 +56,7 @@ 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. Webhook is essential to de-escrow fund back to host chain. +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. --- 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) From 234988d27638a7be5ad9b5c1946c155427874b43 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Thu, 2 Oct 2025 07:58:09 +0100 Subject: [PATCH 06/11] ignore local webhook docker --- .dockerignore | 3 +++ 1 file changed, 3 insertions(+) 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 From 9288e4e0af0bed00d7b1618b89318e0fbc309ac5 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Thu, 2 Oct 2025 10:29:48 +0100 Subject: [PATCH 07/11] update restart --- .../blockchain/event_listener_manager.rs | 30 ++++++- .../blockchain/nightfall_event_listener.rs | 90 ++++++++++++++----- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/nightfall_client/src/drivers/blockchain/event_listener_manager.rs b/nightfall_client/src/drivers/blockchain/event_listener_manager.rs index 148c01ba..0ff69433 100644 --- a/nightfall_client/src/drivers/blockchain/event_listener_manager.rs +++ b/nightfall_client/src/drivers/blockchain/event_listener_manager.rs @@ -1,4 +1,5 @@ -use crate::drivers::blockchain::nightfall_event_listener::start_event_listener; +use crate::ports::trees::CommitmentTree; +use crate::{drivers::blockchain::nightfall_event_listener::start_event_listener, initialisation::get_db_connection}; use crate::ports::contracts::NightfallContract; use configuration::settings::get_settings; @@ -8,6 +9,10 @@ use tokio::{ task::JoinHandle, time::{sleep, Duration}, }; +use mongodb::Client as MongoClient; +use crate::domain::entities::SynchronisationPhase::Synchronized; +use crate::drivers::blockchain::nightfall_event_listener::get_synchronisation_status; +use ark_bn254::Fr as Fr254; // The sole place that holds the listener handle. static LISTENER: OnceCell>>> = OnceCell::const_new(); @@ -49,6 +54,29 @@ pub async fn restart() { sleep(Duration::from_millis(50)).await; } + // 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; + } + *guard = Some(spawn_listener::().await); info!("Event listener restarted."); } diff --git a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs index a639a12d..a52ea656 100644 --- a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs +++ b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs @@ -3,9 +3,8 @@ 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, nightfall_event_listener::SynchronisationPhase::Synchronized}, initialisation::get_db_connection, ports::{contracts::NightfallContract, trees::CommitmentTree}, services::process_events::process_events, @@ -91,30 +90,77 @@ pub async fn listen_for_events( "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![ - Nightfall::BlockProposed::SIGNATURE_HASH, - Nightfall::DepositEscrowed::SIGNATURE_HASH, - Nightfall::Initialized::SIGNATURE_HASH, - Nightfall::Upgraded::SIGNATURE_HASH, - Nightfall::AuthoritiesUpdated::SIGNATURE_HASH, - Nightfall::OwnershipTransferred::SIGNATURE_HASH, - ]) - .from_block(start_block as u64); - + .address(get_addresses().nightfall()) + .event_signature(vec![ + Nightfall::BlockProposed::SIGNATURE_HASH, + Nightfall::DepositEscrowed::SIGNATURE_HASH, + Nightfall::Initialized::SIGNATURE_HASH, + Nightfall::Upgraded::SIGNATURE_HASH, + Nightfall::AuthoritiesUpdated::SIGNATURE_HASH, + Nightfall::OwnershipTransferred::SIGNATURE_HASH, + ]) + .from_block(start_block as u64); +{ + + 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"); + // if !past_events.is_empty() { + // set_synching + // } + 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 { + println!("Start block {} is greater than latest block {}. No past events to process.", start_block, latest_block); + } +} + // Subscribe to the combined events filter let events_subscription = blockchain_client .subscribe_logs(&events_filter) .await .map_err(|_| EventHandlerError::NoEventStream)?; - + // let events_subscription = blockchain_client.subscribe(params![events_filter]) + // .await + // .map_err(|_| EventHandlerError::NoEventStream)?; + // println!("Subscribed to events {:?}", events_subscription); + let mut events_stream = events_subscription.into_stream(); - + println!("Subscribed to events."); + 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 +178,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:?}"), @@ -140,7 +186,7 @@ pub async fn listen_for_events( } } } - + Err(EventHandlerError::StreamTerminated) } From 1c45d1ead22cea4ab8ff44dac6b3f4281583de14 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Thu, 2 Oct 2025 14:14:19 +0100 Subject: [PATCH 08/11] fix(client): add restart lock --- nightfall_client/src/driven/queue.rs | 11 +- .../blockchain/event_listener_manager.rs | 97 +++++++---- .../blockchain/nightfall_event_listener.rs | 159 +++++++----------- 3 files changed, 132 insertions(+), 135 deletions(-) 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 0ff69433..55d06737 100644 --- a/nightfall_client/src/drivers/blockchain/event_listener_manager.rs +++ b/nightfall_client/src/drivers/blockchain/event_listener_manager.rs @@ -1,25 +1,34 @@ -use crate::ports::trees::CommitmentTree; -use crate::{drivers::blockchain::nightfall_event_listener::start_event_listener, initialisation::get_db_connection}; 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}, }; -use mongodb::Client as MongoClient; -use crate::domain::entities::SynchronisationPhase::Synchronized; -use crate::drivers::blockchain::nightfall_event_listener::get_synchronisation_status; -use ark_bn254::Fr as Fr254; // 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<()> { @@ -38,45 +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; } - // 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 + // 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 a52ea656..01a1f45d 100644 --- a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs +++ b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs @@ -3,10 +3,13 @@ use crate::{ entities::{OnChainTransaction, SynchronisationPhase, SynchronisationStatus}, error::EventHandlerError, }, - driven::{db::mongo::{BlockStorageDB, StoredBlock}, event_handlers::nightfall_event::get_expected_layer2_blocknumber}, - drivers::blockchain::{event_listener_manager::restart, 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::{ @@ -14,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; @@ -42,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(_) => { @@ -86,81 +88,74 @@ 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() ); - + let events_filter = Filter::new() - .address(get_addresses().nightfall()) - .event_signature(vec![ - Nightfall::BlockProposed::SIGNATURE_HASH, - Nightfall::DepositEscrowed::SIGNATURE_HASH, - Nightfall::Initialized::SIGNATURE_HASH, - Nightfall::Upgraded::SIGNATURE_HASH, - Nightfall::AuthoritiesUpdated::SIGNATURE_HASH, - Nightfall::OwnershipTransferred::SIGNATURE_HASH, - ]) - .from_block(start_block as u64); -{ - - 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)) + .address(get_addresses().nightfall()) + .event_signature(vec![ + Nightfall::BlockProposed::SIGNATURE_HASH, + Nightfall::DepositEscrowed::SIGNATURE_HASH, + Nightfall::Initialized::SIGNATURE_HASH, + Nightfall::Upgraded::SIGNATURE_HASH, + Nightfall::AuthoritiesUpdated::SIGNATURE_HASH, + Nightfall::OwnershipTransferred::SIGNATURE_HASH, + ]) + .from_block(start_block as u64); + { + let latest_block = blockchain_client + .get_block_number() .await - .expect("could not get past events"); - // if !past_events.is_empty() { - // set_synching - // } - 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); + .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:?}"), } - _ => panic!("Error processing event: {e:?}"), } } } + } else { + info!( + "Start block {start_block} is greater than latest block {latest_block}. No past events to process.", + ); } - } else { - println!("Start block {} is greater than latest block {}. No past events to process.", start_block, latest_block); } -} - + // Subscribe to the combined events filter let events_subscription = blockchain_client .subscribe_logs(&events_filter) .await .map_err(|_| EventHandlerError::NoEventStream)?; - // let events_subscription = blockchain_client.subscribe(params![events_filter]) - // .await - // .map_err(|_| EventHandlerError::NoEventStream)?; - // println!("Subscribed to events {:?}", events_subscription); - + let mut events_stream = events_subscription.into_stream(); - println!("Subscribed to events."); - + info!("Subscribed to events."); + 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) { @@ -186,40 +181,8 @@ 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; + Err(EventHandlerError::StreamTerminated) } pub async fn get_synchronisation_status( From c9ddcad57523d05dcbfb901047f2c3f1e9bb9649 Mon Sep 17 00:00:00 2001 From: Swati Rawal Date: Fri, 3 Oct 2025 12:33:50 +0100 Subject: [PATCH 09/11] key changes --- nightfall_client/src/drivers/derive_key.rs | 9 +++-- nightfall_client/src/drivers/rest/keys.rs | 42 ++++++++++++++-------- nightfall_client/src/lib.rs | 14 ++++++-- 3 files changed, 44 insertions(+), 21 deletions(-) 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 { From 4c1632078ec43735ca2a74293baf5d832ab58c61 Mon Sep 17 00:00:00 2001 From: jiajieey Date: Mon, 6 Oct 2025 13:40:05 +0100 Subject: [PATCH 10/11] change nf address in playbook --- doc/plume_testnet_client_playbook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/plume_testnet_client_playbook.md b/doc/plume_testnet_client_playbook.md index b09a074f..988e26f3 100644 --- a/doc/plume_testnet_client_playbook.md +++ b/doc/plume_testnet_client_playbook.md @@ -43,7 +43,7 @@ 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="0xe407c6d6D86178Dd3Bba8596bf554f0C2624A2Ab" +NIGHTFALL_ADDRESS="0xf86806f5eb3ae6cb08fa2e5ad23bf1ba7b2d7ce3" NF4_SIGNING_KEY="0x..." # same as your private key WEBHOOK_URL= AZURE_VAULT_URL= From 4e84c587456f692848047b9f20c7c94b5acab0b8 Mon Sep 17 00:00:00 2001 From: Swati Rawal Date: Thu, 9 Oct 2025 13:31:29 +0100 Subject: [PATCH 11/11] add a restart for blockchain client connection --- Cargo.lock | 116 +++++++++--------- configuration/Cargo.toml | 2 +- lib/Cargo.toml | 2 +- lib/src/lib.rs | 22 +++- lib/src/wallets.rs | 108 ++++++++++++++-- nightfall_bindings/Cargo.toml | 2 +- nightfall_client/Cargo.toml | 2 +- .../blockchain/nightfall_event_listener.rs | 18 +-- nightfall_deployer/Cargo.toml | 4 +- nightfall_proposer/Cargo.toml | 2 +- nightfall_test/Cargo.toml | 4 +- 11 files changed, 193 insertions(+), 89 deletions(-) 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/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/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_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/src/drivers/blockchain/nightfall_event_listener.rs b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs index 01a1f45d..89c5d14c 100644 --- a/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs +++ b/nightfall_client/src/drivers/blockchain/nightfall_event_listener.rs @@ -92,7 +92,6 @@ pub async fn listen_for_events( "Listening for events on the Nightfall contract at address: {}", get_addresses().nightfall() ); - let events_filter = Filter::new() .address(get_addresses().nightfall()) .event_signature(vec![ @@ -104,6 +103,14 @@ pub async fn listen_for_events( Nightfall::OwnershipTransferred::SIGNATURE_HASH, ]) .from_block(start_block as u64); + let events_subscription = blockchain_client + .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() @@ -148,14 +155,7 @@ pub async fn listen_for_events( } // Subscribe to the combined events filter - let events_subscription = blockchain_client - .subscribe_logs(&events_filter) - .await - .map_err(|_| EventHandlerError::NoEventStream)?; - - let mut events_stream = events_subscription.into_stream(); - info!("Subscribed to events."); - + 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) { 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_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"