From 86184568fb9b7e385dca31db76f9191c7a2d01ba Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 4 Feb 2026 14:50:26 -0800 Subject: [PATCH 1/3] tests: Make the grafted test independent of block numbers We used to use hardcoded POIs which required us to use fixed block hashes. But since commit f145736 that's no longer needed and we can just use whatever block hashes the chain has, making the test setup less fragile. --- tests/tests/integration_tests.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index adaa1732024..f6ea73eeb8d 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -865,25 +865,23 @@ async fn test_subgraph_grafting(ctx: TestContext) -> anyhow::Result<()> { assert!(subgraph.healthy); - // Fixed block hashes from deterministic Anvil config - let block_hashes: Vec<&str> = vec![ - "e26fccbd24dcc76074b432becf29cad3bcba11a8467a7b770fad109c2b5d14c2", - "249dbcbee975c22f8c9cc937536945ca463568c42d8933a3f54129dec352e46b", - "408675f81c409dede08d0eeb2b3420a73b067c4fa8c5f0fc49ce369289467c33", - ]; + // Fetch block hashes from the chain + let mut block_hashes: Vec = Vec::new(); + for i in 1..4 { + let hash = get_block_hash(i) + .await + .ok_or_else(|| anyhow!("Failed to get block hash for block {}", i))?; + block_hashes.push(hash); + } // The deployment hash is dynamic (depends on the base subgraph's hash) let deployment_hash = DeploymentHash::new(&subgraph.deployment).unwrap(); - // Compute the expected POI values dynamically + // Compute the expected POIs using the actual block hashes let expected_pois = compute_expected_pois(&deployment_hash, &block_hashes); for i in 1..4 { - let block_hash = get_block_hash(i).await.unwrap(); - // We need to make sure that the preconditions for POI are fulfilled - // namely that the blockchain produced the proper block hashes for the - // blocks of which we will check the POI. - assert_eq!(block_hash, block_hashes[(i - 1) as usize]); + let block_hash = &block_hashes[(i - 1) as usize]; const FETCH_POI: &str = r#" query proofOfIndexing($subgraph: String!, $blockNumber: Int!, $blockHash: String!, $indexer: String!) { @@ -937,7 +935,7 @@ async fn test_subgraph_grafting(ctx: TestContext) -> anyhow::Result<()> { /// POI algorithm transition: /// - Blocks 0-2: Legacy POI digests (from base subgraph) /// - Block 3+: Fast POI algorithm with transition from Legacy -fn compute_expected_pois(deployment_hash: &DeploymentHash, block_hashes: &[&str]) -> Vec { +fn compute_expected_pois(deployment_hash: &DeploymentHash, block_hashes: &[String]) -> Vec { let logger = Logger::root(Discard, o!()); let causality_region = "ethereum/test"; From f0a7070c5ad28b80a5bec4700582c667dbdcaca7 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 4 Feb 2026 14:53:56 -0800 Subject: [PATCH 2/3] tests: Switch integration tests to mine-on-demand Instead of mining a block every 2s, mine blocks only on demand, simplifying the setup and making it much nicer to keep anvil state for future runs --- .github/workflows/ci.yml | 2 +- flake.nix | 2 +- tests/docker-compose.yml | 2 +- tests/src/contract.rs | 38 +++++++++++++++++++++++++++++++- tests/tests/integration_tests.rs | 35 ++++++++++------------------- 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 415c2b96859..76a0ed7f57b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -171,7 +171,7 @@ jobs: run: pnpm install - name: Start anvil - run: anvil --gas-limit 100000000000 --base-fee 1 --block-time 2 --timestamp 1743944919 --port 3021 & + run: anvil --gas-limit 100000000000 --base-fee 1 --timestamp 1743944919 --port 3021 & - name: Build graph-node run: just build --test integration_tests diff --git a/flake.nix b/flake.nix index decfdd1f554..933a35ef04c 100644 --- a/flake.nix +++ b/flake.nix @@ -197,7 +197,7 @@ timestamp = 1743944919; gasLimit = 100000000000; baseFee = 1; - blockTime = 2; + blockTime = null; state = "./.data/integration/anvil/state.json"; stateInterval = 30; preserveHistoricalStates = true; diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index c05228f8418..8bf5f4c62be 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -25,7 +25,7 @@ services: image: ghcr.io/foundry-rs/foundry:v1.4.0 ports: - '3021:8545' - command: "'anvil --host 0.0.0.0 --gas-limit 100000000000 --base-fee 1 --block-time 2 --timestamp 1743944919 --mnemonic \"test test test test test test test test test test test junk\"'" + command: "'anvil --host 0.0.0.0 --gas-limit 100000000000 --base-fee 1 --timestamp 1743944919 --mnemonic \"test test test test test test test test test test test junk\"'" # graph-node ports: # json-rpc: 8020 diff --git a/tests/src/contract.rs b/tests/src/contract.rs index ffd92fe5e34..d7a67ceffa9 100644 --- a/tests/src/contract.rs +++ b/tests/src/contract.rs @@ -5,7 +5,7 @@ use graph::prelude::{ json_abi::JsonAbi, network::{Ethereum, TransactionBuilder}, primitives::{Address, Bytes, U256}, - providers::{Provider, ProviderBuilder, WalletProvider}, + providers::{ext::AnvilApi, Provider, ProviderBuilder, WalletProvider}, rpc::types::{Block, TransactionReceipt, TransactionRequest}, signers::local::PrivateKeySigner, }, @@ -280,4 +280,40 @@ impl Contract { .ok() .flatten() } + + /// Mine the specified number of empty blocks using Anvil's evm_mine RPC. + pub async fn mine_blocks(count: u64) -> anyhow::Result { + let provider = Self::provider(); + + for _ in 0..count { + provider + .evm_mine(None) + .await + .map_err(|e| anyhow::anyhow!("Failed to mine block: {}", e))?; + } + + let block = provider + .get_block_number() + .await + .map_err(|e| anyhow::anyhow!("Failed to get block number: {}", e))?; + + Ok(block) + } + + /// Ensure the blockchain has reached at least the specified block number. + /// Mines empty blocks if needed. + pub async fn ensure_block(target_block: u64) -> anyhow::Result<()> { + let provider = Self::provider(); + let current = provider + .get_block_number() + .await + .map_err(|e| anyhow::anyhow!("Failed to get block number: {}", e))?; + + if current < target_block { + let blocks_needed = target_block - current; + Self::mine_blocks(blocks_needed).await?; + } + + Ok(()) + } } diff --git a/tests/tests/integration_tests.rs b/tests/tests/integration_tests.rs index f6ea73eeb8d..1eb83f3a660 100644 --- a/tests/tests/integration_tests.rs +++ b/tests/tests/integration_tests.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use std::future::Future; use std::pin::Pin; -use std::time::{self, Duration, Instant}; +use std::time::{Duration, Instant}; use anyhow::{anyhow, bail, Context, Result}; use graph::components::subgraph::{ @@ -1388,24 +1388,6 @@ async fn test_declared_calls_struct_fields(ctx: TestContext) -> anyhow::Result<( Ok(()) } -async fn wait_for_blockchain_block(block_number: i32) -> bool { - // Wait up to 5 minutes for the expected block to appear - const STATUS_WAIT: Duration = Duration::from_secs(300); - const REQUEST_REPEATING: Duration = time::Duration::from_secs(1); - let start = Instant::now(); - while start.elapsed() < STATUS_WAIT { - let latest_block = Contract::latest_block().await; - if let Some(latest_block) = latest_block { - let number = latest_block.header.number; - if number >= block_number as u64 { - return true; - } - } - tokio::time::sleep(REQUEST_REPEATING).await; - } - false -} - /// The main test entrypoint. #[graph::test] async fn integration_tests() -> anyhow::Result<()> { @@ -1452,10 +1434,17 @@ async fn integration_tests() -> anyhow::Result<()> { cases }; - // Here we wait for a block in the blockchain in order not to influence - // block hashes for all the blocks until the end of the grafting tests. - // Currently the last used block for grafting test is the block 3. - assert!(wait_for_blockchain_block(SUBGRAPH_LAST_GRAFTING_BLOCK).await); + // Mine empty blocks to reach the required block number for grafting tests. + // This ensures deterministic block hashes for blocks 1-3 before any + // contract deployments occur. + status!( + "setup", + "Mining blocks to reach block {}", + SUBGRAPH_LAST_GRAFTING_BLOCK + ); + Contract::ensure_block(SUBGRAPH_LAST_GRAFTING_BLOCK as u64) + .await + .context("Failed to mine initial blocks for grafting test")?; let contracts = Contract::deploy_all().await?; From f2c326357f0383de6c4897efd506066e32b48e3c Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Wed, 4 Feb 2026 14:59:54 -0800 Subject: [PATCH 3/3] ci: Do not pass --verbose to cargo for running tests This just adds a lot of unnecessary noise to the CI logs --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76a0ed7f57b..58b84e253f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3 - name: Run unit tests - run: just test-unit --verbose + run: just test-unit runner-tests: name: Subgraph Runner integration tests @@ -113,7 +113,7 @@ jobs: run: pnpm install - name: Run runner tests - run: just test-runner --verbose + run: just test-runner integration-tests: name: Run integration tests @@ -177,7 +177,7 @@ jobs: run: just build --test integration_tests - name: Run integration tests - run: just test-integration --verbose + run: just test-integration - name: Cat graph-node.log if: always()