@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.
- package/README.md +4 -0
- package/lib/archive/schema.d.ts +167 -0
- package/lib/archive/schema.d.ts.map +1 -0
- package/lib/archive/schema.js +117 -0
- package/lib/archive/schema.js.map +1 -0
- package/lib/archive/source.d.ts +15 -0
- package/lib/archive/source.d.ts.map +1 -0
- package/lib/archive/source.js +98 -0
- package/lib/archive/source.js.map +1 -0
- package/lib/data/fields.d.ts +8 -0
- package/lib/data/fields.d.ts.map +1 -0
- package/lib/data/fields.js +47 -0
- package/lib/data/fields.js.map +1 -0
- package/lib/data/model.d.ts +79 -0
- package/lib/data/model.d.ts.map +1 -0
- package/lib/data/model.js +44 -0
- package/lib/data/model.js.map +1 -0
- package/lib/data/partial.d.ts +26 -0
- package/lib/data/partial.d.ts.map +1 -0
- package/lib/data/partial.js +3 -0
- package/lib/data/partial.js.map +1 -0
- package/lib/data/request.d.ts +104 -0
- package/lib/data/request.d.ts.map +1 -0
- package/lib/data/request.js +3 -0
- package/lib/data/request.js.map +1 -0
- package/lib/data/type-util.d.ts +20 -0
- package/lib/data/type-util.d.ts.map +1 -0
- package/lib/data/type-util.js +3 -0
- package/lib/data/type-util.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +21 -0
- package/lib/index.js.map +1 -0
- package/lib/instruction.d.ts +13 -0
- package/lib/instruction.d.ts.map +1 -0
- package/lib/instruction.js +24 -0
- package/lib/instruction.js.map +1 -0
- package/lib/rpc/client.d.ts +17 -0
- package/lib/rpc/client.d.ts.map +1 -0
- package/lib/rpc/client.js +15 -0
- package/lib/rpc/client.js.map +1 -0
- package/lib/rpc/filter.d.ts +4 -0
- package/lib/rpc/filter.d.ts.map +1 -0
- package/lib/rpc/filter.js +337 -0
- package/lib/rpc/filter.js.map +1 -0
- package/lib/rpc/mapping.d.ts +5 -0
- package/lib/rpc/mapping.d.ts.map +1 -0
- package/lib/rpc/mapping.js +13 -0
- package/lib/rpc/mapping.js.map +1 -0
- package/lib/rpc/project.d.ts +5 -0
- package/lib/rpc/project.d.ts.map +1 -0
- package/lib/rpc/project.js +61 -0
- package/lib/rpc/project.js.map +1 -0
- package/lib/rpc/source.d.ts +15 -0
- package/lib/rpc/source.d.ts.map +1 -0
- package/lib/rpc/source.js +82 -0
- package/lib/rpc/source.js.map +1 -0
- package/lib/source.d.ts +107 -0
- package/lib/source.d.ts.map +1 -0
- package/lib/source.js +280 -0
- package/lib/source.js.map +1 -0
- package/package.json +38 -0
- package/src/archive/schema.ts +137 -0
- package/src/archive/source.ts +105 -0
- package/src/data/fields.ts +50 -0
- package/src/data/model.ts +154 -0
- package/src/data/partial.ts +31 -0
- package/src/data/request.ts +138 -0
- package/src/data/type-util.ts +42 -0
- package/src/index.ts +4 -0
- package/src/instruction.ts +28 -0
- package/src/rpc/client.ts +26 -0
- package/src/rpc/filter.ts +351 -0
- package/src/rpc/mapping.ts +13 -0
- package/src/rpc/project.ts +61 -0
- package/src/rpc/source.ts +90 -0
- 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
|
+
}
|