Skip to content

Commit

Permalink
ethapi: add eth cors settings
Browse files Browse the repository at this point in the history
Signed-off-by: Sander Pick <[email protected]>
  • Loading branch information
sanderpick committed Jun 4, 2024
1 parent 8188e25 commit fd51cba
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 40 deletions.
32 changes: 30 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ futures-util = "0.3"
gcra = "0.4"
hex = "0.4"
hex-literal = "0.4.1"
http = "0.2.12"
im = "15.1.0"
integer-encoding = { version = "3.0.3", default-features = false }
jsonrpc-v2 = { version = "0.11", default-features = false, features = [
Expand Down Expand Up @@ -152,6 +153,7 @@ tokio = { version = "1", features = [
"io-std",
"sync",
] }
tower-http = { version = "0.4.0", features = ["cors"] }
tokio-stream = "0.1.14"
tokio-util = { version = "0.7.8", features = ["compat"] }
tokio-tungstenite = { version = "0.18.0", features = ["native-tls"] }
Expand Down
15 changes: 11 additions & 4 deletions fendermint/app/config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,17 @@ host = "127.0.0.1"
# JSON-RPC (POST) and WebSockets (GET) requests.
port = 8545

[eth.cors]
# A list of origins a cross-domain request can be executed from
# Default value '[]' disables cors support
# Use '["*"]' to allow any origin
allowed_origins = []
# A list of methods the client is allowed to use with cross-domain requests
# Suggested methods if allowing origins: "GET", "OPTIONS", "HEAD", "POST"
allowed_methods = []
# A list of non-simple headers the client is allowed to use with cross-domain requests
# Suggested headers if allowing origins: "Accept", "Authorization", "Content-Type", "Origin"
allowed_headers = []

# IPLD Resolver Configuration
[resolver]
Expand All @@ -154,7 +165,6 @@ local_key = "keys/network.sk"
# so we can derive a name using the rootnet ID and use this as an override.
network_name = ""


# Peer Discovery
[resolver.discovery]
# Bootstrap node addresses for peer discovery, or the entire list for a static network
Expand All @@ -169,7 +179,6 @@ target_connections = 50
# Option to disable Kademlia, for example in a fixed static network.
enable_kademlia = true


# IPC Subnet Membership
[resolver.membership]
# User defined list of subnets which will never be pruned from the cache.
Expand Down Expand Up @@ -214,7 +223,6 @@ max_peers_per_query = 5
# consumer gets an error because it's falling behind.
event_buffer_capacity = 100


# Serving Content
[resolver.content]
# Number of bytes that can be consumed by remote peers in a time period. 0 means no limit.
Expand All @@ -237,7 +245,6 @@ vote_interval = 1
# pausing the syncer, preventing new events to trigger votes.
vote_timeout = 60


# # Setting which are only allowed if the `--network` CLI parameter is `testnet`.
# [testing]

Expand Down
3 changes: 2 additions & 1 deletion fendermint/app/settings/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ serde_json = { workspace = true }
serde_with = { workspace = true }
serial_test = { workspace = true }
tendermint-rpc = { workspace = true }
tower-http = { workspace = true }
tracing = { workspace = true }

fvm_shared = { workspace = true }
fvm_ipld_encoding = { workspace = true }
ipc-api = { workspace = true }
ipc-provider = { workspace = true }
tracing = { workspace = true }

fendermint_vm_encoding = { path = "../../vm/encoding" }
fendermint_vm_topdown = { path = "../../vm/topdown" }
16 changes: 16 additions & 0 deletions fendermint/app/settings/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
// SPDX-License-Identifier: Apache-2.0, MIT

use fvm_shared::econ::TokenAmount;
use ipc_provider::config::deserialize::{
deserialize_cors_headers, deserialize_cors_methods, deserialize_cors_origins,
};
use serde::Deserialize;
use serde_with::{serde_as, DurationSeconds};
use std::time::Duration;
use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin};

use crate::{IsHumanReadable, SocketAddress};

Expand All @@ -18,6 +22,7 @@ pub struct EthSettings {
pub cache_capacity: usize,
pub gas: GasOpt,
pub max_nonce_gap: u64,
pub cors: CorsOpt,
}

#[serde_as]
Expand All @@ -29,3 +34,14 @@ pub struct GasOpt {
pub num_blocks_max_prio_fee: u64,
pub max_fee_hist_size: u64,
}

#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub struct CorsOpt {
#[serde(deserialize_with = "deserialize_cors_origins")]
pub allowed_origins: AllowOrigin,
#[serde(deserialize_with = "deserialize_cors_methods")]
pub allowed_methods: AllowMethods,
#[serde(deserialize_with = "deserialize_cors_headers")]
pub allowed_headers: AllowHeaders,
}
96 changes: 89 additions & 7 deletions fendermint/app/settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ impl Display for SocketAddress {
}
}

impl std::net::ToSocketAddrs for SocketAddress {
type Iter = <String as std::net::ToSocketAddrs>::Iter;
impl ToSocketAddrs for SocketAddress {
type Iter = <String as ToSocketAddrs>::Iter;

fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
self.to_string().to_socket_addrs()
}
}

impl TryInto<std::net::SocketAddr> for SocketAddress {
impl TryInto<SocketAddr> for SocketAddress {
type Error = std::io::Error;

fn try_into(self) -> Result<SocketAddr, Self::Error> {
Expand Down Expand Up @@ -328,7 +328,10 @@ impl Settings {
.list_separator(",") // need to list keys explicitly below otherwise it can't pase simple `String` type
.with_list_parse_key("resolver.connection.external_addresses")
.with_list_parse_key("resolver.discovery.static_addresses")
.with_list_parse_key("resolver.membership.static_subnets"),
.with_list_parse_key("resolver.membership.static_subnets")
.with_list_parse_key("eth.cors.allowed_origins")
.with_list_parse_key("eth.cors.allowed_methods")
.with_list_parse_key("eth.cors.allowed_headers"),
))
// Set the home directory based on what was passed to the CLI,
// so everything in the config can be relative to it.
Expand Down Expand Up @@ -381,7 +384,7 @@ mod tests {

use crate::DbCompaction;

use super::Settings;
use super::{ConfigError, Settings};

fn try_parse_config(run_mode: &str) -> Result<Settings, config::ConfigError> {
let current_dir = PathBuf::from(".");
Expand Down Expand Up @@ -422,21 +425,41 @@ mod tests {
let settings = with_env_vars(vec![
("FM_RESOLVER__CONNECTION__EXTERNAL_ADDRESSES", "/ip4/198.51.100.0/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N,/ip6/2604:1380:2000:7a00::1/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"),
("FM_RESOLVER__DISCOVERY__STATIC_ADDRESSES", "/ip4/198.51.100.1/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N,/ip6/2604:1380:2000:7a00::2/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"),
("FM_RESOLVER__MEMBERSHIP__STATIC_SUBNETS", "/r314/f410fijl3evsntewwhqxy6cx5ijdq5qp5cjlocbgzgey,/r314/f410fwplxlims2wnigaha2gofgktue7hiusmttwridkq"),
("FM_ETH__CORS__ALLOWED_ORIGINS", "https://example.com,https://www.example.org"),
("FM_ETH__CORS__ALLOWED_METHODS", "GET,POST"),
("FM_ETH__CORS__ALLOWED_HEADERS", "Accept,Content-Type"),
// Set a normal string key as well to make sure we have configured the library correctly and it doesn't try to parse everything as a list.
("FM_RESOLVER__NETWORK__NETWORK_NAME", "test"),
], || try_parse_config("")).unwrap();

assert_eq!(settings.resolver.discovery.static_addresses.len(), 2);
assert_eq!(settings.resolver.connection.external_addresses.len(), 2);
assert_eq!(settings.resolver.discovery.static_addresses.len(), 2);
assert_eq!(settings.resolver.membership.static_subnets.len(), 2);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_origins),
"List([\"https://example.com\", \"https://www.example.org\"])"
);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_methods),
"Const(Some(\"GET,POST\"))"
);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_headers),
"Const(Some(\"accept,content-type\"))"
);
}

#[test]
fn parse_empty_comma_separated() {
let settings = with_env_vars(
vec![
("FM_RESOLVER__DISCOVERY__STATIC_ADDRESSES", ""),
("FM_RESOLVER__CONNECTION__EXTERNAL_ADDRESSES", ""),
("FM_RESOLVER__DISCOVERY__STATIC_ADDRESSES", ""),
("FM_RESOLVER__MEMBERSHIP__STATIC_SUBNETS", ""),
("FM_ETH__CORS__ALLOWED_ORIGINS", ""),
("FM_ETH__CORS__ALLOWED_METHODS", ""),
("FM_ETH__CORS__ALLOWED_HEADERS", ""),
],
|| try_parse_config(""),
)
Expand All @@ -445,6 +468,18 @@ mod tests {
assert_eq!(settings.resolver.connection.external_addresses.len(), 0);
assert_eq!(settings.resolver.discovery.static_addresses.len(), 0);
assert_eq!(settings.resolver.membership.static_subnets.len(), 0);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_origins),
"List([])"
);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_methods),
"Const(None)"
);
assert_eq!(
format!("{:?}", settings.eth.cors.allowed_headers),
"Const(None)"
);
}

#[test]
Expand All @@ -471,4 +506,51 @@ mod tests {
multiaddr!(Dns4("bar.ai"), Tcp(5678u16))
);
}

#[test]
fn parse_cors_origins_variants() {
// relative URL without a base
let settings = with_env_vars(
vec![("FM_ETH__CORS__ALLOWED_ORIGINS", "example.com")],
|| try_parse_config(""),
);
assert!(
matches!(settings, Err(ConfigError::Message(ref msg)) if msg == "relative URL without a base")
);

// opaque origin
let settings = with_env_vars(
vec![(
"FM_ETH__CORS__ALLOWED_ORIGINS",
"javascript:console.log(\"invalid origin\")",
)],
|| try_parse_config(""),
);
assert!(
matches!(settings, Err(ConfigError::Message(ref msg)) if msg == "opaque origins are not allowed")
);

// Allow all with "*"
let settings = with_env_vars(vec![("FM_ETH__CORS__ALLOWED_ORIGINS", "*")], || {
try_parse_config("")
});
assert!(settings.is_ok());

// IPv4
let settings = with_env_vars(
vec![("FM_ETH__CORS__ALLOWED_ORIGINS", "http://192.0.2.1:1234")],
|| try_parse_config(""),
);
assert!(settings.is_ok());

// IPv6
let settings = with_env_vars(
vec![(
"FM_ETH__CORS__ALLOWED_ORIGINS",
"http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:1234",
)],
|| try_parse_config(""),
);
assert!(settings.is_ok());
}
}
6 changes: 6 additions & 0 deletions fendermint/app/src/cmd/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ async fn run(settings: EthSettings, client: HybridClient) -> anyhow::Result<()>
num_blocks_max_prio_fee: settings.gas.num_blocks_max_prio_fee,
max_fee_hist_size: settings.gas.max_fee_hist_size,
};
let cors = fendermint_eth_api::CorsOpt {
allowed_origins: settings.cors.allowed_origins,
allowed_methods: settings.cors.allowed_methods,
allowed_headers: settings.cors.allowed_headers,
};
fendermint_eth_api::listen(
settings.listen,
client,
settings.filter_timeout,
settings.cache_capacity,
settings.max_nonce_gap,
gas,
cors,
)
.await
}
Loading

0 comments on commit fd51cba

Please sign in to comment.