mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
sv2 debug
This commit is contained in:
@@ -61,6 +61,7 @@ const DEFAULT_TARGET_SHARES_PER_MINUTE = 2;
|
|||||||
const DEFAULT_DIFFICULTY_CHECK_INTERVAL_MS = 60 * 1000;
|
const DEFAULT_DIFFICULTY_CHECK_INTERVAL_MS = 60 * 1000;
|
||||||
const FIXED_STANDARD_EXTRANONCE2 = '0000000000000000';
|
const FIXED_STANDARD_EXTRANONCE2 = '0000000000000000';
|
||||||
const RETIRED_EXTENDED_JOB_RETENTION_MS = 5 * 60 * 1000;
|
const RETIRED_EXTENDED_JOB_RETENTION_MS = 5 * 60 * 1000;
|
||||||
|
const SV2_AUTH_FAILURE_LOG_INTERVAL_MS = 60 * 1000;
|
||||||
|
|
||||||
interface ExtendedJobData {
|
interface ExtendedJobData {
|
||||||
coinbasePrefix: Buffer;
|
coinbasePrefix: Buffer;
|
||||||
@@ -93,6 +94,8 @@ interface ChannelState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class StratumV2Client {
|
export class StratumV2Client {
|
||||||
|
private static authFailureLogState = new Map<string, { nextLogAt: number; suppressed: number }>();
|
||||||
|
|
||||||
private readonly sessionId = crypto.randomBytes(4).toString('hex');
|
private readonly sessionId = crypto.randomBytes(4).toString('hex');
|
||||||
private readonly noiseSession: Sv2NoiseSession;
|
private readonly noiseSession: Sv2NoiseSession;
|
||||||
private readonly frameReader = new Sv2FrameReader(null);
|
private readonly frameReader = new Sv2FrameReader(null);
|
||||||
@@ -116,6 +119,7 @@ export class StratumV2Client {
|
|||||||
private sessionDifficulty: number;
|
private sessionDifficulty: number;
|
||||||
private clientEntity: ClientEntity = null;
|
private clientEntity: ClientEntity = null;
|
||||||
private creatingEntity: Promise<void> = null;
|
private creatingEntity: Promise<void> = null;
|
||||||
|
private readonly firstChunkSummary: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly socket: Socket,
|
private readonly socket: Socket,
|
||||||
@@ -130,6 +134,7 @@ export class StratumV2Client {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly addressSettingsService: AddressSettingsService,
|
private readonly addressSettingsService: AddressSettingsService,
|
||||||
) {
|
) {
|
||||||
|
this.firstChunkSummary = this.describeChunk(firstChunk);
|
||||||
this.noiseSession = new Sv2NoiseSession(this.stratumV2Service.getNoiseConfig());
|
this.noiseSession = new Sv2NoiseSession(this.stratumV2Service.getNoiseConfig());
|
||||||
this.sessionDifficulty = this.getInitialDifficulty();
|
this.sessionDifficulty = this.getInitialDifficulty();
|
||||||
this.targetSharesPerMinute = this.getTargetSharesPerMinute();
|
this.targetSharesPerMinute = this.getTargetSharesPerMinute();
|
||||||
@@ -182,7 +187,7 @@ export class StratumV2Client {
|
|||||||
await this.handleEncryptedData(data);
|
await this.handleEncryptedData(data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[SV2 ${this.sessionId}] ${error.message}`);
|
this.logProtocolError(error);
|
||||||
this.closeSocket();
|
this.closeSocket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1152,6 +1157,43 @@ export class StratumV2Client {
|
|||||||
throw new Error('Invalid network configuration');
|
throw new Error('Invalid network configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private logProtocolError(error: Error): void {
|
||||||
|
if (!this.isNoisyAuthFailure(error)) {
|
||||||
|
console.error(`[SV2 ${this.sessionId}] ${error.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remote = this.socket.remoteAddress ?? 'unknown';
|
||||||
|
const key = `${remote}:${error.message}`;
|
||||||
|
const now = Date.now();
|
||||||
|
const logState = StratumV2Client.authFailureLogState.get(key);
|
||||||
|
if (logState != null && now < logState.nextLogAt) {
|
||||||
|
logState.suppressed += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suppressed = logState?.suppressed ?? 0;
|
||||||
|
const suffix = suppressed > 0 ? ` (${suppressed} similar auth failures suppressed)` : '';
|
||||||
|
console.warn(`[SV2 ${this.sessionId}] Authentication failed from ${remote}: ${error.message}; firstChunk=${this.firstChunkSummary}${suffix}`);
|
||||||
|
StratumV2Client.authFailureLogState.set(key, {
|
||||||
|
nextLogAt: now + SV2_AUTH_FAILURE_LOG_INTERVAL_MS,
|
||||||
|
suppressed: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private isNoisyAuthFailure(error: Error): boolean {
|
||||||
|
return error.message.includes('Unsupported state or unable to authenticate data');
|
||||||
|
}
|
||||||
|
|
||||||
|
private describeChunk(chunk: Buffer): string {
|
||||||
|
const preview = chunk.subarray(0, 16);
|
||||||
|
const printable = Array.from(preview).every(byte => byte >= 0x20 && byte <= 0x7e);
|
||||||
|
const prefix = printable
|
||||||
|
? preview.toString('ascii').replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
||||||
|
: preview.toString('hex');
|
||||||
|
return `len=${chunk.length},${printable ? 'ascii' : 'hex'}=${prefix}`;
|
||||||
|
}
|
||||||
|
|
||||||
private closeSocket(): void {
|
private closeSocket(): void {
|
||||||
if (!this.socket.destroyed) {
|
if (!this.socket.destroyed) {
|
||||||
this.socket.destroy();
|
this.socket.destroy();
|
||||||
|
|||||||
@@ -174,6 +174,16 @@ describe('StratumV1Service', () => {
|
|||||||
expect((service as any).detectProtocol(Buffer.from([0x16, 0xaa, 0xbb]))).toBe('v2');
|
expect((service as any).detectProtocol(Buffer.from([0x16, 0xaa, 0xbb]))).toBe('v2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not route plaintext PROXY protocol headers to Stratum V2', () => {
|
||||||
|
const firstChunk = Buffer.from('PROXY TCP4 203.0.113.10 192.0.2.10 54321 3333\\r\\n');
|
||||||
|
|
||||||
|
expect((service as any).detectProtocol(firstChunk)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not route malformed plaintext to Stratum V2', () => {
|
||||||
|
expect((service as any).detectProtocol(Buffer.from('mining.subscribe\\n'))).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
function restoreEnv(key: string, value: string | undefined) {
|
function restoreEnv(key: string, value: string | undefined) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
delete process.env[key];
|
delete process.env[key];
|
||||||
|
|||||||
@@ -428,6 +428,13 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plaintext that is not JSON-RPC is not a valid SV2 Noise Act 1. This
|
||||||
|
// catches PROXY-protocol lines, SSH banners, and malformed SV1 clients
|
||||||
|
// before they get misrouted into the SV2 decrypt path.
|
||||||
|
if (this.looksLikePlaintext(firstChunk)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return 'v2';
|
return 'v2';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,6 +480,18 @@ export class StratumV1Service implements OnModuleInit {
|
|||||||
|| prefix.startsWith('OPTIONS ');
|
|| prefix.startsWith('OPTIONS ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private looksLikePlaintext(firstChunk: Buffer): boolean {
|
||||||
|
for (const byte of firstChunk) {
|
||||||
|
const isWhitespace = byte === 0x09 || byte === 0x0a || byte === 0x0d;
|
||||||
|
const isPrintableAscii = byte >= 0x20 && byte <= 0x7e;
|
||||||
|
if (!isWhitespace && !isPrintableAscii) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private getPositiveIntegerEnv(key: string, fallback: number) {
|
private getPositiveIntegerEnv(key: string, fallback: number) {
|
||||||
const configured = parseInt(process.env[key], 10);
|
const configured = parseInt(process.env[key], 10);
|
||||||
if (Number.isFinite(configured) && configured > 0) {
|
if (Number.isFinite(configured) && configured > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user