Block Witnesses

Overview

Block witnesses provide cryptographic proofs that verify block validity on the OP_NET network. Each witness attests that the transactions and state changes within a block have been validated according to the protocol rules. Witnesses can be categorized as trusted, originating from known validators, or untrusted, requiring additional verification. The provider exposes methods to retrieve and analyze witness data, enabling applications to verify block integrity and monitor network consensus.

Fetching Block Witnesses

Using getBlockWitness()

The getBlockWitness() method retrieves witnesses for a specific block. The height parameter specifies the block number, with -1 returning witnesses for the latest block. Optional parameters allow filtering by trust status, limiting the number of results, and paginating through large witness sets. This flexibility supports both quick validation checks and comprehensive witness analysis.

The method returns a BlockWitnesses array, where each element contains the block number and its associated witnesses. Each witness includes a trust indicator, cryptographic signature, timestamp, and Merkle proofs for verification. Trusted witnesses may also include the validator's identity and public key for additional authentication.

Method Signature

getBlockWitness() signature
typescript
async getBlockWitness(
    height?: BigNumberish,  // Block height (-1 for latest)
    trusted?: boolean,      // Filter trusted only
    limit?: number,         // Max witnesses to return
    page?: number           // Pagination offset
): Promise<BlockWitnesses>

BlockWitness Structure

BlockWitness structure
typescript
// BlockWitnesses is an array of IBlockWitness
type BlockWitnesses = readonly IBlockWitness[];

interface IBlockWitness {
    blockNumber: bigint;
    readonly witnesses: readonly IBlockWitnessAPI[];
}

interface IBlockWitnessAPI {
    readonly trusted: boolean;           // Is trusted validator
    readonly signature: Uint8Array;      // Witness signature
    readonly timestamp: number;          // When witnessed
    readonly proofs: readonly Uint8Array[]; // Merkle proofs
    readonly identity?: Uint8Array;      // Validator identity
    readonly publicKey?: Address;        // Validator public key
}

Basic Witness Query

Basic witness 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 });

// getBlockWitness returns BlockWitnesses (readonly IBlockWitness[])
const blockWitnesses = await provider.getBlockWitness(123456n);

for (const bw of blockWitnesses) {
    console.log('Block:', bw.blockNumber);
    console.log('Witness count:', bw.witnesses.length);
}

Witness Query With Options

Witness query using options
typescript
// Get only trusted witnesses
const trustedWitnesses = await provider.getBlockWitness(
    123456n,
    true,     // trusted only
    100,      // limit
    0         // page
);

// Get all witnesses (including untrusted)
const allWitnesses = await provider.getBlockWitness(
    123456n,
    false,    // include untrusted
    100,
    0
);

Witness Query With Pagination

Fetch all witnesses using pagination
typescript
async function getAllWitnesses(
    provider: JSONRpcProvider,
    blockNumber: bigint,
    pageSize: number = 100
): Promise<IBlockWitnessAPI[]> {
    const allWitnesses: IBlockWitnessAPI[] = [];
    let page = 0;

    while (true) {
        const blockWitnesses = await provider.getBlockWitness(
            blockNumber,
            false,
            pageSize,
            page
        );

        const pageWitnesses = blockWitnesses.flatMap(bw => [...bw.witnesses]);
        allWitnesses.push(...pageWitnesses);

        if (pageWitnesses.length < pageSize) {
            break;
        }

        page++;
    }

    return allWitnesses;
}

// Usage
const allWitnesses = await getAllWitnesses(provider, 123456n);
console.log('Total witnesses:', allWitnesses.length);

Working with Witnesses

Check if Block is Witnessed

To determine whether a block has been witnessed, query its witnesses and check if any exist. This verification is useful for confirming block finality and ensuring that a block has received validation from the network before relying on its data.

Check if block is witnessed
typescript
async function isBlockWitnessed(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<boolean> {
    try {
        const blockWitnesses = await provider.getBlockWitness(blockNumber);
        return blockWitnesses.some(bw => bw.witnesses.length > 0);
    } catch {
        return false;
    }
}

// Usage
const witnessed = await isBlockWitnessed(provider, 123456n);
console.log('Block witnessed:', witnessed);

Get Trusted Witness Count

Trusted witnesses originate from known validators. The following example demonstrates how to count trusted witnesses for a specific block by filtering the witness array based on the trusted property.

Get trusted witness count
typescript
async function getTrustedWitnessCount(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<number> {
    const blockWitnesses = await provider.getBlockWitness(blockNumber);

    let count = 0;
    for (const bw of blockWitnesses) {
        count += bw.witnesses.filter(w => w.trusted).length;
    }
    return count;
}

// Usage
const trustedCount = await getTrustedWitnessCount(provider, 123456n);
console.log('Trusted witnesses:', trustedCount);

Validate Minimum Witnesses

Applications requiring strong finality guarantees can verify that a block has received a minimum number of trusted witnesses before considering it confirmed. If the witness count falls below the threshold, the block may not yet be final and additional confirmations should be awaited.

Validate a minimum witnesses count
typescript
async function hasMinimumWitnesses(
    provider: JSONRpcProvider,
    blockNumber: bigint,
    minTrusted: number = 1
): Promise<boolean> {
    const count = await getTrustedWitnessCount(provider, blockNumber);
    return count >= minTrusted;
}

// Usage
const hasEnough = await hasMinimumWitnesses(provider, 123456n, 3);
if (!hasEnough) {
    console.log('Block may not be final yet');
}

Witness Analysis

Analyze Witness Details

For comprehensive block validation, witnesses can be analyzed to extract detailed metrics including trust distribution, validator identities, and timestamp information. The following example demonstrates how to aggregate witness data and categorize witnesses by trust status, providing insight into the validation strength of a specific block.

Analyze witness details
typescript
async function analyzeWitnesses(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<{
    total: number;
    trusted: number;
    untrusted: number;
    validators: Address[];
    timestamps: number[];
}> {
    const blockWitnesses = await provider.getBlockWitness(blockNumber, false);

    // Flatten all witnesses from all blocks in the result
    const allWitnesses = blockWitnesses.flatMap(bw => [...bw.witnesses]);

    const trusted = allWitnesses.filter(w => w.trusted);
    const untrusted = allWitnesses.filter(w => !w.trusted);

    const validators = allWitnesses
        .filter(w => w.publicKey)
        .map(w => w.publicKey!);

    const timestamps = allWitnesses.map(w => w.timestamp);

    return {
        total: allWitnesses.length,
        trusted: trusted.length,
        untrusted: untrusted.length,
        validators,
        timestamps,
    };
}

// Usage
const analysis = await analyzeWitnesses(provider, 123456n);
console.log('Witness Analysis:');
console.log('  Total:', analysis.total);
console.log('  Trusted:', analysis.trusted);
console.log('  Untrusted:', analysis.untrusted);
console.log('  Validators:', analysis.validators.length);

Get Witness Timing

Witness timestamps indicate when validators attested to a block's validity. Analyzing the time span between the first and last witness provides insight into network propagation speed and validator responsiveness. The following example extracts timing metrics from witness data, useful for monitoring network health and consensus performance.

Get witness timing
typescript
async function getWitnessTiming(
    provider: JSONRpcProvider,
    blockNumber: bigint
): Promise<{
    firstWitness: number;
    lastWitness: number;
    timespanMs: number;
}> {
    const blockWitnesses = await provider.getBlockWitness(blockNumber);
    const allWitnesses = blockWitnesses.flatMap(bw => [...bw.witnesses]);

    if (allWitnesses.length === 0) {
        throw new Error('No witnesses found');
    }

    const timestamps = allWitnesses.map(w => w.timestamp);
    const firstWitness = Math.min(...timestamps);
    const lastWitness = Math.max(...timestamps);

    return {
        firstWitness,
        lastWitness,
        timespanMs: (lastWitness - firstWitness) * 1000,
    };
}

// Usage
const timing = await getWitnessTiming(provider, 123456n);
console.log('First witness:', new Date(timing.firstWitness * 1000));
console.log('Last witness:', new Date(timing.lastWitness * 1000));

Witness Verification

Verify Witness Signature

Each witness includes a cryptographic signature that can be verified against the block hash to ensure authenticity. Signature verification confirms that the witness was genuinely produced by the claimed validator and has not been tampered with. The following example illustrates a conceptual verification approach; actual implementation depends on the signature scheme used (Schnorr, ECDSA, or ML-DSA).

Conceptual witness verification
typescript
// Note: Actual verification depends on your crypto library
async function verifyWitnessSignature(
    witness: BlockWitnessAPI,
    blockHash: string
): Promise<boolean> {
    if (!witness.publicKey) {
        return false;
    }

    // This is conceptual - actual verification depends on
    // the signature scheme used (Schnorr, ECDSA, etc.)
    try {
        // Verify signature against block hash
        // const valid = crypto.verify(
        //     witness.publicKey,
        //     witness.signature,
        //     fromHex(blockHash)
        // );
        // return valid;

        return witness.trusted; // Simplified for trusted witnesses
    } catch {
        return false;
    }
}

Check Block Finality

Block finality indicates whether a block has received sufficient witness attestations to be considered irreversible. By comparing the number of trusted witnesses against a minimum threshold, applications can determine finality status and calculate a confidence percentage. The following example demonstrates a finality check that returns both the status and a confidence score based on witness count.

Check block finality
typescript
interface FinalityStatus {
    isFinalized: boolean;
    trustedWitnesses: number;
    minimumRequired: number;
    confidence: number;
}

async function checkBlockFinality(
    provider: JSONRpcProvider,
    blockNumber: bigint,
    minWitnesses: number = 3
): Promise<FinalityStatus> {
    const blockWitnesses = await provider.getBlockWitness(blockNumber);
    const allWitnesses = blockWitnesses.flatMap(bw => [...bw.witnesses]);
    const trustedCount = allWitnesses.filter(w => w.trusted).length;

    const isFinalized = trustedCount >= minWitnesses;
    const confidence = Math.min(100, (trustedCount / minWitnesses) * 100);

    return {
        isFinalized,
        trustedWitnesses: trustedCount,
        minimumRequired: minWitnesses,
        confidence: Math.round(confidence),
    };
}

// Usage
const finality = await checkBlockFinality(provider, 123456n);
console.log('Block finality:', finality.isFinalized ? 'FINAL' : 'PENDING');
console.log(`Confidence: ${finality.confidence}%`);

Complete Witness Service

Complete witness service
typescript
class WitnessService {
    constructor(private provider: JSONRpcProvider) {}

    async getWitnesses(
        blockNumber: bigint,
        trustedOnly: boolean = false
    ): Promise<IBlockWitnessAPI[]> {
        const blockWitnesses = await this.provider.getBlockWitness(
            blockNumber,
            trustedOnly
        );
        return blockWitnesses.flatMap(bw => [...bw.witnesses]);
    }

    async getWitnessCount(
        blockNumber: bigint
    ): Promise<{ total: number; trusted: number }> {
        const witnesses = await this.getWitnesses(blockNumber);

        return {
            total: witnesses.length,
            trusted: witnesses.filter(w => w.trusted).length,
        };
    }

    async isFinalized(
        blockNumber: bigint,
        minTrusted: number = 1
    ): Promise<boolean> {
        const { trusted } = await this.getWitnessCount(blockNumber);
        return trusted >= minTrusted;
    }

    async waitForFinality(
        blockNumber: bigint,
        minTrusted: number = 1,
        timeoutMs: number = 60000
    ): Promise<boolean> {
        const startTime = Date.now();

        while (Date.now() - startTime < timeoutMs) {
            if (await this.isFinalized(blockNumber, minTrusted)) {
                return true;
            }

            await new Promise(r => setTimeout(r, 5000));
        }

        return false;
    }

    async getValidators(blockNumber: bigint): Promise<Address[]> {
        const witnesses = await this.getWitnesses(blockNumber, true);
        return witnesses
            .filter(w => w.publicKey)
            .map(w => w.publicKey!);
    }
}

// Usage
const witnessService = new WitnessService(provider);

const counts = await witnessService.getWitnessCount(123456n);
console.log('Witnesses:', counts.total, '(trusted:', counts.trusted + ')');

const finalized = await witnessService.isFinalized(123456n);
console.log('Finalized:', finalized);

// Wait for block to be finalized
const success = await witnessService.waitForFinality(123456n, 3, 30000);
console.log('Finality achieved:', success);