Skip to content

Commit

Permalink
feat: add odyssey_getWallTimeData (#41)
Browse files Browse the repository at this point in the history
adds a new endpoint that returns the tip's walltime

supersedes #38

---------

Co-authored-by: t11s <[email protected]>
Co-authored-by: Oliver <[email protected]>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent 811f874 commit f42997d
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 1 deletion.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"crates/precompile",
"crates/testing",
"crates/wallet",
"crates/walltime",
]
default-members = ["bin/odyssey/"]
resolver = "2"
Expand Down Expand Up @@ -137,6 +138,7 @@ strip = false
odyssey-node = { path = "crates/node" }
odyssey-precompile = { path = "crates/precompile" }
odyssey-wallet = { path = "crates/wallet" }
odyssey-walltime = { path = "crates/walltime" }

alloy = { version = "0.4", features = [
"contract",
Expand Down Expand Up @@ -191,6 +193,7 @@ reth-tracing = { git = "https://github.com/paradigmxyz/reth.git", rev = "75dda1c
reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth.git", rev = "75dda1c" }
reth-network = { git = "https://github.com/paradigmxyz/reth.git", rev = "75dda1c" }
reth-network-types = { git = "https://github.com/paradigmxyz/reth.git", rev = "75dda1c" }
reth-chain-state = { git = "https://github.com/paradigmxyz/reth.git", rev = "75dda1c" }

# rpc
jsonrpsee = "0.24"
Expand All @@ -202,6 +205,7 @@ tracing = "0.1.0"
serde = "1"
serde_json = "1"
thiserror = "1"
futures = "0.3"

# misc-testing
rstest = "0.18.2"
1 change: 1 addition & 0 deletions bin/odyssey/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ alloy-network.workspace = true
alloy-primitives.workspace = true
odyssey-node.workspace = true
odyssey-wallet.workspace = true
odyssey-walltime.workspace = true
eyre.workspace = true
tracing.workspace = true
reth-cli-util.workspace = true
Expand Down
7 changes: 6 additions & 1 deletion bin/odyssey/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ use clap::Parser;
use eyre::Context;
use odyssey_node::{chainspec::OdysseyChainSpecParser, node::OdysseyNode};
use odyssey_wallet::{OdysseyWallet, OdysseyWalletApiServer};
use odyssey_walltime::{OdysseyWallTime, OdysseyWallTimeRpcApiServer};
use reth_node_builder::{engine_tree_config::TreeConfig, EngineNodeLauncher};
use reth_optimism_cli::Cli;
use reth_optimism_node::{args::RollupArgs, node::OptimismAddOns};
use reth_optimism_rpc::sequencer::SequencerClient;
use reth_provider::providers::BlockchainProvider2;
use reth_provider::{providers::BlockchainProvider2, CanonStateSubscriptions};
use tracing::{info, warn};

#[global_allocator]
Expand Down Expand Up @@ -93,6 +94,10 @@ fn main() {
warn!(target: "reth::cli", "EXP0001 wallet not configured");
}

let walltime = OdysseyWallTime::spawn(ctx.provider().canonical_state_stream());
ctx.modules.merge_configured(walltime.into_rpc())?;
info!(target: "reth::cli", "Walltime configured");

Ok(())
})
.launch_with_fn(|builder| {
Expand Down
25 changes: 25 additions & 0 deletions crates/walltime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "odyssey-walltime"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
keywords.workspace = true
categories.workspace = true

[lints]
workspace = true

[dependencies]
reth-chain-state.workspace = true

jsonrpsee = { workspace = true, features = ["server", "macros"] }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["sync"] }
futures.workspace = true


[dev-dependencies]
jsonrpsee = { workspace = true, features = ["server", "client", "macros"] }
107 changes: 107 additions & 0 deletions crates/walltime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! # Odyssey walltime
//!
//! Returns the current walltime and the chain's tip timestamps.

#![cfg_attr(not(test), warn(unused_crate_dependencies))]

use futures::{Stream, StreamExt};
use jsonrpsee::{
core::{async_trait, RpcResult},
proc_macros::rpc,
types::{error::INTERNAL_ERROR_CODE, ErrorObject},
};
use reth_chain_state::CanonStateNotification;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;

/// The odyssey walltime endpoint.
#[derive(Debug, Clone)]
pub struct OdysseyWallTime {
inner: Arc<OdysseyWallTimeInner>,
}

impl OdysseyWallTime {
/// Creates a new instance with the connected stream.
pub fn spawn<St>(mut st: St) -> Self
where
St: Stream<Item = CanonStateNotification> + Send + Unpin + 'static,
{
let walltime = Self { inner: Default::default() };
let listener = walltime.clone();
tokio::task::spawn(async move {
while let Some(notification) = st.next().await {
let tip = BlockTimeData {
wall_time_ms: notification.tip().timestamp,
block_timestamp: unix_epoch_ms(),
};
*listener.inner.block_time_data.write().await = Some(tip);
}
});
walltime
}

/// Returns the currently tracked [`BlockTimeData`] if any.
async fn current_block_time(&self) -> Option<BlockTimeData> {
*self.inner.block_time_data.read().await
}
}

/// Implementation of the Odyssey `odyssey_getWallTimeData` endpoint.
#[derive(Debug, Default)]
struct OdysseyWallTimeInner {
/// Tracks the recent blocktime data
block_time_data: RwLock<Option<BlockTimeData>>,
}

/// Data about the current time and the last block's.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct WallTimeData {
/// Wall time right now
current_wall_time_ms: u64,
/// Wall time of last block
last_block_wall_time_ms: u64,
/// Timestamp of last block (chain time)
last_block_timestamp: u64,
}

/// Rpc endpoints
#[cfg_attr(not(test), rpc(server, namespace = "odyssey"))]
#[cfg_attr(test, rpc(server, client, namespace = "odyssey"))]
pub trait OdysseyWallTimeRpcApi {
/// Return the wall time and block timestamp of the latest block.
#[method(name = "getWallTimeData")]
async fn get_timedata(&self) -> RpcResult<WallTimeData>;
}

#[async_trait]
impl OdysseyWallTimeRpcApiServer for OdysseyWallTime {
async fn get_timedata(&self) -> RpcResult<WallTimeData> {
let Some(current) = self.current_block_time().await else {
return Err(ErrorObject::owned(INTERNAL_ERROR_CODE, "node is not synced", None::<()>));
};
Ok(WallTimeData {
current_wall_time_ms: unix_epoch_ms(),
last_block_wall_time_ms: current.wall_time_ms,
last_block_timestamp: current.block_timestamp,
})
}
}

/// Time data about the last block.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct BlockTimeData {
/// Wall time of last block
wall_time_ms: u64,
/// Timestamp of last block (chain time)
block_timestamp: u64,
}

/// Returns the current unix epoch in milliseconds.
pub fn unix_epoch_ms() -> u64 {
use std::time::SystemTime;
let now = SystemTime::now();
now.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_else(|err| panic!("Current time {now:?} is invalid: {err:?}"))
.as_millis() as u64
}

0 comments on commit f42997d

Please sign in to comment.