mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-02 16:21:42 +00:00
Require manual confirmation to purge database with purge-db (#6154)
* Require manual confirmation to purge database * Fix tests * Rename to `purge-db-force and skip in non-interactive mode * Do not skip when stdin_inputs is true * Change prompt to be info logging to ensure consistent output * Update warning text * Move delete log after deletion
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -836,6 +836,7 @@ dependencies = [
|
||||
name = "beacon_node"
|
||||
version = "5.3.0"
|
||||
dependencies = [
|
||||
"account_utils",
|
||||
"beacon_chain",
|
||||
"clap",
|
||||
"clap_utils",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::common::read_wallet_name_from_cli;
|
||||
use crate::wallet::create::STDIN_INPUTS_FLAG;
|
||||
use crate::{SECRETS_DIR_FLAG, WALLETS_DIR_FLAG};
|
||||
use account_utils::{
|
||||
random_password, read_password_from_user, strip_off_newlines, validator_definitions, PlainText,
|
||||
STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
@@ -114,16 +114,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::Set)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0)
|
||||
.action(ArgAction::SetTrue)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn cli_run<E: EthSpec>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::wallet::create::STDIN_INPUTS_FLAG;
|
||||
use account_utils::STDIN_INPUTS_FLAG;
|
||||
use bls::{Keypair, PublicKey};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
@@ -74,15 +74,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn cli_run<E: EthSpec>(matches: &ArgMatches, env: Environment<E>) -> Result<(), String> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::wallet::create::{PASSWORD_FLAG, STDIN_INPUTS_FLAG};
|
||||
use crate::wallet::create::PASSWORD_FLAG;
|
||||
use account_utils::validator_definitions::SigningDefinition;
|
||||
use account_utils::{
|
||||
eth2_keystore::Keystore,
|
||||
@@ -7,7 +7,7 @@ use account_utils::{
|
||||
recursively_find_voting_keystores, PasswordStorage, ValidatorDefinition,
|
||||
ValidatorDefinitions, CONFIG_FILENAME,
|
||||
},
|
||||
ZeroizeString,
|
||||
ZeroizeString, STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
@@ -59,15 +59,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::Set)
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(REUSE_PASSWORD_FLAG)
|
||||
.long(REUSE_PASSWORD_FLAG)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use super::create::STORE_WITHDRAW_FLAG;
|
||||
use crate::validator::create::COUNT_FLAG;
|
||||
use crate::wallet::create::STDIN_INPUTS_FLAG;
|
||||
use crate::SECRETS_DIR_FLAG;
|
||||
use account_utils::eth2_keystore::{keypair_from_secret, Keystore, KeystoreBuilder};
|
||||
use account_utils::{random_password, read_mnemonic_from_cli};
|
||||
use account_utils::{random_password, read_mnemonic_from_cli, STDIN_INPUTS_FLAG};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap_utils::FLAG_HEADER;
|
||||
use directory::ensure_dir_exists;
|
||||
@@ -76,15 +75,6 @@ pub fn cli_app() -> Command {
|
||||
.help_heading(FLAG_HEADER)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn cli_run(matches: &ArgMatches, validator_dir: PathBuf) -> Result<(), String> {
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::common::read_wallet_name_from_cli;
|
||||
use crate::WALLETS_DIR_FLAG;
|
||||
use account_utils::{
|
||||
is_password_sufficiently_complex, random_password, read_password_from_user, strip_off_newlines,
|
||||
STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use eth2_wallet::{
|
||||
@@ -20,7 +21,6 @@ pub const NAME_FLAG: &str = "name";
|
||||
pub const PASSWORD_FLAG: &str = "password-file";
|
||||
pub const TYPE_FLAG: &str = "type";
|
||||
pub const MNEMONIC_FLAG: &str = "mnemonic-output-path";
|
||||
pub const STDIN_INPUTS_FLAG: &str = "stdin-inputs";
|
||||
pub const MNEMONIC_LENGTH_FLAG: &str = "mnemonic-length";
|
||||
pub const MNEMONIC_TYPES: &[MnemonicType] = &[
|
||||
MnemonicType::Words12,
|
||||
@@ -83,14 +83,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::Set)
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new(MNEMONIC_LENGTH_FLAG)
|
||||
.long(MNEMONIC_LENGTH_FLAG)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::wallet::create::{create_wallet_from_mnemonic, STDIN_INPUTS_FLAG};
|
||||
use crate::wallet::create::create_wallet_from_mnemonic;
|
||||
use crate::wallet::create::{HD_TYPE, NAME_FLAG, PASSWORD_FLAG, TYPE_FLAG};
|
||||
use account_utils::read_mnemonic_from_cli;
|
||||
use account_utils::{read_mnemonic_from_cli, STDIN_INPUTS_FLAG};
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -56,14 +56,6 @@ pub fn cli_app() -> Command {
|
||||
.default_value(HD_TYPE)
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn cli_run(matches: &ArgMatches, wallet_base_dir: PathBuf) -> Result<(), String> {
|
||||
|
||||
@@ -44,3 +44,4 @@ sensitive_url = { workspace = true }
|
||||
http_api = { workspace = true }
|
||||
unused_port = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
account_utils = { workspace = true }
|
||||
|
||||
@@ -919,7 +919,15 @@ pub fn cli_app() -> Command {
|
||||
.long("purge-db")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.help("If present, the chain database will be deleted. Use with caution.")
|
||||
.help("If present, the chain database will be deleted. Requires manual confirmation.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("purge-db-force")
|
||||
.long("purge-db-force")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help_heading(FLAG_HEADER)
|
||||
.help("If present, the chain database will be deleted without confirmation. Use with caution.")
|
||||
.display_order(0)
|
||||
)
|
||||
.arg(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use account_utils::{read_input_from_user, STDIN_INPUTS_FLAG};
|
||||
use beacon_chain::chain_config::{
|
||||
DisallowedReOrgOffsets, ReOrgThreshold, DEFAULT_PREPARE_PAYLOAD_LOOKAHEAD_FACTOR,
|
||||
DEFAULT_RE_ORG_HEAD_THRESHOLD, DEFAULT_RE_ORG_MAX_EPOCHS_SINCE_FINALIZATION,
|
||||
@@ -21,6 +22,7 @@ use slog::{info, warn, Logger};
|
||||
use std::cmp::max;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::io::IsTerminal;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};
|
||||
use std::num::NonZeroU16;
|
||||
@@ -30,6 +32,8 @@ use std::time::Duration;
|
||||
use types::graffiti::GraffitiString;
|
||||
use types::{Checkpoint, Epoch, EthSpec, Hash256, PublicKeyBytes};
|
||||
|
||||
const PURGE_DB_CONFIRMATION: &str = "confirm";
|
||||
|
||||
/// Gets the fully-initialized global client.
|
||||
///
|
||||
/// The top-level `clap` arguments should be provided as `cli_args`.
|
||||
@@ -50,26 +54,45 @@ pub fn get_config<E: EthSpec>(
|
||||
client_config.set_data_dir(get_data_dir(cli_args));
|
||||
|
||||
// If necessary, remove any existing database and configuration
|
||||
if client_config.data_dir().exists() && cli_args.get_flag("purge-db") {
|
||||
// Remove the chain_db.
|
||||
let chain_db = client_config.get_db_path();
|
||||
if chain_db.exists() {
|
||||
fs::remove_dir_all(chain_db)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
}
|
||||
if client_config.data_dir().exists() {
|
||||
if cli_args.get_flag("purge-db-force") {
|
||||
let chain_db = client_config.get_db_path();
|
||||
let freezer_db = client_config.get_freezer_db_path();
|
||||
let blobs_db = client_config.get_blobs_db_path();
|
||||
purge_db(chain_db, freezer_db, blobs_db)?;
|
||||
} else if cli_args.get_flag("purge-db") {
|
||||
let stdin_inputs = cfg!(windows) || cli_args.get_flag(STDIN_INPUTS_FLAG);
|
||||
if std::io::stdin().is_terminal() || stdin_inputs {
|
||||
info!(
|
||||
log,
|
||||
"You are about to delete the chain database. This is irreversable \
|
||||
and you will need to resync the chain."
|
||||
);
|
||||
info!(
|
||||
log,
|
||||
"Type 'confirm' to delete the database. Any other input will leave \
|
||||
the database intact and Lighthouse will exit."
|
||||
);
|
||||
let confirmation = read_input_from_user(stdin_inputs)?;
|
||||
|
||||
// Remove the freezer db.
|
||||
let freezer_db = client_config.get_freezer_db_path();
|
||||
if freezer_db.exists() {
|
||||
fs::remove_dir_all(freezer_db)
|
||||
.map_err(|err| format!("Failed to remove freezer_db: {}", err))?;
|
||||
}
|
||||
|
||||
// Remove the blobs db.
|
||||
let blobs_db = client_config.get_blobs_db_path();
|
||||
if blobs_db.exists() {
|
||||
fs::remove_dir_all(blobs_db)
|
||||
.map_err(|err| format!("Failed to remove blobs_db: {}", err))?;
|
||||
if confirmation == PURGE_DB_CONFIRMATION {
|
||||
let chain_db = client_config.get_db_path();
|
||||
let freezer_db = client_config.get_freezer_db_path();
|
||||
let blobs_db = client_config.get_blobs_db_path();
|
||||
purge_db(chain_db, freezer_db, blobs_db)?;
|
||||
info!(log, "Database was deleted.");
|
||||
} else {
|
||||
info!(log, "Database was not deleted. Lighthouse will now close.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
log,
|
||||
"The `--purge-db` flag was passed, but Lighthouse is not running \
|
||||
interactively. The database was not purged. Use `--purge-db-force` \
|
||||
to purge the database without requiring confirmation."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1526,3 +1549,26 @@ where
|
||||
.next()
|
||||
.ok_or(format!("Must provide at least one value to {}", flag_name))
|
||||
}
|
||||
|
||||
/// Remove chain, freezer and blobs db.
|
||||
fn purge_db(chain_db: PathBuf, freezer_db: PathBuf, blobs_db: PathBuf) -> Result<(), String> {
|
||||
// Remove the chain_db.
|
||||
if chain_db.exists() {
|
||||
fs::remove_dir_all(chain_db)
|
||||
.map_err(|err| format!("Failed to remove chain_db: {}", err))?;
|
||||
}
|
||||
|
||||
// Remove the freezer db.
|
||||
if freezer_db.exists() {
|
||||
fs::remove_dir_all(freezer_db)
|
||||
.map_err(|err| format!("Failed to remove freezer_db: {}", err))?;
|
||||
}
|
||||
|
||||
// Remove the blobs db.
|
||||
if blobs_db.exists() {
|
||||
fs::remove_dir_all(blobs_db)
|
||||
.map_err(|err| format!("Failed to remove blobs_db: {}", err))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -565,7 +565,11 @@ Flags:
|
||||
being referenced by validator client using the --proposer-node flag.
|
||||
This configuration is for enabling more secure setups.
|
||||
--purge-db
|
||||
If present, the chain database will be deleted. Use with caution.
|
||||
If present, the chain database will be deleted. Requires manual
|
||||
confirmation.
|
||||
--purge-db-force
|
||||
If present, the chain database will be deleted without confirmation.
|
||||
Use with caution.
|
||||
--reconstruct-historic-states
|
||||
After a checkpoint sync, reconstruct historic states in the database.
|
||||
This requires syncing all the way back to genesis.
|
||||
@@ -585,6 +589,8 @@ Flags:
|
||||
server on localhost:5052 and import deposit logs from the execution
|
||||
node. This is equivalent to `--http` on merge-ready networks, or
|
||||
`--http --eth1` pre-merge
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
--subscribe-all-subnets
|
||||
Subscribe to all subnets regardless of validator count. This will also
|
||||
advertise the beacon node as being long-lived subscribed to all
|
||||
|
||||
@@ -136,6 +136,8 @@ Flags:
|
||||
contain sensitive information about your validator and so this flag
|
||||
should be used with caution. For Windows users, the log file
|
||||
permissions will be inherited from the parent folder.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
```
|
||||
|
||||
<style> .content main {max-width:88%;} </style>
|
||||
|
||||
@@ -266,6 +266,8 @@ Flags:
|
||||
by builders, regardless of payload value.
|
||||
--produce-block-v3
|
||||
This flag is deprecated and is no longer in use.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
--unencrypted-http-transport
|
||||
This is a safety flag to ensure that the user is aware that the http
|
||||
transport is unencrypted and using a custom HTTP address is unsafe.
|
||||
|
||||
@@ -123,6 +123,8 @@ Flags:
|
||||
contain sensitive information about your validator and so this flag
|
||||
should be used with caution. For Windows users, the log file
|
||||
permissions will be inherited from the parent folder.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
```
|
||||
|
||||
<style> .content main {max-width:88%;} </style>
|
||||
|
||||
@@ -121,6 +121,8 @@ Flags:
|
||||
contain sensitive information about your validator and so this flag
|
||||
should be used with caution. For Windows users, the log file
|
||||
permissions will be inherited from the parent folder.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
```
|
||||
|
||||
<style> .content main {max-width:88%;} </style>
|
||||
|
||||
@@ -88,8 +88,6 @@ Options:
|
||||
A HTTP(S) address of a validator client using the keymanager-API. This
|
||||
validator client is the "source" and contains the validators that are
|
||||
to be moved.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
--suggested-fee-recipient <ETH1_ADDRESS>
|
||||
All created validators will use this value for the suggested fee
|
||||
recipient. Omit this flag to use the default value from the VC.
|
||||
@@ -142,6 +140,8 @@ Flags:
|
||||
contain sensitive information about your validator and so this flag
|
||||
should be used with caution. For Windows users, the log file
|
||||
permissions will be inherited from the parent folder.
|
||||
--stdin-inputs
|
||||
If present, read all user inputs from stdin instead of tty.
|
||||
```
|
||||
|
||||
<style> .content main {max-width:88%;} </style>
|
||||
|
||||
@@ -35,6 +35,8 @@ const DEFAULT_PASSWORD_LEN: usize = 48;
|
||||
|
||||
pub const MNEMONIC_PROMPT: &str = "Enter the mnemonic phrase:";
|
||||
|
||||
pub const STDIN_INPUTS_FLAG: &str = "stdin-inputs";
|
||||
|
||||
/// Returns the "default" path where a wallet should store its password file.
|
||||
pub fn default_wallet_password_path<P: AsRef<Path>>(wallet_name: &str, secrets_dir: P) -> PathBuf {
|
||||
secrets_dir.as_ref().join(format!("{}.pass", wallet_name))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod cli;
|
||||
mod metrics;
|
||||
|
||||
use account_utils::STDIN_INPUTS_FLAG;
|
||||
use beacon_node::ProductionBeaconNode;
|
||||
use clap::FromArgMatches;
|
||||
use clap::Subcommand;
|
||||
@@ -104,6 +105,16 @@ fn main() {
|
||||
)
|
||||
.long_version(LONG_VERSION.as_str())
|
||||
.display_order(0)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.help_heading(FLAG_HEADER)
|
||||
.hide(cfg!(windows))
|
||||
.global(true)
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("env_log")
|
||||
.short('l')
|
||||
|
||||
@@ -15,7 +15,7 @@ use account_manager::{
|
||||
use account_utils::{
|
||||
eth2_keystore::KeystoreBuilder,
|
||||
validator_definitions::{SigningDefinition, ValidatorDefinition, ValidatorDefinitions},
|
||||
ZeroizeString,
|
||||
ZeroizeString, STDIN_INPUTS_FLAG,
|
||||
};
|
||||
use slashing_protection::{SlashingDatabase, SLASHING_PROTECTION_FILENAME};
|
||||
use std::env;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub use account_utils::STDIN_INPUTS_FLAG;
|
||||
use account_utils::{strip_off_newlines, ZeroizeString};
|
||||
use eth2::lighthouse_vc::std_types::{InterchangeJsonStr, KeystoreJsonStr};
|
||||
use eth2::{
|
||||
@@ -15,7 +16,6 @@ use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
|
||||
pub const IGNORE_DUPLICATES_FLAG: &str = "ignore-duplicates";
|
||||
pub const STDIN_INPUTS_FLAG: &str = "stdin-inputs";
|
||||
pub const COUNT_FLAG: &str = "count";
|
||||
|
||||
/// When the `ethereum/staking-deposit-cli` tool generates deposit data JSON, it adds a
|
||||
|
||||
@@ -105,15 +105,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::Set)
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0)
|
||||
.help_heading(FLAG_HEADER),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(DISABLE_DEPOSITS_FLAG)
|
||||
.long(DISABLE_DEPOSITS_FLAG)
|
||||
|
||||
@@ -184,14 +184,6 @@ pub fn cli_app() -> Command {
|
||||
.action(ArgAction::Set)
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(STDIN_INPUTS_FLAG)
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide(cfg!(windows))
|
||||
.long(STDIN_INPUTS_FLAG)
|
||||
.help("If present, read all user inputs from stdin instead of tty.")
|
||||
.display_order(0),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(BUILDER_BOOST_FACTOR_FLAG)
|
||||
.long(BUILDER_BOOST_FACTOR_FLAG)
|
||||
|
||||
Reference in New Issue
Block a user