Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
timestamp = 1743944919;
gasLimit = 100000000000;
baseFee = 1;
blockTime = 2;
blockTime = null;
state = "./.data/integration/anvil/state.json";
stateInterval = 30;
preserveHistoricalStates = true;
Expand Down
2 changes: 1 addition & 1 deletion tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 37 additions & 1 deletion tests/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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<u64> {
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(())
}
}
59 changes: 23 additions & 36 deletions tests/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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<String> = 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!) {
Expand Down Expand Up @@ -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<String> {
fn compute_expected_pois(deployment_hash: &DeploymentHash, block_hashes: &[String]) -> Vec<String> {
let logger = Logger::root(Discard, o!());
let causality_region = "ethereum/test";

Expand Down Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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?;

Expand Down
Loading