mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
reduce overhead
This commit is contained in:
@@ -120,6 +120,16 @@ describe('StratumV1Client', () => {
|
||||
clientStatisticsService = moduleRef.get<ClientStatisticsService>(ClientStatisticsService);
|
||||
|
||||
configService = moduleRef.get<ConfigService>(ConfigService);
|
||||
(configService.get as jest.Mock).mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'DEV_FEE_ADDRESS':
|
||||
return 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4';
|
||||
case 'NETWORK':
|
||||
return 'testnet';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
(StratumV1Client as any).blockedUserAgentLogState.clear();
|
||||
|
||||
bitcoinRpcService = {
|
||||
newBlockTemplate$: newBlockEmitter.asObservable(),
|
||||
@@ -141,6 +151,7 @@ describe('StratumV1Client', () => {
|
||||
});
|
||||
|
||||
socket.end = jest.fn();
|
||||
jest.spyOn(socket, 'destroy').mockImplementation(() => socket);
|
||||
|
||||
const addressSettings = moduleRef.get<AddressSettingsService>(AddressSettingsService);
|
||||
notificationService = {
|
||||
@@ -220,6 +231,73 @@ describe('StratumV1Client', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should block non-compliant user agents on subscribe without allocating a session', async () => {
|
||||
(configService.get as jest.Mock).mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'NON_COMPLIANT_USER_AGENTS':
|
||||
return 'NMMiner';
|
||||
case 'DEV_FEE_ADDRESS':
|
||||
return 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4';
|
||||
case 'NETWORK':
|
||||
return 'testnet';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
emitMessage(`{"id":1,"method":"mining.subscribe","params":["NMMiner/1.0"]}`);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(socket.destroy).toHaveBeenCalled();
|
||||
expect(socket.write).not.toHaveBeenCalled();
|
||||
expect((client as any).statistics).toBeUndefined();
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith('Blocked non-compliant connection from userAgent: NMMiner');
|
||||
});
|
||||
|
||||
it('should throttle repeated non-compliant user agent logs', async () => {
|
||||
(configService.get as jest.Mock).mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'NON_COMPLIANT_USER_AGENTS':
|
||||
return 'NMMiner';
|
||||
case 'DEV_FEE_ADDRESS':
|
||||
return 'tb1qumezefzdeqqwn5zfvgdrhxjzc5ylr39uhuxcz4';
|
||||
case 'NETWORK':
|
||||
return 'testnet';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
jest.spyOn(socket, 'write').mockImplementation((data) => true);
|
||||
|
||||
emitMessage(`{"id":1,"method":"mining.subscribe","params":["NMMiner/1.0"]}`);
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
const secondSocket = new Socket();
|
||||
jest.spyOn(secondSocket, 'on').mockImplementation((event: string, listener: (...args: any[]) => void) => {
|
||||
socketEmitter = listener;
|
||||
return secondSocket;
|
||||
});
|
||||
secondSocket.end = jest.fn();
|
||||
jest.spyOn(secondSocket, 'destroy').mockImplementation(() => secondSocket);
|
||||
const secondClient = new StratumV1Client(
|
||||
secondSocket,
|
||||
stratumV1JobsService,
|
||||
bitcoinRpcService,
|
||||
clientService,
|
||||
clientStatisticsService,
|
||||
notificationService,
|
||||
blocksService,
|
||||
configService,
|
||||
moduleRef.get<AddressSettingsService>(AddressSettingsService)
|
||||
);
|
||||
|
||||
socketEmitter(Buffer.from(`{"id":1,"method":"mining.subscribe","params":["NMMiner/1.0"]}\n`));
|
||||
await new Promise((r) => setTimeout(r, 1));
|
||||
|
||||
expect(secondSocket.destroy).toHaveBeenCalled();
|
||||
expect(consoleLogSpy.mock.calls.filter(call => call[0]?.startsWith('Blocked non-compliant connection'))).toHaveLength(1);
|
||||
await secondClient.destroy();
|
||||
});
|
||||
|
||||
|
||||
it('should respond to mining.configure', async () => {
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
||||
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||
|
||||
const TRUE_DIFF_ONE = 2.695953529101131e67;
|
||||
const BLOCKED_USER_AGENT_LOG_INTERVAL_MS = 60 * 1000;
|
||||
|
||||
export function effectiveJobDifficulty(
|
||||
jobIdInt: number,
|
||||
@@ -47,6 +48,7 @@ export function effectiveJobDifficulty(
|
||||
|
||||
|
||||
export class StratumV1Client {
|
||||
private static blockedUserAgentLogState = new Map<string, { nextLogAt: number, suppressed: number }>();
|
||||
|
||||
public clientSubscription: SubscriptionMessage;
|
||||
private clientConfiguration: ConfigurationMessage;
|
||||
@@ -71,6 +73,7 @@ export class StratumV1Client {
|
||||
//public hashRate: number = 0;
|
||||
|
||||
private buffer: string = '';
|
||||
private connectionClosed = false;
|
||||
|
||||
private miningSubmissionHashes = new Set<string>()
|
||||
|
||||
@@ -93,6 +96,9 @@ export class StratumV1Client {
|
||||
|
||||
(async () => {
|
||||
for (const m of lines.filter(l => l.length > 0)) {
|
||||
if (this.connectionClosed || this.socket.destroyed || this.socket.writableEnded) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
await this.handleMessage(m);
|
||||
} catch (e) {
|
||||
@@ -157,6 +163,11 @@ export class StratumV1Client {
|
||||
const errors = await validate(subscriptionMessage, validatorOptions);
|
||||
|
||||
if (errors.length === 0) {
|
||||
if (this.isBlockedUserAgent(subscriptionMessage.userAgent)) {
|
||||
this.logBlockedUserAgent(subscriptionMessage.userAgent);
|
||||
this.closeSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sessionStart == null) {
|
||||
this.sessionStart = new Date();
|
||||
@@ -372,9 +383,9 @@ export class StratumV1Client {
|
||||
private async initStratum() {
|
||||
this.stratumInitialized = true;
|
||||
|
||||
if (this.validateHeaderCompliance(this.clientSubscription.userAgent)) {
|
||||
console.log(`Non compliant connection from userAgent: ${this.clientSubscription.userAgent}`);
|
||||
await this.socket.end();
|
||||
if (this.isBlockedUserAgent(this.clientSubscription.userAgent)) {
|
||||
this.logBlockedUserAgent(this.clientSubscription.userAgent);
|
||||
this.closeSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -722,16 +733,43 @@ export class StratumV1Client {
|
||||
return number;
|
||||
}
|
||||
|
||||
private validateHeaderCompliance(userAgent: string): boolean {
|
||||
const headerCompliance = this.configService.get<string>('COMPLIANT_HEADERS');
|
||||
if (!headerCompliance || headerCompliance.trim() === '') {
|
||||
private isBlockedUserAgent(userAgent: string): boolean {
|
||||
const blockedUserAgents = this.configService.get<string>('NON_COMPLIANT_USER_AGENTS')
|
||||
|| this.configService.get<string>('BLOCKED_USER_AGENTS')
|
||||
|| this.configService.get<string>('COMPLIANT_HEADERS');
|
||||
if (!blockedUserAgents || blockedUserAgents.trim() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const complianceList = headerCompliance.split(',').map(ua => ua.trim().toLowerCase());
|
||||
const blockedList = blockedUserAgents.split(',').map(ua => ua.trim().toLowerCase());
|
||||
const userAgentLower = userAgent.toLowerCase();
|
||||
|
||||
return complianceList.some(compliant => compliant.length > 0 && userAgentLower.includes(compliant));
|
||||
return blockedList.some(blocked => blocked.length > 0 && userAgentLower.includes(blocked));
|
||||
}
|
||||
|
||||
private logBlockedUserAgent(userAgent: string) {
|
||||
const now = Date.now();
|
||||
const logState = StratumV1Client.blockedUserAgentLogState.get(userAgent);
|
||||
|
||||
if (logState != null && now < logState.nextLogAt) {
|
||||
logState.suppressed += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
const suppressed = logState?.suppressed ?? 0;
|
||||
const suffix = suppressed > 0 ? ` (${suppressed} similar connections suppressed)` : '';
|
||||
console.log(`Blocked non-compliant connection from userAgent: ${userAgent}${suffix}`);
|
||||
StratumV1Client.blockedUserAgentLogState.set(userAgent, {
|
||||
nextLogAt: now + BLOCKED_USER_AGENT_LOG_INTERVAL_MS,
|
||||
suppressed: 0
|
||||
});
|
||||
}
|
||||
|
||||
private closeSocket() {
|
||||
this.connectionClosed = true;
|
||||
if (!this.socket.destroyed) {
|
||||
this.socket.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private async write(message: string): Promise<boolean> {
|
||||
|
||||
Reference in New Issue
Block a user