Skip to content

Commit 94e3165

Browse files
authored
Merge pull request #376 from njgheorghita/validate-accept
Add content validation for accepted content
2 parents 1024ddb + 4fda56f commit 94e3165

File tree

10 files changed

+144
-52
lines changed

10 files changed

+144
-52
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ethportal-peertest/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ anyhow = "1.0.57"
1111
clap = "2.33.3"
1212
discv5 = { git = "https://github.com/sigp/discv5.git", branch = "master" }
1313
futures = "0.3.21"
14-
hyper = { version = "0.14", features = ["full"] }
1514
hex = "0.4.3"
15+
httpmock = "0.6.6"
16+
hyper = { version = "0.14", features = ["full"] }
1617
log = "0.4.14"
1718
rocksdb = "0.18.0"
1819
serde_json = "1.0.59"

ethportal-peertest/src/lib.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,54 @@ use std::net::{IpAddr, Ipv4Addr};
99
use std::{sync::Arc, thread, time};
1010

1111
use futures::future;
12+
use httpmock::prelude::{MockServer, POST};
13+
use serde_json::json;
1214

1315
use trin_core::{
1416
cli::TrinConfig, jsonrpc::service::JsonRpcExiter, portalnet::types::messages::SszEnr,
1517
};
1618

19+
fn setup_mock_infura_server() -> MockServer {
20+
let server = MockServer::start();
21+
server.mock(|when, then| {
22+
// setup up a mock infura response for validating accepted content
23+
// inside test_offer_accept scenario
24+
when.method(POST)
25+
.body_contains("eth_getBlockByNumber");
26+
then.status(200)
27+
.header("content-type", "application/json")
28+
.json_body(json!({
29+
"jsonrpc": "2.0",
30+
"id": 0,
31+
"result": {
32+
"baseFeePerGas": "0x1aae1651b6",
33+
"difficulty": "0x327bd7ad3116ce",
34+
"extraData": "0x457468657265756d50504c4e532f326d696e6572735f55534133",
35+
"gasLimit": "0x1c9c364",
36+
"gasUsed": "0x140db1",
37+
"hash": "0x720704f3aa11c53cf344ea069db95cecb81ad7453c8f276b2a1062979611f09c",
38+
"logsBloom": "0x00200000400000001000400080080000000000010004010001000008000000002000110000000000000090020001110402008000080208040010000000a8000000000000000000210822000900205020000000000160020020000400800040000000000042080000000400004008084020001000001004004000001000000000000001000000110000040000010200844040048101000008002000404810082002800000108020000200408008000100000000000000002020000b0001008060090200020000005000040000000000000040000000202101000000a00002000003420000800400000020100002000000000000000c000400000010000001001",
39+
"miner": "0x00192fb10df37c9fb26829eb2cc623cd1bf599e8",
40+
"mixHash": "0xf1a32e24eb62f01ec3f2b3b5893f7be9062fbf5482bc0d490a54352240350e26",
41+
"nonce": "0x2087fbb243327696",
42+
"number": "0xe147ed",
43+
"parentHash": "0x2c58e3212c085178dbb1277e2f3c24b3f451267a75a234945c1581af639f4a7a",
44+
"receiptsRoot": "0x168a3827607627e781941dc777737fc4b6beb69a8b139240b881992b35b854ea",
45+
"sha3Uncles": "0x58a694212e0416353a4d3865ccf475496b55af3a3d3b002057000741af973191",
46+
"size": "0x1f96",
47+
"stateRoot": "0x67a9fb631f4579f9015ef3c6f1f3830dfa2dc08afe156f750e90022134b9ebf6",
48+
"timestamp": "0x627d9afa",
49+
"totalDifficulty": "0xa55e1baf12dfa3fc50c",
50+
// transactions have been left out of response
51+
"transactions": [],
52+
"transactionsRoot": "0x18a2978fc62cd1a23e90de920af68c0c3af3330327927cda4c005faccefb5ce7",
53+
"uncles": ["0x817d4158df626cd8e9a20da9552c51a0d43f22b25de0b4dc5a089d81af899c70"]
54+
}
55+
}));
56+
});
57+
server
58+
}
59+
1760
pub struct PeertestNode {
1861
pub enr: SszEnr,
1962
pub web3_ipc_path: String,
@@ -88,7 +131,9 @@ pub async fn launch_node(id: u16, bootnode_enr: Option<&SszEnr>) -> anyhow::Resu
88131
}
89132
};
90133
let web3_ipc_path = trin_config.web3_ipc_path.clone();
91-
let exiter = trin::run_trin(trin_config, String::new()).await.unwrap();
134+
let server = setup_mock_infura_server();
135+
let mock_infura_url = server.url("/");
136+
let exiter = trin::run_trin(trin_config, mock_infura_url).await.unwrap();
92137
let enr = get_enode(&web3_ipc_path)?;
93138

94139
// Short sleep to make sure all peertest nodes can connect

newsfragments/376.added.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added history network content validation for accepted content.

tests/self_peertest.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#[cfg(test)]
22
mod test {
3-
use ethportal_peertest as peertest;
43
use std::net::{IpAddr, Ipv4Addr};
54
use std::{thread, time};
5+
6+
use ethportal_peertest as peertest;
67
use trin_core::cli::TrinConfig;
78

89
// Logs don't show up when trying to use test_log here, maybe because of multi_thread

trin-core/src/portalnet/overlay.rs

+73-46
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ use discv5::{
4141
};
4242
use ethereum_types::U256;
4343
use futures::channel::oneshot;
44+
use futures::future::join_all;
4445
use log::error;
4546
use parking_lot::RwLock;
4647
use rand::seq::IteratorRandom;
4748
use ssz::Encode;
4849
use ssz_types::VariableList;
4950
use tokio::sync::mpsc::UnboundedSender;
51+
use tokio::task::JoinHandle;
5052
use tracing::{debug, warn};
5153

5254
pub use super::overlay_service::{OverlayRequestError, RequestDirection};
@@ -113,8 +115,8 @@ pub struct OverlayProtocol<TContentKey, TMetric, TValidator> {
113115
phantom_content_key: PhantomData<TContentKey>,
114116
/// Associate a metric with the overlay network.
115117
phantom_metric: PhantomData<TMetric>,
116-
/// Declare the Validator type for a given overlay network.
117-
phantom_validator: PhantomData<TValidator>,
118+
/// Accepted content validator that makes requests to this/other overlay networks (or infura)
119+
validator: Arc<TValidator>,
118120
}
119121

120122
impl<
@@ -153,7 +155,7 @@ where
153155
protocol.clone(),
154156
utp_listener_tx.clone(),
155157
config.enable_metrics,
156-
validator,
158+
Arc::clone(&validator),
157159
config.query_timeout,
158160
config.query_peer_timeout,
159161
config.query_parallelism,
@@ -173,7 +175,7 @@ where
173175
utp_listener_tx,
174176
phantom_content_key: PhantomData,
175177
phantom_metric: PhantomData,
176-
phantom_validator: PhantomData,
178+
validator,
177179
}
178180
}
179181

@@ -249,35 +251,71 @@ where
249251
));
250252
}
251253

252-
// TODO: Verify overlay content data with an Oracle
253-
254-
// Temporarily store content key/value pairs to propagate here
255-
let mut content_keys_values: Vec<(TContentKey, ByteList)> = Vec::new();
254+
let validator = Arc::clone(&self.validator);
255+
let storage = Arc::clone(&self.storage);
256+
let kbuckets = Arc::clone(&self.kbuckets);
257+
let command_tx = self.command_tx.clone();
256258

257-
// Try to store the content into the database and propagate gossip the content
258-
for (content_key, content_value) in content_keys.into_iter().zip(content_values.to_vec()) {
259-
match TContentKey::try_from(content_key) {
260-
Ok(key) => {
261-
// Store accepted content in DB
262-
self.store_overlay_content(&key, content_value.clone());
263-
content_keys_values.push((key, content_value))
264-
}
265-
Err(err) => {
266-
return Err(anyhow!(
267-
"Unexpected error while decoding overlay content key: {err}"
268-
));
269-
}
270-
}
271-
}
272-
// Propagate gossip accepted content
273-
self.propagate_gossip(content_keys_values)?;
259+
// Spawn a task that spawns a validation task for each piece of content,
260+
// collects validated content and propagates it via gossip.
261+
tokio::spawn(async move {
262+
let handles: Vec<JoinHandle<_>> = content_keys
263+
.into_iter()
264+
.zip(content_values.to_vec())
265+
.map(
266+
|(content_key, content_value)| match TContentKey::try_from(content_key) {
267+
Ok(key) => {
268+
// Spawn a task that...
269+
// - Validates accepted content (this step requires a dedicated task since it
270+
// might require non-blocking requests to this/other overlay networks)
271+
// - Checks if validated content should be stored, and stores it if true
272+
// - Propagate all validated content
273+
let validator = Arc::clone(&validator);
274+
let storage = Arc::clone(&storage);
275+
Some(tokio::spawn(async move {
276+
// Validated received content
277+
validator
278+
.validate_content(&key, &content_value.to_vec())
279+
.await
280+
// Skip storing & propagating content if it's not valid
281+
.expect("Unable to validate received content: {err:?}");
282+
283+
// Check if data should be stored, and store if true.
284+
// Ignore error since all validated content is propagated.
285+
let _ = storage
286+
.write()
287+
.store_if_should(&key, &content_value.to_vec());
288+
289+
(key, content_value)
290+
}))
291+
}
292+
Err(err) => {
293+
warn!("Unexpected error while decoding overlay content key: {err}");
294+
None
295+
}
296+
},
297+
)
298+
.flatten()
299+
.collect();
300+
let validated_content = join_all(handles)
301+
.await
302+
.into_iter()
303+
.filter_map(|content| content.ok())
304+
.collect();
305+
// Propagate all validated content, whether or not it was stored.
306+
Self::propagate_gossip(validated_content, kbuckets, command_tx);
307+
});
274308
Ok(())
275309
}
276310

277311
/// Propagate gossip accepted content via OFFER/ACCEPT:
278-
fn propagate_gossip(&self, content: Vec<(TContentKey, ByteList)>) -> anyhow::Result<()> {
312+
fn propagate_gossip(
313+
content: Vec<(TContentKey, ByteList)>,
314+
kbuckets: Arc<RwLock<KBucketsTable<NodeId, Node>>>,
315+
command_tx: UnboundedSender<OverlayCommand<TContentKey>>,
316+
) {
279317
// Get all nodes from overlay routing table
280-
let kbuckets = self.kbuckets.read();
318+
let kbuckets = kbuckets.read();
281319
let all_nodes: Vec<&kbucket::Node<NodeId, Node>> = kbuckets
282320
.buckets_iter()
283321
.map(|kbucket| {
@@ -312,7 +350,13 @@ where
312350
}
313351

314352
// Get log2 random ENRs to gossip
315-
let random_enrs = log2_random_enrs(interested_enrs)?;
353+
let random_enrs = match log2_random_enrs(interested_enrs) {
354+
Ok(val) => val,
355+
Err(msg) => {
356+
debug!("Error calculating log2 random enrs for gossip propagation: {msg}");
357+
return;
358+
}
359+
};
316360

317361
// Temporarily store all randomly selected nodes with the content of interest.
318362
// We want this so we can offer all the content to interested node in one request.
@@ -346,27 +390,10 @@ where
346390
None,
347391
);
348392

349-
if let Err(err) = self
350-
.command_tx
351-
.send(OverlayCommand::Request(overlay_request))
352-
{
393+
if let Err(err) = command_tx.send(OverlayCommand::Request(overlay_request)) {
353394
error!("Unable to send OFFER request to overlay: {err}.")
354395
}
355396
}
356-
Ok(())
357-
}
358-
359-
/// Try to store overlay content into database
360-
fn store_overlay_content(&self, content_key: &TContentKey, value: ByteList) {
361-
let should_store = self.storage.read().should_store(content_key);
362-
match should_store {
363-
Ok(_) => {
364-
if let Err(err) = self.storage.write().store(content_key, &value.into()) {
365-
warn!("Unable to store accepted content: {err}");
366-
}
367-
}
368-
Err(err) => error!("Unable to determine whether to store accepted content: {err}"),
369-
}
370397
}
371398

372399
/// Returns a vector of all ENR node IDs of nodes currently contained in the routing table.

trin-core/src/portalnet/storage.rs

+15
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,21 @@ impl PortalStorage {
137137
}
138138
}
139139

140+
/// Public method for automatically storing content after a `should_store` check.
141+
pub fn store_if_should(
142+
&mut self,
143+
key: &impl OverlayContentKey,
144+
value: &Vec<u8>,
145+
) -> Result<bool, PortalStorageError> {
146+
match self.should_store(key)? {
147+
true => {
148+
self.store(key, value)?;
149+
Ok(true)
150+
}
151+
false => Ok(false),
152+
}
153+
}
154+
140155
/// Public method for storing a given value for a given content-key.
141156
pub fn store(
142157
&mut self,

trin-core/src/types/validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ impl Default for HeaderOracle {
4343
impl HeaderOracle {
4444
// Currently falls back to infura, to be updated to use canonical block indices network.
4545
pub fn get_hash_at_height(&self, block_number: u64) -> anyhow::Result<String> {
46-
let hex_number = format!("0x:{:02X}", block_number);
46+
let hex_number = format!("0x{:02X}", block_number);
4747
let request = JsonRequest {
4848
jsonrpc: "2.0".to_string(),
4949
params: Params::Array(vec![json!(hex_number), json!(false)]),

trin-core/src/utp/stream.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1430,12 +1430,12 @@ impl UtpStream {
14301430
)
14311431
.await
14321432
{
1433-
let msg = format!("Unavle to send FIN packet: {msg}");
1433+
let msg = format!("Unable to send FIN packet: {msg}");
14341434
debug!("{msg}");
14351435
return Err(anyhow!(msg));
14361436
}
14371437

1438-
debug!("CLosing connection, sent {:?}", packet);
1438+
debug!("Closing connection, sent {:?}", packet);
14391439
self.state = StreamState::FinSent;
14401440

14411441
// Receive JAKE

trin-state/src/network.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use discv5::enr::NodeId;
44
use eth_trie::EthTrie;
55
use parking_lot::RwLock;
66
use tokio::sync::mpsc::UnboundedSender;
7+
78
use trin_core::{
89
portalnet::{
910
discovery::Discovery,

0 commit comments

Comments
 (0)