UNPKG

opnet

Version:

The perfect library for building Bitcoin-based applications.

435 lines (326 loc) 11 kB
# PoW Challenges This guide covers proof-of-work challenges required for transaction signing on OPNet. ## Overview OPNet transactions require a proof-of-work (PoW) challenge to be included when signing. The challenge provides additional security and spam protection. ```mermaid flowchart LR A[Provider] --> B[getChallenge] B --> C[ChallengeSolution] C --> D[Preimage] C --> E[Reward] C --> F[Difficulty] D --> G[Transaction Signing] ``` --- ## Get Current Challenge ### Basic Query ```typescript 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); ``` ### Method Signature ```typescript async getChallenge(): Promise<ChallengeSolution> ``` --- ## ChallengeSolution Structure ```typescript interface ChallengeSolution { preimage: Uint8Array; // Challenge preimage data reward: bigint; // Reward for solving difficulty?: bigint; // Current difficulty version?: number; // Challenge version } ``` --- ## ProofOfWorkChallenge in Transactions When a transaction is processed, it may include PoW challenge data: ```typescript interface ProofOfWorkChallenge { preimage: Uint8Array; // Challenge preimage reward: bigint; // Reward amount difficulty?: bigint; // Difficulty level version?: number; // Version number } ``` ### Access PoW from Transaction ```typescript 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); } ``` --- ## Using Challenges in Transactions ### Include Challenge in Transaction ```typescript import { getContract } from 'opnet'; import { Wallet } from '@btc-vision/transaction'; // Get the current challenge const challenge = await provider.getChallenge(); // Set up contract const contract = getContract<IOP20Contract>( tokenAddress, OP_20_ABI, provider, network ); // The challenge is automatically fetched when sending transactions const simulation = await contract.transfer(recipientAddress, amount, new Uint8Array(0)); if (simulation.revert) { throw new Error(`Transfer would fail: ${simulation.revert}`); } const result = await simulation.sendTransaction({ signer: wallet.keypair, mldsaSigner: wallet.mldsaKeypair, refundTo: wallet.p2tr, feeRate: 10, network: network, maximumAllowedSatToSpend: 10000n, }); ``` ### Manual Challenge Management ```typescript class ChallengeManager { private provider: JSONRpcProvider; private cachedChallenge: ChallengeSolution | null = null; private cacheTime: number = 0; private cacheDuration: number = 30000; // 30 seconds constructor(provider: JSONRpcProvider) { this.provider = provider; } async getChallenge(): Promise<ChallengeSolution> { // Use cached challenge if still valid if (this.cachedChallenge && Date.now() - this.cacheTime < this.cacheDuration) { return this.cachedChallenge; } // Fetch new challenge this.cachedChallenge = await this.provider.getChallenge(); this.cacheTime = Date.now(); return this.cachedChallenge; } invalidateCache(): void { this.cachedChallenge = null; } } // Usage const challengeManager = new ChallengeManager(provider); const challenge = await challengeManager.getChallenge(); console.log('Challenge reward:', challenge.reward); ``` --- ## Challenge Validation ### Verify Challenge Freshness ```typescript 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 ```typescript 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 ### Track Difficulty Over Time ```typescript 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 ### Monitor Challenge Changes ```typescript 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 ```typescript 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'); } ``` --- ## Best Practices 1. **Cache Challenges**: Challenges can be cached for short periods 2. **Handle Expiration**: Always be ready to fetch a fresh challenge 3. **Monitor Difficulty**: Track difficulty for transaction planning 4. **Include in Transactions**: The library handles challenge inclusion automatically 5. **Error Handling**: Handle challenge-related errors gracefully --- ## Next Steps - [Fetching Transactions](./fetching-transactions.md) - Transaction data - [Transaction Receipts](./transaction-receipts.md) - Receipt handling - [Broadcasting](./broadcasting.md) - Sending transactions --- [← Previous: Transaction Receipts](./transaction-receipts.md) | [Next: Broadcasting →](./broadcasting.md)