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);
|
clientStatisticsService = moduleRef.get<ClientStatisticsService>(ClientStatisticsService);
|
||||||
|
|
||||||
configService = moduleRef.get<ConfigService>(ConfigService);
|
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 = {
|
bitcoinRpcService = {
|
||||||
newBlockTemplate$: newBlockEmitter.asObservable(),
|
newBlockTemplate$: newBlockEmitter.asObservable(),
|
||||||
@@ -141,6 +151,7 @@ describe('StratumV1Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.end = jest.fn();
|
socket.end = jest.fn();
|
||||||
|
jest.spyOn(socket, 'destroy').mockImplementation(() => socket);
|
||||||
|
|
||||||
const addressSettings = moduleRef.get<AddressSettingsService>(AddressSettingsService);
|
const addressSettings = moduleRef.get<AddressSettingsService>(AddressSettingsService);
|
||||||
notificationService = {
|
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 () => {
|
it('should respond to mining.configure', async () => {
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { SuggestDifficulty } from './stratum-messages/SuggestDifficultyMessage';
|
|||||||
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
import { StratumV1ClientStatistics } from './StratumV1ClientStatistics';
|
||||||
|
|
||||||
const TRUE_DIFF_ONE = 2.695953529101131e67;
|
const TRUE_DIFF_ONE = 2.695953529101131e67;
|
||||||
|
const BLOCKED_USER_AGENT_LOG_INTERVAL_MS = 60 * 1000;
|
||||||
|
|
||||||
export function effectiveJobDifficulty(
|
export function effectiveJobDifficulty(
|
||||||
jobIdInt: number,
|
jobIdInt: number,
|
||||||
@@ -47,6 +48,7 @@ export function effectiveJobDifficulty(
|
|||||||
|
|
||||||
|
|
||||||
export class StratumV1Client {
|
export class StratumV1Client {
|
||||||
|
private static blockedUserAgentLogState = new Map<string, { nextLogAt: number, suppressed: number }>();
|
||||||
|
|
||||||
public clientSubscription: SubscriptionMessage;
|
public clientSubscription: SubscriptionMessage;
|
||||||
private clientConfiguration: ConfigurationMessage;
|
private clientConfiguration: ConfigurationMessage;
|
||||||
@@ -71,6 +73,7 @@ export class StratumV1Client {
|
|||||||
//public hashRate: number = 0;
|
//public hashRate: number = 0;
|
||||||
|
|
||||||
private buffer: string = '';
|
private buffer: string = '';
|
||||||
|
private connectionClosed = false;
|
||||||
|
|
||||||
private miningSubmissionHashes = new Set<string>()
|
private miningSubmissionHashes = new Set<string>()
|
||||||
|
|
||||||
@@ -93,6 +96,9 @@ export class StratumV1Client {
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const m of lines.filter(l => l.length > 0)) {
|
for (const m of lines.filter(l => l.length > 0)) {
|
||||||
|
if (this.connectionClosed || this.socket.destroyed || this.socket.writableEnded) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.handleMessage(m);
|
await this.handleMessage(m);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -157,6 +163,11 @@ export class StratumV1Client {
|
|||||||
const errors = await validate(subscriptionMessage, validatorOptions);
|
const errors = await validate(subscriptionMessage, validatorOptions);
|
||||||
|
|
||||||
if (errors.length === 0) {
|
if (errors.length === 0) {
|
||||||
|
if (this.isBlockedUserAgent(subscriptionMessage.userAgent)) {
|
||||||
|
this.logBlockedUserAgent(subscriptionMessage.userAgent);
|
||||||
|
this.closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.sessionStart == null) {
|
if (this.sessionStart == null) {
|
||||||
this.sessionStart = new Date();
|
this.sessionStart = new Date();
|
||||||
@@ -372,9 +383,9 @@ export class StratumV1Client {
|
|||||||
private async initStratum() {
|
private async initStratum() {
|
||||||
this.stratumInitialized = true;
|
this.stratumInitialized = true;
|
||||||
|
|
||||||
if (this.validateHeaderCompliance(this.clientSubscription.userAgent)) {
|
if (this.isBlockedUserAgent(this.clientSubscription.userAgent)) {
|
||||||
console.log(`Non compliant connection from userAgent: ${this.clientSubscription.userAgent}`);
|
this.logBlockedUserAgent(this.clientSubscription.userAgent);
|
||||||
await this.socket.end();
|
this.closeSocket();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,16 +733,43 @@ export class StratumV1Client {
|
|||||||
return number;
|
return number;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validateHeaderCompliance(userAgent: string): boolean {
|
private isBlockedUserAgent(userAgent: string): boolean {
|
||||||
const headerCompliance = this.configService.get<string>('COMPLIANT_HEADERS');
|
const blockedUserAgents = this.configService.get<string>('NON_COMPLIANT_USER_AGENTS')
|
||||||
if (!headerCompliance || headerCompliance.trim() === '') {
|
|| this.configService.get<string>('BLOCKED_USER_AGENTS')
|
||||||
|
|| this.configService.get<string>('COMPLIANT_HEADERS');
|
||||||
|
if (!blockedUserAgents || blockedUserAgents.trim() === '') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const complianceList = headerCompliance.split(',').map(ua => ua.trim().toLowerCase());
|
const blockedList = blockedUserAgents.split(',').map(ua => ua.trim().toLowerCase());
|
||||||
const userAgentLower = userAgent.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> {
|
private async write(message: string): Promise<boolean> {
|
||||||
|
|||||||
Reference in New Issue
Block a user