@xyo-network/chain-services 1.20.14 → 1.20.16

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.
@@ -1,7 +0,0 @@
1
- import type { Promisable } from '@xylabs/sdk-js'
2
- import type { BlockBoundWitness, SignedHydratedTransactionWithStorageMeta } from '@xyo-network/xl1-sdk'
3
-
4
- export interface Validator {
5
- validatePendingBlock(block: BlockBoundWitness): Promisable<Error[]>
6
- validatePendingTransaction(tx: SignedHydratedTransactionWithStorageMeta): Promise<boolean>
7
- }
@@ -1 +0,0 @@
1
- export * from './Validator.ts'
@@ -1,55 +0,0 @@
1
- import {
2
- Address, assertEx, Hash,
3
- } from '@xylabs/sdk-js'
4
- import { hexToLast4BytesInt, shuffleWithSeed } from '@xyo-network/chain-utils'
5
- import { WithHashMeta } from '@xyo-network/sdk-js'
6
- import {
7
- AbstractCreatableProvider,
8
- type BlockViewer,
9
- type ChainStakeViewer, creatableProvider,
10
- type ElectionService, type StakeIntentService,
11
- } from '@xyo-network/xl1-sdk'
12
- import { BlockBoundWitness } from '@xyo-network/xl1-sdk'
13
-
14
- import { BaseServiceParams } from '../model/index.ts'
15
-
16
- export interface BaseElectionServicesParams extends BaseServiceParams {
17
- blockViewer?: BlockViewer
18
- chainStakeViewer?: ChainStakeViewer
19
- stakeIntentService?: StakeIntentService
20
- }
21
-
22
- @creatableProvider()
23
- export class BaseElectionService extends AbstractCreatableProvider<BaseElectionServicesParams> implements ElectionService {
24
- static readonly defaultMoniker = 'Election'
25
- static readonly dependencies = []
26
- static readonly monikers = ['Election']
27
- moniker = BaseElectionService.defaultMoniker
28
- get blockViewer() {
29
- return assertEx(this.params.blockViewer, () => 'No block viewer')
30
- }
31
-
32
- get chainStakeViewer() {
33
- return assertEx(this.params.chainStakeViewer, () => 'No chain stake viewer')
34
- }
35
-
36
- get stakeIntentService() {
37
- return assertEx(this.params.stakeIntentService, () => 'No staked intent service')
38
- }
39
-
40
- async getCreatorCommitteeForNextBlock(current: WithHashMeta<BlockBoundWitness>): Promise<Address[]> {
41
- return await this.spanAsync('getCreatorCommitteeForNextBlock', async () => {
42
- const nextBlock = current.block + 1
43
- const candidates = await this.stakeIntentService.getDeclaredCandidatesForBlock(nextBlock, 'producer')
44
- const previousBlockHash = current._hash
45
- return this.generateCreatorCommittee(candidates, previousBlockHash)
46
- }, this.context)
47
- }
48
-
49
- protected generateCreatorCommittee(candidates: Address[], previousBlockHash: Hash, maxSize = 3): Address[] {
50
- const creators = new Set<Address>(candidates)
51
- const seed = hexToLast4BytesInt(previousBlockHash)
52
- const creatorArray = shuffleWithSeed(creators, seed)
53
- return creatorArray.slice(0, maxSize)
54
- }
55
- }
@@ -1 +0,0 @@
1
- export * from './BaseElectionService.ts'
@@ -1,99 +0,0 @@
1
- import { Address, Promisable } from '@xylabs/sdk-js'
2
- import { ReadArchivist } from '@xyo-network/sdk-js'
3
- import {
4
- AbstractCreatableProvider,
5
- AttoXL1,
6
- creatableProvider,
7
- NetworkStakeStepRewardService, NetworkStakeStepRewardViewerMoniker,
8
- StepIdentity,
9
- StepIdentityString,
10
- } from '@xyo-network/xl1-sdk'
11
- import { Provider } from 'ethers'
12
-
13
- import { BaseServiceParams } from '../model/index.ts'
14
-
15
- export interface BaseNetworkStakeStepRewardServiceParams extends BaseServiceParams {
16
- chainArchivist: ReadArchivist
17
- ethProvider?: Provider
18
- }
19
-
20
- @creatableProvider()
21
- export class BaseNetworkStakeStepRewardService extends
22
- AbstractCreatableProvider<BaseNetworkStakeStepRewardServiceParams> implements NetworkStakeStepRewardService {
23
- static readonly defaultMoniker = NetworkStakeStepRewardViewerMoniker
24
- static readonly dependencies = []
25
- static readonly monikers = [NetworkStakeStepRewardViewerMoniker]
26
- override moniker = BaseNetworkStakeStepRewardService.defaultMoniker
27
-
28
- networkStakeStepRewardAddressHistory(_address: Address): Promisable<Record<Address, AttoXL1>> {
29
- throw new Error('Method [networkStakeStepRewardAddressHistory] not implemented.')
30
- }
31
-
32
- networkStakeStepRewardAddressReward(_context: StepIdentity, _address: Address): Promisable<Record<Address, AttoXL1>> {
33
- throw new Error('Method [networkStakeStepRewardAddressReward] not implemented.')
34
- }
35
-
36
- networkStakeStepRewardAddressShare(_context: StepIdentity, _address: Address): Promisable<[bigint, bigint]> {
37
- throw new Error('Method [networkStakeStepRewardAddressShare] not implemented.')
38
- }
39
-
40
- networkStakeStepRewardClaimedByAddress(_address: Address): Promisable<AttoXL1> {
41
- throw new Error('Method [networkStakeStepRewardClaimedByAddress] not implemented.')
42
- }
43
-
44
- networkStakeStepRewardForPosition(_position: number, _range: [number, number]): Promisable<[AttoXL1, AttoXL1]> {
45
- throw new Error('Method [networkStakeStepRewardForPosition] not implemented.')
46
- }
47
-
48
- networkStakeStepRewardForStep(_context: StepIdentity): Promisable<AttoXL1> {
49
- throw new Error('Method [networkStakeStepRewardForStep] not implemented.')
50
- }
51
-
52
- networkStakeStepRewardForStepForPosition(_context: StepIdentity, _position: number): Promisable<[AttoXL1, AttoXL1]> {
53
- throw new Error('Method [networkStakeStepRewardForStepForPosition] not implemented.')
54
- }
55
-
56
- networkStakeStepRewardPoolRewards(_context: StepIdentity): Promisable<Record<Address, AttoXL1>> {
57
- throw new Error('Method [networkStakeStepRewardPoolRewards] not implemented.')
58
- }
59
-
60
- networkStakeStepRewardPoolShares(_context: StepIdentity): Promisable<Record<Address, bigint>> {
61
- throw new Error('Method [networkStakeStepRewardPoolShares] not implemented.')
62
- }
63
-
64
- networkStakeStepRewardPositionWeight(_context: StepIdentity, _position: number): Promisable<bigint> {
65
- throw new Error('Method [networkStakeStepRewardPositionWeight] not implemented.')
66
- }
67
-
68
- networkStakeStepRewardPotentialPositionLoss(_context: StepIdentity, _position: number): Promisable<AttoXL1> {
69
- throw new Error('Method [networkStakeStepRewardPotentialPositionLoss] not implemented.')
70
- }
71
-
72
- networkStakeStepRewardRandomizer(_context: StepIdentity): Promisable<AttoXL1> {
73
- throw new Error('Method [networkStakeStepRewardRandomizer] not implemented.')
74
- }
75
-
76
- networkStakeStepRewardStakerCount(_context: StepIdentity): Promisable<number> {
77
- throw new Error('Method [networkStakeStepRewardStakerCount] not implemented.')
78
- }
79
-
80
- networkStakeStepRewardUnclaimedByAddress(_address: Address): Promisable<AttoXL1> {
81
- throw new Error('Method [networkStakeStepRewardUnclaimedByAddress] not implemented.')
82
- }
83
-
84
- networkStakeStepRewardWeightForAddress(_context: StepIdentity, _address: Address): Promisable<bigint> {
85
- throw new Error('Method [networkStakeStepRewardWeightForAddress] not implemented.')
86
- }
87
-
88
- networkStakeStepRewardsForPosition(_position: number, _range: [number, number]): Promisable<Record<StepIdentityString, [AttoXL1, AttoXL1]>> {
89
- throw new Error('Method [networkStakeStepRewardsForPosition] not implemented.')
90
- }
91
-
92
- networkStakeStepRewardsForRange(_range: [number, number]): Promisable<AttoXL1> {
93
- throw new Error('Method [networkStakeStepRewardsForRange] not implemented.')
94
- }
95
-
96
- networkStakeStepRewardsForStepLevel(_stepLevel: number, _range: [number, number]): Promisable<AttoXL1> {
97
- throw new Error('Method [networkStakeStepRewardsForStepLevel] not implemented.')
98
- }
99
- }
@@ -1 +0,0 @@
1
- export * from './BaseNetworkStakeStepRewardService.ts'
@@ -1,24 +0,0 @@
1
- import type { Address, Promisable } from '@xylabs/sdk-js'
2
- import type { ReadArchivist } from '@xyo-network/sdk-js'
3
- import type { StepIdentity, StepStakeViewer } from '@xyo-network/xl1-sdk'
4
- import { AbstractCreatableProvider, StepStakeViewerMoniker } from '@xyo-network/xl1-sdk'
5
-
6
- import type { BaseServiceParams } from '../model/index.ts'
7
-
8
- export interface BaseStepStakeServiceParams extends BaseServiceParams {
9
- chainArchivist: ReadArchivist
10
- }
11
-
12
- export abstract class AbstractStepStakeService extends AbstractCreatableProvider<BaseStepStakeServiceParams> implements StepStakeViewer {
13
- static readonly defaultMoniker = StepStakeViewerMoniker
14
- static readonly monikers = [StepStakeViewerMoniker]
15
- override moniker = AbstractStepStakeService.defaultMoniker
16
-
17
- stepStake(_step: StepIdentity): Promisable<Record<Address, bigint>> {
18
- throw new Error('Method [stepStake] not implemented.')
19
- }
20
-
21
- stepStakeForAddress(_address: Address, _step: StepIdentity): Promisable<bigint> {
22
- throw new Error('Method [stepStakeForAddress] not implemented.')
23
- }
24
- }
@@ -1 +0,0 @@
1
- export * from './BaseStepStakeService.ts'
@@ -1,36 +0,0 @@
1
- import type { Address } from '@xylabs/sdk-js'
2
- import { buildNextBlock, createGenesisBlock } from '@xyo-network/chain-protocol'
3
- import type { AccountInstance } from '@xyo-network/sdk-js'
4
- import type {
5
- AttoXL1, ChainId, SignedHydratedBlockWithHashMeta,
6
- } from '@xyo-network/xl1-sdk'
7
- import { createDeclarationIntent } from '@xyo-network/xl1-sdk'
8
-
9
- export const createBootstrapHead = async (
10
- account: AccountInstance,
11
- chainId: ChainId,
12
- genesisBlockRewardAmount: AttoXL1,
13
- genesisBlockRewardAddress: Address,
14
- ): Promise<SignedHydratedBlockWithHashMeta[]> => {
15
- const chain: SignedHydratedBlockWithHashMeta[] = []
16
-
17
- // Create genesis block
18
- const genesisBlock = await createGenesisBlock(account, chainId, genesisBlockRewardAmount, genesisBlockRewardAddress)
19
- chain.push(genesisBlock)
20
-
21
- // Create producer declaration block
22
- const producerDeclarationPayload = createDeclarationIntent(
23
- account.address,
24
- 'producer',
25
- genesisBlock[0].block,
26
- genesisBlock[0].block + 10_000,
27
- )
28
- const producerDeclarationBlock = await buildNextBlock(
29
- genesisBlock[0],
30
- [],
31
- [producerDeclarationPayload],
32
- [account],
33
- )
34
- chain.push(producerDeclarationBlock)
35
- return chain
36
- }
@@ -1 +0,0 @@
1
- export * from './createBootstrapHead.ts'
@@ -1 +0,0 @@
1
- export * from './head/index.ts'
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- export * from './BlockReward/index.ts'
2
- export * from './ChainValidator/index.ts'
3
- export * from './Election/index.ts'
4
- export * from './implementation/index.ts'
5
- export * from './model/index.ts'
6
- export * from './NetworkStakeStepReward/index.ts'
7
- export * from './simple/index.ts'
8
- export * from './StepStake/index.ts'
@@ -1,10 +0,0 @@
1
- import type { AccountInstance } from '@xyo-network/sdk-js'
2
- import type { CreatableProviderParams, OpenTelemetryProviders } from '@xyo-network/xl1-sdk'
3
-
4
- export interface BaseServiceParams extends CreatableProviderParams, OpenTelemetryProviders {
5
-
6
- }
7
-
8
- export interface BaseAccountableServiceParams extends BaseServiceParams {
9
- account: AccountInstance
10
- }
@@ -1 +0,0 @@
1
- export * from './Params.ts'
@@ -1 +0,0 @@
1
- export * from './runner/index.ts'
@@ -1,363 +0,0 @@
1
- import {
2
- Address, assertEx, exists, Hex, hexToBigInt, isDefined, Promisable,
3
- } from '@xylabs/sdk-js'
4
- import {
5
- BlockRewardDiviner, FixedPercentageBlockRewardDiviner, FixedPercentageBlockRewardDivinerConfigSchema,
6
- } from '@xyo-network/chain-modules'
7
- import { buildNextBlock } from '@xyo-network/chain-protocol'
8
- import {
9
- AccountInstance,
10
- ArchivistInstance, MemoryArchivist,
11
- PayloadBuilder, WithHashMeta,
12
- } from '@xyo-network/sdk-js'
13
- import {
14
- AbstractCreatableProvider, AccountBalanceViewer, AccountBalanceViewerMoniker, AllowedBlockPayload, asBlockBoundWitness, AttoXL1, BlockBoundWitness,
15
- BlockNumberPayload, BlockNumberSchema, BlockRewardViewer, BlockRewardViewerMoniker, BlockRunner, BlockRunnerMoniker, BlockValidationViewer,
16
- BlockValidationViewerMoniker, ChainStakeIntent, creatableProvider, CreatableProviderParams, createDeclarationIntent, defaultRewardRatio,
17
- FinalizationViewer,
18
- FinalizationViewerMoniker,
19
- HydratedBlockStateValidationFunction, isSignedHydratedBlockWithHashMeta, MempoolRunner, MempoolRunnerMoniker, MempoolViewer, MempoolViewerMoniker,
20
- SignedBlockBoundWitnessWithHashMeta,
21
- SignedHydratedBlockWithHashMeta, SignedHydratedTransaction, TimeSyncViewer, TimeSyncViewerMoniker, Transfer, XYO_STEP_REWARD_ADDRESS,
22
- } from '@xyo-network/xl1-sdk'
23
-
24
- import { generateTransactionFeeTransfers } from './generateTransactionFeeTransfers.ts'
25
-
26
- /**
27
- * The default block size for a block
28
- */
29
- export const DEFAULT_BLOCK_SIZE = 10
30
-
31
- /**
32
- * The amount of time for which a producer will restake their intent
33
- */
34
- export const XYO_PRODUCER_REDECLARATION_DURATION = 10_000
35
-
36
- /**
37
- * The number of blocks within which a producer will redeclare
38
- * their intent to produce blocks
39
- */
40
- export const XYO_PRODUCER_REDECLARATION_WINDOW = 500
41
-
42
- export interface SimpleBlockRunnerParams extends CreatableProviderParams {
43
- account: AccountInstance
44
- disableIntentRedeclaration?: boolean
45
- heartbeatInterval?: number
46
- rejectedTransactionsArchivist?: ArchivistInstance
47
- rewardAddress: Address
48
- validateHydratedBlockState?: HydratedBlockStateValidationFunction
49
- }
50
-
51
- @creatableProvider()
52
- export class SimpleBlockRunner extends AbstractCreatableProvider<SimpleBlockRunnerParams> implements BlockRunner {
53
- static readonly defaultMoniker = BlockRunnerMoniker
54
- static readonly dependencies = [
55
- AccountBalanceViewerMoniker,
56
- BlockRewardViewerMoniker,
57
- BlockValidationViewerMoniker,
58
- FinalizationViewerMoniker,
59
- MempoolRunnerMoniker,
60
- MempoolViewerMoniker,
61
- TimeSyncViewerMoniker,
62
- ]
63
-
64
- static readonly monikers = [BlockRunnerMoniker]
65
- moniker = SimpleBlockRunner.defaultMoniker
66
-
67
- protected _blockRewardDiviner?: BlockRewardDiviner
68
- protected _lastRedeclarationBlock?: number
69
- protected _rejectedTransactionsArchivist?: ArchivistInstance
70
-
71
- private _account?: AccountInstance
72
- private _accountBalanceViewer?: AccountBalanceViewer
73
- private _address?: Address
74
- private _blockRewardViewer?: BlockRewardViewer
75
- private _blockValidationViewer?: BlockValidationViewer
76
- private _finalizationViewer?: FinalizationViewer
77
- private _mempoolRunner?: MempoolRunner
78
- private _mempoolViewer?: MempoolViewer
79
- private _rewardAddress?: Address
80
- private _timeSyncViewer?: TimeSyncViewer
81
-
82
- /**
83
- * The default block size for a block
84
- */
85
- static get DefaultBlockSize(): number {
86
- return DEFAULT_BLOCK_SIZE
87
- }
88
-
89
- /**
90
- * The amount of time for which the producer will redeclare
91
- * their intent to continue producing blocks
92
- */
93
- static get RedeclarationDuration(): number {
94
- return XYO_PRODUCER_REDECLARATION_DURATION
95
- }
96
-
97
- /**
98
- * The number of blocks within which the producer will redeclare
99
- * their intent to continue producing blocks
100
- */
101
- static get RedeclarationWindow(): number {
102
- return XYO_PRODUCER_REDECLARATION_WINDOW
103
- }
104
-
105
- protected get account() {
106
- return this._account!
107
- }
108
-
109
- protected get accountBalanceViewer() {
110
- return this._accountBalanceViewer!
111
- }
112
-
113
- protected get address() {
114
- return this._address!
115
- }
116
-
117
- protected get blockRewardViewer() {
118
- return this._blockRewardViewer!
119
- }
120
-
121
- protected get blockValidationViewer() {
122
- return this._blockValidationViewer!
123
- }
124
-
125
- protected get finalizationViewer() {
126
- return this._finalizationViewer!
127
- }
128
-
129
- protected get heartbeatInterval() {
130
- return this.params.heartbeatInterval ?? 3_600_000
131
- }
132
-
133
- protected get mempoolRunner() {
134
- return this._mempoolRunner!
135
- }
136
-
137
- protected get mempoolViewer() {
138
- return this._mempoolViewer!
139
- }
140
-
141
- // protected get pendingTransactionsService() {
142
- // return assertEx(this.params.pendingTransactionsService, () => 'Missing pendingTransactionsService')
143
- // }
144
-
145
- protected get rejectedTransactionsArchivist() {
146
- return this._rejectedTransactionsArchivist!
147
- }
148
-
149
- protected get rewardAddress(): Address {
150
- return this._rewardAddress!
151
- }
152
-
153
- // protected get stakeIntentService(): StakeIntentService {
154
- // return assertEx(this.params.stakeIntentService, () => 'No StakeIntentService provided')
155
- // }
156
-
157
- protected get timeSyncViewer(): TimeSyncViewer {
158
- return this._timeSyncViewer!
159
- }
160
-
161
- // protected get validateHydratedBlockState() {
162
- // return assertEx(this.params.validateHydratedBlockState, () => 'validateHydratedBlockState is required')
163
- // }
164
-
165
- override async createHandler() {
166
- this._rejectedTransactionsArchivist = this.params.rejectedTransactionsArchivist ?? await MemoryArchivist.create()
167
- this._account = assertEx(this.params.account, () => 'Account is required')
168
- this._address = this.account.address
169
- this._accountBalanceViewer = await this.locateAndCreate<AccountBalanceViewer>(AccountBalanceViewerMoniker)
170
- this._blockRewardViewer = await this.locateAndCreate<BlockRewardViewer>(BlockRewardViewerMoniker)
171
- this._blockValidationViewer = await this.locator.getInstance<BlockValidationViewer>(BlockValidationViewerMoniker)
172
- this._finalizationViewer = await this.locateAndCreate<FinalizationViewer>(FinalizationViewerMoniker)
173
- this._mempoolRunner = await this.locateAndCreate<MempoolRunner>(MempoolRunnerMoniker)
174
- this._mempoolViewer = await this.locateAndCreate<MempoolViewer>(MempoolViewerMoniker)
175
- this._rewardAddress = this.params.rewardAddress
176
- this._timeSyncViewer = await this.locateAndCreate<TimeSyncViewer>(TimeSyncViewerMoniker)
177
- }
178
-
179
- async next(head: WithHashMeta<BlockBoundWitness>): Promise<SignedHydratedBlockWithHashMeta | undefined> {
180
- // If the block is for another chain, ignore
181
- // if (head.chain !== this.chainId) return
182
- // const leadersStart = Date.now()
183
- // const leaders = await this.electionService.getCreatorCommitteeForNextBlock(head)
184
- // const leadersDuration = Date.now() - leadersStart
185
- // if (leadersDuration > 100) {
186
- // this.logger?.warn(`[Slow] Fetched leaders in ${leadersDuration}ms: ${leaders.map(l => l.slice(0, 6)).join(', ')}`)
187
- // }
188
- // TODO: Should we propose block if creator committee is empty?
189
- // TODO: Handle the case where we're not the 1st leader but they're not responding
190
- // at a higher level than here as that's a network issue
191
- // if (!leaders.includes(this.address)) return
192
- return await this.proposeNextValidBlock(head)
193
- }
194
-
195
- async produceNextBlock(head: SignedBlockBoundWitnessWithHashMeta, force: true): Promise<SignedHydratedBlockWithHashMeta>
196
- async produceNextBlock(head: SignedBlockBoundWitnessWithHashMeta, force?: false): Promise<SignedHydratedBlockWithHashMeta | undefined>
197
- async produceNextBlock(head: SignedBlockBoundWitnessWithHashMeta, force?: boolean): Promise<SignedHydratedBlockWithHashMeta | undefined> {
198
- // assertEx(head.chain === this.chainId, () => 'Block chain ID does not match')
199
- const result = await this.proposeNextValidBlock(head)
200
- return force ? assertEx(result, () => 'Failed to produce next block') : result
201
- }
202
-
203
- protected async getBlockRewardTransfers(block: number): Promise<Transfer[]> {
204
- if (!this._blockRewardDiviner) {
205
- // TODO: Adjust to allow for genesis block reward vs. normal block reward
206
- this._blockRewardDiviner = await FixedPercentageBlockRewardDiviner.create({
207
- account: 'random',
208
- blockRewardViewer: this.blockRewardViewer,
209
- config: {
210
- rewardAddress: this.rewardAddress,
211
- rewardPercentageRatio: defaultRewardRatio,
212
- schema: FixedPercentageBlockRewardDivinerConfigSchema,
213
- },
214
- })
215
- }
216
-
217
- const blockId = new PayloadBuilder<BlockNumberPayload>({ schema: BlockNumberSchema }).fields({ block }).build()
218
- const rewards = await this._blockRewardDiviner.divine([blockId])
219
- return rewards as Transfer[]
220
- }
221
-
222
- /**
223
- * Handles the producer redeclaration logic
224
- * @param head The current head block
225
- * @returns chain stake intent for the producer redeclaration, or undefined if no redeclaration is needed
226
- */
227
- protected getProducerRedeclaration(head: WithHashMeta<BlockBoundWitness>): Promisable<ChainStakeIntent | undefined> {
228
- // TODO: Do not redeclare on every block
229
- // Decide if we should redeclare intent
230
- if (this.params.disableIntentRedeclaration) return
231
- // Decide if we need to redeclare intent
232
- // const ranges = await this.stakeIntentService.getDeclaredCandidateRanges(this.address, 'producer')
233
- // TODO: This doesn't handle the case where the producer had declared a range for the future
234
- // but we're in a range that's not the future
235
- // Sort in ascending order based on ending range to get range with highest ending block
236
- // const lastRange = ranges.toSorted((a, b) => a[1] > b[1] ? 1 : -1).at(-1)
237
- // if (!lastRange) return
238
- // const [, currentDeclarationEnd] = lastRange
239
- const currentBlock = head.block
240
- // const timeToProducerExpiration = currentDeclarationEnd - currentBlock
241
- // if (timeToProducerExpiration > BaseBlockProducerService.RedeclarationWindow) return
242
- return createDeclarationIntent(this.address, 'producer', currentBlock, currentBlock + SimpleBlockRunner.RedeclarationDuration)
243
- }
244
-
245
- protected async proposeNextValidBlock(head: WithHashMeta<BlockBoundWitness>, validateBalances = false, force = false) {
246
- // eslint-disable-next-line max-statements
247
- return await this.spanAsync('proposeNextValidBlock', async () => {
248
- try {
249
- // Calculate the next block components
250
- const { block: previousBlock } = assertEx(asBlockBoundWitness(head), () => 'Invalid head block')
251
- const nextBlock = previousBlock + 1
252
- const nextBlockTransactions = await this.mempoolViewer.pendingTransactions({ limit: SimpleBlockRunner.DefaultBlockSize })
253
-
254
- this.logger?.info(`Pending Tx Count ${nextBlockTransactions.length}`)
255
-
256
- const blockPayloads: AllowedBlockPayload[] = []
257
-
258
- // Calculate the optional producer redeclaration and add it if necessary
259
- const producerRedeclarationPayload = await this.getProducerRedeclaration(head)
260
- if (producerRedeclarationPayload) blockPayloads.push(producerRedeclarationPayload)
261
-
262
- // If there are no transactions, no payloads and no heartbeat required, we don't need to create a block
263
- if (nextBlockTransactions.length === 0 && !this.heartbeatRequired(head) && !force) return
264
-
265
- // Calculate the optional block reward transfer and add if necessary
266
- const rewardTransferPayloads = await this.getBlockRewardTransfers(nextBlock)
267
- blockPayloads.push(...rewardTransferPayloads)
268
-
269
- const transactionTransfers = await generateTransactionFeeTransfers(this.address, nextBlockTransactions)
270
- const timeStart = Date.now()
271
- const timePayload = await this.generateTimePayload()
272
- const timeDuration = Date.now() - timeStart
273
- if (timeDuration > 100) {
274
- this.logger?.warn(`[Slow] Generated time payload in ${timeDuration}ms`)
275
- }
276
-
277
- const [fundedNextBlockTransactions, fundedTransfers] = await this.filterByFunded(head, nextBlockTransactions, transactionTransfers, validateBalances)
278
-
279
- blockPayloads.push(...fundedTransfers, timePayload)
280
-
281
- // Build the block
282
- this.logger?.info(`Building block ${head.block + 1}`)
283
- const startBuild = Date.now()
284
- const stepRewardPoolBalance = (await this.accountBalanceViewer.accountBalances([XYO_STEP_REWARD_ADDRESS]))[XYO_STEP_REWARD_ADDRESS]
285
- const block = await buildNextBlock(
286
- head,
287
- fundedNextBlockTransactions,
288
- blockPayloads,
289
- [this.account],
290
- XYO_STEP_REWARD_ADDRESS,
291
- stepRewardPoolBalance,
292
- undefined,
293
- await this.finalizationViewer.chainId(),
294
- )
295
-
296
- this.logger?.info(
297
- `Built block ${block[0].block} in ${Date.now() - startBuild}ms with ${block[1].length} payloads`,
298
- )
299
-
300
- this.logger?.info(`Validating block ${block[0].block} with ${block[1].length} payloads`)
301
- const startValidate = Date.now()
302
- const validatedBlock = await this.blockValidationViewer.validateBlock(block, { head: head._hash })
303
- this.logger?.info(`Validated block ${block[0].block} in ${Date.now() - startValidate}ms with ${block[1].length} payloads`)
304
-
305
- if (isSignedHydratedBlockWithHashMeta(validatedBlock)) {
306
- await this.mempoolRunner.submitBlocks([validatedBlock])
307
- return validatedBlock
308
- } else {
309
- const errors = validatedBlock
310
- this.logger?.warn(`Validation of produced block failed: ${errors.at(0)?.message}`)
311
- const rejectedTransactions = block[1]
312
- await this.rejectedTransactionsArchivist.insert(rejectedTransactions)
313
- }
314
- } catch (error) {
315
- this.logger?.error(`Error proposing next valid block: ${(error as Error).message}`)
316
- throw error
317
- }
318
- }, this.context)
319
- }
320
-
321
- // remove unfunded transactions and block transfers
322
- private async filterByFunded(
323
- head: WithHashMeta<BlockBoundWitness>,
324
- txs: SignedHydratedTransaction[],
325
- transfers: Transfer[],
326
- validateBalances = false,
327
- ): Promise<[SignedHydratedTransaction[], Transfer[]]> {
328
- const fundedTransfers: Transfer[] = []
329
- const fundedTransactions = (await Promise.all(txs.map(async (tx) => {
330
- const transfer: Transfer | undefined = transfers.find(transfer => transfer.from === tx[0].from)
331
- if (!transfer) return
332
- const totalTransferCost = Object.values(transfer?.transfers).reduce((acc, t) => acc + hexToBigInt(t ?? '00' as Hex), 0n)
333
- if (validateBalances) {
334
- const balance = (await this.accountBalanceViewer.accountBalances([transfer.from]))[transfer.from] ?? AttoXL1(0n)
335
- if (balance >= totalTransferCost) {
336
- fundedTransfers.push(transfer)
337
- return tx
338
- }
339
- } else {
340
- fundedTransfers.push(transfer)
341
- return tx
342
- }
343
- }))).filter(exists)
344
- return [fundedTransactions, fundedTransfers]
345
- }
346
-
347
- private async generateTimePayload() {
348
- return await this.timeSyncViewer.currentTimePayload()
349
- }
350
-
351
- /**
352
- * Check if a heartbeat block is required based on network activity.
353
- * @param head The current head block
354
- * @returns True if a heartbeat is required, false otherwise
355
- */
356
- private heartbeatRequired(head: WithHashMeta<BlockBoundWitness>): boolean {
357
- const epoch = head.$epoch
358
- if (isDefined(epoch) && Date.now() - epoch > this.heartbeatInterval) {
359
- return true
360
- }
361
- return false
362
- }
363
- }