@xyo-network/chain-services 1.16.16 → 1.16.17
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/package.json +13 -10
- package/dist/neutral/BlockProducer/spec/BaseBlockProducerService.spec.d.ts +0 -2
- package/dist/neutral/BlockProducer/spec/BaseBlockProducerService.spec.d.ts.map +0 -1
- package/dist/neutral/BlockProducer/spec/generateTransactionTransfer.spec.d.ts +0 -2
- package/dist/neutral/BlockProducer/spec/generateTransactionTransfer.spec.d.ts.map +0 -1
- package/dist/neutral/BlockReward/spec/MemoryBlockRewardService.spec.d.ts +0 -2
- package/dist/neutral/BlockReward/spec/MemoryBlockRewardService.spec.d.ts.map +0 -1
- package/dist/neutral/PendingTransactions/spec/BasePendingTransactions.spec.d.ts +0 -2
- package/dist/neutral/PendingTransactions/spec/BasePendingTransactions.spec.d.ts.map +0 -1
- package/dist/neutral/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.d.ts +0 -2
- package/dist/neutral/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.d.ts.map +0 -1
- package/dist/neutral/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.d.ts +0 -2
- package/dist/neutral/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.d.ts.map +0 -1
- package/src/BlockProducer/spec/BaseBlockProducerService.spec.ts +0 -391
- package/src/BlockProducer/spec/generateTransactionTransfer.spec.ts +0 -75
- package/src/BlockReward/spec/MemoryBlockRewardService.spec.ts +0 -64
- package/src/PendingTransactions/spec/BasePendingTransactions.spec.ts +0 -276
- package/src/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.ts +0 -35
- package/src/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.ts +0 -28
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.16.
|
|
4
|
+
"version": "1.16.17",
|
|
5
5
|
"description": "XYO Layer One SDK Services",
|
|
6
6
|
"homepage": "https://xylabs.com",
|
|
7
7
|
"bugs": {
|
|
@@ -33,7 +33,10 @@
|
|
|
33
33
|
"types": "./dist/neutral/index.d.ts",
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
36
|
-
"src"
|
|
36
|
+
"src",
|
|
37
|
+
"!**/*.bench.*",
|
|
38
|
+
"!**/*.spec.*",
|
|
39
|
+
"!**/*.test.*"
|
|
37
40
|
],
|
|
38
41
|
"dependencies": {
|
|
39
42
|
"@opentelemetry/api": "~1.9.0",
|
|
@@ -45,17 +48,17 @@
|
|
|
45
48
|
"@xyo-network/boundwitness-model": "~5.1.23",
|
|
46
49
|
"@xyo-network/boundwitness-validator": "~5.1.23",
|
|
47
50
|
"@xyo-network/boundwitness-wrapper": "~5.1.23",
|
|
48
|
-
"@xyo-network/chain-analyze": "~1.16.
|
|
49
|
-
"@xyo-network/chain-modules": "~1.16.
|
|
50
|
-
"@xyo-network/chain-protocol": "~1.16.
|
|
51
|
-
"@xyo-network/chain-utils": "~1.16.
|
|
51
|
+
"@xyo-network/chain-analyze": "~1.16.17",
|
|
52
|
+
"@xyo-network/chain-modules": "~1.16.17",
|
|
53
|
+
"@xyo-network/chain-protocol": "~1.16.17",
|
|
54
|
+
"@xyo-network/chain-utils": "~1.16.17",
|
|
52
55
|
"@xyo-network/payload-builder": "~5.1.23",
|
|
53
56
|
"@xyo-network/payload-model": "~5.1.23",
|
|
54
57
|
"@xyo-network/typechain": "~4.0.10",
|
|
55
58
|
"@xyo-network/xl1-protocol": "~1.13.11",
|
|
56
|
-
"@xyo-network/xl1-protocol-sdk": "~1.16.
|
|
57
|
-
"@xyo-network/xl1-validation": "~1.16.
|
|
58
|
-
"@xyo-network/xl1-wrappers": "~1.16.
|
|
59
|
+
"@xyo-network/xl1-protocol-sdk": "~1.16.17",
|
|
60
|
+
"@xyo-network/xl1-validation": "~1.16.17",
|
|
61
|
+
"@xyo-network/xl1-wrappers": "~1.16.17",
|
|
59
62
|
"async-mutex": "~0.5.0",
|
|
60
63
|
"ethers": "6.15.0",
|
|
61
64
|
"lru-cache": "~11.2.2"
|
|
@@ -68,7 +71,7 @@
|
|
|
68
71
|
"@xylabs/vitest-extended": "~5.0.34",
|
|
69
72
|
"@xyo-network/account": "~5.1.23",
|
|
70
73
|
"@xyo-network/account-model": "~5.1.23",
|
|
71
|
-
"@xyo-network/chain-validation": "~1.16.
|
|
74
|
+
"@xyo-network/chain-validation": "~1.16.17",
|
|
72
75
|
"@xyo-network/wallet": "~5.1.23",
|
|
73
76
|
"eslint": "^9.39.1",
|
|
74
77
|
"tslib": "~2.8.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BaseBlockProducerService.spec.d.ts","sourceRoot":"","sources":["../../../../src/BlockProducer/spec/BaseBlockProducerService.spec.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generateTransactionTransfer.spec.d.ts","sourceRoot":"","sources":["../../../../src/BlockProducer/spec/generateTransactionTransfer.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MemoryBlockRewardService.spec.d.ts","sourceRoot":"","sources":["../../../../src/BlockReward/spec/MemoryBlockRewardService.spec.ts"],"names":[],"mappings":"AAAA,OAAO,yBAAyB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"BasePendingTransactions.spec.d.ts","sourceRoot":"","sources":["../../../../src/PendingTransactions/spec/BasePendingTransactions.spec.ts"],"names":[],"mappings":""}
|
package/dist/neutral/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bundledPayloadToHydratedTransaction.spec.d.ts","sourceRoot":"","sources":["../../../../src/PendingTransactions/spec/bundledPayloadToHydratedTransaction.spec.ts"],"names":[],"mappings":""}
|
package/dist/neutral/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hydratedTransactionToPayloadBundle.spec.d.ts","sourceRoot":"","sources":["../../../../src/PendingTransactions/spec/hydratedTransactionToPayloadBundle.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import '@xylabs/vitest-extended'
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
Address, CreatableName, Hash, Promisable,
|
|
5
|
-
} from '@xylabs/sdk-js'
|
|
6
|
-
import {
|
|
7
|
-
asAddress, assertEx,
|
|
8
|
-
delay,
|
|
9
|
-
filterAs,
|
|
10
|
-
hexToBigInt, ZERO_HASH,
|
|
11
|
-
} from '@xylabs/sdk-js'
|
|
12
|
-
import { Account } from '@xyo-network/account'
|
|
13
|
-
import type { AccountInstance } from '@xyo-network/account-model'
|
|
14
|
-
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
15
|
-
import type { ArchivistInstance } from '@xyo-network/archivist-model'
|
|
16
|
-
import { buildRandomChain, buildRandomTransaction } from '@xyo-network/chain-protocol'
|
|
17
|
-
import { validateHydratedBlockState } from '@xyo-network/chain-validation'
|
|
18
|
-
import {
|
|
19
|
-
type PayloadBundle, PayloadBundleSchema, type WithStorageMeta,
|
|
20
|
-
} from '@xyo-network/payload-model'
|
|
21
|
-
import { HDWallet } from '@xyo-network/wallet'
|
|
22
|
-
import type {
|
|
23
|
-
BlockBoundWitness, ChainId,
|
|
24
|
-
HydratedBlock,
|
|
25
|
-
TimeDomain, TimePayload,
|
|
26
|
-
} from '@xyo-network/xl1-protocol'
|
|
27
|
-
import {
|
|
28
|
-
asBlockBoundWitness,
|
|
29
|
-
asChainStakeIntent,
|
|
30
|
-
asTransactionBoundWitness,
|
|
31
|
-
asTransfer,
|
|
32
|
-
TimeSchema,
|
|
33
|
-
XYO_ZERO_ADDRESS,
|
|
34
|
-
} from '@xyo-network/xl1-protocol'
|
|
35
|
-
import type {
|
|
36
|
-
BlockRewardService, Config, ElectionService, HydratedBlockStateValidationFunction, StakeIntentService,
|
|
37
|
-
TimeSyncViewer,
|
|
38
|
-
} from '@xyo-network/xl1-protocol-sdk'
|
|
39
|
-
import {
|
|
40
|
-
flattenHydratedBlock, flattenHydratedTransaction, getDefaultConfig,
|
|
41
|
-
HydratedBlockStateValidationError,
|
|
42
|
-
} from '@xyo-network/xl1-protocol-sdk'
|
|
43
|
-
import {
|
|
44
|
-
beforeAll, beforeEach, describe, expect, it,
|
|
45
|
-
} from 'vitest'
|
|
46
|
-
import { mock } from 'vitest-mock-extended'
|
|
47
|
-
|
|
48
|
-
import { accountBalancesServiceFromArchivist } from '../../AccountBalance/index.ts'
|
|
49
|
-
import { MemoryBlockRewardService } from '../../BlockReward/index.ts'
|
|
50
|
-
import type { BasePendingTransactionsServiceParams } from '../../PendingTransactions/index.ts'
|
|
51
|
-
import { BasePendingTransactionsService } from '../../PendingTransactions/index.ts'
|
|
52
|
-
import type { BaseBlockProducerServiceParams } from '../BaseBlockProducerService.ts'
|
|
53
|
-
import { BaseBlockProducerService } from '../BaseBlockProducerService.ts'
|
|
54
|
-
|
|
55
|
-
describe('XyoBlockProducer', () => {
|
|
56
|
-
const leaderCount = 3
|
|
57
|
-
let account: AccountInstance
|
|
58
|
-
let blockProducer: BaseBlockProducerService
|
|
59
|
-
let chainArchivist: ArchivistInstance
|
|
60
|
-
let electionService: ReturnType<typeof mock<ElectionService>>
|
|
61
|
-
let pendingBundledTransactionsArchivist: ArchivistInstance
|
|
62
|
-
let pendingTransactionsService: BasePendingTransactionsService
|
|
63
|
-
let rejectedTransactionsArchivist: ArchivistInstance
|
|
64
|
-
let rewardAddress = '1111111111111111111111111111111111111111' as Address
|
|
65
|
-
let rewardService: BlockRewardService
|
|
66
|
-
let stakeIntentService: ReturnType<typeof mock<StakeIntentService>>
|
|
67
|
-
let time: TimeSyncViewer
|
|
68
|
-
let transactionAccount: AccountInstance
|
|
69
|
-
const validPendingTransactions = Math.ceil(BaseBlockProducerService.DefaultBlockSize)
|
|
70
|
-
let config: Config
|
|
71
|
-
|
|
72
|
-
let currentBlock: WithStorageMeta<BlockBoundWitness>
|
|
73
|
-
|
|
74
|
-
const chainId = assertEx(asAddress('Be17531fec6fEc55f3EAc3f1c187c87e4C47F81E'))
|
|
75
|
-
|
|
76
|
-
beforeAll(async () => {
|
|
77
|
-
account = await Account.random()
|
|
78
|
-
transactionAccount = await HDWallet.fromPhrase('room maximum palace fragile man pyramid school indoor base business want bronze assume marble report')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
beforeEach(async () => {
|
|
82
|
-
config = getDefaultConfig()
|
|
83
|
-
config.producer.disableIntentRedeclaration = false
|
|
84
|
-
chainArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
85
|
-
rejectedTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
86
|
-
electionService = mock<ElectionService>()
|
|
87
|
-
pendingBundledTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
88
|
-
const BasePendingTransactionsServiceParams: BasePendingTransactionsServiceParams = {
|
|
89
|
-
name: 'TestBasePendingTransactionsServiceParams' as CreatableName,
|
|
90
|
-
chainArchivist: chainArchivist,
|
|
91
|
-
chainId,
|
|
92
|
-
config,
|
|
93
|
-
pendingBundledTransactionsArchivist,
|
|
94
|
-
rejectedTransactionsArchivist,
|
|
95
|
-
}
|
|
96
|
-
pendingTransactionsService = await BasePendingTransactionsService.create(BasePendingTransactionsServiceParams)
|
|
97
|
-
await addPendingTransactions(validPendingTransactions)
|
|
98
|
-
|
|
99
|
-
stakeIntentService = mock<StakeIntentService>()
|
|
100
|
-
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
|
|
101
|
-
time = {
|
|
102
|
-
currentTimeAndHash(domain: TimeDomain): Promisable<[number, Hash | null]> {
|
|
103
|
-
switch (domain) {
|
|
104
|
-
case 'epoch': {
|
|
105
|
-
return [Date.now(), null]
|
|
106
|
-
}
|
|
107
|
-
case 'xl1': {
|
|
108
|
-
return [1, '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' as Hash]
|
|
109
|
-
}
|
|
110
|
-
case 'ethereum': {
|
|
111
|
-
return [1, '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' as Hash]
|
|
112
|
-
}
|
|
113
|
-
// No default
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
currentTime(domain: TimeDomain): Promisable<[string, number]> {
|
|
117
|
-
switch (domain) {
|
|
118
|
-
case 'epoch': {
|
|
119
|
-
return ['epoch', Date.now()]
|
|
120
|
-
}
|
|
121
|
-
case 'xl1': {
|
|
122
|
-
return ['xl1', 1]
|
|
123
|
-
}
|
|
124
|
-
case 'ethereum': {
|
|
125
|
-
return ['ethereum', 1]
|
|
126
|
-
}
|
|
127
|
-
// No default
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
currentTimePayload(): Promisable<TimePayload> {
|
|
131
|
-
return {
|
|
132
|
-
schema: TimeSchema, epoch: Date.now(), xl1: 1,
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
/** Convert time between different domains */
|
|
136
|
-
convertTime(fromDomain: TimeDomain, toDomain: TimeDomain, from: number): Promisable<number> {
|
|
137
|
-
if (fromDomain === toDomain) {
|
|
138
|
-
return from
|
|
139
|
-
}
|
|
140
|
-
if (fromDomain === 'epoch' && toDomain === 'xl1') {
|
|
141
|
-
return 1
|
|
142
|
-
}
|
|
143
|
-
if (fromDomain === 'xl1' && toDomain === 'epoch') {
|
|
144
|
-
return Date.now()
|
|
145
|
-
}
|
|
146
|
-
return from
|
|
147
|
-
},
|
|
148
|
-
}
|
|
149
|
-
rewardService = await MemoryBlockRewardService.create()
|
|
150
|
-
const balanceService = await accountBalancesServiceFromArchivist(chainId, chainArchivist)
|
|
151
|
-
const params: BaseBlockProducerServiceParams = {
|
|
152
|
-
name: 'TestXyoBlockProducerParams' as CreatableName,
|
|
153
|
-
account,
|
|
154
|
-
balanceService,
|
|
155
|
-
chainArchivist,
|
|
156
|
-
chainId,
|
|
157
|
-
config,
|
|
158
|
-
electionService,
|
|
159
|
-
pendingTransactionsService,
|
|
160
|
-
pendingBundledTransactionsArchivist,
|
|
161
|
-
rejectedTransactionsArchivist,
|
|
162
|
-
rewardAddress,
|
|
163
|
-
rewardService,
|
|
164
|
-
stakeIntentService,
|
|
165
|
-
time,
|
|
166
|
-
validateHydratedBlockState,
|
|
167
|
-
}
|
|
168
|
-
blockProducer = await BaseBlockProducerService.create(params)
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
const addPendingTransactions = async (count: number = validPendingTransactions) => {
|
|
172
|
-
for (let i = 0; i < count; i++) {
|
|
173
|
-
const transaction = await buildRandomTransaction(chainId, [], transactionAccount)
|
|
174
|
-
const bundledPayload: PayloadBundle = {
|
|
175
|
-
schema: PayloadBundleSchema, payloads: flattenHydratedTransaction(transaction), root: transaction[0]._hash,
|
|
176
|
-
}
|
|
177
|
-
await pendingBundledTransactionsArchivist.insert([bundledPayload])
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
describe('next', () => {
|
|
182
|
-
describe('block production', () => {
|
|
183
|
-
describe('when not current block leader', () => {
|
|
184
|
-
beforeEach(async () => {
|
|
185
|
-
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
186
|
-
currentBlock = hydratedBlock[0]
|
|
187
|
-
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
188
|
-
const leaders = await Promise.all(Array.from({ length: leaderCount }, async () => (await Account.random()).address))
|
|
189
|
-
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue(leaders)
|
|
190
|
-
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
|
|
191
|
-
})
|
|
192
|
-
it('should return undefined', async () => {
|
|
193
|
-
// Arrange
|
|
194
|
-
|
|
195
|
-
// Act
|
|
196
|
-
const result = await blockProducer.next(currentBlock)
|
|
197
|
-
|
|
198
|
-
// Assert
|
|
199
|
-
expect(result).toBeUndefined()
|
|
200
|
-
})
|
|
201
|
-
})
|
|
202
|
-
describe('when current block leader', () => {
|
|
203
|
-
beforeEach(async () => {
|
|
204
|
-
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
205
|
-
currentBlock = hydratedBlock[0]
|
|
206
|
-
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
207
|
-
await delay(1000)
|
|
208
|
-
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
209
|
-
})
|
|
210
|
-
it('should return a valid block', async () => {
|
|
211
|
-
// Arrange
|
|
212
|
-
|
|
213
|
-
// Act
|
|
214
|
-
const result = await blockProducer.next(currentBlock)
|
|
215
|
-
|
|
216
|
-
// Assert
|
|
217
|
-
expect(result).toBeDefined()
|
|
218
|
-
expect(result).toBeArrayOfSize(2)
|
|
219
|
-
const [block, transactionsAndData] = result ?? []
|
|
220
|
-
expect(block).toBeDefined()
|
|
221
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
222
|
-
expect(transactionsAndData).toBeDefined()
|
|
223
|
-
expect(transactionsAndData).toBeArray()
|
|
224
|
-
const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
|
|
225
|
-
expect(transactions).toBeArrayOfSize(validPendingTransactions)
|
|
226
|
-
})
|
|
227
|
-
it('should remove invalid transactions', async () => {
|
|
228
|
-
// Arrange
|
|
229
|
-
let rejectBlock = true
|
|
230
|
-
const validateHydratedBlockState: HydratedBlockStateValidationFunction = async (
|
|
231
|
-
hydratedBlock: HydratedBlock,
|
|
232
|
-
chainId: ChainId,
|
|
233
|
-
) => {
|
|
234
|
-
return rejectBlock ? [await Promise.resolve(new HydratedBlockStateValidationError(ZERO_HASH, chainId, hydratedBlock, 'Invalid block'))] : []
|
|
235
|
-
}
|
|
236
|
-
const balanceService = await accountBalancesServiceFromArchivist(chainId, chainArchivist)
|
|
237
|
-
const params: BaseBlockProducerServiceParams = {
|
|
238
|
-
name: 'TestXyoBlockProducerParams' as CreatableName,
|
|
239
|
-
account,
|
|
240
|
-
balanceService,
|
|
241
|
-
chainArchivist,
|
|
242
|
-
chainId,
|
|
243
|
-
config,
|
|
244
|
-
electionService,
|
|
245
|
-
pendingBundledTransactionsArchivist,
|
|
246
|
-
pendingTransactionsService,
|
|
247
|
-
rejectedTransactionsArchivist,
|
|
248
|
-
rewardAddress,
|
|
249
|
-
rewardService,
|
|
250
|
-
stakeIntentService,
|
|
251
|
-
time,
|
|
252
|
-
validateHydratedBlockState,
|
|
253
|
-
}
|
|
254
|
-
blockProducer = await BaseBlockProducerService.create(params)
|
|
255
|
-
|
|
256
|
-
// Act
|
|
257
|
-
// Force producer to reject first block
|
|
258
|
-
rejectBlock = true
|
|
259
|
-
const invalidTransactionCount = Math.floor(validPendingTransactions / 2)
|
|
260
|
-
await addPendingTransactions(invalidTransactionCount)
|
|
261
|
-
|
|
262
|
-
// Ensure bad block is not produced
|
|
263
|
-
const firstResult = await blockProducer.next(currentBlock)
|
|
264
|
-
expect(firstResult).toBeUndefined()
|
|
265
|
-
// Do not reject second block
|
|
266
|
-
rejectBlock = false
|
|
267
|
-
// Add more pending transactions
|
|
268
|
-
const newTransactionCount = Math.floor(validPendingTransactions / 2)
|
|
269
|
-
await addPendingTransactions(newTransactionCount)
|
|
270
|
-
// Ensure producer recovers from bad block
|
|
271
|
-
const result = await blockProducer.next(currentBlock)
|
|
272
|
-
|
|
273
|
-
// Assert
|
|
274
|
-
expect(result).toBeDefined()
|
|
275
|
-
expect(result).toBeArrayOfSize(2)
|
|
276
|
-
const [block, transactionsAndData] = result ?? []
|
|
277
|
-
expect(block).toBeDefined()
|
|
278
|
-
if (block) {
|
|
279
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
280
|
-
expect(transactionsAndData).toBeDefined()
|
|
281
|
-
expect(transactionsAndData).toBeArray()
|
|
282
|
-
const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
|
|
283
|
-
expect(transactions).toBeArrayOfSize(newTransactionCount)
|
|
284
|
-
const transfers = filterAs(assertEx(transactionsAndData), asTransfer)
|
|
285
|
-
const blockRewardTransfer = transfers.find(transfer => transfer.from === XYO_ZERO_ADDRESS)
|
|
286
|
-
expect(blockRewardTransfer).toBeDefined()
|
|
287
|
-
let totalTransfer = 0n
|
|
288
|
-
for (const value of Object.values(blockRewardTransfer?.transfers ?? {})) {
|
|
289
|
-
const bigIntValue = hexToBigInt(value)
|
|
290
|
-
totalTransfer += bigIntValue
|
|
291
|
-
}
|
|
292
|
-
expect(totalTransfer).toEqual(3_000_000_000_000_000_000_000n)
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
describe('producer re-declare intent', () => {
|
|
298
|
-
describe('when within re-declare intent window', () => {
|
|
299
|
-
beforeEach(async () => {
|
|
300
|
-
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
301
|
-
currentBlock = hydratedBlock[0]
|
|
302
|
-
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
303
|
-
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
304
|
-
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 10]])
|
|
305
|
-
})
|
|
306
|
-
it('should re-declare intent if configured for re-declaration', async () => {
|
|
307
|
-
// Arrange
|
|
308
|
-
config.producer.disableIntentRedeclaration = false
|
|
309
|
-
|
|
310
|
-
// Act
|
|
311
|
-
const result = await blockProducer.next(currentBlock)
|
|
312
|
-
|
|
313
|
-
// Assert
|
|
314
|
-
expect(result).toBeDefined()
|
|
315
|
-
expect(result).toBeArrayOfSize(2)
|
|
316
|
-
const [block, payloads] = result ?? []
|
|
317
|
-
expect(block).toBeDefined()
|
|
318
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
319
|
-
expect(payloads).toBeArray()
|
|
320
|
-
const allData = flattenHydratedBlock(assertEx(result))
|
|
321
|
-
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
322
|
-
expect(declaration).toBeDefined()
|
|
323
|
-
expect(declaration?.from).toEqual(blockProducer.address)
|
|
324
|
-
})
|
|
325
|
-
it('should not re-declare intent if not configured for re-declaration', async () => {
|
|
326
|
-
// Arrange
|
|
327
|
-
config.producer.disableIntentRedeclaration = true
|
|
328
|
-
|
|
329
|
-
// Act
|
|
330
|
-
const result = await blockProducer.next(currentBlock)
|
|
331
|
-
|
|
332
|
-
// Assert
|
|
333
|
-
expect(result).toBeDefined()
|
|
334
|
-
expect(result).toBeArrayOfSize(2)
|
|
335
|
-
const [block, payloads] = result ?? []
|
|
336
|
-
expect(block).toBeDefined()
|
|
337
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
338
|
-
expect(payloads).toBeArray()
|
|
339
|
-
const allData = flattenHydratedBlock(assertEx(result))
|
|
340
|
-
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
341
|
-
expect(declaration).toBeUndefined()
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
describe('when not within re-declare intent window', () => {
|
|
345
|
-
beforeEach(async () => {
|
|
346
|
-
const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
|
|
347
|
-
currentBlock = hydratedBlock[0]
|
|
348
|
-
await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
|
|
349
|
-
electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
|
|
350
|
-
stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 1_000_000_000]])
|
|
351
|
-
})
|
|
352
|
-
it('should not re-declare intent if configured for re-declaration', async () => {
|
|
353
|
-
// Arrange
|
|
354
|
-
config.producer.disableIntentRedeclaration = false
|
|
355
|
-
|
|
356
|
-
// Act
|
|
357
|
-
const result = await blockProducer.next(currentBlock)
|
|
358
|
-
|
|
359
|
-
// Assert
|
|
360
|
-
expect(result).toBeDefined()
|
|
361
|
-
expect(result).toBeArrayOfSize(2)
|
|
362
|
-
const [block, payloads] = result ?? []
|
|
363
|
-
expect(block).toBeDefined()
|
|
364
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
365
|
-
expect(payloads).toBeArray()
|
|
366
|
-
const allData = flattenHydratedBlock(assertEx(result))
|
|
367
|
-
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
368
|
-
expect(declaration).toBeUndefined()
|
|
369
|
-
})
|
|
370
|
-
it('should not re-declare intent if not configured for re-declaration', async () => {
|
|
371
|
-
// Arrange
|
|
372
|
-
config.producer.disableIntentRedeclaration = true
|
|
373
|
-
|
|
374
|
-
// Act
|
|
375
|
-
const result = await blockProducer.next(currentBlock)
|
|
376
|
-
|
|
377
|
-
// Assert
|
|
378
|
-
expect(result).toBeDefined()
|
|
379
|
-
expect(result).toBeArrayOfSize(2)
|
|
380
|
-
const [block, payloads] = result ?? []
|
|
381
|
-
expect(block).toBeDefined()
|
|
382
|
-
expect(asBlockBoundWitness(block)).toBeDefined()
|
|
383
|
-
expect(payloads).toBeArray()
|
|
384
|
-
const allData = flattenHydratedBlock(assertEx(result))
|
|
385
|
-
const declaration = filterAs(allData, asChainStakeIntent).at(0)
|
|
386
|
-
expect(declaration).toBeUndefined()
|
|
387
|
-
})
|
|
388
|
-
})
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Address, Hash, Hex,
|
|
3
|
-
} from '@xylabs/sdk-js'
|
|
4
|
-
import {
|
|
5
|
-
asAddress, assertEx,
|
|
6
|
-
hexToBigInt, isDefined,
|
|
7
|
-
toHex,
|
|
8
|
-
} from '@xylabs/sdk-js'
|
|
9
|
-
import type { Sequence, WithStorageMeta } from '@xyo-network/payload-model'
|
|
10
|
-
import { XYO_ZERO_ADDRESS } from '@xyo-network/xl1-protocol'
|
|
11
|
-
import {
|
|
12
|
-
defaultTransactionFees, type SignedHydratedTransactionWithStorageMeta, type Transfer,
|
|
13
|
-
} from '@xyo-network/xl1-protocol'
|
|
14
|
-
import {
|
|
15
|
-
describe, expect, it,
|
|
16
|
-
} from 'vitest'
|
|
17
|
-
|
|
18
|
-
import { generateTransactionFeeTransfers } from '../generateTransactionFeeTransfers.ts'
|
|
19
|
-
|
|
20
|
-
describe('generateTransactionTransfers', () => {
|
|
21
|
-
const testCases: SignedHydratedTransactionWithStorageMeta[][] = [
|
|
22
|
-
[
|
|
23
|
-
[
|
|
24
|
-
{
|
|
25
|
-
schema: 'network.xyo.boundwitness',
|
|
26
|
-
addresses: ['39d22bed37f77bee0c056f648bde6efa489981c3' as Address],
|
|
27
|
-
payload_hashes: ['65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8' as Hash],
|
|
28
|
-
payload_schemas: ['network.xyo.transfer'],
|
|
29
|
-
previous_hashes: [null],
|
|
30
|
-
$signatures: [
|
|
31
|
-
'0605878a92045bb752cc12066b585589b513cee652bf4bc36c19289495ccd3d3751a1202b5bfb334325c4c6a59a4a7dad7468739a8b4ffe07b6091317b16dc09' as Hex,
|
|
32
|
-
],
|
|
33
|
-
nbf: 2835,
|
|
34
|
-
exp: 3835,
|
|
35
|
-
fees: {
|
|
36
|
-
base: toHex(defaultTransactionFees.base),
|
|
37
|
-
gasLimit: toHex(defaultTransactionFees.gasLimit),
|
|
38
|
-
gasPrice: toHex(defaultTransactionFees.gasPrice),
|
|
39
|
-
priority: '00' as Hex,
|
|
40
|
-
},
|
|
41
|
-
chain: 'a82920051db4fcbb804463440dd45e03f72442fd' as Hex,
|
|
42
|
-
script: ['elevate|65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8'],
|
|
43
|
-
from: '39d22bed37f77bee0c056f648bde6efa489981c3' as Address,
|
|
44
|
-
_dataHash: '47be77303edd6073f82e9fff119107f1e173a08e7285e8e5f6e0c979d43e5b7d' as Hash,
|
|
45
|
-
_hash: '1419062ddb266ff58515c4ffbc435ec482b6129d9e0c3c707fb7610c67215281' as Hash,
|
|
46
|
-
_sequence: '00000196f46014770000000167215281' as Sequence,
|
|
47
|
-
},
|
|
48
|
-
[
|
|
49
|
-
{
|
|
50
|
-
schema: 'network.xyo.transfer',
|
|
51
|
-
epoch: 1_747_856_659_420,
|
|
52
|
-
from: '39d22bed37f77bee0c056f648bde6efa489981c3' as Address,
|
|
53
|
-
transfers: { db5df9c99e661500c95f2c933023ba693e5c1ae9: '056bc75e2d63100000' as Hex },
|
|
54
|
-
_dataHash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8' as Hash,
|
|
55
|
-
_hash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8' as Hash,
|
|
56
|
-
_sequence: '00000196f4601477000000002aef69c8' as Sequence,
|
|
57
|
-
} as WithStorageMeta<Transfer>,
|
|
58
|
-
],
|
|
59
|
-
],
|
|
60
|
-
],
|
|
61
|
-
]
|
|
62
|
-
it.each(testCases)('should generate correct Transfer payloads from hydrated transactions', async (...transactions) => {
|
|
63
|
-
const producerAddress = assertEx(asAddress('3e86dac7546202156c4620d3cf36fb0ac30404a3'))
|
|
64
|
-
|
|
65
|
-
// Patch the transactionRequiredGas function to return our test gas amount
|
|
66
|
-
|
|
67
|
-
const result = await generateTransactionFeeTransfers(producerAddress, transactions)
|
|
68
|
-
|
|
69
|
-
const burnValueHex = result.at(0)?.transfers[XYO_ZERO_ADDRESS]
|
|
70
|
-
expect(burnValueHex).toBeDefined()
|
|
71
|
-
const burnValue = isDefined(burnValueHex) ? hexToBigInt(burnValueHex) : 0n
|
|
72
|
-
expect(burnValue).toBeGreaterThan(0n)
|
|
73
|
-
expect(burnValue).toEqual(1_000_000_000_000n)
|
|
74
|
-
})
|
|
75
|
-
})
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import '@xylabs/vitest-extended'
|
|
2
|
-
|
|
3
|
-
import { toFixedPoint } from '@xylabs/sdk-js'
|
|
4
|
-
import {
|
|
5
|
-
beforeAll, describe, expect, it,
|
|
6
|
-
} from 'vitest'
|
|
7
|
-
|
|
8
|
-
import { MemoryBlockRewardService } from '../index.ts'
|
|
9
|
-
|
|
10
|
-
describe('MemoryBlockRewardService', () => {
|
|
11
|
-
let sut: MemoryBlockRewardService
|
|
12
|
-
// const genesisReward = toFixedPoint(18_000_000_000n)
|
|
13
|
-
// const initialReward = toFixedPoint(500n)
|
|
14
|
-
// const minRewardPerBlock = toFixedPoint(10n)
|
|
15
|
-
const genesisReward = 18_000_000_000_000_000_000_000_000_000n
|
|
16
|
-
const initialReward = 500_000_000_000_000_000_000n
|
|
17
|
-
const minRewardPerBlock = 10_000_000_000_000_000_000n
|
|
18
|
-
const stepFactorDenominator = 100n
|
|
19
|
-
const stepFactorNumerator = 95n
|
|
20
|
-
const stepSize = 1_000_000n
|
|
21
|
-
beforeAll(async () => {
|
|
22
|
-
sut = await MemoryBlockRewardService.create({
|
|
23
|
-
stepFactorNumerator,
|
|
24
|
-
stepFactorDenominator,
|
|
25
|
-
stepSize,
|
|
26
|
-
initialStepReward: initialReward,
|
|
27
|
-
minRewardPerBlock,
|
|
28
|
-
creatorReward: genesisReward,
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
describe('rewards', () => {
|
|
33
|
-
it('for block 0', async () => {
|
|
34
|
-
const reward = await sut.getRewardForBlock(0n)
|
|
35
|
-
expect(reward).toEqual(18_000_000_000_000_000_000_000_000_000n)
|
|
36
|
-
expect(reward).toEqual(toFixedPoint(18_000_000_000n))
|
|
37
|
-
})
|
|
38
|
-
it('for block 1', async () => {
|
|
39
|
-
const reward = await sut.getRewardForBlock(1n)
|
|
40
|
-
expect(reward).toEqual(500_000_000_000_000_000_000n)
|
|
41
|
-
expect(reward).toEqual(toFixedPoint(500n))
|
|
42
|
-
})
|
|
43
|
-
it('for block 2', async () => {
|
|
44
|
-
const reward = await sut.getRewardForBlock(2n)
|
|
45
|
-
expect(reward).toEqual(500_000_000_000_000_000_000n)
|
|
46
|
-
expect(reward).toEqual(toFixedPoint(500n))
|
|
47
|
-
})
|
|
48
|
-
it('for block 3', async () => {
|
|
49
|
-
const reward = await sut.getRewardForBlock(3n)
|
|
50
|
-
expect(reward).toEqual(500_000_000_000_000_000_000n)
|
|
51
|
-
expect(reward).toEqual(toFixedPoint(500n))
|
|
52
|
-
})
|
|
53
|
-
it('for block 4', async () => {
|
|
54
|
-
const reward = await sut.getRewardForBlock(4n)
|
|
55
|
-
expect(reward).toEqual(500_000_000_000_000_000_000n)
|
|
56
|
-
expect(reward).toEqual(toFixedPoint(500n))
|
|
57
|
-
})
|
|
58
|
-
it('for block 1000000', async () => {
|
|
59
|
-
const reward = await sut.getRewardForBlock(1_000_000n)
|
|
60
|
-
expect(reward).toEqual(475_000_000_000_000_000_000n)
|
|
61
|
-
expect(reward).toEqual(toFixedPoint(475n))
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
})
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import type { CreatableName } from '@xylabs/sdk-js'
|
|
2
|
-
import {
|
|
3
|
-
asAddress, assertEx, delay,
|
|
4
|
-
} from '@xylabs/sdk-js'
|
|
5
|
-
import { Account } from '@xyo-network/account'
|
|
6
|
-
import type { AccountInstance } from '@xyo-network/account-model'
|
|
7
|
-
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
8
|
-
import type { ArchivistInstance } from '@xyo-network/archivist-model'
|
|
9
|
-
import {
|
|
10
|
-
buildRandomChain, buildRandomTransaction,
|
|
11
|
-
findMostRecentBlock, TestChainId,
|
|
12
|
-
} from '@xyo-network/chain-protocol'
|
|
13
|
-
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
14
|
-
import type { WithStorageMeta } from '@xyo-network/payload-model'
|
|
15
|
-
import type {
|
|
16
|
-
BlockBoundWitness, HashPayload,
|
|
17
|
-
HydratedBlock,
|
|
18
|
-
SignedHydratedTransactionWithStorageMeta,
|
|
19
|
-
} from '@xyo-network/xl1-protocol'
|
|
20
|
-
import { HashSchema } from '@xyo-network/xl1-protocol'
|
|
21
|
-
import {
|
|
22
|
-
buildTransaction, flattenHydratedBlock, flattenHydratedTransactions,
|
|
23
|
-
getDefaultConfig,
|
|
24
|
-
} from '@xyo-network/xl1-protocol-sdk'
|
|
25
|
-
import {
|
|
26
|
-
beforeAll,
|
|
27
|
-
beforeEach, describe, expect, it,
|
|
28
|
-
} from 'vitest'
|
|
29
|
-
|
|
30
|
-
import type { BasePendingTransactionsServiceParams } from '../BasePendingTransactions.ts'
|
|
31
|
-
import { BasePendingTransactionsService } from '../BasePendingTransactions.ts'
|
|
32
|
-
import { hydratedTransactionToPayloadBundle } from '../hydratedTransactionToPayloadBundle.ts'
|
|
33
|
-
|
|
34
|
-
describe('BasePendingTransactionsService', () => {
|
|
35
|
-
let sut: BasePendingTransactionsService
|
|
36
|
-
let chainArchivist: ArchivistInstance
|
|
37
|
-
let pendingBundledTransactionsArchivist: ArchivistInstance
|
|
38
|
-
let rejectedTransactionsArchivist: ArchivistInstance
|
|
39
|
-
let producer: AccountInstance
|
|
40
|
-
let chain: HydratedBlock[]
|
|
41
|
-
let head: WithStorageMeta<BlockBoundWitness>
|
|
42
|
-
const chainId = TestChainId
|
|
43
|
-
const config = getDefaultConfig()
|
|
44
|
-
|
|
45
|
-
beforeAll(async () => {
|
|
46
|
-
producer = await Account.random()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
beforeEach(async () => {
|
|
50
|
-
chain = await buildRandomChain(producer, 10)
|
|
51
|
-
chainArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
52
|
-
await chainArchivist.insert(chain.flatMap(block => flattenHydratedBlock(block)))
|
|
53
|
-
pendingBundledTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
54
|
-
rejectedTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
|
|
55
|
-
head = assertEx(await findMostRecentBlock(chainArchivist))
|
|
56
|
-
const params: BasePendingTransactionsServiceParams = {
|
|
57
|
-
name: 'TestBasePendingTransactionsServiceParams' as CreatableName,
|
|
58
|
-
chainArchivist,
|
|
59
|
-
chainId,
|
|
60
|
-
config,
|
|
61
|
-
pendingBundledTransactionsArchivist,
|
|
62
|
-
rejectedTransactionsArchivist,
|
|
63
|
-
}
|
|
64
|
-
sut = await BasePendingTransactionsService.create(params)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe('with pending transactions', () => {
|
|
68
|
-
let newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
69
|
-
beforeEach(async () => {
|
|
70
|
-
for (let index = 0; index < 5; index++) {
|
|
71
|
-
const tx = await buildRandomTransaction(TestChainId)
|
|
72
|
-
newTransactions.push(tx)
|
|
73
|
-
await delay(2)
|
|
74
|
-
}
|
|
75
|
-
const payloads = newTransactions.map(hydratedTransactionToPayloadBundle)
|
|
76
|
-
await pendingBundledTransactionsArchivist.insert(payloads)
|
|
77
|
-
})
|
|
78
|
-
it('should return the requested amount of hydrated transactions', async () => {
|
|
79
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
80
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
81
|
-
})
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
describe('with no pending transactions', () => {
|
|
85
|
-
it('should return an empty array', async () => {
|
|
86
|
-
const result = await sut.getPendingTransactions(head._dataHash, 10)
|
|
87
|
-
expect(result).toEqual([])
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('with pending and finalized transactions', () => {
|
|
92
|
-
let finalizedTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
93
|
-
let newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
94
|
-
beforeEach(async () => {
|
|
95
|
-
for (let index = 0; index < 5; index++) {
|
|
96
|
-
const tx = await buildRandomTransaction(TestChainId)
|
|
97
|
-
finalizedTransactions.push(tx)
|
|
98
|
-
await delay(2)
|
|
99
|
-
}
|
|
100
|
-
await pendingBundledTransactionsArchivist.insert(finalizedTransactions.map(hydratedTransactionToPayloadBundle))
|
|
101
|
-
await chainArchivist.insert(flattenHydratedTransactions(finalizedTransactions))
|
|
102
|
-
for (let index = 0; index < 5; index++) {
|
|
103
|
-
const tx = await buildRandomTransaction(TestChainId)
|
|
104
|
-
newTransactions.push(tx)
|
|
105
|
-
await delay(2)
|
|
106
|
-
}
|
|
107
|
-
const pending = newTransactions.map(hydratedTransactionToPayloadBundle)
|
|
108
|
-
await pendingBundledTransactionsArchivist.insert(pending)
|
|
109
|
-
})
|
|
110
|
-
it('should remove finalized transactions from pending', async () => {
|
|
111
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length + finalizedTransactions.length)
|
|
112
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
describe('with repeated transactions', () => {
|
|
117
|
-
const hashPayload = new PayloadBuilder<HashPayload>({ schema: HashSchema })
|
|
118
|
-
.fields({ hash: '3fc6fb2edd79e0d3f27c668a706cb48473d5f9e432e5462ae647702221a2c97a' })
|
|
119
|
-
.build()
|
|
120
|
-
beforeEach(async () => {})
|
|
121
|
-
it('when unique should allow transactions', async () => {
|
|
122
|
-
let newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
123
|
-
// NOTE: Cast to Account to allow loadPreviousHash to be called
|
|
124
|
-
const signer = await Account.random()
|
|
125
|
-
const transaction1 = await buildTransaction(TestChainId, [hashPayload], [], signer, 0, 100)
|
|
126
|
-
const transaction2 = await buildTransaction(TestChainId, [hashPayload], [], signer, 0, 100)
|
|
127
|
-
expect(transaction1[0]._hash).not.toEqual(transaction2[0]._hash)
|
|
128
|
-
for (const tx of [transaction1, transaction2]) {
|
|
129
|
-
const bundled = hydratedTransactionToPayloadBundle(tx)
|
|
130
|
-
await pendingBundledTransactionsArchivist.insert([bundled])
|
|
131
|
-
newTransactions.push(tx)
|
|
132
|
-
}
|
|
133
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
134
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
135
|
-
})
|
|
136
|
-
it('when non-unique should filter duplicate transactions', async () => {
|
|
137
|
-
let newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
138
|
-
const previousHash = 'bb27dd470ccf91665d573a16b4f652a47b8a24686d1b44c189c1fa826cd3fef5'
|
|
139
|
-
// NOTE: Cast to Account to allow loadPreviousHash to be called
|
|
140
|
-
const signer = await Account.random() as Account
|
|
141
|
-
await signer.loadPreviousHash(previousHash)
|
|
142
|
-
const transaction1 = await buildTransaction(TestChainId, [hashPayload], [], signer, 0, 100)
|
|
143
|
-
await signer.loadPreviousHash(previousHash)
|
|
144
|
-
const transaction2 = await buildTransaction(TestChainId, [hashPayload], [], signer, 0, 100)
|
|
145
|
-
expect(transaction1[0]._hash).toEqual(transaction2[0]._hash)
|
|
146
|
-
for (const tx of [transaction1, transaction2]) {
|
|
147
|
-
const bundled = hydratedTransactionToPayloadBundle(tx)
|
|
148
|
-
await pendingBundledTransactionsArchivist.insert([bundled])
|
|
149
|
-
newTransactions.push(tx)
|
|
150
|
-
}
|
|
151
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
152
|
-
expect(result.length).toEqual(newTransactions.length / 2)
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
describe('with expiring transactions', () => {
|
|
157
|
-
const insertTransaction = async (nbf: number, exp: number) => {
|
|
158
|
-
const newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
159
|
-
const signer = await Account.random()
|
|
160
|
-
const hashPayload = new PayloadBuilder<HashPayload>({ schema: HashSchema })
|
|
161
|
-
.fields({ hash: head._hash })
|
|
162
|
-
.build()
|
|
163
|
-
const tx = await buildRandomTransaction(TestChainId, [hashPayload], signer, nbf, exp)
|
|
164
|
-
const bundled = hydratedTransactionToPayloadBundle(tx)
|
|
165
|
-
await pendingBundledTransactionsArchivist.insert([bundled])
|
|
166
|
-
newTransactions.push(tx)
|
|
167
|
-
await delay(2)
|
|
168
|
-
return newTransactions
|
|
169
|
-
}
|
|
170
|
-
describe('transaction nbf', () => {
|
|
171
|
-
describe('is included', () => {
|
|
172
|
-
it('when equal to previous block', async () => {
|
|
173
|
-
const nbf = head.block - 1
|
|
174
|
-
const exp = head.block + 3
|
|
175
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
176
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
177
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
178
|
-
})
|
|
179
|
-
it('when equal to current block', async () => {
|
|
180
|
-
const nbf = head.block
|
|
181
|
-
const exp = head.block + 3
|
|
182
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
183
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
184
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
185
|
-
})
|
|
186
|
-
it('when equal to next block', async () => {
|
|
187
|
-
const nbf = head.block + 1
|
|
188
|
-
const exp = head.block + 3
|
|
189
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
190
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
191
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
describe('is excluded', () => {
|
|
195
|
-
it('when beyond next block', async () => {
|
|
196
|
-
const nbf = head.block + 2
|
|
197
|
-
const exp = head.block + 3
|
|
198
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
199
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
200
|
-
expect(result.length).toEqual(0)
|
|
201
|
-
})
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
describe('transaction exp', () => {
|
|
205
|
-
describe('is excluded', () => {
|
|
206
|
-
it('when equal to previous block', async () => {
|
|
207
|
-
const nbf = head.block - 3
|
|
208
|
-
const exp = head.block - 1
|
|
209
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
210
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
211
|
-
expect(result.length).toEqual(0)
|
|
212
|
-
})
|
|
213
|
-
it('when equal to current block', async () => {
|
|
214
|
-
const nbf = head.block - 3
|
|
215
|
-
const exp = head.block
|
|
216
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
217
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
218
|
-
expect(result.length).toEqual(0)
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
describe('is included', () => {
|
|
222
|
-
it('when equal to next block', async () => {
|
|
223
|
-
const nbf = head.block - 3
|
|
224
|
-
const exp = head.block + 1
|
|
225
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
226
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
227
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
228
|
-
})
|
|
229
|
-
it('when beyond next block', async () => {
|
|
230
|
-
const nbf = head.block - 3
|
|
231
|
-
const exp = head.block + 3
|
|
232
|
-
const newTransactions = await insertTransaction(nbf, exp)
|
|
233
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length)
|
|
234
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
describe('with pending and rejected transactions', () => {
|
|
241
|
-
let rejectedTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
242
|
-
let newTransactions: SignedHydratedTransactionWithStorageMeta[] = []
|
|
243
|
-
beforeEach(async () => {
|
|
244
|
-
for (let index = 0; index < 5; index++) {
|
|
245
|
-
const tx = await buildRandomTransaction(TestChainId)
|
|
246
|
-
rejectedTransactions.push(tx)
|
|
247
|
-
await delay(2)
|
|
248
|
-
}
|
|
249
|
-
await pendingBundledTransactionsArchivist.insert(rejectedTransactions.map(hydratedTransactionToPayloadBundle))
|
|
250
|
-
for (let index = 0; index < 5; index++) {
|
|
251
|
-
const tx = await buildRandomTransaction(TestChainId)
|
|
252
|
-
newTransactions.push(tx)
|
|
253
|
-
await delay(2)
|
|
254
|
-
}
|
|
255
|
-
await pendingBundledTransactionsArchivist.insert(newTransactions.map(hydratedTransactionToPayloadBundle))
|
|
256
|
-
await rejectedTransactionsArchivist.insert(flattenHydratedTransactions(rejectedTransactions))
|
|
257
|
-
})
|
|
258
|
-
it('should remove rejected transactions from pending', async () => {
|
|
259
|
-
const result = await sut.getPendingTransactions(head._hash, newTransactions.length + rejectedTransactions.length)
|
|
260
|
-
expect(result.length).toEqual(newTransactions.length)
|
|
261
|
-
})
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
describe('with invalid transactions in mempool', () => {
|
|
265
|
-
beforeEach(async () => {
|
|
266
|
-
const incorrectChainId = assertEx(asAddress('1234567890abcdef1234567890abcdef12345678'))
|
|
267
|
-
const invalidTransactions: SignedHydratedTransactionWithStorageMeta[] = [await buildRandomTransaction(incorrectChainId)]
|
|
268
|
-
const pending = invalidTransactions.map(hydratedTransactionToPayloadBundle)
|
|
269
|
-
await pendingBundledTransactionsArchivist.insert(pending)
|
|
270
|
-
})
|
|
271
|
-
it('should remove invalid transactions from pending', async () => {
|
|
272
|
-
const result = await sut.getPendingTransactions(head._hash, 20)
|
|
273
|
-
expect(result.length).toEqual(0)
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { buildRandomTransaction, TestChainId } from '@xyo-network/chain-protocol'
|
|
2
|
-
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
3
|
-
import type { HashPayload } from '@xyo-network/xl1-protocol'
|
|
4
|
-
import { HashSchema } from '@xyo-network/xl1-protocol'
|
|
5
|
-
import {
|
|
6
|
-
describe, expect, it,
|
|
7
|
-
} from 'vitest'
|
|
8
|
-
|
|
9
|
-
import { bundledPayloadToHydratedTransaction } from '../bundledPayloadToHydratedTransaction.ts'
|
|
10
|
-
import { hydratedTransactionToPayloadBundle } from '../hydratedTransactionToPayloadBundle.ts'
|
|
11
|
-
|
|
12
|
-
describe('bundledPayloadToHydratedTransaction', () => {
|
|
13
|
-
it('creates a hydrated transaction from valid payload bundle', async () => {
|
|
14
|
-
// Create a minimal Transaction
|
|
15
|
-
const hashPayload = new PayloadBuilder<HashPayload>({ schema: HashSchema })
|
|
16
|
-
.fields({ hash: '3fc6fb2edd79e0d3f27c668a706cb48473d5f9e432e5462ae647702221a2c97a' })
|
|
17
|
-
.build()
|
|
18
|
-
const payloads = [hashPayload]
|
|
19
|
-
const tx = await buildRandomTransaction(TestChainId, payloads)
|
|
20
|
-
|
|
21
|
-
// Create bundle
|
|
22
|
-
const bundle = await PayloadBuilder.addStorageMeta(hydratedTransactionToPayloadBundle(tx))
|
|
23
|
-
|
|
24
|
-
// Act
|
|
25
|
-
const result = await bundledPayloadToHydratedTransaction(bundle)
|
|
26
|
-
|
|
27
|
-
// Assert
|
|
28
|
-
expect(result).toBeDefined()
|
|
29
|
-
expect(result?.[0]._hash).toEqual(tx[0]._hash)
|
|
30
|
-
expect(result?.[1].length).toEqual(payloads.length)
|
|
31
|
-
for (const payload of result?.[1] ?? []) {
|
|
32
|
-
expect(payload._hash).toEqual(await PayloadBuilder.hash(payload))
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { buildRandomTransaction, TestChainId } from '@xyo-network/chain-protocol'
|
|
2
|
-
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
3
|
-
import { asOptionalPayloadBundle } from '@xyo-network/payload-model'
|
|
4
|
-
import type { HashPayload } from '@xyo-network/xl1-protocol'
|
|
5
|
-
import { HashSchema } from '@xyo-network/xl1-protocol'
|
|
6
|
-
import {
|
|
7
|
-
describe, expect, it,
|
|
8
|
-
} from 'vitest'
|
|
9
|
-
|
|
10
|
-
import { hydratedTransactionToPayloadBundle } from '../hydratedTransactionToPayloadBundle.ts'
|
|
11
|
-
|
|
12
|
-
describe('hydratedTransactionToPayloadBundle', () => {
|
|
13
|
-
it('creates a payload bundle from a hydrated transaction', async () => {
|
|
14
|
-
// Create a minimal Transaction
|
|
15
|
-
const hashPayload = new PayloadBuilder<HashPayload>({ schema: HashSchema })
|
|
16
|
-
.fields({ hash: '3fc6fb2edd79e0d3f27c668a706cb48473d5f9e432e5462ae647702221a2c97a' })
|
|
17
|
-
.build()
|
|
18
|
-
const payloads = [hashPayload]
|
|
19
|
-
const tx = await buildRandomTransaction(TestChainId, payloads)
|
|
20
|
-
|
|
21
|
-
// Act
|
|
22
|
-
const bundle = hydratedTransactionToPayloadBundle(tx)
|
|
23
|
-
|
|
24
|
-
// Assert
|
|
25
|
-
expect(bundle).toBeDefined()
|
|
26
|
-
expect(asOptionalPayloadBundle(bundle)).toBeDefined()
|
|
27
|
-
})
|
|
28
|
-
})
|