@xmbl/state-machine 0.1.1 → 0.1.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmbl/state-machine",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -21,6 +21,7 @@ export class StateMachine extends EventEmitter {
21
21
  this.shards = [];
22
22
  this.totalShards = options.totalShards || 4;
23
23
  this.diffs = [];
24
+ this._diffIndex = new Map();
24
25
  this.transactionLog = [];
25
26
 
26
27
  // Initialize shards
@@ -76,7 +77,7 @@ export class StateMachine extends EventEmitter {
76
77
  const diffData = JSON.parse(value.toString());
77
78
  const diff = new StateDiff(diffData.txId, diffData.changes);
78
79
  diff.timestamp = diffData.timestamp;
79
- this.diffs.push(diff);
80
+ this._recordDiff(diff);
80
81
  loaded.push(diff);
81
82
  }
82
83
  loaded.sort((a, b) => (a.timestamp ?? 0) - (b.timestamp ?? 0) || String(a.txId).localeCompare(String(b.txId)));
@@ -142,6 +143,23 @@ export class StateMachine extends EventEmitter {
142
143
  // Every transaction type IS a state change, so each maps to its natural key space. Keys are namespaced by
143
144
  // type so two kinds can never collide, and values carry only consensus-derived fields — nothing node-local,
144
145
  // because the state root is a cross-node commitment.
146
+ // applied_tx_count COUNTED APPLY CALLS, NOT TRANSACTIONS. Every apply site appended to `this.diffs`
147
+ // unconditionally while persisting to `diff:<txId>`, a key that OVERWRITES. So a second apply_backfill over
148
+ // an unchanged block set left the verkle root identical (the root is a function of the final key SET) and
149
+ // still added one array entry per block: MEASURED on node xmb0844bbed..., 82910 -> 104446 over the same
150
+ // 21536 blocks, then 88486 after a restart reloaded the deduped rows from disk. A monitoring number that
151
+ // moves on a repeated no-op cannot tell work from a replay, and /api/v1/xmbl/status publishes this one.
152
+ // Upsert by txId so the in-memory set matches the durable one it was always meant to mirror.
153
+ //
154
+ // REPLACE, don't skip: re-applying a txId with different changes is a legitimate later state for that key
155
+ // and the disk already resolves it that way. Skipping would leave the array disagreeing with the tree.
156
+ _recordDiff(diff) {
157
+ const at = this._diffIndex.get(diff.txId);
158
+ if (at === undefined) { this._diffIndex.set(diff.txId, this.diffs.length); this.diffs.push(diff); return true; }
159
+ this.diffs[at] = diff;
160
+ return false;
161
+ }
162
+
145
163
  _stateChangesFor(block) {
146
164
  const tx = block?.tx;
147
165
  if (!tx || typeof tx !== 'object') return null;
@@ -182,7 +200,7 @@ export class StateMachine extends EventEmitter {
182
200
  if (!changes || !Object.keys(changes).length) return;
183
201
  try {
184
202
  const diff = new StateDiff(block.id, changes);
185
- this.diffs.push(diff);
203
+ this._recordDiff(diff);
186
204
  for (const [key, value] of Object.entries(changes)) {
187
205
  await this.stateTree.insert(key, value);
188
206
  }
@@ -231,7 +249,7 @@ export class StateMachine extends EventEmitter {
231
249
  if (!changes || !Object.keys(changes).length) { out.skipped++; continue; }
232
250
  try {
233
251
  const diff = new StateDiff(block.id, changes);
234
- this.diffs.push(diff);
252
+ this._recordDiff(diff);
235
253
  for (const [key, val] of Object.entries(changes)) await this.stateTree.insert(key, val);
236
254
  if (this._dbOpen !== false) {
237
255
  try { await this.db.put(`diff:${block.id}`, diff.serialize()); } catch { /* in-memory fallback */ }
@@ -254,6 +272,7 @@ export class StateMachine extends EventEmitter {
254
272
  const out = { requested: list.length, applied: 0, skipped: 0, state_root: null, started: true };
255
273
  await this.stateTree.clear();
256
274
  this.diffs = [];
275
+ this._diffIndex.clear();
257
276
  for (const a of list) {
258
277
  if (!a || !a.event || !a.hash) { out.skipped++; continue; }
259
278
  try {