trac-msb 0.2.24 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/config/config.js
CHANGED
|
@@ -219,6 +219,11 @@ export class Config {
|
|
|
219
219
|
return this.#config.validatorHealthCheckFailureThreshold
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
get confirmationStallTimeout() {
|
|
223
|
+
if (this.#isOverriden('confirmationStallTimeout')) return this.#options.confirmationStallTimeout
|
|
224
|
+
return this.#config.confirmationStallTimeout
|
|
225
|
+
}
|
|
226
|
+
|
|
222
227
|
get maxPendingRequestsInPendingRequestsService() {
|
|
223
228
|
return this.#config.maxPendingRequestsInPendingRequestsService
|
|
224
229
|
}
|
package/src/config/env.js
CHANGED
|
@@ -57,6 +57,7 @@ const configData = {
|
|
|
57
57
|
txPoolSize: 1000, // size of transaction pool
|
|
58
58
|
validatorHealthCheckInterval: 5 * 60 * 1000, // How often to check validator health (ms)
|
|
59
59
|
validatorHealthCheckFailureThreshold: 3, // Consecutive failed health checks before a validator is removed
|
|
60
|
+
confirmationStallTimeout: 120_000, // Force a fast-forward when the confirmed length has not moved for this long while unconfirmed entries wait (ms, 0 = off)
|
|
60
61
|
storesDirectory: 'stores/',
|
|
61
62
|
storeName: 'testnet',
|
|
62
63
|
},
|
|
@@ -107,6 +108,7 @@ const configData = {
|
|
|
107
108
|
txPoolSize: 1000, // size of transaction pool
|
|
108
109
|
validatorHealthCheckInterval: 5 * 60 * 1000, // How often to check validator health (ms)
|
|
109
110
|
validatorHealthCheckFailureThreshold: 3, // Consecutive failed health checks before a validator is removed
|
|
111
|
+
confirmationStallTimeout: 120_000, // Force a fast-forward when the confirmed length has not moved for this long while unconfirmed entries wait (ms, 0 = off)
|
|
110
112
|
storesDirectory: 'stores/',
|
|
111
113
|
storeName: 'mainnet',
|
|
112
114
|
},
|
|
@@ -157,6 +159,7 @@ const configData = {
|
|
|
157
159
|
txPoolSize: 1000, // size of transaction pool
|
|
158
160
|
validatorHealthCheckInterval: 1_000, // How often to check validator health (ms)
|
|
159
161
|
validatorHealthCheckFailureThreshold: 3, // Consecutive failed health checks before a validator is removed
|
|
162
|
+
confirmationStallTimeout: 120_000, // Force a fast-forward when the confirmed length has not moved for this long while unconfirmed entries wait (ms, 0 = off)
|
|
160
163
|
storesDirectory: 'stores/',
|
|
161
164
|
storeName: 'development',
|
|
162
165
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const DEFAULT_CHECK_INTERVAL = 15_000;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Autobase starts a follower's fast-forward only when the network's signed length is at least
|
|
5
|
+
* 16 entries past the follower's total length, unconfirmed entries included. After a writer is
|
|
6
|
+
* removed in the middle of a batch, a follower that already holds unconfirmed entries can stay
|
|
7
|
+
* above that mark: its confirmed length then stops for good, and a restart does not help.
|
|
8
|
+
*
|
|
9
|
+
* This watch forces one fast-forward when the confirmed length has not moved for `timeout` ms
|
|
10
|
+
* while unconfirmed entries wait. It never runs on an indexer and does not change how entries are
|
|
11
|
+
* applied.
|
|
12
|
+
*/
|
|
13
|
+
export class ConfirmationWatch {
|
|
14
|
+
#base;
|
|
15
|
+
#timeout;
|
|
16
|
+
#interval;
|
|
17
|
+
#now;
|
|
18
|
+
#log;
|
|
19
|
+
#timer = null;
|
|
20
|
+
#mark = null;
|
|
21
|
+
#running = false;
|
|
22
|
+
|
|
23
|
+
constructor(base, { timeout, interval = DEFAULT_CHECK_INTERVAL, now = Date.now, log = console.log } = {}) {
|
|
24
|
+
this.#base = base;
|
|
25
|
+
this.#timeout = timeout;
|
|
26
|
+
this.#interval = Math.min(interval, timeout);
|
|
27
|
+
this.#now = now;
|
|
28
|
+
this.#log = log;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get enabled() {
|
|
32
|
+
return Number.isFinite(this.#timeout) && this.#timeout > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
start() {
|
|
36
|
+
if (!this.enabled || this.#timer !== null) return;
|
|
37
|
+
this.#timer = setInterval(() => { this.check().catch(() => {}) }, this.#interval);
|
|
38
|
+
this.#timer.unref?.();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
stop() {
|
|
42
|
+
if (this.#timer !== null) clearInterval(this.#timer);
|
|
43
|
+
this.#timer = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @returns {Promise<boolean>} true when a fast-forward was forced */
|
|
47
|
+
async check() {
|
|
48
|
+
const base = this.#base;
|
|
49
|
+
if (this.#running || base.closing || base.isIndexer) return false;
|
|
50
|
+
if (base.isFastForwarding?.()) {
|
|
51
|
+
this.#mark = null;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const confirmed = base.view.core.signedLength;
|
|
56
|
+
const total = base.view.core.length;
|
|
57
|
+
const now = this.#now();
|
|
58
|
+
|
|
59
|
+
if (total <= confirmed) {
|
|
60
|
+
this.#mark = null;
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (this.#mark === null || this.#mark.confirmed !== confirmed) {
|
|
64
|
+
this.#mark = { confirmed, since: now };
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (now - this.#mark.since < this.#timeout) return false;
|
|
68
|
+
|
|
69
|
+
this.#running = true;
|
|
70
|
+
try {
|
|
71
|
+
this.#log(`[MSB] Confirmed length stuck at ${confirmed} (local ${total}) for ${Math.round((now - this.#mark.since) / 1000)} s; forcing a fast-forward`);
|
|
72
|
+
await base.forceFastForward();
|
|
73
|
+
this.#log(`[MSB] Fast-forward finished: confirmed ${base.view.core.signedLength}, local ${base.view.core.length}`);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.#log(`[MSB] Fast-forward failed: ${error?.message ?? error}`);
|
|
76
|
+
} finally {
|
|
77
|
+
this.#running = false;
|
|
78
|
+
this.#mark = { confirmed: base.view.core.signedLength, since: this.#now() };
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/core/state/State.js
CHANGED
|
@@ -42,6 +42,7 @@ import { deepCopyBuffer } from '../../utils/buffer.js';
|
|
|
42
42
|
import { Status } from './utils/transaction.js';
|
|
43
43
|
import remote from 'hypercore/lib/fully-remote-proof.js'
|
|
44
44
|
import PQueue from 'p-queue';
|
|
45
|
+
import { ConfirmationWatch } from './ConfirmationWatch.js';
|
|
45
46
|
|
|
46
47
|
const OVERSIZED_BATCH_PENALTY_MULTIPLIER = BATCH_SIZE;
|
|
47
48
|
|
|
@@ -56,6 +57,7 @@ class State extends ReadyResource {
|
|
|
56
57
|
#config
|
|
57
58
|
#wallet
|
|
58
59
|
#activeWriterCountCache = new Map();
|
|
60
|
+
#confirmationWatch = null;
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* @param {Corestore} store
|
|
@@ -96,10 +98,13 @@ class State extends ReadyResource {
|
|
|
96
98
|
console.log("State initialization...")
|
|
97
99
|
await this.#base.ready();
|
|
98
100
|
this.#writingKey = this.#base.local.key;
|
|
101
|
+
this.#confirmationWatch = new ConfirmationWatch(this.#base, { timeout: this.#config.confirmationStallTimeout });
|
|
102
|
+
this.#confirmationWatch.start();
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
async _close() {
|
|
102
106
|
console.log("State: closing gracefully...");
|
|
107
|
+
this.#confirmationWatch?.stop();
|
|
103
108
|
if (this.#bee !== null) {
|
|
104
109
|
await this.#bee.close();
|
|
105
110
|
}
|