Block Operations

Overview

The provider exposes methods for retrieving and analyzing block data from the OP_NET network. Blocks can be fetched individually by number, hash, or checksum, or in batches for efficient multi-block queries. Each block contains height, timestamp, transaction data, and network metadata, enabling applications to track blockchain state, analyze historical data, and monitor for new blocks.

Fetching Blocks

Getting Current Block Number

The getBlockNumber() method returns the latest block height on the network. This is useful for tracking chain progress, calculating confirmations, and determining the current state of the blockchain.

typescript
Getting the current block number
import { JSONRpcProvider } from 'opnet';
import { networks } from '@btc-vision/bitcoin';

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

const blockNumber = await provider.getBlockNumber();
console.log('Current block:', blockNumber);

Getting Block by Number

The getBlock() method retrieves a block by its height.

typescript
Getting a block by number
const block = await provider.getBlock(123456);

console.log('Block info:');
console.log('  Height:', block.height);
console.log('  Hash:', block.hash);
console.log('  Time:', new Date(block.time * 1000));
console.log('  TX count:', block.txCount);

Getting Block by Hash

Blocks can also be retrieved by their hash using the getBlockByHash() method. This is useful when referencing a specific block from transaction receipts or external data sources.

typescript
Getting a block by hash
const block = await provider.getBlockByHash(
    '00000000000000000001a2b3c4d5e6f...'
);

console.log('Block height:', block.height);

Getting Block by Checksum

The getBlockByChecksum() method retrieves a block using its OP_NET checksum. Checksums provide an additional verification mechanism specific to the OP_NET protocol, ensuring block integrity beyond the standard Bitcoin block hash.

typescript
Getting a block by checksum
const block = await provider.getBlockByChecksum(
    '0xabcdef123456...'
);

console.log('Block:', block.height);

Getting Block with Transactions

By default, block retrieval returns only metadata without transaction details. Setting the prefetchTxs parameter to true includes the full transaction data in the response, enabling immediate access to all transactions within the block without additional queries.

typescript
Getting a block with Transactions
// Prefetch transactions for faster access
const block = await provider.getBlock(123456, true);

console.log('Transactions:', block.transactions.length);
for (const tx of block.transactions) {
    console.log('  TX:', tx.hash);
}

Fetching Multiple Blocks

The getBlocks() method retrieves multiple blocks in a single request, accepting an array of block numbers or hashes. This approach significantly reduces network overhead compared to fetching blocks individually, making it ideal for batch processing and historical data analysis.

typescript
Getting multiple blocks
const blockNumbers = [100, 101, 102, 103, 104];
const blocks = await provider.getBlocks(blockNumbers);

for (const block of blocks) {
    console.log(`Block ${block.height}: ${block.txCount} transactions`);
}

Working with Block Data

Getting Deployed Contracts in Block

Each block contains a list of contracts deployed during that block's execution. The deployments property provides an array of contract addresses, enabling applications to track new deployments and index contract creation events across the network.

typescript
Getting deployed contracts in block
async function getDeploymentsInBlock(
    provider: JSONRpcProvider,
    blockNumber: number
): Promise<Address[]> {
    const block = await provider.getBlock(blockNumber, true);
    return block.deployments;
}

// Usage
const deployments = await getDeploymentsInBlock(provider, 123456);
console.log('Deployed contracts:', deployments.length);
for (const addr of deployments) {
    console.log('  Contract:', addr.toHex());
}

Analyze Block Gas Usage

Each block contains gas metrics that provide insight into network utilization and fee dynamics. The gasUsed property indicates the total gas consumed by all transactions in the block, while baseGas reflects the minimum gas price for inclusion. The ema (exponential moving average) tracks gas usage trends over time, useful for fee estimation and network congestion analysis.

typescript
Getting gas usage in block
async function analyzeBlockGas(
    provider: JSONRpcProvider,
    blockNumber: number
): Promise<{
    gasUsed: bigint;
    baseGas: bigint;
    ema: bigint;
}> {
    const block = await provider.getBlock(blockNumber);

    return {
        gasUsed: block.gasUsed,
        baseGas: block.baseGas,
        ema: block.ema,
    };
}

// Usage
const gasInfo = await analyzeBlockGas(provider, 123456);
console.log('Gas used:', gasInfo.gasUsed);
console.log('Base gas:', gasInfo.baseGas);
console.log('EMA:', gasInfo.ema);

Find Block by Time

The provider does not offer a direct method to retrieve a block by timestamp. However, a binary search algorithm can efficiently locate the block closest to a target time by comparing block timestamps. This approach is useful for historical analysis, event correlation, and time-based data retrieval.

typescript
Find a block by time
async function findBlockByTime(
    provider: JSONRpcProvider,
    targetTime: Date
): Promise<Block | null> {
    const targetTimestamp = Math.floor(targetTime.getTime() / 1000);
    const currentBlock = await provider.getBlockNumber();

    // Binary search for block
    let low = 1n;
    let high = currentBlock;

    while (low <= high) {
        const mid = (low + high) / 2n;
        const block = await provider.getBlock(mid);

        if (block.time === targetTimestamp) {
            return block;
        } else if (block.time < targetTimestamp) {
            low = mid + 1n;
        } else {
            high = mid - 1n;
        }
    }

    // Return closest block
    return provider.getBlock(low);
}

Block Monitoring

For near real-time block monitoring, use the polling approach. The getBlockNumber() method can be called periodically to detect new blocks and fetch them as they arrive.

typescript
Polling for a new block
async function monitorBlocks(
    provider: JSONRpcProvider,
    callback: (block: Block) => void,
    intervalMs: number = 10000
): Promise<() => void> {
    let lastBlock = await provider.getBlockNumber();

    const intervalId = setInterval(async () => {
        try {
            const currentBlock = await provider.getBlockNumber();

            if (currentBlock > lastBlock) {
                // Fetch new blocks
                for (let i = lastBlock + 1n; i <= currentBlock; i++) {
                    const block = await provider.getBlock(i);
                    callback(block);
                }
                lastBlock = currentBlock;
            }
        } catch (error) {
            console.error('Error monitoring blocks:', error);
        }
    }, intervalMs);

    return () => clearInterval(intervalId);
}

// Usage
const stopMonitoring = await monitorBlocks(provider, (block) => {
    console.log(`New block: ${block.height} with ${block.txCount} transactions`);
});

// Later: stop monitoring
// stopMonitoring();

Complete Block Service

typescript
Block service example
class BlockService {
    private provider: JSONRpcProvider;
    private blockCache: Map<string, Block> = new Map();

    constructor(provider: JSONRpcProvider) {
        this.provider = provider;
    }

    async getCurrentHeight(): Promise<bigint> {
        return this.provider.getBlockNumber();
    }

    async getBlock(blockNumberOrHash: number | string): Promise<Block> {
        const cacheKey = String(blockNumberOrHash);

        if (this.blockCache.has(cacheKey)) {
            return this.blockCache.get(cacheKey)!;
        }

        const block = await this.provider.getBlock(blockNumberOrHash);
        this.blockCache.set(cacheKey, block);
        this.blockCache.set(block.hash, block);

        return block;
    }

    async getBlocksInRange(
        start: number,
        end: number
    ): Promise<Block[]> {
        const numbers: number[] = [];
        for (let i = start; i <= end; i++) {
            numbers.push(i);
        }
        return this.provider.getBlocks(numbers);
    }

    async getLatestBlocks(count: number): Promise<Block[]> {
        const current = await this.getCurrentHeight();
        const start = Number(current) - count + 1;
        return this.getBlocksInRange(Math.max(1, start), Number(current));
    }

    async getBlockTransactions(
        blockNumber: number
    ): Promise<TransactionBase[]> {
        const block = await this.provider.getBlock(blockNumber, true);
        return block.transactions;
    }

    async getBlockDeployments(blockNumber: number): Promise<Address[]> {
        const block = await this.provider.getBlock(blockNumber, true);
        return block.deployments;
    }

    clearCache(): void {
        this.blockCache.clear();
    }
}

// Usage
const blockService = new BlockService(provider);

const height = await blockService.getCurrentHeight();
console.log('Current height:', height);

const latestBlocks = await blockService.getLatestBlocks(10);
console.log('Latest 10 blocks:', latestBlocks.map(b => b.height));

const deployments = await blockService.getBlockDeployments(123456);
console.log('Deployments:', deployments.length);