@bsv/overlay-express
Version:
BSV Blockchain Overlay Express
88 lines • 2.96 kB
JavaScript
const TERMINAL_CODES = new Set([
'400',
'460',
'463',
'464',
'DOUBLE_SPEND_ATTEMPTED',
'REJECTED',
'INVALID',
'MALFORMED',
'MINED_IN_STALE_BLOCK'
]);
function isTerminalBroadcastFailure(failure) {
const code = failure.code.toUpperCase();
const description = failure.description.toUpperCase();
if (TERMINAL_CODES.has(code))
return true;
if (code.includes('ORPHAN') || description.includes('ORPHAN'))
return true;
if (typeof failure.more === 'object' &&
failure.more !== null &&
'terminal' in failure.more &&
failure.more.terminal === true) {
return true;
}
return false;
}
/**
* Tries transaction propagation providers in priority order.
*
* Transient provider failures fall through to the next provider. Terminal
* validation states, including double spends, stop immediately so a conflicting
* transaction is not retried against a second propagation surface.
*/
export class ProviderChainBroadcaster {
providers;
constructor(providers) {
this.providers = providers;
if (providers.length === 0) {
throw new TypeError('ProviderChainBroadcaster requires at least one provider');
}
}
async broadcast(tx) {
const failures = [];
for (const provider of this.providers) {
let response;
try {
response = await provider.broadcaster.broadcast(tx);
}
catch (error) {
response = {
status: 'error',
code: '500',
description: error instanceof Error ? error.message : 'Internal Server Error',
more: { provider: provider.name, terminal: false }
};
}
if (response.status === 'success') {
return {
...response,
message: `[${provider.name}] ${response.message}`
};
}
failures.push({ provider: provider.name, failure: response });
if (isTerminalBroadcastFailure(response)) {
return annotateFailure(response, provider.name, failures);
}
}
const last = failures[failures.length - 1];
return annotateFailure(last.failure, last.provider, failures);
}
}
function annotateFailure(failure, provider, failures) {
return {
...failure,
more: {
...(typeof failure.more === 'object' && failure.more !== null ? failure.more : {}),
provider,
providerFailures: failures.map(item => ({
provider: item.provider,
code: item.failure.code,
description: item.failure.description,
txid: item.failure.txid,
more: item.failure.more
}))
}
};
}
//# sourceMappingURL=ProviderChainBroadcaster.js.map