@xyo-network/chain-services 1.3.17 → 1.3.19

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.
@@ -19,15 +19,9 @@ var BaseService = class extends BaseEmitter {
19
19
  static get singletons() {
20
20
  return globalThis["xyoServiceSingletons"] ?? (globalThis["xyoServiceSingletons"] = {});
21
21
  }
22
- get meter() {
23
- return this.params.meterProvider?.getMeter(this.name);
24
- }
25
22
  get name() {
26
23
  return this.constructor.name;
27
24
  }
28
- get tracer() {
29
- return this.params.traceProvider?.getTracer(this.name);
30
- }
31
25
  static async create(params) {
32
26
  const result = new this(params);
33
27
  if (result.name === "BaseService") throw new Error("Cannot create BaseService");
@@ -46,8 +40,8 @@ var BaseService = class extends BaseEmitter {
46
40
  span(name, fn) {
47
41
  return span(`${this.name}:${name}`, fn, this.tracer);
48
42
  }
49
- async spanAsync(name, fn, timeout) {
50
- return await spanAsync(`${this.name}:${name}`, fn, this.tracer, timeout);
43
+ async spanAsync(name, fn) {
44
+ return await spanAsync(`${this.name}:${name}`, fn, this.tracer);
51
45
  }
52
46
  };
53
47
  var BaseAccountableService = class extends BaseService {
@@ -651,11 +645,12 @@ XyoElectionService = _ts_decorate6([
651
645
  ], XyoElectionService);
652
646
 
653
647
  // src/PendingTransactions/PendingTransactions.ts
648
+ import { ValueType } from "@opentelemetry/api";
654
649
  import { filterAs, filterAsync } from "@xylabs/array";
655
650
  import { assertEx as assertEx7 } from "@xylabs/assert";
656
651
  import { exists } from "@xylabs/exists";
652
+ import { forget } from "@xylabs/forget";
657
653
  import { MemoryArchivist } from "@xyo-network/archivist-memory";
658
- import { hydrateBlock } from "@xyo-network/chain-protocol";
659
654
  import { validateTransaction } from "@xyo-network/chain-validation";
660
655
  import { asOptionalTransactionBoundWitnessWithStorageMeta } from "@xyo-network/xl1-protocol";
661
656
  import { flattenHydratedTransactions, tryHydrateTransaction } from "@xyo-network/xl1-protocol-sdk";
@@ -689,12 +684,25 @@ var XyoPendingTransactionsService = class _XyoPendingTransactionsService extends
689
684
  */
690
685
  RemoveFinalizedTransactions: 1
691
686
  };
692
- // used to remember the last block's transactions that was removed due to it being in the chain
693
- _cleanedToBlock;
694
- // a local archivist that has only the transactions in it (used as queue)
695
- _pendingTransactionsLocalArchivist;
696
- _pendingTransactionsLocalArchivistCursor;
697
- _updatePendingTransactionsLocalArchivistMutex = new Mutex2();
687
+ /**
688
+ * A mutex to ensure that the counting the number of pending transactions is
689
+ * not called concurrently
690
+ */
691
+ _countPendingTransactionsMutex = new Mutex2();
692
+ /**
693
+ * A local Archivist optimized for fast retrieval that stores only validated
694
+ * pending transactions
695
+ */
696
+ _curatedPendingTransactionsArchivist;
697
+ /**
698
+ * The last count of total pending transactions
699
+ */
700
+ _pendingTransactionsCount = 0;
701
+ /**
702
+ * A mutex to ensure that the curated pending transactions archivist is
703
+ * updated in a thread-safe manner
704
+ */
705
+ _updateCuratedPendingTransactionsArchivistMutex = new Mutex2();
698
706
  get chainArchivist() {
699
707
  return assertEx7(this.params.chainArchivist, () => "No completed blocks with data archivist");
700
708
  }
@@ -704,46 +712,41 @@ var XyoPendingTransactionsService = class _XyoPendingTransactionsService extends
704
712
  get pendingTransactionsArchivist() {
705
713
  return assertEx7(this.params.pendingTransactionsArchivist, () => "No pending transactions archivist");
706
714
  }
715
+ get pendingTransactionsCount() {
716
+ forget(this.countPendingTransactions());
717
+ return this._pendingTransactionsCount;
718
+ }
707
719
  get pendingTransactionsLocalArchivist() {
708
- return assertEx7(this._pendingTransactionsLocalArchivist, () => "No pending transactions curate archivist");
720
+ return assertEx7(this._curatedPendingTransactionsArchivist, () => "No pending transactions curated archivist");
709
721
  }
710
722
  get rejectedTransactionsArchivist() {
711
723
  return assertEx7(this.params.rejectedTransactionsArchivist, () => "No rejected transactions archivist");
712
724
  }
713
725
  async createHandler() {
714
726
  await super.createHandler();
715
- this._pendingTransactionsLocalArchivist = await MemoryArchivist.create({
727
+ this._curatedPendingTransactionsArchivist = await MemoryArchivist.create({
716
728
  account: "random"
717
729
  });
718
730
  this.pendingTransactionsArchivist.on("inserted", async ({ payloads }) => {
719
- await this._updatePendingTransactionsLocalArchivistMutex.runExclusive(async () => {
720
- const unprocessedTransactions = await this.filterAlreadyFinalizedTransactions(payloads);
721
- const hydratedUnprocessedTransactions = (await Promise.all(unprocessedTransactions.map((tx) => {
722
- return tryHydrateTransaction(this.pendingTransactionsArchivist, tx._hash);
723
- }))).filter(exists);
724
- const validTransactions = await filterAsync(hydratedUnprocessedTransactions, async (tx) => {
725
- const errors = await validateTransaction(tx, this.chainIdentification.id);
726
- return errors.length > 0 ? false : true;
727
- });
728
- await this.pendingTransactionsLocalArchivist.insert(flattenHydratedTransactions(validTransactions));
729
- }, _XyoPendingTransactionsService.MutexPriority.InsertNewTransactions);
731
+ await this.insertNewTransactions(payloads);
730
732
  });
731
733
  this.chainArchivist.on("inserted", async ({ payloads }) => {
732
- await this._updatePendingTransactionsLocalArchivistMutex.runExclusive(async () => {
733
- const transactions = filterAs(payloads, asOptionalTransactionBoundWitnessWithStorageMeta);
734
- await this.pendingTransactionsLocalArchivist.delete(transactions.map((tx) => tx._hash));
735
- }, _XyoPendingTransactionsService.MutexPriority.RemoveFinalizedTransactions);
734
+ await this.removeFinalizedTransactions(payloads);
736
735
  });
737
736
  this.rejectedTransactionsArchivist.on("inserted", async ({ payloads }) => {
738
- await this._updatePendingTransactionsLocalArchivistMutex.runExclusive(async () => {
739
- const transactions = filterAs(payloads, asOptionalTransactionBoundWitnessWithStorageMeta);
740
- await this.pendingTransactionsLocalArchivist.delete(transactions.map((tx) => tx._hash));
741
- }, _XyoPendingTransactionsService.MutexPriority.RemoveRejectedTransactions);
737
+ await this.removeRejectedTransactions(payloads);
738
+ });
739
+ const pendingTransactionsGauge = this.meter?.createObservableGauge("xyo_pending_transactions_count", {
740
+ description: "The current number of pending transactions",
741
+ valueType: ValueType.INT
742
+ });
743
+ pendingTransactionsGauge?.addCallback((observer) => {
744
+ observer.observe(this.pendingTransactionsCount);
742
745
  });
743
746
  }
744
- async getPendingTransactions(head, limit, timeout = 1e6) {
747
+ async getPendingTransactions(head, limit) {
745
748
  return await this.spanAsync("getPendingTransactions", async () => {
746
- return await this._updatePendingTransactionsLocalArchivistMutex.runExclusive(async () => {
749
+ return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
747
750
  const foundPendingTransactions = [];
748
751
  let cursor;
749
752
  while (foundPendingTransactions.length < limit) {
@@ -760,7 +763,15 @@ var XyoPendingTransactionsService = class _XyoPendingTransactionsService extends
760
763
  const hydratedTransactions = await Promise.all(foundPendingTransactions.map((tx) => tryHydrateTransaction(this.pendingTransactionsLocalArchivist, tx._hash)));
761
764
  return hydratedTransactions.filter(exists);
762
765
  }, _XyoPendingTransactionsService.MutexPriority.ReadTransactions);
763
- }, timeout);
766
+ });
767
+ }
768
+ async countPendingTransactions() {
769
+ if (this._countPendingTransactionsMutex.isLocked()) return;
770
+ await this._countPendingTransactionsMutex.runExclusive(async () => {
771
+ const payloads = await this._curatedPendingTransactionsArchivist?.all() ?? [];
772
+ const pendingTransactions = filterAs(payloads, asOptionalTransactionBoundWitnessWithStorageMeta);
773
+ this._pendingTransactionsCount = pendingTransactions.length;
774
+ });
764
775
  }
765
776
  async filterAlreadyFinalizedTransactions(payloads) {
766
777
  const incomingTransactions = filterAs(payloads, asOptionalTransactionBoundWitnessWithStorageMeta);
@@ -770,28 +781,34 @@ var XyoPendingTransactionsService = class _XyoPendingTransactionsService extends
770
781
  const nonFinalizedTransactions = incomingTransactions.filter((item) => !finalizedTransactionHashes.has(item._hash));
771
782
  return nonFinalizedTransactions;
772
783
  }
773
- async removeAlreadyIncludedTransactions(head, timeout = 1e6) {
774
- return await this.spanAsync("removeAlreadyIncludedTransactions", async () => {
775
- let headPtr = head ?? null;
776
- while (headPtr !== null) {
777
- const [block, payloads] = assertEx7(await hydrateBlock(this.chainArchivist, headPtr), () => `Failed to find head [${head}]`);
778
- if (block._hash === this._cleanedToBlock) break;
779
- await this.pendingTransactionsLocalArchivist.delete(payloads.map((payload) => asOptionalTransactionBoundWitnessWithStorageMeta(payload)?._hash).filter(exists));
780
- this._cleanedToBlock = block._hash;
781
- headPtr = block?.previous;
782
- }
783
- }, timeout);
784
+ async insertNewTransactions(payloads) {
785
+ return await this.spanAsync("InsertNewTransactions", async () => {
786
+ return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
787
+ const unprocessedTransactions = await this.filterAlreadyFinalizedTransactions(payloads);
788
+ const hydratedUnprocessedTransactions = (await Promise.all(unprocessedTransactions.map((tx) => {
789
+ return tryHydrateTransaction(this.pendingTransactionsArchivist, tx._hash);
790
+ }))).filter(exists);
791
+ const validTransactions = await filterAsync(hydratedUnprocessedTransactions, async (tx) => {
792
+ const errors = await validateTransaction(tx, this.chainIdentification.id);
793
+ return errors.length > 0 ? false : true;
794
+ });
795
+ await this.pendingTransactionsLocalArchivist.insert(flattenHydratedTransactions(validTransactions));
796
+ }, _XyoPendingTransactionsService.MutexPriority.InsertNewTransactions);
797
+ });
784
798
  }
785
- async updatePendingTransactionsLocalArchivist(timeout = 1e6) {
786
- return await this.spanAsync("updatePendingTransactionsLocalArchivist", async () => {
787
- if (this._updatePendingTransactionsLocalArchivistMutex.isLocked()) {
788
- console.warn("updatePendingTransactionsLocalArchivist busy - skipping");
789
- return;
790
- }
791
- return await this._updatePendingTransactionsLocalArchivistMutex.runExclusive(async () => {
792
- await Promise.resolve();
793
- });
794
- }, timeout);
799
+ async removeFinalizedTransactions(payloads) {
800
+ return await this.removeTransactions(payloads, "RemoveFinalizedTransactions");
801
+ }
802
+ async removeRejectedTransactions(payloads) {
803
+ return await this.removeTransactions(payloads, "RemoveRejectedTransactions");
804
+ }
805
+ async removeTransactions(payloads, priority) {
806
+ return await this.spanAsync(priority, async () => {
807
+ return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
808
+ const transactions = filterAs(payloads, asOptionalTransactionBoundWitnessWithStorageMeta);
809
+ await this.pendingTransactionsLocalArchivist.delete(transactions.map((tx) => tx._hash));
810
+ }, _XyoPendingTransactionsService.MutexPriority[priority]);
811
+ });
795
812
  }
796
813
  };
797
814
  XyoPendingTransactionsService = _ts_decorate7([