Skip to content

Commit c1d5492

Browse files
authored
feat: add chain head metrics (#1863)
1 parent 4c7897d commit c1d5492

File tree

13 files changed

+235
-88
lines changed

13 files changed

+235
-88
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/portal-bridge/src/bridge/beacon.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
cmp::Ordering,
33
sync::{Arc, Mutex as StdMutex},
4-
time::{Instant, SystemTime},
4+
time::{Duration, Instant},
55
};
66

77
use alloy::primitives::B256;
@@ -22,6 +22,7 @@ use ethportal_api::{
2222
ForkVersionedHistoricalSummariesWithProof, LightClientUpdatesByRange,
2323
},
2424
network::Subnetwork,
25+
network_spec::network_spec,
2526
portal_wire::OfferTrace,
2627
},
2728
BeaconContentKey, BeaconContentValue, ContentValue, LightClientBootstrapKey,
@@ -30,19 +31,14 @@ use ethportal_api::{
3031
use ssz_types::VariableList;
3132
use tokio::{
3233
sync::Mutex,
33-
time::{interval, sleep, timeout, Duration, MissedTickBehavior},
34+
time::{interval_at, sleep, timeout, Instant as TokioInstant, MissedTickBehavior},
3435
};
3536
use tracing::{error, info, warn, Instrument};
3637
use trin_beacon::network::BeaconNetwork;
3738
use trin_metrics::bridge::BridgeMetricsReporter;
3839

3940
use super::{constants::SERVE_BLOCK_TIMEOUT, offer_report::OfferReport};
40-
use crate::{
41-
census::Census,
42-
constants::BEACON_GENESIS_TIME,
43-
types::mode::BridgeMode,
44-
utils::{duration_until_next_update, expected_current_slot},
45-
};
41+
use crate::{census::Census, types::mode::BridgeMode, utils::duration_until_next_update};
4642

4743
/// A helper struct to hold the finalized beacon state metadata.
4844
#[derive(Clone, Debug, Default)]
@@ -119,14 +115,12 @@ impl BeaconBridge {
119115

120116
// Sleep until next update becomes available. This sets up the interval to update as soon as
121117
// the following slot becomes available.
122-
let now = SystemTime::now();
123-
let next_update = duration_until_next_update(BEACON_GENESIS_TIME, now)
124-
.to_std()
125-
.expect("failed to convert chrono duration to std duration");
126-
sleep(next_update).await;
127-
128-
// Run the beacon bridge update once every slot
129-
let mut interval = interval(Duration::from_secs(12));
118+
let until_next_update = duration_until_next_update();
119+
let mut interval = interval_at(
120+
TokioInstant::now() + until_next_update,
121+
Duration::from_secs(12),
122+
);
123+
130124
// If serving takes a little too long, then we want to serve the next one as soon as
131125
// possible, but not serve any extras until the following slot. "Skip" gets this behavior.
132126
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
@@ -314,9 +308,7 @@ impl BeaconBridge {
314308
metrics: BridgeMetricsReporter,
315309
census: Census,
316310
) -> anyhow::Result<()> {
317-
let now = SystemTime::now();
318-
let expected_current_period =
319-
expected_current_slot(BEACON_GENESIS_TIME, now) / SLOTS_PER_HISTORICAL_ROOT;
311+
let expected_current_period = network_spec().current_slot() / SLOTS_PER_HISTORICAL_ROOT;
320312
match expected_current_period.cmp(&*current_period.lock().await) {
321313
Ordering::Equal => {
322314
// We already gossiped the latest data from the current period, no need to serve it

bin/portal-bridge/src/constants.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ pub const HEADER_WITH_PROOF_CONTENT_KEY: &str =
44
pub const HEADER_WITH_PROOF_CONTENT_VALUE: &str =
55
"0x0800000023020000f90218a00409be8253ad6ac0eb2056bc94194c6ccb83c74f4292c40c82e2dc8203bdc759a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942a65aca4d5fc5b5c859090a6c34d164135398226a0afbf9bfd23008e8df44a83bb51ade45b993b3253fbce69cf7cec5d628eca6d45a0a7120e4bd136c0b6bdb0fa4990649f8c34d10d180dbd5ad6d03502ae92d32308a0d78aa953fedc7f7c112b2686d0b2b7e37eba716dd1f5d74ef3c8a37005f35215b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000004000000000000000000040000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000860b69dd9d66ce830f424a832fefd88303a68c8456bfb4e398d783010303844765746887676f312e352e31856c696e7578a0e962efb883f91286e4fc6fd12989a70f24c174bd087f472528137c4134af0a1a88e857c5acc15dd827cead98e305c70563000000000000000000000000000000000000000000000000be1b4a7a57f5316eea09c5e3e349141c46c1cb43664a815d28644cd74f282ca122360456d89447c0d586a8f5490922ea86b20e056879d64d87d104c14c0e594a6d800f67f5331ee2e511dc20e169c644b3df0f4c6b7c1717fc29d4844050b74044b506bf91edd14825aaec4f36fc5ad97b9eed9773aa2df15f80dff21eb668e24d61c29c3fda0fb425078a0479c5ea375ff95ad7780d0cdc87012009fd4a3dd003b06c7a28d6188e6be50ac544548cc7e3ee6cd07a8129f5c6d4d494b62ee8d96d26d0875bc87b56be0bf3e45846c0e3773abfccc239fdab29640b4e2aef297efcc6cb89b00a2566221cb4197ece3f66c24ea89969bd16265a74910aaf08d775116191117416b8799d0984f452a6fba19623442a7f199ef1627f1ae7295963a67db5534a292f98edbfb419ed85756abe76cd2d2bff8eb9b848b1e7b80b8274bbc469a36dce58b48ae57be6312bca843463ac45c54122a9f3fa9dca124b0fd50bce300708549c77b81b031278b9d193464f5e4b14769f6018055a457a577c508e811bcf55b297df3509f3db7e66ec68451e25acfbf935200e246f71e3c48240d00020000000000000000000000000000000000000000000000000000000000000";
66

7-
// Beacon chain mainnet genesis time: Tue Dec 01 2020 12:00:23 GMT+0000
8-
pub const BEACON_GENESIS_TIME: u64 = 1606824023;
9-
107
// The maximum number of concurrent offer jsonrpc requests that can be made simultaneously.
118
// Currently, this is only used in the state bridge.
129
pub const DEFAULT_OFFER_LIMIT: usize = 64;

bin/portal-bridge/src/utils.rs

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use std::time::{SystemTime, UNIX_EPOCH};
1+
use std::time::{Duration, SystemTime};
22

33
use alloy::primitives::B256;
4-
use chrono::Duration;
54
use discv5::enr::{CombinedKey, Enr, NodeId};
6-
use ethportal_api::{types::network_spec::network_spec, utils::bytes::hex_encode};
5+
use ethportal_api::{
6+
consensus::constants::SECONDS_PER_SLOT, types::network_spec::network_spec,
7+
utils::bytes::hex_encode,
8+
};
79

810
/// Generates a set of N private keys, with node ids that are equally spaced
911
/// around the 256-bit keys space.
@@ -51,41 +53,31 @@ fn random_node_id() -> (NodeId, CombinedKey) {
5153

5254
/// Gets the duration until the next light client update
5355
/// Updates are scheduled for 4 seconds into each slot
54-
pub fn duration_until_next_update(genesis_time: u64, now: SystemTime) -> Duration {
55-
let current_slot = expected_current_slot(genesis_time, now);
56-
let next_slot = current_slot + 1;
57-
let next_slot_timestamp = network_spec().slot_to_timestamp(next_slot);
56+
pub fn duration_until_next_update() -> Duration {
57+
let current_slot_timestamp = network_spec().slot_to_timestamp(network_spec().current_slot());
5858

59-
let now = now
60-
.duration_since(UNIX_EPOCH)
61-
.expect("Time went backwards")
62-
.as_secs();
59+
// Calculate the update timestamp for current timestamp
60+
let mut next_update_timestamp = current_slot_timestamp + Duration::from_secs(4);
6361

64-
let time_to_next_slot = next_slot_timestamp - now;
65-
let next_update = time_to_next_slot + 4;
66-
67-
Duration::seconds(next_update as i64)
68-
}
69-
70-
pub fn expected_current_slot(genesis_time: u64, now: SystemTime) -> u64 {
71-
let now = now.duration_since(UNIX_EPOCH).expect("Time went backwards");
72-
let since_genesis = now - std::time::Duration::from_secs(genesis_time);
73-
74-
since_genesis.as_secs() / 12
62+
// 4 seconds into current slot might have passed already, so we should try next slot
63+
loop {
64+
if let Ok(until_next_update) = next_update_timestamp.duration_since(SystemTime::now()) {
65+
return until_next_update;
66+
}
67+
next_update_timestamp += SECONDS_PER_SLOT;
68+
}
7569
}
7670

7771
#[cfg(test)]
7872
#[allow(clippy::unwrap_used)]
7973
mod tests {
80-
use chrono::{DateTime, TimeZone, Utc};
8174
use ethportal_api::{
8275
types::distance::{Metric, XorMetric},
8376
utils::bytes::hex_decode,
8477
};
8578
use rstest::rstest;
8679

8780
use super::*;
88-
use crate::constants::BEACON_GENESIS_TIME;
8981

9082
#[rstest]
9183
#[case(2)]
@@ -113,24 +105,4 @@ mod tests {
113105
"{distance} vs {min_spread}, first bytes: {first_byte1} vs {first_byte2}"
114106
);
115107
}
116-
117-
#[rstest]
118-
#[case(10, 5)]
119-
#[case(11, 16)]
120-
fn test_duration_until_next_update(#[case] seconds: u32, #[case] expected_duration: i64) {
121-
let date: DateTime<Utc> = Utc.with_ymd_and_hms(2023, 8, 23, 11, 00, seconds).unwrap();
122-
let now = SystemTime::from(date);
123-
let duration = duration_until_next_update(BEACON_GENESIS_TIME, now);
124-
assert_eq!(duration, Duration::seconds(expected_duration));
125-
}
126-
127-
#[rstest]
128-
#[case(10, 7163698)]
129-
#[case(11, 7163699)]
130-
fn test_expected_current_slot(#[case] seconds: u32, #[case] expected_slot: u64) {
131-
let date: DateTime<Utc> = Utc.with_ymd_and_hms(2023, 8, 23, 11, 00, seconds).unwrap();
132-
let now = SystemTime::from(date);
133-
let slot = expected_current_slot(BEACON_GENESIS_TIME, now);
134-
assert_eq!(slot, expected_slot);
135-
}
136108
}
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
//! Consensus specs constants.
22
//!
3-
//! Mostly taken from: https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/presets/mainnet/phase0.yaml
3+
//! Mostly taken from:
4+
//! https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/presets/mainnet/phase0.yaml
5+
//! or
6+
//! https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/configs/mainnet.yaml
47
//!
58
//! These should eventually be part of the Chain configuration parameters.
69
10+
use std::time::Duration;
11+
712
/// Number of slots per Epoch.
813
///
914
/// 2**5 (= 32) slots 6.4 minutes
@@ -14,20 +19,22 @@ pub const SLOTS_PER_EPOCH: u64 = 32;
1419
/// 2**13 (= 8,192) slots ~27 hours
1520
pub const SLOTS_PER_HISTORICAL_ROOT: u64 = 8192;
1621

22+
/// Seconds per slot
23+
///
24+
/// 12 seconds
25+
pub const SECONDS_PER_SLOT: Duration = Duration::from_secs(12);
26+
1727
/// The Epoch of the mainnet Capella fork.
1828
///
1929
/// April 12, 2023, 10:27:35pm UTC
20-
/// Source: https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/configs/mainnet.yaml
2130
pub const CAPELLA_FORK_EPOCH: u64 = 194_048;
2231

2332
/// The Epoch of the mainnet Deneb fork.
2433
///
2534
/// March 13, 2024, 01:55:35pm UTC
26-
/// Source: https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/configs/mainnet.yaml
2735
pub const DENEB_FORK_EPOCH: u64 = 269_568;
2836

2937
/// The Epoch of the mainnet Electra fork.
3038
///
3139
/// May 7, 2025, 10:05:11am UTC
32-
/// Source: https://github.com/ethereum/consensus-specs/blob/d8cfdf2626c1219a40048f8fa3dd103ae8c0b040/configs/mainnet.yaml
3340
pub const ELECTRA_FORK_EPOCH: u64 = 364_032;

crates/ethportal-api/src/types/network_spec.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use std::sync::{Arc, LazyLock};
1+
use std::{
2+
sync::{Arc, LazyLock},
3+
time::{Duration, SystemTime},
4+
};
25

36
use alloy_hardforks::{EthereumChainHardforks, EthereumHardfork, EthereumHardforks, ForkCondition};
47
use anyhow::anyhow;
@@ -8,6 +11,7 @@ use once_cell::sync::Lazy;
811
use parking_lot::RwLock;
912

1013
use super::{
14+
consensus::constants::SECONDS_PER_SLOT,
1115
network::{Network, Subnetwork},
1216
protocol_versions::{
1317
ProtocolVersion, ProtocolVersionError, ProtocolVersionList, ENR_PROTOCOL_VERSION_KEY,
@@ -51,7 +55,7 @@ pub struct NetworkSpec {
5155
portal_subnetworks: BiHashMap<Subnetwork, String>,
5256
supported_protocol_versions: ProtocolVersionList,
5357
hardforks: EthereumChainHardforks,
54-
beacon_genesis_timestamp: u64,
58+
beacon_genesis_timestamp: SystemTime,
5559
}
5660

5761
impl NetworkSpec {
@@ -70,7 +74,8 @@ impl NetworkSpec {
7074
network,
7175
supported_protocol_versions,
7276
hardforks,
73-
beacon_genesis_timestamp,
77+
beacon_genesis_timestamp: SystemTime::UNIX_EPOCH
78+
+ Duration::from_secs(beacon_genesis_timestamp),
7479
})
7580
}
7681

@@ -123,8 +128,24 @@ impl NetworkSpec {
123128
.ok_or(ProtocolVersionError::NoMatchingVersion)
124129
}
125130

126-
pub fn slot_to_timestamp(&self, slot: u64) -> u64 {
127-
self.beacon_genesis_timestamp + slot * 12
131+
pub fn slot_to_timestamp(&self, slot: u64) -> SystemTime {
132+
self.beacon_genesis_timestamp + (SECONDS_PER_SLOT * slot as u32)
133+
}
134+
135+
pub fn slot_to_timestamp_u64(&self, slot: u64) -> u64 {
136+
self.slot_to_timestamp(slot)
137+
.duration_since(SystemTime::UNIX_EPOCH)
138+
.expect("Slot's timestamp must be after UNIX EPOCH timestamp")
139+
.as_secs()
140+
}
141+
142+
pub fn current_slot(&self) -> u64 {
143+
let seconds_since_genesis = self
144+
.beacon_genesis_timestamp
145+
.elapsed()
146+
.expect("Genesis timestamp must be in the past")
147+
.as_secs();
148+
seconds_since_genesis / SECONDS_PER_SLOT.as_secs()
128149
}
129150
}
130151

0 commit comments

Comments
 (0)