Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions crates/ethportal-api/src/history.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use discv5::enr::NodeId;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use serde_json::Value;

use crate::{
types::{
enr::Enr,
ping_extensions::extension_types::PingExtensionType,
portal::{
AcceptInfo, DataRadius, FindContentInfo, FindNodesInfo, GetContentInfo, PongInfo,
PutContentInfo, TraceContentInfo,
},
portal_wire::OfferTrace,
},
HistoryContentKey, RawContentValue, RoutingTableInfo,
};

/// Portal History JSON-RPC endpoints
#[rpc(client, server, namespace = "portal")]
pub trait HistoryNetworkApi {
/// Returns meta information about overlay routing table.
#[method(name = "historyRoutingTableInfo")]
async fn routing_table_info(&self) -> RpcResult<RoutingTableInfo>;

/// Returns the node data radios
#[method(name = "historyRadius")]
async fn radius(&self) -> RpcResult<DataRadius>;

/// Write an Ethereum Node Record to the overlay routing table.
#[method(name = "historyAddEnr")]
async fn add_enr(&self, enr: Enr) -> RpcResult<bool>;

/// Fetch the latest ENR associated with the given node ID.
#[method(name = "historyGetEnr")]
async fn get_enr(&self, node_id: NodeId) -> RpcResult<Enr>;

/// Delete Node ID from the overlay routing table.
#[method(name = "historyDeleteEnr")]
async fn delete_enr(&self, node_id: NodeId) -> RpcResult<bool>;

/// Fetch the ENR representation associated with the given Node ID.
#[method(name = "historyLookupEnr")]
async fn lookup_enr(&self, node_id: NodeId) -> RpcResult<Enr>;

/// Send a PING message to the designated node and wait for a PONG response
#[method(name = "historyPing")]
async fn ping(
&self,
enr: Enr,
payload_type: Option<PingExtensionType>,
payload: Option<Value>,
) -> RpcResult<PongInfo>;

/// Send a FINDNODES request for nodes that fall within the given set of distances, to the
/// designated peer and wait for a response
#[method(name = "historyFindNodes")]
async fn find_nodes(&self, enr: Enr, distances: Vec<u16>) -> RpcResult<FindNodesInfo>;

/// Lookup a target node within in the network
#[method(name = "historyRecursiveFindNodes")]
async fn recursive_find_nodes(&self, node_id: NodeId) -> RpcResult<Vec<Enr>>;

/// Send FINDCONTENT message to get the content with a content key.
#[method(name = "historyFindContent")]
async fn find_content(
&self,
enr: Enr,
content_key: HistoryContentKey,
) -> RpcResult<FindContentInfo>;

/// First checks local storage if content is not found lookup a target content key in the
/// network
#[method(name = "historyGetContent")]
async fn get_content(&self, content_key: HistoryContentKey) -> RpcResult<GetContentInfo>;

/// First checks local storage if content is not found lookup a target content key in the
/// network. Return tracing info.
#[method(name = "historyTraceGetContent")]
async fn trace_get_content(
&self,
content_key: HistoryContentKey,
) -> RpcResult<TraceContentInfo>;

/// Send the provided content value to interested peers. Clients may choose to send to some or
/// all peers. Return the number of peers that the content was gossiped to.
#[method(name = "historyPutContent")]
async fn put_content(
&self,
content_key: HistoryContentKey,
content_value: RawContentValue,
) -> RpcResult<PutContentInfo>;

/// Send an OFFER request with given ContentItems, to the designated peer and wait for a
/// response. Does not store the content locally.
/// Returns the content keys bitlist upon successful content transmission or empty bitlist
/// receive.
#[method(name = "historyOffer")]
async fn offer(
&self,
enr: Enr,
content_items: Vec<(HistoryContentKey, RawContentValue)>,
) -> RpcResult<AcceptInfo>;

/// Send an OFFER request with given ContentItems, to the designated peer.
/// Does not store the content locally.
/// Returns trace info for the offer.
#[method(name = "historyTraceOffer")]
async fn trace_offer(
&self,
enr: Enr,
content_key: HistoryContentKey,
content_value: RawContentValue,
) -> RpcResult<OfferTrace>;

/// Store content key with a content data to the local database.
#[method(name = "historyStore")]
async fn store(
&self,
content_key: HistoryContentKey,
content_value: RawContentValue,
) -> RpcResult<bool>;

/// Get a content value from the local database
#[method(name = "historyLocalContent")]
async fn local_content(&self, content_key: HistoryContentKey) -> RpcResult<RawContentValue>;
}
7 changes: 5 additions & 2 deletions crates/ethportal-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern crate lazy_static;
mod beacon;
pub mod discv5;
mod eth;
mod history;
mod legacy_history;
mod state;
#[cfg(test)]
Expand All @@ -21,6 +22,7 @@ mod web3;
pub use beacon::{BeaconNetworkApiClient, BeaconNetworkApiServer};
pub use discv5::{Discv5ApiClient, Discv5ApiServer};
pub use eth::{EthApiClient, EthApiServer};
pub use history::{HistoryNetworkApiClient, HistoryNetworkApiServer};
// Re-exports jsonrpsee crate
pub use jsonrpsee;
pub use legacy_history::{LegacyHistoryNetworkApiClient, LegacyHistoryNetworkApiServer};
Expand All @@ -31,12 +33,13 @@ pub use types::{
content_key::{
beacon::{BeaconContentKey, LightClientBootstrapKey, LightClientUpdatesByRangeKey},
error::ContentKeyError,
legacy_history::{BlockBodyKey, BlockReceiptsKey, LegacyHistoryContentKey},
history::HistoryContentKey,
legacy_history::LegacyHistoryContentKey,
overlay::{IdentityContentKey, OverlayContentKey},
state::StateContentKey,
},
content_value::{
beacon::BeaconContentValue, error::ContentValueError,
beacon::BeaconContentValue, error::ContentValueError, history::HistoryContentValue,
legacy_history::LegacyHistoryContentValue, state::StateContentValue, ContentValue,
},
discv5::*,
Expand Down
65 changes: 65 additions & 0 deletions crates/ethportal-api/src/types/content_key/content_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use alloy::primitives::U256;

const CYCLE_BITS: usize = 16;
const CYCLE_BITS_BITMAP: u64 = (1 << CYCLE_BITS) - 1;

/// Creates content id that can be used for range queries.
pub fn range_content_id(block_number: u64, content_type: u8) -> [u8; 32] {
// extract the 16 least significant bits
let cycle_bits = block_number & CYCLE_BITS_BITMAP;

// extract the 48 most significant bits
let offset_bits = block_number & !CYCLE_BITS_BITMAP;

// make cycle bits most significant, reverse and make offset bits least significant
let content_id_prefix = cycle_bits.rotate_right(CYCLE_BITS as u32) | offset_bits.reverse_bits();

// create content_id as U256
let content_id = U256::from_limbs([content_type as u64, 0, 0, content_id_prefix]);

content_id.to_be_bytes()
}

#[cfg(test)]
mod tests {
use alloy::primitives::{b256, B256};
use rstest::rstest;

use super::*;
use crate::types::content_key::history::{
HISTORY_BLOCK_BODY_KEY_PREFIX, HISTORY_BLOCK_RECEIPTS_KEY_PREFIX,
};

#[rstest]
#[case::zero(0, 0, B256::ZERO)]
#[case::genesis_block_body(
0,
HISTORY_BLOCK_BODY_KEY_PREFIX,
B256::with_last_byte(HISTORY_BLOCK_BODY_KEY_PREFIX)
)]
#[case::genesis_receipts(
0,
HISTORY_BLOCK_RECEIPTS_KEY_PREFIX,
B256::with_last_byte(HISTORY_BLOCK_RECEIPTS_KEY_PREFIX)
)]
#[case::block_number_12345678_block_body(
12_345_678,
HISTORY_BLOCK_BODY_KEY_PREFIX,
b256!("0x614e3d0000000000000000000000000000000000000000000000000000000009"),
)]
#[case::block_number_12345678_receipts(
12_345_678,
HISTORY_BLOCK_RECEIPTS_KEY_PREFIX,
b256!("0x614e3d000000000000000000000000000000000000000000000000000000000A"),
)]
fn simple(
#[case] block_number: u64,
#[case] content_type: u8,
#[case] expected_content_id: B256,
) {
assert_eq!(
range_content_id(block_number, content_type),
expected_content_id,
)
}
}
Loading
Loading