Epoch Operations

Overview

Epochs are time-bounded periods in OP_NET that provide finality and consensus. Each epoch spans multiple Bitcoin blocks and requires a SHA-1 collision solution to be proposed by a miner. The provider exposes methods to query epoch data, retrieve mining templates, and submit solutions, enabling applications to monitor network consensus and participate in epoch mining.

For a comprehensive explanation of the epoch system, SHA-1 collision mining, and the consensus mechanism, refer to the About Epochs section in the Epochs and Mining documentation.

Epoch Structures Reference

Epoch

The Epoch interface contains comprehensive data about a completed epoch.

typescript
Epoch structure
interface Epoch {
    // Identity
    epochNumber: bigint;          // Sequential epoch ID
    epochHash: Uint8Array;        // Unique hash of this epoch

    // State
    epochRoot: Uint8Array;        // State root at epoch end

    // Block range
    startBlock: bigint;           // First block in epoch
    endBlock: bigint;             // Last block in epoch

    // Mining parameters
    difficultyScaled: bigint;     // Scaled difficulty value
    minDifficulty?: string;       // Minimum required difficulty
    targetHash: Uint8Array;       // Mining target hash

    // Proposer info
    proposer: EpochMiner;         // Winning miner

    // Proofs
    proofs: readonly Uint8Array[];// Epoch validity proofs
}

EpochMiner

The EpochMiner interface represents the miner who proposed an epoch.

typescript
EpochMiner structure
interface EpochMiner {
    solution: Uint8Array;      // SHA-1 collision solution
    publicKey: Address;        // Miner's public key address
    salt: Uint8Array;          // Salt used in solution
    graffiti?: Uint8Array;     // Optional miner message (32 bytes max)
}

Fetching Epochs

Using getLatestEpoch()

The getLatestEpoch() method retrieves the most recent completed epoch from the network. The includeSubmissions parameter controls whether all miner submissions for the epoch are included in the response, or only the winning proposer.

The returned Epoch object contains comprehensive epoch data including the epoch number, state root, block range, difficulty, and proposer information. This method is useful for monitoring network consensus and verifying the current state of the chain.

Method Signature

typescript
getLatestEpoch() signature
async getLatestEpoch(
    includeSubmissions: boolean  // Include all submissions
): Promise<Epoch>
        

Basic Query

typescript
Basic query
import { JSONRpcProvider } from 'opnet';
import { networks } from '@btc-vision/bitcoin';

const network = networks.regtest;
const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });

// Get latest epoch without submissions
const epoch = await provider.getLatestEpoch(false);

console.log('Epoch Number:', epoch.epochNumber);
console.log('Start Block:', epoch.startBlock);
console.log('End Block:', epoch.endBlock);
console.log('Proposer:', epoch.proposer.publicKey.toHex());

With Submissions

typescript
Query with submissions
import { toHex } from '@btc-vision/bitcoin';

// Get latest epoch with all submissions
const epochWithSubmissions = await provider.getLatestEpoch(true);

console.log('Epoch:', epochWithSubmissions.epochNumber);
console.log('Total submissions:', epochWithSubmissions.submissions?.length ?? 0);

// Access all miner submissions
if (epochWithSubmissions.submissions) {
    for (const submission of epochWithSubmissions.submissions) {
        console.log('Miner:', submission.publicKey.toHex());
        console.log('Solution:', toHex(submission.solution));
    }
}

Using getEpochByNumber()

The getEpochByNumber() method retrieves a specific epoch by its sequential identifier. The epochNumber parameter specifies which epoch to fetch, and the optional includeSubmissions parameter controls whether all miner submissions are included in the response.

When includeSubmissions is false, the method returns an Epoch object containing only the winning proposer. When true, it returns an EpochWithSubmissions object containing all submitted solutions for the epoch, useful for analyzing mining participation and competition.

Method Signature

typescript
getEpochByNumber() signature
async getEpochByNumber(
    epochNumber: BigNumberish,                  // Epoch sequence number
    includeSubmissions: boolean = false          // Include all submissions
): Promise<Epoch | EpochWithSubmissions>
        

Get Epoch by Number

typescript
Query epoch by number
import { toHex } from '@btc-vision/bitcoin';

// Fetch specific epoch by number
const epoch = await provider.getEpochByNumber(100n);

console.log('Epoch:', epoch.epochNumber);
console.log('Hash:', toHex(epoch.epochHash));
console.log('Target:', toHex(epoch.targetHash));
console.log('Difficulty:', epoch.difficultyScaled);

Using getEpochByHash()

The getEpochByHash() method retrieves an epoch using its unique hash identifier. This is useful when referencing epochs from external sources or when the hash is known but the sequential number is not.

The method accepts the epoch hash as a string and an optional includeSubmissions parameter. The return type matches getEpochByNumber(), providing either an Epoch or EpochWithSubmissions object depending on whether submissions are requested.

Method Signature

typescript
getEpochByHash() signature
async getEpochByHash(
    epochHash: string,                          // Unique epoch hash as string
    includeSubmissions: boolean = false          // Include all submissions
): Promise<Epoch | EpochWithSubmissions>

Get Epoch by Hash

typescript
Query epoch by hash
// Fetch epoch by its unique hash (string parameter)
const epoch = await provider.getEpochByHash('0xabcdef...');

console.log('Epoch Number:', epoch.epochNumber);
console.log('Block Range:', epoch.startBlock, '-', epoch.endBlock);

Working with Epochs

Get Epoch Block Count

The number of blocks covered by an epoch can be calculated from its start and end block values. The following example demonstrates retrieving an epoch and computing the total block count within its range.

typescript
Get epoch block count
async function getEpochBlockCount(
    provider: JSONRpcProvider,
    epochNumber: bigint
): Promise<bigint> {
    const epoch = await provider.getEpochByNumber(epochNumber);
    return epoch.endBlock - epoch.startBlock + 1n;
}

// Usage
const blockCount = await getEpochBlockCount(provider, 100n);
console.log('Blocks in epoch:', blockCount);

Compare Epochs

Comparing epochs provides insight into network evolution over time. The following example fetches two epochs concurrently and calculates the difference in difficulty and block count between them, useful for analyzing network adjustments.

typescript
Compare epochs
async function compareEpochs(
    provider: JSONRpcProvider,
    epochA: bigint,
    epochB: bigint
): Promise<{
    difficultyChange: bigint;
    blockCountDiff: bigint;
}> {
    const [a, b] = await Promise.all([
        provider.getEpochByNumber(epochA),
        provider.getEpochByNumber(epochB),
    ]);

    const blocksA = a.endBlock - a.startBlock;
    const blocksB = b.endBlock - b.startBlock;

    return {
        difficultyChange: b.difficultyScaled - a.difficultyScaled,
        blockCountDiff: blocksB - blocksA,
    };
}

// Usage
const comparison = await compareEpochs(provider, 99n, 100n);
console.log('Difficulty change:', comparison.difficultyChange);

Get Epoch History

Retrieving a sequence of recent epochs enables historical analysis and trend tracking. The following example fetches the latest epoch and iterates backward to collect a specified number of previous epochs, providing a view of recent network activity.

typescript
Get epoch history
async function getEpochHistory(
    provider: JSONRpcProvider,
    count: number
): Promise<Epoch[]> {
    const latest = await provider.getLatestEpoch(false);
    const epochs: Epoch[] = [latest];

    for (let i = 1; i < count; i++) {
        const epochNum = latest.epochNumber - BigInt(i);
        if (epochNum < 0n) break;

        const epoch = await provider.getEpochByNumber(epochNum);
        epochs.push(epoch);
    }

    return epochs;
}

// Usage
const history = await getEpochHistory(provider, 10);
console.log('Last 10 epochs:');
for (const epoch of history) {
    console.log(`  Epoch ${epoch.epochNumber}: blocks ${epoch.startBlock}-${epoch.endBlock}`);
}

Analyze Epoch Difficulty

Each epoch contains difficulty parameters that govern the SHA-1 collision mining process. The following example extracts and formats the difficulty metrics from an epoch, useful for monitoring network difficulty adjustments over time.

typescript
Analyze epoch difficulty
import { toHex } from '@btc-vision/bitcoin';

async function analyzeEpochDifficulty(
    provider: JSONRpcProvider,
    epochNumber: bigint
): Promise<{
    epoch: bigint;
    difficulty: bigint;
    minDifficulty: string | undefined;
    targetHash: string;
}> {
    const epoch = await provider.getEpochByNumber(epochNumber);

    return {
        epoch: epoch.epochNumber,
        difficulty: epoch.difficultyScaled,
        minDifficulty: epoch.minDifficulty,
        targetHash: toHex(epoch.targetHash),
    };
}

// Usage
const analysis = await analyzeEpochDifficulty(provider, 100n);
console.log('Difficulty Analysis:');
console.log('  Scaled difficulty:', analysis.difficulty);
console.log('  Min difficulty:', analysis.minDifficulty);

Track Proposer Wins

Tracking proposer wins across epochs provides insight into mining distribution and network decentralization. The following example iterates through recent epochs and aggregates win counts by proposer address, useful for analyzing mining participation over time.

typescript
Track proposer wins
async function getProposerStats(
    provider: JSONRpcProvider,
    epochCount: number
): Promise<Map<string, number>> {
    const stats = new Map<string, number>();
    const latest = await provider.getLatestEpoch(false);

    for (let i = 0; i < epochCount; i++) {
        const epochNum = latest.epochNumber - BigInt(i);
        if (epochNum < 0n) break;

        const epoch = await provider.getEpochByNumber(epochNum);
        const proposer = epoch.proposer.publicKey.toHex();

        stats.set(proposer, (stats.get(proposer) ?? 0) + 1);
    }

    return stats;
}

// Usage
const proposerStats = await getProposerStats(provider, 100);
console.log('Proposer statistics (last 100 epochs):');
for (const [proposer, wins] of proposerStats) {
    console.log(`  ${proposer.slice(0, 16)}...: ${wins} wins`);
}

Finding Epochs

Find Epoch Containing Block

Determining which epoch contains a specific block is useful for correlating transactions with epoch finality. The following example searches backward from the latest epoch to find the epoch whose block range includes the specified block number.

typescript
Find epoch containing block
async function findEpochForBlock(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<Epoch | null> {
    // Start from latest and search backwards
    let current = await provider.getLatestEpoch(false);

    while (current.epochNumber >= 0n) {
        if (blockNumber >= current.startBlock && blockNumber <= current.endBlock) {
            return current;
        }

        if (blockNumber > current.endBlock) {
            // Block is after this epoch, not found yet
            return null;
        }

        if (current.epochNumber === 0n) break;

        current = await provider.getEpochByNumber(current.epochNumber - 1n);
    }

    return null;
}

// Usage
const epoch = await findEpochForBlock(provider, 50000n);
if (epoch) {
    console.log('Block 50000 is in epoch', epoch.epochNumber);
}

Find Epochs by Proposer

Filtering epochs by proposer allows tracking a specific miner's contributions to the network. The following example searches through recent epochs and collects those where the specified public key was the winning proposer.

typescript
Find epochs by proposer
async function findEpochsByProposer(
    provider: JSONRpcProvider,
    proposerKey: string,
    maxEpochs: number = 100
): Promise<Epoch[]> {
    const results: Epoch[] = [];
    const latest = await provider.getLatestEpoch(false);

    for (let i = 0; i < maxEpochs; i++) {
        const epochNum = latest.epochNumber - BigInt(i);
        if (epochNum < 0n) break;

        const epoch = await provider.getEpochByNumber(epochNum);

        if (epoch.proposer.publicKey.toHex() === proposerKey) {
            results.push(epoch);
        }
    }

    return results;
}

// Usage
const myEpochs = await findEpochsByProposer(
    provider,
    '0x1234...minerkey...',
    500
);
console.log('Found', myEpochs.length, 'epochs proposed by this miner');

Complete Epoch Service

typescript
Epoch Service
import { toHex } from '@btc-vision/bitcoin';

class EpochService {
    constructor(private provider: JSONRpcProvider) {}

    async getLatest(includeSubmissions: boolean = false): Promise<Epoch> {
        return this.provider.getLatestEpoch(includeSubmissions);
    }

    async getByNumber(epochNumber: bigint): Promise<Epoch | EpochWithSubmissions> {
        return this.provider.getEpochByNumber(epochNumber);
    }

    async getByHash(hash: string): Promise<Epoch | EpochWithSubmissions> {
        return this.provider.getEpochByHash(hash);
    }

    async getCurrentEpochNumber(): Promise<bigint> {
        const latest = await this.getLatest();
        return latest.epochNumber;
    }

    async getEpochRange(
        startEpoch: bigint,
        endEpoch: bigint
    ): Promise<Epoch[]> {
        const epochs: Epoch[] = [];

        for (let i = startEpoch; i <= endEpoch; i++) {
            const epoch = await this.getByNumber(i);
            epochs.push(epoch);
        }

        return epochs;
    }

    async getProposerInfo(epochNumber: bigint): Promise<{
        publicKey: string;
        solution: string;
        salt: string;
        graffiti?: string;
    }> {
        const epoch = await this.getByNumber(epochNumber);
        const proposer = epoch.proposer;

        return {
            publicKey: proposer.publicKey.toHex(),
            solution: toHex(proposer.solution),
            salt: toHex(proposer.salt),
            graffiti: proposer.graffiti
                ? new TextDecoder().decode(proposer.graffiti)
                : undefined,
        };
    }

    async isEpochFinalized(epochNumber: bigint): Promise<boolean> {
        try {
            const epoch = await this.getByNumber(epochNumber);
            return epoch.proposer !== undefined;
        } catch {
            return false;
        }
    }
}

// Usage
const epochService = new EpochService(provider);

const current = await epochService.getCurrentEpochNumber();
console.log('Current epoch:', current);

const epoch = await epochService.getByNumber(current);
console.log('Proposer:', (await epochService.getProposerInfo(current)).publicKey);

const isFinalized = await epochService.isEpochFinalized(current);
console.log('Finalized:', isFinalized);