diff --git a/Cargo.lock b/Cargo.lock index 0813097ad..0758be38f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7721,6 +7721,7 @@ dependencies = [ "r2d2", "r2d2_sqlite", "serde_json", + "serial_test", "test-log", "tokio", "tracing", diff --git a/ethportal-api/src/types/jsonrpc/endpoints.rs b/ethportal-api/src/types/jsonrpc/endpoints.rs index b1f83719a..6159eb1d0 100644 --- a/ethportal-api/src/types/jsonrpc/endpoints.rs +++ b/ethportal-api/src/types/jsonrpc/endpoints.rs @@ -14,42 +14,42 @@ pub enum Discv5Endpoint { /// State network JSON-RPC endpoints. Start with "portal_state" prefix #[derive(Debug, PartialEq, Eq, Clone)] pub enum StateEndpoint { + /// params: None + RoutingTableInfo, + /// params: [enr] + Ping(Enr), /// params: [enr] AddEnr(Enr), - /// params: None - DataRadius, /// params: [node_id] DeleteEnr(NodeId), - /// params: [enr, content_key] - FindContent(Enr, StateContentKey), + /// params: [node_id] + GetEnr(NodeId), + /// params: [node_id] + LookupEnr(NodeId), /// params: [enr, distances] FindNodes(Enr, Vec), /// params: [node_id] - GetEnr(NodeId), + RecursiveFindNodes(NodeId), + /// params: None + DataRadius, /// params: content_key LocalContent(StateContentKey), - /// params: [node_id] - LookupEnr(NodeId), - /// params: [content_key, content_value] - Gossip(StateContentKey, StateContentValue), - /// params: [content_key, content_value] - TraceGossip(StateContentKey, StateContentValue), /// params: [enr, content_key] - Offer(Enr, StateContentKey, Option), - /// params: [enr] - Ping(Enr), + FindContent(Enr, StateContentKey), /// params: content_key RecursiveFindContent(StateContentKey), /// params: content_key TraceRecursiveFindContent(StateContentKey), /// params: [content_key, content_value] Store(StateContentKey, StateContentValue), - /// params: None - RoutingTableInfo, + /// params: [enr, content_key] + Offer(Enr, StateContentKey, Option), + /// params: [content_key, content_value] + Gossip(StateContentKey, StateContentValue), + /// params: [content_key, content_value] + TraceGossip(StateContentKey, StateContentValue), /// params: [offset, limit] PaginateLocalContentKeys(u64, u64), - /// params: [node_id] - RecursiveFindNodes(NodeId), } /// History network JSON-RPC endpoints. Start with "portal_history" prefix diff --git a/ethportal-peertest/src/lib.rs b/ethportal-peertest/src/lib.rs index b5c132465..8df6ef6cf 100644 --- a/ethportal-peertest/src/lib.rs +++ b/ethportal-peertest/src/lib.rs @@ -80,7 +80,7 @@ fn generate_trin_config(id: u16, bootnode_enr: Option<&Enr>) -> TrinConfig { let trin_config_args = vec![ "trin", "--networks", - "history,beacon", + "history,beacon,state", "--external-address", external_addr.as_str(), "--bootnodes", @@ -102,7 +102,7 @@ fn generate_trin_config(id: u16, bootnode_enr: Option<&Enr>) -> TrinConfig { let trin_config_args = vec![ "trin", "--networks", - "history,beacon", + "history,beacon,state", "--external-address", external_addr.as_str(), "--bootnodes", diff --git a/ethportal-peertest/src/scenarios/basic.rs b/ethportal-peertest/src/scenarios/basic.rs index fbd4ee6b9..e7b3c2ce8 100644 --- a/ethportal-peertest/src/scenarios/basic.rs +++ b/ethportal-peertest/src/scenarios/basic.rs @@ -1,8 +1,9 @@ use crate::{utils::fixture_header_with_proof, Peertest}; use ethereum_types::{H256, U256}; use ethportal_api::{ - types::distance::Distance, BlockHeaderKey, Discv5ApiClient, HistoryContentKey, - HistoryNetworkApiClient, PossibleHistoryContentValue, Web3ApiClient, + types::{distance::Distance, portal_wire::ProtocolId}, + BeaconNetworkApiClient, BlockHeaderKey, Discv5ApiClient, HistoryContentKey, + HistoryNetworkApiClient, PossibleHistoryContentValue, StateNetworkApiClient, Web3ApiClient, }; use jsonrpsee::async_client::Client; use ssz::Encode; @@ -28,53 +29,103 @@ pub async fn test_discv5_routing_table_info(target: &Client) { assert!(result.local_node_id.starts_with("0x")); } -pub async fn test_history_radius(target: &Client) { - info!("Testing portal_historyRadius"); - let result = target.radius().await.unwrap(); +pub async fn test_routing_table_info(protocol: ProtocolId, target: &Client) { + info!("Testing routing_table_info for {protocol}"); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::routing_table_info(target), + ProtocolId::History => HistoryNetworkApiClient::routing_table_info(target), + ProtocolId::State => StateNetworkApiClient::routing_table_info(target), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); + assert!(result.local_node_id.starts_with("0x")); +} + +pub async fn test_radius(protocol: ProtocolId, target: &Client) { + info!("Testing radius for {protocol}"); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::radius(target), + ProtocolId::History => HistoryNetworkApiClient::radius(target), + ProtocolId::State => StateNetworkApiClient::radius(target), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert_eq!( result, U256::from_big_endian(Distance::MAX.as_ssz_bytes().as_slice()) ); } -pub async fn test_history_add_enr(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyAddEnr"); - let result = HistoryNetworkApiClient::add_enr(target, peertest.bootnode.enr.clone()) - .await - .unwrap(); +pub async fn test_add_enr(protocol: ProtocolId, target: &Client, peertest: &Peertest) { + info!("Testing add_enr for {protocol}"); + let bootnode_enr = peertest.bootnode.enr.clone(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::add_enr(target, bootnode_enr), + ProtocolId::History => HistoryNetworkApiClient::add_enr(target, bootnode_enr), + ProtocolId::State => StateNetworkApiClient::add_enr(target, bootnode_enr), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert!(result); } -pub async fn test_history_get_enr(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyGetEnr"); - let result = HistoryNetworkApiClient::get_enr(target, peertest.bootnode.enr.node_id()) - .await - .unwrap(); +pub async fn test_get_enr(protocol: ProtocolId, target: &Client, peertest: &Peertest) { + info!("Testing get_enr for {protocol}"); + let node_id = peertest.bootnode.enr.node_id(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::get_enr(target, node_id), + ProtocolId::History => HistoryNetworkApiClient::get_enr(target, node_id), + ProtocolId::State => StateNetworkApiClient::get_enr(target, node_id), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert_eq!(result, peertest.bootnode.enr); } -pub async fn test_history_delete_enr(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyDeleteEnr"); - let result = HistoryNetworkApiClient::delete_enr(target, peertest.bootnode.enr.node_id()) - .await - .unwrap(); +pub async fn test_delete_enr(protocol: ProtocolId, target: &Client, peertest: &Peertest) { + info!("Testing delete_enr for {protocol}"); + let node_id = peertest.bootnode.enr.node_id(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::delete_enr(target, node_id), + ProtocolId::History => HistoryNetworkApiClient::delete_enr(target, node_id), + ProtocolId::State => StateNetworkApiClient::delete_enr(target, node_id), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert!(result); } -pub async fn test_history_lookup_enr(peertest: &Peertest) { - info!("Testing portal_historyLookupEnr"); - let result = HistoryNetworkApiClient::lookup_enr( - &peertest.bootnode.ipc_client, - peertest.nodes[0].enr.node_id(), - ) +pub async fn test_lookup_enr(protocol: ProtocolId, peertest: &Peertest) { + info!("Testing lookup_enr for {protocol}"); + let target = &peertest.bootnode.ipc_client; + let node_id = peertest.nodes[0].enr.node_id(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::lookup_enr(target, node_id), + ProtocolId::History => HistoryNetworkApiClient::lookup_enr(target, node_id), + ProtocolId::State => StateNetworkApiClient::lookup_enr(target, node_id), + _ => panic!("Unexpected protocol: {protocol}"), + } .await .unwrap(); assert_eq!(result, peertest.nodes[0].enr); } -pub async fn test_history_ping(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyPing"); - let result = target.ping(peertest.bootnode.enr.clone()).await.unwrap(); +pub async fn test_ping(protocol: ProtocolId, target: &Client, peertest: &Peertest) { + info!("Testing ping for {protocol}"); + let bootnode_enr = peertest.bootnode.enr.clone(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::ping(target, bootnode_enr), + ProtocolId::History => HistoryNetworkApiClient::ping(target, bootnode_enr), + ProtocolId::State => StateNetworkApiClient::ping(target, bootnode_enr), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert_eq!( result.data_radius, U256::from_big_endian(Distance::MAX.as_ssz_bytes().as_slice()) @@ -82,37 +133,45 @@ pub async fn test_history_ping(target: &Client, peertest: &Peertest) { assert_eq!(result.enr_seq, 1); } -pub async fn test_history_find_nodes(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyFindNodes"); - let result = target - .find_nodes(peertest.bootnode.enr.clone(), vec![256]) - .await - .unwrap(); +pub async fn test_find_nodes(protocol: ProtocolId, target: &Client, peertest: &Peertest) { + info!("Testing find_nodes for {protocol}"); + let bootnode_enr = peertest.bootnode.enr.clone(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::find_nodes(target, bootnode_enr, vec![256]), + ProtocolId::History => HistoryNetworkApiClient::find_nodes(target, bootnode_enr, vec![256]), + ProtocolId::State => StateNetworkApiClient::find_nodes(target, bootnode_enr, vec![256]), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert!(result.contains(&peertest.nodes[0].enr)); } -pub async fn test_history_find_nodes_zero_distance(target: &Client, peertest: &Peertest) { - info!("Testing portal_historyFindNodes with zero distance"); - let result = target - .find_nodes(peertest.bootnode.enr.clone(), vec![0]) - .await - .unwrap(); +pub async fn test_find_nodes_zero_distance( + protocol: ProtocolId, + target: &Client, + peertest: &Peertest, +) { + info!("Testing find_nodes with zero distance for {protocol}"); + let bootnode_enr = peertest.bootnode.enr.clone(); + let result = match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::find_nodes(target, bootnode_enr, vec![0]), + ProtocolId::History => HistoryNetworkApiClient::find_nodes(target, bootnode_enr, vec![0]), + ProtocolId::State => StateNetworkApiClient::find_nodes(target, bootnode_enr, vec![0]), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap(); assert!(result.contains(&peertest.bootnode.enr)); } pub async fn test_history_store(target: &Client) { info!("Testing portal_historyStore"); let (content_key, content_value) = fixture_header_with_proof(); - let result = target.store(content_key, content_value).await.unwrap(); - assert!(result); -} - -pub async fn test_history_routing_table_info(target: &Client) { - info!("Testing portal_historyRoutingTableInfo"); - let result = HistoryNetworkApiClient::routing_table_info(target) + let result = HistoryNetworkApiClient::store(target, content_key, content_value) .await .unwrap(); - assert!(result.local_node_id.starts_with("0x")); + assert!(result); } pub async fn test_history_local_content_absent(target: &Client) { @@ -120,7 +179,9 @@ pub async fn test_history_local_content_absent(target: &Client) { let content_key = HistoryContentKey::BlockHeaderWithProof(BlockHeaderKey { block_hash: H256::random().into(), }); - let result = target.local_content(content_key).await.unwrap(); + let result = HistoryNetworkApiClient::local_content(target, content_key) + .await + .unwrap(); if let PossibleHistoryContentValue::ContentPresent(_) = result { panic!("Expected absent content"); diff --git a/ethportal-peertest/src/scenarios/find.rs b/ethportal-peertest/src/scenarios/find.rs index 74377b874..84ee77cf1 100644 --- a/ethportal-peertest/src/scenarios/find.rs +++ b/ethportal-peertest/src/scenarios/find.rs @@ -6,11 +6,42 @@ use tracing::info; use crate::{utils::fixture_header_with_proof, Peertest}; use ethportal_api::{ - types::history::{ContentInfo, TraceContentInfo}, + types::{history::ContentInfo, portal_wire::ProtocolId}, utils::bytes::hex_decode, - HistoryNetworkApiClient, OverlayContentKey, PossibleHistoryContentValue, + BeaconNetworkApiClient, Enr, HistoryNetworkApiClient, OverlayContentKey, + PossibleHistoryContentValue, StateNetworkApiClient, }; +pub async fn test_recursive_find_nodes_self(protocol: ProtocolId, peertest: &Peertest) { + info!("Testing recursive find nodes self for {protocol}"); + let target_enr = peertest.bootnode.enr.clone(); + let target_node_id = NodeId::from(target_enr.node_id().raw()); + let result = + call_recursive_find_nodes(protocol, &peertest.bootnode.ipc_client, target_node_id).await; + assert_eq!(result, vec![target_enr]); +} + +pub async fn test_recursive_find_nodes_peer(protocol: ProtocolId, peertest: &Peertest) { + info!("Testing recursive find nodes peer for {protocol}"); + let target_enr = peertest.nodes[0].enr.clone(); + let target_node_id = NodeId::from(target_enr.node_id().raw()); + let result = + call_recursive_find_nodes(protocol, &peertest.bootnode.ipc_client, target_node_id).await; + assert_eq!(result, vec![target_enr]); +} + +pub async fn test_recursive_find_nodes_random(protocol: ProtocolId, peertest: &Peertest) { + info!("Testing recursive find nodes random for {protocol}"); + let mut bytes = [0u8; 32]; + let random_node_id = + hex_decode("0xcac75e7e776d84fba55a3104bdccfd716537bca3ad8465113f67f04d62694183").unwrap(); + bytes.copy_from_slice(&random_node_id); + let target_node_id = NodeId::from(bytes); + let result = + call_recursive_find_nodes(protocol, &peertest.bootnode.ipc_client, target_node_id).await; + assert_eq!(result.len(), 2); +} + pub async fn test_find_content_return_enr(target: &Client, peertest: &Peertest) { info!("Testing find content returns enrs properly"); @@ -31,9 +62,12 @@ pub async fn test_find_content_return_enr(target: &Client, peertest: &Peertest) Err(err) => panic!("{err}"), } - let result = target - .find_content(peertest.bootnode.enr.clone(), content_key.clone()) - .await; + let result = HistoryNetworkApiClient::find_content( + target, + peertest.bootnode.enr.clone(), + content_key.clone(), + ) + .await; let enrs = if let Ok(ContentInfo::Enrs { enrs }) = result { enrs @@ -47,66 +81,26 @@ pub async fn test_find_content_return_enr(target: &Client, peertest: &Peertest) } } -pub async fn test_recursive_find_nodes_self(peertest: &Peertest) { - info!("Testing recursive find nodes self"); - let target_enr = peertest.bootnode.enr.clone(); - let target_node_id = NodeId::from(target_enr.node_id().raw()); - let result = peertest - .bootnode - .ipc_client - .recursive_find_nodes(target_node_id) - .await - .unwrap(); - assert_eq!(result, vec![target_enr]); -} - -pub async fn test_recursive_find_nodes_peer(peertest: &Peertest) { - info!("Testing recursive find nodes peer"); - let target_enr = peertest.nodes[0].enr.clone(); - let target_node_id = NodeId::from(target_enr.node_id().raw()); - let result = peertest - .bootnode - .ipc_client - .recursive_find_nodes(target_node_id) - .await - .unwrap(); - assert_eq!(result, vec![target_enr]); -} - -pub async fn test_recursive_find_nodes_random(peertest: &Peertest) { - info!("Testing recursive find nodes random"); - let mut bytes = [0u8; 32]; - let random_node_id = - hex_decode("0xcac75e7e776d84fba55a3104bdccfd716537bca3ad8465113f67f04d62694183").unwrap(); - bytes.copy_from_slice(&random_node_id); - let target_node_id = NodeId::from(bytes); - let result = peertest - .bootnode - .ipc_client - .recursive_find_nodes(target_node_id) - .await - .unwrap(); - assert_eq!(result.len(), 2); -} - pub async fn test_trace_recursive_find_content(peertest: &Peertest) { info!("Testing trace recursive find content"); let (content_key, content_value) = fixture_header_with_proof(); - let store_result = peertest - .bootnode - .ipc_client - .store(content_key.clone(), content_value.clone()) - .await - .unwrap(); + let store_result = HistoryNetworkApiClient::store( + &peertest.bootnode.ipc_client, + content_key.clone(), + content_value.clone(), + ) + .await + .unwrap(); assert!(store_result); let query_start_time = SystemTime::now(); - let trace_content_info: TraceContentInfo = peertest.nodes[0] - .ipc_client - .trace_recursive_find_content(content_key.clone()) - .await - .unwrap(); + let trace_content_info = HistoryNetworkApiClient::trace_recursive_find_content( + &peertest.nodes[0].ipc_client, + content_key.clone(), + ) + .await + .unwrap(); assert!(!trace_content_info.utp_transfer); let content = trace_content_info.content; @@ -149,8 +143,7 @@ pub async fn test_trace_recursive_find_content_for_absent_content(peertest: &Pee let client = &peertest.nodes[0].ipc_client; let (content_key, _) = fixture_header_with_proof(); - let result = client - .trace_recursive_find_content(content_key) + let result = HistoryNetworkApiClient::trace_recursive_find_content(client, content_key) .await .unwrap(); @@ -163,21 +156,22 @@ pub async fn test_trace_recursive_find_content_for_absent_content(peertest: &Pee pub async fn test_trace_recursive_find_content_local_db(peertest: &Peertest) { let (content_key, content_value) = fixture_header_with_proof(); - let store_result = peertest - .bootnode - .ipc_client - .store(content_key.clone(), content_value.clone()) - .await - .unwrap(); + let store_result = HistoryNetworkApiClient::store( + &peertest.bootnode.ipc_client, + content_key.clone(), + content_value.clone(), + ) + .await + .unwrap(); assert!(store_result); - let trace_content_info: TraceContentInfo = peertest - .bootnode - .ipc_client - .trace_recursive_find_content(content_key) - .await - .unwrap(); + let trace_content_info = HistoryNetworkApiClient::trace_recursive_find_content( + &peertest.bootnode.ipc_client, + content_key, + ) + .await + .unwrap(); assert!(!trace_content_info.utp_transfer); assert_eq!( trace_content_info.content, @@ -190,3 +184,18 @@ pub async fn test_trace_recursive_find_content_local_db(peertest: &Peertest) { let expected_origin_id: NodeId = peertest.bootnode.enr.node_id(); assert_eq!(expected_origin_id, origin); } + +async fn call_recursive_find_nodes( + protocol: ProtocolId, + client: &Client, + node_id: NodeId, +) -> Vec { + match protocol { + ProtocolId::Beacon => BeaconNetworkApiClient::recursive_find_nodes(client, node_id), + ProtocolId::History => HistoryNetworkApiClient::recursive_find_nodes(client, node_id), + ProtocolId::State => StateNetworkApiClient::recursive_find_nodes(client, node_id), + _ => panic!("Unexpected protocol: {protocol}"), + } + .await + .unwrap() +} diff --git a/tests/self_peertest.rs b/tests/self_peertest.rs index f645b12a4..87bd79d3a 100644 --- a/tests/self_peertest.rs +++ b/tests/self_peertest.rs @@ -5,7 +5,10 @@ use std::{ net::{IpAddr, Ipv4Addr}, }; -use ethportal_api::types::cli::{TrinConfig, DEFAULT_WEB3_HTTP_ADDRESS, DEFAULT_WEB3_IPC_PATH}; +use ethportal_api::types::{ + cli::{TrinConfig, DEFAULT_WEB3_HTTP_ADDRESS, DEFAULT_WEB3_IPC_PATH}, + portal_wire::ProtocolId, +}; use ethportal_peertest as peertest; use ethportal_peertest::Peertest; use jsonrpsee::{async_client::Client, http_client::HttpClient}; @@ -24,25 +27,31 @@ async fn peertest_stateless() { // If a scenario is testing the state of the content database, // it should be added to its own test function. let (peertest, target, handle) = setup_peertest().await; + peertest::scenarios::paginate::test_paginate_local_storage(&peertest).await; peertest::scenarios::basic::test_web3_client_version(&target).await; peertest::scenarios::basic::test_discv5_node_info(&peertest).await; peertest::scenarios::basic::test_discv5_routing_table_info(&target).await; - peertest::scenarios::basic::test_history_radius(&target).await; - peertest::scenarios::basic::test_history_add_enr(&target, &peertest).await; - peertest::scenarios::basic::test_history_get_enr(&target, &peertest).await; - peertest::scenarios::basic::test_history_delete_enr(&target, &peertest).await; - peertest::scenarios::basic::test_history_lookup_enr(&peertest).await; - peertest::scenarios::basic::test_history_ping(&target, &peertest).await; - peertest::scenarios::basic::test_history_find_nodes(&target, &peertest).await; - peertest::scenarios::basic::test_history_find_nodes_zero_distance(&target, &peertest).await; + peertest::scenarios::eth_rpc::test_eth_chain_id(&peertest).await; + + for protocol in [ProtocolId::History, ProtocolId::Beacon, ProtocolId::State] { + peertest::scenarios::basic::test_routing_table_info(protocol, &target).await; + peertest::scenarios::basic::test_radius(protocol, &target).await; + peertest::scenarios::basic::test_add_enr(protocol, &target, &peertest).await; + peertest::scenarios::basic::test_get_enr(protocol, &target, &peertest).await; + peertest::scenarios::basic::test_delete_enr(protocol, &target, &peertest).await; + peertest::scenarios::basic::test_lookup_enr(protocol, &peertest).await; + peertest::scenarios::basic::test_ping(protocol, &target, &peertest).await; + peertest::scenarios::basic::test_find_nodes(protocol, &target, &peertest).await; + peertest::scenarios::basic::test_find_nodes_zero_distance(protocol, &target, &peertest) + .await; + peertest::scenarios::find::test_recursive_find_nodes_self(protocol, &peertest).await; + peertest::scenarios::find::test_recursive_find_nodes_peer(protocol, &peertest).await; + peertest::scenarios::find::test_recursive_find_nodes_random(protocol, &peertest).await; + } + peertest::scenarios::basic::test_history_store(&target).await; - peertest::scenarios::basic::test_history_routing_table_info(&target).await; peertest::scenarios::basic::test_history_local_content_absent(&target).await; - peertest::scenarios::find::test_recursive_find_nodes_self(&peertest).await; - peertest::scenarios::find::test_recursive_find_nodes_peer(&peertest).await; - peertest::scenarios::find::test_recursive_find_nodes_random(&peertest).await; - peertest::scenarios::eth_rpc::test_eth_chain_id(&peertest).await; peertest.exit_all_nodes(); handle.stop().unwrap(); } @@ -183,7 +192,7 @@ async fn setup_peertest() -> (peertest::Peertest, Client, RpcServerHandle) { [ "trin", "--networks", - "history,state", + "history,beacon,state", "--external-address", external_addr.as_str(), "--web3-ipc-path", @@ -226,7 +235,7 @@ async fn setup_peertest_bridge() -> (Peertest, HttpClient, RpcServerHandle) { [ "trin", "--networks", - "history,beacon", + "history,beacon,state", "--external-address", external_addr.as_str(), // Run bridge test with http, since bridge doesn't support ipc yet. diff --git a/trin-state/Cargo.toml b/trin-state/Cargo.toml index 21a920964..b7606ecde 100644 --- a/trin-state/Cargo.toml +++ b/trin-state/Cargo.toml @@ -27,6 +27,7 @@ trin-metrics = { path = "../trin-metrics" } trin-validation = { path = "../trin-validation" } utp-rs = { git = "https://github.com/ethereum/utp", tag = "v0.1.0-alpha.9" } serde_json = "1.0.89" +serial_test = "0.5.1" [dev-dependencies] env_logger = "0.9.0" diff --git a/trin-state/src/jsonrpc.rs b/trin-state/src/jsonrpc.rs index 0189eb85f..41bc9cd53 100644 --- a/trin-state/src/jsonrpc.rs +++ b/trin-state/src/jsonrpc.rs @@ -1,10 +1,19 @@ use std::sync::Arc; -use serde_json::Value; +use discv5::{enr::NodeId, Enr}; +use portalnet::overlay_service::OverlayRequestError; +use serde_json::{json, Value}; use tokio::sync::mpsc; use crate::network::StateNetwork; -use ethportal_api::types::jsonrpc::request::StateJsonRpcRequest; +use ethportal_api::{ + jsonrpsee::core::Serialize, + types::{ + distance::Distance, + jsonrpc::{endpoints::StateEndpoint, request::StateJsonRpcRequest}, + portal::{FindNodesInfo, PongInfo}, + }, +}; /// Handles State network JSON-RPC requests pub struct StateRequestHandler { @@ -20,8 +29,96 @@ impl StateRequestHandler { } } - async fn handle_request(_network: Arc, request: StateJsonRpcRequest) { - let response: Result = Err("Not implemented".to_string()); + async fn handle_request(network: Arc, request: StateJsonRpcRequest) { + let response: Result = match request.endpoint { + StateEndpoint::RoutingTableInfo => routing_table_info(network), + StateEndpoint::Ping(enr) => ping(network, enr).await, + StateEndpoint::AddEnr(enr) => add_enr(network, enr), + StateEndpoint::DeleteEnr(node_id) => delete_enr(network, node_id), + StateEndpoint::GetEnr(node_id) => get_enr(network, node_id), + StateEndpoint::LookupEnr(node_id) => lookup_enr(network, node_id).await, + StateEndpoint::FindNodes(enr, distances) => find_nodes(network, enr, distances).await, + StateEndpoint::RecursiveFindNodes(node_id) => { + recursive_find_nodes(network, node_id).await + } + StateEndpoint::DataRadius => radius(network), + _ => Err("Not implemented".to_string()), + }; + let _ = request.resp.send(response); } } + +fn routing_table_info(network: Arc) -> Result { + serde_json::to_value(network.overlay.routing_table_info()).map_err(|err| err.to_string()) +} + +async fn ping(network: Arc, enr: Enr) -> Result { + to_json_result( + "Ping", + network.overlay.send_ping(enr).await.map(|pong| PongInfo { + enr_seq: pong.enr_seq as u32, + data_radius: *Distance::from(pong.custom_payload), + }), + ) +} + +fn add_enr(network: Arc, enr: Enr) -> Result { + to_json_result("AddEnr", network.overlay.add_enr(enr).map(|_| true)) +} + +fn delete_enr(network: Arc, node_id: NodeId) -> Result { + let is_deleted = network.overlay.delete_enr(node_id); + Ok(json!(is_deleted)) +} + +fn get_enr(network: Arc, node_id: NodeId) -> Result { + to_json_result("GetEnr", network.overlay.get_enr(node_id)) +} + +async fn lookup_enr(network: Arc, node_id: NodeId) -> Result { + to_json_result("LookupEnr", network.overlay.lookup_enr(node_id).await) +} + +fn radius(network: Arc) -> Result { + let radius = network.overlay.data_radius(); + Ok(json!(*radius)) +} + +async fn find_nodes( + network: Arc, + enr: Enr, + distances: Vec, +) -> Result { + to_json_result( + "FindNodes", + network + .overlay + .send_find_nodes(enr, distances) + .await + .map(|nodes| { + nodes + .enrs + .into_iter() + .map(Enr::from) + .collect::() + }), + ) +} + +async fn recursive_find_nodes( + network: Arc, + node_id: NodeId, +) -> Result { + let nodes = network.overlay.lookup_node(node_id).await; + Ok(json!(nodes)) +} + +fn to_json_result( + request: &str, + result: Result, +) -> Result { + result + .map(|value| json!(value)) + .map_err(|err| format!("{request} failed: {err:?}")) +} diff --git a/trin-state/src/network.rs b/trin-state/src/network.rs index ce4fae57b..d9ec43242 100644 --- a/trin-state/src/network.rs +++ b/trin-state/src/network.rs @@ -38,10 +38,7 @@ impl StateNetwork { disable_poke: portal_config.disable_poke, ..Default::default() }; - let storage = Arc::new(PLRwLock::new(StateStorage::new( - storage_config, - ProtocolId::State, - )?)); + let storage = Arc::new(PLRwLock::new(StateStorage::new(storage_config)?)); let validator = Arc::new(StateValidator { header_oracle }); let overlay = OverlayProtocol::new( config, diff --git a/trin-state/src/storage.rs b/trin-state/src/storage.rs index 09d0a3810..09439976b 100644 --- a/trin-state/src/storage.rs +++ b/trin-state/src/storage.rs @@ -47,18 +47,15 @@ impl ContentStore for StateStorage { } fn radius(&self) -> Distance { - unimplemented!() + self.radius } } impl StateStorage { - pub fn new( - config: PortalStorageConfig, - protocol: ProtocolId, - ) -> Result { + pub fn new(config: PortalStorageConfig) -> Result { let metrics = StorageMetricsReporter { storage_metrics: PORTALNET_METRICS.storage(), - protocol: protocol.to_string(), + protocol: ProtocolId::State.to_string(), }; Ok(Self { @@ -69,7 +66,7 @@ impl StateStorage { sql_connection_pool: config.sql_connection_pool, distance_fn: config.distance_fn, metrics, - network: protocol, + network: ProtocolId::State, }) } @@ -78,3 +75,45 @@ impl StateStorage { self.metrics.get_summary() } } + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +pub mod test { + use discv5::{enr::CombinedKey, Enr}; + use ethportal_api::types::distance::Distance; + use portalnet::utils::db::{configure_node_data_dir, setup_temp_dir}; + use serial_test::serial; + + use super::*; + + const CAPACITY_MB: u64 = 2; + + fn get_active_node_id(temp_dir: PathBuf) -> NodeId { + let (_, mut pk) = configure_node_data_dir(temp_dir, None).unwrap(); + let pk = CombinedKey::secp256k1_from_bytes(pk.0.as_mut_slice()).unwrap(); + Enr::empty(&pk).unwrap().node_id() + } + + #[test_log::test(tokio::test)] + #[serial] + async fn test_new() -> Result<(), ContentStoreError> { + let temp_dir = setup_temp_dir().unwrap(); + let node_id = get_active_node_id(temp_dir.path().to_path_buf()); + + let storage_config = + PortalStorageConfig::new(CAPACITY_MB, temp_dir.path().to_path_buf(), node_id).unwrap(); + let storage = StateStorage::new(storage_config)?; + + // Assert that configs match the storage object's fields + assert_eq!(storage.node_id, node_id); + assert_eq!( + storage.storage_capacity_in_bytes, + CAPACITY_MB * BYTES_IN_MB_U64 + ); + assert_eq!(storage.radius, Distance::MAX); + + std::mem::drop(storage); + temp_dir.close()?; + Ok(()) + } +}