From 676bf0cf8b4b7390f12a9fdbfc8730117566456c Mon Sep 17 00:00:00 2001 From: Filipe Azevedo Date: Fri, 2 Dec 2022 12:36:59 +0000 Subject: [PATCH] fail when ens rainbow not present --- graph/src/components/store/traits.rs | 2 ++ runtime/wasm/src/host_exports.rs | 4 +++ runtime/wasm/src/module/mod.rs | 6 ++++ store/postgres/src/primary.rs | 12 +++++++ store/postgres/src/subgraph_store.rs | 52 +++++++++++++++++++++++++--- 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/graph/src/components/store/traits.rs b/graph/src/components/store/traits.rs index 4a0ccecb766..24d3918907e 100644 --- a/graph/src/components/store/traits.rs +++ b/graph/src/components/store/traits.rs @@ -32,6 +32,8 @@ pub trait EnsLookup: Send + Sync + 'static { /// Find the reverse of keccak256 for `hash` through looking it up in the /// rainbow table. fn find_name(&self, hash: &str) -> Result, StoreError>; + // Check if the rainbow table is filled. + fn is_table_empty(&self) -> Result; } /// An entry point for all operations that require access to the node's storage diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index a412260fd32..d6eaf0d0f24 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -676,6 +676,10 @@ impl HostExports { Ok(self.ens_lookup.find_name(hash)?) } + pub(crate) fn is_ens_data_empty(&self) -> Result { + Ok(self.ens_lookup.is_table_empty()?) + } + pub(crate) fn log_log( &self, logger: &Logger, diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index b4306e72b08..d7d473583a3 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -1689,6 +1689,12 @@ impl WasmInstanceContext { let hash: String = asc_get(self, hash_ptr, gas)?; let name = self.ctx.host_exports.ens_name_by_hash(&*hash)?; + if name.is_none() && self.ctx.host_exports.is_ens_data_empty()? { + return Err(anyhow!( + "Missing ENS data: see https://github.com/graphprotocol/ens-rainbow" + ) + .into()); + } // map `None` to `null`, and `Some(s)` to a runtime string name.map(|name| asc_new(self, &*name, gas).map_err(Into::into)) diff --git a/store/postgres/src/primary.rs b/store/postgres/src/primary.rs index 80e45db241a..c4a01086667 100644 --- a/store/postgres/src/primary.rs +++ b/store/postgres/src/primary.rs @@ -1495,6 +1495,18 @@ impl<'a> Connection<'a> { .map_err(|e| anyhow!("error looking up ens_name for hash {}: {}", hash, e).into()) } + pub fn is_ens_table_empty(&self) -> Result { + use ens_names as dsl; + + dsl::table + .select(dsl::name) + .limit(1) + .get_result::(self.conn.as_ref()) + .optional() + .map(|r| r.is_none()) + .map_err(|e| anyhow!("error if ens table is empty: {}", e).into()) + } + pub fn record_active_copy(&self, src: &Site, dst: &Site) -> Result<(), StoreError> { use active_copies as cp; diff --git a/store/postgres/src/subgraph_store.rs b/store/postgres/src/subgraph_store.rs index 99cdab1fbae..04ef489658d 100644 --- a/store/postgres/src/subgraph_store.rs +++ b/store/postgres/src/subgraph_store.rs @@ -6,7 +6,7 @@ use diesel::{ }; use std::{ collections::{BTreeMap, HashMap}, - sync::{Arc, Mutex}, + sync::{atomic::AtomicU8, Arc, Mutex}, }; use std::{fmt, io::Write}; use std::{iter::FromIterator, time::Duration}; @@ -1147,8 +1147,35 @@ impl SubgraphStoreInner { } } +const STATE_ENS_NOT_CHECKED: u8 = 0; +const STATE_ENS_EMPTY: u8 = 1; +const STATE_ENS_NOT_EMPTY: u8 = 2; + +/// EnsLookup reads from a rainbow table store in postgres that needs to be manually +/// loaded. To avoid unnecessary database roundtrips, the empty table check is lazy +/// and will not be retried. Once the table is checked, any subsequent calls will +/// just used the stored result. struct EnsLookup { primary: ConnectionPool, + // In order to keep the struct lock free, we'll use u8 for the status: + // 0 - Not Checked + // 1 - Checked - empty + // 2 - Checked - non empty + state: AtomicU8, +} + +impl EnsLookup { + pub fn new(pool: ConnectionPool) -> Self { + Self { + primary: pool, + state: AtomicU8::new(STATE_ENS_NOT_CHECKED), + } + } + + fn is_table_empty(pool: &ConnectionPool) -> Result { + let conn = pool.get()?; + primary::Connection::new(conn).is_ens_table_empty() + } } impl EnsLookupTrait for EnsLookup { @@ -1156,14 +1183,31 @@ impl EnsLookupTrait for EnsLookup { let conn = self.primary.get()?; primary::Connection::new(conn).find_ens_name(hash) } + + fn is_table_empty(&self) -> Result { + match self.state.load(std::sync::atomic::Ordering::SeqCst) { + STATE_ENS_NOT_CHECKED => {} + STATE_ENS_EMPTY => return Ok(true), + STATE_ENS_NOT_EMPTY => return Ok(false), + _ => unreachable!("unsupported state"), + } + + let is_empty = Self::is_table_empty(&self.primary)?; + let new_state = match is_empty { + true => STATE_ENS_EMPTY, + false => STATE_ENS_NOT_EMPTY, + }; + self.state + .store(new_state, std::sync::atomic::Ordering::SeqCst); + + Ok(is_empty) + } } #[async_trait::async_trait] impl SubgraphStoreTrait for SubgraphStore { fn ens_lookup(&self) -> Arc { - Arc::new(EnsLookup { - primary: self.mirror.primary().clone(), - }) + Arc::new(EnsLookup::new(self.mirror.primary().clone())) } // FIXME: This method should not get a node_id