From 54a2122d505a4860f4699870d0ab4d424ca2007f Mon Sep 17 00:00:00 2001 From: m0ar Date: Fri, 9 Jan 2026 16:13:17 +0100 Subject: [PATCH] fix: revert RPC autodiscovery of proofs to self-heal unstored self-anchors --- event-svc/src/event/service.rs | 38 ++++++++++++++++++-------- event-svc/src/event/validator/event.rs | 4 +++ event-svc/src/tests/self_anchor.rs | 31 ++++++++------------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/event-svc/src/event/service.rs b/event-svc/src/event/service.rs index 1fcf1746..29b74cfc 100644 --- a/event-svc/src/event/service.rs +++ b/event-svc/src/event/service.rs @@ -544,26 +544,42 @@ impl EventService { } /// Get the chain proof for a given event from the database. - /// All proofs should have been validated and stored during the event validation phase (v0.55.0+). + /// All proofs should have been validated and stored during the event validation phase (v0.55.0+), + /// but in case it can't be found we try to dynamically discover it through the RPC provider pub(crate) async fn discover_chain_proof( &self, event: &ceramic_event::unvalidated::TimeEvent, ) -> std::result::Result { let tx_hash = event.proof().tx_hash(); let tx_hash = tx_hash_try_from_cid(tx_hash).unwrap().to_string(); - self.event_access + if let Some(proof) = self + .event_access .get_chain_proof(event.proof().chain_id(), &tx_hash) .await .map_err(|e| crate::eth_rpc::Error::Application(e.into()))? - .ok_or_else(|| { - // Note: Using InvalidProof here rather than TxNotFound because: - // - TxNotFound is for "transaction not found on blockchain" (RPC-level) - // - This is "proof not in local database" (local storage issue) - crate::eth_rpc::Error::InvalidProof(format!( - "Chain proof for tx {} not found in database.", - tx_hash - )) - }) + { + return Ok(proof); + } + + warn!( + "Chain proof for tx {} not found in database, validating and storing it now.", + tx_hash + ); + + // Try using the RPC provider and store the proof + let proof = self + .event_validator + .time_event_validator() + .validate_chain_inclusion(event) + .await?; + + let proof = ChainProof::from(proof); + self.event_access + .persist_chain_inclusion_proofs(std::slice::from_ref(&proof)) + .await + .map_err(|e| crate::eth_rpc::Error::Application(e.into()))?; + + Ok(proof) } } diff --git a/event-svc/src/event/validator/event.rs b/event-svc/src/event/validator/event.rs index 7eeb8ffe..18f09fcd 100644 --- a/event-svc/src/event/validator/event.rs +++ b/event-svc/src/event/validator/event.rs @@ -275,6 +275,10 @@ impl EventValidator { } } } + + pub fn time_event_validator(&self) -> &TimeEventValidator { + &self.time_event_validator + } } #[cfg(test)] diff --git a/event-svc/src/tests/self_anchor.rs b/event-svc/src/tests/self_anchor.rs index 3541cba8..d45538e4 100644 --- a/event-svc/src/tests/self_anchor.rs +++ b/event-svc/src/tests/self_anchor.rs @@ -614,21 +614,23 @@ async fn test_discover_chain_proof_direct() { assert_eq!(proof.timestamp, timestamp as i64); } -/// Test that discover_chain_proof returns an error for non-existent proofs. +/// Test that discover_chain_proof falls back to RPC when proof not in DB, +/// and returns NoChainProvider error when no RPC provider is configured. #[tokio::test] -async fn test_discover_chain_proof_not_found() { +async fn test_discover_chain_proof_fallback_no_provider() { use ceramic_event::unvalidated; use crate::{EventService, UndeliveredEventReview}; let pool = SqlitePool::connect_in_memory().await.unwrap(); + // Service created with no RPC providers let service = EventService::try_new(pool.clone(), UndeliveredEventReview::Skip, false, vec![]) .await .unwrap(); // Create a time event for a non-existent chain proof - let tx_hash = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - let tx_hash_cid = tx_hash_to_cid(tx_hash); + let tx_hash_cid = + tx_hash_to_cid("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); let init_cid = super::deterministic_cid(b"discover not found test init"); let prev_cid = super::deterministic_cid(b"discover not found test prev"); @@ -643,29 +645,20 @@ async fn test_discover_chain_proof_not_found() { .build() .expect("test time event should build"); - // Call discover_chain_proof - should fail because no proof was stored + // Call discover_chain_proof - should fail because proof not in DB and no RPC provider let result = service.discover_chain_proof(&time_event).await; assert!( result.is_err(), - "discover_chain_proof should fail for non-existent proof" + "discover_chain_proof should fail when no RPC provider configured" ); - // Verify it's an InvalidProof error (discover_chain_proof returns InvalidProof when not found) + // Verify it's a NoChainProvider error (RPC fallback fails without provider) match result.unwrap_err() { - crate::eth_rpc::Error::InvalidProof(msg) => { - assert!( - msg.contains("not found in database"), - "Error message should indicate proof not found, got: {}", - msg - ); - assert!( - msg.contains(tx_hash), - "Error message should contain tx_hash, got: {}", - msg - ); + crate::eth_rpc::Error::NoChainProvider(chain_id) => { + assert_eq!(chain_id.to_string(), "eip155:100"); } - other => panic!("Expected InvalidProof error, got: {:?}", other), + other => panic!("Expected NoChainProvider error, got: {:?}", other), } }