@subsquid/solana-stream 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +4 -0
  2. package/lib/archive/schema.d.ts +167 -0
  3. package/lib/archive/schema.d.ts.map +1 -0
  4. package/lib/archive/schema.js +117 -0
  5. package/lib/archive/schema.js.map +1 -0
  6. package/lib/archive/source.d.ts +15 -0
  7. package/lib/archive/source.d.ts.map +1 -0
  8. package/lib/archive/source.js +98 -0
  9. package/lib/archive/source.js.map +1 -0
  10. package/lib/data/fields.d.ts +8 -0
  11. package/lib/data/fields.d.ts.map +1 -0
  12. package/lib/data/fields.js +47 -0
  13. package/lib/data/fields.js.map +1 -0
  14. package/lib/data/model.d.ts +79 -0
  15. package/lib/data/model.d.ts.map +1 -0
  16. package/lib/data/model.js +44 -0
  17. package/lib/data/model.js.map +1 -0
  18. package/lib/data/partial.d.ts +26 -0
  19. package/lib/data/partial.d.ts.map +1 -0
  20. package/lib/data/partial.js +3 -0
  21. package/lib/data/partial.js.map +1 -0
  22. package/lib/data/request.d.ts +104 -0
  23. package/lib/data/request.d.ts.map +1 -0
  24. package/lib/data/request.js +3 -0
  25. package/lib/data/request.js.map +1 -0
  26. package/lib/data/type-util.d.ts +20 -0
  27. package/lib/data/type-util.d.ts.map +1 -0
  28. package/lib/data/type-util.js +3 -0
  29. package/lib/data/type-util.js.map +1 -0
  30. package/lib/index.d.ts +5 -0
  31. package/lib/index.d.ts.map +1 -0
  32. package/lib/index.js +21 -0
  33. package/lib/index.js.map +1 -0
  34. package/lib/instruction.d.ts +13 -0
  35. package/lib/instruction.d.ts.map +1 -0
  36. package/lib/instruction.js +24 -0
  37. package/lib/instruction.js.map +1 -0
  38. package/lib/rpc/client.d.ts +17 -0
  39. package/lib/rpc/client.d.ts.map +1 -0
  40. package/lib/rpc/client.js +15 -0
  41. package/lib/rpc/client.js.map +1 -0
  42. package/lib/rpc/filter.d.ts +4 -0
  43. package/lib/rpc/filter.d.ts.map +1 -0
  44. package/lib/rpc/filter.js +337 -0
  45. package/lib/rpc/filter.js.map +1 -0
  46. package/lib/rpc/mapping.d.ts +5 -0
  47. package/lib/rpc/mapping.d.ts.map +1 -0
  48. package/lib/rpc/mapping.js +13 -0
  49. package/lib/rpc/mapping.js.map +1 -0
  50. package/lib/rpc/project.d.ts +5 -0
  51. package/lib/rpc/project.d.ts.map +1 -0
  52. package/lib/rpc/project.js +61 -0
  53. package/lib/rpc/project.js.map +1 -0
  54. package/lib/rpc/source.d.ts +15 -0
  55. package/lib/rpc/source.d.ts.map +1 -0
  56. package/lib/rpc/source.js +82 -0
  57. package/lib/rpc/source.js.map +1 -0
  58. package/lib/source.d.ts +107 -0
  59. package/lib/source.d.ts.map +1 -0
  60. package/lib/source.js +280 -0
  61. package/lib/source.js.map +1 -0
  62. package/package.json +38 -0
  63. package/src/archive/schema.ts +137 -0
  64. package/src/archive/source.ts +105 -0
  65. package/src/data/fields.ts +50 -0
  66. package/src/data/model.ts +154 -0
  67. package/src/data/partial.ts +31 -0
  68. package/src/data/request.ts +138 -0
  69. package/src/data/type-util.ts +42 -0
  70. package/src/index.ts +4 -0
  71. package/src/instruction.ts +28 -0
  72. package/src/rpc/client.ts +26 -0
  73. package/src/rpc/filter.ts +351 -0
  74. package/src/rpc/mapping.ts +13 -0
  75. package/src/rpc/project.ts +61 -0
  76. package/src/rpc/source.ts +90 -0
  77. package/src/source.ts +392 -0
@@ -0,0 +1,28 @@
1
+ import {Base58Bytes} from '@subsquid/solana-rpc-data'
2
+ import {toHex} from '@subsquid/util-internal-hex'
3
+ import bs58 from 'bs58'
4
+ import {Bytes} from './data/model'
5
+
6
+
7
+ export const DATA_SYM = Symbol('DATA')
8
+ export const D8_SYM = Symbol('D8')
9
+
10
+
11
+ interface Instruction {
12
+ data: Base58Bytes
13
+ [DATA_SYM]?: Uint8Array
14
+ [D8_SYM]?: Bytes
15
+ }
16
+
17
+
18
+ export function getInstructionData(i: Instruction): Uint8Array {
19
+ if (i[DATA_SYM]) return i[DATA_SYM]
20
+ return i[DATA_SYM] = bs58.decode(i.data)
21
+ }
22
+
23
+
24
+ export function getInstructionDescriptor(i: Instruction): string {
25
+ if (i[D8_SYM]) return i[D8_SYM]
26
+ let bytes = toHex(getInstructionData(i))
27
+ return i[D8_SYM] = bytes.slice(0, 18)
28
+ }
@@ -0,0 +1,26 @@
1
+ import {RpcClient, RpcClientOptions} from '@subsquid/rpc-client'
2
+
3
+
4
+ export interface SolanaRpcClientOptions extends RpcClientOptions {
5
+ /**
6
+ * Retry on connection error.
7
+ *
8
+ * By default, retries indefinitely.
9
+ */
10
+ retryAttempts?: number
11
+ /**
12
+ * Unsafe integer check must be always enabled
13
+ */
14
+ fixUnsafeIntegers?: true
15
+ }
16
+
17
+
18
+ export class SolanaRpcClient extends RpcClient {
19
+ constructor(options: SolanaRpcClientOptions) {
20
+ super({
21
+ retryAttempts: Number.MAX_SAFE_INTEGER,
22
+ ...options,
23
+ fixUnsafeIntegers: true
24
+ })
25
+ }
26
+ }
@@ -0,0 +1,351 @@
1
+ import {
2
+ Balance,
3
+ Block,
4
+ Instruction,
5
+ LogMessage,
6
+ Reward,
7
+ TokenBalance,
8
+ Transaction
9
+ } from '@subsquid/solana-normalization'
10
+ import {bisect, def, groupBy, weakMemo} from '@subsquid/util-internal'
11
+ import {EntityFilter, FilterBuilder} from '@subsquid/util-internal-processor-tools'
12
+ import assert from 'assert'
13
+ import {
14
+ BalanceRequestRelations,
15
+ DataRequest,
16
+ InstructionRequestRelations,
17
+ LogRequestRelations,
18
+ TokenBalanceRequestRelations,
19
+ TransactionRequestRelations
20
+ } from '../data/request'
21
+ import {getInstructionDescriptor} from '../instruction'
22
+
23
+
24
+ function buildTransactionFilter(dataRequest: DataRequest): EntityFilter<Transaction, TransactionRequestRelations> {
25
+ let items = new EntityFilter()
26
+ for (let req of dataRequest.transactions ?? []) {
27
+ let filter = new FilterBuilder<Transaction>()
28
+ let where = req.where || {}
29
+ filter.getIn(tx => tx.accountKeys[0], where.feePayer)
30
+ items.add(filter, req.include || {})
31
+ }
32
+ return items
33
+ }
34
+
35
+
36
+ function buildInstructionFilter(dataRequest: DataRequest): EntityFilter<Instruction, InstructionRequestRelations> {
37
+ let items = new EntityFilter()
38
+ for (let req of dataRequest.instructions ?? []) {
39
+ let filter = new FilterBuilder<Instruction>()
40
+ let where = req.where || {}
41
+ filter.propIn('programId', where.programId)
42
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d1)
43
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d2)
44
+ filter.matchAny((i, d) => getInstructionDescriptor(i).startsWith(d), where.d4)
45
+ filter.getIn(getInstructionDescriptor, where.d8)
46
+ filter.getIn(i => i.accounts[0], where.a0)
47
+ filter.getIn(i => i.accounts[1], where.a1)
48
+ filter.getIn(i => i.accounts[2], where.a2)
49
+ filter.getIn(i => i.accounts[3], where.a3)
50
+ filter.getIn(i => i.accounts[4], where.a4)
51
+ filter.getIn(i => i.accounts[5], where.a5)
52
+ filter.getIn(i => i.accounts[6], where.a6)
53
+ filter.getIn(i => i.accounts[7], where.a7)
54
+ filter.getIn(i => i.accounts[8], where.a8)
55
+ filter.getIn(i => i.accounts[9], where.a9)
56
+ if (where.isCommitted != null) {
57
+ filter.propIn('isCommitted', [where.isCommitted])
58
+ }
59
+ items.add(filter, req.include ?? {})
60
+ }
61
+ return items
62
+ }
63
+
64
+
65
+ function buildLogFilter(dataRequest: DataRequest): EntityFilter<LogMessage, LogRequestRelations> {
66
+ let items = new EntityFilter()
67
+ for (let req of dataRequest.logs ?? []) {
68
+ let filter = new FilterBuilder<LogMessage>()
69
+ let where = req.where ?? {}
70
+ filter.propIn('programId', where.programId)
71
+ filter.propIn('kind', where.kind)
72
+ items.add(filter, req.include ?? {})
73
+ }
74
+ return items
75
+ }
76
+
77
+
78
+ function buildBalanceFilter(dataRequest: DataRequest): EntityFilter<Balance, BalanceRequestRelations> {
79
+ let items = new EntityFilter()
80
+ for (let req of dataRequest.balances ?? []) {
81
+ let filter = new FilterBuilder<Balance>()
82
+ let where = req.where || {}
83
+ filter.propIn('account', where.account)
84
+ items.add(filter, req.include || {})
85
+ }
86
+ return items
87
+ }
88
+
89
+
90
+ function buildTokenBalanceFilter(dataRequest: DataRequest): EntityFilter<TokenBalance, TokenBalanceRequestRelations> {
91
+ let items = new EntityFilter()
92
+ for (let req of dataRequest.tokenBalances ?? []) {
93
+ let filter = new FilterBuilder<TokenBalance>()
94
+ let where = req.where || {}
95
+ filter.propIn('account', where.account)
96
+ filter.propIn('preProgramId', where.preProgramId)
97
+ filter.propIn('postProgramId', where.postProgramId)
98
+ filter.propIn('preMint', where.preMint)
99
+ filter.propIn('postMint', where.postMint)
100
+ filter.propIn('preOwner', where.preOwner)
101
+ filter.propIn('postOwner', where.postOwner)
102
+ items.add(filter, req.include || {})
103
+ }
104
+ return items
105
+ }
106
+
107
+
108
+ function buildRewardsFilter(dataRequest: DataRequest): EntityFilter<Reward, {}> {
109
+ let items = new EntityFilter()
110
+ for (let req of dataRequest.rewards ?? []) {
111
+ let filter = new FilterBuilder<Reward>()
112
+ let where = req.where || {}
113
+ filter.propIn('pubkey', where.pubkey)
114
+ items.add(filter, {})
115
+ }
116
+ return items
117
+ }
118
+
119
+
120
+ const getItemFilters = weakMemo((req: DataRequest) => {
121
+ return {
122
+ transactions: buildTransactionFilter(req),
123
+ instructions: buildInstructionFilter(req),
124
+ logs: buildLogFilter(req),
125
+ balances: buildBalanceFilter(req),
126
+ tokenBalances: buildTokenBalanceFilter(req),
127
+ rewards: buildRewardsFilter(req)
128
+ }
129
+ })
130
+
131
+
132
+ class IncludeSet {
133
+ transactions = new Set<Transaction>
134
+ instructions = new Set<Instruction>
135
+ logs = new Set<LogMessage>
136
+ balances = new Set<Balance>
137
+ tokenBalances = new Set<TokenBalance>
138
+ rewards = new Set<Reward>
139
+ }
140
+
141
+
142
+ export function filterBlockItems(block: Block, req: DataRequest): void {
143
+ new BlockFilter(block, req).apply()
144
+ }
145
+
146
+
147
+ class BlockFilter {
148
+ private include = new IncludeSet()
149
+ private items: ReturnType<typeof getItemFilters>
150
+
151
+ constructor(private block: Block, req: DataRequest) {
152
+ this.items = getItemFilters(req)
153
+ }
154
+
155
+ @def
156
+ private transactions(): Map<number, Transaction> {
157
+ return new Map(this.block.transactions.map(tx => [tx.transactionIndex, tx]))
158
+ }
159
+
160
+ private getTransaction(idx: number): Transaction {
161
+ let tx = this.transactions().get(idx)
162
+ assert(tx)
163
+ return tx
164
+ }
165
+
166
+ @def
167
+ private logsByTx(): Map<number, LogMessage[]> {
168
+ return groupBy(this.block.logs, log => log.transactionIndex)
169
+ }
170
+
171
+ @def
172
+ private instructionsByTx(): Map<number, Instruction[]> {
173
+ return groupBy(this.block.instructions, i => i.transactionIndex)
174
+ }
175
+
176
+ @def
177
+ private tokenBalancesByTx(): Map<number, TokenBalance[]> {
178
+ return groupBy(this.block.tokenBalances, b => b.transactionIndex)
179
+ }
180
+
181
+ private filterTransactions(): void {
182
+ if (!this.items.transactions.present()) return
183
+ for (let tx of this.block.transactions) {
184
+ let rel = this.items.transactions.match(tx)
185
+ if (rel == null) continue
186
+ this.include.transactions.add(tx)
187
+ if (rel.logs) {
188
+ include(this.include.logs, this.logsByTx().get(tx.transactionIndex))
189
+ }
190
+ if (rel.instructions) {
191
+ include(this.include.instructions, this.instructionsByTx().get(tx.transactionIndex))
192
+ }
193
+ }
194
+ }
195
+
196
+ private filterInstructions(): void {
197
+ if (!this.items.instructions.present()) return
198
+ for (let i = 0; i < this.block.instructions.length; i++) {
199
+ let ins = this.block.instructions[i]
200
+ let rel = this.items.instructions.match(ins)
201
+ if (rel == null) continue
202
+ this.include.instructions.add(ins)
203
+ if (rel.innerInstructions) {
204
+ for (let j = i + 1; j < this.block.instructions.length; j++) {
205
+ let child = this.block.instructions[j]
206
+ if (
207
+ ins.transactionIndex == child.transactionIndex &&
208
+ isChildAddress(ins.instructionAddress, child.instructionAddress)
209
+ ) {
210
+ this.include.instructions.add(child)
211
+ } else {
212
+ break
213
+ }
214
+ }
215
+ }
216
+ if (rel.logs) {
217
+ let logs = this.logsByTx().get(ins.transactionIndex) ?? []
218
+ include(this.include.logs, findInstructionChildren(logs, ins.instructionAddress))
219
+ }
220
+ if (rel.transaction) {
221
+ this.include.transactions.add(this.getTransaction(ins.transactionIndex))
222
+ }
223
+ if (rel.transactionTokenBalances) {
224
+ include(this.include.tokenBalances, this.tokenBalancesByTx().get(ins.transactionIndex))
225
+ }
226
+ }
227
+ }
228
+
229
+ private filterLogs(): void {
230
+ if (!this.items.logs.present()) return
231
+ for (let log of this.block.logs) {
232
+ let rel = this.items.logs.match(log)
233
+ if (rel == null) continue
234
+ this.include.logs.add(log)
235
+ if (rel.transaction) {
236
+ this.include.transactions.add(this.getTransaction(log.transactionIndex))
237
+ }
238
+ if (rel.instruction) {
239
+ this.include.instructions.add(this.getInstruction(log.transactionIndex, log.instructionAddress))
240
+ }
241
+ }
242
+ }
243
+
244
+ private getInstruction(transactionIdx: number, address: number[]): Instruction {
245
+ let pos = bisect(
246
+ this.block.instructions,
247
+ null,
248
+ ins => ins.transactionIndex - transactionIdx || addressCompare(ins.instructionAddress, address)
249
+ )
250
+ let ins = this.block.instructions[pos]
251
+ assert(
252
+ ins &&
253
+ ins.transactionIndex == transactionIdx &&
254
+ addressCompare(ins.instructionAddress, address) == 0
255
+ )
256
+ return ins
257
+ }
258
+
259
+ private filterTokenBalances(): void {
260
+ if (!this.items.tokenBalances.present()) return
261
+ for (let balance of this.block.tokenBalances) {
262
+ let rel = this.items.tokenBalances.match(balance)
263
+ if (rel == null) continue
264
+ this.include.tokenBalances.add(balance)
265
+ if (rel.transaction) {
266
+ this.include.transactions.add(this.getTransaction(balance.transactionIndex))
267
+ }
268
+ if (rel.transactionInstructions) {
269
+ include(this.include.instructions, this.instructionsByTx().get(balance.transactionIndex))
270
+ }
271
+ }
272
+ }
273
+
274
+ private filterBalances(): void {
275
+ if (!this.items.balances.present()) return
276
+ for (let balance of this.block.balances) {
277
+ let rel = this.items.balances.match(balance)
278
+ if (rel == null) continue
279
+ this.include.balances.add(balance)
280
+ if (rel.transaction) {
281
+ this.include.transactions.add(this.getTransaction(balance.transactionIndex))
282
+ }
283
+ if (rel.transactionInstructions) {
284
+ include(this.include.instructions, this.instructionsByTx().get(balance.transactionIndex))
285
+ }
286
+ }
287
+ }
288
+
289
+ private filterRewards(): void {
290
+ if (!this.items.rewards.present() || this.block.rewards == null) return
291
+ for (let reward of this.block.rewards) {
292
+ if (this.items.rewards.match(reward)) {
293
+ this.include.rewards.add(reward)
294
+ }
295
+ }
296
+ }
297
+
298
+ apply(): void {
299
+ this.filterTransactions()
300
+ this.filterInstructions()
301
+ this.filterLogs()
302
+ this.filterBalances()
303
+ this.filterTokenBalances()
304
+ this.filterRewards()
305
+ this.block.transactions = this.block.transactions.filter(i => this.include.transactions.has(i))
306
+ this.block.instructions = this.block.instructions.filter(i => this.include.instructions.has(i))
307
+ this.block.logs = this.block.logs.filter(i => this.include.logs.has(i))
308
+ this.block.balances = this.block.balances.filter(i => this.include.balances.has(i))
309
+ this.block.tokenBalances = this.block.tokenBalances.filter(i => this.include.tokenBalances.has(i))
310
+ this.block.rewards = this.block.rewards.filter(i => this.include.rewards.has(i))
311
+ }
312
+ }
313
+
314
+
315
+ function include<T>(set: Set<T>, items?: Iterable<T>): void {
316
+ if (items == null) return
317
+ for (let it of items) {
318
+ set.add(it)
319
+ }
320
+ }
321
+
322
+
323
+ function* findInstructionChildren<I extends {instructionAddress: number[]}>(
324
+ items: I[],
325
+ addr: number[],
326
+ ): Iterable<I> {
327
+ if (items.length == 0) return
328
+ let beg = bisect(items, addr, (ins, addr) => addressCompare(ins.instructionAddress, addr))
329
+ while (beg < items.length && isChildAddress(items[beg].instructionAddress, addr)) {
330
+ yield items[beg]
331
+ beg += 1
332
+ }
333
+ }
334
+
335
+
336
+ function isChildAddress(parent: number[], addr: number[]): boolean {
337
+ if (parent.length > addr.length) return false
338
+ for (let i = 0; i < parent.length; i++) {
339
+ if (parent[i] !== addr[i]) return false
340
+ }
341
+ return true
342
+ }
343
+
344
+
345
+ function addressCompare(a: number[], b: number[]): number {
346
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
347
+ let order = a[i] - b[i]
348
+ if (order) return order
349
+ }
350
+ return a.length - b.length
351
+ }
@@ -0,0 +1,13 @@
1
+ import {mapRpcBlock} from '@subsquid/solana-normalization'
2
+ import type * as rpc from '@subsquid/solana-rpc-data'
3
+ import {PartialBlock} from '../data/partial'
4
+ import {DataRequest} from '../data/request'
5
+ import {filterBlockItems} from './filter'
6
+ import {projectFields} from './project'
7
+
8
+
9
+ export function mapBlock(src: rpc.Block, req: DataRequest): PartialBlock {
10
+ let block = mapRpcBlock(src)
11
+ filterBlockItems(block, req)
12
+ return projectFields(block, req.fields || {})
13
+ }
@@ -0,0 +1,61 @@
1
+ import {Block} from '@subsquid/solana-normalization'
2
+ import {project} from '../data/fields'
3
+ import {FieldSelection} from '../data/model'
4
+ import {PartialBlock} from '../data/partial'
5
+ import {D8_SYM, DATA_SYM} from '../instruction'
6
+
7
+
8
+ export function projectFields(block: Block, fields: FieldSelection): PartialBlock {
9
+ return {
10
+ header: {
11
+ height: block.header.height,
12
+ hash: block.header.hash,
13
+ parentHash: block.header.parentHash,
14
+ ...project(fields.block, block.header)
15
+ },
16
+ transactions: block.transactions.map(tx => {
17
+ return {
18
+ transactionIndex: tx.transactionIndex,
19
+ ...project(fields.transaction, tx)
20
+ }
21
+ }),
22
+ instructions: block.instructions.map(i => {
23
+ let ins = {
24
+ transactionIndex: i.transactionIndex,
25
+ instructionAddress: i.instructionAddress,
26
+ ...project(fields.instruction, i)
27
+ }
28
+ ;(ins as any)[D8_SYM] = (i as any)[D8_SYM]
29
+ ;(ins as any)[DATA_SYM] = (i as any)[DATA_SYM]
30
+ return ins
31
+ }),
32
+ logs: block.logs.map(log => {
33
+ return {
34
+ transactionIndex: log.transactionIndex,
35
+ logIndex: log.logIndex,
36
+ instructionAddress: log.instructionAddress,
37
+ ...project(fields.log, log)
38
+ }
39
+ }),
40
+ balances: block.balances.map(b => {
41
+ return {
42
+ transactionIndex: b.transactionIndex,
43
+ account: b.account,
44
+ ...project(fields.balance, b)
45
+ }
46
+ }),
47
+ tokenBalances: block.tokenBalances.map(b => {
48
+ return {
49
+ transactionIndex: b.transactionIndex,
50
+ account: b.account,
51
+ ...project(fields.tokenBalance, b)
52
+ }
53
+ }),
54
+ rewards: block.rewards.map(r => {
55
+ return {
56
+ pubkey: r.pubkey,
57
+ ...project(fields.reward, r)
58
+ }
59
+ })
60
+ }
61
+ }
@@ -0,0 +1,90 @@
1
+ import {Base58Bytes, BlockInfo, findSlot, GetBlock, ingestFinalizedBlocks, Rpc} from '@subsquid/solana-rpc'
2
+ import {addErrorContext, wait} from '@subsquid/util-internal'
3
+ import {getRequestAt, mapRangeRequestList, RangeRequestList} from '@subsquid/util-internal-range'
4
+ import {PartialBlock} from '../data/partial'
5
+ import {DataRequest} from '../data/request'
6
+ import type {RpcSettings} from '../source'
7
+ import {mapBlock} from './mapping'
8
+
9
+
10
+ export class RpcDataSource {
11
+ private rpc: Rpc
12
+
13
+ constructor(private options: RpcSettings) {
14
+ this.rpc = new Rpc(options.client)
15
+ }
16
+
17
+ getFinalizedHeight(): Promise<number> {
18
+ return this.rpc.getFinalizedHeight()
19
+ }
20
+
21
+ async getBlockInfo(slot: number): Promise<BlockInfo | undefined> {
22
+ let attempts = 10
23
+ while (attempts) {
24
+ let block = await this.rpc.getBlockInfo('finalized', slot)
25
+ if (block || block === undefined) return block
26
+ await wait(100)
27
+ attempts -= 1
28
+ }
29
+ throw new Error(`Failed to getBlock with finalized commitment at slot ${slot} 10 times in a row`)
30
+ }
31
+
32
+ async getBlockHash(height: number): Promise<Base58Bytes | undefined> {
33
+ let headSlot = await this.rpc.getTopSlot('finalized')
34
+ let top = {
35
+ slot: headSlot,
36
+ height: await this.rpc.getFinalizedBlockHeight(headSlot)
37
+ }
38
+ if (top.height < height) return
39
+ let bottom = {slot: 0, height: 0}
40
+ let slot = await findSlot(this.rpc, height, bottom, top)
41
+ let block = await this.rpc.getFinalizedBlockInfo(slot)
42
+ return block.blockhash
43
+ }
44
+
45
+ async *getBlockStream(
46
+ requests: RangeRequestList<DataRequest>,
47
+ stopOnHead?: boolean | undefined
48
+ ): AsyncIterable<PartialBlock[]> {
49
+ let blockStream = ingestFinalizedBlocks({
50
+ requests: toRpcRequests(requests),
51
+ stopOnHead,
52
+ rpc: this.rpc,
53
+ headPollInterval: 5_000,
54
+ strideSize: this.options.strideSize ?? 5,
55
+ strideConcurrency: this.options.strideConcurrency ?? 5,
56
+ concurrentFetchThreshold: this.options.concurrentFetchThreshold ?? 50
57
+ })
58
+
59
+ for await (let batch of blockStream) {
60
+ let req = getRequestAt(requests, batch[0].height) || {}
61
+ yield batch.map(block => {
62
+ try {
63
+ return mapBlock(block, req)
64
+ } catch(err: any) {
65
+ throw addErrorContext(err, {
66
+ blockHash: block.hash,
67
+ blockHeight: block.height,
68
+ blockSlot: block.slot
69
+ })
70
+ }
71
+ })
72
+ }
73
+ }
74
+ }
75
+
76
+
77
+ function toRpcRequests(requests: RangeRequestList<DataRequest>) {
78
+ return mapRangeRequestList(requests, req => {
79
+ let rewards = req.rewards?.length
80
+ let transactions = req.instructions?.length
81
+ || req.transactions?.length
82
+ || req.tokenBalances?.length
83
+ || req.balances?.length
84
+ || req.logs?.length
85
+ return {
86
+ rewards: !!rewards,
87
+ transactions: !!transactions
88
+ }
89
+ })
90
+ }