use alloy::{
network::{Ethereum, Network},
primitives::B256,
providers::Provider,
};
use async_trait::async_trait;
use newton_chainio::error::ChainIoError;
use newton_core::state_commit_registry::StateCommitRegistry::StateCommitRegistryInstance;
use crate::state_commit::error::{from_chainio, StateCommitError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RegistryView {
pub sequence_no: u64,
pub state_root: B256,
pub last_commit_timestamp: u64,
}
#[async_trait]
pub trait RegistryReader: Send + Sync + 'static {
async fn read_view(&self) -> Result<RegistryView, StateCommitError>;
}
pub struct OnchainRegistryReader<P, N = Ethereum>
where
P: Provider<N> + Clone + 'static,
N: Network,
{
instance: StateCommitRegistryInstance<P, N>,
chain_id: u64,
}
impl<P, N> OnchainRegistryReader<P, N>
where
P: Provider<N> + Clone + 'static,
N: Network,
{
pub fn new(instance: StateCommitRegistryInstance<P, N>, chain_id: u64) -> Self {
Self { instance, chain_id }
}
pub fn chain_id(&self) -> u64 {
self.chain_id
}
}
impl<P, N> std::fmt::Debug for OnchainRegistryReader<P, N>
where
P: Provider<N> + Clone + 'static,
N: Network,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OnchainRegistryReader")
.field("chain_id", &self.chain_id)
.finish()
}
}
#[async_trait]
impl<P, N> RegistryReader for OnchainRegistryReader<P, N>
where
P: Provider<N> + Clone + 'static,
N: Network,
{
async fn read_view(&self) -> Result<RegistryView, StateCommitError> {
let seq_call = self.instance.currentSequenceNo();
let root_call = self.instance.currentStateRoot();
let ts_call = self.instance.lastCommitTimestamp();
let (sequence_no, state_root, last_commit_timestamp) =
tokio::try_join!(seq_call.call(), root_call.call(), ts_call.call())
.map_err(|e| from_chainio(ChainIoError::ContractError(e)))?;
Ok(RegistryView {
sequence_no,
state_root,
last_commit_timestamp,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use tokio::sync::Mutex;
struct FakeRegistryReader {
state: Arc<Mutex<FakeState>>,
}
#[derive(Clone)]
struct FakeState {
view: RegistryView,
next_error: bool,
call_count: usize,
}
impl FakeRegistryReader {
fn new(view: RegistryView) -> Self {
Self {
state: Arc::new(Mutex::new(FakeState {
view,
next_error: false,
call_count: 0,
})),
}
}
async fn set_view(&self, view: RegistryView) {
self.state.lock().await.view = view;
}
async fn set_next_error(&self) {
self.state.lock().await.next_error = true;
}
async fn call_count(&self) -> usize {
self.state.lock().await.call_count
}
}
#[async_trait]
impl RegistryReader for FakeRegistryReader {
async fn read_view(&self) -> Result<RegistryView, StateCommitError> {
let mut state = self.state.lock().await;
state.call_count += 1;
if state.next_error {
state.next_error = false;
return Err(StateCommitError::RegistryNotConfigured);
}
Ok(state.view)
}
}
#[tokio::test]
async fn fake_returns_initial_view() {
let initial = RegistryView {
sequence_no: 5,
state_root: B256::repeat_byte(0xab),
last_commit_timestamp: 1_000,
};
let reader = FakeRegistryReader::new(initial);
let view = reader.read_view().await.expect("fake reader succeeds");
assert_eq!(view, initial);
assert_eq!(reader.call_count().await, 1);
}
#[tokio::test]
async fn fake_reflects_view_updates_between_calls() {
let initial = RegistryView {
sequence_no: 5,
state_root: B256::repeat_byte(0xab),
last_commit_timestamp: 1_000,
};
let reader = FakeRegistryReader::new(initial);
let v1 = reader.read_view().await.expect("first read");
assert_eq!(v1.sequence_no, 5);
let next = RegistryView {
sequence_no: 6,
state_root: B256::repeat_byte(0xcd),
last_commit_timestamp: 2_000,
};
reader.set_view(next).await;
let v2 = reader.read_view().await.expect("second read");
assert_eq!(v2, next);
assert_eq!(reader.call_count().await, 2);
}
#[tokio::test]
async fn trait_is_object_safe_behind_arc() {
let view = RegistryView {
sequence_no: 0,
state_root: B256::ZERO,
last_commit_timestamp: 0,
};
let reader: Arc<dyn RegistryReader> = Arc::new(FakeRegistryReader::new(view));
let read = reader.read_view().await.expect("fake reader succeeds");
assert_eq!(read, view);
}
#[tokio::test]
async fn fake_can_inject_errors_for_orchestrator_tests() {
let reader = FakeRegistryReader::new(RegistryView {
sequence_no: 0,
state_root: B256::ZERO,
last_commit_timestamp: 0,
});
reader.set_next_error().await;
let err = reader.read_view().await.expect_err("error injected");
assert!(matches!(err, StateCommitError::RegistryNotConfigured));
reader.read_view().await.expect("recovers after one-shot error");
}
#[test]
fn registry_view_is_copy_and_eq() {
let v1 = RegistryView {
sequence_no: 1,
state_root: B256::repeat_byte(0x01),
last_commit_timestamp: 100,
};
let v2 = v1; assert_eq!(v1, v2);
let v3 = RegistryView { sequence_no: 2, ..v1 };
assert_ne!(v1, v3);
}
}