stricter configs

This commit is contained in:
Bryan Stitt 2022-10-18 23:27:33 +00:00
parent 69a090522b
commit f6c2d29d0e
9 changed files with 114 additions and 72 deletions

@ -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
));