trac-peer 0.4.7 → 0.4.8
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 +1 -1
- package/src/config/config.js +11 -0
- package/src/config/env.js +3 -0
- package/src/msbClient.js +26 -2
- package/src/operations/tx/index.js +5 -0
- package/tests/unit/applyGuards.test.js +59 -0
package/package.json
CHANGED
package/src/config/config.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import b4a from "b4a";
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
|
+
const DEFAULT_MAX_MSB_SIGNED_LENGTH_FUTURE_DELTA = 1_000_000;
|
|
5
|
+
|
|
4
6
|
export class Config {
|
|
5
7
|
#options;
|
|
6
8
|
|
|
@@ -47,6 +49,15 @@ export class Config {
|
|
|
47
49
|
}
|
|
48
50
|
this.maxMsbSignedLength = maxMsbSignedLength;
|
|
49
51
|
|
|
52
|
+
const maxMsbSignedLengthFutureDelta =
|
|
53
|
+
this.#select("maxMsbSignedLengthFutureDelta", options, defaults) ??
|
|
54
|
+
DEFAULT_MAX_MSB_SIGNED_LENGTH_FUTURE_DELTA;
|
|
55
|
+
if (!Number.isSafeInteger(maxMsbSignedLengthFutureDelta) ||
|
|
56
|
+
maxMsbSignedLengthFutureDelta < 0) {
|
|
57
|
+
throw new Error("Peer: maxMsbSignedLengthFutureDelta must be a non-negative safe integer.");
|
|
58
|
+
}
|
|
59
|
+
this.maxMsbSignedLengthFutureDelta = maxMsbSignedLengthFutureDelta;
|
|
60
|
+
|
|
50
61
|
const maxMsbApplyOperationBytes = this.#select("maxMsbApplyOperationBytes", options, defaults);
|
|
51
62
|
if (!Number.isSafeInteger(maxMsbApplyOperationBytes)) {
|
|
52
63
|
throw new Error("Peer: maxMsbApplyOperationBytes must be a safe integer.");
|
package/src/config/env.js
CHANGED
|
@@ -15,6 +15,7 @@ const configData = {
|
|
|
15
15
|
txPoolMaxSize: 1_000,
|
|
16
16
|
maxTxDelay: 60,
|
|
17
17
|
maxMsbSignedLength: 1_000_000_000,
|
|
18
|
+
maxMsbSignedLengthFutureDelta: 1_000_000,
|
|
18
19
|
maxMsbApplyOperationBytes: 1024 * 1024,
|
|
19
20
|
enableInteractiveMode: true,
|
|
20
21
|
enableBackgroundTasks: true,
|
|
@@ -39,6 +40,7 @@ const configData = {
|
|
|
39
40
|
txPoolMaxSize: 1_000,
|
|
40
41
|
maxTxDelay: 60,
|
|
41
42
|
maxMsbSignedLength: 1_000_000_000,
|
|
43
|
+
maxMsbSignedLengthFutureDelta: 1_000_000,
|
|
42
44
|
maxMsbApplyOperationBytes: 1024 * 1024,
|
|
43
45
|
enableInteractiveMode: true,
|
|
44
46
|
enableBackgroundTasks: true,
|
|
@@ -63,6 +65,7 @@ const configData = {
|
|
|
63
65
|
txPoolMaxSize: 1_000,
|
|
64
66
|
maxTxDelay: 60,
|
|
65
67
|
maxMsbSignedLength: 1_000_000_000,
|
|
68
|
+
maxMsbSignedLengthFutureDelta: 1_000_000,
|
|
66
69
|
maxMsbApplyOperationBytes: 1024 * 1024,
|
|
67
70
|
enableInteractiveMode: true,
|
|
68
71
|
enableBackgroundTasks: false,
|
package/src/msbClient.js
CHANGED
|
@@ -97,11 +97,35 @@ export class MsbClient extends ReadyResource {
|
|
|
97
97
|
return await this.#msb.network.tryConnect(pubKeyHex, role);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
async waitForSignedLengthAtLeast(targetSignedLength) {
|
|
100
|
+
async waitForSignedLengthAtLeast(targetSignedLength, { pollMs = 1_000 } = {}) {
|
|
101
101
|
const core = this.#msb.state?.base?.view?.core ?? null;
|
|
102
102
|
if (!core) throw new Error('MSB view core not available.');
|
|
103
|
+
if (!Number.isSafeInteger(targetSignedLength) || targetSignedLength < 0) {
|
|
104
|
+
throw new Error('Invalid MSB signed length target.');
|
|
105
|
+
}
|
|
106
|
+
if (!Number.isSafeInteger(pollMs) || pollMs < 1) {
|
|
107
|
+
throw new Error('Invalid MSB signed length wait poll interval.');
|
|
108
|
+
}
|
|
103
109
|
while (core.signedLength < targetSignedLength) {
|
|
104
|
-
await new Promise((resolve) =>
|
|
110
|
+
await new Promise((resolve) => {
|
|
111
|
+
const onAppend = () => {
|
|
112
|
+
cleanup();
|
|
113
|
+
resolve();
|
|
114
|
+
};
|
|
115
|
+
const cleanup = () => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
if (typeof core.off === 'function') {
|
|
118
|
+
core.off('append', onAppend);
|
|
119
|
+
} else if (typeof core.removeListener === 'function') {
|
|
120
|
+
core.removeListener('append', onAppend);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
cleanup();
|
|
125
|
+
resolve();
|
|
126
|
+
}, pollMs);
|
|
127
|
+
core.once('append', onAppend);
|
|
128
|
+
});
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
131
|
|
|
@@ -36,6 +36,11 @@ export class TxOperation {
|
|
|
36
36
|
if(false === this.#validator.validate(op)) return;
|
|
37
37
|
// Stall guard: don't allow a writer to pin apply waiting on an absurd MSB height
|
|
38
38
|
if (op.value.msbsl > this.#config.maxMsbSignedLength) return;
|
|
39
|
+
const localMsbSignedLength = this.#msbClient.getSignedLength();
|
|
40
|
+
if (localMsbSignedLength > 0 &&
|
|
41
|
+
op.value.msbsl > localMsbSignedLength + this.#config.maxMsbSignedLengthFutureDelta) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
39
44
|
// Wait for local MSB view to reach the referenced signed length
|
|
40
45
|
await this.#msbClient.waitForSignedLengthAtLeast(op.value.msbsl);
|
|
41
46
|
// Fetch MSB apply-op at msbsl by tx key (op.key = tx hash)
|
|
@@ -164,6 +164,65 @@ test('apply: tx msbsl stall guard skips waiting', async (t) => {
|
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
test('apply: tx msbsl relative future guard skips waiting', async (t) => {
|
|
168
|
+
await withTempDir(async ({ storesDirectory }) => {
|
|
169
|
+
const msbBootstrapBuf = b4a.alloc(32).fill(7);
|
|
170
|
+
const msb = makeMsbStub({
|
|
171
|
+
msbBootstrapBuf,
|
|
172
|
+
signedLength: 100,
|
|
173
|
+
async getEntry() {
|
|
174
|
+
return null;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
msb.state.base.view.core.once = () => {
|
|
179
|
+
throw new Error('apply should not wait for a far-future msbsl');
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const storeName = 'peer-relative-stall-guard';
|
|
183
|
+
const wallet = await prepareWallet(storesDirectory, storeName);
|
|
184
|
+
const config = createConfig(ENV.DEVELOPMENT, {
|
|
185
|
+
storesDirectory,
|
|
186
|
+
storeName,
|
|
187
|
+
maxMsbSignedLength: 1_000_000_000,
|
|
188
|
+
maxMsbSignedLengthFutureDelta: 10,
|
|
189
|
+
});
|
|
190
|
+
const peer = new Peer({
|
|
191
|
+
config,
|
|
192
|
+
msb,
|
|
193
|
+
protocol: TestProtocol,
|
|
194
|
+
contract: TestContract,
|
|
195
|
+
wallet,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
await peer.ready();
|
|
200
|
+
|
|
201
|
+
const txHashHex = makeHex32(1);
|
|
202
|
+
const op = {
|
|
203
|
+
type: 'tx',
|
|
204
|
+
key: txHashHex,
|
|
205
|
+
value: {
|
|
206
|
+
dispatch: { type: 'ping', value: { msg: 'hi' } },
|
|
207
|
+
msbsl: 111,
|
|
208
|
+
ipk: makeHex32(2),
|
|
209
|
+
wp: makeHex32(3),
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const timeout = new Promise((_, reject) =>
|
|
214
|
+
setTimeout(() => reject(new Error('append timed out (possible apply stall)')), 2000)
|
|
215
|
+
);
|
|
216
|
+
await Promise.race([peer.base.append(op), timeout]);
|
|
217
|
+
|
|
218
|
+
const txl = await peer.bee.get('txl');
|
|
219
|
+
t.is(txl, null, 'tx should not be indexed when msbsl exceeds the local future window');
|
|
220
|
+
} finally {
|
|
221
|
+
await closePeer(peer);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
167
226
|
test('apply: tx MSB payload size guard blocks otherwise-valid tx', async (t) => {
|
|
168
227
|
await withTempDir(async ({ storesDirectory }) => {
|
|
169
228
|
const msbBootstrapBuf = b4a.alloc(32).fill(7);
|