@xyo-network/chain-services 1.5.34 → 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.
- package/dist/neutral/index.mjs +428 -259
- package/dist/neutral/index.mjs.map +1 -1
- package/dist/types/AccountBalance/ChainAccountBalanceServiceV2.d.ts +14 -0
- package/dist/types/AccountBalance/ChainAccountBalanceServiceV2.d.ts.map +1 -0
- package/dist/types/AccountBalance/XyoChainAccountBalanceService.d.ts +12 -8
- package/dist/types/AccountBalance/XyoChainAccountBalanceService.d.ts.map +1 -1
- package/dist/types/AccountBalance/accountBalanceServiceFromArchivist.d.ts +3 -2
- package/dist/types/AccountBalance/accountBalanceServiceFromArchivist.d.ts.map +1 -1
- package/dist/types/AccountBalance/index.d.ts +1 -0
- package/dist/types/AccountBalance/index.d.ts.map +1 -1
- package/dist/types/BaseService.d.ts +7 -8
- package/dist/types/BaseService.d.ts.map +1 -1
- package/dist/types/BlockProducer/XyoBlockProducer.d.ts +28 -9
- package/dist/types/BlockProducer/XyoBlockProducer.d.ts.map +1 -1
- package/dist/types/BlockReward/EvmBlockRewardService.d.ts +2 -1
- package/dist/types/BlockReward/EvmBlockRewardService.d.ts.map +1 -1
- package/dist/types/BlockReward/XyoBlockRewardService.d.ts +2 -1
- package/dist/types/BlockReward/XyoBlockRewardService.d.ts.map +1 -1
- package/dist/types/ChainBlockNumberIteration/ChainBlockNumberIterationService.d.ts +4 -2
- package/dist/types/ChainBlockNumberIteration/ChainBlockNumberIterationService.d.ts.map +1 -1
- package/dist/types/ChainBlockNumberIteration/model/Params.d.ts +8 -0
- package/dist/types/ChainBlockNumberIteration/model/Params.d.ts.map +1 -0
- package/dist/types/ChainBlockNumberIteration/model/index.d.ts +1 -0
- package/dist/types/ChainBlockNumberIteration/model/index.d.ts.map +1 -1
- package/dist/types/ChainIndexService.d.ts +8 -5
- package/dist/types/ChainIndexService.d.ts.map +1 -1
- package/dist/types/ChainValidator/XyoValidator.d.ts +20 -9
- package/dist/types/ChainValidator/XyoValidator.d.ts.map +1 -1
- package/dist/types/Election/XyoElectionService.d.ts +2 -2
- package/dist/types/Election/XyoElectionService.d.ts.map +1 -1
- package/dist/types/Params.d.ts +9 -0
- package/dist/types/Params.d.ts.map +1 -0
- package/dist/types/PendingTransactions/PendingTransactions.d.ts +7 -5
- package/dist/types/PendingTransactions/PendingTransactions.d.ts.map +1 -1
- package/dist/types/StakeIntent/XyoStakeIntentService.d.ts +10 -6
- package/dist/types/StakeIntent/XyoStakeIntentService.d.ts.map +1 -1
- package/dist/types/Staker/Evm/Evm.d.ts +5 -5
- package/dist/types/Staker/Evm/Evm.d.ts.map +1 -1
- package/dist/types/Staker/Memory/Memory.d.ts +31 -0
- package/dist/types/Staker/Memory/Memory.d.ts.map +1 -0
- package/dist/types/Staker/Memory/index.d.ts +2 -0
- package/dist/types/Staker/Memory/index.d.ts.map +1 -0
- package/dist/types/Staker/index.d.ts +1 -0
- package/dist/types/Staker/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +45 -44
- package/src/AccountBalance/ChainAccountBalanceServiceV2.ts +33 -0
- package/src/AccountBalance/XyoChainAccountBalanceService.ts +23 -19
- package/src/AccountBalance/accountBalanceServiceFromArchivist.ts +67 -35
- package/src/AccountBalance/index.ts +1 -0
- package/src/BaseService.ts +10 -23
- package/src/BlockProducer/XyoBlockProducer.ts +53 -30
- package/src/BlockReward/EvmBlockRewardService.ts +5 -5
- package/src/BlockReward/XyoBlockRewardService.ts +5 -3
- package/src/ChainBlockNumberIteration/ChainBlockNumberIterationService.ts +5 -3
- package/src/ChainBlockNumberIteration/model/Params.ts +9 -0
- package/src/ChainBlockNumberIteration/model/index.ts +1 -0
- package/src/ChainIndexService.ts +5 -5
- package/src/ChainValidator/XyoValidator.ts +9 -8
- package/src/Election/XyoElectionService.ts +5 -7
- package/src/Params.ts +9 -0
- package/src/PendingTransactions/PendingTransactions.ts +127 -63
- package/src/StakeIntent/XyoStakeIntentService.ts +30 -17
- package/src/Staker/Evm/Evm.ts +9 -12
- package/src/Staker/Memory/Memory.ts +90 -0
- package/src/Staker/Memory/index.ts +1 -0
- package/src/Staker/index.ts +1 -0
- 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
90
|
-
return assertEx(this.params.
|
|
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',
|
|
116
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
|
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 (
|
|
164
|
+
if (pendingBundledTransactions.length === 0) break
|
|
161
165
|
|
|
162
166
|
// Update the cursor for the next iteration to fetch subsequent payloads.
|
|
163
|
-
cursor =
|
|
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 =
|
|
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
|
-
//
|
|
184
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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.
|
|
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
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
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
|
-
@
|
|
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()
|
|
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
|
|
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
|
|
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)
|
package/src/Staker/Evm/Evm.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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.
|
|
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'
|
package/src/Staker/index.ts
CHANGED
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'
|