use std::{
collections::{BTreeMap, BTreeSet},
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering},
},
time::SystemTime,
};
use alloy_primitives::{Address, B256, Bytes, I256};
use tokio::sync::broadcast;
use crate::{FeedId, FeedRegistration, OracleAdapterId};
mod adapter;
mod chainlink;
#[cfg(feature = "pending-oracle-ethereum")]
mod ethereum;
#[cfg(feature = "pending-oracle-mev-share")]
mod mev_share;
mod source;
use adapter::adapter_owns_update;
pub use adapter::{
ChainlinkPendingAdapter, PendingOracleAdapter, PendingOracleAdapterError,
PendingOracleAdapterFailure, PendingOracleAdapterObservation, PendingOracleCandidateReport,
PendingOracleInterest,
};
pub use chainlink::{
CHAINLINK_FORWARD_SELECTOR, CHAINLINK_TRANSMIT_SECONDARY_SELECTOR, CHAINLINK_TRANSMIT_SELECTOR,
ChainlinkPendingReport, DecodedChainlinkOcr2, PendingOracleDecodeError,
decode_chainlink_ocr2_calldata,
};
#[cfg(feature = "pending-oracle-ethereum")]
pub use ethereum::AlchemyPendingTransactionSource;
#[cfg(feature = "pending-oracle-mev-share")]
pub use mev_share::MevSharePendingTransactionSource;
pub use source::{
PendingOracleCandidateSource, PendingOracleCoverageGap, PendingOracleSourceDescriptor,
PendingOracleSourceError, PendingOracleSourceFuture, PendingOracleSourceHealth,
PendingOracleSourceSession, PendingOracleSourceSink, PendingOracleSourceState,
};
pub const ETHEREUM_MAINNET_CHAIN_ID: u64 = 1;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleConfig {
chain_id: u64,
feed_filter: PendingFeedFilter,
sources: BTreeSet<PendingOracleSource>,
channel_capacity: usize,
}
impl PendingOracleConfig {
pub const fn for_chain(chain_id: u64) -> Self {
Self {
chain_id,
feed_filter: PendingFeedFilter::AllRegistered,
sources: BTreeSet::new(),
channel_capacity: 1_024,
}
}
pub const fn ethereum_mainnet() -> Self {
Self::for_chain(ETHEREUM_MAINNET_CHAIN_ID)
}
pub const fn chain_id(&self) -> u64 {
self.chain_id
}
pub fn registered_feeds(mut self, filter: PendingFeedFilter) -> Self {
self.feed_filter = filter;
self
}
pub const fn feed_filter(&self) -> &PendingFeedFilter {
&self.feed_filter
}
pub fn public_mempool(mut self) -> Self {
self.sources.insert(PendingOracleSource::PublicMempool);
self
}
pub fn mev_share(mut self) -> Self {
self.sources.insert(PendingOracleSource::MevShare);
self
}
pub fn source(mut self, source: PendingOracleSource) -> Self {
self.sources.insert(source);
self
}
pub fn source_enabled(&self, source: &PendingOracleSource) -> bool {
self.sources.contains(source)
}
pub fn sources(&self) -> impl Iterator<Item = PendingOracleSource> + '_ {
self.sources.iter().cloned()
}
pub fn channel_capacity(mut self, channel_capacity: usize) -> Self {
self.channel_capacity = channel_capacity.max(1);
self
}
pub const fn channel_capacity_value(&self) -> usize {
self.channel_capacity
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PendingOracleSource {
PublicMempool,
MevShare,
Custom(String),
}
impl PendingOracleSource {
pub fn custom(id: impl Into<String>) -> Self {
Self::Custom(id.into())
}
pub fn as_str(&self) -> &str {
match self {
Self::PublicMempool => "ethereum-public-mempool",
Self::MevShare => "flashbots-mev-share",
Self::Custom(id) => id,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum PendingFeedFilter {
#[default]
AllRegistered,
Only(BTreeSet<FeedId>),
}
impl PendingFeedFilter {
pub fn only<I, S>(ids: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self::Only(ids.into_iter().map(|id| FeedId::new(id.into())).collect())
}
fn includes(&self, id: &FeedId) -> bool {
match self {
Self::AllRegistered => true,
Self::Only(ids) => ids.contains(id),
}
}
}
#[derive(Clone)]
pub struct PendingOracleRuntime {
config: PendingOracleConfig,
feed_ids: Arc<Mutex<Vec<FeedId>>>,
registrations: Arc<Mutex<Vec<FeedRegistration>>>,
adapters: Arc<Mutex<BTreeMap<OracleAdapterId, Arc<dyn PendingOracleAdapter>>>>,
sender: broadcast::Sender<PendingOracleStreamEvent>,
sequence: Arc<AtomicU64>,
updates: Arc<Mutex<BTreeMap<PendingOracleUpdateId, PendingOracleUpdate>>>,
source_health: Arc<Mutex<BTreeMap<PendingOracleSourceId, PendingOracleSourceHealth>>>,
}
impl std::fmt::Debug for PendingOracleRuntime {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("PendingOracleRuntime")
.field("config", &self.config)
.field(
"feed_ids",
&self
.feed_ids
.lock()
.unwrap_or_else(|error| error.into_inner()),
)
.field(
"adapter_ids",
&self
.adapters
.lock()
.unwrap_or_else(|error| error.into_inner())
.keys()
.collect::<Vec<_>>(),
)
.field("update_count", &self.update_count())
.finish_non_exhaustive()
}
}
impl PendingOracleRuntime {
pub(crate) fn from_registrations<'a>(
config: PendingOracleConfig,
registrations: impl IntoIterator<Item = &'a FeedRegistration>,
adapters: impl IntoIterator<Item = Arc<dyn PendingOracleAdapter>>,
) -> Self {
let feed_filter = config.feed_filter().clone();
let (sender, _) = broadcast::channel(config.channel_capacity_value());
let registrations = registrations
.into_iter()
.filter(|registration| feed_filter.includes(®istration.id))
.cloned()
.collect::<Vec<_>>();
let mut installed = BTreeMap::<OracleAdapterId, Arc<dyn PendingOracleAdapter>>::new();
let chainlink: Arc<dyn PendingOracleAdapter> = Arc::new(ChainlinkPendingAdapter);
installed.insert(chainlink.adapter_id(), chainlink);
for adapter in adapters {
installed.entry(adapter.adapter_id()).or_insert(adapter);
}
Self {
config,
feed_ids: Arc::new(Mutex::new(
registrations
.iter()
.map(|registration| registration.id.clone())
.collect(),
)),
registrations: Arc::new(Mutex::new(registrations)),
adapters: Arc::new(Mutex::new(installed)),
sender,
sequence: Arc::new(AtomicU64::new(0)),
updates: Arc::new(Mutex::new(BTreeMap::new())),
source_health: Arc::new(Mutex::new(BTreeMap::new())),
}
}
pub fn interests(&self) -> Vec<PendingOracleInterest> {
let registrations = self
.registrations
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone();
self.adapters
.lock()
.unwrap_or_else(|error| error.into_inner())
.values()
.flat_map(|adapter| adapter.interests(®istrations))
.collect()
}
pub fn register_adapter<A>(&self, adapter: A) -> bool
where
A: PendingOracleAdapter,
{
let id = adapter.adapter_id();
let mut adapters = self
.adapters
.lock()
.unwrap_or_else(|error| error.into_inner());
if adapters.contains_key(&id) {
return false;
}
adapters.insert(id, Arc::new(adapter));
true
}
pub const fn config(&self) -> &PendingOracleConfig {
&self.config
}
pub fn feed_ids(&self) -> Vec<FeedId> {
self.feed_ids
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone()
}
pub fn subscribe(&self) -> PendingOracleSubscription {
PendingOracleSubscription {
receiver: self.sender.subscribe(),
}
}
pub fn publisher(&self) -> PendingOraclePublisher {
PendingOraclePublisher {
sender: self.sender.clone(),
sequence: Arc::clone(&self.sequence),
}
}
pub fn start_source<S>(
&self,
source: S,
) -> Result<PendingOracleSourceSession, PendingOracleSourceError>
where
S: PendingOracleCandidateSource,
{
let descriptor = source.descriptor();
if !self.config.source_enabled(&descriptor.source) {
return Err(PendingOracleSourceError::SourceDisabled {
transport: descriptor.source.clone(),
});
}
if self
.source_health
.lock()
.unwrap_or_else(|error| error.into_inner())
.get(&descriptor.id)
.is_some_and(|health| health.state != PendingOracleSourceState::Stopped)
{
return Err(PendingOracleSourceError::DuplicateSource {
id: descriptor.id.clone(),
});
}
let handle = tokio::runtime::Handle::try_current()
.map_err(|_| PendingOracleSourceError::RuntimeUnavailable)?;
let (shutdown, receiver) = tokio::sync::watch::channel(false);
let sink = PendingOracleSourceSink::new(self.clone(), descriptor.clone());
sink.connecting();
let task_sink = sink.clone();
let task = handle.spawn(async move {
let result = Box::new(source).run(task_sink.clone(), receiver).await;
match &result {
Ok(()) => task_sink.stopped(),
Err(error) => task_sink.failed(error),
}
result
});
Ok(PendingOracleSourceSession::new(descriptor, shutdown, task))
}
pub fn source_health(
&self,
source_id: &PendingOracleSourceId,
) -> Option<PendingOracleSourceHealth> {
self.source_health
.lock()
.unwrap_or_else(|error| error.into_inner())
.get(source_id)
.cloned()
}
pub(crate) fn insert_source_health(&self, health: PendingOracleSourceHealth) {
self.source_health
.lock()
.unwrap_or_else(|error| error.into_inner())
.insert(health.descriptor.id.clone(), health.clone());
self.publisher()
.publish(PendingOracleEvent::SourceHealthChanged(health));
}
pub(crate) fn mutate_source_health(
&self,
descriptor: &PendingOracleSourceDescriptor,
mutate: impl FnOnce(&mut PendingOracleSourceHealth),
) {
let mut health = self
.source_health
.lock()
.unwrap_or_else(|error| error.into_inner());
let health =
health
.entry(descriptor.id.clone())
.or_insert_with(|| PendingOracleSourceHealth {
descriptor: descriptor.clone(),
state: PendingOracleSourceState::Connecting,
last_transport_message_at: None,
last_candidate_at: None,
coverage_gap_count: 0,
last_error: None,
});
mutate(health);
}
pub(crate) fn update_source_health(
&self,
descriptor: &PendingOracleSourceDescriptor,
mutate: impl FnOnce(&mut PendingOracleSourceHealth),
) {
let health = {
let mut sources = self
.source_health
.lock()
.unwrap_or_else(|error| error.into_inner());
let health =
sources
.entry(descriptor.id.clone())
.or_insert_with(|| PendingOracleSourceHealth {
descriptor: descriptor.clone(),
state: PendingOracleSourceState::Connecting,
last_transport_message_at: None,
last_candidate_at: None,
coverage_gap_count: 0,
last_error: None,
});
mutate(health);
health.clone()
};
self.publisher()
.publish(PendingOracleEvent::SourceHealthChanged(health));
}
pub fn observe(&self, update: PendingOracleUpdate) -> PendingOracleObserveOutcome {
if update.status != PendingOracleStatus::Pending
|| update
.transmissions
.iter()
.any(|transmission| transmission.status != PendingOracleTransmissionStatus::Pending)
{
return PendingOracleObserveOutcome::Conflict;
}
let feed_ids = self
.feed_ids
.lock()
.unwrap_or_else(|error| error.into_inner());
if update.feed_ids.iter().any(|id| !feed_ids.contains(id)) {
return PendingOracleObserveOutcome::OutsideScope;
}
drop(feed_ids);
let mut updates = self
.updates
.lock()
.unwrap_or_else(|error| error.into_inner());
if let Some(existing) = updates.get_mut(&update.id) {
if existing.status != PendingOracleStatus::Pending {
return PendingOracleObserveOutcome::Conflict;
}
if existing.feed_ids != update.feed_ids
|| existing.evidence != update.evidence
|| existing.proposed_value != update.proposed_value
{
return PendingOracleObserveOutcome::Conflict;
}
if update.transmissions.iter().any(|candidate| {
existing
.transmissions
.iter()
.find(|tracked| tracked.id == candidate.id)
.is_some_and(|tracked| !tracked.same_variant_content(candidate))
}) {
return PendingOracleObserveOutcome::Conflict;
}
let added = update
.transmissions
.into_iter()
.filter(|candidate| {
!existing
.transmissions
.iter()
.any(|tracked| tracked.id == candidate.id)
})
.collect::<Vec<_>>();
existing.transmissions.extend(added.iter().cloned());
drop(updates);
for transmission in &added {
self.publisher()
.publish(PendingOracleEvent::TransmissionAdded {
update_id: update.id,
transmission: transmission.clone(),
});
}
return if added.is_empty() {
PendingOracleObserveOutcome::Duplicate
} else {
PendingOracleObserveOutcome::TransmissionAdded { count: added.len() }
};
}
updates.insert(update.id, update.clone());
drop(updates);
self.publisher()
.publish(PendingOracleEvent::Observed(update));
PendingOracleObserveOutcome::Observed
}
pub fn observe_candidate(
&self,
candidate: PendingTransportCandidate,
) -> Result<PendingOracleCandidateReport, PendingOracleAdapterError> {
if candidate.chain_id != self.config.chain_id() {
return Err(PendingOracleAdapterError::WrongChain {
expected: self.config.chain_id(),
observed: candidate.chain_id,
});
}
if !self.config.source_enabled(&candidate.source) {
return Err(PendingOracleAdapterError::SourceDisabled(
candidate.source.clone(),
));
}
let registrations = self
.registrations
.lock()
.unwrap_or_else(|error| error.into_inner())
.clone();
let adapters = self
.adapters
.lock()
.unwrap_or_else(|error| error.into_inner())
.values()
.cloned()
.collect::<Vec<_>>();
let mut report = PendingOracleCandidateReport::default();
for adapter in adapters {
if !adapter
.interests(®istrations)
.iter()
.any(|interest| interest.matches(&candidate))
{
continue;
}
let adapter_id = adapter.adapter_id();
let updates = match adapter.decode(&candidate, ®istrations) {
Ok(updates) => updates,
Err(error) => {
report
.failures
.push(PendingOracleAdapterFailure { adapter_id, error });
continue;
}
};
for update in updates {
let update_id = update.id;
report.observations.push(PendingOracleAdapterObservation {
adapter_id: adapter_id.clone(),
update_id,
outcome: self.observe(update),
});
}
}
Ok(report)
}
pub fn update(&self, id: &PendingOracleUpdateId) -> Option<PendingOracleUpdate> {
self.updates
.lock()
.unwrap_or_else(|error| error.into_inner())
.get(id)
.cloned()
}
pub fn update_count(&self) -> usize {
self.updates
.lock()
.unwrap_or_else(|error| error.into_inner())
.len()
}
pub fn resolve_confirmed_transmission(
&self,
resolution: PendingOracleResolution,
) -> PendingOracleResolveOutcome {
let mut updates = self
.updates
.lock()
.unwrap_or_else(|error| error.into_inner());
let Some(update) = updates.get_mut(&resolution.update_id) else {
return PendingOracleResolveOutcome::UnknownUpdate;
};
let Some(transmission) = update
.transmissions
.iter_mut()
.find(|transmission| transmission.id == resolution.transmission_id)
else {
return PendingOracleResolveOutcome::UnknownTransmission;
};
if transmission
.ordering
.transaction_hash()
.is_some_and(|hash| hash != resolution.transaction_hash)
{
return PendingOracleResolveOutcome::Conflict;
}
let target = match resolution.kind {
PendingOracleResolutionKind::Landed => PendingOracleTransmissionStatus::Landed,
PendingOracleResolutionKind::Reverted => PendingOracleTransmissionStatus::Reverted,
};
if transmission.status == target {
return PendingOracleResolveOutcome::Duplicate;
}
if transmission.status != PendingOracleTransmissionStatus::Pending {
return PendingOracleResolveOutcome::Conflict;
}
transmission.status = target;
update.status =
if resolution.kind == PendingOracleResolutionKind::Landed {
PendingOracleStatus::Landed
} else if update.transmissions.iter().all(|transmission| {
transmission.status == PendingOracleTransmissionStatus::Reverted
}) {
PendingOracleStatus::Reverted
} else {
PendingOracleStatus::Pending
};
drop(updates);
self.publisher()
.publish(PendingOracleEvent::Resolved(resolution));
PendingOracleResolveOutcome::Resolved
}
pub fn observe_confirmed_log(
&self,
log: &alloy_rpc_types_eth::Log,
) -> Vec<PendingOracleResolution> {
if log.removed {
return Vec::new();
}
let (Some(transaction_hash), Some(block_number)) = (log.transaction_hash, log.block_number)
else {
return Vec::new();
};
let updates = self
.updates
.lock()
.unwrap_or_else(|error| error.into_inner())
.values()
.filter(|update| update.status == PendingOracleStatus::Pending)
.cloned()
.collect::<Vec<_>>();
let adapters = self
.adapters
.lock()
.unwrap_or_else(|error| error.into_inner())
.values()
.cloned()
.collect::<Vec<_>>();
let mut resolutions = Vec::new();
for update in updates {
if !adapters.iter().any(|adapter| {
adapter_owns_update(&adapter.adapter_id(), &update)
&& adapter.confirms(&update, log)
}) {
continue;
}
for transmission in update.transmissions.iter().filter(|transmission| {
transmission.status == PendingOracleTransmissionStatus::Pending
&& transmission.ordering.transaction_hash() == Some(transaction_hash)
}) {
let resolution = PendingOracleResolution::new(
update.id,
transmission.id,
transaction_hash,
block_number,
PendingOracleResolutionKind::Landed,
);
if self.resolve_confirmed_transmission(resolution.clone())
== PendingOracleResolveOutcome::Resolved
{
resolutions.push(resolution);
}
}
}
resolutions
}
pub fn observe_chainlink_candidate(
&self,
candidate: PendingTransportCandidate,
) -> Result<PendingOracleCandidateOutcome, PendingOracleAdapterError> {
match self.observe_candidate(candidate) {
Ok(report) => {
if let Some(failure) = report
.failures
.into_iter()
.find(|failure| failure.adapter_id.as_str() == ChainlinkPendingAdapter::ID)
{
return Err(failure.error);
}
Ok(report
.observations
.into_iter()
.find(|observation| {
observation.adapter_id.as_str() == ChainlinkPendingAdapter::ID
})
.map_or(PendingOracleCandidateOutcome::Ignored, |observation| {
PendingOracleCandidateOutcome::Tracked(observation.outcome)
}))
}
Err(PendingOracleAdapterError::WrongChain { expected, observed }) => {
Ok(PendingOracleCandidateOutcome::WrongChain { expected, observed })
}
Err(PendingOracleAdapterError::SourceDisabled(source)) => {
Ok(PendingOracleCandidateOutcome::SourceDisabled(source))
}
Err(error) => Err(error),
}
}
pub(crate) fn refresh<'a>(
&mut self,
registrations: impl IntoIterator<Item = &'a FeedRegistration>,
) {
let registrations = registrations
.into_iter()
.filter(|registration| self.config.feed_filter().includes(®istration.id))
.cloned()
.collect::<Vec<_>>();
*self
.feed_ids
.lock()
.unwrap_or_else(|error| error.into_inner()) = registrations
.iter()
.map(|registration| registration.id.clone())
.collect();
*self
.registrations
.lock()
.unwrap_or_else(|error| error.into_inner()) = registrations.clone();
let in_scope = self
.feed_ids
.lock()
.unwrap_or_else(|error| error.into_inner())
.iter()
.cloned()
.collect::<BTreeSet<_>>();
let mut expired = Vec::new();
let mut updates = self
.updates
.lock()
.unwrap_or_else(|error| error.into_inner());
updates.retain(|id, update| {
update.feed_ids.retain(|feed_id| {
if !in_scope.contains(feed_id) {
return false;
}
match &update.evidence {
PendingOracleEvidence::ChainlinkReport(report) => {
registrations.iter().any(|registration| {
®istration.id == feed_id
&& registration.current_aggregator == Some(report.aggregator)
})
}
PendingOracleEvidence::Adapter { .. } => true,
}
});
if update.feed_ids.is_empty() {
expired.push(*id);
false
} else {
true
}
});
drop(updates);
for id in expired {
self.publisher().publish(PendingOracleEvent::Expired(id));
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PendingOracleObserveOutcome {
Observed,
TransmissionAdded {
count: usize,
},
Duplicate,
Conflict,
OutsideScope,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PendingOracleCandidateOutcome {
Ignored,
SourceDisabled(PendingOracleSource),
WrongChain {
expected: u64,
observed: u64,
},
AggregatorOutsideScope(Address),
Tracked(PendingOracleObserveOutcome),
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PendingOracleStatus {
#[default]
Pending,
Landed,
Reverted,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PendingOracleResolutionKind {
Landed,
Reverted,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleResolution {
pub update_id: PendingOracleUpdateId,
pub transmission_id: PendingOracleTransmissionId,
pub transaction_hash: B256,
pub block_number: u64,
pub kind: PendingOracleResolutionKind,
}
impl PendingOracleResolution {
pub const fn new(
update_id: PendingOracleUpdateId,
transmission_id: PendingOracleTransmissionId,
transaction_hash: B256,
block_number: u64,
kind: PendingOracleResolutionKind,
) -> Self {
Self {
update_id,
transmission_id,
transaction_hash,
block_number,
kind,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PendingOracleResolveOutcome {
Resolved,
Duplicate,
UnknownUpdate,
UnknownTransmission,
Conflict,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingTransportCandidate {
pub chain_id: u64,
pub id: PendingOracleTransmissionId,
pub source: PendingOracleSource,
pub source_id: PendingOracleSourceId,
pub to: Address,
pub calldata: Bytes,
pub ordering: PendingOracleOrderingHandle,
pub observed_at_head: Option<u64>,
}
impl PendingTransportCandidate {
#[allow(clippy::too_many_arguments)]
pub fn new(
chain_id: u64,
id: PendingOracleTransmissionId,
source: PendingOracleSource,
source_id: PendingOracleSourceId,
to: Address,
calldata: Bytes,
ordering: PendingOracleOrderingHandle,
observed_at_head: Option<u64>,
) -> Self {
Self {
chain_id,
id,
source,
source_id,
to,
calldata,
ordering,
observed_at_head,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PendingOracleUpdateId(B256);
impl PendingOracleUpdateId {
pub const fn from_hash(hash: B256) -> Self {
Self(hash)
}
pub const fn as_hash(&self) -> B256 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PendingOracleTransmissionId(B256);
impl PendingOracleTransmissionId {
pub const fn from_hash(hash: B256) -> Self {
Self(hash)
}
pub const fn as_hash(&self) -> B256 {
self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PendingOracleSourceId(String);
impl PendingOracleSourceId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for PendingOracleSourceId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(formatter)
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PendingOracleRoute {
ChainlinkDirect {
aggregator: Address,
secondary: bool,
},
ChainlinkForwarded {
forwarder: Address,
aggregator: Address,
secondary: bool,
},
Adapter {
adapter_id: OracleAdapterId,
kind: String,
},
}
impl PendingOracleRoute {
pub fn adapter(adapter_id: OracleAdapterId, kind: impl Into<String>) -> Self {
Self::Adapter {
adapter_id,
kind: kind.into(),
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum PendingOracleOrderingHandle {
MevShare {
hash: B256,
},
RawEthereumTransaction {
tx_hash: B256,
signed_envelope: Bytes,
},
TransactionHashOnly {
tx_hash: B256,
},
SequencerPreconfirmation {
tx_hash: B256,
block_number: u64,
payload_id: B256,
sequence_index: u64,
},
#[default]
None,
}
impl PendingOracleOrderingHandle {
pub const fn is_referenceable(&self) -> bool {
!matches!(self, Self::None)
}
pub const fn transaction_hash(&self) -> Option<B256> {
match self {
Self::MevShare { hash } => Some(*hash),
Self::RawEthereumTransaction { tx_hash, .. }
| Self::TransactionHashOnly { tx_hash }
| Self::SequencerPreconfirmation { tx_hash, .. } => Some(*tx_hash),
Self::None => None,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum PendingOracleSimulationFidelity {
InsufficientMaterial,
AnswerOnlyProjection,
VerifiedStateProjection,
ExactSignedTransaction,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PendingOracleSimulationMaterial {
ExactSignedTransaction,
VerifiedStateProjection,
AnswerOnlyProjection,
InsufficientMaterial,
}
impl PendingOracleSimulationMaterial {
pub const fn fidelity(self) -> PendingOracleSimulationFidelity {
match self {
Self::ExactSignedTransaction => PendingOracleSimulationFidelity::ExactSignedTransaction,
Self::VerifiedStateProjection => {
PendingOracleSimulationFidelity::VerifiedStateProjection
}
Self::AnswerOnlyProjection => PendingOracleSimulationFidelity::AnswerOnlyProjection,
Self::InsufficientMaterial => PendingOracleSimulationFidelity::InsufficientMaterial,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PendingOracleTransmissionStatus {
#[default]
Pending,
Landed,
Reverted,
Dropped,
Superseded,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleTransmission {
pub id: PendingOracleTransmissionId,
pub source: PendingOracleSourceId,
pub route: PendingOracleRoute,
pub ordering: PendingOracleOrderingHandle,
pub simulation: PendingOracleSimulationMaterial,
pub calldata_hash: B256,
pub observed_at_head: Option<u64>,
pub first_seen_at: SystemTime,
pub status: PendingOracleTransmissionStatus,
}
impl PendingOracleTransmission {
pub fn new(
id: PendingOracleTransmissionId,
source: PendingOracleSourceId,
route: PendingOracleRoute,
ordering: PendingOracleOrderingHandle,
simulation: PendingOracleSimulationMaterial,
calldata_hash: B256,
observed_at_head: Option<u64>,
) -> Self {
Self {
id,
source,
route,
ordering,
simulation,
calldata_hash,
observed_at_head,
first_seen_at: SystemTime::now(),
status: PendingOracleTransmissionStatus::Pending,
}
}
pub const fn is_referenceable(&self) -> bool {
self.ordering.is_referenceable()
}
pub const fn actionability_rank(&self) -> u8 {
match (&self.ordering, self.simulation.fidelity()) {
(
PendingOracleOrderingHandle::RawEthereumTransaction { .. },
PendingOracleSimulationFidelity::ExactSignedTransaction,
) => 4,
(PendingOracleOrderingHandle::MevShare { .. }, _) => 3,
(PendingOracleOrderingHandle::RawEthereumTransaction { .. }, _) => 2,
(PendingOracleOrderingHandle::TransactionHashOnly { .. }, _) => 1,
(PendingOracleOrderingHandle::SequencerPreconfirmation { .. }, _) => 1,
(PendingOracleOrderingHandle::None, _) => 0,
}
}
fn same_variant_content(&self, other: &Self) -> bool {
self.id == other.id
&& self.source == other.source
&& self.route == other.route
&& self.ordering == other.ordering
&& self.simulation == other.simulation
&& self.calldata_hash == other.calldata_hash
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PendingOracleEvidence {
ChainlinkReport(ChainlinkPendingReport),
Adapter {
adapter_id: OracleAdapterId,
kind: String,
digest: B256,
},
}
impl PendingOracleEvidence {
pub fn adapter(adapter_id: OracleAdapterId, kind: impl Into<String>, digest: B256) -> Self {
Self::Adapter {
adapter_id,
kind: kind.into(),
digest,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleUpdate {
pub id: PendingOracleUpdateId,
pub feed_ids: Vec<FeedId>,
pub evidence: PendingOracleEvidence,
pub proposed_value: Option<PendingOracleValue>,
pub transmissions: Vec<PendingOracleTransmission>,
pub status: PendingOracleStatus,
}
impl PendingOracleUpdate {
pub fn new(
id: PendingOracleUpdateId,
feed_ids: impl IntoIterator<Item = FeedId>,
evidence: PendingOracleEvidence,
) -> Self {
Self {
id,
feed_ids: feed_ids.into_iter().collect(),
evidence,
proposed_value: None,
transmissions: Vec::new(),
status: PendingOracleStatus::Pending,
}
}
pub fn with_proposed_value(mut self, proposed_value: PendingOracleValue) -> Self {
self.proposed_value = Some(proposed_value);
self
}
pub fn with_transmission(mut self, transmission: PendingOracleTransmission) -> Self {
self.transmissions.push(transmission);
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleValue {
pub raw_answer: I256,
pub observed_at: u64,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PendingOracleEvent {
Observed(PendingOracleUpdate),
TransmissionAdded {
update_id: PendingOracleUpdateId,
transmission: PendingOracleTransmission,
},
Expired(PendingOracleUpdateId),
Resolved(PendingOracleResolution),
SourceHealthChanged(PendingOracleSourceHealth),
CoverageGap(PendingOracleCoverageGap),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PendingOracleStreamEvent {
pub sequence: u64,
pub event: PendingOracleEvent,
}
#[derive(Clone, Debug)]
pub struct PendingOraclePublisher {
sender: broadcast::Sender<PendingOracleStreamEvent>,
sequence: Arc<AtomicU64>,
}
impl PendingOraclePublisher {
pub fn publish(&self, event: PendingOracleEvent) -> usize {
let sequence = self.sequence.fetch_add(1, Ordering::Relaxed) + 1;
self.sender
.send(PendingOracleStreamEvent { sequence, event })
.unwrap_or(0)
}
}
#[derive(Debug)]
pub struct PendingOracleSubscription {
receiver: broadcast::Receiver<PendingOracleStreamEvent>,
}
impl PendingOracleSubscription {
pub async fn recv(&mut self) -> Result<PendingOracleStreamEvent, PendingOracleRecvError> {
self.receiver.recv().await.map_err(Into::into)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum PendingOracleRecvError {
#[error("pending oracle channel closed")]
Closed,
#[error("pending oracle receiver lagged by {missed} events")]
Lagged {
missed: u64,
},
}
impl From<broadcast::error::RecvError> for PendingOracleRecvError {
fn from(error: broadcast::error::RecvError) -> Self {
match error {
broadcast::error::RecvError::Closed => Self::Closed,
broadcast::error::RecvError::Lagged(missed) => Self::Lagged { missed },
}
}
}