PoW Challenges
Overview
OP_NET transactions require a proof-of-work (PoW) challenge to be solved and included when signing. This mechanism provides additional security by preventing transaction spam and ensuring that submitters expend computational resources before broadcasting. The provider exposes methods to retrieve the current challenge parameters, which are automatically integrated into the transaction signing process by the library.
Fetching the Current Challenge
Using getChallenge()
The getChallenge() method retrieves the current proof-of-work challenge parameters from the network. This method returns the challenge data required for transaction signing, including the difficulty target and any associated metadata. The challenge is typically fetched automatically by the library when preparing a transaction.
The returned Challenge object contains the challenge identifier, difficulty level, and expiration information. Applications that require manual challenge management can use this method to fetch and cache challenges, though most use cases rely on the library's automatic handling during transaction submission.
Method Signature
async getChallenge(): Promise<ChallengeSolution>ChallengeSolution Structure
The ChallengeSolution interface represents a solved proof-of-work challenge. The preimage field contains the solution data that satisfies the difficulty requirement, while reward indicates the incentive for solving. The optional difficulty field specifies the target difficulty level, and version identifies the challenge protocol version for compatibility.
interface ChallengeSolution {
preimage: Uint8Array; // Challenge preimage data
reward: bigint; // Reward for solving
difficulty?: bigint; // Current difficulty
version?: number; // Challenge version
}ProofOfWorkChallenge Structure in Transactions
When a transaction is processed, it includes the solved proof-of-work challenge as part of its payload. The ProofOfWorkChallenge interface mirrors the ChallengeSolution structure, containing the preimage solution, reward amount, and optional difficulty and version fields. The library automatically attaches this data during transaction signing, ensuring valid challenge inclusion without manual intervention.
interface ProofOfWorkChallenge {
preimage: Uint8Array; // Challenge preimage
reward: bigint; // Reward amount
difficulty?: bigint; // Difficulty level
version?: number; // Version number
}Basic Challenge Query
The following example demonstrates how to retrieve and inspect the current proof-of-work challenge. The challenge data includes the preimage, reward amount, difficulty level, and protocol version, providing all necessary information for challenge solving and transaction preparation.
import { JSONRpcProvider } from 'opnet';
import { networks, toHex } from '@btc-vision/bitcoin';
const network = networks.regtest;
const provider = new JSONRpcProvider({ url: 'https://regtest.opnet.org', network });
const challenge = await provider.getChallenge();
console.log('Challenge:');
console.log(' Preimage:', toHex(challenge.preimage));
console.log(' Reward:', challenge.reward);
console.log(' Difficulty:', challenge.difficulty);
console.log(' Version:', challenge.version);Access PoW from Transaction
Completed transactions include the proof-of-work data that was submitted during signing. The pow property on a transaction provides access to the preimage, reward, and difficulty values used, enabling verification and analysis of the challenge solution included in the transaction.
const tx = await provider.getTransaction(txHash);
if (tx.pow) {
console.log('Transaction PoW:');
console.log(' Preimage:', toHex(tx.pow.preimage));
console.log(' Reward:', tx.pow.reward);
console.log(' Difficulty:', tx.pow.difficulty);
}Challenge Validation
Challenges have a limited validity period and must be verified before use. If a challenge expires between retrieval and transaction submission, the transaction will be rejected. Applications should check challenge freshness before signing and be prepared to fetch a new challenge if the current one has expired. Implementing retry logic with fresh challenge retrieval ensures reliable transaction submission even during periods of high network latency.
Verify Challenge Freshness
Challenge validity can be verified by comparing a cached challenge against the current network challenge. If the preimages differ, the cached challenge has expired and a fresh one must be fetched before transaction submission. This check is useful when challenges are cached for performance optimization.
async function isChallengeValid(
provider: JSONRpcProvider,
challenge: ChallengeSolution
): Promise<boolean> {
// Get current challenge
const currentChallenge = await provider.getChallenge();
// Compare preimages
return challenge.preimage.length === currentChallenge.preimage.length &&
challenge.preimage.every((b, i) => b === currentChallenge.preimage[i]);
}
// Usage
const challenge = await provider.getChallenge();
// ... some time passes ...
const stillValid = await isChallengeValid(provider, challenge);
console.log('Challenge still valid:', stillValid);
}Handle Expired Challenges
When a challenge expires during transaction processing, the operation should be retried with a fresh challenge. Implementing a retry wrapper that catches challenge-related errors and automatically fetches new challenge data ensures robust transaction submission without manual intervention.
async function executeWithFreshChallenge<T>(
provider: JSONRpcProvider,
operation: (challenge: ChallengeSolution) => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const challenge = await provider.getChallenge();
return await operation(challenge);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : '';
if (errorMessage.includes('challenge') && attempt < maxRetries) {
console.log(`Challenge expired, retrying (${attempt}/${maxRetries})...`);
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
// Usage
const result = await executeWithFreshChallenge(provider, async (challenge) => {
// Use challenge in transaction
const simulation = await contract.transfer(recipient, amount, new Uint8Array(0));
return simulation;
});Difficulty Analysis
Challenge difficulty fluctuates based on network conditions and can impact transaction processing time. Tracking difficulty over time enables applications to identify optimal periods for transaction submission and estimate solving times. The following example demonstrates a utility class that records difficulty samples and calculates averages for trend analysis.
interface DifficultyRecord {
timestamp: number;
difficulty: bigint;
reward: bigint;
}
class DifficultyTracker {
private records: DifficultyRecord[] = [];
async recordCurrent(provider: JSONRpcProvider): Promise<void> {
const challenge = await provider.getChallenge();
this.records.push({
timestamp: Date.now(),
difficulty: challenge.difficulty ?? 0n,
reward: challenge.reward,
});
// Keep last 100 records
if (this.records.length > 100) {
this.records.shift();
}
}
getAverageDifficulty(): bigint {
if (this.records.length === 0) return 0n;
const sum = this.records.reduce(
(acc, record) => acc + record.difficulty,
0n
);
return sum / BigInt(this.records.length);
}
getAverageReward(): bigint {
if (this.records.length === 0) return 0n;
const sum = this.records.reduce(
(acc, record) => acc + record.reward,
0n
);
return sum / BigInt(this.records.length);
}
getRecords(): DifficultyRecord[] {
return [...this.records];
}
}
// Usage
const tracker = new DifficultyTracker();
// Record periodically
await tracker.recordCurrent(provider);
console.log('Average difficulty:', tracker.getAverageDifficulty());
console.log('Average reward:', tracker.getAverageReward());Challenge Monitoring
Applications can monitor for challenge changes by polling the network at regular intervals and comparing preimages. When a new challenge is detected, the callback is invoked with the updated challenge data. This pattern is useful for applications that maintain cached challenges or need to react to difficulty adjustments in real-time.
async function monitorChallenges(
provider: JSONRpcProvider,
onNewChallenge: (challenge: ChallengeSolution) => void,
intervalMs: number = 10000
): Promise<() => void> {
let lastPreimage: string | null = null;
const intervalId = setInterval(async () => {
try {
const challenge = await provider.getChallenge();
const preimageHex = toHex(challenge.preimage);
if (preimageHex !== lastPreimage) {
lastPreimage = preimageHex;
onNewChallenge(challenge);
}
} catch (error) {
console.error('Error fetching challenge:', error);
}
}, intervalMs);
return () => clearInterval(intervalId);
}
// Usage
const stopMonitoring = await monitorChallenges(provider, (challenge) => {
console.log('New challenge detected!');
console.log(' Reward:', challenge.reward);
console.log(' Difficulty:', challenge.difficulty);
});
// Later: stop monitoring
// stopMonitoring();Complete Challenge Service
class ChallengeService {
private provider: JSONRpcProvider;
private cache: { challenge: ChallengeSolution; expireAt: number } | null = null;
private cacheDuration: number = 30000; // 30 seconds
constructor(provider: JSONRpcProvider) {
this.provider = provider;
}
async getChallenge(): Promise<ChallengeSolution> {
if (this.cache && Date.now() < this.cache.expireAt) {
return this.cache.challenge;
}
const challenge = await this.provider.getChallenge();
this.cache = {
challenge,
expireAt: Date.now() + this.cacheDuration,
};
return challenge;
}
async getReward(): Promise<bigint> {
const challenge = await this.getChallenge();
return challenge.reward;
}
async getDifficulty(): Promise<bigint> {
const challenge = await this.getChallenge();
return challenge.difficulty ?? 0n;
}
async getPreimage(): Promise<Uint8Array> {
const challenge = await this.getChallenge();
return challenge.preimage;
}
async isExpired(): Promise<boolean> {
if (!this.cache) return true;
return Date.now() >= this.cache.expireAt;
}
invalidate(): void {
this.cache = null;
}
setCacheDuration(ms: number): void {
this.cacheDuration = ms;
}
}
// Usage
const challengeService = new ChallengeService(provider);
// Get current challenge
const challenge = await challengeService.getChallenge();
console.log('Challenge reward:', challenge.reward);
// Get specific properties
const reward = await challengeService.getReward();
const difficulty = await challengeService.getDifficulty();
console.log('Reward:', reward);
console.log('Difficulty:', difficulty);
// Check expiration
if (await challengeService.isExpired()) {
console.log('Challenge cache expired');
}