diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 415c2b96859..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 @@ -171,13 +171,13 @@ 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 - name: Run integration tests - run: just test-integration --verbose + run: just test-integration - name: Cat graph-node.log if: always() 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 adaa1732024..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::{ @@ -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"; @@ -1390,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<()> { @@ -1454,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?;