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.
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.
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
async getLatestEpoch(
includeSubmissions: boolean // Include all submissions
): Promise<Epoch>
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
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
async getEpochByNumber(
epochNumber: BigNumberish, // Epoch sequence number
includeSubmissions: boolean = false // Include all submissions
): Promise<Epoch | EpochWithSubmissions>
Get 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
async getEpochByHash(
epochHash: string, // Unique epoch hash as string
includeSubmissions: boolean = false // Include all submissions
): Promise<Epoch | EpochWithSubmissions>Get 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.
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.
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.
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.
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.
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.
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.
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
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);