@xyo-network/chain-services 1.5.35 → 1.5.36

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.
Files changed (69) hide show
  1. package/dist/neutral/index.mjs +428 -259
  2. package/dist/neutral/index.mjs.map +1 -1
  3. package/dist/types/AccountBalance/ChainAccountBalanceServiceV2.d.ts +14 -0
  4. package/dist/types/AccountBalance/ChainAccountBalanceServiceV2.d.ts.map +1 -0
  5. package/dist/types/AccountBalance/XyoChainAccountBalanceService.d.ts +12 -8
  6. package/dist/types/AccountBalance/XyoChainAccountBalanceService.d.ts.map +1 -1
  7. package/dist/types/AccountBalance/accountBalanceServiceFromArchivist.d.ts +3 -2
  8. package/dist/types/AccountBalance/accountBalanceServiceFromArchivist.d.ts.map +1 -1
  9. package/dist/types/AccountBalance/index.d.ts +1 -0
  10. package/dist/types/AccountBalance/index.d.ts.map +1 -1
  11. package/dist/types/BaseService.d.ts +7 -8
  12. package/dist/types/BaseService.d.ts.map +1 -1
  13. package/dist/types/BlockProducer/XyoBlockProducer.d.ts +28 -9
  14. package/dist/types/BlockProducer/XyoBlockProducer.d.ts.map +1 -1
  15. package/dist/types/BlockReward/EvmBlockRewardService.d.ts +2 -1
  16. package/dist/types/BlockReward/EvmBlockRewardService.d.ts.map +1 -1
  17. package/dist/types/BlockReward/XyoBlockRewardService.d.ts +2 -1
  18. package/dist/types/BlockReward/XyoBlockRewardService.d.ts.map +1 -1
  19. package/dist/types/ChainBlockNumberIteration/ChainBlockNumberIterationService.d.ts +4 -2
  20. package/dist/types/ChainBlockNumberIteration/ChainBlockNumberIterationService.d.ts.map +1 -1
  21. package/dist/types/ChainBlockNumberIteration/model/Params.d.ts +8 -0
  22. package/dist/types/ChainBlockNumberIteration/model/Params.d.ts.map +1 -0
  23. package/dist/types/ChainBlockNumberIteration/model/index.d.ts +1 -0
  24. package/dist/types/ChainBlockNumberIteration/model/index.d.ts.map +1 -1
  25. package/dist/types/ChainIndexService.d.ts +8 -5
  26. package/dist/types/ChainIndexService.d.ts.map +1 -1
  27. package/dist/types/ChainValidator/XyoValidator.d.ts +20 -9
  28. package/dist/types/ChainValidator/XyoValidator.d.ts.map +1 -1
  29. package/dist/types/Election/XyoElectionService.d.ts +2 -2
  30. package/dist/types/Election/XyoElectionService.d.ts.map +1 -1
  31. package/dist/types/Params.d.ts +9 -0
  32. package/dist/types/Params.d.ts.map +1 -0
  33. package/dist/types/PendingTransactions/PendingTransactions.d.ts +7 -5
  34. package/dist/types/PendingTransactions/PendingTransactions.d.ts.map +1 -1
  35. package/dist/types/StakeIntent/XyoStakeIntentService.d.ts +10 -6
  36. package/dist/types/StakeIntent/XyoStakeIntentService.d.ts.map +1 -1
  37. package/dist/types/Staker/Evm/Evm.d.ts +5 -5
  38. package/dist/types/Staker/Evm/Evm.d.ts.map +1 -1
  39. package/dist/types/Staker/Memory/Memory.d.ts +31 -0
  40. package/dist/types/Staker/Memory/Memory.d.ts.map +1 -0
  41. package/dist/types/Staker/Memory/index.d.ts +2 -0
  42. package/dist/types/Staker/Memory/index.d.ts.map +1 -0
  43. package/dist/types/Staker/index.d.ts +1 -0
  44. package/dist/types/Staker/index.d.ts.map +1 -1
  45. package/dist/types/index.d.ts +1 -0
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/package.json +45 -44
  48. package/src/AccountBalance/ChainAccountBalanceServiceV2.ts +33 -0
  49. package/src/AccountBalance/XyoChainAccountBalanceService.ts +23 -19
  50. package/src/AccountBalance/accountBalanceServiceFromArchivist.ts +67 -35
  51. package/src/AccountBalance/index.ts +1 -0
  52. package/src/BaseService.ts +10 -23
  53. package/src/BlockProducer/XyoBlockProducer.ts +53 -30
  54. package/src/BlockReward/EvmBlockRewardService.ts +5 -5
  55. package/src/BlockReward/XyoBlockRewardService.ts +5 -3
  56. package/src/ChainBlockNumberIteration/ChainBlockNumberIterationService.ts +5 -3
  57. package/src/ChainBlockNumberIteration/model/Params.ts +9 -0
  58. package/src/ChainBlockNumberIteration/model/index.ts +1 -0
  59. package/src/ChainIndexService.ts +5 -5
  60. package/src/ChainValidator/XyoValidator.ts +9 -8
  61. package/src/Election/XyoElectionService.ts +5 -7
  62. package/src/Params.ts +9 -0
  63. package/src/PendingTransactions/PendingTransactions.ts +127 -63
  64. package/src/StakeIntent/XyoStakeIntentService.ts +30 -17
  65. package/src/Staker/Evm/Evm.ts +9 -12
  66. package/src/Staker/Memory/Memory.ts +90 -0
  67. package/src/Staker/Memory/index.ts +1 -0
  68. package/src/Staker/index.ts +1 -0
  69. package/src/index.ts +1 -0
@@ -1,37 +1,39 @@
1
1
  import { ValueType } from '@opentelemetry/api'
2
- import { filterAsync } from '@xylabs/array'
2
+ import { filterAs, filterAsync } from '@xylabs/array'
3
3
  import { assertEx } from '@xylabs/assert'
4
+ import { creatable } from '@xylabs/creatable'
4
5
  import { exists } from '@xylabs/exists'
5
6
  import { forget } from '@xylabs/forget'
6
- import { Hash } from '@xylabs/hex'
7
+ import { Address, Hash } from '@xylabs/hex'
8
+ import { isDefined, isUndefined } from '@xylabs/typeof'
7
9
  import { MemoryArchivist } from '@xyo-network/archivist-memory'
8
10
  import { ArchivistInstance } from '@xyo-network/archivist-model'
11
+ import { findMostRecentBlock } from '@xyo-network/chain-protocol'
9
12
  import { globalAttributes } from '@xyo-network/chain-telemetry'
10
13
  import { validateTransaction } from '@xyo-network/chain-validation'
11
- import { PayloadBuilder } from '@xyo-network/payload-builder'
12
14
  import {
13
15
  Payload, PayloadBundle, Sequence, WithStorageMeta,
14
16
  } from '@xyo-network/payload-model'
15
17
  import {
16
- asTransactionBoundWitnessWithStorageMeta, BaseServiceParams, ChainIdentification, HydratedTransaction,
17
- isTransactionBoundWitnessWithStorageMeta, PendingTransactionsService,
18
+ asBlockBoundWitnessWithHashStorageMeta, HydratedTransaction, isTransactionBoundWitnessWithStorageMeta, PendingTransactionsService,
18
19
  } from '@xyo-network/xl1-protocol'
19
20
  import { Mutex } from 'async-mutex'
20
21
 
21
- import { BaseService, creatableService } from '../BaseService.ts'
22
+ import { BaseService } from '../BaseService.ts'
23
+ import { BaseServiceParams } from '../Params.ts'
22
24
  import { bundledPayloadToHydratedTransaction } from './bundledPayloadToHydratedTransaction.ts'
23
25
  import { hydratedTransactionToPayloadBundle } from './hydratedTransactionToPayloadBundle.ts'
24
26
 
25
27
  export interface XyoPendingTransactionsServiceParams extends BaseServiceParams {
26
28
  chainArchivist?: ArchivistInstance
27
- chainIdentification?: ChainIdentification
29
+ chainId?: Address
28
30
  pendingBundledTransactionsArchivist?: ArchivistInstance
29
31
  rejectedTransactionsArchivist?: ArchivistInstance
30
32
  }
31
33
 
32
34
  globalAttributes.setAttribute('XyoPendingTransactionsService:status', 'unknown')
33
35
 
34
- @creatableService()
36
+ @creatable()
35
37
  export class XyoPendingTransactionsService extends BaseService<XyoPendingTransactionsServiceParams> implements PendingTransactionsService {
36
38
  private static readonly MutexPriority = {
37
39
  /**
@@ -43,13 +45,9 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
43
45
  */
44
46
  ReadTransactions: 3,
45
47
  /**
46
- * Priority for removing rejected transactions
48
+ * Priority for removing finalized/expired/rejected transactions
47
49
  */
48
- RemoveRejectedTransactions: 2,
49
- /**
50
- * Priority for removing finalized transactions
51
- */
52
- RemoveFinalizedTransactions: 1,
50
+ PurgeTransactions: 1,
53
51
  } as const
54
52
 
55
53
  /**
@@ -86,8 +84,8 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
86
84
  return assertEx(this.params.chainArchivist, () => 'No completed blocks with data archivist')
87
85
  }
88
86
 
89
- private get chainIdentification() {
90
- return assertEx(this.params.chainIdentification, () => 'No chain id')
87
+ private get chainId() {
88
+ return assertEx(this.params.chainId, () => 'No chain id')
91
89
  }
92
90
 
93
91
  private get pendingBundledTransactionsArchivist() {
@@ -112,18 +110,20 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
112
110
  this._curatedPendingBundledTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
113
111
 
114
112
  // On new pending transactions, insert them into the curated archivist
115
- this.pendingBundledTransactionsArchivist.on('inserted', async ({ payloads }) => {
116
- await this.insertNewTransactions(payloads as WithStorageMeta<PayloadBundle>[])
113
+ this.pendingBundledTransactionsArchivist.on('inserted', ({ payloads }) => {
114
+ forget(this.insertNewTransactions(payloads as WithStorageMeta<PayloadBundle>[]))
117
115
  })
118
116
 
119
117
  // On new finalized blocks, remove the transactions from the curated archivist
120
118
  this.chainArchivist.on('inserted', ({ payloads }) => {
121
119
  this.markAnyIncludedTransactionsForRemoval(payloads)
120
+ forget(this.cleanupWorker())
122
121
  })
123
122
 
124
123
  // On new rejected transactions, remove the transactions from the curated archivist
125
124
  this.rejectedTransactionsArchivist.on('inserted', ({ payloads }) => {
126
125
  this.markAnyIncludedTransactionsForRemoval(payloads)
126
+ forget(this.cleanupWorker())
127
127
  })
128
128
 
129
129
  const pendingTransactionsCounter = this.meter?.createObservableUpDownCounter(
@@ -142,37 +142,32 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
142
142
  return await this.spanAsync('getPendingTransactions', async () => {
143
143
  // Acquires an exclusive mutex to ensure no race conditions while accessing pending transactions.
144
144
  return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
145
- const foundPendingTransactions: HydratedTransaction[] = []
146
- const foundPendingTransactionsToDeleteHashes: Hash[] = []
145
+ // Find the supplied head
146
+ let [lastHead] = filterAs(await this.chainArchivist.get([head]), asBlockBoundWitnessWithHashStorageMeta)
147
+ if (isUndefined(lastHead)) return []
148
+
149
+ await this.pruneCuratedPendingTransactionsArchivist(lastHead._hash)
147
150
 
151
+ const foundPendingTransactions: HydratedTransaction[] = []
148
152
  let cursor: Sequence | undefined
149
153
 
150
154
  // Continue fetching until the desired number of transactions is reached.
151
155
  while (foundPendingTransactions.length < limit) {
152
156
  // Fetch the next batch of payloads
153
- const payloads = await this.pendingBundledTransactionsLocalArchivist.next({
157
+ const pendingBundledTransactions = await this.pendingBundledTransactionsLocalArchivist.next({
154
158
  limit: 100,
155
159
  order: 'asc',
156
160
  cursor,
157
161
  }) as WithStorageMeta<PayloadBundle>[]
158
162
 
159
163
  // Exit if no more payloads are available.
160
- if (payloads.length === 0) break
164
+ if (pendingBundledTransactions.length === 0) break
161
165
 
162
166
  // Update the cursor for the next iteration to fetch subsequent payloads.
163
- cursor = payloads.at(-1)?._sequence
164
-
165
- // Filter out bundles that are marked as removable.
166
- const deletedTransactionBundles = payloads.filter(tx =>
167
- this._removablePendingTransactionHashes.has(tx.root))
168
-
169
- // Queue the hashes of deletable transactions for deletion and cleanup.
170
- foundPendingTransactionsToDeleteHashes.push(
171
- ...deletedTransactionBundles.map(tx => tx._hash).filter(exists),
172
- )
167
+ cursor = pendingBundledTransactions.at(-1)?._sequence
173
168
 
174
169
  // Keep only those payloads that are not marked for deletion.
175
- const undeletedTransactionBundles = payloads.filter(tx =>
170
+ const undeletedTransactionBundles = pendingBundledTransactions.filter(tx =>
176
171
  !this._removablePendingTransactionHashes.has(tx.root))
177
172
 
178
173
  // Convert each undeleted payload bundle into a hydrated transaction.
@@ -180,23 +175,12 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
180
175
  undeletedTransactionBundles.map(p => bundledPayloadToHydratedTransaction(p)),
181
176
  )).filter(exists)
182
177
 
183
- // Add the valid hydrated transactions to the result set.
184
- foundPendingTransactions.push(...transactions)
185
- }
186
-
187
- // Actually delete the marked payload bundles from the archivist
188
- await this.pendingBundledTransactionsLocalArchivist.delete(foundPendingTransactionsToDeleteHashes)
178
+ // Filter transactions to only include those that are active for the next
179
+ // potential block based on the last supplied head.
180
+ const activeTransactions = transactions.filter(isTransactionActive(lastHead.block + 1))
189
181
 
190
- // Remove deleted hashes from the "pending delete" set now that they are deleted
191
- for (const hash of foundPendingTransactionsToDeleteHashes) {
192
- this._removablePendingTransactionHashes.delete(hash)
193
- }
194
-
195
- if (foundPendingTransactionsToDeleteHashes.length > 0) {
196
- this.logger?.log(`foundPendingTransactionsToDeleteHashes: Found ${foundPendingTransactionsToDeleteHashes.length} deletable transactions`)
197
- for (const hash of foundPendingTransactionsToDeleteHashes) {
198
- this.logger?.log(hash)
199
- }
182
+ // Add the valid hydrated transactions to the result set.
183
+ foundPendingTransactions.push(...activeTransactions)
200
184
  }
201
185
 
202
186
  if (foundPendingTransactions.length > 0) {
@@ -211,6 +195,13 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
211
195
  })
212
196
  }
213
197
 
198
+ private async cleanupWorker() {
199
+ return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
200
+ const lastHead = await findMostRecentBlock(this.chainArchivist)
201
+ if (isDefined(lastHead)) await this.pruneCuratedPendingTransactionsArchivist(lastHead._hash)
202
+ }, XyoPendingTransactionsService.MutexPriority.PurgeTransactions)
203
+ }
204
+
214
205
  private async countPendingTransactions() {
215
206
  if (this._countPendingTransactionsMutex.isLocked()) return
216
207
  await this._countPendingTransactionsMutex.runExclusive(async () => {
@@ -241,7 +232,7 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
241
232
  }))).filter(exists)
242
233
  // Filter to only valid transactions
243
234
  const validTransactions = await filterAsync(hydratedUnprocessedTransactions, async (tx) => {
244
- const errors = await validateTransaction(tx, this.chainIdentification.id)
235
+ const errors = await validateTransaction(tx, this.chainId)
245
236
  if (errors.length > 0) {
246
237
  this.logger?.warn('validateTransaction', errors)
247
238
  }
@@ -268,19 +259,92 @@ export class XyoPendingTransactionsService extends BaseService<XyoPendingTransac
268
259
  }
269
260
  }
270
261
 
271
- private async removeBundledTransactions(
272
- bundledPayloads: WithStorageMeta<PayloadBundle>[],
273
- priority: keyof typeof XyoPendingTransactionsService.MutexPriority,
274
- ) {
275
- return await this.spanAsync(priority, async () => {
276
- return await this._updateCuratedPendingTransactionsArchivistMutex.runExclusive(async () => {
277
- const bundledTransactions = (await Promise.all(bundledPayloads.map(async (payload) => {
278
- const withStorageMeta = await PayloadBuilder.addStorageMeta(payload.payloads)
279
- const tx = asTransactionBoundWitnessWithStorageMeta(withStorageMeta.find(p => p._hash === payload.root))
280
- return tx !== undefined && payload._hash !== undefined ? tx : undefined
281
- }))).filter(exists)
282
- await this.pendingBundledTransactionsLocalArchivist.delete(bundledTransactions.map(btx => btx._hash))
283
- }, XyoPendingTransactionsService.MutexPriority[priority])
262
+ private async pruneCuratedPendingTransactionsArchivist(head: Hash) {
263
+ return await this.spanAsync('pruneCuratedPendingTransactionsArchivist', async () => {
264
+ const foundPendingTransactionsToDeleteHashes: Hash[] = []
265
+
266
+ let cursor: Sequence | undefined
267
+ let [lastHead] = filterAs(await this.chainArchivist.get([head]), asBlockBoundWitnessWithHashStorageMeta)
268
+
269
+ // Continue fetching until the desired number of transactions is reached.
270
+ while (isDefined(lastHead)) {
271
+ // Fetch the next batch of payloads
272
+ const pendingBundledTransactions = await this.pendingBundledTransactionsLocalArchivist.next({
273
+ limit: 100,
274
+ order: 'asc',
275
+ cursor,
276
+ }) as WithStorageMeta<PayloadBundle>[]
277
+
278
+ // Exit if no more payloads are available.
279
+ if (pendingBundledTransactions.length === 0) {
280
+ break
281
+ }
282
+
283
+ // Update the cursor for the next iteration to fetch subsequent payloads.
284
+ cursor = pendingBundledTransactions.at(-1)?._sequence
285
+
286
+ // Filter out bundles that are marked as removable.
287
+ const deletedTransactionBundles = pendingBundledTransactions.filter(tx =>
288
+ this._removablePendingTransactionHashes.has(tx.root))
289
+
290
+ // Queue the hashes of deletable transactions for deletion and cleanup.
291
+ foundPendingTransactionsToDeleteHashes.push(
292
+ ...deletedTransactionBundles.map(tx => tx._hash).filter(exists),
293
+ )
294
+
295
+ // Keep only those payloads that are not marked for deletion.
296
+ const undeletedTransactionBundles = pendingBundledTransactions.filter(tx =>
297
+ !this._removablePendingTransactionHashes.has(tx.root))
298
+
299
+ // Convert each undeleted payload bundle into a hydrated transaction.
300
+ const transactions = (await Promise.all(
301
+ undeletedTransactionBundles.map(p => bundledPayloadToHydratedTransaction(p)),
302
+ )).filter(exists)
303
+
304
+ // Find expired transactions based on the last supplied head
305
+ const expiredTransactions = transactions.filter(isTransactionExpired(lastHead.block + 1))
306
+ // Find the corresponding bundle hashes for the expired transactions
307
+ const expiredBundleHashes = expiredTransactions
308
+ .map(expiredHydratedTx =>
309
+ // Find the corresponding payload bundle hash for the expired transaction
310
+ pendingBundledTransactions.find(bundledTx => bundledTx.root === expiredHydratedTx[0]._hash)?._hash)
311
+ .filter(exists)
312
+ // Mark all expired bundled transactions for deletion.
313
+ foundPendingTransactionsToDeleteHashes.push(...expiredBundleHashes)
314
+ }
315
+
316
+ // Actually delete the marked payload bundles from the archivist
317
+ const deletedHashes = await this.pendingBundledTransactionsLocalArchivist.delete(foundPendingTransactionsToDeleteHashes)
318
+
319
+ // Remove all deleted hashes from the "pending delete" set now that they are deleted
320
+ for (const payload of deletedHashes) {
321
+ this._removablePendingTransactionHashes.delete(payload._hash)
322
+ }
323
+
324
+ if (deletedHashes.length > 0) {
325
+ this.logger?.log(`foundPendingTransactionsToDeleteHashes: Found ${deletedHashes.length} deletable transactions`)
326
+ for (const payload of deletedHashes) {
327
+ this.logger?.log(payload._hash)
328
+ }
329
+ }
284
330
  })
285
331
  }
286
332
  }
333
+
334
+ /**
335
+ * Checks if a transaction is expired for a given block.
336
+ * @param block The block number to check against the transaction's validity period.
337
+ * @returns True if the transaction is expired for the given block, false otherwise.
338
+ */
339
+ const isTransactionExpired = (block: number) =>
340
+ ([txBw]: HydratedTransaction): boolean =>
341
+ txBw.exp < block
342
+
343
+ /**
344
+ * Checks if a transaction is active for a given block.
345
+ * @param block The block number to check against the transaction's validity period.
346
+ * @returns True if the transaction is active for the given block, false otherwise.
347
+ */
348
+ const isTransactionActive = (block: number) =>
349
+ ([txBw]: HydratedTransaction): boolean =>
350
+ txBw.nbf <= block && txBw.exp >= block
@@ -1,8 +1,10 @@
1
1
  import { filterAs } from '@xylabs/array'
2
2
  import { assertEx } from '@xylabs/assert'
3
+ import { creatable } from '@xylabs/creatable'
3
4
  import {
4
5
  Address, asAddress, Hash,
5
6
  } from '@xylabs/hex'
7
+ import { isDefined, isUndefined } from '@xylabs/typeof'
6
8
  import { ArchivistInstance, ArchivistNextOptions } from '@xyo-network/archivist-model'
7
9
  import {
8
10
  analyzeChain, ChainStakeIntentAnalyzer,
@@ -15,9 +17,7 @@ import {
15
17
  } from '@xyo-network/chain-utils'
16
18
  import { PayloadBuilder } from '@xyo-network/payload-builder'
17
19
  import { Payload, WithStorageMeta } from '@xyo-network/payload-model'
18
- import type {
19
- BaseServiceParams, EventingChainBlockNumberIterator, Intent,
20
- } from '@xyo-network/xl1-protocol'
20
+ import type { EventingChainBlockNumberIterator, Intent } from '@xyo-network/xl1-protocol'
21
21
  import {
22
22
  asBlockBoundWitness, asBlockBoundWitnessWithStorageMeta,
23
23
  asChainIndexingServiceStateWithStorageMeta,
@@ -28,7 +28,8 @@ import {
28
28
  import { Mutex } from 'async-mutex'
29
29
  import { LRUCache } from 'lru-cache'
30
30
 
31
- import { BaseService, creatableService } from '../BaseService.ts'
31
+ import { BaseService } from '../BaseService.ts'
32
+ import { BaseServiceParams } from '../Params.ts'
32
33
 
33
34
  export interface XyoStakeIntentServiceParams extends BaseServiceParams {
34
35
  chainArchivist?: ArchivistInstance
@@ -46,7 +47,7 @@ const ACTIVE_STAKE_TTL = 1000 * 60 * 60 * 2 // 2 hours in milliseconds
46
47
  const NO_ACTIVE_STAKE_TTL = 1000 * 2 // 2 seconds in milliseconds
47
48
  const STAKE_CACHE_MAX_ENTRIES = 10_000
48
49
 
49
- @creatableService()
50
+ @creatable()
50
51
  export class XyoStakeIntentService extends BaseService<XyoStakeIntentServiceParams> implements StakeIntentService {
51
52
  // TODO: Use hash instead of block number to handle chain reorgs
52
53
  protected _lastIndexedBlockHash: Hash | undefined = undefined
@@ -61,13 +62,6 @@ export class XyoStakeIntentService extends BaseService<XyoStakeIntentServicePara
61
62
  protected _stakeCache = new LRUCache<Address, bigint>({ max: STAKE_CACHE_MAX_ENTRIES })
62
63
  protected _updateMutex = new Mutex()
63
64
 
64
- constructor(params: XyoStakeIntentServiceParams) {
65
- super(params)
66
- this.chainIterator.on('headUpdated', async () => {
67
- await this.updateIndex()
68
- })
69
- }
70
-
71
65
  protected get chainArchivist() {
72
66
  return assertEx(this.params.chainArchivist, () => 'chainArchivist not set')
73
67
  }
@@ -84,12 +78,14 @@ export class XyoStakeIntentService extends BaseService<XyoStakeIntentServicePara
84
78
  return assertEx(this.params.stakeIntentStateArchivist, () => 'stakeIntentStateArchivist not set')
85
79
  }
86
80
 
87
- override async createHandler(): Promise<void> {
81
+ override async createHandler() {
82
+ this.chainIterator.on('headUpdated', async () => {
83
+ await this.updateIndex()
84
+ })
88
85
  const head = await this.chainIterator.head()
86
+ if (isUndefined(head)) return
89
87
  const headHash = await PayloadBuilder.hash(head)
90
- if (head?.block === undefined) return
91
88
  await this.recoverState(headHash)
92
- await this.updateIndex(true)
93
89
  }
94
90
 
95
91
  async getDeclaredCandidateRanges(address: Address, intent: Intent): Promise<Readonly<Readonly<[number, number]>[]>> {
@@ -104,20 +100,36 @@ export class XyoStakeIntentService extends BaseService<XyoStakeIntentServicePara
104
100
  assertEx(intent === 'producer', () => `Error: Support not yet added for intent ${intent}`)
105
101
  const results = this._producers.findAllContaining(block)
106
102
  const candidates = [...results]
107
- const validCandidates = await this.filterToValidStake(candidates, this.chainStakeViewer)
103
+ const requiredMinimumStake = this.getRequiredMinimumStakeForIntent(intent)
104
+ const validCandidates = await this.filterToValidStake(candidates, this.chainStakeViewer, requiredMinimumStake)
108
105
  return validCandidates
109
106
  })
110
107
  }
111
108
 
109
+ getRequiredMinimumStakeForIntent(intent: Intent): bigint {
110
+ switch (intent) {
111
+ case 'producer': {
112
+ const requiredMinimumStake = isDefined(process.env.XYO_PRODUCER_MIN_STAKE)
113
+ ? BigInt(process.env.XYO_PRODUCER_MIN_STAKE)
114
+ : 1n
115
+ return requiredMinimumStake
116
+ }
117
+ }
118
+ }
119
+
112
120
  async isStakedForBlock(block: number, intent: Intent, address: Address): Promise<boolean> {
113
121
  const candidates = await this.getDeclaredCandidatesForBlock(block, intent)
114
122
  return candidates.includes(address)
115
123
  }
116
124
 
125
+ override async startHandler(): Promise<void> {
126
+ await this.updateIndex(true)
127
+ }
128
+
117
129
  private async filterToValidStake(
118
130
  candidates: Address[],
119
131
  chainStakeViewer: ChainStakeViewer,
120
- requiredMinimumStake: bigint = 1n,
132
+ requiredMinimumStake: bigint,
121
133
  ): Promise<Address[]> {
122
134
  type CandidateStake = { candidate: Address; stake: bigint }
123
135
 
@@ -194,6 +206,7 @@ export class XyoStakeIntentService extends BaseService<XyoStakeIntentServicePara
194
206
  await this._updateMutex.runExclusive(async () => {
195
207
  return await this.spanAsync('updateIndex', async () => {
196
208
  const currentHead = await this.chainIterator.head()
209
+ if (isUndefined(currentHead)) return
197
210
  const currentHeadHash = await PayloadBuilder.hash(currentHead)
198
211
  const result = await analyzeChain(this.chainArchivist, [new ChainStakeIntentAnalyzer('producer')], currentHeadHash, this._lastIndexedBlockHash)
199
212
  const signedDeclarations = filterAs(result.find(isChainSummaryStakeIntent)?.intents ?? [], asChainStakeIntent)
@@ -4,36 +4,37 @@ import { toAddress } from '@xylabs/hex'
4
4
  import { toEthAddress } from '@xyo-network/chain-ethereum'
5
5
  import type { StakedXyoChain } from '@xyo-network/typechain'
6
6
  import { StakedXyoChain__factory as StakedXyoChainFactory } from '@xyo-network/typechain'
7
- import type { BaseServiceParams, ChainService } from '@xyo-network/xl1-protocol'
7
+ import type { ChainService } from '@xyo-network/xl1-protocol'
8
8
  import { getAddress } from 'ethers/address'
9
9
  import type { ContractRunner } from 'ethers/providers'
10
10
 
11
11
  import { BaseService } from '../../BaseService.ts'
12
+ import type { BaseServiceParams } from '../../Params.ts'
12
13
 
13
- export type EvmChainServiceParams = BaseServiceParams<{
14
+ export interface EvmChainServiceParams extends BaseServiceParams {
14
15
  contract: StakedXyoChain
15
16
  id: Address
16
17
  runner: ContractRunner
17
- }>
18
+ }
18
19
 
19
20
  /**
20
21
  * A class that represents a chain stake as backed by an EVM smart contract
21
22
  */
22
23
  export class EvmChainService extends BaseService<EvmChainServiceParams> implements ChainService {
24
+ get chainId(): Address {
25
+ return assertEx(this.params.id)
26
+ }
27
+
23
28
  get contract() {
24
29
  if (this.params.contract === undefined) {
25
30
  this.params.contract = StakedXyoChainFactory.connect(
26
- toEthAddress(this.id),
31
+ toEthAddress(this.chainId),
27
32
  this.params.runner,
28
33
  ) as StakedXyoChain
29
34
  }
30
35
  return assertEx(this.params.contract)
31
36
  }
32
37
 
33
- get id(): Address {
34
- return assertEx(this.params.id)
35
- }
36
-
37
38
  get runner(): ContractRunner {
38
39
  return assertEx(this.params.runner)
39
40
  }
@@ -56,10 +57,6 @@ export class EvmChainService extends BaseService<EvmChainServiceParams> implemen
56
57
  return true
57
58
  }
58
59
 
59
- async chainId(): Promise<Address> {
60
- return toAddress(await this.contract.chainId())
61
- }
62
-
63
60
  async forkedAtBlockNumber(): Promise<bigint> {
64
61
  return await this.contract.forkedAtBlockNumber()
65
62
  }
@@ -0,0 +1,90 @@
1
+ import type { Address } from '@xylabs/hex'
2
+ import { ZERO_ADDRESS } from '@xylabs/hex'
3
+ import { isDefined } from '@xylabs/typeof'
4
+ import type { ChainService } from '@xyo-network/xl1-protocol'
5
+
6
+ import { BaseService } from '../../BaseService.ts'
7
+ import type { BaseServiceParams } from '../../Params.ts'
8
+
9
+ export interface MemoryChainServiceParams extends BaseServiceParams {}
10
+
11
+ /**
12
+ * A class that represents a chain stake as backed in memory
13
+ */
14
+ export class MemoryChainService extends BaseService<MemoryChainServiceParams> implements ChainService {
15
+ protected _simulatedStake: bigint = 1n
16
+
17
+ get chainId(): Address {
18
+ return ZERO_ADDRESS
19
+ }
20
+
21
+ async active(): Promise<bigint> {
22
+ return await Promise.resolve(this._simulatedStake)
23
+ }
24
+
25
+ async activeByAddressStaked(_address: string): Promise<bigint> {
26
+ return await Promise.resolve(this._simulatedStake)
27
+ }
28
+
29
+ async activeByStaker(_address: string): Promise<bigint> {
30
+ return await Promise.resolve(this._simulatedStake)
31
+ }
32
+
33
+ async addStake(_staked: string, _amount: bigint): Promise<boolean> {
34
+ return await Promise.resolve(true)
35
+ }
36
+
37
+ override createHandler(): void {
38
+ this._simulatedStake = isDefined(process.env.XYO_PRODUCER_MIN_STAKE)
39
+ ? BigInt(process.env.XYO_PRODUCER_MIN_STAKE)
40
+ : 1n
41
+ }
42
+
43
+ async forkedAtBlockNumber(): Promise<bigint> {
44
+ return await Promise.resolve(0n)
45
+ }
46
+
47
+ async forkedAtHash(): Promise<bigint> {
48
+ return await Promise.resolve(0n)
49
+ }
50
+
51
+ async forkedChainId(): Promise<Address> {
52
+ return await Promise.resolve(ZERO_ADDRESS)
53
+ }
54
+
55
+ async minWithdrawalBlocks(): Promise<bigint> {
56
+ return await Promise.resolve(1n)
57
+ }
58
+
59
+ async pending(): Promise<bigint> {
60
+ return await Promise.resolve(0n)
61
+ }
62
+
63
+ async pendingByStaker(_staker: string): Promise<bigint> {
64
+ return await Promise.resolve(0n)
65
+ }
66
+
67
+ async removeStake(_slot: bigint): Promise<boolean> {
68
+ return await Promise.resolve(true)
69
+ }
70
+
71
+ async rewardsContract(): Promise<string> {
72
+ return await Promise.resolve('')
73
+ }
74
+
75
+ async stakingTokenAddress(): Promise<string> {
76
+ return await Promise.resolve('')
77
+ }
78
+
79
+ async withdrawStake(_slot: bigint): Promise<boolean> {
80
+ return await Promise.resolve(true)
81
+ }
82
+
83
+ async withdrawn(): Promise<bigint> {
84
+ return await Promise.resolve(0n)
85
+ }
86
+
87
+ async withdrawnByStaker(_staker: string): Promise<bigint> {
88
+ return await Promise.resolve(0n)
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ export * from './Memory.ts'
@@ -1 +1,2 @@
1
1
  export * from './Evm/index.ts'
2
+ export * from './Memory/index.ts'
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from './BlockReward/index.ts'
5
5
  export * from './ChainBlockNumberIteration/index.ts'
6
6
  export * from './ChainValidator/index.ts'
7
7
  export * from './Election/index.ts'
8
+ export * from './Params.ts'
8
9
  export * from './PendingTransactions/index.ts'
9
10
  export * from './StakeIntent/index.ts'
10
11
  export * from './Staker/index.ts'