1use std::{
6 collections::{BTreeMap, BTreeSet},
7 sync::{
8 Arc, Mutex,
9 atomic::{AtomicU64, Ordering},
10 },
11 time::SystemTime,
12};
13
14use alloy_primitives::{Address, B256, Bytes, I256};
15use tokio::sync::broadcast;
16
17use crate::{FeedId, FeedRegistration, OracleAdapterId};
18
19mod adapter;
20mod chainlink;
21#[cfg(feature = "pending-oracle-ethereum")]
22mod ethereum;
23#[cfg(feature = "pending-oracle-mev-share")]
24mod mev_share;
25mod source;
26
27use adapter::adapter_owns_update;
28pub use adapter::{
29 ChainlinkPendingAdapter, PendingOracleAdapter, PendingOracleAdapterError,
30 PendingOracleAdapterFailure, PendingOracleAdapterObservation, PendingOracleCandidateReport,
31 PendingOracleInterest,
32};
33pub use chainlink::{
34 CHAINLINK_FORWARD_SELECTOR, CHAINLINK_TRANSMIT_SECONDARY_SELECTOR, CHAINLINK_TRANSMIT_SELECTOR,
35 ChainlinkPendingReport, DecodedChainlinkOcr2, PendingOracleDecodeError,
36 decode_chainlink_ocr2_calldata,
37};
38#[cfg(feature = "pending-oracle-ethereum")]
39pub use ethereum::AlchemyPendingTransactionSource;
40#[cfg(feature = "pending-oracle-mev-share")]
41pub use mev_share::MevSharePendingTransactionSource;
42pub use source::{
43 PendingOracleCandidateSource, PendingOracleCoverageGap, PendingOracleSourceDescriptor,
44 PendingOracleSourceError, PendingOracleSourceFuture, PendingOracleSourceHealth,
45 PendingOracleSourceSession, PendingOracleSourceSink, PendingOracleSourceState,
46};
47
48pub const ETHEREUM_MAINNET_CHAIN_ID: u64 = 1;
50
51#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct PendingOracleConfig {
54 chain_id: u64,
55 feed_filter: PendingFeedFilter,
56 sources: BTreeSet<PendingOracleSource>,
57 channel_capacity: usize,
58}
59
60impl PendingOracleConfig {
61 pub const fn for_chain(chain_id: u64) -> Self {
63 Self {
64 chain_id,
65 feed_filter: PendingFeedFilter::AllRegistered,
66 sources: BTreeSet::new(),
67 channel_capacity: 1_024,
68 }
69 }
70
71 pub const fn ethereum_mainnet() -> Self {
73 Self::for_chain(ETHEREUM_MAINNET_CHAIN_ID)
74 }
75
76 pub const fn chain_id(&self) -> u64 {
78 self.chain_id
79 }
80
81 pub fn registered_feeds(mut self, filter: PendingFeedFilter) -> Self {
83 self.feed_filter = filter;
84 self
85 }
86
87 pub const fn feed_filter(&self) -> &PendingFeedFilter {
89 &self.feed_filter
90 }
91
92 pub fn public_mempool(mut self) -> Self {
94 self.sources.insert(PendingOracleSource::PublicMempool);
95 self
96 }
97
98 pub fn mev_share(mut self) -> Self {
100 self.sources.insert(PendingOracleSource::MevShare);
101 self
102 }
103
104 pub fn source(mut self, source: PendingOracleSource) -> Self {
106 self.sources.insert(source);
107 self
108 }
109
110 pub fn source_enabled(&self, source: &PendingOracleSource) -> bool {
112 self.sources.contains(source)
113 }
114
115 pub fn sources(&self) -> impl Iterator<Item = PendingOracleSource> + '_ {
117 self.sources.iter().cloned()
118 }
119
120 pub fn channel_capacity(mut self, channel_capacity: usize) -> Self {
122 self.channel_capacity = channel_capacity.max(1);
123 self
124 }
125
126 pub const fn channel_capacity_value(&self) -> usize {
128 self.channel_capacity
129 }
130}
131
132#[non_exhaustive]
134#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
135pub enum PendingOracleSource {
136 PublicMempool,
138 MevShare,
140 Custom(String),
142}
143
144impl PendingOracleSource {
145 pub fn custom(id: impl Into<String>) -> Self {
147 Self::Custom(id.into())
148 }
149
150 pub fn as_str(&self) -> &str {
152 match self {
153 Self::PublicMempool => "ethereum-public-mempool",
154 Self::MevShare => "flashbots-mev-share",
155 Self::Custom(id) => id,
156 }
157 }
158}
159
160#[derive(Clone, Debug, Default, PartialEq, Eq)]
162pub enum PendingFeedFilter {
163 #[default]
165 AllRegistered,
166 Only(BTreeSet<FeedId>),
168}
169
170impl PendingFeedFilter {
171 pub fn only<I, S>(ids: I) -> Self
173 where
174 I: IntoIterator<Item = S>,
175 S: Into<String>,
176 {
177 Self::Only(ids.into_iter().map(|id| FeedId::new(id.into())).collect())
178 }
179
180 fn includes(&self, id: &FeedId) -> bool {
181 match self {
182 Self::AllRegistered => true,
183 Self::Only(ids) => ids.contains(id),
184 }
185 }
186}
187
188#[derive(Clone)]
190pub struct PendingOracleRuntime {
191 config: PendingOracleConfig,
192 feed_ids: Arc<Mutex<Vec<FeedId>>>,
193 registrations: Arc<Mutex<Vec<FeedRegistration>>>,
194 adapters: Arc<Mutex<BTreeMap<OracleAdapterId, Arc<dyn PendingOracleAdapter>>>>,
195 sender: broadcast::Sender<PendingOracleStreamEvent>,
196 sequence: Arc<AtomicU64>,
197 updates: Arc<Mutex<BTreeMap<PendingOracleUpdateId, PendingOracleUpdate>>>,
198 source_health: Arc<Mutex<BTreeMap<PendingOracleSourceId, PendingOracleSourceHealth>>>,
199}
200
201impl std::fmt::Debug for PendingOracleRuntime {
202 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203 formatter
204 .debug_struct("PendingOracleRuntime")
205 .field("config", &self.config)
206 .field(
207 "feed_ids",
208 &self
209 .feed_ids
210 .lock()
211 .unwrap_or_else(|error| error.into_inner()),
212 )
213 .field(
214 "adapter_ids",
215 &self
216 .adapters
217 .lock()
218 .unwrap_or_else(|error| error.into_inner())
219 .keys()
220 .collect::<Vec<_>>(),
221 )
222 .field("update_count", &self.update_count())
223 .finish_non_exhaustive()
224 }
225}
226
227impl PendingOracleRuntime {
228 pub(crate) fn from_registrations<'a>(
229 config: PendingOracleConfig,
230 registrations: impl IntoIterator<Item = &'a FeedRegistration>,
231 adapters: impl IntoIterator<Item = Arc<dyn PendingOracleAdapter>>,
232 ) -> Self {
233 let feed_filter = config.feed_filter().clone();
234 let (sender, _) = broadcast::channel(config.channel_capacity_value());
235 let registrations = registrations
236 .into_iter()
237 .filter(|registration| feed_filter.includes(®istration.id))
238 .cloned()
239 .collect::<Vec<_>>();
240 let mut installed = BTreeMap::<OracleAdapterId, Arc<dyn PendingOracleAdapter>>::new();
241 let chainlink: Arc<dyn PendingOracleAdapter> = Arc::new(ChainlinkPendingAdapter);
242 installed.insert(chainlink.adapter_id(), chainlink);
243 for adapter in adapters {
244 installed.entry(adapter.adapter_id()).or_insert(adapter);
245 }
246 Self {
247 config,
248 feed_ids: Arc::new(Mutex::new(
249 registrations
250 .iter()
251 .map(|registration| registration.id.clone())
252 .collect(),
253 )),
254 registrations: Arc::new(Mutex::new(registrations)),
255 adapters: Arc::new(Mutex::new(installed)),
256 sender,
257 sequence: Arc::new(AtomicU64::new(0)),
258 updates: Arc::new(Mutex::new(BTreeMap::new())),
259 source_health: Arc::new(Mutex::new(BTreeMap::new())),
260 }
261 }
262
263 pub fn interests(&self) -> Vec<PendingOracleInterest> {
265 let registrations = self
266 .registrations
267 .lock()
268 .unwrap_or_else(|error| error.into_inner())
269 .clone();
270 self.adapters
271 .lock()
272 .unwrap_or_else(|error| error.into_inner())
273 .values()
274 .flat_map(|adapter| adapter.interests(®istrations))
275 .collect()
276 }
277
278 pub fn register_adapter<A>(&self, adapter: A) -> bool
282 where
283 A: PendingOracleAdapter,
284 {
285 let id = adapter.adapter_id();
286 let mut adapters = self
287 .adapters
288 .lock()
289 .unwrap_or_else(|error| error.into_inner());
290 if adapters.contains_key(&id) {
291 return false;
292 }
293 adapters.insert(id, Arc::new(adapter));
294 true
295 }
296
297 pub const fn config(&self) -> &PendingOracleConfig {
299 &self.config
300 }
301
302 pub fn feed_ids(&self) -> Vec<FeedId> {
304 self.feed_ids
305 .lock()
306 .unwrap_or_else(|error| error.into_inner())
307 .clone()
308 }
309
310 pub fn subscribe(&self) -> PendingOracleSubscription {
312 PendingOracleSubscription {
313 receiver: self.sender.subscribe(),
314 }
315 }
316
317 pub fn publisher(&self) -> PendingOraclePublisher {
319 PendingOraclePublisher {
320 sender: self.sender.clone(),
321 sequence: Arc::clone(&self.sequence),
322 }
323 }
324
325 pub fn start_source<S>(
327 &self,
328 source: S,
329 ) -> Result<PendingOracleSourceSession, PendingOracleSourceError>
330 where
331 S: PendingOracleCandidateSource,
332 {
333 let descriptor = source.descriptor();
334 if !self.config.source_enabled(&descriptor.source) {
335 return Err(PendingOracleSourceError::SourceDisabled {
336 transport: descriptor.source.clone(),
337 });
338 }
339 if self
340 .source_health
341 .lock()
342 .unwrap_or_else(|error| error.into_inner())
343 .get(&descriptor.id)
344 .is_some_and(|health| health.state != PendingOracleSourceState::Stopped)
345 {
346 return Err(PendingOracleSourceError::DuplicateSource {
347 id: descriptor.id.clone(),
348 });
349 }
350 let handle = tokio::runtime::Handle::try_current()
351 .map_err(|_| PendingOracleSourceError::RuntimeUnavailable)?;
352 let (shutdown, receiver) = tokio::sync::watch::channel(false);
353 let sink = PendingOracleSourceSink::new(self.clone(), descriptor.clone());
354 sink.connecting();
355 let task_sink = sink.clone();
356 let task = handle.spawn(async move {
357 let result = Box::new(source).run(task_sink.clone(), receiver).await;
358 match &result {
359 Ok(()) => task_sink.stopped(),
360 Err(error) => task_sink.failed(error),
361 }
362 result
363 });
364 Ok(PendingOracleSourceSession::new(descriptor, shutdown, task))
365 }
366
367 pub fn source_health(
369 &self,
370 source_id: &PendingOracleSourceId,
371 ) -> Option<PendingOracleSourceHealth> {
372 self.source_health
373 .lock()
374 .unwrap_or_else(|error| error.into_inner())
375 .get(source_id)
376 .cloned()
377 }
378
379 pub(crate) fn insert_source_health(&self, health: PendingOracleSourceHealth) {
380 self.source_health
381 .lock()
382 .unwrap_or_else(|error| error.into_inner())
383 .insert(health.descriptor.id.clone(), health.clone());
384 self.publisher()
385 .publish(PendingOracleEvent::SourceHealthChanged(health));
386 }
387
388 pub(crate) fn mutate_source_health(
389 &self,
390 descriptor: &PendingOracleSourceDescriptor,
391 mutate: impl FnOnce(&mut PendingOracleSourceHealth),
392 ) {
393 let mut health = self
394 .source_health
395 .lock()
396 .unwrap_or_else(|error| error.into_inner());
397 let health =
398 health
399 .entry(descriptor.id.clone())
400 .or_insert_with(|| PendingOracleSourceHealth {
401 descriptor: descriptor.clone(),
402 state: PendingOracleSourceState::Connecting,
403 last_transport_message_at: None,
404 last_candidate_at: None,
405 coverage_gap_count: 0,
406 last_error: None,
407 });
408 mutate(health);
409 }
410
411 pub(crate) fn update_source_health(
412 &self,
413 descriptor: &PendingOracleSourceDescriptor,
414 mutate: impl FnOnce(&mut PendingOracleSourceHealth),
415 ) {
416 let health = {
417 let mut sources = self
418 .source_health
419 .lock()
420 .unwrap_or_else(|error| error.into_inner());
421 let health =
422 sources
423 .entry(descriptor.id.clone())
424 .or_insert_with(|| PendingOracleSourceHealth {
425 descriptor: descriptor.clone(),
426 state: PendingOracleSourceState::Connecting,
427 last_transport_message_at: None,
428 last_candidate_at: None,
429 coverage_gap_count: 0,
430 last_error: None,
431 });
432 mutate(health);
433 health.clone()
434 };
435 self.publisher()
436 .publish(PendingOracleEvent::SourceHealthChanged(health));
437 }
438
439 pub fn observe(&self, update: PendingOracleUpdate) -> PendingOracleObserveOutcome {
441 if update.status != PendingOracleStatus::Pending
442 || update
443 .transmissions
444 .iter()
445 .any(|transmission| transmission.status != PendingOracleTransmissionStatus::Pending)
446 {
447 return PendingOracleObserveOutcome::Conflict;
448 }
449 let feed_ids = self
450 .feed_ids
451 .lock()
452 .unwrap_or_else(|error| error.into_inner());
453 if update.feed_ids.iter().any(|id| !feed_ids.contains(id)) {
454 return PendingOracleObserveOutcome::OutsideScope;
455 }
456 drop(feed_ids);
457 let mut updates = self
458 .updates
459 .lock()
460 .unwrap_or_else(|error| error.into_inner());
461 if let Some(existing) = updates.get_mut(&update.id) {
462 if existing.status != PendingOracleStatus::Pending {
463 return PendingOracleObserveOutcome::Conflict;
464 }
465 if existing.feed_ids != update.feed_ids
466 || existing.evidence != update.evidence
467 || existing.proposed_value != update.proposed_value
468 {
469 return PendingOracleObserveOutcome::Conflict;
470 }
471
472 if update.transmissions.iter().any(|candidate| {
473 existing
474 .transmissions
475 .iter()
476 .find(|tracked| tracked.id == candidate.id)
477 .is_some_and(|tracked| !tracked.same_variant_content(candidate))
478 }) {
479 return PendingOracleObserveOutcome::Conflict;
480 }
481
482 let added = update
483 .transmissions
484 .into_iter()
485 .filter(|candidate| {
486 !existing
487 .transmissions
488 .iter()
489 .any(|tracked| tracked.id == candidate.id)
490 })
491 .collect::<Vec<_>>();
492 existing.transmissions.extend(added.iter().cloned());
493 drop(updates);
494
495 for transmission in &added {
496 self.publisher()
497 .publish(PendingOracleEvent::TransmissionAdded {
498 update_id: update.id,
499 transmission: transmission.clone(),
500 });
501 }
502 return if added.is_empty() {
503 PendingOracleObserveOutcome::Duplicate
504 } else {
505 PendingOracleObserveOutcome::TransmissionAdded { count: added.len() }
506 };
507 }
508 updates.insert(update.id, update.clone());
509 drop(updates);
510 self.publisher()
511 .publish(PendingOracleEvent::Observed(update));
512 PendingOracleObserveOutcome::Observed
513 }
514
515 pub fn observe_candidate(
517 &self,
518 candidate: PendingTransportCandidate,
519 ) -> Result<PendingOracleCandidateReport, PendingOracleAdapterError> {
520 if candidate.chain_id != self.config.chain_id() {
521 return Err(PendingOracleAdapterError::WrongChain {
522 expected: self.config.chain_id(),
523 observed: candidate.chain_id,
524 });
525 }
526 if !self.config.source_enabled(&candidate.source) {
527 return Err(PendingOracleAdapterError::SourceDisabled(
528 candidate.source.clone(),
529 ));
530 }
531 let registrations = self
532 .registrations
533 .lock()
534 .unwrap_or_else(|error| error.into_inner())
535 .clone();
536 let adapters = self
537 .adapters
538 .lock()
539 .unwrap_or_else(|error| error.into_inner())
540 .values()
541 .cloned()
542 .collect::<Vec<_>>();
543 let mut report = PendingOracleCandidateReport::default();
544 for adapter in adapters {
545 if !adapter
546 .interests(®istrations)
547 .iter()
548 .any(|interest| interest.matches(&candidate))
549 {
550 continue;
551 }
552 let adapter_id = adapter.adapter_id();
553 let updates = match adapter.decode(&candidate, ®istrations) {
554 Ok(updates) => updates,
555 Err(error) => {
556 report
557 .failures
558 .push(PendingOracleAdapterFailure { adapter_id, error });
559 continue;
560 }
561 };
562 for update in updates {
563 let update_id = update.id;
564 report.observations.push(PendingOracleAdapterObservation {
565 adapter_id: adapter_id.clone(),
566 update_id,
567 outcome: self.observe(update),
568 });
569 }
570 }
571 Ok(report)
572 }
573
574 pub fn update(&self, id: &PendingOracleUpdateId) -> Option<PendingOracleUpdate> {
576 self.updates
577 .lock()
578 .unwrap_or_else(|error| error.into_inner())
579 .get(id)
580 .cloned()
581 }
582
583 pub fn update_count(&self) -> usize {
585 self.updates
586 .lock()
587 .unwrap_or_else(|error| error.into_inner())
588 .len()
589 }
590
591 pub fn resolve_confirmed_transmission(
597 &self,
598 resolution: PendingOracleResolution,
599 ) -> PendingOracleResolveOutcome {
600 let mut updates = self
601 .updates
602 .lock()
603 .unwrap_or_else(|error| error.into_inner());
604 let Some(update) = updates.get_mut(&resolution.update_id) else {
605 return PendingOracleResolveOutcome::UnknownUpdate;
606 };
607 let Some(transmission) = update
608 .transmissions
609 .iter_mut()
610 .find(|transmission| transmission.id == resolution.transmission_id)
611 else {
612 return PendingOracleResolveOutcome::UnknownTransmission;
613 };
614 if transmission
615 .ordering
616 .transaction_hash()
617 .is_some_and(|hash| hash != resolution.transaction_hash)
618 {
619 return PendingOracleResolveOutcome::Conflict;
620 }
621 let target = match resolution.kind {
622 PendingOracleResolutionKind::Landed => PendingOracleTransmissionStatus::Landed,
623 PendingOracleResolutionKind::Reverted => PendingOracleTransmissionStatus::Reverted,
624 };
625 if transmission.status == target {
626 return PendingOracleResolveOutcome::Duplicate;
627 }
628 if transmission.status != PendingOracleTransmissionStatus::Pending {
629 return PendingOracleResolveOutcome::Conflict;
630 }
631 transmission.status = target;
632 update.status =
633 if resolution.kind == PendingOracleResolutionKind::Landed {
634 PendingOracleStatus::Landed
635 } else if update.transmissions.iter().all(|transmission| {
636 transmission.status == PendingOracleTransmissionStatus::Reverted
637 }) {
638 PendingOracleStatus::Reverted
639 } else {
640 PendingOracleStatus::Pending
641 };
642 drop(updates);
643 self.publisher()
644 .publish(PendingOracleEvent::Resolved(resolution));
645 PendingOracleResolveOutcome::Resolved
646 }
647
648 pub fn observe_confirmed_log(
654 &self,
655 log: &alloy_rpc_types_eth::Log,
656 ) -> Vec<PendingOracleResolution> {
657 if log.removed {
658 return Vec::new();
659 }
660 let (Some(transaction_hash), Some(block_number)) = (log.transaction_hash, log.block_number)
661 else {
662 return Vec::new();
663 };
664 let updates = self
665 .updates
666 .lock()
667 .unwrap_or_else(|error| error.into_inner())
668 .values()
669 .filter(|update| update.status == PendingOracleStatus::Pending)
670 .cloned()
671 .collect::<Vec<_>>();
672 let adapters = self
673 .adapters
674 .lock()
675 .unwrap_or_else(|error| error.into_inner())
676 .values()
677 .cloned()
678 .collect::<Vec<_>>();
679 let mut resolutions = Vec::new();
680 for update in updates {
681 if !adapters.iter().any(|adapter| {
682 adapter_owns_update(&adapter.adapter_id(), &update)
683 && adapter.confirms(&update, log)
684 }) {
685 continue;
686 }
687 for transmission in update.transmissions.iter().filter(|transmission| {
688 transmission.status == PendingOracleTransmissionStatus::Pending
689 && transmission.ordering.transaction_hash() == Some(transaction_hash)
690 }) {
691 let resolution = PendingOracleResolution::new(
692 update.id,
693 transmission.id,
694 transaction_hash,
695 block_number,
696 PendingOracleResolutionKind::Landed,
697 );
698 if self.resolve_confirmed_transmission(resolution.clone())
699 == PendingOracleResolveOutcome::Resolved
700 {
701 resolutions.push(resolution);
702 }
703 }
704 }
705 resolutions
706 }
707
708 pub fn observe_chainlink_candidate(
710 &self,
711 candidate: PendingTransportCandidate,
712 ) -> Result<PendingOracleCandidateOutcome, PendingOracleAdapterError> {
713 match self.observe_candidate(candidate) {
714 Ok(report) => {
715 if let Some(failure) = report
716 .failures
717 .into_iter()
718 .find(|failure| failure.adapter_id.as_str() == ChainlinkPendingAdapter::ID)
719 {
720 return Err(failure.error);
721 }
722 Ok(report
723 .observations
724 .into_iter()
725 .find(|observation| {
726 observation.adapter_id.as_str() == ChainlinkPendingAdapter::ID
727 })
728 .map_or(PendingOracleCandidateOutcome::Ignored, |observation| {
729 PendingOracleCandidateOutcome::Tracked(observation.outcome)
730 }))
731 }
732 Err(PendingOracleAdapterError::WrongChain { expected, observed }) => {
733 Ok(PendingOracleCandidateOutcome::WrongChain { expected, observed })
734 }
735 Err(PendingOracleAdapterError::SourceDisabled(source)) => {
736 Ok(PendingOracleCandidateOutcome::SourceDisabled(source))
737 }
738 Err(error) => Err(error),
739 }
740 }
741
742 pub(crate) fn refresh<'a>(
743 &mut self,
744 registrations: impl IntoIterator<Item = &'a FeedRegistration>,
745 ) {
746 let registrations = registrations
747 .into_iter()
748 .filter(|registration| self.config.feed_filter().includes(®istration.id))
749 .cloned()
750 .collect::<Vec<_>>();
751 *self
752 .feed_ids
753 .lock()
754 .unwrap_or_else(|error| error.into_inner()) = registrations
755 .iter()
756 .map(|registration| registration.id.clone())
757 .collect();
758 *self
759 .registrations
760 .lock()
761 .unwrap_or_else(|error| error.into_inner()) = registrations.clone();
762
763 let in_scope = self
764 .feed_ids
765 .lock()
766 .unwrap_or_else(|error| error.into_inner())
767 .iter()
768 .cloned()
769 .collect::<BTreeSet<_>>();
770 let mut expired = Vec::new();
771 let mut updates = self
772 .updates
773 .lock()
774 .unwrap_or_else(|error| error.into_inner());
775 updates.retain(|id, update| {
776 update.feed_ids.retain(|feed_id| {
777 if !in_scope.contains(feed_id) {
778 return false;
779 }
780 match &update.evidence {
781 PendingOracleEvidence::ChainlinkReport(report) => {
782 registrations.iter().any(|registration| {
783 ®istration.id == feed_id
784 && registration.current_aggregator == Some(report.aggregator)
785 })
786 }
787 PendingOracleEvidence::Adapter { .. } => true,
788 }
789 });
790 if update.feed_ids.is_empty() {
791 expired.push(*id);
792 false
793 } else {
794 true
795 }
796 });
797 drop(updates);
798 for id in expired {
799 self.publisher().publish(PendingOracleEvent::Expired(id));
800 }
801 }
802}
803
804#[derive(Clone, Copy, Debug, PartialEq, Eq)]
806pub enum PendingOracleObserveOutcome {
807 Observed,
809 TransmissionAdded {
811 count: usize,
813 },
814 Duplicate,
816 Conflict,
818 OutsideScope,
820}
821
822#[non_exhaustive]
824#[derive(Clone, Debug, PartialEq, Eq)]
825pub enum PendingOracleCandidateOutcome {
826 Ignored,
828 SourceDisabled(PendingOracleSource),
830 WrongChain {
832 expected: u64,
834 observed: u64,
836 },
837 AggregatorOutsideScope(Address),
839 Tracked(PendingOracleObserveOutcome),
841}
842
843#[non_exhaustive]
845#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
846pub enum PendingOracleStatus {
847 #[default]
849 Pending,
850 Landed,
852 Reverted,
854}
855
856#[non_exhaustive]
858#[derive(Clone, Copy, Debug, PartialEq, Eq)]
859pub enum PendingOracleResolutionKind {
860 Landed,
862 Reverted,
864}
865
866#[derive(Clone, Debug, PartialEq, Eq)]
868pub struct PendingOracleResolution {
869 pub update_id: PendingOracleUpdateId,
871 pub transmission_id: PendingOracleTransmissionId,
873 pub transaction_hash: B256,
875 pub block_number: u64,
877 pub kind: PendingOracleResolutionKind,
879}
880
881impl PendingOracleResolution {
882 pub const fn new(
884 update_id: PendingOracleUpdateId,
885 transmission_id: PendingOracleTransmissionId,
886 transaction_hash: B256,
887 block_number: u64,
888 kind: PendingOracleResolutionKind,
889 ) -> Self {
890 Self {
891 update_id,
892 transmission_id,
893 transaction_hash,
894 block_number,
895 kind,
896 }
897 }
898}
899
900#[non_exhaustive]
902#[derive(Clone, Copy, Debug, PartialEq, Eq)]
903pub enum PendingOracleResolveOutcome {
904 Resolved,
906 Duplicate,
908 UnknownUpdate,
910 UnknownTransmission,
912 Conflict,
914}
915
916#[derive(Clone, Debug, PartialEq, Eq)]
918pub struct PendingTransportCandidate {
919 pub chain_id: u64,
921 pub id: PendingOracleTransmissionId,
923 pub source: PendingOracleSource,
925 pub source_id: PendingOracleSourceId,
927 pub to: Address,
929 pub calldata: Bytes,
931 pub ordering: PendingOracleOrderingHandle,
933 pub observed_at_head: Option<u64>,
935}
936
937impl PendingTransportCandidate {
938 #[allow(clippy::too_many_arguments)]
940 pub fn new(
941 chain_id: u64,
942 id: PendingOracleTransmissionId,
943 source: PendingOracleSource,
944 source_id: PendingOracleSourceId,
945 to: Address,
946 calldata: Bytes,
947 ordering: PendingOracleOrderingHandle,
948 observed_at_head: Option<u64>,
949 ) -> Self {
950 Self {
951 chain_id,
952 id,
953 source,
954 source_id,
955 to,
956 calldata,
957 ordering,
958 observed_at_head,
959 }
960 }
961}
962
963#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
965pub struct PendingOracleUpdateId(B256);
966
967impl PendingOracleUpdateId {
968 pub const fn from_hash(hash: B256) -> Self {
970 Self(hash)
971 }
972
973 pub const fn as_hash(&self) -> B256 {
975 self.0
976 }
977}
978
979#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
981pub struct PendingOracleTransmissionId(B256);
982
983impl PendingOracleTransmissionId {
984 pub const fn from_hash(hash: B256) -> Self {
986 Self(hash)
987 }
988
989 pub const fn as_hash(&self) -> B256 {
991 self.0
992 }
993}
994
995#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
997pub struct PendingOracleSourceId(String);
998
999impl PendingOracleSourceId {
1000 pub fn new(id: impl Into<String>) -> Self {
1002 Self(id.into())
1003 }
1004
1005 pub fn as_str(&self) -> &str {
1007 &self.0
1008 }
1009}
1010
1011impl std::fmt::Display for PendingOracleSourceId {
1012 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1013 self.0.fmt(formatter)
1014 }
1015}
1016
1017#[non_exhaustive]
1019#[derive(Clone, Debug, PartialEq, Eq)]
1020pub enum PendingOracleRoute {
1021 ChainlinkDirect {
1023 aggregator: Address,
1025 secondary: bool,
1027 },
1028 ChainlinkForwarded {
1030 forwarder: Address,
1032 aggregator: Address,
1034 secondary: bool,
1036 },
1037 Adapter {
1039 adapter_id: OracleAdapterId,
1041 kind: String,
1043 },
1044}
1045
1046impl PendingOracleRoute {
1047 pub fn adapter(adapter_id: OracleAdapterId, kind: impl Into<String>) -> Self {
1049 Self::Adapter {
1050 adapter_id,
1051 kind: kind.into(),
1052 }
1053 }
1054}
1055
1056#[non_exhaustive]
1058#[derive(Clone, Debug, Default, PartialEq, Eq)]
1059pub enum PendingOracleOrderingHandle {
1060 MevShare {
1062 hash: B256,
1064 },
1065 RawEthereumTransaction {
1067 tx_hash: B256,
1069 signed_envelope: Bytes,
1071 },
1072 TransactionHashOnly {
1074 tx_hash: B256,
1076 },
1077 SequencerPreconfirmation {
1079 tx_hash: B256,
1081 block_number: u64,
1083 payload_id: B256,
1085 sequence_index: u64,
1087 },
1088 #[default]
1090 None,
1091}
1092
1093impl PendingOracleOrderingHandle {
1094 pub const fn is_referenceable(&self) -> bool {
1096 !matches!(self, Self::None)
1097 }
1098
1099 pub const fn transaction_hash(&self) -> Option<B256> {
1101 match self {
1102 Self::MevShare { hash } => Some(*hash),
1103 Self::RawEthereumTransaction { tx_hash, .. }
1104 | Self::TransactionHashOnly { tx_hash }
1105 | Self::SequencerPreconfirmation { tx_hash, .. } => Some(*tx_hash),
1106 Self::None => None,
1107 }
1108 }
1109}
1110
1111#[non_exhaustive]
1113#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1114pub enum PendingOracleSimulationFidelity {
1115 InsufficientMaterial,
1117 AnswerOnlyProjection,
1119 VerifiedStateProjection,
1121 ExactSignedTransaction,
1123}
1124
1125#[non_exhaustive]
1127#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1128pub enum PendingOracleSimulationMaterial {
1129 ExactSignedTransaction,
1131 VerifiedStateProjection,
1133 AnswerOnlyProjection,
1135 InsufficientMaterial,
1137}
1138
1139impl PendingOracleSimulationMaterial {
1140 pub const fn fidelity(self) -> PendingOracleSimulationFidelity {
1142 match self {
1143 Self::ExactSignedTransaction => PendingOracleSimulationFidelity::ExactSignedTransaction,
1144 Self::VerifiedStateProjection => {
1145 PendingOracleSimulationFidelity::VerifiedStateProjection
1146 }
1147 Self::AnswerOnlyProjection => PendingOracleSimulationFidelity::AnswerOnlyProjection,
1148 Self::InsufficientMaterial => PendingOracleSimulationFidelity::InsufficientMaterial,
1149 }
1150 }
1151}
1152
1153#[non_exhaustive]
1155#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1156pub enum PendingOracleTransmissionStatus {
1157 #[default]
1159 Pending,
1160 Landed,
1162 Reverted,
1164 Dropped,
1166 Superseded,
1168}
1169
1170#[derive(Clone, Debug, PartialEq, Eq)]
1172pub struct PendingOracleTransmission {
1173 pub id: PendingOracleTransmissionId,
1175 pub source: PendingOracleSourceId,
1177 pub route: PendingOracleRoute,
1179 pub ordering: PendingOracleOrderingHandle,
1181 pub simulation: PendingOracleSimulationMaterial,
1183 pub calldata_hash: B256,
1185 pub observed_at_head: Option<u64>,
1187 pub first_seen_at: SystemTime,
1189 pub status: PendingOracleTransmissionStatus,
1191}
1192
1193impl PendingOracleTransmission {
1194 pub fn new(
1196 id: PendingOracleTransmissionId,
1197 source: PendingOracleSourceId,
1198 route: PendingOracleRoute,
1199 ordering: PendingOracleOrderingHandle,
1200 simulation: PendingOracleSimulationMaterial,
1201 calldata_hash: B256,
1202 observed_at_head: Option<u64>,
1203 ) -> Self {
1204 Self {
1205 id,
1206 source,
1207 route,
1208 ordering,
1209 simulation,
1210 calldata_hash,
1211 observed_at_head,
1212 first_seen_at: SystemTime::now(),
1213 status: PendingOracleTransmissionStatus::Pending,
1214 }
1215 }
1216
1217 pub const fn is_referenceable(&self) -> bool {
1219 self.ordering.is_referenceable()
1220 }
1221
1222 pub const fn actionability_rank(&self) -> u8 {
1224 match (&self.ordering, self.simulation.fidelity()) {
1225 (
1226 PendingOracleOrderingHandle::RawEthereumTransaction { .. },
1227 PendingOracleSimulationFidelity::ExactSignedTransaction,
1228 ) => 4,
1229 (PendingOracleOrderingHandle::MevShare { .. }, _) => 3,
1230 (PendingOracleOrderingHandle::RawEthereumTransaction { .. }, _) => 2,
1231 (PendingOracleOrderingHandle::TransactionHashOnly { .. }, _) => 1,
1232 (PendingOracleOrderingHandle::SequencerPreconfirmation { .. }, _) => 1,
1233 (PendingOracleOrderingHandle::None, _) => 0,
1234 }
1235 }
1236
1237 fn same_variant_content(&self, other: &Self) -> bool {
1238 self.id == other.id
1239 && self.source == other.source
1240 && self.route == other.route
1241 && self.ordering == other.ordering
1242 && self.simulation == other.simulation
1243 && self.calldata_hash == other.calldata_hash
1244 }
1245}
1246
1247#[non_exhaustive]
1249#[derive(Clone, Debug, PartialEq, Eq)]
1250pub enum PendingOracleEvidence {
1251 ChainlinkReport(ChainlinkPendingReport),
1253 Adapter {
1255 adapter_id: OracleAdapterId,
1257 kind: String,
1259 digest: B256,
1261 },
1262}
1263
1264impl PendingOracleEvidence {
1265 pub fn adapter(adapter_id: OracleAdapterId, kind: impl Into<String>, digest: B256) -> Self {
1267 Self::Adapter {
1268 adapter_id,
1269 kind: kind.into(),
1270 digest,
1271 }
1272 }
1273}
1274
1275#[derive(Clone, Debug, PartialEq, Eq)]
1277pub struct PendingOracleUpdate {
1278 pub id: PendingOracleUpdateId,
1280 pub feed_ids: Vec<FeedId>,
1282 pub evidence: PendingOracleEvidence,
1284 pub proposed_value: Option<PendingOracleValue>,
1286 pub transmissions: Vec<PendingOracleTransmission>,
1288 pub status: PendingOracleStatus,
1290}
1291
1292impl PendingOracleUpdate {
1293 pub fn new(
1295 id: PendingOracleUpdateId,
1296 feed_ids: impl IntoIterator<Item = FeedId>,
1297 evidence: PendingOracleEvidence,
1298 ) -> Self {
1299 Self {
1300 id,
1301 feed_ids: feed_ids.into_iter().collect(),
1302 evidence,
1303 proposed_value: None,
1304 transmissions: Vec::new(),
1305 status: PendingOracleStatus::Pending,
1306 }
1307 }
1308
1309 pub fn with_proposed_value(mut self, proposed_value: PendingOracleValue) -> Self {
1311 self.proposed_value = Some(proposed_value);
1312 self
1313 }
1314
1315 pub fn with_transmission(mut self, transmission: PendingOracleTransmission) -> Self {
1317 self.transmissions.push(transmission);
1318 self
1319 }
1320}
1321
1322#[derive(Clone, Debug, PartialEq, Eq)]
1324pub struct PendingOracleValue {
1325 pub raw_answer: I256,
1327 pub observed_at: u64,
1329}
1330
1331#[non_exhaustive]
1333#[derive(Clone, Debug, PartialEq, Eq)]
1334pub enum PendingOracleEvent {
1335 Observed(PendingOracleUpdate),
1337 TransmissionAdded {
1339 update_id: PendingOracleUpdateId,
1341 transmission: PendingOracleTransmission,
1343 },
1344 Expired(PendingOracleUpdateId),
1346 Resolved(PendingOracleResolution),
1348 SourceHealthChanged(PendingOracleSourceHealth),
1350 CoverageGap(PendingOracleCoverageGap),
1352}
1353
1354#[derive(Clone, Debug, PartialEq, Eq)]
1356pub struct PendingOracleStreamEvent {
1357 pub sequence: u64,
1359 pub event: PendingOracleEvent,
1361}
1362
1363#[derive(Clone, Debug)]
1365pub struct PendingOraclePublisher {
1366 sender: broadcast::Sender<PendingOracleStreamEvent>,
1367 sequence: Arc<AtomicU64>,
1368}
1369
1370impl PendingOraclePublisher {
1371 pub fn publish(&self, event: PendingOracleEvent) -> usize {
1373 let sequence = self.sequence.fetch_add(1, Ordering::Relaxed) + 1;
1374 self.sender
1375 .send(PendingOracleStreamEvent { sequence, event })
1376 .unwrap_or(0)
1377 }
1378}
1379
1380#[derive(Debug)]
1382pub struct PendingOracleSubscription {
1383 receiver: broadcast::Receiver<PendingOracleStreamEvent>,
1384}
1385
1386impl PendingOracleSubscription {
1387 pub async fn recv(&mut self) -> Result<PendingOracleStreamEvent, PendingOracleRecvError> {
1389 self.receiver.recv().await.map_err(Into::into)
1390 }
1391}
1392
1393#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
1395pub enum PendingOracleRecvError {
1396 #[error("pending oracle channel closed")]
1398 Closed,
1399 #[error("pending oracle receiver lagged by {missed} events")]
1401 Lagged {
1402 missed: u64,
1404 },
1405}
1406
1407impl From<broadcast::error::RecvError> for PendingOracleRecvError {
1408 fn from(error: broadcast::error::RecvError) -> Self {
1409 match error {
1410 broadcast::error::RecvError::Closed => Self::Closed,
1411 broadcast::error::RecvError::Lagged(missed) => Self::Lagged { missed },
1412 }
1413 }
1414}