@btc-stamps/tx-builder
Version:
Transaction builder for Bitcoin Stamps and SRC-20 tokens with advanced UTXO selection
625 lines (555 loc) • 17.7 kB
text/typescript
/**
* Parallel Algorithm Execution System
* Runs multiple UTXO selection algorithms in parallel for optimal results
*/
import type {
SelectionOptions,
SelectionResult,
SelectorAlgorithm,
UTXO,
} from '../interfaces/selector.interface.ts';
import { isSelectionSuccess } from '../interfaces/selector-result.interface.ts';
import { PerformanceAwareSelectorFactory } from './performance-aware-selector.ts';
import { PerformanceMonitor } from './performance-monitor.ts';
export interface ParallelSelectionConfig {
maxConcurrency: number; // Maximum number of algorithms to run simultaneously
timeoutMs: number; // Timeout for each algorithm
returnFirst: boolean; // Return first successful result or wait for all
enableRacing: boolean; // Enable racing mode (fastest wins)
qualityThreshold: number; // Minimum quality score to consider a result
enableFallback: boolean; // Enable fallback to sequential execution
}
export interface ParallelSelectionResult {
result: SelectionResult;
algorithm: SelectorAlgorithm;
executionTime: number;
allResults: Array<{
algorithm: SelectorAlgorithm;
result: SelectionResult | null;
executionTime: number;
error?: string;
}>;
totalExecutionTime: number;
winnerMetrics: {
wasteScore: number;
efficiencyScore: number;
qualityScore: number;
};
}
export interface AlgorithmResult {
algorithm: SelectorAlgorithm;
result: SelectionResult | null;
executionTime: number;
error?: string;
metrics: {
wasteScore: number;
efficiencyScore: number;
qualityScore: number;
};
}
/**
* Parallel UTXO selection system
*/
export class ParallelSelector {
private selectorFactory: PerformanceAwareSelectorFactory;
private performanceMonitor: PerformanceMonitor;
private config: Required<ParallelSelectionConfig>;
private workerPool: Worker[] = [];
private activeJobs = new Map<
string,
{
resolve: (value: AlgorithmResult) => void;
reject: (error: Error) => void;
timeout: number;
}
>();
constructor(
selectorFactory: PerformanceAwareSelectorFactory,
performanceMonitor: PerformanceMonitor,
config?: Partial<ParallelSelectionConfig>,
) {
this.selectorFactory = selectorFactory;
this.performanceMonitor = performanceMonitor;
this.config = {
maxConcurrency: 4,
timeoutMs: 5000,
returnFirst: false,
enableRacing: true,
qualityThreshold: 0.7,
enableFallback: true,
...config,
};
}
/**
* Select UTXOs using parallel algorithm execution
*/
async selectParallel(
utxos: UTXO[],
options: SelectionOptions,
algorithms?: SelectorAlgorithm[],
): Promise<ParallelSelectionResult | null> {
const startTime = Date.now();
// Determine algorithms to run
const selectedAlgorithms = algorithms ||
this.selectOptimalAlgorithms(utxos, options);
if (selectedAlgorithms.length === 0) {
return null;
}
// Limit concurrency
const algorithmsToRun = selectedAlgorithms.slice(
0,
this.config.maxConcurrency,
);
try {
// Run algorithms in parallel
const results = await this.runAlgorithmsParallel(
utxos,
options,
algorithmsToRun,
);
// Process results
const successfulResults = results.filter((r) => r.result !== null);
if (successfulResults.length === 0) {
// Try fallback if enabled
if (this.config.enableFallback) {
return await this.fallbackToSequential(
utxos,
options,
selectedAlgorithms,
);
}
return null;
}
// Select best result
const winner = this.selectBestResult(successfulResults);
const totalExecutionTime = Date.now() - startTime;
return {
result: winner.result!,
algorithm: winner.algorithm,
executionTime: winner.executionTime,
allResults: results,
totalExecutionTime,
winnerMetrics: winner.metrics,
};
} catch (error) {
console.error('Parallel selection failed:', error);
// Fallback to sequential if enabled
if (this.config.enableFallback) {
return await this.fallbackToSequential(
utxos,
options,
selectedAlgorithms,
);
}
return null;
}
}
/**
* Select UTXOs using racing mode (first successful result wins)
*/
async selectRacing(
utxos: UTXO[],
options: SelectionOptions,
algorithms?: SelectorAlgorithm[],
): Promise<ParallelSelectionResult | null> {
const startTime = Date.now();
const selectedAlgorithms = algorithms ||
this.selectOptimalAlgorithms(utxos, options);
if (selectedAlgorithms.length === 0) {
return null;
}
const algorithmsToRun = selectedAlgorithms.slice(
0,
this.config.maxConcurrency,
);
// Create promises for each algorithm
const algorithmPromises = algorithmsToRun.map((algorithm) =>
this.runAlgorithmWithTimeout(utxos, options, algorithm)
);
try {
// Wait for first successful result
const winner = await Promise.any(
algorithmPromises.map(async (promise, index) => {
const result = await promise;
if (result.result === null) {
throw new Error(`Algorithm ${algorithmsToRun[index]} failed`);
}
return result;
}),
);
// Wait a bit more to collect other results for comparison
const allResults = await Promise.allSettled(algorithmPromises);
const processedResults = allResults.map((settled, index) => {
if (settled.status === 'fulfilled') {
return settled.value;
} else {
return {
algorithm: algorithmsToRun[index]!,
result: null,
executionTime: this.config.timeoutMs,
error: settled.reason?.message || 'Unknown error',
metrics: {
wasteScore: Infinity,
efficiencyScore: 0,
qualityScore: 0,
},
};
}
});
const totalExecutionTime = Date.now() - startTime;
return {
result: winner.result!,
algorithm: winner.algorithm,
executionTime: winner.executionTime,
allResults: processedResults,
totalExecutionTime,
winnerMetrics: winner.metrics,
};
} catch (error) {
console.error('Racing selection failed:', error);
if (this.config.enableFallback) {
return await this.fallbackToSequential(
utxos,
options,
selectedAlgorithms,
);
}
return null;
}
}
/**
* Run multiple algorithms and compare results
*/
async benchmarkAlgorithms(
utxos: UTXO[],
options: SelectionOptions,
algorithms?: SelectorAlgorithm[],
): Promise<Array<AlgorithmResult>> {
const selectedAlgorithms = algorithms ||
this.selectOptimalAlgorithms(utxos, options);
return await this.runAlgorithmsParallel(utxos, options, selectedAlgorithms);
}
/**
* Select optimal algorithms for the given scenario
*/
private selectOptimalAlgorithms(
utxos: UTXO[],
options: SelectionOptions,
): SelectorAlgorithm[] {
const scenario = {
utxoCount: utxos.length,
targetValue: options.targetValue,
feeRate: options.feeRate,
dustThreshold: options.dustThreshold,
};
// Get performance-based recommendations
const recommended = this.selectorFactory.getRecommendedAlgorithm(scenario);
const parallel = this.selectorFactory.getParallelSelectors(
scenario,
this.config.maxConcurrency,
);
// Combine recommended with parallel suggestions
const algorithms: SelectorAlgorithm[] = [recommended];
for (const selector of parallel) {
const algorithmName = selector
.getName()
.replace('performance-aware-', '') as SelectorAlgorithm;
if (!algorithms.includes(algorithmName)) {
algorithms.push(algorithmName);
}
}
return algorithms;
}
/**
* Run algorithms in parallel
*/
private async runAlgorithmsParallel(
utxos: UTXO[],
options: SelectionOptions,
algorithms: SelectorAlgorithm[],
): Promise<AlgorithmResult[]> {
const promises = algorithms.map((algorithm) =>
this.runAlgorithmWithTimeout(utxos, options, algorithm)
);
const results = await Promise.allSettled(promises);
return results.map((settled, index) => {
if (settled.status === 'fulfilled') {
return settled.value;
} else {
return {
algorithm: algorithms[index]!,
result: null,
executionTime: this.config.timeoutMs,
error: settled.reason?.message || 'Unknown error',
metrics: {
wasteScore: Infinity,
efficiencyScore: 0,
qualityScore: 0,
},
};
}
});
}
/**
* Run single algorithm with timeout
*/
private runAlgorithmWithTimeout(
utxos: UTXO[],
options: SelectionOptions,
algorithm: SelectorAlgorithm,
): Promise<AlgorithmResult> {
return new Promise((resolve) => {
const startTime = Date.now();
const selector = this.selectorFactory.create(algorithm);
const timeout = setTimeout(() => {
resolve({
algorithm,
result: null,
executionTime: this.config.timeoutMs,
error: 'Timeout',
metrics: {
wasteScore: Infinity,
efficiencyScore: 0,
qualityScore: 0,
},
});
}, this.config.timeoutMs);
try {
// Use setTimeout to make it async
setTimeout(() => {
try {
const result = selector.select(utxos, options);
const executionTime = Date.now() - startTime;
clearTimeout(timeout);
const convertedResult = result.success ? result : null;
const metrics = this.calculateMetrics(convertedResult, utxos, options);
resolve({
algorithm,
result: convertedResult,
executionTime,
metrics,
});
} catch (error) {
clearTimeout(timeout);
resolve({
algorithm,
result: null,
executionTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error',
metrics: {
wasteScore: Infinity,
efficiencyScore: 0,
qualityScore: 0,
},
});
}
}, 0);
} catch (error) {
clearTimeout(timeout);
resolve({
algorithm,
result: null,
executionTime: Date.now() - startTime,
error: error instanceof Error ? error.message : 'Unknown error',
metrics: {
wasteScore: Infinity,
efficiencyScore: 0,
qualityScore: 0,
},
});
}
});
}
/**
* Select best result from multiple successful results
*/
private selectBestResult(results: AlgorithmResult[]): AlgorithmResult {
if (results.length === 1) {
return results[0]!;
}
// Filter by quality threshold
const qualityResults = results.filter(
(r) => r.metrics.qualityScore >= this.config.qualityThreshold,
);
if (qualityResults.length === 0) {
// If no results meet quality threshold, pick the best available
return results.reduce((best, current) =>
current.metrics.qualityScore > best.metrics.qualityScore ? current : best
);
}
// Among quality results, pick the most efficient
return qualityResults.reduce((best, current) => {
// Weighted scoring: quality (40%), efficiency (30%), waste (30%)
const bestScore = best.metrics.qualityScore * 0.4 +
best.metrics.efficiencyScore * 0.3 +
(1 / (1 + best.metrics.wasteScore)) * 0.3;
const currentScore = current.metrics.qualityScore * 0.4 +
current.metrics.efficiencyScore * 0.3 +
(1 / (1 + current.metrics.wasteScore)) * 0.3;
return currentScore > bestScore ? current : best;
});
}
/**
* Calculate quality metrics for a result
*/
private calculateMetrics(
result: SelectionResult | null,
utxos: UTXO[],
options: SelectionOptions,
): { wasteScore: number; efficiencyScore: number; qualityScore: number } {
if (!result) {
return { wasteScore: Infinity, efficiencyScore: 0, qualityScore: 0 };
}
// Waste score (lower is better)
const wasteScore = (isSelectionSuccess(result) && result.wasteMetric) ||
this.calculateWaste(result, options);
// Efficiency score (higher is better) - based on input count and value
if (!isSelectionSuccess(result)) {
return { wasteScore, efficiencyScore: 0, qualityScore: 0 };
}
const totalValue = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
const utilization = result.totalValue / totalValue;
const inputEfficiency = 1 / result.inputs.length; // Fewer inputs = more efficient
const efficiencyScore = (utilization + inputEfficiency) / 2;
// Quality score (higher is better) - overall assessment
const hasChange = result.change > (options.dustThreshold || 546);
const changePenalty = hasChange ? 0.9 : 1.0; // Slight penalty for creating change
const wasteNormalized = Math.min(1, 10000 / (wasteScore + 1)); // Normalize waste to 0-1
const qualityScore = (wasteNormalized * changePenalty + efficiencyScore) /
2;
return { wasteScore, efficiencyScore, qualityScore };
}
/**
* Calculate waste metric if not provided
*/
private calculateWaste(
result: SelectionResult,
options: SelectionOptions,
): number {
if (!isSelectionSuccess(result)) {
return Infinity;
}
const excess = result.totalValue - options.targetValue - result.fee;
const changeOutput = result.change > (options.dustThreshold || 546) ? 1 : 0;
const changeCost = changeOutput * 34 * options.feeRate; // Cost of change output
return excess + changeCost;
}
/**
* Fallback to sequential execution
*/
private fallbackToSequential(
utxos: UTXO[],
options: SelectionOptions,
algorithms: SelectorAlgorithm[],
): Promise<ParallelSelectionResult | null> {
console.warn('Falling back to sequential execution');
return new Promise((resolve) => {
for (const algorithm of algorithms) {
try {
const startTime = Date.now();
const selector = this.selectorFactory.create(algorithm);
const result = selector.select(utxos, options);
if (result && result.success) {
const convertedResult = result;
const executionTime = Date.now() - startTime;
const metrics = this.calculateMetrics(convertedResult, utxos, options);
resolve({
result: convertedResult,
algorithm,
executionTime,
allResults: [
{
algorithm,
result: convertedResult,
executionTime,
},
],
totalExecutionTime: executionTime,
winnerMetrics: metrics,
});
return;
}
} catch (error) {
console.warn(`Sequential fallback failed for ${algorithm}:`, error);
}
}
resolve(null);
});
}
/**
* Update configuration
*/
updateConfig(newConfig: Partial<ParallelSelectionConfig>): void {
this.config = { ...this.config, ...newConfig };
}
/**
* Get performance statistics
*/
getStats(): {
config: Required<ParallelSelectionConfig>;
activeJobs: number;
workerPoolSize: number;
} {
return {
config: this.config,
activeJobs: this.activeJobs.size,
workerPoolSize: this.workerPool.length,
};
}
/**
* Cleanup resources
*/
cleanup(): void {
// Clear any pending jobs
for (const [, job] of this.activeJobs.entries()) {
clearTimeout(job.timeout);
job.reject(new Error('Cleanup - job cancelled'));
}
this.activeJobs.clear();
// Cleanup worker pool if we had implemented workers
this.workerPool.forEach((worker) => worker.terminate?.());
this.workerPool = [];
}
}
/**
* Create parallel selector with default configuration
*/
export function createParallelSelector(
selectorFactory: PerformanceAwareSelectorFactory,
performanceMonitor: PerformanceMonitor,
config?: Partial<ParallelSelectionConfig>,
): ParallelSelector {
return new ParallelSelector(selectorFactory, performanceMonitor, config);
}
/**
* Create high-performance parallel selector
*/
export function createHighPerformanceParallelSelector(
selectorFactory: PerformanceAwareSelectorFactory,
performanceMonitor: PerformanceMonitor,
): ParallelSelector {
return new ParallelSelector(selectorFactory, performanceMonitor, {
maxConcurrency: 6,
timeoutMs: 3000,
returnFirst: false,
enableRacing: true,
qualityThreshold: 0.8,
enableFallback: true,
});
}
/**
* Create racing-optimized parallel selector (speed over quality)
*/
export function createRacingParallelSelector(
selectorFactory: PerformanceAwareSelectorFactory,
performanceMonitor: PerformanceMonitor,
): ParallelSelector {
return new ParallelSelector(selectorFactory, performanceMonitor, {
maxConcurrency: 8,
timeoutMs: 1000,
returnFirst: true,
enableRacing: true,
qualityThreshold: 0.5,
enableFallback: true,
});
}