@xyo-network/xl1-protocol-sdk 1.30.0 → 1.30.1
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/dist/neutral/index.mjs +143 -17
- package/dist/neutral/index.mjs.map +1 -1
- package/dist/neutral/simple/mempool/SimpleMempoolRunner.d.ts +14 -0
- package/dist/neutral/simple/mempool/SimpleMempoolRunner.d.ts.map +1 -1
- package/dist/neutral/simple/mempool/SimpleMempoolViewer.d.ts +28 -0
- package/dist/neutral/simple/mempool/SimpleMempoolViewer.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/neutral/index.mjs
CHANGED
|
@@ -4973,18 +4973,26 @@ import {
|
|
|
4973
4973
|
isSignedHydratedBlockWithHashMeta,
|
|
4974
4974
|
isSignedHydratedTransactionWithHashMeta,
|
|
4975
4975
|
MempoolRunnerMoniker,
|
|
4976
|
+
MempoolViewerMoniker,
|
|
4976
4977
|
TransactionRejectionSchema,
|
|
4977
4978
|
TransactionValidationViewerMoniker
|
|
4978
4979
|
} from "@xyo-network/xl1-protocol-lib";
|
|
4979
4980
|
import { Mutex } from "async-mutex";
|
|
4980
4981
|
var DEFAULT_SYNC_INTERVAL = 3e4;
|
|
4981
4982
|
var DEFAULT_SYNC_LIMIT = 100;
|
|
4983
|
+
var ENFORCE_CAP_BATCH_SIZE = 1e3;
|
|
4984
|
+
function isDemotionAware(viewer) {
|
|
4985
|
+
if (!viewer) return false;
|
|
4986
|
+
const candidate = viewer;
|
|
4987
|
+
return typeof candidate.forget === "function" && typeof candidate.getEvictionPriorityOrder === "function";
|
|
4988
|
+
}
|
|
4982
4989
|
var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
4983
4990
|
moniker = SimpleMempoolRunner.defaultMoniker;
|
|
4984
4991
|
_blockValidationViewer;
|
|
4985
4992
|
_chainContractViewer;
|
|
4986
4993
|
_deadLetterQueueRunner;
|
|
4987
4994
|
_finalizationViewer;
|
|
4995
|
+
_mempoolViewer;
|
|
4988
4996
|
_transactionValidationViewer;
|
|
4989
4997
|
_syncMutex = new Mutex();
|
|
4990
4998
|
_syncTimerId = null;
|
|
@@ -5003,6 +5011,9 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5003
5011
|
get maxExpAhead() {
|
|
5004
5012
|
return this.params.maxExpAhead ?? DEFAULT_MAX_EXP_AHEAD;
|
|
5005
5013
|
}
|
|
5014
|
+
get maxPendingTransactions() {
|
|
5015
|
+
return this.params.maxPendingTransactions ?? 0;
|
|
5016
|
+
}
|
|
5006
5017
|
get pendingBlocksArchivist() {
|
|
5007
5018
|
return this.params.pendingBlocksArchivist;
|
|
5008
5019
|
}
|
|
@@ -5035,6 +5046,7 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5035
5046
|
this._finalizationViewer = await this.locator.getInstance(FinalizationViewerMoniker4);
|
|
5036
5047
|
this._transactionValidationViewer = await this.locator.getInstance(TransactionValidationViewerMoniker);
|
|
5037
5048
|
this._deadLetterQueueRunner = await this.locator.tryGetInstance(DeadLetterQueueRunnerMoniker);
|
|
5049
|
+
this._mempoolViewer = await this.locator.tryGetInstance(MempoolViewerMoniker);
|
|
5038
5050
|
}
|
|
5039
5051
|
async prunePendingBlocks({
|
|
5040
5052
|
batchSize = 10,
|
|
@@ -5149,6 +5161,7 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5149
5161
|
pruned += pruneHashes.length;
|
|
5150
5162
|
total += batch.length;
|
|
5151
5163
|
await this.pendingTransactionsArchivist.delete(pruneHashes);
|
|
5164
|
+
this.forgetBundleHashes(pruneHashes);
|
|
5152
5165
|
const pruneSet = new Set(pruneHashes);
|
|
5153
5166
|
const lastSurvivor = batch.findLast((p) => !pruneSet.has(p._hash));
|
|
5154
5167
|
cursor = lastSurvivor?._sequence ?? cursor;
|
|
@@ -5158,8 +5171,7 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5158
5171
|
order: "desc"
|
|
5159
5172
|
});
|
|
5160
5173
|
}
|
|
5161
|
-
this.
|
|
5162
|
-
return [pruned, total];
|
|
5174
|
+
return this.finalizePruneTransactionsResult(pruned, total);
|
|
5163
5175
|
}
|
|
5164
5176
|
async submitBlocks(blocks) {
|
|
5165
5177
|
const bundles = await Promise.all(blocks.map(async ([bw, payloads]) => {
|
|
@@ -5205,6 +5217,7 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5205
5217
|
}
|
|
5206
5218
|
const bundles = hashedTransactions.map((tx) => hydratedTransactionToPayloadBundle(tx));
|
|
5207
5219
|
const inserted = await this.pendingTransactionsArchivist.insert(bundles);
|
|
5220
|
+
await this.enforceCap();
|
|
5208
5221
|
return inserted.map((p) => p._hash);
|
|
5209
5222
|
}
|
|
5210
5223
|
async startHandler() {
|
|
@@ -5223,6 +5236,50 @@ var SimpleMempoolRunner = class extends AbstractCreatableProvider {
|
|
|
5223
5236
|
this._syncTimerId = null;
|
|
5224
5237
|
}
|
|
5225
5238
|
}
|
|
5239
|
+
async collectAllPendingTransactionBundles() {
|
|
5240
|
+
const all = [];
|
|
5241
|
+
let cursor;
|
|
5242
|
+
while (true) {
|
|
5243
|
+
const batch = await this.pendingTransactionsArchivist.next({
|
|
5244
|
+
limit: ENFORCE_CAP_BATCH_SIZE,
|
|
5245
|
+
cursor,
|
|
5246
|
+
order: "asc"
|
|
5247
|
+
});
|
|
5248
|
+
if (batch.length === 0) break;
|
|
5249
|
+
all.push(...batch);
|
|
5250
|
+
cursor = batch.at(-1)?._sequence;
|
|
5251
|
+
if (batch.length < ENFORCE_CAP_BATCH_SIZE) break;
|
|
5252
|
+
}
|
|
5253
|
+
return all;
|
|
5254
|
+
}
|
|
5255
|
+
async enforceCap() {
|
|
5256
|
+
const cap = this.maxPendingTransactions;
|
|
5257
|
+
if (cap <= 0) return;
|
|
5258
|
+
const all = await this.collectAllPendingTransactionBundles();
|
|
5259
|
+
if (all.length <= cap) return;
|
|
5260
|
+
const excess = all.length - cap;
|
|
5261
|
+
const bundleHashesOldestFirst = all.map((p) => p._hash);
|
|
5262
|
+
const orderedForEviction = this.orderForEviction(bundleHashesOldestFirst);
|
|
5263
|
+
const toEvict = orderedForEviction.slice(0, excess);
|
|
5264
|
+
await this.pendingTransactionsArchivist.delete(toEvict);
|
|
5265
|
+
this.forgetBundleHashes(toEvict);
|
|
5266
|
+
this.logger?.debug(`enforceCap evicted ${toEvict.length} bundles (pool=${all.length}, cap=${cap})`);
|
|
5267
|
+
}
|
|
5268
|
+
async finalizePruneTransactionsResult(pruned, total) {
|
|
5269
|
+
this.logger?.debug(`prunePendingTransactions completed: pruned=${pruned}, totalChecked=${total}`);
|
|
5270
|
+
await this.enforceCap();
|
|
5271
|
+
return [pruned, total];
|
|
5272
|
+
}
|
|
5273
|
+
forgetBundleHashes(bundleHashes) {
|
|
5274
|
+
if (bundleHashes.length === 0) return;
|
|
5275
|
+
if (isDemotionAware(this._mempoolViewer)) this._mempoolViewer.forget(bundleHashes);
|
|
5276
|
+
}
|
|
5277
|
+
orderForEviction(bundleHashesOldestFirst) {
|
|
5278
|
+
if (isDemotionAware(this._mempoolViewer)) {
|
|
5279
|
+
return this._mempoolViewer.getEvictionPriorityOrder(bundleHashesOldestFirst);
|
|
5280
|
+
}
|
|
5281
|
+
return bundleHashesOldestFirst;
|
|
5282
|
+
}
|
|
5226
5283
|
async routeRejectedTransaction(transaction, errors) {
|
|
5227
5284
|
if (!this._deadLetterQueueRunner) return;
|
|
5228
5285
|
const rejectionErrors = errors.map((e) => ({
|
|
@@ -5335,13 +5392,22 @@ import {
|
|
|
5335
5392
|
} from "@xylabs/sdk-js";
|
|
5336
5393
|
import { isHashMeta as isHashMeta2, isPayloadBundle as isPayloadBundle2 } from "@xyo-network/sdk-js";
|
|
5337
5394
|
import {
|
|
5338
|
-
MempoolViewerMoniker,
|
|
5395
|
+
MempoolViewerMoniker as MempoolViewerMoniker2,
|
|
5339
5396
|
WindowedBlockViewerMoniker
|
|
5340
5397
|
} from "@xyo-network/xl1-protocol-lib";
|
|
5341
5398
|
var DEFAULT_MEMPOOL_SELECTION_RATIO = 0.66;
|
|
5399
|
+
var DEFAULT_DEMOTION_THRESHOLD = 3;
|
|
5400
|
+
var DEFAULT_HANDOUT_STATS_TTL_BLOCKS = 1e3;
|
|
5342
5401
|
var SimpleMempoolViewer = class extends AbstractCreatableProvider {
|
|
5343
5402
|
moniker = SimpleMempoolViewer.defaultMoniker;
|
|
5403
|
+
_handoutStats = /* @__PURE__ */ new Map();
|
|
5344
5404
|
_windowedBlockViewer;
|
|
5405
|
+
get demotionThreshold() {
|
|
5406
|
+
return this.params.demotionThreshold ?? DEFAULT_DEMOTION_THRESHOLD;
|
|
5407
|
+
}
|
|
5408
|
+
get handoutStatsTtlBlocks() {
|
|
5409
|
+
return this.params.handoutStatsTtlBlocks ?? DEFAULT_HANDOUT_STATS_TTL_BLOCKS;
|
|
5410
|
+
}
|
|
5345
5411
|
get pendingBlocksArchivist() {
|
|
5346
5412
|
return this.params.pendingBlocksArchivist;
|
|
5347
5413
|
}
|
|
@@ -5355,6 +5421,32 @@ var SimpleMempoolViewer = class extends AbstractCreatableProvider {
|
|
|
5355
5421
|
await super.createHandler();
|
|
5356
5422
|
this._windowedBlockViewer = await this.locator.getInstance(WindowedBlockViewerMoniker);
|
|
5357
5423
|
}
|
|
5424
|
+
/** Drop handout stats for the given bundle hashes. Called when a bundle has been evicted or otherwise removed from the pool. */
|
|
5425
|
+
forget(bundleHashes) {
|
|
5426
|
+
for (const hash of bundleHashes) this._handoutStats.delete(hash);
|
|
5427
|
+
}
|
|
5428
|
+
/** Return the subset of the given bundle hashes that are currently considered demoted. */
|
|
5429
|
+
getDemotedBundleHashes(bundleHashes) {
|
|
5430
|
+
return bundleHashes.filter((hash) => this.isDemoted(hash));
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* Return the bundle hashes in the order they should be evicted under size pressure.
|
|
5434
|
+
* Demoted entries come first, sorted by handouts descending; the remainder is left as-is
|
|
5435
|
+
* so the caller can append by FIFO sequence order.
|
|
5436
|
+
*/
|
|
5437
|
+
getEvictionPriorityOrder(bundleHashes) {
|
|
5438
|
+
const demoted = [];
|
|
5439
|
+
const nonDemoted = [];
|
|
5440
|
+
for (const hash of bundleHashes) {
|
|
5441
|
+
if (this.isDemoted(hash)) demoted.push(hash);
|
|
5442
|
+
else nonDemoted.push(hash);
|
|
5443
|
+
}
|
|
5444
|
+
demoted.sort((a, b) => (this._handoutStats.get(b)?.handouts ?? 0) - (this._handoutStats.get(a)?.handouts ?? 0));
|
|
5445
|
+
return [...demoted, ...nonDemoted];
|
|
5446
|
+
}
|
|
5447
|
+
getHandoutStats(bundleHash) {
|
|
5448
|
+
return this._handoutStats.get(bundleHash);
|
|
5449
|
+
}
|
|
5358
5450
|
async pendingBlocks({ cursor: providedCursor } = {}) {
|
|
5359
5451
|
let cursor = void 0;
|
|
5360
5452
|
if (isHash2(providedCursor)) {
|
|
@@ -5397,6 +5489,7 @@ var SimpleMempoolViewer = class extends AbstractCreatableProvider {
|
|
|
5397
5489
|
})
|
|
5398
5490
|
)).filter(exists9);
|
|
5399
5491
|
const currentBlock = await this.windowedBlockViewer.currentBlock();
|
|
5492
|
+
const currentBlockNumber = currentBlock[0].block;
|
|
5400
5493
|
const evaluated = await Promise.all(
|
|
5401
5494
|
hydratedWithBundle.map(async ({ bundle: bundle3, tx }) => ({
|
|
5402
5495
|
bundle: bundle3,
|
|
@@ -5410,25 +5503,43 @@ var SimpleMempoolViewer = class extends AbstractCreatableProvider {
|
|
|
5410
5503
|
await Promise.all(
|
|
5411
5504
|
deletionCandidates.map(async ({ bundle: bundle3, tx }) => {
|
|
5412
5505
|
await this.deleteBundledTransaction(bundle3);
|
|
5506
|
+
this._handoutStats.delete(bundle3._hash);
|
|
5413
5507
|
this.logger?.debug(`Purged completed/expired bundled transaction: ${bundle3._hash}/${tx[0]._hash}`);
|
|
5414
5508
|
})
|
|
5415
5509
|
);
|
|
5416
|
-
|
|
5417
|
-
|
|
5510
|
+
this.gcHandoutStats(currentBlockNumber);
|
|
5511
|
+
const inclusionCandidates = (await Promise.all(validTransactions.map(async ({ bundle: bundle3, tx }) => {
|
|
5512
|
+
if (await this.isInclusionCandidate(tx, currentBlock, false)) return { bundle: bundle3, tx };
|
|
5418
5513
|
}))).filter(exists9);
|
|
5419
5514
|
const selectionRatio = this.params.mempoolSelectionRatio ?? DEFAULT_MEMPOOL_SELECTION_RATIO;
|
|
5420
|
-
const
|
|
5421
|
-
const
|
|
5422
|
-
const
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5515
|
+
const nonDemoted = inclusionCandidates.filter(({ bundle: bundle3 }) => !this.isDemoted(bundle3._hash));
|
|
5516
|
+
const demoted = inclusionCandidates.filter(({ bundle: bundle3 }) => this.isDemoted(bundle3._hash));
|
|
5517
|
+
const primary = this.selectWithRatio(nonDemoted, limit, selectionRatio);
|
|
5518
|
+
const topupNeeded = limit - primary.length;
|
|
5519
|
+
const topup = topupNeeded > 0 ? this.selectWithRatio(demoted, topupNeeded, selectionRatio) : [];
|
|
5520
|
+
let combined = [...primary, ...topup];
|
|
5521
|
+
if (combined.length === 0 && inclusionCandidates.length > 0) {
|
|
5522
|
+
combined = deduplicateWithBundleBySigner(inclusionCandidates).slice(0, 1);
|
|
5523
|
+
}
|
|
5524
|
+
for (const { bundle: bundle3 } of combined) {
|
|
5525
|
+
this.recordHandout(bundle3._hash, currentBlockNumber);
|
|
5526
|
+
}
|
|
5527
|
+
this.logger?.debug(`Inclusion candidates: ${inclusionCandidates.length} (nonDemoted=${nonDemoted.length}, demoted=${demoted.length}); returning ${combined.length}`);
|
|
5528
|
+
return combined.map(({ tx }) => tx);
|
|
5529
|
+
}
|
|
5530
|
+
isDemoted(bundleHash) {
|
|
5531
|
+
const stats = this._handoutStats.get(bundleHash);
|
|
5532
|
+
return stats !== void 0 && stats.handouts >= this.demotionThreshold;
|
|
5428
5533
|
}
|
|
5429
5534
|
async deleteBundledTransaction(bundle3) {
|
|
5430
5535
|
await this.pendingTransactionsArchivist.delete([bundle3._hash]);
|
|
5431
5536
|
}
|
|
5537
|
+
gcHandoutStats(currentBlockNumber) {
|
|
5538
|
+
const ttl = this.handoutStatsTtlBlocks;
|
|
5539
|
+
for (const [hash, stats] of this._handoutStats) {
|
|
5540
|
+
if (currentBlockNumber - stats.firstHandoutAt > ttl) this._handoutStats.delete(hash);
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5432
5543
|
/**
|
|
5433
5544
|
* Evaluates a transaction to determine if it should be purged from the mempool.
|
|
5434
5545
|
* @param tx The transaction to evaluate
|
|
@@ -5461,16 +5572,31 @@ var SimpleMempoolViewer = class extends AbstractCreatableProvider {
|
|
|
5461
5572
|
if (checkForDeletable && await this.isDeletable(tx, currentBlock)) return false;
|
|
5462
5573
|
return true;
|
|
5463
5574
|
}
|
|
5575
|
+
recordHandout(bundleHash, currentBlockNumber) {
|
|
5576
|
+
const existing = this._handoutStats.get(bundleHash);
|
|
5577
|
+
if (existing) {
|
|
5578
|
+
existing.handouts += 1;
|
|
5579
|
+
} else {
|
|
5580
|
+
this._handoutStats.set(bundleHash, { handouts: 1, firstHandoutAt: currentBlockNumber });
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
selectWithRatio(group, take, selectionRatio) {
|
|
5584
|
+
if (take <= 0 || group.length === 0) return [];
|
|
5585
|
+
const maxByRatio = Math.ceil(group.length * selectionRatio);
|
|
5586
|
+
const effectiveLimit = Math.min(take, maxByRatio);
|
|
5587
|
+
const randomSelected = group.filter(() => Math.random() < selectionRatio);
|
|
5588
|
+
return deduplicateWithBundleBySigner(randomSelected).slice(0, effectiveLimit);
|
|
5589
|
+
}
|
|
5464
5590
|
};
|
|
5465
|
-
__publicField(SimpleMempoolViewer, "defaultMoniker",
|
|
5591
|
+
__publicField(SimpleMempoolViewer, "defaultMoniker", MempoolViewerMoniker2);
|
|
5466
5592
|
__publicField(SimpleMempoolViewer, "dependencies", [WindowedBlockViewerMoniker]);
|
|
5467
|
-
__publicField(SimpleMempoolViewer, "monikers", [
|
|
5593
|
+
__publicField(SimpleMempoolViewer, "monikers", [MempoolViewerMoniker2]);
|
|
5468
5594
|
SimpleMempoolViewer = __decorateClass([
|
|
5469
5595
|
creatableProvider()
|
|
5470
5596
|
], SimpleMempoolViewer);
|
|
5471
|
-
function
|
|
5597
|
+
function deduplicateWithBundleBySigner(items) {
|
|
5472
5598
|
const seen = /* @__PURE__ */ new Set();
|
|
5473
|
-
return
|
|
5599
|
+
return items.filter(({ tx }) => {
|
|
5474
5600
|
const key = tx[0].addresses.toSorted().join(",");
|
|
5475
5601
|
if (seen.has(key)) return false;
|
|
5476
5602
|
seen.add(key);
|