trac-peer 0.4.6 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trac-peer",
3
3
  "main": "src/index.js",
4
- "version": "0.4.6",
4
+ "version": "0.4.8",
5
5
  "type": "module",
6
6
  "pear": {
7
7
  "name": "trac-peer",
@@ -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) => core.once('append', 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);