stricter configs
This commit is contained in:
parent
69a090522b
commit
f6c2d29d0e
9
TODO.md
9
TODO.md
@ -180,8 +180,11 @@ These are roughly in order of completition
|
||||
- [x] user create script should allow a description field
|
||||
- [x] change stats to using the database
|
||||
- [x] emit user stat on retry
|
||||
- [ ] include if archive query or not in the stats
|
||||
- this is already partially done, but we need to double check it works. preferrably with tests
|
||||
- [x] improve `web3_proxy_cli check_config`
|
||||
- print out warnings if important settings are missing
|
||||
- [x] if unknown config items, error
|
||||
- unknown configs are almost always a mistake. usually from me changing config parsing on my side and old fields not being updated to the new way
|
||||
- [x] also need to change how we disable rpcs since i was using an unknown field
|
||||
- [-] ability to domain lock or ip lock said key
|
||||
- the code to check the database and use these entries already exists, but users don't have a way to set them
|
||||
- [-] new endpoints for users (not totally sure about the exact paths, but these features are all needed):
|
||||
@ -198,6 +201,8 @@ These are roughly in order of completition
|
||||
- allow setting things such as private relay, revert logging, ip/origin/etc checks
|
||||
- [ ] GET logged reverts on an endpoint that requires authentication.
|
||||
- [ ] endpoint for creating/modifying api keys and their advanced security features
|
||||
- [ ] include if archive query or not in the stats
|
||||
- this is already partially done, but we need to double check it works. preferrably with tests
|
||||
- [ ] WARN http_request:request: web3_proxy::block_number: could not get block from params err=unexpected params length id=01GF4HTRKM4JV6NX52XSF9AYMW method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })
|
||||
- ERROR http_request:request:try_send_all_upstream_servers: web3_proxy::rpcs::request: bad response! err=JsonRpcClientError(JsonRpcError(JsonRpcError { code: -32000, message: "INTERNAL_ERROR: existing tx with same hash", data: None })) method=eth_sendRawTransaction rpc=local_erigon_alpha_archive id=01GF4HV03Y4ZNKQV8DW5NDQ5CG method=POST authorized_request=User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 }) self=Web3Connections { conns: {"local_erigon_alpha_archive_ws": Web3Connection { name: "local_erigon_alpha_archive_ws", blocks: "all", .. }, "local_geth_ws": Web3Connection { name: "local_geth_ws", blocks: 64, .. }, "local_erigon_alpha_archive": Web3Connection { name: "local_erigon_alpha_archive", blocks: "all", .. }}, .. } authorized_request=Some(User(Some(SqlxMySqlPoolConnection), AuthorizedKey { ip: 10.11.12.15, origin: None, user_key_id: 4, log_revert_chance: 0.0000 })) request=JsonRpcRequest { id: RawValue(39), method: "eth_sendRawTransaction", .. } request_metadata=Some(RequestMetadata { datetime: 2022-10-11T22:14:57.406829095Z, period_seconds: 60, request_bytes: 633, backend_requests: 0, no_servers: 0, error_response: false, response_bytes: 0, response_millis: 0 }) block_needed=None
|
||||
- why is it failing to get the block from params when its set to None? That should be the simple case
|
||||
|
@ -1,36 +0,0 @@
|
||||
[app]
|
||||
chain_id = 1
|
||||
public_rate_limit_per_minute = 60_000
|
||||
|
||||
[balanced_rpcs]
|
||||
|
||||
[balanced_rpcs.erigon_archive]
|
||||
url = "http://127.0.0.1:8549"
|
||||
# TODO: double check soft_limit on erigon
|
||||
soft_limit = 100_000
|
||||
|
||||
[balanced_rpcs.geth]
|
||||
url = "http://127.0.0.1:8545"
|
||||
soft_limit = 200_000
|
||||
|
||||
[private_rpcs]
|
||||
|
||||
[private_rpcs.eden]
|
||||
url = "https://api.edennetwork.io/v1/"
|
||||
soft_limit = 1_805
|
||||
|
||||
[private_rpcs.eden_beta]
|
||||
url = "https://api.edennetwork.io/v1/beta"
|
||||
soft_limit = 5_861
|
||||
|
||||
[private_rpcs.ethermine]
|
||||
url = "https://rpc.ethermine.org"
|
||||
soft_limit = 5_861
|
||||
|
||||
[private_rpcs.flashbots_fast]
|
||||
url = "https://rpc.flashbots.net"
|
||||
soft_limit = 7074
|
||||
|
||||
[private_rpcs.securerpc]
|
||||
url = "https://gibson.securerpc.com/v1"
|
||||
soft_limit = 4560
|
@ -1,9 +1,9 @@
|
||||
[shared]
|
||||
chain_id = 1
|
||||
|
||||
db_url = "mysql://root:dev_web3_proxy@dev-db:3306/dev_web3_proxy"
|
||||
# TODO: how do we find the optimal db_max_connections? too high actually ends up being slower
|
||||
db_max_connections = 99
|
||||
db_url = "mysql://root:dev_web3_proxy@dev-db:3306/dev_web3_proxy"
|
||||
|
||||
min_sum_soft_limit = 2000
|
||||
min_synced_rpcs = 2
|
||||
@ -15,7 +15,7 @@ volatile_redis_url = "redis://dev-vredis:6379/"
|
||||
redirect_public_url = "https://llamanodes.com/free-rpc-stats"
|
||||
redirect_user_url = "https://llamanodes.com/user-rpc-stats/{{user_id}}"
|
||||
|
||||
frontend_rate_limit_per_minute = 0
|
||||
public_requests_per_minute = 0
|
||||
|
||||
# 1GB of cache
|
||||
response_cache_max_bytes = 10000000000
|
||||
@ -72,27 +72,32 @@ response_cache_max_bytes = 10000000000
|
||||
|
||||
# these worked well on ETH 1.0, but 2.0 ends up not working as well. we will re-assess as more validators turn on private transactions
|
||||
|
||||
[private_rpcs_off.eden]
|
||||
[private_rpcs.eden]
|
||||
disabled = true
|
||||
url = "https://api.edennetwork.io/v1/"
|
||||
soft_limit = 1_805
|
||||
weight = 0
|
||||
|
||||
[private_rpcs_off.eden_beta]
|
||||
[private_rpcs.eden_beta]
|
||||
disabled = true
|
||||
url = "https://api.edennetwork.io/v1/beta"
|
||||
soft_limit = 5_861
|
||||
weight = 0
|
||||
|
||||
[private_rpcs_off.ethermine]
|
||||
[private_rpcs.ethermine]
|
||||
disabled = true
|
||||
url = "https://rpc.ethermine.org"
|
||||
soft_limit = 5_861
|
||||
weight = 0
|
||||
|
||||
[private_rpcs_off.flashbots]
|
||||
[private_rpcs.flashbots]
|
||||
disabled = true
|
||||
url = "https://rpc.flashbots.net/fast"
|
||||
soft_limit = 7074
|
||||
weight = 0
|
||||
|
||||
[private_rpcs_off.securerpc]
|
||||
[private_rpcs.securerpc]
|
||||
disabled = true
|
||||
url = "https://gibson.securerpc.com/v1"
|
||||
soft_limit = 4560
|
||||
weight = 0
|
||||
|
@ -349,9 +349,8 @@ impl Web3ProxyApp {
|
||||
block_map,
|
||||
// subscribing to new heads here won't work well. if they are fast, they might be ahead of balanced_rpcs
|
||||
None,
|
||||
// minimum doesn't really matter on private rpcs
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
// TODO: subscribe to pending transactions on the private rpcs? they seem to have low rate limits
|
||||
None,
|
||||
pending_transactions.clone(),
|
||||
@ -376,7 +375,11 @@ impl Web3ProxyApp {
|
||||
let rpc_rrl = RedisRateLimiter::new(
|
||||
"web3_proxy",
|
||||
"frontend",
|
||||
top_config.app.frontend_rate_limit_per_minute,
|
||||
// TODO: think about this unwrapping
|
||||
top_config
|
||||
.app
|
||||
.public_requests_per_minute
|
||||
.unwrap_or(u64::MAX),
|
||||
60.0,
|
||||
redis_pool.clone(),
|
||||
);
|
||||
|
@ -215,10 +215,10 @@ mod tests {
|
||||
let app_config = TopConfig {
|
||||
app: AppConfig {
|
||||
chain_id: 31337,
|
||||
default_requests_per_minute: Some(6_000_000),
|
||||
default_user_requests_per_minute: Some(6_000_000),
|
||||
min_sum_soft_limit: 1,
|
||||
min_synced_rpcs: 1,
|
||||
frontend_rate_limit_per_minute: 1_000_000,
|
||||
public_requests_per_minute: Some(1_000_000),
|
||||
response_cache_max_bytes: 10_usize.pow(7),
|
||||
redirect_public_url: Some("example.com/".to_string()),
|
||||
redirect_user_url: Some("example.com/{{user_id}}".to_string()),
|
||||
@ -227,11 +227,11 @@ mod tests {
|
||||
balanced_rpcs: HashMap::from([
|
||||
(
|
||||
"anvil".to_string(),
|
||||
Web3ConnectionConfig::new(anvil.endpoint(), 100, None, 1, Some(false)),
|
||||
Web3ConnectionConfig::new(false, anvil.endpoint(), 100, None, 1, Some(false)),
|
||||
),
|
||||
(
|
||||
"anvil_ws".to_string(),
|
||||
Web3ConnectionConfig::new(anvil.ws_endpoint(), 100, None, 0, Some(true)),
|
||||
Web3ConnectionConfig::new(false, anvil.ws_endpoint(), 100, None, 0, Some(true)),
|
||||
),
|
||||
]),
|
||||
private_rpcs: None,
|
||||
|
@ -1,13 +1,13 @@
|
||||
use argh::FromArgs;
|
||||
use std::fs;
|
||||
use tracing::info;
|
||||
use tracing::{info, warn};
|
||||
use web3_proxy::config::TopConfig;
|
||||
|
||||
#[derive(FromArgs, PartialEq, Eq, Debug)]
|
||||
/// Second subcommand.
|
||||
#[argh(subcommand, name = "check_config")]
|
||||
pub struct CheckConfigSubCommand {
|
||||
#[argh(option)]
|
||||
#[argh(positional)]
|
||||
/// path to the configuration toml.
|
||||
path: String,
|
||||
}
|
||||
@ -18,7 +18,56 @@ impl CheckConfigSubCommand {
|
||||
let top_config: String = fs::read_to_string(self.path)?;
|
||||
let top_config: TopConfig = toml::from_str(&top_config)?;
|
||||
|
||||
info!("config: {:?}", top_config);
|
||||
// TODO: pretty print
|
||||
info!("config: {:#?}", top_config);
|
||||
|
||||
if top_config.app.db_url.is_none() {
|
||||
warn!("app.db_url is not set! Some features disabled")
|
||||
}
|
||||
|
||||
match top_config.app.public_requests_per_minute {
|
||||
None => {
|
||||
info!("app.public_requests_per_minute is None. Fully open to public requests!")
|
||||
}
|
||||
Some(0) => {
|
||||
info!("app.public_requests_per_minute is 0. Public requests are blocked!")
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
|
||||
match top_config.app.default_user_requests_per_minute {
|
||||
None => {
|
||||
info!("app.default_user_requests_per_minute is None. Fully open to registered requests!")
|
||||
}
|
||||
Some(0) => warn!("app.default_user_requests_per_minute is 0. Registered user's requests are blocked! Are you sure you want that?"),
|
||||
Some(_) => {
|
||||
// TODO: make sure this isn't < anonymous requests per minute
|
||||
}
|
||||
}
|
||||
|
||||
match top_config.app.invite_code {
|
||||
None => info!("app.invite_code is None. Registration is open"),
|
||||
Some(_) => info!("app.invite_code is set. Registration is limited"),
|
||||
}
|
||||
|
||||
// TODO: check min_sum_soft_limit is a reasonable amount
|
||||
// TODO: check min_synced_rpcs is a reasonable amount
|
||||
// TODO: check frontend_rate_limit_per_minute is a reasonable amount. requires redis
|
||||
// TODO: check login_rate_limit_per_minute is a reasonable amount. requires redis
|
||||
|
||||
if top_config.app.volatile_redis_url.is_none() {
|
||||
warn!("app.volatile_redis_url is not set! Some features disabled")
|
||||
}
|
||||
|
||||
// TODO: check response_cache_max_bytes is a reasonable amount
|
||||
|
||||
if top_config.app.redirect_public_url.is_none() {
|
||||
warn!("app.redirect_public_url is None. Anonyoumous users will get an error page instead of a redirect")
|
||||
}
|
||||
|
||||
if top_config.app.redirect_user_url.is_none() {
|
||||
warn!("app.redirect_public_url is None. Registered users will get an error page instead of a redirect")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ pub struct CliConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TopConfig {
|
||||
pub app: AppConfig,
|
||||
pub balanced_rpcs: HashMap<String, Web3ConnectionConfig>,
|
||||
@ -47,6 +48,7 @@ pub struct TopConfig {
|
||||
/// shared configuration between Web3Connections
|
||||
// TODO: no String, only &str
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AppConfig {
|
||||
/// EVM chain id. 1 for ETH
|
||||
/// TODO: better type for chain_id? max of `u64::MAX / 2 - 36` https://github.com/ethereum/EIPs/issues/2294
|
||||
@ -63,7 +65,7 @@ pub struct AppConfig {
|
||||
/// Default request limit for registered users.
|
||||
/// 0 = block all requests
|
||||
/// None = allow all requests
|
||||
pub default_requests_per_minute: Option<u64>,
|
||||
pub default_user_requests_per_minute: Option<u64>,
|
||||
/// Restrict user registration.
|
||||
/// None = no code needed
|
||||
pub invite_code: Option<String>,
|
||||
@ -74,9 +76,10 @@ pub struct AppConfig {
|
||||
#[serde(default = "default_min_synced_rpcs")]
|
||||
pub min_synced_rpcs: usize,
|
||||
/// Request limit for anonymous users.
|
||||
/// Set to 0 to block all anonymous requests.
|
||||
#[serde(default = "default_frontend_rate_limit_per_minute")]
|
||||
pub frontend_rate_limit_per_minute: u64,
|
||||
/// Some(0) = block all requests
|
||||
/// None = allow all requests
|
||||
#[serde(default = "default_public_requests_per_minute")]
|
||||
pub public_requests_per_minute: Option<u64>,
|
||||
/// Rate limit for the login entrypoint.
|
||||
/// This is separate from the rpc limits.
|
||||
#[serde(default = "default_login_rate_limit_per_minute")]
|
||||
@ -107,8 +110,8 @@ fn default_min_synced_rpcs() -> usize {
|
||||
}
|
||||
|
||||
/// 0 blocks anonymous requests by default.
|
||||
fn default_frontend_rate_limit_per_minute() -> u64 {
|
||||
0
|
||||
fn default_public_requests_per_minute() -> Option<u64> {
|
||||
Some(0)
|
||||
}
|
||||
|
||||
/// Having a low amount of requests per minute for login is safest.
|
||||
@ -122,19 +125,25 @@ fn default_response_cache_max_bytes() -> usize {
|
||||
10_usize.pow(8)
|
||||
}
|
||||
|
||||
/// Configuration for a backend web3 RPC server
|
||||
#[derive(Debug, Deserialize, Constructor)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Web3ConnectionConfig {
|
||||
/// simple way to disable a connection without deleting the row
|
||||
#[serde(default)]
|
||||
pub disabled: bool,
|
||||
/// websocket (or http if no websocket)
|
||||
url: String,
|
||||
pub url: String,
|
||||
/// the requests per second at which the server starts slowing down
|
||||
soft_limit: u32,
|
||||
pub soft_limit: u32,
|
||||
/// the requests per second at which the server throws errors (rate limit or otherwise)
|
||||
hard_limit: Option<u64>,
|
||||
pub hard_limit: Option<u64>,
|
||||
/// All else equal, a server with a lower weight receives requests
|
||||
weight: u32,
|
||||
pub weight: u32,
|
||||
/// Subscribe to the firehose of pending transactions
|
||||
/// Don't do this with free rpcs
|
||||
subscribe_txs: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub subscribe_txs: Option<bool>,
|
||||
}
|
||||
|
||||
impl Web3ConnectionConfig {
|
||||
|
@ -221,7 +221,7 @@ pub async fn user_login_post(
|
||||
let uk = user_keys::ActiveModel {
|
||||
user_id: sea_orm::Set(u.id),
|
||||
api_key: sea_orm::Set(user_key.into()),
|
||||
requests_per_minute: sea_orm::Set(app.config.default_requests_per_minute),
|
||||
requests_per_minute: sea_orm::Set(app.config.default_user_requests_per_minute),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -113,7 +113,11 @@ impl Web3Connections {
|
||||
// TODO: move this into a helper function. then we can use it when configs change (will need a remove function too)
|
||||
let spawn_handles: Vec<_> = server_configs
|
||||
.into_iter()
|
||||
.map(|(server_name, server_config)| {
|
||||
.filter_map(|(server_name, server_config)| {
|
||||
if server_config.disabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
let http_client = http_client.clone();
|
||||
let redis_pool = redis_pool.clone();
|
||||
let http_interval_sender = http_interval_sender.clone();
|
||||
@ -128,7 +132,7 @@ impl Web3Connections {
|
||||
let block_map = block_map.clone();
|
||||
let open_request_handle_metrics = open_request_handle_metrics.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let handle = tokio::spawn(async move {
|
||||
server_config
|
||||
.spawn(
|
||||
server_name,
|
||||
@ -142,7 +146,9 @@ impl Web3Connections {
|
||||
open_request_handle_metrics,
|
||||
)
|
||||
.await
|
||||
})
|
||||
});
|
||||
|
||||
Some(handle)
|
||||
})
|
||||
.collect();
|
||||
|
||||
@ -169,9 +175,10 @@ impl Web3Connections {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: now this errors for private rpcs when we disable all!
|
||||
if connections.len() < min_synced_rpcs {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Only {}/{} connections!",
|
||||
"Only {}/{} connections! Add more connections or reduce min_synced_rpcs.",
|
||||
connections.len(),
|
||||
min_synced_rpcs
|
||||
));
|
||||
|
Loading…
Reference in New Issue
Block a user