/**
 * Test fixtures and utilities to set up and run a test using multiple validators
 */ import { AztecNodeService } from '@aztec/aztec-node';
import { range } from '@aztec/foundation/array';
import { SecretValue } from '@aztec/foundation/config';
import { withLoggerBindings } from '@aztec/foundation/log/server';
import { bufferToHex } from '@aztec/foundation/string';
import getPort from 'get-port';
import { TEST_PEER_CHECK_INTERVAL_MS } from './fixtures.js';
import { createAndSyncProverNode, getPrivateKeyFromIndex } from './utils.js';
import { getEndToEndTestTelemetryClient } from './with_telemetry_utils.js';
// Setup snapshots will create a node with index 0, and run extra bootstrap with
// index 1, and prover node with index 2, so all of our loops here need to start from 3
// to avoid running validators with the same key
export const ATTESTER_PRIVATE_KEYS_START_INDEX = 3;
// Global counters for actor naming (start at 1)
let validatorCounter = 1;
let nodeCounter = 1;
let proverCounter = 1;
export function generatePrivateKeys(startIndex, numberOfKeys) {
    const privateKeys = [];
    // Do not start from 0 as it is used during setup
    for(let i = startIndex; i < startIndex + numberOfKeys; i++){
        privateKeys.push(`0x${getPrivateKeyFromIndex(i).toString('hex')}`);
    }
    return privateKeys;
}
export async function createNodes(config, dateProvider, bootstrapNodeEnr, numNodes, bootNodePort, prefilledPublicData, dataDirectory, metricsPort, indexOffset = 0, validatorsPerNode = 1) {
    const nodePromises = [];
    for(let i = 0; i < numNodes; i++){
        const index = indexOffset + i;
        // We run on ports from the bootnode upwards
        const port = bootNodePort + 1 + index;
        // Determine validator indices for this node
        const validatorIndices = validatorsPerNode === 1 ? index : range(validatorsPerNode, validatorsPerNode * index);
        // Assign data directory
        const dataDir = dataDirectory ? `${dataDirectory}-${index}` : undefined;
        const nodePromise = createNode(config, dateProvider, port, bootstrapNodeEnr, validatorIndices, prefilledPublicData, dataDir, metricsPort);
        nodePromises.push(nodePromise);
    }
    const nodes = await Promise.all(nodePromises);
    // Sanity check that we have a sequencer if required
    const seqClient = nodes[0].getSequencer();
    if (!seqClient && config.disableValidator === false) {
        throw new Error('Sequencer not found');
    }
    return nodes;
}
/** Creates a P2P enabled instance of Aztec Node Service with a validator. */ export async function createNode(config, dateProvider, tcpPort, bootstrapNode, addressIndex, prefilledPublicData, dataDirectory, metricsPort) {
    const actorIndex = validatorCounter++;
    return await withLoggerBindings({
        actor: `validator-${actorIndex}`
    }, async ()=>{
        const validatorConfig = await createValidatorConfig(config, bootstrapNode, tcpPort, addressIndex, dataDirectory);
        const telemetry = await getEndToEndTestTelemetryClient(metricsPort);
        return await AztecNodeService.createAndSync(validatorConfig, {
            telemetry,
            dateProvider
        }, {
            prefilledPublicData,
            dontStartSequencer: config.dontStartSequencer
        });
    });
}
/** Creates a P2P enabled instance of Aztec Node Service without a validator */ export async function createNonValidatorNode(baseConfig, dateProvider, tcpPort, bootstrapNode, prefilledPublicData, dataDirectory, metricsPort) {
    const actorIndex = nodeCounter++;
    return await withLoggerBindings({
        actor: `node-${actorIndex}`
    }, async ()=>{
        const p2pConfig = await createP2PConfig(baseConfig, bootstrapNode, tcpPort, dataDirectory);
        const config = {
            ...p2pConfig,
            disableValidator: true,
            validatorPrivateKeys: undefined,
            sequencerPublisherPrivateKeys: []
        };
        const telemetry = await getEndToEndTestTelemetryClient(metricsPort);
        return await AztecNodeService.createAndSync(config, {
            telemetry,
            dateProvider
        }, {
            prefilledPublicData
        });
    });
}
export async function createProverNode(config, tcpPort, bootstrapNode, addressIndex, deps, prefilledPublicData, dataDirectory, metricsPort) {
    const actorIndex = proverCounter++;
    return await withLoggerBindings({
        actor: `prover-${actorIndex}`
    }, async ()=>{
        const proverNodePrivateKey = getPrivateKeyFromIndex(ATTESTER_PRIVATE_KEYS_START_INDEX + addressIndex);
        const telemetry = await getEndToEndTestTelemetryClient(metricsPort);
        const p2pConfig = await createP2PConfig(config, bootstrapNode, tcpPort, dataDirectory);
        return await createAndSyncProverNode(bufferToHex(proverNodePrivateKey), {
            ...config,
            ...p2pConfig
        }, {
            dataDirectory
        }, {
            ...deps,
            telemetry
        }, {
            prefilledPublicData: prefilledPublicData ?? []
        });
    });
}
export async function createP2PConfig(config, bootstrapNodeEnr, port, dataDirectory) {
    port = port ?? await getPort();
    const nodeConfig = {
        ...config,
        p2pIp: `127.0.0.1`,
        p2pPort: port,
        p2pEnabled: true,
        peerCheckIntervalMS: TEST_PEER_CHECK_INTERVAL_MS,
        blockCheckIntervalMS: 1000,
        dataDirectory,
        bootstrapNodes: bootstrapNodeEnr ? [
            bootstrapNodeEnr
        ] : []
    };
    return nodeConfig;
}
export async function createValidatorConfig(config, bootstrapNodeEnr, port, addressIndex = 1, dataDirectory) {
    const addressIndices = Array.isArray(addressIndex) ? addressIndex : [
        addressIndex
    ];
    if (addressIndices.length === 0 && !config.validatorPrivateKey) {
        throw new Error('At least one address index must be provided to create a validator config');
    }
    // Use override private key if provided, otherwise derive from address indices
    const attesterPrivateKeys = config.validatorPrivateKey ? [
        config.validatorPrivateKey
    ] : addressIndices.map((index)=>bufferToHex(getPrivateKeyFromIndex(ATTESTER_PRIVATE_KEYS_START_INDEX + index)));
    const p2pConfig = await createP2PConfig(config, bootstrapNodeEnr, port, dataDirectory);
    const nodeConfig = {
        ...config,
        ...p2pConfig,
        validatorPrivateKeys: new SecretValue(attesterPrivateKeys),
        sequencerPublisherPrivateKeys: [
            new SecretValue(attesterPrivateKeys[0])
        ]
    };
    return nodeConfig;
}
