UNPKG

opnet

Version:

The perfect library for building Bitcoin-based applications.

487 lines (366 loc) 12.3 kB
# Epoch Operations This guide covers fetching and working with epochs on OPNet. ## Overview Epochs can be fetched by number, hash, or you can retrieve the latest epoch. Each epoch contains consensus data about a period of blocks. ```mermaid flowchart LR A[Query] --> B{By what?} B -->|Number| C[getEpochByNumber] B -->|Hash| D[getEpochByHash] B -->|Latest| E[getLatestEpoch] C --> F[Epoch] D --> F E --> F ``` --- ## Get Latest Epoch ### Basic Query ```typescript 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 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)); } } ``` ### Method Signature ```typescript async getLatestEpoch( includeSubmissions: boolean // Include all submissions ): Promise<Epoch> ``` --- ## Get Epoch by Number ```typescript 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); ``` ### Method Signature ```typescript async getEpochByNumber( epochNumber: BigNumberish, // Epoch sequence number includeSubmissions: boolean = false // Include all submissions ): Promise<Epoch | EpochWithSubmissions> ``` --- ## Get Epoch by Hash ```typescript // 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); ``` ### Method Signature ```typescript async getEpochByHash( epochHash: string, // Unique epoch hash as string includeSubmissions: boolean = false // Include all submissions ): Promise<Epoch | EpochWithSubmissions> ``` --- ## Epoch Class Reference ```typescript 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 Reference The proposer who won the epoch: ```typescript 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) } ``` --- ## Working with Epochs ### Get Epoch Block Count ```typescript 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 ```typescript 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 ```typescript 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}`); } ``` --- ## Epoch Analysis ### Analyze Epoch Difficulty ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 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); ``` --- ## Best Practices 1. **Cache Epoch Data**: Finalized epochs don't change, safe to cache 2. **Check Finalization**: Verify epoch has a proposer before relying on data 3. **Handle Missing Epochs**: Future epoch numbers will throw errors 4. **Use Submissions Sparingly**: Only fetch submissions when needed (larger response) 5. **Batch Requests**: Fetch multiple epochs in parallel when possible --- ## Next Steps - [Epoch Overview](./overview.md) - Understanding epochs - [Mining Template](./mining-template.md) - Mining requirements - [Submitting Epochs](./submitting-epochs.md) - Solution submission --- [ Previous: Epoch Overview](./overview.md) | [Next: Mining Template ](./mining-template.md)