Skip to content

Commit 24dd039

Browse files
authored
Implement portal_history* add_enr, get_enr, delete_enr, lookup_enr RPC's (#718)
* Implementing the last 4 unimplemented portal_history* RPC's * Added peertests for history* ``addEnr``, ``getEnr``, ``deleteEnr``, ``lookupEnr`` RPC's
1 parent 9b680b5 commit 24dd039

File tree

10 files changed

+233
-26
lines changed

10 files changed

+233
-26
lines changed

ethportal-api/src/discv5.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub trait Discv5Api {
3434
#[method(name = "deleteEnr")]
3535
async fn delete_enr(&self, node_id: NodeId) -> RpcResult<bool>;
3636

37-
/// Fetch the ENR representation associated with the given Node ID and optional sequence number.
37+
/// Fetch the ENR representation associated with the given Node ID.
3838
#[method(name = "lookupEnr")]
39-
async fn lookup_enr(&self, node_id: NodeId, enr_seq: Option<u32>) -> RpcResult<Enr>;
39+
async fn lookup_enr(&self, node_id: NodeId) -> RpcResult<Enr>;
4040
}

ethportal-api/src/history.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ pub trait HistoryNetworkApi {
3131
#[method(name = "historyDeleteEnr")]
3232
async fn delete_enr(&self, node_id: NodeId) -> RpcResult<bool>;
3333

34-
/// Fetch the ENR representation associated with the given Node ID and optional sequence number.
34+
/// Fetch the ENR representation associated with the given Node ID.
3535
#[method(name = "historyLookupEnr")]
36-
async fn lookup_enr(&self, node_id: NodeId, enr_seq: Option<u32>) -> RpcResult<Enr>;
36+
async fn lookup_enr(&self, node_id: NodeId) -> RpcResult<Enr>;
3737

3838
/// Send a PING message to the designated node and wait for a PONG response
3939
#[method(name = "historyPing")]

ethportal-peertest/src/scenarios/basic.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,42 @@ pub async fn test_history_radius(target: &Client) {
4242
);
4343
}
4444

45+
pub async fn test_history_add_enr(target: &Client, peertest: &Peertest) {
46+
info!("Testing portal_historyAddEnr");
47+
let result = HistoryNetworkApiClient::add_enr(target, peertest.bootnode.enr.clone())
48+
.await
49+
.unwrap();
50+
assert!(result);
51+
}
52+
53+
pub async fn test_history_get_enr(target: &Client, peertest: &Peertest) {
54+
info!("Testing portal_historyGetEnr");
55+
let result = HistoryNetworkApiClient::get_enr(target, peertest.bootnode.enr.node_id().into())
56+
.await
57+
.unwrap();
58+
assert_eq!(result, peertest.bootnode.enr);
59+
}
60+
61+
pub async fn test_history_delete_enr(target: &Client, peertest: &Peertest) {
62+
info!("Testing portal_historyDeleteEnr");
63+
let result =
64+
HistoryNetworkApiClient::delete_enr(target, peertest.bootnode.enr.node_id().into())
65+
.await
66+
.unwrap();
67+
assert!(result);
68+
}
69+
70+
pub async fn test_history_lookup_enr(peertest: &Peertest) {
71+
info!("Testing portal_historyLookupEnr");
72+
let result = HistoryNetworkApiClient::lookup_enr(
73+
&peertest.bootnode.ipc_client,
74+
peertest.nodes[0].enr.node_id().into(),
75+
)
76+
.await
77+
.unwrap();
78+
assert_eq!(result, peertest.nodes[0].enr);
79+
}
80+
4581
pub async fn test_history_ping(target: &Client, peertest: &Peertest) {
4682
info!("Testing portal_historyPing");
4783
let result = target.ping(peertest.bootnode.enr.clone()).await.unwrap();

newsfragments/718.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement portal_history* ``add_enr``, ``get_enr``, ``delete_enr``, ``lookup_enr`` RPC's

portalnet/src/overlay.rs

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1+
use discv5::{
2+
enr::NodeId,
3+
kbucket::{
4+
Entry, FailureReason, Filter, InsertResult, KBucketsTable, Key, NodeStatus,
5+
MAX_NODES_PER_BUCKET,
6+
},
7+
ConnectionDirection, ConnectionState, TalkRequest,
8+
};
9+
use futures::channel::oneshot;
10+
use parking_lot::RwLock;
11+
use ssz::Encode;
112
use std::{
213
collections::{BTreeMap, HashSet},
314
fmt::{Debug, Display},
415
marker::{PhantomData, Sync},
516
sync::Arc,
617
time::Duration,
718
};
8-
9-
use discv5::{
10-
enr::NodeId,
11-
kbucket::{Filter, KBucketsTable, NodeStatus, MAX_NODES_PER_BUCKET},
12-
TalkRequest,
13-
};
14-
use futures::channel::oneshot;
15-
use parking_lot::RwLock;
16-
use ssz::Encode;
1719
use tokio::sync::mpsc::UnboundedSender;
1820
use tracing::{debug, error, info, warn};
1921
use utp_rs::socket::UtpSocket;
@@ -276,6 +278,97 @@ where
276278
.collect()
277279
}
278280

281+
/// `AddEnr` adds requested `enr` to our kbucket.
282+
pub fn add_enr(&self, enr: Enr) -> Result<(), OverlayRequestError> {
283+
let key = Key::from(enr.node_id());
284+
match self.kbuckets.write().insert_or_update(
285+
&key,
286+
Node {
287+
enr,
288+
data_radius: Distance::MAX,
289+
},
290+
NodeStatus {
291+
state: ConnectionState::Disconnected,
292+
direction: ConnectionDirection::Incoming,
293+
},
294+
) {
295+
InsertResult::Inserted
296+
| InsertResult::Pending { .. }
297+
| InsertResult::StatusUpdated { .. }
298+
| InsertResult::ValueUpdated
299+
| InsertResult::Updated { .. }
300+
| InsertResult::UpdatedPending => Ok(()),
301+
InsertResult::Failed(FailureReason::BucketFull) => {
302+
Err(OverlayRequestError::Failure("The bucket was full.".into()))
303+
}
304+
InsertResult::Failed(FailureReason::BucketFilter) => Err(OverlayRequestError::Failure(
305+
"The node didn't pass the bucket filter.".into(),
306+
)),
307+
InsertResult::Failed(FailureReason::TableFilter) => Err(OverlayRequestError::Failure(
308+
"The node didn't pass the table filter.".into(),
309+
)),
310+
InsertResult::Failed(FailureReason::InvalidSelfUpdate) => {
311+
Err(OverlayRequestError::Failure("Cannot update self.".into()))
312+
}
313+
InsertResult::Failed(_) => {
314+
Err(OverlayRequestError::Failure("Failed to insert ENR".into()))
315+
}
316+
}
317+
}
318+
319+
/// `GetEnr` gets requested `enr` from our kbucket.
320+
pub fn get_enr(&self, node_id: NodeId) -> Result<Enr, OverlayRequestError> {
321+
if node_id == self.local_enr().node_id() {
322+
return Ok(self.local_enr());
323+
}
324+
let key = Key::from(node_id);
325+
if let Entry::Present(entry, _) = self.kbuckets.write().entry(&key) {
326+
return Ok(entry.value().enr());
327+
}
328+
Err(OverlayRequestError::Failure("Couldn't get ENR".into()))
329+
}
330+
331+
/// `DeleteEnr` deletes requested `enr` from our kbucket.
332+
pub fn delete_enr(&self, node_id: NodeId) -> bool {
333+
let key = &Key::from(node_id);
334+
self.kbuckets.write().remove(key)
335+
}
336+
337+
/// `LookupEnr` finds requested `enr` from our kbucket, FindNode, and RecursiveFindNode.
338+
pub async fn lookup_enr(&self, node_id: NodeId) -> Result<Enr, OverlayRequestError> {
339+
if node_id == self.local_enr().node_id() {
340+
return Ok(self.local_enr());
341+
}
342+
343+
let enr = self.get_enr(node_id);
344+
345+
// try to find more up to date enr
346+
if let Ok(enr) = enr.clone() {
347+
let nodes = self.send_find_nodes(enr, vec![0]).await?;
348+
let enr_highest_seq = nodes.enrs.into_iter().max_by(|a, b| a.seq().cmp(&b.seq()));
349+
350+
if let Some(enr_highest_seq) = enr_highest_seq {
351+
return Ok(enr_highest_seq.into());
352+
}
353+
}
354+
355+
let lookup_node_enr = self.lookup_node(node_id).await;
356+
let lookup_node_enr = lookup_node_enr
357+
.into_iter()
358+
.max_by(|a, b| a.seq().cmp(&b.seq()));
359+
if let Some(lookup_node_enr) = lookup_node_enr {
360+
let mut enr_seq = 0;
361+
if let Ok(enr) = enr.clone() {
362+
enr_seq = enr.seq();
363+
}
364+
if lookup_node_enr.seq() > enr_seq {
365+
return Ok(lookup_node_enr);
366+
}
367+
}
368+
369+
enr
370+
}
371+
279372
/// Sends a `Ping` request to `enr`.
280373
pub async fn send_ping(&self, enr: Enr) -> Result<Pong, OverlayRequestError> {
281374
// Construct the request.

rpc/src/discv5_rpc.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ impl Discv5ApiServer for Discv5Api {
5252
Err(Error::MethodNotFound("delete_enr".to_owned()))
5353
}
5454

55-
/// Fetch the ENR representation associated with the given Node ID and optional sequence number.
56-
async fn lookup_enr(&self, _node_id: NodeId, _enr_seq: Option<u32>) -> RpcResult<Enr> {
55+
/// Fetch the ENR representation associated with the given Node ID.
56+
async fn lookup_enr(&self, _node_id: NodeId) -> RpcResult<Enr> {
5757
Err(Error::MethodNotFound("lookup_enr".to_owned()))
5858
}
5959
}

rpc/src/history_rpc.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::jsonrpsee::core::{async_trait, Error, RpcResult};
1+
use crate::jsonrpsee::core::{async_trait, RpcResult};
22
use anyhow::anyhow;
33
use ethportal_api::types::portal::{
44
AcceptInfo, ContentInfo, DataRadius, FindNodesInfo, PaginateLocalContentInfo, PongInfo,
@@ -59,23 +59,35 @@ impl HistoryNetworkApiServer for HistoryNetworkApi {
5959
}
6060

6161
/// Write an Ethereum Node Record to the overlay routing table.
62-
async fn add_enr(&self, _enr: Enr) -> RpcResult<bool> {
63-
Err(Error::MethodNotFound("add_enr".to_owned()))
62+
async fn add_enr(&self, enr: Enr) -> RpcResult<bool> {
63+
let endpoint = HistoryEndpoint::AddEnr(enr);
64+
let result = self.proxy_query_to_history_subnet(endpoint).await?;
65+
let result: bool = from_value(result)?;
66+
Ok(result)
6467
}
6568

6669
/// Fetch the latest ENR associated with the given node ID.
67-
async fn get_enr(&self, _node_id: NodeId) -> RpcResult<Enr> {
68-
Err(Error::MethodNotFound("get_enr".to_owned()))
70+
async fn get_enr(&self, node_id: NodeId) -> RpcResult<Enr> {
71+
let endpoint = HistoryEndpoint::GetEnr(node_id);
72+
let result = self.proxy_query_to_history_subnet(endpoint).await?;
73+
let result: Enr = from_value(result)?;
74+
Ok(result)
6975
}
7076

7177
/// Delete Node ID from the overlay routing table.
72-
async fn delete_enr(&self, _node_id: NodeId) -> RpcResult<bool> {
73-
Err(Error::MethodNotFound("delete_enr".to_owned()))
78+
async fn delete_enr(&self, node_id: NodeId) -> RpcResult<bool> {
79+
let endpoint = HistoryEndpoint::DeleteEnr(node_id);
80+
let result = self.proxy_query_to_history_subnet(endpoint).await?;
81+
let result: bool = from_value(result)?;
82+
Ok(result)
7483
}
7584

76-
/// Fetch the ENR representation associated with the given Node ID and optional sequence number.
77-
async fn lookup_enr(&self, _node_id: NodeId, _enr_seq: Option<u32>) -> RpcResult<Enr> {
78-
Err(Error::MethodNotFound("lookup_enr".to_owned()))
85+
/// Fetch the ENR representation associated with the given Node ID.
86+
async fn lookup_enr(&self, node_id: NodeId) -> RpcResult<Enr> {
87+
let endpoint = HistoryEndpoint::LookupEnr(node_id);
88+
let result = self.proxy_query_to_history_subnet(endpoint).await?;
89+
let result: Enr = from_value(result)?;
90+
Ok(result)
7991
}
8092

8193
/// Send a PING message to the designated node and wait for a PONG response

tests/self_peertest.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ mod test {
5959
peertest::scenarios::basic::test_discv5_node_info(&peertest).await;
6060
peertest::scenarios::basic::test_discv5_routing_table_info(&target).await;
6161
peertest::scenarios::basic::test_history_radius(&target).await;
62+
peertest::scenarios::basic::test_history_add_enr(&target, &peertest).await;
63+
peertest::scenarios::basic::test_history_get_enr(&target, &peertest).await;
64+
peertest::scenarios::basic::test_history_delete_enr(&target, &peertest).await;
65+
peertest::scenarios::basic::test_history_lookup_enr(&peertest).await;
6266
peertest::scenarios::basic::test_history_ping(&target, &peertest).await;
6367
peertest::scenarios::basic::test_history_find_nodes(&target, &peertest).await;
6468
peertest::scenarios::basic::test_history_find_nodes_zero_distance(&target, &peertest).await;

trin-history/src/jsonrpc.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,21 @@ async fn complete_request(network: Arc<RwLock<HistoryNetwork>>, request: History
5757
HistoryEndpoint::TraceRecursiveFindContent(content_key) => {
5858
recursive_find_content(network, content_key, true).await
5959
}
60+
HistoryEndpoint::AddEnr(enr) => add_enr(network, enr).await,
6061
HistoryEndpoint::DataRadius => {
6162
let radius = network.read().await.overlay.data_radius();
6263
Ok(json!(*radius))
6364
}
65+
HistoryEndpoint::DeleteEnr(node_id) => delete_enr(network, node_id).await,
6466
HistoryEndpoint::FindContent(enr, content_key) => {
6567
find_content(network, enr, content_key).await
6668
}
6769
HistoryEndpoint::FindNodes(enr, distances) => find_nodes(network, enr, distances).await,
70+
HistoryEndpoint::GetEnr(node_id) => get_enr(network, node_id).await,
6871
HistoryEndpoint::Gossip(content_key, content_value) => {
6972
gossip(network, content_key, content_value).await
7073
}
74+
HistoryEndpoint::LookupEnr(node_id) => lookup_enr(network, node_id).await,
7175
HistoryEndpoint::Offer(enr, content_key, content_value) => {
7276
offer(network, enr, content_key, content_value).await
7377
}
@@ -188,11 +192,60 @@ async fn store(
188192
.put::<HistoryContentKey, Vec<u8>>(content_key, data)
189193
{
190194
Ok(_) => Ok(Value::Bool(true)),
191-
Err(msg) => Ok(Value::String(msg.to_string())),
195+
Err(err) => Ok(Value::String(err.to_string())),
192196
};
193197
response
194198
}
195199

200+
/// Constructs a JSON call for the AddEnr method.
201+
async fn add_enr(
202+
network: Arc<RwLock<HistoryNetwork>>,
203+
enr: discv5::enr::Enr<discv5::enr::CombinedKey>,
204+
) -> Result<Value, String> {
205+
let overlay = network.read().await.overlay.clone();
206+
match overlay.add_enr(enr) {
207+
Ok(_) => Ok(json!(true)),
208+
Err(err) => Err(format!("AddEnr failed: {err:?}")),
209+
}
210+
}
211+
212+
/// Constructs a JSON call for the GetEnr method.
213+
async fn get_enr(
214+
network: Arc<RwLock<HistoryNetwork>>,
215+
node_id: ethportal_api::NodeId,
216+
) -> Result<Value, String> {
217+
let node_id = discv5::enr::NodeId::from(node_id.0);
218+
let overlay = network.read().await.overlay.clone();
219+
match overlay.get_enr(node_id) {
220+
Ok(enr) => Ok(json!(enr)),
221+
Err(err) => Err(format!("GetEnr failed: {err:?}")),
222+
}
223+
}
224+
225+
/// Constructs a JSON call for the deleteEnr method.
226+
async fn delete_enr(
227+
network: Arc<RwLock<HistoryNetwork>>,
228+
node_id: ethportal_api::NodeId,
229+
) -> Result<Value, String> {
230+
let node_id = discv5::enr::NodeId::from(node_id.0);
231+
let overlay = network.read().await.overlay.clone();
232+
let is_deleted = overlay.delete_enr(node_id);
233+
Ok(json!(is_deleted))
234+
}
235+
236+
/// Constructs a JSON call for the LookupEnr method.
237+
async fn lookup_enr(
238+
network: Arc<RwLock<HistoryNetwork>>,
239+
node_id: ethportal_api::NodeId,
240+
) -> Result<Value, String> {
241+
let node_id = discv5::enr::NodeId::from(node_id.0);
242+
let overlay = network.read().await.overlay.clone();
243+
match overlay.lookup_enr(node_id).await {
244+
Ok(enr) => Ok(json!(enr)),
245+
Err(err) => Err(format!("LookupEnr failed: {err:?}")),
246+
}
247+
}
248+
196249
/// Constructs a JSON call for the FindContent method.
197250
async fn find_content(
198251
network: Arc<RwLock<HistoryNetwork>>,

trin-types/src/jsonrpc/endpoints.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,22 @@ pub enum StateEndpoint {
2727
/// History network JSON-RPC endpoints. Start with "portal_history" prefix
2828
#[derive(Debug, PartialEq, Eq, Clone)]
2929
pub enum HistoryEndpoint {
30+
/// params: [enr]
31+
AddEnr(Enr),
3032
/// params: None
3133
DataRadius,
34+
/// params: [node_id]
35+
DeleteEnr(NodeId),
3236
/// params: [enr, content_key]
3337
FindContent(Enr, HistoryContentKey),
3438
/// params: [enr, distances]
3539
FindNodes(Enr, Vec<u16>),
40+
/// params: [node_id]
41+
GetEnr(NodeId),
3642
/// params: content_key
3743
LocalContent(HistoryContentKey),
44+
/// params: [node_id]
45+
LookupEnr(NodeId),
3846
/// params: [content_key, content_value]
3947
Gossip(HistoryContentKey, HistoryContentValue),
4048
/// params: [enr, content_key]

0 commit comments

Comments
 (0)