@xyo-network/chain-services 1.7.7 → 1.7.9
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/README.md +8669 -3
- package/dist/neutral/index.mjs +1 -1
- package/dist/neutral/index.mjs.map +1 -1
- package/package.json +43 -41
- package/src/BlockProducer/BaseBlockProducerService.ts +1 -1
- package/src/BlockProducer/spec/BaseBlockProducerService.spec.ts +320 -0
- package/src/BlockProducer/spec/generateTransactionTransfer.spec.ts +72 -0
- package/src/PendingTransactions/spec/BasePendingTransactions.spec.ts +274 -0
- package/src/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.ts +35 -0
- package/src/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.ts +28 -0
- package/src/StakeIntent/XyoStakeIntentService.ts +1 -1
- package/xy.config.ts +0 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@xyo-network/chain-services",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.9",
|
|
5
5
|
"description": "XYO Layer One SDK Services",
|
|
6
6
|
"homepage": "https://xylabs.com",
|
|
7
7
|
"bugs": {
|
|
@@ -23,62 +23,64 @@
|
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
25
|
"types": "./dist/neutral/index.d.ts",
|
|
26
|
+
"source": "./src/index.ts",
|
|
26
27
|
"default": "./dist/neutral/index.mjs"
|
|
27
28
|
},
|
|
28
29
|
"./package.json": "./package.json"
|
|
29
30
|
},
|
|
30
31
|
"module": "./dist/neutral/index.mjs",
|
|
32
|
+
"source": "./src/index.ts",
|
|
31
33
|
"types": "./dist/neutral/index.d.ts",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"src"
|
|
37
|
+
],
|
|
36
38
|
"dependencies": {
|
|
37
39
|
"@opentelemetry/api": "^1.9.0",
|
|
38
|
-
"@xylabs/array": "^4.13.
|
|
39
|
-
"@xylabs/assert": "^4.13.
|
|
40
|
-
"@xylabs/creatable": "^4.13.
|
|
41
|
-
"@xylabs/decimal-precision": "^4.13.
|
|
42
|
-
"@xylabs/events": "^4.13.
|
|
43
|
-
"@xylabs/exists": "^4.13.
|
|
44
|
-
"@xylabs/forget": "^4.13.
|
|
45
|
-
"@xylabs/hex": "^4.13.
|
|
46
|
-
"@xylabs/promise": "^4.13.
|
|
47
|
-
"@xylabs/telemetry": "^4.13.
|
|
48
|
-
"@xylabs/typeof": "^4.13.
|
|
49
|
-
"@xyo-network/account-model": "^4.1.
|
|
50
|
-
"@xyo-network/archivist-memory": "^4.1.
|
|
51
|
-
"@xyo-network/archivist-model": "^4.1.
|
|
52
|
-
"@xyo-network/boundwitness-model": "^4.1.
|
|
53
|
-
"@xyo-network/boundwitness-validator": "^4.1.
|
|
54
|
-
"@xyo-network/boundwitness-wrapper": "^4.1.
|
|
55
|
-
"@xyo-network/chain-analyze": "^1.7.
|
|
56
|
-
"@xyo-network/chain-ethereum": "^1.7.
|
|
57
|
-
"@xyo-network/chain-modules": "^1.7.
|
|
58
|
-
"@xyo-network/chain-protocol": "^1.7.
|
|
59
|
-
"@xyo-network/chain-utils": "^1.7.
|
|
60
|
-
"@xyo-network/chain-validation": "^1.7.
|
|
61
|
-
"@xyo-network/chain-wrappers": "^1.7.
|
|
62
|
-
"@xyo-network/payload-builder": "^4.1.
|
|
63
|
-
"@xyo-network/payload-model": "^4.1.
|
|
40
|
+
"@xylabs/array": "^4.13.21",
|
|
41
|
+
"@xylabs/assert": "^4.13.21",
|
|
42
|
+
"@xylabs/creatable": "^4.13.21",
|
|
43
|
+
"@xylabs/decimal-precision": "^4.13.21",
|
|
44
|
+
"@xylabs/events": "^4.13.21",
|
|
45
|
+
"@xylabs/exists": "^4.13.21",
|
|
46
|
+
"@xylabs/forget": "^4.13.21",
|
|
47
|
+
"@xylabs/hex": "^4.13.21",
|
|
48
|
+
"@xylabs/promise": "^4.13.21",
|
|
49
|
+
"@xylabs/telemetry": "^4.13.21",
|
|
50
|
+
"@xylabs/typeof": "^4.13.21",
|
|
51
|
+
"@xyo-network/account-model": "^4.1.6",
|
|
52
|
+
"@xyo-network/archivist-memory": "^4.1.6",
|
|
53
|
+
"@xyo-network/archivist-model": "^4.1.6",
|
|
54
|
+
"@xyo-network/boundwitness-model": "^4.1.6",
|
|
55
|
+
"@xyo-network/boundwitness-validator": "^4.1.6",
|
|
56
|
+
"@xyo-network/boundwitness-wrapper": "^4.1.6",
|
|
57
|
+
"@xyo-network/chain-analyze": "^1.7.9",
|
|
58
|
+
"@xyo-network/chain-ethereum": "^1.7.9",
|
|
59
|
+
"@xyo-network/chain-modules": "^1.7.9",
|
|
60
|
+
"@xyo-network/chain-protocol": "^1.7.9",
|
|
61
|
+
"@xyo-network/chain-utils": "^1.7.9",
|
|
62
|
+
"@xyo-network/chain-validation": "^1.7.9",
|
|
63
|
+
"@xyo-network/chain-wrappers": "^1.7.9",
|
|
64
|
+
"@xyo-network/payload-builder": "^4.1.6",
|
|
65
|
+
"@xyo-network/payload-model": "^4.1.6",
|
|
64
66
|
"@xyo-network/typechain": "^3.5.4",
|
|
65
|
-
"@xyo-network/xl1-protocol": "^1.7.
|
|
66
|
-
"@xyo-network/xl1-protocol-sdk": "^1.7.
|
|
67
|
+
"@xyo-network/xl1-protocol": "^1.7.10",
|
|
68
|
+
"@xyo-network/xl1-protocol-sdk": "^1.7.9",
|
|
67
69
|
"async-mutex": "^0.5.0",
|
|
68
70
|
"ethers": "6.15.0",
|
|
69
71
|
"lru-cache": "^11.1.0"
|
|
70
72
|
},
|
|
71
73
|
"devDependencies": {
|
|
72
|
-
"@types/node": "^24.0.
|
|
73
|
-
"@xylabs/delay": "^4.13.
|
|
74
|
+
"@types/node": "^24.0.15",
|
|
75
|
+
"@xylabs/delay": "^4.13.21",
|
|
74
76
|
"@xylabs/ts-scripts-yarn3": "^7.0.0",
|
|
75
77
|
"@xylabs/tsconfig": "^7.0.0",
|
|
76
|
-
"@xylabs/vitest-extended": "^4.13.
|
|
77
|
-
"@xyo-network/account": "^4.1.
|
|
78
|
-
"@xyo-network/account-model": "^4.1.
|
|
79
|
-
"@xyo-network/chain-validation": "^1.7.
|
|
80
|
-
"@xyo-network/wallet": "^4.1.
|
|
81
|
-
"knip": "^5.
|
|
78
|
+
"@xylabs/vitest-extended": "^4.13.21",
|
|
79
|
+
"@xyo-network/account": "^4.1.6",
|
|
80
|
+
"@xyo-network/account-model": "^4.1.6",
|
|
81
|
+
"@xyo-network/chain-validation": "^1.7.9",
|
|
82
|
+
"@xyo-network/wallet": "^4.1.6",
|
|
83
|
+
"knip": "^5.62.0",
|
|
82
84
|
"typescript": "^5.8.3",
|
|
83
85
|
"vitest": "^3.2.4",
|
|
84
86
|
"vitest-mock-extended": "^3.1.0",
|
|
@@ -215,7 +215,7 @@ export class BaseBlockProducerService extends BaseService<BaseBlockProducerServi
|
|
|
215
215
|
|
|
216
216
|
// Build the block
|
|
217
217
|
const block = await buildNextBlock(head, fundedNextBlockTransactions, blockPayloads, [this.account])
|
|
218
|
-
this.logger?.
|
|
218
|
+
this.logger?.info(`buildBlock: ${block[0].block} with ${block[1].length} payloads`)
|
|
219
219
|
const errors = await this.validateHydratedBlockState(block, this.chainId, { accountBalance: this.balanceService })
|
|
220
220
|
if (errors.length > 0) {
|
|
221
221
|
this.logger?.warn(`Validation of produced block failed: ${errors.at(0)?.message}`)
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import '@xylabs/vitest-extended'
|
|
2
|
+
|
|
3
|
+
import { filterAs } from '@xylabs/array'
|
|
4
|
+
import { assertEx } from '@xylabs/assert'
|
|
5
|
+
import { delay } from '@xylabs/delay'
|
|
6
|
+
import type { Address } from '@xylabs/hex'
|
|
7
|
+
import { asAddress, ZERO_HASH } from '@xylabs/hex'
|
|
8
|
+
import { Account } from '@xyo-network/account'
|
|
9
|
+
import type { AccountInstance } from '@xyo-network/account-model'
|
|
10
|
+
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
11
|
+
import type { ArchivistInstance } from '@xyo-network/archivist-model'
|
|
12
|
+
import { buildRandomChain, buildRandomTransaction } from '@xyo-network/chain-protocol'
|
|
13
|
+
import { validateHydratedBlockState } from '@xyo-network/chain-validation'
|
|
14
|
+
import {
|
|
15
|
+
type PayloadBundle, PayloadBundleSchema, type WithStorageMeta,
|
|
16
|
+
} from '@xyo-network/payload-model'
|
|
17
|
+
import { HDWallet } from '@xyo-network/wallet'
|
|
18
|
+
import type {
|
|
19
|
+
BlockBoundWitness, BlockRewardService, ElectionService,
|
|
20
|
+
HydratedBlock,
|
|
21
|
+
HydratedBlockStateValidationFunctionV2,
|
|
22
|
+
StakeIntentService,
|
|
23
|
+
} from '@xyo-network/xl1-protocol'
|
|
24
|
+
import {
|
|
25
|
+
asBlockBoundWitness,
|
|
26
|
+
asChainStakeIntent,
|
|
27
|
+
asTransactionBoundWitness,
|
|
28
|
+
HydratedBlockStateValidationError,
|
|
29
|
+
} from '@xyo-network/xl1-protocol'
|
|
30
|
+
import type { Config } from '@xyo-network/xl1-protocol-sdk'
|
|
31
|
+
import {
|
|
32
|
+
flattenHydratedBlock, flattenHydratedTransaction, getDefaultConfig,
|
|
33
|
+
} from '@xyo-network/xl1-protocol-sdk'
|
|
34
|
+
import {
|
|
35
|
+
beforeAll, beforeEach, describe, expect, it,
|
|
36
|
+
} from 'vitest'
|
|
37
|
+
import { mock } from 'vitest-mock-extended'
|
|
38
|
+
|
|
39
|
+
import { accountBalanceServiceFromArchivist } from '../../AccountBalance/index.ts'
|
|
40
|
+
import { MemoryBlockRewardService } from '../../BlockReward/index.ts'
|
|
41
|
+
import type { BasePendingTransactionsServiceParams } from '../../PendingTransactions/index.ts'
|
|
42
|
+
import { BasePendingTransactionsService } from '../../PendingTransactions/index.ts'
|
|
43
|
+
import type { BaseBlockProducerServiceParams } from '../BaseBlockProducerService.ts'
|
|
44
|
+
import { BaseBlockProducerService } from '../BaseBlockProducerService.ts'
|
|
45
|
+
|
|
46
|
+
describe('XyoBlockProducer', () => {
|
|
47
|
+
const leaderCount = 3
|
|
48
|
+
let account: AccountInstance
|
|
49
|
+
let blockProducer: BaseBlockProducerService
|
|
50
|
+
let chainArchivist: ArchivistInstance
|
|
51
|
+
let electionService: ReturnType<typeof mock<ElectionService>>
|
|
52
|
+
let pendingBundledTransactionsArchivist: ArchivistInstance
|
|
53
|
+
let pendingTransactionsService: BasePendingTransactionsService
|
|
54
|
+
let rejectedTransactionsArchivist: ArchivistInstance
|
|
55
|
+
let rewardAddress: Address = '1111111111111111111111111111111111111111'
|
|
56
|
+
let rewardService: BlockRewardService
|
|
57
|
+
let stakeIntentService: ReturnType<typeof mock<StakeIntentService>>
|
|
58
|
+
let transactionAccount: AccountInstance
|
|
59
|
+
const validPendingTransactions = Math.ceil(BaseBlockProducerService.DefaultBlockSize)
|
|
60
|
+
let config: Config
|
|
61
|
+
|
|
62
|
+
let currentBlock: WithStorageMeta<BlockBoundWitness>
|
|
63
|
+
|
|
64
|
+
const chainId = assertEx(asAddress('Be17531fec6fEc55f3EAc3f1c187c87e4C47F81E'))
|
|
65
|
+
|
|
66
|
+
beforeAll(async () => {
|
|
67
|
+
account = await Account.random()
|
|
68
|
+
transactionAccount = await HDWallet.fromPhrase('room maximum palace fragile man pyramid school indoor base business want bronze assume marble report')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
beforeEach(async () => {
|
|
72
|
+
config = getDefaultConfig()
|
|
73
|
+
config.producer.disableIntentRedeclaration = false
|
|
74
|
+
chainArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
75
|
+
rejectedTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
76
|
+
electionService = mock<ElectionService>()
|
|
77
|
+
pendingBundledTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
78
|
+
const BasePendingTransactionsServiceParams: BasePendingTransactionsServiceParams = {
|
|
79
|
+
name: 'TestBasePendingTransactionsServiceParams',
|
|
80
|
+
chainArchivist: chainArchivist,
|
|
81
|
+
chainId,
|
|
82
|
+
config,
|
|
83
|
+
pendingBundledTransactionsArchivist,
|
|
84
|
+
rejectedTransactionsArchivist,
|
|
85
|
+
}
|
|
86
|
+
pendingTransactionsService = await BasePendingTransactionsService.create(BasePendingTransactionsServiceParams)
|
|
87
|
+
await addPendingTransactions(validPendingTransactions)
|
|
88
|
+
|
|
89
|
+
stakeIntentService = mock<StakeIntentService>()
|
|
90
|
+
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
|
|
91
|
+
rewardService = await MemoryBlockRewardService.create()
|
|
92
|
+
const balanceService = await accountBalanceServiceFromArchivist(chainArchivist)
|
|
93
|
+
const params: BaseBlockProducerServiceParams = {
|
|
94
|
+
name: 'TestXyoBlockProducerParams',
|
|
95
|
+
account,
|
|
96
|
+
balanceService,
|
|
97
|
+
chainArchivist,
|
|
98
|
+
chainId,
|
|
99
|
+
config,
|
|
100
|
+
electionService,
|
|
101
|
+
pendingTransactionsService,
|
|
102
|
+
pendingBundledTransactionsArchivist,
|
|
103
|
+
rejectedTransactionsArchivist,
|
|
104
|
+
rewardAddress,
|
|
105
|
+
rewardService,
|
|
106
|
+
stakeIntentService,
|
|
107
|
+
validateHydratedBlockState,
|
|
108
|
+
}
|
|
109
|
+
blockProducer = await BaseBlockProducerService.create(params)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const addPendingTransactions = async (count: number = validPendingTransactions) => {
|
|
113
|
+
for (let i = 0; i < count; i++) {
|
|
114
|
+
const transaction = await buildRandomTransaction(chainId, [], transactionAccount)
|
|
115
|
+
const bundledPayload: PayloadBundle = {
|
|
116
|
+
schema: PayloadBundleSchema, payloads: flattenHydratedTransaction(transaction), root: transaction[0]._hash,
|
|
117
|
+
}
|
|
118
|
+
await pendingBundledTransactionsArchivist.insert([bundledPayload])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe('next', () => {
|
|
123
|
+
describe('block production', () => {
|
|
124
|
+
describe('when not current block leader', () => {
|
|
125
|
+
beforeEach(async () => {
|
|
126
|
+
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
127
|
+
currentBlock = hydratedBlock[0]
|
|
128
|
+
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
129
|
+
const leaders = await Promise.all(Array.from({ length: leaderCount }, async () => (await Account.random()).address))
|
|
130
|
+
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue(leaders)
|
|
131
|
+
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
|
|
132
|
+
})
|
|
133
|
+
it('should return undefined', async () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
|
|
136
|
+
// Act
|
|
137
|
+
const result = await blockProducer.next(currentBlock)
|
|
138
|
+
|
|
139
|
+
// Assert
|
|
140
|
+
expect(result).toBeUndefined()
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
describe('when current block leader', () => {
|
|
144
|
+
beforeEach(async () => {
|
|
145
|
+
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
146
|
+
currentBlock = hydratedBlock[0]
|
|
147
|
+
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
148
|
+
await delay(1000)
|
|
149
|
+
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
150
|
+
})
|
|
151
|
+
it('should return a valid block', async () => {
|
|
152
|
+
// Arrange
|
|
153
|
+
|
|
154
|
+
// Act
|
|
155
|
+
const result = await blockProducer.next(currentBlock)
|
|
156
|
+
|
|
157
|
+
// Assert
|
|
158
|
+
expect(result).toBeDefined()
|
|
159
|
+
expect(result).toBeArrayOfSize(2)
|
|
160
|
+
const [block, transactionsAndData] = result ?? []
|
|
161
|
+
expect(block).toBeDefined()
|
|
162
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
163
|
+
expect(transactionsAndData).toBeDefined()
|
|
164
|
+
expect(transactionsAndData).toBeArray()
|
|
165
|
+
const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
|
|
166
|
+
expect(transactions).toBeArrayOfSize(validPendingTransactions)
|
|
167
|
+
})
|
|
168
|
+
it('should remove invalid transactions', async () => {
|
|
169
|
+
// Arrange
|
|
170
|
+
let rejectBlock = true
|
|
171
|
+
const validateHydratedBlockState: HydratedBlockStateValidationFunctionV2 = async (
|
|
172
|
+
hydratedBlock: HydratedBlock,
|
|
173
|
+
chainId: Address,
|
|
174
|
+
) => {
|
|
175
|
+
return rejectBlock ? [await Promise.resolve(new HydratedBlockStateValidationError(ZERO_HASH, chainId, hydratedBlock, 'Invalid block'))] : []
|
|
176
|
+
}
|
|
177
|
+
const balanceService = await accountBalanceServiceFromArchivist(chainArchivist)
|
|
178
|
+
const params: BaseBlockProducerServiceParams = {
|
|
179
|
+
name: 'TestXyoBlockProducerParams',
|
|
180
|
+
account,
|
|
181
|
+
balanceService,
|
|
182
|
+
chainArchivist,
|
|
183
|
+
chainId,
|
|
184
|
+
config,
|
|
185
|
+
electionService,
|
|
186
|
+
pendingBundledTransactionsArchivist,
|
|
187
|
+
pendingTransactionsService,
|
|
188
|
+
rejectedTransactionsArchivist,
|
|
189
|
+
rewardAddress,
|
|
190
|
+
rewardService,
|
|
191
|
+
stakeIntentService,
|
|
192
|
+
validateHydratedBlockState,
|
|
193
|
+
}
|
|
194
|
+
blockProducer = await BaseBlockProducerService.create(params)
|
|
195
|
+
|
|
196
|
+
// Act
|
|
197
|
+
// Force producer to reject first block
|
|
198
|
+
rejectBlock = true
|
|
199
|
+
const invalidTransactionCount = Math.floor(validPendingTransactions / 2)
|
|
200
|
+
await addPendingTransactions(invalidTransactionCount)
|
|
201
|
+
|
|
202
|
+
// Ensure bad block is not produced
|
|
203
|
+
const firstResult = await blockProducer.next(currentBlock)
|
|
204
|
+
expect(firstResult).toBeUndefined()
|
|
205
|
+
// Do not reject second block
|
|
206
|
+
rejectBlock = false
|
|
207
|
+
// Add more pending transactions
|
|
208
|
+
const newTransactionCount = Math.floor(validPendingTransactions / 2)
|
|
209
|
+
await addPendingTransactions(newTransactionCount)
|
|
210
|
+
// Ensure producer recovers from bad block
|
|
211
|
+
const result = await blockProducer.next(currentBlock)
|
|
212
|
+
|
|
213
|
+
// Assert
|
|
214
|
+
expect(result).toBeDefined()
|
|
215
|
+
expect(result).toBeArrayOfSize(2)
|
|
216
|
+
const [block, transactionsAndData] = result ?? []
|
|
217
|
+
expect(block).toBeDefined()
|
|
218
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
219
|
+
expect(transactionsAndData).toBeDefined()
|
|
220
|
+
expect(transactionsAndData).toBeArray()
|
|
221
|
+
const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
|
|
222
|
+
expect(transactions).toBeArrayOfSize(newTransactionCount)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
describe('producer re-declare intent', () => {
|
|
227
|
+
describe('when within re-declare intent window', () => {
|
|
228
|
+
beforeEach(async () => {
|
|
229
|
+
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
230
|
+
currentBlock = hydratedBlock[0]
|
|
231
|
+
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
232
|
+
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
233
|
+
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 10]])
|
|
234
|
+
})
|
|
235
|
+
it('should re-declare intent if configured for re-declaration', async () => {
|
|
236
|
+
// Arrange
|
|
237
|
+
config.producer.disableIntentRedeclaration = false
|
|
238
|
+
|
|
239
|
+
// Act
|
|
240
|
+
const result = await blockProducer.next(currentBlock)
|
|
241
|
+
|
|
242
|
+
// Assert
|
|
243
|
+
expect(result).toBeDefined()
|
|
244
|
+
expect(result).toBeArrayOfSize(2)
|
|
245
|
+
const [block, payloads] = result ?? []
|
|
246
|
+
expect(block).toBeDefined()
|
|
247
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
248
|
+
expect(payloads).toBeArray()
|
|
249
|
+
const allData = flattenHydratedBlock(assertEx(result))
|
|
250
|
+
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
251
|
+
expect(declaration).toBeDefined()
|
|
252
|
+
expect(declaration?.from).toEqual(blockProducer.address)
|
|
253
|
+
})
|
|
254
|
+
it('should not re-declare intent if not configured for re-declaration', async () => {
|
|
255
|
+
// Arrange
|
|
256
|
+
config.producer.disableIntentRedeclaration = true
|
|
257
|
+
|
|
258
|
+
// Act
|
|
259
|
+
const result = await blockProducer.next(currentBlock)
|
|
260
|
+
|
|
261
|
+
// Assert
|
|
262
|
+
expect(result).toBeDefined()
|
|
263
|
+
expect(result).toBeArrayOfSize(2)
|
|
264
|
+
const [block, payloads] = result ?? []
|
|
265
|
+
expect(block).toBeDefined()
|
|
266
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
267
|
+
expect(payloads).toBeArray()
|
|
268
|
+
const allData = flattenHydratedBlock(assertEx(result))
|
|
269
|
+
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
270
|
+
expect(declaration).toBeUndefined()
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
describe('when not within re-declare intent window', () => {
|
|
274
|
+
beforeEach(async () => {
|
|
275
|
+
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
276
|
+
currentBlock = hydratedBlock[0]
|
|
277
|
+
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
278
|
+
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
279
|
+
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 1_000_000_000]])
|
|
280
|
+
})
|
|
281
|
+
it('should not re-declare intent if configured for re-declaration', async () => {
|
|
282
|
+
// Arrange
|
|
283
|
+
config.producer.disableIntentRedeclaration = false
|
|
284
|
+
|
|
285
|
+
// Act
|
|
286
|
+
const result = await blockProducer.next(currentBlock)
|
|
287
|
+
|
|
288
|
+
// Assert
|
|
289
|
+
expect(result).toBeDefined()
|
|
290
|
+
expect(result).toBeArrayOfSize(2)
|
|
291
|
+
const [block, payloads] = result ?? []
|
|
292
|
+
expect(block).toBeDefined()
|
|
293
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
294
|
+
expect(payloads).toBeArray()
|
|
295
|
+
const allData = flattenHydratedBlock(assertEx(result))
|
|
296
|
+
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
297
|
+
expect(declaration).toBeUndefined()
|
|
298
|
+
})
|
|
299
|
+
it('should not re-declare intent if not configured for re-declaration', async () => {
|
|
300
|
+
// Arrange
|
|
301
|
+
config.producer.disableIntentRedeclaration = true
|
|
302
|
+
|
|
303
|
+
// Act
|
|
304
|
+
const result = await blockProducer.next(currentBlock)
|
|
305
|
+
|
|
306
|
+
// Assert
|
|
307
|
+
expect(result).toBeDefined()
|
|
308
|
+
expect(result).toBeArrayOfSize(2)
|
|
309
|
+
const [block, payloads] = result ?? []
|
|
310
|
+
expect(block).toBeDefined()
|
|
311
|
+
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
312
|
+
expect(payloads).toBeArray()
|
|
313
|
+
const allData = flattenHydratedBlock(assertEx(result))
|
|
314
|
+
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
315
|
+
expect(declaration).toBeUndefined()
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import {
|
|
3
|
+
asAddress, hexToBigInt, toHex,
|
|
4
|
+
} from '@xylabs/hex'
|
|
5
|
+
import { isDefined } from '@xylabs/typeof'
|
|
6
|
+
import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
|
|
7
|
+
import type { WithStorageMeta } from '@xyo-network/payload-model'
|
|
8
|
+
import {
|
|
9
|
+
defaultTransactionFees, type HydratedTransaction, type Transfer,
|
|
10
|
+
} from '@xyo-network/xl1-protocol'
|
|
11
|
+
import {
|
|
12
|
+
describe, expect, it,
|
|
13
|
+
} from 'vitest'
|
|
14
|
+
|
|
15
|
+
import { generateTransactionFeeTransfers } from '../generateTransactionFeeTransfers.ts'
|
|
16
|
+
|
|
17
|
+
describe('generateTransactionTransfers', () => {
|
|
18
|
+
const testCases: HydratedTransaction[][] = [
|
|
19
|
+
[
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
schema: 'network.xyo.boundwitness',
|
|
23
|
+
addresses: ['39d22bed37f77bee0c056f648bde6efa489981c3'],
|
|
24
|
+
payload_hashes: ['65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8'],
|
|
25
|
+
payload_schemas: ['network.xyo.transfer'],
|
|
26
|
+
previous_hashes: [null],
|
|
27
|
+
$signatures: [
|
|
28
|
+
'0605878a92045bb752cc12066b585589b513cee652bf4bc36c19289495ccd3d3751a1202b5bfb334325c4c6a59a4a7dad7468739a8b4ffe07b6091317b16dc09',
|
|
29
|
+
],
|
|
30
|
+
nbf: 2835,
|
|
31
|
+
exp: 3835,
|
|
32
|
+
fees: {
|
|
33
|
+
base: toHex(defaultTransactionFees.base),
|
|
34
|
+
gasLimit: toHex(defaultTransactionFees.gasLimit),
|
|
35
|
+
gasPrice: toHex(defaultTransactionFees.gasPrice),
|
|
36
|
+
priority: '00',
|
|
37
|
+
},
|
|
38
|
+
chain: 'a82920051db4fcbb804463440dd45e03f72442fd',
|
|
39
|
+
script: ['elevate|65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8'],
|
|
40
|
+
from: '39d22bed37f77bee0c056f648bde6efa489981c3',
|
|
41
|
+
_dataHash: '47be77303edd6073f82e9fff119107f1e173a08e7285e8e5f6e0c979d43e5b7d',
|
|
42
|
+
_hash: '1419062ddb266ff58515c4ffbc435ec482b6129d9e0c3c707fb7610c67215281',
|
|
43
|
+
_sequence: '00000196f46014770000000167215281',
|
|
44
|
+
},
|
|
45
|
+
[
|
|
46
|
+
{
|
|
47
|
+
schema: 'network.xyo.transfer',
|
|
48
|
+
epoch: 1_747_856_659_420,
|
|
49
|
+
from: '39d22bed37f77bee0c056f648bde6efa489981c3',
|
|
50
|
+
transfers: { db5df9c99e661500c95f2c933023ba693e5c1ae9: '056bc75e2d63100000' },
|
|
51
|
+
_dataHash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8',
|
|
52
|
+
_hash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8',
|
|
53
|
+
_sequence: '00000196f4601477000000002aef69c8',
|
|
54
|
+
} as WithStorageMeta<Transfer>,
|
|
55
|
+
],
|
|
56
|
+
],
|
|
57
|
+
],
|
|
58
|
+
]
|
|
59
|
+
it.each(testCases)('should generate correct Transfer payloads from hydrated transactions', async (...transactions) => {
|
|
60
|
+
const producerAddress = assertEx(asAddress('3e86dac7546202156c4620d3cf36fb0ac30404a3'))
|
|
61
|
+
|
|
62
|
+
// Patch the transactionRequiredGas function to return our test gas amount
|
|
63
|
+
|
|
64
|
+
const result = await generateTransactionFeeTransfers(producerAddress, transactions)
|
|
65
|
+
|
|
66
|
+
const burnValueHex = result.at(0)?.transfers[XYO_ZERO_ADDRESS]
|
|
67
|
+
expect(burnValueHex).toBeDefined()
|
|
68
|
+
const burnValue = isDefined(burnValueHex) ? hexToBigInt(burnValueHex) : 0n
|
|
69
|
+
expect(burnValue).toBeGreaterThan(0n)
|
|
70
|
+
expect(burnValue).toEqual(1_000_000_000_000n)
|
|
71
|
+
})
|
|
72
|
+
})
|