diff --git a/.env b/.env index f2c9566..c07e5a0 100644 --- a/.env +++ b/.env @@ -39,11 +39,11 @@ ESPRESSO_SEQUENCER_L1_PROVIDER=http://zkevm-mock-l1-network:$ESPRESSO_ZKEVM_L1_P ESPRESSO_SEQUENCER_QUERY_SERVICE_URL=http://sequencer0:$ESPRESSO_SEQUENCER_API_PORT # Config shared between both zkevm-nodes -ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN="" -ESPRESSO_ZKEVM_FAUCET_GRANT_AMOUNT_ETHERS=1000 -ESPRESSO_ZKEVM_FAUCET_MNEMONIC="test test test test test test test test test test test junk" -ESPRESSO_ZKEVM_FAUCET_NUM_CLIENTS=1 -ESPRESSO_ZKEVM_FAUCET_TRANSACTION_TIMEOUT_SECS=300 +ESPRESSO_DISCORD_FAUCET_DISCORD_TOKEN="" +ESPRESSO_DISCORD_FAUCET_GRANT_AMOUNT_ETHERS=1000 +ESPRESSO_DISCORD_FAUCET_MNEMONIC="test test test test test test test test test test test junk" +ESPRESSO_DISCORD_FAUCET_NUM_CLIENTS=1 +ESPRESSO_DISCORD_FAUCET_TRANSACTION_TIMEOUT_SECS=300 ESPRESSO_ZKEVM_IGNORE_GEN_BLOCK_NUMBER_CHECK=1 ESPRESSO_ZKEVM_ETH_TX_MANAGER_POLLING_INTERVAL="1s" diff --git a/.github/workflows/build_static.yml b/.github/workflows/build_static.yml index 14a1004..e603951 100644 --- a/.github/workflows/build_static.yml +++ b/.github/workflows/build_static.yml @@ -55,7 +55,6 @@ jobs: name: x86_64-unknown-linux-musl-services path: | target/x86_64-unknown-linux-musl/release/polygon-zkevm-adaptor - target/x86_64-unknown-linux-musl/release/faucet static-dockers: runs-on: ubuntu-latest @@ -96,19 +95,3 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.polygon-zkevm-adaptor.outputs.tags }} labels: ${{ steps.polygon-zkevm-adaptor.outputs.labels }} - - - name: Generate faucet docker metadata - uses: docker/metadata-action@v4 - id: faucet - with: - images: ghcr.io/espressosystems/espresso-polygon-zkevm-demo/faucet - - - name: Build and push faucet docker - uses: docker/build-push-action@v4 - with: - context: ./ - file: ./docker/faucet.Dockerfile - platforms: linux/amd64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.faucet.outputs.tags }} - labels: ${{ steps.faucet.outputs.labels }} diff --git a/Cargo.lock b/Cargo.lock index 42eadc7..3fbf4d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1127,22 +1127,6 @@ dependencies = [ "tungstenite 0.15.0", ] -[[package]] -name = "async-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" -dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite 0.2.10", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite 0.17.3", - "webpki-roots 0.22.6", -] - [[package]] name = "async_io_stream" version = "0.3.3" @@ -3077,7 +3061,6 @@ dependencies = [ "bytes 1.4.0", "enr", "ethers-core", - "futures-channel", "futures-core", "futures-timer", "futures-util", @@ -3192,30 +3175,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" -[[package]] -name = "faucet" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-compatibility-layer", - "async-std", - "clap", - "ethers", - "futures", - "polygon-zkevm-adaptor", - "portpicker", - "regex", - "sequencer-utils", - "serde", - "serenity", - "surf-disco 0.4.1 (git+https://github.com/EspressoSystems/surf-disco?tag=v0.4.2)", - "thiserror", - "tide-disco 0.4.1 (git+https://github.com/EspressoSystems/tide-disco?tag=v0.4.2)", - "toml 0.7.6", - "tracing", - "url", -] - [[package]] name = "femme" version = "2.2.1" @@ -4179,7 +4138,7 @@ dependencies = [ "hyper", "rustls 0.21.5", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] @@ -5962,15 +5921,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-multimap" version = "0.4.3" @@ -6811,7 +6761,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "once_cell", "percent-encoding", "pin-project-lite 0.2.10", @@ -6821,13 +6770,11 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls 0.24.1", - "tokio-util", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", "webpki-roots 0.22.6", "winreg 0.10.1", @@ -7334,16 +7281,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.188" @@ -7473,34 +7410,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serenity" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d007dc45584ecc47e791f2a9a7cf17bf98ac386728106f111159c846d624be3f" -dependencies = [ - "async-trait", - "async-tungstenite 0.17.2", - "base64 0.13.1", - "bitflags 1.3.2", - "bytes 1.4.0", - "cfg-if 1.0.0", - "flate2", - "futures", - "mime", - "mime_guess", - "percent-encoding", - "reqwest", - "serde", - "serde-value", - "serde_json", - "time 0.3.28", - "tokio", - "tracing", - "typemap_rev", - "url", -] - [[package]] name = "sha-1" version = "0.9.8" @@ -7514,17 +7423,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.6.1" @@ -7813,7 +7711,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1", ] [[package]] @@ -8484,7 +8382,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "sha-1 0.9.8", + "sha-1", "tide", ] @@ -8605,32 +8503,9 @@ dependencies = [ "pin-project-lite 0.2.10", "signal-hook-registry", "socket2 0.4.9", - "tokio-macros", "windows-sys", ] -[[package]] -name = "tokio-macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -8662,7 +8537,7 @@ dependencies = [ "log", "rustls 0.21.5", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tungstenite 0.19.0", "webpki-roots 0.23.1", ] @@ -8912,7 +8787,7 @@ dependencies = [ "input_buffer", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1", "thiserror", "url", "utf-8", @@ -8931,33 +8806,12 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1", "thiserror", "url", "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64 0.13.1", - "byteorder", - "bytes 1.4.0", - "http", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.20.8", - "sha-1 0.10.1", - "thiserror", - "url", - "utf-8", - "webpki", -] - [[package]] name = "tungstenite" version = "0.19.0" @@ -8979,12 +8833,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "typemap_rev" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" - [[package]] name = "typenum" version = "1.16.0" @@ -9286,19 +9134,6 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" -[[package]] -name = "wasm-streams" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.64" diff --git a/Cargo.toml b/Cargo.toml index 6b2360b..01bbb4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ - "faucet", "gen-bindings", "keygen", "polygon-zkevm-adaptor", diff --git a/docker/faucet.Dockerfile b/docker/faucet.Dockerfile deleted file mode 100644 index cf66494..0000000 --- a/docker/faucet.Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM ubuntu:jammy - -RUN apt-get update \ -&& apt-get install -y curl wait-for-it \ -&& rm -rf /var/lib/apt/lists/* - -COPY target/x86_64-unknown-linux-musl/release/faucet /bin/faucet -RUN chmod +x /bin/faucet - -CMD [ "/bin/faucet"] diff --git a/faucet/Cargo.toml b/faucet/Cargo.toml deleted file mode 100644 index 1b43ee5..0000000 --- a/faucet/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "faucet" -version = "0.1.0" -authors = ["Espresso Systems "] -edition = "2021" -license = "GPL-3.0-or-later" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.71" -async-compatibility-layer = { git = "https://github.com/EspressoSystems/async-compatibility-layer", tag = "1.3.0", features = [ - "logging-utils", - "async-std-executor", - "channel-async-std", -] } -async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } -clap = "4.3.9" -ethers = { version = "2.0.7", features = ["ws"] } -futures = "0.3.28" -portpicker = "0.1.1" -regex = "1.8.4" -serde = "1.0.164" -serenity = { version = "0.11", default-features = false, features = [ - "client", - "gateway", - "rustls_backend", - "model", -] } -surf-disco = { git = "https://github.com/EspressoSystems/surf-disco", tag = "v0.4.2" } -thiserror = "1.0.40" -tide-disco = { git = "https://github.com/EspressoSystems/tide-disco", tag = "v0.4.2" } -toml = "0.7" -tracing = "0.1.37" -url = "2.4.0" - -[dev-dependencies] -polygon-zkevm-adaptor = { path = "../polygon-zkevm-adaptor", features = [ - "testing", -] } -sequencer-utils = { git = "https://github.com/EspressoSystems/espresso-sequencer.git" } diff --git a/faucet/README.md b/faucet/README.md deleted file mode 100644 index a14aa2b..0000000 --- a/faucet/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Demo Faucet - -This faucet sends funds to users that request it in a Discord channel. - -## Usage -The easiest way to run the faucet is to run it against an anvil node: - -``` -anvil -cargo run --bin faucet -- \ - --mnemonic "test test test test test test test test test test test junk" \ - --faucet-grant-amount 1 --provider-url ws://localhost:8545 - -curl -X POST localhost:8111/faucet/request/0x1234567890123456789012345678901234567890 -``` - -You can also run `just demo` to use the faucet with the zkevm-node. The the web -faucet will also be available listening at `http://localhost:8111`. - -## Discord config - -Note, for testing a throwaway private server is recommended. It can be created -by clicking the `+` sign in the left pane in the Discord web app. To add a -channel for the faucet click `+` next to `TEXT CHANNELS`. - -1. Create a Discord app on https://discord.com/developers/applications. -1. On the application page, in the left pane, click `Bot`. -1. Click `Reset Token` and save the token. This will later be the value of the - env var `ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN`. -1. Disable `PUBLIC BOT`. -1. Enable `MESSAGE CONTENT INTENT`. -1. Click `Save Changes`. -1. In the left pane, click `OAuth2` -> `URL Generator`. -1. For `SCOPES` tick `bot`. -1. For `BOT PERMISSIONS` -> `TEXT PERMISSIONS` tick `Send Messages`. -1. At the bottom, copy the generated URL and open it in the browser. -1. Select the server to add the bot to, then hit `Continue`, then `Authorize`. -1. Navigate to the Discord server page. Go to `Server Settings` -> - `Integrations` -> `Bots and Apps`. -1. Click the `Manage` button next to the entry of the bot. -1. Under `CHANNELS`, disable `# All Channels` and enable the channel(s) where - the bot should accept faucet requests. Click `Save Changes`. - -To test locally run `anvil` in one terminal. In another terminal run - ``` -env ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN="..." cargo run --bin faucet -- \ - --mnemonic "test test test test test test test test test test test junk" \ - --faucet-grant-amount 1 --provider-url ws://localhost:8545 - ``` -with your Discord bot token. - -In Discord go to the faucet channel and write `/faucet 0x1234567890123456789012345678901234567890`. diff --git a/faucet/src/api.toml b/faucet/src/api.toml deleted file mode 100644 index 737592f..0000000 --- a/faucet/src/api.toml +++ /dev/null @@ -1,10 +0,0 @@ -[meta] -NAME = "faucet" -DESCRIPTION = "A Discord faucet" -FORMAT_VERSION = "0.1.0" - -[route.request] -PATH = ["/request/:address"] -":address" = "Literal" -METHOD = "POST" -DOC = "Request from faucet" diff --git a/faucet/src/discord.rs b/faucet/src/discord.rs deleted file mode 100644 index bdbe8bf..0000000 --- a/faucet/src/discord.rs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the Espresso Sequencer-Polygon zkEVM integration demo. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -// You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -//! A discord event handler for the faucet. -//! -//! Suggestions for improvements: -//! - After starting up, process messages sent since last online. -use crate::serve; -use crate::WebState; -use crate::{Faucet, Options}; -use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; -use async_std::task::spawn; -use clap::Parser; -use ethers::types::Address; -use regex::Regex; -use serenity::{ - async_trait, - model::{ - gateway::Ready, - prelude::{ - command::{Command, CommandOptionType}, - interaction::{ - application_command::{CommandDataOption, CommandDataOptionValue}, - Interaction, InteractionResponseType, - }, - }, - }, - prelude::{Context, EventHandler, GatewayIntents}, - Client, -}; -use std::io; - -impl WebState { - async fn handle_faucet_request(&self, options: &[CommandDataOption]) -> String { - let option = options - .get(0) - .expect("Expected address option") - .resolved - .as_ref() - .expect("Expected user object"); - match option { - CommandDataOptionValue::String(input) => { - // Try to find an ethereum address in the message body. - let re = Regex::new("0x[a-fA-F0-9]{40}").unwrap(); - - if let Some(matched) = re.captures(input) { - let address = matched - .get(0) - .expect("At least one match") - .as_str() - .parse::
() - .expect("Address can be parsed after matching regex"); - if let Err(err) = self.request(address).await { - tracing::error!("Failed make faucet request for {address:?}: {}", err); - format!("Internal Error: Failed to send funds to {address:?}") - } else { - format!("Sending funds to {address:?}") - } - } else { - "No address found!".to_string() - } - } - _ => unreachable!(), - } - } -} - -#[async_trait] -impl EventHandler for WebState { - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - if let Interaction::ApplicationCommand(command) = interaction { - tracing::info!("Received command interaction: {:#?}", command); - - let content = match command.data.name.as_str() { - "faucet" => self.handle_faucet_request(&command.data.options).await, - _ => "not implemented".to_string(), - }; - - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| message.content(content)) - }) - .await - { - tracing::error!("Cannot respond to slash command: {}", why); - } - } - } - - // Set a handler to be called on the `ready` event. This is called when a - // shard is booted, and a READY payload is sent by Discord. This payload - // contains data like the current user's guild Ids, current user data, - // private channels, and more. - async fn ready(&self, ctx: Context, ready: Ready) { - tracing::info!("{} is connected!", ready.user.name); - - Command::create_global_application_command(&ctx.http, |command| { - command - .name("faucet") - .description("Request funds from the faucet") - .create_option(|option| { - option - .name("address") - .description("Your ethereum address") - .kind(CommandOptionType::String) - .required(true) - }) - }) - .await - .expect("Command creation succeeds"); - } -} - -#[async_std::main] -pub async fn main() -> io::Result<()> { - // Configure the client with your Discord bot token in the environment. - setup_logging(); - setup_backtrace(); - - let opts = Options::parse(); - - // Create a new instance of the Client, logging in as a bot. This will - // automatically prepend your bot token with "Bot ", which is a requirement - // by Discord for bot users. - let (sender, receiver) = async_std::channel::unbounded(); - let state = WebState::new(sender); - let faucet = Faucet::create(opts.clone(), receiver) - .await - .expect("Failed to create faucet"); - - // Do not attempt to start the discord bot if the token is missing or empty. - let discord_client = if let Some(token) = opts.discord_token.filter(|token| !token.is_empty()) { - // Set gateway intents, which decides what events the bot will be notified about - let intents = GatewayIntents::GUILD_MESSAGES - | GatewayIntents::DIRECT_MESSAGES - | GatewayIntents::MESSAGE_CONTENT; - let client = Client::builder(token, intents) - .event_handler(state.clone()) - .await - .expect("Err creating discord client"); - Some(client) - } else { - tracing::warn!("Discord bot disabled. For local testing this is fine."); - None - }; - - let faucet_handle = spawn(faucet.start()); - let api_handle = spawn(serve(opts.port, state)); - - if let Some(mut discord) = discord_client { - let _result = futures::join!(faucet_handle, api_handle, discord.start()); - } else { - let _result = futures::join!(faucet_handle, api_handle); - }; - Ok(()) -} diff --git a/faucet/src/faucet.rs b/faucet/src/faucet.rs deleted file mode 100644 index 5307918..0000000 --- a/faucet/src/faucet.rs +++ /dev/null @@ -1,662 +0,0 @@ -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the Espresso Sequencer-Polygon zkEVM integration demo. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -// You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -use anyhow::{Error, Result}; -use async_std::{channel::Receiver, sync::RwLock, task::JoinHandle}; -use clap::Parser; -use ethers::{ - prelude::SignerMiddleware, - providers::{Http, Middleware as _, Provider, StreamExt, Ws}, - signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer}, - types::{Address, TransactionRequest, H256, U256, U512}, - utils::{parse_ether, ConversionError}, -}; -use std::{ - collections::{BinaryHeap, HashMap, VecDeque}, - num::ParseIntError, - ops::Index, - sync::Arc, - time::{Duration, Instant}, -}; -use thiserror::Error; -use url::Url; - -pub type Middleware = SignerMiddleware, LocalWallet>; - -#[derive(Parser, Debug, Clone)] -pub struct Options { - /// Number of Ethereum accounts to use for the faucet. - /// - /// This is the number of faucet grant requests that can be executed in - /// parallel. Each client can only do about one request per block_time - /// (which is 12 seconds for public Ethereum networks.) - /// - /// When initially setting and increasing the number of wallets the faucet - /// will make sure they are all funded before serving any faucet requests. - /// However when reducing the number of wallets the faucet will not collect - /// the funds in the wallets that are no longer used. - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_NUM_CLIENTS", default_value = "10")] - pub num_clients: usize, - - /// The mnemonic of the faucet wallet. - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_MNEMONIC")] - pub mnemonic: String, - - /// Port on which to serve the API. - #[arg( - short, - long, - env = "ESPRESSO_ZKEVM_FAUCET_PORT", - default_value = "8111" - )] - pub port: u16, - - /// The amount of funds to grant to each account on startup in Ethers. - #[arg( - long, - env = "ESPRESSO_ZKEVM_FAUCET_GRANT_AMOUNT_ETHERS", - value_parser = |arg: &str| -> Result { Ok(parse_ether(arg)?) } - )] - pub faucet_grant_amount: U256, - - /// The time after which a transfer is considered timed out and will be re-sent - #[arg( - long, - env = "ESPRESSO_ZKEVM_FAUCET_TRANSACTION_TIMEOUT_SECS", - default_value = "300", - value_parser = |arg: &str| -> Result { Ok(Duration::from_secs(arg.parse::()?)) } - )] - pub transaction_timeout: Duration, - - /// The URL of the JsonRPC the faucet connects to. - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_WS")] - pub provider_url_ws: Url, - - /// The URL of the JsonRPC the faucet connects to. - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_HTTP")] - pub provider_url_http: Url, - - /// The authentication token for the discord bot. - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN")] - pub discord_token: Option, - - /// Enable the funding on startup (currently broken). - #[arg(long, env = "ESPRESSO_ZKEVM_FAUCET_ENABLE_FUNDING")] - pub enable_funding: bool, -} - -impl Default for Options { - fn default() -> Self { - Self { - num_clients: 10, - mnemonic: "test test test test test test test test test test test junk".to_string(), - port: 8111, - faucet_grant_amount: parse_ether("100").unwrap(), - transaction_timeout: Duration::from_secs(300), - provider_url_ws: Url::parse("ws://localhost:8545").unwrap(), - provider_url_http: Url::parse("http://localhost:8545").unwrap(), - discord_token: None, - enable_funding: true, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub enum TransferRequest { - Faucet { - to: Address, - amount: U256, - }, - Funding { - to: Address, - average_wallet_balance: U256, - }, -} - -impl TransferRequest { - pub fn faucet(to: Address, amount: U256) -> Self { - Self::Faucet { to, amount } - } - - pub fn funding(to: Address, average_wallet_balance: U256) -> Self { - Self::Funding { - to, - average_wallet_balance, - } - } - - pub fn to(&self) -> Address { - match self { - Self::Faucet { to, .. } => *to, - Self::Funding { to, .. } => *to, - } - } - - pub fn required_funds(&self) -> U256 { - match self { - // Double the faucet amount to be on the safe side regarding gas. - Self::Faucet { amount, .. } => *amount * 2, - Self::Funding { - average_wallet_balance, - .. - } => *average_wallet_balance, - } - } -} - -#[derive(Debug, Clone)] -struct Transfer { - sender: Arc, - request: TransferRequest, - timestamp: Instant, -} - -impl Transfer { - pub fn new(sender: Arc, request: TransferRequest) -> Self { - Self { - sender, - request, - timestamp: Instant::now(), - } - } -} - -#[derive(Clone, Debug, Error)] -pub enum TransferError { - #[error("Error during transfer submission: {transfer:?} {sender:?} {msg}")] - RpcSubmitError { - transfer: TransferRequest, - sender: Address, - msg: String, - }, - #[error("No client available")] - NoClient, - #[error("No transfers requests available")] - NoRequests, -} - -#[derive(Debug, Clone, Default)] -struct ClientPool { - clients: HashMap>, - priority: BinaryHeap<(U256, Address)>, -} - -impl ClientPool { - pub fn pop(&mut self) -> Option<(U256, Arc)> { - let (balance, address) = self.priority.pop()?; - let client = self.clients.remove(&address)?; - Some((balance, client)) - } - - pub fn push(&mut self, balance: U256, client: Arc) { - self.clients.insert(client.address(), client.clone()); - self.priority.push((balance, client.address())); - } - - pub fn has_client_for(&self, transfer: TransferRequest) -> bool { - self.priority - .peek() - .map_or(false, |(balance, _)| *balance >= transfer.required_funds()) - } -} - -#[derive(Debug, Clone, Default)] -struct State { - clients: ClientPool, - inflight: HashMap, - clients_being_funded: HashMap>, - // Funding wallets has priority, these transfer requests must be pushed to - // the front. - transfer_queue: VecDeque, - monitoring_started: bool, -} - -#[derive(Debug, Clone)] -pub struct Faucet { - config: Options, - state: Arc>, - /// Used to monitor Ethereum transactions. - provider: Provider, - /// Channel to receive faucet requests. - faucet_receiver: Arc>>, -} - -impl Faucet { - /// Create a new faucet. - /// - /// Creates `num_clients` wallets and transfers funds and queues transfers - /// from the ones with most balance to the ones with less than average - /// balance. - pub async fn create(options: Options, faucet_receiver: Receiver
) -> Result { - // Use a http provider for non-subscribe requests - let provider = Provider::::try_from(options.provider_url_http.to_string())?; - let chain_id = provider.get_chainid().await?.as_u64(); - - let mut state = State::default(); - let mut clients = vec![]; - - // We want each account to have a minimum value that is at least 80% of the average value. - // For this computation, convert into U512 to avoid overflow while adding up the total - // balance or multiplying to compute 80%. - let mut total_balance = U512::zero(); - - // Create clients - for index in 0..options.num_clients { - let wallet = MnemonicBuilder::::default() - .phrase(options.mnemonic.as_str()) - .index(index as u32)? - .build()? - .with_chain_id(chain_id); - let client = Arc::new(Middleware::new(provider.clone(), wallet)); - - // On startup we may get a "[-32000] failed to get the last block - // number from state" error even after the request for getChainId is - // successful. - let balance = loop { - if let Ok(balance) = provider.get_balance(client.address(), None).await { - break balance; - } - tracing::info!("Failed to get balance for client, retrying..."); - async_std::task::sleep(Duration::from_secs(1)).await; - }; - - tracing::info!( - "Created client {index} {:?} with balance {balance}", - client.address(), - ); - - total_balance += balance.into(); - clients.push((balance, client)); - } - - let desired_balance = total_balance / options.num_clients * 8 / 10; - // At this point, `desired_balance` is less than the average of all the clients' balances, - // each of which was a `U256`, so we can safely cast back into a `U256`. - let desired_balance = - U256::try_from(desired_balance).expect("average balance overflows U256"); - - for (balance, client) in clients { - // Fund all clients who have significantly less than average balance. - if options.enable_funding && balance < desired_balance { - tracing::info!("Queuing funding transfer for {:?}", client.address()); - let transfer = TransferRequest::funding(client.address(), desired_balance); - state.transfer_queue.push_back(transfer); - state.clients_being_funded.insert(client.address(), client); - } else { - state.clients.push(balance, client); - } - } - - Ok(Self { - config: options, - state: Arc::new(RwLock::new(state)), - provider, - faucet_receiver: Arc::new(RwLock::new(faucet_receiver)), - }) - } - - pub async fn start( - self, - ) -> JoinHandle<( - Result<(), Error>, - Result<(), Error>, - Result<(), Error>, - Result<(), Error>, - )> { - let futures = async move { - futures::join!( - self.monitor_transactions(), - self.monitor_faucet_requests(), - self.monitor_transaction_timeouts(), - self.execute_transfers_loop() - ) - }; - async_std::task::spawn(futures) - } - - async fn balance(&self, address: Address) -> Result { - Ok(self.provider.get_balance(address, None).await?) - } - - async fn request_transfer(&self, transfer: TransferRequest) { - tracing::info!("Adding transfer to queue: {:?}", transfer); - self.state.write().await.transfer_queue.push_back(transfer); - } - - async fn execute_transfers_loop(&self) -> Result<()> { - loop { - if self.state.read().await.monitoring_started { - break; - } else { - tracing::info!("Waiting for transaction monitoring to start..."); - async_std::task::sleep(Duration::from_secs(1)).await; - } - } - loop { - if let Err(err) = self.execute_transfer().await { - match err { - TransferError::RpcSubmitError { .. } => { - tracing::error!("Failed to execute transfer: {:?}", err) - } - TransferError::NoClient => { - tracing::info!("No clients to handle transfer requests.") - } - TransferError::NoRequests => {} - }; - // Avoid creating a busy loop. - async_std::task::sleep(Duration::from_secs(1)).await; - }; - } - } - - async fn execute_transfer(&self) -> Result { - let mut state = self.state.write().await; - if state.transfer_queue.is_empty() { - Err(TransferError::NoRequests)?; - } - let transfer = state.transfer_queue.index(0); - if !state.clients.has_client_for(*transfer) { - Err(TransferError::NoClient)?; - } - let (balance, sender) = state.clients.pop().unwrap(); - let transfer = state.transfer_queue.pop_front().unwrap(); - - // Drop the guard while we are doing the request to the RPC. - drop(state); - - let amount = match transfer { - TransferRequest::Faucet { amount, .. } => amount, - TransferRequest::Funding { .. } => balance / 2, - }; - match sender - .clone() - .send_transaction(TransactionRequest::pay(transfer.to(), amount), None) - .await - { - Ok(tx) => { - tracing::info!("Sending transfer: {:?} hash={:?}", transfer, tx.tx_hash()); - // Note: if running against an *extremely* fast chain , it is possible - // that the transaction is mined before we have a chance to add it to - // the inflight transfers. In that case, the receipt handler may not yet - // find the transaction and fail to process it correctly. I think the - // risk of this happening outside of local testing is neglible. We could - // sign the tx locally first and then insert it but this also means we - // would have to remove it again if the submission fails. - self.state - .write() - .await - .inflight - .insert(tx.tx_hash(), Transfer::new(sender.clone(), transfer)); - Ok(tx.tx_hash()) - } - Err(err) => { - // Make the client available again. - self.state - .write() - .await - .clients - .push(balance, sender.clone()); - - // Requeue the transfer. - self.request_transfer(transfer).await; - - Err(TransferError::RpcSubmitError { - transfer, - sender: sender.address(), - msg: err.to_string(), - })? - } - } - } - - async fn handle_receipt(&self, tx_hash: H256) -> Result<()> { - tracing::debug!("Got tx hash {:?}", tx_hash); - - let Transfer { - sender, request, .. - } = { - if let Some(inflight) = self.state.read().await.inflight.get(&tx_hash) { - inflight.clone() - } else { - // Not a transaction we are monitoring. - return Ok(()); - } - }; - - // In case there is a race condition and the receipt is not yet available, wait for it. - let receipt = loop { - if let Ok(Some(tx)) = self.provider.get_transaction_receipt(tx_hash).await { - break tx; - } - tracing::warn!("No receipt for tx_hash={tx_hash:?}, will retry"); - async_std::task::sleep(Duration::from_secs(1)).await; - }; - - tracing::info!("Received receipt for {:?}", request); - - // Do all external calls before state modifications - let new_sender_balance = self.balance(sender.address()).await?; - - // For successful funding transfers, we also need to update the receiver's balance. - let receiver_update = if receipt.status == Some(1.into()) { - if let TransferRequest::Funding { to: receiver, .. } = request { - Some((receiver, self.balance(receiver).await?)) - } else { - None - } - } else { - None - }; - - // Update state, the rest of the operations must be atomic. - let mut state = self.state.write().await; - - // Make the sender available - state.clients.push(new_sender_balance, sender.clone()); - - // Apply the receiver update, if there is one. - if let Some((receiver, balance)) = receiver_update { - if let Some(client) = state.clients_being_funded.remove(&receiver) { - tracing::info!("Funded client {:?} with {:?}", receiver, balance); - state.clients.push(balance, client); - } else { - tracing::warn!( - "Received funding transfer for unknown client {:?}", - receiver - ); - } - } - - // If the transaction failed, schedule it again. - if receipt.status == Some(0.into()) { - // TODO: this code is currently untested. - tracing::warn!( - "Transfer failed tx_hash={:?}, will resend: {:?}", - tx_hash, - request - ); - state.transfer_queue.push_back(request); - }; - - // Finally remove the transaction from the inflight list. - state.inflight.remove(&tx_hash); - - // TODO: I think for transactions with bad nonces we would not even get - // a transactions receipt. As a result the sending client would remain - // stuck. As a workaround we could add a timeout to the inflight clients - // and unlock them after a while. It may be difficult to set a good - // fixed value for the timeout because the zkevm-node currently waits - // for hotshot blocks being sequenced in the contract. - - Ok(()) - } - - async fn monitor_transactions(&self) -> Result<()> { - loop { - let provider = match Provider::::connect(self.config.provider_url_ws.clone()).await - { - Ok(provider) => provider, - Err(err) => { - tracing::error!("Failed to connect to provider: {}, will retry", err); - async_std::task::sleep(Duration::from_secs(5)).await; - continue; - } - }; - - let mut stream = provider - .subscribe_blocks() - .await - .unwrap() - .flat_map(|block| futures::stream::iter(block.transactions)); - - self.state.write().await.monitoring_started = true; - tracing::info!("Transaction monitoring started ..."); - while let Some(tx_hash) = stream.next().await { - self.handle_receipt(tx_hash).await?; - } - - // If we get here, the subscription was closed. This happens for example - // if the RPC server is restarted. - tracing::warn!("Block subscription closed, will restart ..."); - async_std::task::sleep(Duration::from_secs(5)).await; - } - } - - async fn monitor_faucet_requests(&self) -> Result<()> { - loop { - if let Ok(address) = self.faucet_receiver.write().await.recv().await { - self.request_transfer(TransferRequest::faucet( - address, - self.config.faucet_grant_amount, - )) - .await; - } - } - } - - async fn monitor_transaction_timeouts(&self) -> Result<()> { - loop { - async_std::task::sleep(Duration::from_secs(60)).await; - self.process_transaction_timeouts().await?; - } - } - - async fn process_transaction_timeouts(&self) -> Result<()> { - let inflight = self.state.read().await.inflight.clone(); - - for ( - tx_hash, - Transfer { - sender, request, .. - }, - ) in inflight - .iter() - .filter(|(_, transfer)| transfer.timestamp.elapsed() > self.config.transaction_timeout) - { - tracing::warn!("Transfer timed out: {:?}", request); - let balance = self.balance(sender.address()).await?; - let mut state = self.state.write().await; - state.transfer_queue.push_back(*request); - state.inflight.remove(tx_hash); - state.clients.push(balance, sender.clone()); - } - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; - use sequencer_utils::AnvilOptions; - - #[async_std::test] - async fn test_faucet_inflight_timeouts() -> Result<()> { - setup_logging(); - setup_backtrace(); - - let anvil = AnvilOptions::default() - .block_time(Duration::from_secs(3600)) - .spawn() - .await; - - let mut ws_url = anvil.url(); - ws_url.set_scheme("ws").unwrap(); - - let options = Options { - num_clients: 1, - provider_url_ws: ws_url, - provider_url_http: anvil.url(), - transaction_timeout: Duration::from_secs(0), - ..Default::default() - }; - - let (_, receiver) = async_std::channel::unbounded(); - let faucet = Faucet::create(options.clone(), receiver).await?; - - // Manually execute a transfer. - let transfer = TransferRequest::faucet(Address::zero(), options.faucet_grant_amount); - faucet.request_transfer(transfer).await; - faucet.execute_transfer().await?; - - // Assert that there is an inflight transaction. - assert!(!faucet.state.read().await.inflight.is_empty()); - - // Process the timed out transaction. - faucet.process_transaction_timeouts().await?; - assert!(faucet.state.read().await.inflight.is_empty()); - - // Assert that the client is available again. - faucet.state.write().await.clients.pop().unwrap(); - - // Assert that the transaction was not executed. - assert_eq!(faucet.balance(Address::zero()).await?, 0.into()); - - Ok(()) - } - - // A regression test for a bug where clients that received funding transfers - // were not made available. - #[async_std::test] - async fn test_faucet_funding() -> Result<()> { - setup_logging(); - setup_backtrace(); - - let anvil = AnvilOptions::default().spawn().await; - - let mut ws_url = anvil.url(); - ws_url.set_scheme("ws").unwrap(); - let options = Options { - // 10 clients are already funded with anvil - num_clients: 11, - provider_url_ws: ws_url, - provider_url_http: anvil.url(), - ..Default::default() - }; - - let (_, receiver) = async_std::channel::unbounded(); - let faucet = Faucet::create(options.clone(), receiver).await?; - - // There is one client that needs funding. - assert_eq!(faucet.state.read().await.clients_being_funded.len(), 1); - - let tx_hash = faucet.execute_transfer().await?; - faucet.handle_receipt(tx_hash).await?; - - let mut state = faucet.state.write().await; - // The newly funded client is now funded. - assert_eq!(state.clients_being_funded.len(), 0); - assert_eq!(state.clients.clients.len(), 11); - - // All clients now have a non-zero balance. - while let Some((balance, _)) = state.clients.pop() { - assert!(balance > 0.into()); - } - - Ok(()) - } -} diff --git a/faucet/src/lib.rs b/faucet/src/lib.rs deleted file mode 100644 index 8980845..0000000 --- a/faucet/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the Espresso Sequencer-Polygon zkEVM integration demo. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -// You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -mod faucet; -pub(crate) use crate::faucet::*; - -mod web; -pub(crate) use web::*; - -mod discord; -pub use discord::*; diff --git a/faucet/src/main.rs b/faucet/src/main.rs deleted file mode 100644 index 68562cf..0000000 --- a/faucet/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the Espresso Sequencer-Polygon zkEVM integration demo. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -// You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -#[async_std::main] -async fn main() -> std::io::Result<()> { - faucet::main() -} diff --git a/faucet/src/web.rs b/faucet/src/web.rs deleted file mode 100644 index d434819..0000000 --- a/faucet/src/web.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright (c) 2023 Espresso Systems (espressosys.com) -// This file is part of the Espresso Sequencer-Polygon zkEVM integration demo. -// -// This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. -// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. -// You should have received a copy of the GNU Affero General Public License along with this program. If not, see . - -//! Web server for the discord faucet. -//! -//! Serves these purposes: -//! 1. Provide a healthcheck endpoint for the discord bot, so it can be automatically -//! restarted if it fails. -//! 2. Test and use the faucet locally without connecting to Discord. -use async_std::channel::Sender; -use async_std::sync::RwLock; -use ethers::types::Address; -use futures::FutureExt; -use serde::{Deserialize, Serialize}; -use std::env; -use std::io; -use thiserror::Error; -use tide_disco::RequestError; -use tide_disco::{http::StatusCode, Api, App, Error}; - -#[derive(Clone, Debug, Deserialize, Serialize, Error)] -pub enum FaucetError { - #[error("faucet error {status}: {msg}")] - FaucetError { status: StatusCode, msg: String }, - #[error("unable to parse Ethereum address: {input}")] - BadAddress { status: StatusCode, input: String }, -} - -impl tide_disco::Error for FaucetError { - fn catch_all(status: StatusCode, msg: String) -> Self { - Self::FaucetError { status, msg } - } - - fn status(&self) -> StatusCode { - match self { - Self::FaucetError { status, .. } => *status, - Self::BadAddress { status, .. } => *status, - } - } -} - -impl From for FaucetError { - fn from(err: RequestError) -> Self { - Self::catch_all(StatusCode::BadRequest, err.to_string()) - } -} - -pub(crate) async fn serve(port: u16, state: WebState) -> io::Result<()> { - let mut app = App::<_, FaucetError>::with_state(RwLock::new(state)); - app.with_version(env!("CARGO_PKG_VERSION").parse().unwrap()); - - // Include API specification in binary - let toml = toml::from_str::(include_str!("api.toml")) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; - - let mut api = Api::, FaucetError>::new(toml).unwrap(); - api.with_version(env!("CARGO_PKG_VERSION").parse().unwrap()); - - // Can invoke with - // `curl -i -X POST http://0.0.0.0:8111/faucet/request/0x1234567890123456789012345678901234567890` - api.post("request", |req, state| { - async move { - let address = req.string_param("address")?; - let address = address.parse().map_err(|_| FaucetError::BadAddress { - status: StatusCode::BadRequest, - input: address.to_string(), - })?; - tracing::info!("Received faucet request for {:?}", address); - state.request(address).await?; - Ok(()) - } - .boxed() - }) - .unwrap(); - - app.register_module("faucet", api).unwrap(); - app.serve(format!("0.0.0.0:{}", port)).await -} - -#[derive(Clone, Debug)] -pub(crate) struct WebState { - faucet_queue: Sender
, -} - -impl WebState { - pub fn new(faucet_queue: Sender
) -> Self { - Self { faucet_queue } - } - - pub async fn request(&self, address: Address) -> Result<(), FaucetError> { - self.faucet_queue - .send(address) - .await - .map_err(|err| FaucetError::FaucetError { - status: StatusCode::InternalServerError, - msg: err.to_string(), - })?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::faucet::{Faucet, Options}; - use anyhow::Result; - use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; - use async_std::task::spawn; - use ethers::{ - providers::{Http, Middleware, Provider}, - types::U256, - utils::parse_ether, - }; - use polygon_zkevm_adaptor::SequencerZkEvmDemo; - use sequencer_utils::AnvilOptions; - use std::time::Duration; - use surf_disco::Client; - - async fn run_faucet_test(options: Options, num_transfers: usize) -> Result<()> { - let client = - Client::::new(format!("http://localhost:{}", options.port).parse()?); - // Avoids waiting 10 seconds for the retry in `connect`. - async_std::task::sleep(Duration::from_millis(100)).await; - client.connect(None).await; - - let recipient = Address::random(); - let mut total_transfer_amount = U256::zero(); - - for _ in 0..num_transfers { - client - .post(&format!("faucet/request/{recipient:?}")) - .send() - .await?; - - total_transfer_amount += options.faucet_grant_amount; - } - - let provider = Provider::::try_from(options.provider_url_http.to_string())?; - loop { - let balance = provider.get_balance(recipient, None).await.unwrap(); - tracing::info!("Balance is {balance}"); - if balance == total_transfer_amount { - break; - } - async_std::task::sleep(Duration::from_secs(1)).await; - } - - Ok(()) - } - - #[async_std::test] - async fn test_faucet_anvil() -> Result<()> { - setup_logging(); - setup_backtrace(); - - let anvil = AnvilOptions::default().spawn().await; - - let mut ws_url = anvil.url(); - ws_url.set_scheme("ws").unwrap(); - - // With anvil 10 clients are pre-funded. We use more than that to make - // sure the funding logic runs. - let options = Options { - num_clients: 12, - faucet_grant_amount: parse_ether(1).unwrap(), - provider_url_ws: ws_url, - provider_url_http: anvil.url(), - port: portpicker::pick_unused_port().unwrap(), - ..Default::default() - }; - - let (sender, receiver) = async_std::channel::unbounded(); - - // Start the faucet - let faucet = Faucet::create(options.clone(), receiver).await?; - let _handle = faucet.start().await; - - // Start the web server - spawn(async move { serve(options.port, WebState::new(sender)).await }); - - run_faucet_test(options, 30).await?; - Ok(()) - } - - // Currently this test fails if run together with the others tests due to - // errors when talking to the zkevm-node weboscket RPC. It passes if its the - // only running test. - #[ignore] - #[async_std::test] - async fn test_faucet_zkevm_node() -> Result<()> { - setup_logging(); - setup_backtrace(); - - let faucet_grant_amount_ethers = 123u64; - // Use fewer clients to shorten test time. - let num_clients = 2; - std::env::set_var("ESPRESSO_ZKEVM_FAUCET_NUM_CLIENTS", num_clients.to_string()); - std::env::set_var( - "ESPRESSO_ZKEVM_FAUCET_GRANT_AMOUNT_ETHERS", - faucet_grant_amount_ethers.to_string(), - ); - - let demo = - SequencerZkEvmDemo::start_with_sequencer("faucet-test".to_string(), Default::default()) - .await; - let env = demo.env(); - - // Connect to the faucet running inside the docker compose environment. - let mut ws_url = env.l2_provider(); - ws_url.set_scheme("ws").unwrap(); - ws_url.set_port(Some(8133)).unwrap(); // zkevm-node uses 8133 for websockets - - let options = Options { - num_clients, - faucet_grant_amount: parse_ether(faucet_grant_amount_ethers).unwrap(), - provider_url_ws: ws_url, - provider_url_http: env.l2_provider(), - ..Default::default() - }; - run_faucet_test(options, 3).await?; - Ok(()) - } - - #[async_std::test] - async fn test_node_restart() -> Result<()> { - setup_logging(); - setup_backtrace(); - - let anvil_opts = AnvilOptions::default(); - let mut anvil = anvil_opts.clone().spawn().await; - - let mut ws_url = anvil.url(); - ws_url.set_scheme("ws").unwrap(); - - // With anvil 10 clients are pre-funded. We use more than that to make - // sure the funding logic runs. - let options = Options { - num_clients: 12, - faucet_grant_amount: parse_ether(1).unwrap(), - provider_url_ws: ws_url, - provider_url_http: anvil.url(), - port: portpicker::pick_unused_port().unwrap(), - ..Default::default() - }; - - let (sender, receiver) = async_std::channel::unbounded(); - - // Start the faucet - let faucet = Faucet::create(options.clone(), receiver).await?; - let _handle = faucet.start().await; - - // Start the web server - spawn(async move { serve(options.port, WebState::new(sender)).await }); - - run_faucet_test(options.clone(), 3).await?; - - tracing::info!("Restarting anvil to trigger web socket reconnect"); - anvil.restart(anvil_opts).await; - - run_faucet_test(options, 3).await?; - - Ok(()) - } -} diff --git a/permissionless-docker-compose.yaml b/permissionless-docker-compose.yaml index 4f585b8..27b3501 100644 --- a/permissionless-docker-compose.yaml +++ b/permissionless-docker-compose.yaml @@ -271,29 +271,24 @@ services: - "zkevm1" zkevm-1-faucet: - image: ghcr.io/espressosystems/espresso-polygon-zkevm-demo/faucet:main + image: ghcr.io/espressosystems/discord-faucet:main ports: - $ESPRESSO_ZKEVM_1_FAUCET_PORT:$ESPRESSO_ZKEVM_1_FAUCET_PORT environment: - - ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN - - ESPRESSO_ZKEVM_FAUCET_GRANT_AMOUNT_ETHERS - - ESPRESSO_ZKEVM_FAUCET_MNEMONIC - - ESPRESSO_ZKEVM_FAUCET_NUM_CLIENTS - - ESPRESSO_ZKEVM_FAUCET_PORT=$ESPRESSO_ZKEVM_1_FAUCET_PORT - - ESPRESSO_ZKEVM_FAUCET_TRANSACTION_TIMEOUT_SECS - - ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_WS=$ESPRESSO_ZKEVM_1_FAUCET_WEB3_PROVIDER_URL_WS - - ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_HTTP=$ESPRESSO_ZKEVM_1_FAUCET_WEB3_PROVIDER_URL_HTTP + - ESPRESSO_DISCORD_FAUCET_DISCORD_TOKEN + - ESPRESSO_DISCORD_FAUCET_GRANT_AMOUNT_ETHERS + - ESPRESSO_DISCORD_FAUCET_MNEMONIC + - ESPRESSO_DISCORD_FAUCET_NUM_CLIENTS + - ESPRESSO_DISCORD_FAUCET_PORT=$ESPRESSO_ZKEVM_1_FAUCET_PORT + - ESPRESSO_DISCORD_FAUCET_TRANSACTION_TIMEOUT_SECS + - ESPRESSO_DISCORD_FAUCET_WEB3_PROVIDER_URL_WS=$ESPRESSO_ZKEVM_1_FAUCET_WEB3_PROVIDER_URL_WS + - ESPRESSO_DISCORD_FAUCET_WEB3_PROVIDER_URL_HTTP=$ESPRESSO_ZKEVM_1_FAUCET_WEB3_PROVIDER_URL_HTTP - RUST_LOG - RUST_LOG_FORMAT - RUST_BACKTRACE=1 depends_on: zkevm-1-permissionless-node: condition: service_healthy - healthcheck: - test: curl --fail http://localhost:$ESPRESSO_ZKEVM_1_FAUCET_PORT/healthcheck || exit 1 - interval: 2s - retries: 10 - timeout: 1s profiles: - "zkevm1" @@ -565,27 +560,22 @@ services: - "zkevm2" zkevm-2-faucet: - image: ghcr.io/espressosystems/espresso-polygon-zkevm-demo/faucet:main + image: ghcr.io/espressosystems/discord-faucet:main ports: - $ESPRESSO_ZKEVM_2_FAUCET_PORT:$ESPRESSO_ZKEVM_2_FAUCET_PORT environment: - - ESPRESSO_ZKEVM_FAUCET_DISCORD_TOKEN - - ESPRESSO_ZKEVM_FAUCET_GRANT_AMOUNT_ETHERS - - ESPRESSO_ZKEVM_FAUCET_MNEMONIC - - ESPRESSO_ZKEVM_FAUCET_NUM_CLIENTS - - ESPRESSO_ZKEVM_FAUCET_PORT=$ESPRESSO_ZKEVM_2_FAUCET_PORT - - ESPRESSO_ZKEVM_FAUCET_TRANSACTION_TIMEOUT_SECS - - ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_WS=$ESPRESSO_ZKEVM_2_FAUCET_WEB3_PROVIDER_URL_WS - - ESPRESSO_ZKEVM_FAUCET_WEB3_PROVIDER_URL_HTTP=$ESPRESSO_ZKEVM_2_FAUCET_WEB3_PROVIDER_URL_HTTP + - ESPRESSO_DISCORD_FAUCET_DISCORD_TOKEN + - ESPRESSO_DISCORD_FAUCET_GRANT_AMOUNT_ETHERS + - ESPRESSO_DISCORD_FAUCET_MNEMONIC + - ESPRESSO_DISCORD_FAUCET_NUM_CLIENTS + - ESPRESSO_DISCORD_FAUCET_PORT=$ESPRESSO_ZKEVM_2_FAUCET_PORT + - ESPRESSO_DISCORD_FAUCET_TRANSACTION_TIMEOUT_SECS + - ESPRESSO_DISCORD_FAUCET_WEB3_PROVIDER_URL_WS=$ESPRESSO_ZKEVM_2_FAUCET_WEB3_PROVIDER_URL_WS + - ESPRESSO_DISCORD_FAUCET_WEB3_PROVIDER_URL_HTTP=$ESPRESSO_ZKEVM_2_FAUCET_WEB3_PROVIDER_URL_HTTP - RUST_LOG - RUST_LOG_FORMAT depends_on: zkevm-2-permissionless-node: condition: service_healthy - healthcheck: - test: curl --fail http://localhost:$ESPRESSO_ZKEVM_2_FAUCET_PORT/healthcheck || exit 1 - interval: 2s - retries: 10 - timeout: 1s profiles: - "zkevm2" diff --git a/scripts/build-docker-images b/scripts/build-docker-images index 9c49096..826d4f0 100755 --- a/scripts/build-docker-images +++ b/scripts/build-docker-images @@ -3,7 +3,6 @@ set -e nix develop .#crossShell --ignore-environment --keep HOME --command cargo build --release -docker build -t ghcr.io/espressosystems/espresso-polygon-zkevm-demo/faucet:main -f docker/faucet.Dockerfile . docker build -t ghcr.io/espressosystems/espresso-polygon-zkevm-demo/polygon-zkevm-adaptor:main -f docker/polygon-zkevm-adaptor.Dockerfile . docker build -t ghcr.io/espressosystems/espresso-polygon-zkevm-demo/state-db:main -f docker/state-db.Dockerfile . docker build -t ghcr.io/espressosystems/espresso-polygon-zkevm-demo/zkevm-node:main -f docker/node.Dockerfile . diff --git a/scripts/copy-docker-images-to-host b/scripts/copy-docker-images-to-host index 4ad9ea4..74042f5 100755 --- a/scripts/copy-docker-images-to-host +++ b/scripts/copy-docker-images-to-host @@ -13,4 +13,4 @@ copy ghcr.io/espressosystems/espresso-sequencer/web-server:main copy ghcr.io/espressosystems/espresso-sequencer/orchestrator:main copy ghcr.io/espressosystems/espresso-polygon-zkevm-demo/geth-with-contracts:main copy ghcr.io/espressosystems/espresso-polygon-zkevm-demo/polygon-zkevm-adaptor:main -copy ghcr.io/espressosystems/espresso-polygon-zkevm-demo/faucet:main +copy ghcr.io/espressosystems/discord-faucet:main