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.

Getting the current block number
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 });

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

Getting Block by Number

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

Getting a block by number
typescript
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.

Getting a block by hash
typescript
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.

Getting a block by checksum
typescript
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.

Getting a block with Transactions
typescript
// 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.

Getting multiple blocks
typescript
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.

Getting deployed contracts in block
typescript
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.

Getting gas usage in block
typescript
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.

Find a block by time
typescript
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.

Polling for a new block
typescript
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

Block service example
typescript
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);