#![recursion_limit = "256"]
#![cfg(unix)]

use beacon_chain::{
    test_utils::{AttestationStrategy, BeaconChainHarness, BlockStrategy, EphemeralHarnessType},
    ChainConfig,
};
use eth2::{types::BlockId, BeaconNodeHttpClient, SensitiveUrl, Timeouts};
use http_api::test_utils::{create_api_server, ApiServer};
use log::error;
use logging::test_logger;
use network::NetworkReceivers;
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::collections::HashMap;
use std::env;
use std::time::Duration;
use testcontainers::{clients::Cli, core::WaitFor, Image, RunnableImage};
use tokio::{runtime, task::JoinHandle};
use tokio_postgres::{config::Config as PostgresConfig, Client, NoTls};
use types::{Hash256, MainnetEthSpec, Slot};
use unused_port::unused_tcp4_port;
use url::Url;
use watch::{
    client::WatchHttpClient,
    config::Config,
    database::{self, Config as DatabaseConfig, PgPool, WatchSlot},
    server::{start_server, Config as ServerConfig},
    updater::{handler::*, run_updater, Config as UpdaterConfig, WatchSpec},
};

#[derive(Debug)]
pub struct Postgres(HashMap<String, String>);

impl Default for Postgres {
    fn default() -> Self {
        let mut env_vars = HashMap::new();
        env_vars.insert("POSTGRES_DB".to_owned(), "postgres".to_owned());
        env_vars.insert("POSTGRES_HOST_AUTH_METHOD".into(), "trust".into());

        Self(env_vars)
    }
}

impl Image for Postgres {
    type Args = ();

    fn name(&self) -> String {
        "postgres".to_owned()
    }

    fn tag(&self) -> String {
        "11-alpine".to_owned()
    }

    fn ready_conditions(&self) -> Vec<WaitFor> {
        vec![WaitFor::message_on_stderr(
            "database system is ready to accept connections",
        )]
    }

    fn env_vars(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
        Box::new(self.0.iter())
    }
}

type E = MainnetEthSpec;

const VALIDATOR_COUNT: usize = 32;
const SLOTS_PER_EPOCH: u64 = 32;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);

/// Set this environment variable to use a different hostname for connecting to
/// the database. Can be set to `host.docker.internal` for docker-in-docker
/// setups.
const WATCH_HOST_ENV_VARIABLE: &str = "WATCH_HOST";

fn build_test_config(config: &DatabaseConfig) -> PostgresConfig {
    let mut postgres_config = PostgresConfig::new();
    postgres_config
        .user(&config.user)
        .password(&config.password)
        .dbname(&config.default_dbname)
        .host(&config.host)
        .port(config.port)
        .connect_timeout(Duration::from_millis(config.connect_timeout_millis));
    postgres_config
}

async fn connect(config: &DatabaseConfig) -> (Client, JoinHandle<()>) {
    let db_config = build_test_config(config);
    let (client, conn) = db_config
        .connect(NoTls)
        .await
        .expect("Could not connect to db");
    let connection = runtime::Handle::current().spawn(async move {
        if let Err(e) = conn.await {
            error!("Connection error {:?}", e);
        }
    });

    (client, connection)
}

pub async fn create_test_database(config: &DatabaseConfig) {
    let (db, _) = connect(config).await;

    db.execute(&format!("CREATE DATABASE {};", config.dbname), &[])
        .await
        .expect("Database creation failed");
}

pub fn get_host_from_env() -> String {
    env::var(WATCH_HOST_ENV_VARIABLE).unwrap_or_else(|_| "localhost".to_string())
}

struct TesterBuilder {
    pub harness: BeaconChainHarness<EphemeralHarnessType<E>>,
    pub config: Config,
    _bn_network_rx: NetworkReceivers<E>,
}

impl TesterBuilder {
    pub async fn new() -> TesterBuilder {
        let harness = BeaconChainHarness::builder(E::default())
            .default_spec()
            .chain_config(ChainConfig {
                reconstruct_historic_states: true,
                ..ChainConfig::default()
            })
            .logger(test_logger())
            .deterministic_keypairs(VALIDATOR_COUNT)
            .fresh_ephemeral_store()
            .build();

        /*
         * Spawn a Beacon Node HTTP API.
         */
        let ApiServer {
            server,
            listening_socket: bn_api_listening_socket,
            network_rx: _bn_network_rx,
            ..
        } = create_api_server(
            harness.chain.clone(),
            &harness.runtime,
            harness.logger().clone(),
        )
        .await;
        tokio::spawn(server);

        /*
         * Create a watch configuration
         */
        let database_port = unused_tcp4_port().expect("Unable to find unused port.");
        let server_port = 0;
        let config = Config {
            database: DatabaseConfig {
                dbname: random_dbname(),
                port: database_port,
                host: get_host_from_env(),
                ..Default::default()
            },
            server: ServerConfig {
                listen_port: server_port,
                ..Default::default()
            },
            updater: UpdaterConfig {
                beacon_node_url: format!(
                    "http://{}:{}",
                    bn_api_listening_socket.ip(),
                    bn_api_listening_socket.port()
                ),
                ..Default::default()
            },
            ..Default::default()
        };

        Self {
            harness,
            config,
            _bn_network_rx,
        }
    }
    pub async fn build(self, pool: PgPool) -> Tester {
        /*
         * Spawn a Watch HTTP API.
         */
        let (addr, watch_server) = start_server(&self.config, SLOTS_PER_EPOCH, pool).unwrap();
        tokio::spawn(watch_server);

        /*
         * Create a HTTP client to talk to the watch HTTP API.
         */
        let client = WatchHttpClient {
            client: reqwest::Client::new(),
            server: Url::parse(&format!("http://{}:{}", addr.ip(), addr.port())).unwrap(),
        };

        /*
         * Create a HTTP client to talk to the Beacon Node API.
         */
        let beacon_node_url = SensitiveUrl::parse(&self.config.updater.beacon_node_url).unwrap();
        let bn = BeaconNodeHttpClient::new(beacon_node_url, Timeouts::set_all(DEFAULT_TIMEOUT));
        let spec = WatchSpec::mainnet("mainnet".to_string());

        /*
         * Build update service
         */
        let updater = UpdateHandler::new(bn, spec, self.config.clone())
            .await
            .unwrap();

        Tester {
            harness: self.harness,
            client,
            config: self.config,
            updater,
            _bn_network_rx: self._bn_network_rx,
        }
    }
    async fn initialize_database(&self) -> PgPool {
        create_test_database(&self.config.database).await;
        database::utils::run_migrations(&self.config.database);
        database::build_connection_pool(&self.config.database)
            .expect("Could not build connection pool")
    }
}

struct Tester {
    pub harness: BeaconChainHarness<EphemeralHarnessType<E>>,
    pub client: WatchHttpClient,
    pub config: Config,
    pub updater: UpdateHandler<E>,
    _bn_network_rx: NetworkReceivers<E>,
}

impl Tester {
    /// Extend the chain on the beacon chain harness. Do not update the beacon watch database.
    pub async fn extend_chain(&mut self, num_blocks: u64) -> &mut Self {
        self.harness.advance_slot();
        self.harness
            .extend_chain(
                num_blocks as usize,
                BlockStrategy::OnCanonicalHead,
                AttestationStrategy::AllValidators,
            )
            .await;
        self
    }

    // Advance the slot clock without a block. This results in a skipped slot.
    pub fn skip_slot(&mut self) -> &mut Self {
        self.harness.advance_slot();
        self
    }

    // Perform a single slot re-org.
    pub async fn reorg_chain(&mut self) -> &mut Self {
        let previous_slot = self.harness.get_current_slot();
        self.harness.advance_slot();
        let first_slot = self.harness.get_current_slot();
        self.harness
            .extend_chain(
                1,
                BlockStrategy::ForkCanonicalChainAt {
                    previous_slot,
                    first_slot,
                },
                AttestationStrategy::AllValidators,
            )
            .await;
        self
    }

    /// Run the watch updater service.
    pub async fn run_update_service(&mut self, num_runs: usize) -> &mut Self {
        for _ in 0..num_runs {
            run_updater(self.config.clone()).await.unwrap();
        }
        self
    }

    pub async fn perform_head_update(&mut self) -> &mut Self {
        self.updater.perform_head_update().await.unwrap();
        self
    }

    pub async fn perform_backfill(&mut self) -> &mut Self {
        self.updater.backfill_canonical_slots().await.unwrap();
        self
    }

    pub async fn update_unknown_blocks(&mut self) -> &mut Self {
        self.updater.update_unknown_blocks().await.unwrap();
        self
    }

    pub async fn update_validator_set(&mut self) -> &mut Self {
        self.updater.update_validator_set().await.unwrap();
        self
    }

    pub async fn fill_suboptimal_attestations(&mut self) -> &mut Self {
        self.updater.fill_suboptimal_attestations().await.unwrap();

        self
    }

    pub async fn backfill_suboptimal_attestations(&mut self) -> &mut Self {
        self.updater
            .backfill_suboptimal_attestations()
            .await
            .unwrap();

        self
    }

    pub async fn fill_block_rewards(&mut self) -> &mut Self {
        self.updater.fill_block_rewards().await.unwrap();

        self
    }

    pub async fn backfill_block_rewards(&mut self) -> &mut Self {
        self.updater.backfill_block_rewards().await.unwrap();

        self
    }

    pub async fn fill_block_packing(&mut self) -> &mut Self {
        self.updater.fill_block_packing().await.unwrap();

        self
    }

    pub async fn backfill_block_packing(&mut self) -> &mut Self {
        self.updater.backfill_block_packing().await.unwrap();

        self
    }

    pub async fn assert_canonical_slots_empty(&mut self) -> &mut Self {
        let lowest_slot = self
            .client
            .get_lowest_canonical_slot()
            .await
            .unwrap()
            .map(|slot| slot.slot.as_slot());

        assert_eq!(lowest_slot, None);

        self
    }

    pub async fn assert_lowest_canonical_slot(&mut self, expected: u64) -> &mut Self {
        let slot = self
            .client
            .get_lowest_canonical_slot()
            .await
            .unwrap()
            .unwrap()
            .slot
            .as_slot();

        assert_eq!(slot, Slot::new(expected));

        self
    }

    pub async fn assert_highest_canonical_slot(&mut self, expected: u64) -> &mut Self {
        let slot = self
            .client
            .get_highest_canonical_slot()
            .await
            .unwrap()
            .unwrap()
            .slot
            .as_slot();

        assert_eq!(slot, Slot::new(expected));

        self
    }

    pub async fn assert_canonical_slots_not_empty(&mut self) -> &mut Self {
        self.client
            .get_lowest_canonical_slot()
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_slot_is_skipped(&mut self, slot: u64) -> &mut Self {
        assert!(self
            .client
            .get_beacon_blocks(BlockId::Slot(Slot::new(slot)))
            .await
            .unwrap()
            .is_none());
        self
    }

    pub async fn assert_all_validators_exist(&mut self) -> &mut Self {
        assert_eq!(
            self.client
                .get_all_validators()
                .await
                .unwrap()
                .unwrap()
                .len(),
            VALIDATOR_COUNT
        );
        self
    }

    pub async fn assert_lowest_block_has_proposer_info(&mut self) -> &mut Self {
        let mut block = self
            .client
            .get_lowest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        if block.slot.as_slot() == 0 {
            block = self
                .client
                .get_next_beacon_block(block.root.as_hash())
                .await
                .unwrap()
                .unwrap()
        }

        self.client
            .get_proposer_info(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_highest_block_has_proposer_info(&mut self) -> &mut Self {
        let block = self
            .client
            .get_highest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        self.client
            .get_proposer_info(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_lowest_block_has_block_rewards(&mut self) -> &mut Self {
        let mut block = self
            .client
            .get_lowest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        if block.slot.as_slot() == 0 {
            block = self
                .client
                .get_next_beacon_block(block.root.as_hash())
                .await
                .unwrap()
                .unwrap()
        }

        self.client
            .get_block_reward(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_highest_block_has_block_rewards(&mut self) -> &mut Self {
        let block = self
            .client
            .get_highest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        self.client
            .get_block_reward(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_lowest_block_has_block_packing(&mut self) -> &mut Self {
        let mut block = self
            .client
            .get_lowest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        while block.slot.as_slot() <= SLOTS_PER_EPOCH {
            block = self
                .client
                .get_next_beacon_block(block.root.as_hash())
                .await
                .unwrap()
                .unwrap()
        }

        self.client
            .get_block_packing(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    pub async fn assert_highest_block_has_block_packing(&mut self) -> &mut Self {
        let block = self
            .client
            .get_highest_beacon_block()
            .await
            .unwrap()
            .unwrap();

        self.client
            .get_block_packing(BlockId::Root(block.root.as_hash()))
            .await
            .unwrap()
            .unwrap();

        self
    }

    /// Check that the canonical chain in watch matches that of the harness. Also check that all
    /// canonical blocks can be retrieved.
    pub async fn assert_canonical_chain_consistent(&mut self, last_slot: u64) -> &mut Self {
        let head_root = self.harness.chain.head_beacon_block_root();
        let mut chain: Vec<(Hash256, Slot)> = self
            .harness
            .chain
            .rev_iter_block_roots_from(head_root)
            .unwrap()
            .map(Result::unwrap)
            .collect();

        // `chain` contains skip slots, but the `watch` API will not return blocks that do not
        // exist.
        // We need to filter them out.
        chain.reverse();
        chain.dedup_by(|(hash1, _), (hash2, _)| hash1 == hash2);

        // Remove any slots below `last_slot` since it is known that the database has not
        // backfilled past it.
        chain.retain(|(_, slot)| slot.as_u64() >= last_slot);

        for (root, slot) in &chain {
            let block = self
                .client
                .get_beacon_blocks(BlockId::Root(*root))
                .await
                .unwrap()
                .unwrap();
            assert_eq!(block.slot.as_slot(), *slot);
        }

        self
    }

    /// Check that every block in the `beacon_blocks` table has corresponding entries in the
    /// `proposer_info`, `block_rewards` and `block_packing` tables.
    pub async fn assert_all_blocks_have_metadata(&mut self) -> &mut Self {
        let pool = database::build_connection_pool(&self.config.database).unwrap();

        let mut conn = database::get_connection(&pool).unwrap();
        let highest_block_slot = database::get_highest_beacon_block(&mut conn)
            .unwrap()
            .unwrap()
            .slot
            .as_slot();
        let lowest_block_slot = database::get_lowest_beacon_block(&mut conn)
            .unwrap()
            .unwrap()
            .slot
            .as_slot();
        for slot in lowest_block_slot.as_u64()..=highest_block_slot.as_u64() {
            let canonical_slot = database::get_canonical_slot(&mut conn, WatchSlot::new(slot))
                .unwrap()
                .unwrap();
            if !canonical_slot.skipped {
                database::get_block_rewards_by_slot(&mut conn, WatchSlot::new(slot))
                    .unwrap()
                    .unwrap();
                database::get_proposer_info_by_slot(&mut conn, WatchSlot::new(slot))
                    .unwrap()
                    .unwrap();
                database::get_block_packing_by_slot(&mut conn, WatchSlot::new(slot))
                    .unwrap()
                    .unwrap();
            }
        }

        self
    }
}

pub fn random_dbname() -> String {
    let mut s: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(8)
        .map(char::from)
        .collect();
    // Postgres gets weird about capitals in database names.
    s.make_ascii_lowercase();
    format!("test_{}", s)
}

#[cfg(unix)]
#[tokio::test]
async fn short_chain() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    tester
        .extend_chain(16)
        .await
        .assert_canonical_slots_empty()
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_canonical_slots_not_empty()
        .await
        .assert_canonical_chain_consistent(0)
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn short_chain_sync_starts_on_skip_slot() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    tester
        .skip_slot()
        .skip_slot()
        .extend_chain(6)
        .await
        .skip_slot()
        .extend_chain(6)
        .await
        .skip_slot()
        .assert_canonical_slots_empty()
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_canonical_slots_not_empty()
        .await
        .assert_canonical_chain_consistent(0)
        .await
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn short_chain_with_skip_slot() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    tester
        .extend_chain(5)
        .await
        .assert_canonical_slots_empty()
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_canonical_slots_not_empty()
        .await
        .assert_highest_canonical_slot(5)
        .await
        .assert_lowest_canonical_slot(0)
        .await
        .assert_canonical_chain_consistent(0)
        .await
        .skip_slot()
        .extend_chain(1)
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_highest_canonical_slot(7)
        .await
        .assert_slot_is_skipped(6)
        .await
        .assert_canonical_chain_consistent(0)
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn short_chain_with_reorg() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    tester
        .extend_chain(5)
        .await
        .assert_canonical_slots_empty()
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_canonical_slots_not_empty()
        .await
        .assert_highest_canonical_slot(5)
        .await
        .assert_lowest_canonical_slot(0)
        .await
        .assert_canonical_chain_consistent(0)
        .await
        .skip_slot()
        .reorg_chain()
        .await
        .extend_chain(1)
        .await
        .run_update_service(1)
        .await
        .assert_all_validators_exist()
        .await
        .assert_highest_canonical_slot(8)
        .await
        .assert_slot_is_skipped(6)
        .await
        .assert_canonical_chain_consistent(0)
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn chain_grows() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    // Apply four blocks to the chain.
    tester
        .extend_chain(4)
        .await
        .perform_head_update()
        .await
        // Head update should insert the head block.
        .assert_highest_canonical_slot(4)
        .await
        // And also backfill to the epoch boundary.
        .assert_lowest_canonical_slot(0)
        .await
        // Fill back to genesis.
        .perform_backfill()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(4)
        .await
        // Apply one block to the chain.
        .extend_chain(1)
        .await
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(5)
        .await
        // Apply two blocks to the chain.
        .extend_chain(2)
        .await
        // Update the head.
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(7)
        .await
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // Check the chain is consistent
        .assert_canonical_chain_consistent(0)
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn chain_grows_with_metadata() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    tester
        // Apply four blocks to the chain.
        .extend_chain(4)
        .await
        .perform_head_update()
        .await
        // Head update should insert the head block.
        .assert_highest_canonical_slot(4)
        .await
        // And also backfill to the epoch boundary.
        .assert_lowest_canonical_slot(0)
        .await
        // Fill back to genesis.
        .perform_backfill()
        .await
        // Insert all validators
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // Check the chain is consistent
        .assert_canonical_chain_consistent(0)
        .await
        // Get other chain data.
        // Backfill before forward fill to ensure order is arbitrary.
        .backfill_block_rewards()
        .await
        .fill_block_rewards()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(4)
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await
        // Apply one block to the chain.
        .extend_chain(1)
        .await
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(5)
        .await
        // Apply two blocks to the chain.
        .extend_chain(2)
        .await
        // Update the head.
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(7)
        .await
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // Check the chain is consistent
        .assert_canonical_chain_consistent(0)
        .await
        // Get other chain data.
        .fill_block_rewards()
        .await
        .backfill_block_rewards()
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn chain_grows_with_metadata_and_multiple_skip_slots() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;

    // Apply four blocks to the chain.
    tester
        .extend_chain(4)
        .await
        .perform_head_update()
        .await
        // Head update should insert the head block.
        .assert_highest_canonical_slot(4)
        // And also backfill to the epoch boundary.
        .await
        .assert_lowest_canonical_slot(0)
        .await
        // Fill back to genesis.
        .perform_backfill()
        .await
        // Insert all validators
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // Check the chain is consistent.
        .assert_canonical_chain_consistent(0)
        .await
        // Get other chain data.
        .fill_block_rewards()
        .await
        .backfill_block_rewards()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(4)
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await
        // Add multiple skip slots.
        .skip_slot()
        .skip_slot()
        .skip_slot()
        // Apply one block to the chain.
        .extend_chain(1)
        .await
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(8)
        .await
        // Apply two blocks to the chain.
        .extend_chain(2)
        .await
        // Update the head.
        .perform_head_update()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(10)
        .await
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // Check the chain is consistent
        .assert_canonical_chain_consistent(0)
        .await
        // Get other chain data.
        // Backfill before forward fill to ensure order is arbitrary.
        .backfill_block_rewards()
        .await
        .fill_block_rewards()
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn chain_grows_to_second_epoch() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;
    // Apply 40 blocks to the chain.
    tester
        .extend_chain(40)
        .await
        .perform_head_update()
        .await
        // Head update should insert the head block.
        .assert_highest_canonical_slot(40)
        .await
        // And also backfill to the epoch boundary.
        .assert_lowest_canonical_slot(32)
        .await
        // Fill back to genesis.
        .perform_backfill()
        .await
        // Insert all validators
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // Check the chain is consistent.
        .assert_canonical_chain_consistent(0)
        .await
        // Get block packings.
        .fill_block_packing()
        .await
        .backfill_block_packing()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(40)
        .await
        // All packings should be present.
        .assert_lowest_block_has_block_packing()
        .await
        .assert_highest_block_has_block_packing()
        .await
        // Skip a slot
        .skip_slot()
        // Apply two blocks to the chain.
        .extend_chain(2)
        .await
        // Update the head.
        .perform_head_update()
        .await
        // All blocks should be present.
        .assert_lowest_canonical_slot(0)
        .await
        .assert_highest_canonical_slot(43)
        .await
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // Update new block_packing
        // Backfill before forward fill to ensure order is arbitrary
        .backfill_block_packing()
        .await
        .fill_block_packing()
        .await
        // All packings should be present.
        .assert_lowest_block_has_block_packing()
        .await
        .assert_highest_block_has_block_packing()
        .await
        // Check the chain is consistent
        .assert_canonical_chain_consistent(0)
        .await;
}

#[cfg(unix)]
#[tokio::test]
async fn large_chain() {
    let builder = TesterBuilder::new().await;

    let docker = Cli::default();
    let image = RunnableImage::from(Postgres::default())
        .with_mapped_port((builder.config.database.port, 5432));
    let _node = docker.run(image);

    let pool = builder.initialize_database().await;
    let mut tester = builder.build(pool).await;
    // Apply 40 blocks to the chain.
    tester
        .extend_chain(400)
        .await
        .perform_head_update()
        .await
        // Head update should insert the head block.
        .assert_highest_canonical_slot(400)
        .await
        // And also backfill to the epoch boundary.
        .assert_lowest_canonical_slot(384)
        .await
        // Backfill 2 epochs as per default config.
        .perform_backfill()
        .await
        // Insert all validators
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // Check the chain is consistent.
        .assert_canonical_chain_consistent(384)
        .await
        // Get block rewards and proposer info.
        .fill_block_rewards()
        .await
        .backfill_block_rewards()
        .await
        // Get block packings.
        .fill_block_packing()
        .await
        .backfill_block_packing()
        .await
        // Should have backfilled 2 more epochs.
        .assert_lowest_canonical_slot(320)
        .await
        .assert_highest_canonical_slot(400)
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await
        // All packings should be present.
        .assert_lowest_block_has_block_packing()
        .await
        .assert_highest_block_has_block_packing()
        .await
        // Skip a slot
        .skip_slot()
        // Apply two blocks to the chain.
        .extend_chain(2)
        .await
        // Update the head.
        .perform_head_update()
        .await
        .perform_backfill()
        .await
        // Should have backfilled 2 more epochs
        .assert_lowest_canonical_slot(256)
        .await
        .assert_highest_canonical_slot(403)
        .await
        // Update validators
        .update_validator_set()
        .await
        // Insert all blocks.
        .update_unknown_blocks()
        .await
        // All validators should be present.
        .assert_all_validators_exist()
        .await
        // Get suboptimal attestations.
        .fill_suboptimal_attestations()
        .await
        .backfill_suboptimal_attestations()
        .await
        // Get block rewards and proposer info.
        .fill_block_rewards()
        .await
        .backfill_block_rewards()
        .await
        // Get block packing.
        // Backfill before forward fill to ensure order is arbitrary.
        .backfill_block_packing()
        .await
        .fill_block_packing()
        .await
        // All rewards should be present.
        .assert_lowest_block_has_block_rewards()
        .await
        .assert_highest_block_has_block_rewards()
        .await
        // All proposers should be present.
        .assert_lowest_block_has_proposer_info()
        .await
        .assert_highest_block_has_proposer_info()
        .await
        // All packings should be present.
        .assert_lowest_block_has_block_packing()
        .await
        .assert_highest_block_has_block_packing()
        .await
        // Check the chain is consistent.
        .assert_canonical_chain_consistent(256)
        .await
        // Check every block has rewards, proposer info and packing statistics.
        .assert_all_blocks_have_metadata()
        .await;
}
