diff --git a/docs/graphman.md b/docs/graphman.md index 12655d32ad0..0964efc6051 100644 --- a/docs/graphman.md +++ b/docs/graphman.md @@ -7,6 +7,7 @@ - [Unused Remove](#unused-remove) - [Drop](#drop) - [Chain Check Blocks](#check-blocks) +- [Chain Call Cache Remove](#chain-call-cache-remove) # ⌘ Info @@ -362,3 +363,54 @@ Inspect a block range, deleting any duplicated blocks: Inspect all blocks after block `13000000`: graphman --config config.toml chain check-blocks mainnet by-range --from 13000000 + + +# ⌘ Chain Call Cache Remove + +### SYNOPSIS + +Remove the call cache of the specified chain. + +If block numbers are not mentioned in `--from` and `--to`, then all the call cache will be removed. + +USAGE: + graphman chain call-cache remove [OPTIONS] + +OPTIONS: + -f, --from + Starting block number + + -h, --help + Print help information + + -t, --to + Ending block number + +### DESCRIPTION + +Remove the call cache of a specified chain. + +### OPTIONS + +The `from` and `to` options are used to decide the block range of the call cache that needs to be removed. + +#### `from` + +The `from` option is used to specify the starting block number of the block range. In the absence of `from` option, +the first block number will be used as the starting block number. + +#### `to` + +The `to` option is used to specify the ending block number of the block range. In the absence of `to` option, +the last block number will be used as the ending block number. + +### EXAMPLES + +Remove the call cache for all blocks numbered from 10 to 20: + + graphman --config config.toml chain call-cache ethereum remove --from 10 --to 20 + +Remove all the call cache of the specified chain: + + graphman --config config.toml chain call-cache ethereum remove + diff --git a/graph/src/components/store/traits.rs b/graph/src/components/store/traits.rs index a3f75a3aa51..409b91d7492 100644 --- a/graph/src/components/store/traits.rs +++ b/graph/src/components/store/traits.rs @@ -412,6 +412,9 @@ pub trait ChainStore: Send + Sync + 'static { &self, block_ptr: &H256, ) -> Result, StoreError>; + + /// Clears call cache of the chain for the given `from` and `to` block number. + async fn clear_call_cache(&self, from: Option, to: Option) -> Result<(), Error>; } pub trait EthereumCallCache: Send + Sync + 'static { diff --git a/node/src/bin/manager.rs b/node/src/bin/manager.rs index 637e51e85e4..180f0345c14 100644 --- a/node/src/bin/manager.rs +++ b/node/src/bin/manager.rs @@ -427,6 +427,31 @@ pub enum ChainCommand { #[clap(long, short)] force: bool, }, + + /// Execute operations on call cache. + CallCache { + #[clap(subcommand)] + method: CallCacheCommand, + /// Chain name (must be an existing chain, see 'chain list') + #[clap(empty_values = false)] + chain_name: String, + }, +} + +#[derive(Clone, Debug, Subcommand)] +pub enum CallCacheCommand { + /// Remove the call cache of the specified chain. + /// + /// If block numbers are not mentioned in `--from` and `--to`, then all the call cache will be + /// removed. + Remove { + /// Starting block number + #[clap(long, short)] + from: Option, + /// Ending block number + #[clap(long, short)] + to: Option, + }, } #[derive(Clone, Debug, Subcommand)] @@ -1088,6 +1113,12 @@ async fn main() -> anyhow::Result<()> { let chain_store = ctx.chain_store(&chain_name)?; truncate(chain_store, force) } + CallCache { method, chain_name } => match method { + CallCacheCommand::Remove { from, to } => { + let chain_store = ctx.chain_store(&chain_name)?; + commands::chain::clear_call_cache(chain_store, from, to).await + } + }, } } Stats(cmd) => { diff --git a/node/src/manager/commands/chain.rs b/node/src/manager/commands/chain.rs index 764ae06e495..dfa0ab15a6b 100644 --- a/node/src/manager/commands/chain.rs +++ b/node/src/manager/commands/chain.rs @@ -11,6 +11,7 @@ use graph::{ components::store::BlockStore as _, prelude::anyhow::Error, prelude::serde_json as json, }; use graph_store_postgres::BlockStore; +use graph_store_postgres::ChainStore; use graph_store_postgres::{ command_support::catalog::block_store, connection_pool::ConnectionPool, }; @@ -49,6 +50,16 @@ pub async fn list(primary: ConnectionPool, store: Arc) -> Result<(), Ok(()) } +pub async fn clear_call_cache( + chain_store: Arc, + from: Option, + to: Option, +) -> Result<(), Error> { + chain_store.clear_call_cache(from, to).await?; + println!("The call cache has cleared"); + Ok(()) +} + pub async fn info( primary: ConnectionPool, store: Arc, diff --git a/store/postgres/src/chain_store.rs b/store/postgres/src/chain_store.rs index 74a9101bed8..cb08c70d9bc 100644 --- a/store/postgres/src/chain_store.rs +++ b/store/postgres/src/chain_store.rs @@ -77,6 +77,8 @@ mod data { pub(crate) const ETHEREUM_BLOCKS_TABLE_NAME: &'static str = "public.ethereum_blocks"; + pub(crate) const ETHEREUM_CALL_CACHE_TABLE_NAME: &'static str = "public.eth_call_cache"; + mod public { pub(super) use super::super::public::ethereum_networks; @@ -404,6 +406,15 @@ mod data { Ok(()) } + fn truncate_call_cache(&self, conn: &PgConnection) -> Result<(), StoreError> { + let table_name = match &self { + Storage::Shared => ETHEREUM_CALL_CACHE_TABLE_NAME, + Storage::Private(Schema { call_cache, .. }) => &call_cache.qname, + }; + conn.batch_execute(&format!("truncate table {} restart identity", table_name))?; + Ok(()) + } + /// Insert a block. If the table already contains a block with the /// same hash, then overwrite that block since it may be adding /// transaction receipts. If `overwrite` is `true`, overwrite a @@ -1028,6 +1039,72 @@ mod data { .collect()) } + pub(super) fn clear_call_cache( + &self, + conn: &PgConnection, + from: Option, + to: Option, + ) -> Result<(), Error> { + if from.is_none() && to.is_none() { + // If both `from` and `to` arguments are equal to `None`, then truncation should be + // preferred over deletion as it is a faster operation. + self.truncate_call_cache(conn)?; + return Ok(()); + } + match self { + Storage::Shared => { + use public::eth_call_cache as cache; + let mut delete_stmt = diesel::delete(cache::table).into_boxed(); + if let Some(from) = from { + delete_stmt = delete_stmt.filter(cache::block_number.ge(from)); + } + if let Some(to) = to { + delete_stmt = delete_stmt.filter(cache::block_number.le(to)) + } + delete_stmt.execute(conn).map_err(Error::from)?; + Ok(()) + } + Storage::Private(Schema { call_cache, .. }) => match (from, to) { + // Because they are dynamically defined, our private call cache tables can't + // implement all the required traits for deletion. This means we can't use Diesel + // DSL with them and must rely on the `sql_query` function instead. + (Some(from), None) => { + let query = + format!("delete from {} where block_number >= $1", call_cache.qname); + sql_query(query) + .bind::(from) + .execute(conn) + .map_err(Error::from)?; + Ok(()) + } + (None, Some(to)) => { + let query = + format!("delete from {} where block_number <= $1", call_cache.qname); + sql_query(query) + .bind::(to) + .execute(conn) + .map_err(Error::from)?; + Ok(()) + } + (Some(from), Some(to)) => { + let query = format!( + "delete from {} where block_number >= $1 and block_number <= $2", + call_cache.qname + ); + sql_query(query) + .bind::(from) + .bind::(to) + .execute(conn) + .map_err(Error::from)?; + Ok(()) + } + (None, None) => { + unreachable!("truncation was handled at the beginning of this function"); + } + }, + } + } + pub(super) fn update_accessed_at( &self, conn: &PgConnection, @@ -1715,6 +1792,11 @@ impl ChainStoreTrait for ChainStore { .await } + async fn clear_call_cache(&self, from: Option, to: Option) -> Result<(), Error> { + let conn = self.get_conn()?; + self.storage.clear_call_cache(&conn, from, to) + } + async fn transaction_receipts_in_block( &self, block_hash: &H256,