mirror of
https://github.com/benjamin-wilson/public-pool.git
synced 2026-09-29 09:05:06 -07:00
Fix one-off hashrate calculation
This commit is contained in:
@@ -32,7 +32,30 @@ describe('StratumV1ClientStatistics', () => {
|
||||
await statistics.addShares(client, 64);
|
||||
}
|
||||
|
||||
expect(statistics.hashRate).toBeGreaterThan(0);
|
||||
expect(statistics.hashRate).toBeCloseTo((64 * 4294967296) / 62);
|
||||
});
|
||||
|
||||
it('keeps exactly the configured number of share samples', async () => {
|
||||
for (let i = 0; i < 31; i++) {
|
||||
jest.setSystemTime(new Date(Date.parse('2026-05-06T12:00:00Z') + (i * 31000)));
|
||||
await statistics.addShares(client, 64);
|
||||
}
|
||||
|
||||
expect((statistics as any).submissionCache).toHaveLength(30);
|
||||
expect((statistics as any).submissionCacheDifficultySum).toBe(30 * 64);
|
||||
});
|
||||
|
||||
it('excludes pre-window work and corrects share-terminated sampling bias', async () => {
|
||||
for (let i = 0; i < 30; i++) {
|
||||
jest.setSystemTime(new Date(Date.parse('2026-05-06T12:00:00Z') + (i * 31000)));
|
||||
await statistics.addShares(client, 64);
|
||||
}
|
||||
|
||||
const elapsedSeconds = 29 * 31;
|
||||
const expectedUnbiasedDifficulty = 28 * 64;
|
||||
expect(statistics.hashRate).toBeCloseTo(
|
||||
(expectedUnbiasedDifficulty * 4294967296) / elapsedSeconds,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not suggest a difficulty change before enough time or shares have passed', () => {
|
||||
@@ -51,7 +74,7 @@ describe('StratumV1ClientStatistics', () => {
|
||||
await statistics.addShares(client, 64);
|
||||
}
|
||||
|
||||
expect(statistics.getSuggestedDifficulty(64)).toBe(2048);
|
||||
expect(statistics.getSuggestedDifficulty(64)).toBe(1024);
|
||||
});
|
||||
|
||||
it('does not retarget when accepted shares have a zero-duration sample window', async () => {
|
||||
@@ -76,7 +99,7 @@ describe('StratumV1ClientStatistics', () => {
|
||||
await statistics.addShares(client, 64);
|
||||
}
|
||||
|
||||
expect(statistics.getSuggestedDifficulty(128)).toBe(16);
|
||||
expect(statistics.getSuggestedDifficulty(128)).toBe(8);
|
||||
});
|
||||
|
||||
it('should not suggest a difficulty below the configured minimum', () => {
|
||||
|
||||
@@ -16,9 +16,9 @@ export class StratumV1ClientStatistics {
|
||||
}
|
||||
|
||||
public async addShares(_client: ClientEntity, targetDifficulty: number) {
|
||||
var date = new Date();
|
||||
const date = new Date();
|
||||
|
||||
if (this.submissionCache.length > CACHE_SIZE) {
|
||||
if (this.submissionCache.length >= CACHE_SIZE) {
|
||||
this.submissionCacheDifficultySum -= this.submissionCache[0].difficulty;
|
||||
this.submissionCache.shift();
|
||||
}
|
||||
@@ -28,9 +28,12 @@ export class StratumV1ClientStatistics {
|
||||
});
|
||||
this.submissionCacheDifficultySum += targetDifficulty;
|
||||
|
||||
const time = new Date().getTime() - this.submissionCache[0].time.getTime();
|
||||
if(time > 60000 && this.submissionCache.length > 2) {
|
||||
this.hashRate = (this.submissionCacheDifficultySum * 4294967296) / (time / 1000);
|
||||
const elapsedSeconds = (date.getTime() - this.submissionCache[0].time.getTime()) / 1000;
|
||||
if (elapsedSeconds > 60) {
|
||||
const difficultyPerSecond = this.getDifficultyPerSecond(elapsedSeconds);
|
||||
if (difficultyPerSecond != null) {
|
||||
this.hashRate = difficultyPerSecond * 4294967296;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,17 +49,12 @@ export class StratumV1ClientStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
const sum = this.submissionCache.reduce((pre, cur) => {
|
||||
pre += cur.difficulty;
|
||||
return pre;
|
||||
}, 0);
|
||||
const diffSeconds = (this.submissionCache[this.submissionCache.length - 1].time.getTime() - this.submissionCache[0].time.getTime()) / 1000;
|
||||
if (!Number.isFinite(diffSeconds) || diffSeconds <= 0) {
|
||||
const difficultyPerSecond = this.getDifficultyPerSecond(diffSeconds);
|
||||
if (difficultyPerSecond == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const difficultyPerSecond = sum / diffSeconds;
|
||||
|
||||
const targetDifficulty = difficultyPerSecond * this.targetSubmitShareEveryNSeconds;
|
||||
if (!Number.isFinite(targetDifficulty) || targetDifficulty <= 0) {
|
||||
return null;
|
||||
@@ -69,6 +67,32 @@ export class StratumV1ClientStatistics {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimate work rate from a share-terminated sample window.
|
||||
*
|
||||
* A cache of N shares contains N - 1 observed inter-share intervals. The
|
||||
* first share's work predates the window and must not be counted. Because
|
||||
* the window closes on a share arrival, the reciprocal elapsed time also
|
||||
* has the usual finite-sample Poisson bias; multiplying by
|
||||
* (intervalCount - 1) / intervalCount removes it.
|
||||
*/
|
||||
private getDifficultyPerSecond(elapsedSeconds: number): number | null {
|
||||
const sampleCount = this.submissionCache.length;
|
||||
if (sampleCount <= 2 || !Number.isFinite(elapsedSeconds) || elapsedSeconds <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const intervalCount = sampleCount - 1;
|
||||
const observedDifficulty = this.submissionCacheDifficultySum
|
||||
- this.submissionCache[0].difficulty;
|
||||
const unbiasedDifficulty = observedDifficulty * (intervalCount - 1) / intervalCount;
|
||||
const difficultyPerSecond = unbiasedDifficulty / elapsedSeconds;
|
||||
|
||||
return Number.isFinite(difficultyPerSecond) && difficultyPerSecond > 0
|
||||
? difficultyPerSecond
|
||||
: null;
|
||||
}
|
||||
|
||||
private nearestPowerOfTwo(val: number): number {
|
||||
if (!Number.isFinite(val) || val <= 0) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user