@subsquid/solana-normalization 0.0.9 → 0.1.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/src/mapping.ts CHANGED
@@ -4,9 +4,11 @@ import {addErrorContext, unexpectedCase} from '@subsquid/util-internal'
4
4
  import assert from 'assert'
5
5
  import {Balance, Block, BlockHeader, Instruction, LogMessage, Reward, TokenBalance, Transaction} from './data'
6
6
  import {InvokeMessage, InvokeResultMessage, LogTruncatedMessage, Message, parseLogMessage} from './log-parser'
7
+ import {Journal, TransactionContext} from './transaction-context'
8
+ import {InstructionTreeTraversal, MessageStream, ParsingError} from './instruction-parser'
7
9
 
8
10
 
9
- export function mapRpcBlock(src: rpc.Block): Block {
11
+ export function mapRpcBlock(src: rpc.Block, journal: Journal): Block {
10
12
  let header: BlockHeader = {
11
13
  hash: src.hash,
12
14
  height: src.height,
@@ -16,21 +18,7 @@ export function mapRpcBlock(src: rpc.Block): Block {
16
18
  timestamp: src.block.blockTime ?? 0
17
19
  }
18
20
 
19
- let instructions: Instruction[] = []
20
- let logs: LogMessage[] = []
21
- let balances: Balance[] = []
22
- let tokenBalances: TokenBalance[] = []
23
-
24
- let transactions = src.block.transactions
25
- ?.map((tx, i) => {
26
- try {
27
- return mapRpcTransaction(i, tx, instructions, logs, balances, tokenBalances)
28
- } catch(err: any) {
29
- throw addErrorContext(err, {
30
- blockTransaction: tx.transaction.signatures[0]
31
- })
32
- }
33
- }) ?? []
21
+ let items = new ItemMapping(journal, src.block)
34
22
 
35
23
  let rewards = src.block.rewards?.map(s => {
36
24
  let reward: Reward = {
@@ -52,396 +40,130 @@ export function mapRpcBlock(src: rpc.Block): Block {
52
40
 
53
41
  return {
54
42
  header,
55
- transactions,
56
- instructions,
57
- logs,
58
- balances,
59
- tokenBalances,
43
+ transactions: items.transactions,
44
+ instructions: items.instructions,
45
+ logs: items.logs,
46
+ balances: items.balances,
47
+ tokenBalances: items.tokenBalances,
60
48
  rewards: rewards || []
61
49
  }
62
50
  }
63
51
 
64
52
 
65
- function mapRpcTransaction(
66
- transactionIndex: number,
67
- src: rpc.Transaction,
68
- instructions: Instruction[],
69
- logs: LogMessage[],
70
- balances: Balance[],
71
- tokenBalances: TokenBalance[]
72
- ): Transaction {
73
- let tx: Transaction = {
74
- transactionIndex,
75
- version: src.version,
76
- accountKeys: src.transaction.message.accountKeys,
77
- addressTableLookups: src.transaction.message.addressTableLookups ?? [],
78
- numReadonlySignedAccounts: src.transaction.message.header.numReadonlySignedAccounts,
79
- numReadonlyUnsignedAccounts: src.transaction.message.header.numReadonlyUnsignedAccounts,
80
- numRequiredSignatures: src.transaction.message.header.numRequiredSignatures,
81
- recentBlockhash: src.transaction.message.recentBlockhash,
82
- signatures: src.transaction.signatures,
83
- err: src.meta.err,
84
- computeUnitsConsumed: BigInt(src.meta.computeUnitsConsumed ?? 0),
85
- fee: BigInt(src.meta.fee),
86
- loadedAddresses: src.meta.loadedAddresses ?? {readonly: [], writable: []},
87
- hasDroppedLogMessages: false
88
- }
89
-
90
- let accounts: Base58Bytes[]
91
- if (tx.version === 'legacy') {
92
- accounts = tx.accountKeys
93
- } else {
94
- assert(src.meta?.loadedAddresses)
95
- accounts = tx.accountKeys.concat(
96
- src.meta.loadedAddresses.writable,
97
- src.meta.loadedAddresses.readonly
98
- )
99
- }
100
-
101
- let getAccount = (index: number): Base58Bytes => {
102
- assert(index < accounts.length)
103
- return accounts[index]
104
- }
105
-
106
- new InstructionParser(
107
- getAccount,
108
- tx,
109
- src,
110
- src.meta.logMessages?.map(parseLogMessage),
111
- instructions,
112
- logs
113
- ).parse()
114
-
115
- balances.push(
116
- ...mapBalances(getAccount, transactionIndex, src)
117
- )
118
-
119
- tokenBalances.push(
120
- ...mapTokenBalances(getAccount, transactionIndex, src)
121
- )
122
-
123
- return tx
124
- }
125
-
126
-
127
- const PROGRAMS_MISSING_INVOKE_LOG = new Set([
128
- 'AddressLookupTab1e1111111111111111111111111',
129
- 'BPFLoader1111111111111111111111111111111111',
130
- 'BPFLoader2111111111111111111111111111111111',
131
- 'BPFLoaderUpgradeab1e11111111111111111111111',
132
- 'Ed25519SigVerify111111111111111111111111111',
133
- 'KeccakSecp256k11111111111111111111111111111',
134
- 'NativeLoader1111111111111111111111111111111',
135
- 'ZkTokenProof1111111111111111111111111111111',
136
- 'Secp256r1SigVerify1111111111111111111111111',
137
- '1111111PXvn95h8m6x4oGorNVerA2F4FFRpp7feiK',
138
- '11111112cMQwSC9qirWGjZM6gLGwW69X22mqwLLGP',
139
- '1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM1',
140
- ])
141
-
142
-
143
- class InstructionParser {
144
- private pos = 0
145
- private messages: Message[]
146
- private messagePos = 0
147
- private messagesTruncated = false
148
- private errorPos?: number
149
- private lastAddress: number[] = []
53
+ class ItemMapping {
54
+ transactions: Transaction[] = []
55
+ instructions: Instruction[] = []
56
+ logs: LogMessage[] = []
57
+ balances: Balance[] = []
58
+ tokenBalances: TokenBalance[] = []
150
59
 
151
60
  constructor(
152
- private getAccount: (index: number) => Base58Bytes,
153
- private tx: Transaction,
154
- private src: rpc.Transaction,
155
- messages: Message[] | undefined,
156
- private instructions: Instruction[],
157
- private logs: LogMessage[]
61
+ private journal: Journal,
62
+ block: rpc.GetBlock
158
63
  ) {
159
- if (messages == null) {
160
- this.messages = []
161
- this.messagesTruncated = true
162
- } else {
163
- this.messages = messages
164
- }
165
- let err: any = this.src.meta.err
166
- if (isIntructionError(err)) {
167
- let pos = err.InstructionError[0]
168
- assert(typeof pos == 'number')
169
- assert(0 <= pos && pos < this.src.transaction.message.instructions.length)
170
- this.errorPos = pos
171
- }
172
- }
173
-
174
- parse(): void {
175
- while (this.pos < this.src.transaction.message.instructions.length) {
176
- let instruction = this.src.transaction.message.instructions[this.pos]
177
-
178
- let inner = this.src.meta.innerInstructions
179
- ?.filter(i => i.index === this.pos)
180
- .flatMap(i => i.instructions) ?? []
181
-
182
- if (this.errorPos == null || this.errorPos >= this.pos) {
183
- let instructions = [instruction].concat(inner)
184
- let end = this.traverse(instructions, 0, 1)
185
- assert(end == instructions.length)
186
- } else {
187
- this.assert(inner.length == 0, false, 0, 'seemingly non-executed instruction has inner instructions')
188
- this.push(1, instruction)
64
+ let transactions = block.transactions ?? []
65
+ for (let i = 0; i < transactions.length; i++) {
66
+ let tx = transactions[i]
67
+ let txIndex = tx._index ?? i
68
+ try {
69
+ this.processTransaction(txIndex, tx)
70
+ } catch(err: any) {
71
+ throw addErrorContext(err, {
72
+ transactionHash: tx.transaction.signatures[0]
73
+ })
189
74
  }
190
-
191
- this.pos += 1
192
75
  }
193
- this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || this.messagesTruncated
194
76
  }
195
77
 
196
- private traverse(
197
- instructions: rpc.Instruction[],
198
- pos: number,
199
- stackHeight: number
200
- ): number {
201
- this.assert(pos < instructions.length, true, 0, 'unexpected and of inner instructions')
202
-
203
- let instruction = instructions[pos]
78
+ private processTransaction(transactionIndex: number, src: rpc.Transaction): void {
79
+ let mapped = mapTransaction(transactionIndex, src)
204
80
 
205
- this.assert(
206
- instruction.stackHeight == null || instruction.stackHeight == stackHeight,
207
- false, pos, 'instruction has unexpected stack height'
208
- )
209
-
210
- let msg: Message | undefined
211
- if (this.messagePos < this.messages.length) {
212
- msg = this.messages[this.messagePos]
213
- }
214
-
215
- if (msg?.kind == 'truncate') {
216
- this.messagePos += 1
217
- this.messagesTruncated = true
218
- }
219
-
220
- if (this.messagesTruncated) {
221
- this.push(stackHeight, instruction)
222
- return this.logLessTraversal(stackHeight, instructions, pos + 1)
223
- }
81
+ let ctx = new TransactionContext(transactionIndex, src, this.journal)
224
82
 
225
- let programId = this.getAccount(instruction.programIdIndex)
83
+ let messages = new MessageStream(src.meta.logMessages ?? [])
226
84
 
227
- if (msg?.kind === 'invoke' && msg.programId === programId) {
228
- this.assert(msg.stackHeight == stackHeight, true, pos, 'invoke message has unexpected stack height')
229
- this.messagePos += 1
230
- return this.invokeInstruction(stackHeight, instructions, pos)
231
- }
232
-
233
- if (PROGRAMS_MISSING_INVOKE_LOG.has(programId)) {
234
- let dropped = this.dropInvokeLessInstructionMessages(pos)
235
- let ins = this.push(stackHeight, instruction)
236
- ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || dropped
237
- this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || dropped
238
- return this.invokeLessTraversal(dropped, stackHeight, instructions, pos + 1)
239
- }
240
-
241
- // FIXME: add an option to ignore this
242
- throw this.error(true, pos, 'missing invoke message')
243
- }
244
-
245
- private dropInvokeLessInstructionMessages(pos: number): boolean {
246
- let initialPos = this.messagePos
247
- while (this.messagePos < this.messages.length && !this.messagesTruncated) {
248
- let msg = this.messages[this.messagePos]
249
- switch(msg.kind) {
250
- case 'log':
251
- case 'data':
252
- case 'cu':
253
- case 'other':
254
- this.messagePos += 1
255
- break
256
- case 'truncate':
257
- this.messagePos += 1
258
- this.messagesTruncated = true
259
- return true
260
- case 'invoke':
261
- return this.messagePos - initialPos > 0
262
- case 'invoke-result':
263
- throw this.error(true, pos, `invoke result message does not match any invoke`)
264
- default:
265
- throw unexpectedCase()
266
- }
267
- }
268
- return false
269
- }
270
-
271
- private invokeInstruction(
272
- stackHeight: number,
273
- instructions: rpc.Instruction[],
274
- instructionPos: number
275
- ): number {
276
- let ins = this.push(stackHeight, instructions[instructionPos])
277
- let pos = instructionPos + 1
278
- while (true) {
279
- let token = this.takeInstructionMessages(ins, instructionPos)
280
- switch(token.kind) {
281
- case 'invoke':
282
- pos = this.traverse(instructions, pos, stackHeight + 1)
283
- break
284
- case 'invoke-result':
285
- if (token.programId != ins.programId) {
286
- throw this.error(true, instructionPos,
287
- `invoke result message and instruction program ids don't match`
288
- )
289
- }
290
- if (token.error) {
291
- ins.error = token.error
292
- }
293
- pos = this.invokeLessTraversal(true, stackHeight, instructions, pos)
294
- this.messagePos += 1
295
- return pos
296
- case 'truncate':
297
- ins.hasDroppedLogMessages = true
298
- return this.logLessTraversal(stackHeight, instructions, pos)
299
- default:
300
- throw unexpectedCase()
301
- }
302
- }
303
- }
304
-
305
- private takeInstructionMessages(
306
- ins: Instruction,
307
- pos: number
308
- ): InvokeMessage | InvokeResultMessage | LogTruncatedMessage {
309
- if (this.messagesTruncated) return new LogTruncatedMessage()
310
- while (this.messagePos < this.messages.length) {
311
- let msg = this.messages[this.messagePos]
312
- switch(msg.kind) {
313
- case 'log':
314
- case 'data':
315
- case 'other':
316
- this.logs.push({
317
- transactionIndex: ins.transactionIndex,
318
- logIndex: this.messagePos,
319
- instructionAddress: ins.instructionAddress,
320
- programId: ins.programId,
321
- kind: msg.kind,
322
- message: msg.message
323
- })
324
- break
325
- case 'cu':
326
- if (ins.programId != msg.programId) {
327
- throw this.error(true, pos, 'unexpected programId in compute unit message')
328
- }
329
- ins.computeUnitsConsumed = msg.consumed
330
- break
331
- case 'invoke':
332
- case 'invoke-result':
333
- return msg
334
- case 'truncate':
335
- this.messagesTruncated = true
336
- this.messagePos += 1
337
- return msg
338
- default:
339
- throw unexpectedCase()
85
+ let insCheckPoint = this.instructions.length
86
+ let logCheckPoint = this.logs.length
87
+ try {
88
+ this.traverseInstructions(ctx, messages)
89
+ mapped.hasDroppedLogMessages = messages.truncated
90
+ if (!messages.ended) {
91
+ let err = new ParsingError('not all log messages where consumed')
92
+ err.logMessageIndex = messages.position
93
+ throw err
340
94
  }
341
- this.messagePos += 1
342
- }
343
- throw this.error(false, pos, 'unexpected end of log messages')
344
- }
345
-
346
- private invokeLessTraversal(
347
- messagesDropped: boolean,
348
- parentStackHeight: number,
349
- instructions: rpc.Instruction[],
350
- pos: number
351
- ): number {
352
- return this.logLessTraversal(parentStackHeight, instructions, pos, (ins, pos) => {
353
- ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || messagesDropped
354
- if (PROGRAMS_MISSING_INVOKE_LOG.has(ins.programId)) {
355
- } else if (isIntructionError(this.tx.err) && this.tx.err.InstructionError[1] === 'CallDepth') {
95
+ } catch(err: any) {
96
+ if (err instanceof ParsingError) {
97
+ // report parsing problem
98
+ let {msg, ...props} = err
99
+ ctx.error(props, msg)
100
+ // reparse without log messages
101
+ mapped.hasDroppedLogMessages = true
102
+ // restore state before failed traversal
103
+ this.instructions = this.instructions.slice(0, insCheckPoint)
104
+ this.logs = this.logs.slice(0, logCheckPoint)
105
+ // traverse again with dummy truncated MessageStream
106
+ this.traverseInstructions(
107
+ ctx,
108
+ new MessageStream([new LogTruncatedMessage().toString()])
109
+ )
356
110
  } else {
357
- throw this.error(false, pos, 'invoke message is missing')
358
- }
359
- })
360
- }
361
-
362
- private logLessTraversal(
363
- parentStackHeight: number,
364
- instructions: rpc.Instruction[],
365
- pos: number,
366
- cb?: (ins: Instruction, pos: number) => void
367
- ): number {
368
- while (pos < instructions.length) {
369
- let instruction = instructions[pos]
370
- let stackHeight = instruction.stackHeight ?? 2
371
- if (stackHeight > parentStackHeight) {
372
- let ins = this.push(stackHeight, instruction)
373
- cb?.(ins, pos)
374
- pos += 1
375
- } else {
376
- return pos
111
+ throw err
377
112
  }
378
113
  }
379
- return pos
380
- }
381
-
382
- private push(stackHeight: number, src: rpc.Instruction): Instruction {
383
- assert(stackHeight > 0)
384
114
 
385
- if (src.stackHeight != null) {
386
- assert(stackHeight === src.stackHeight)
387
- }
115
+ this.transactions.push(mapped)
116
+ this.balances.push(...mapBalances(ctx))
117
+ this.tokenBalances.push(...mapTokenBalances(ctx))
118
+ }
388
119
 
389
- let address = this.lastAddress.slice()
120
+ private traverseInstructions(ctx: TransactionContext, messages: MessageStream): void {
121
+ for (let i = 0; i < ctx.tx.transaction.message.instructions.length; i++) {
122
+ let ins = ctx.tx.transaction.message.instructions[i]
390
123
 
391
- while (address.length > stackHeight) {
392
- address.pop()
393
- }
394
-
395
- if (address.length === stackHeight) {
396
- address[stackHeight - 1] += 1
397
- } else {
398
- assert(address.length + 1 == stackHeight)
399
- address[stackHeight - 1] = 0
400
- }
124
+ let inner = ctx.tx.meta.innerInstructions?.flatMap(pack => {
125
+ return pack.index === i ? pack.instructions : []
126
+ })
401
127
 
402
- let i: Instruction = {
403
- transactionIndex: this.tx.transactionIndex,
404
- instructionAddress: address,
405
- programId: this.getAccount(src.programIdIndex),
406
- accounts: src.accounts.map(a => this.getAccount(a)),
407
- data: src.data,
408
- isCommitted: !this.tx.err,
409
- hasDroppedLogMessages: this.messagesTruncated
128
+ new InstructionTreeTraversal(
129
+ ctx,
130
+ messages,
131
+ i,
132
+ ins,
133
+ inner ?? [],
134
+ this.instructions,
135
+ this.logs
136
+ )
410
137
  }
411
-
412
- this.instructions.push(i)
413
- this.lastAddress = address
414
- return i
415
138
  }
139
+ }
416
140
 
417
- private assert(ok: unknown, withMessagePos: boolean, innerPos: number, msg: string): asserts ok {
418
- if (!ok) throw this.error(withMessagePos, innerPos, msg)
419
- }
420
141
 
421
- private error(withMessagePos: boolean, innerPos: number, msg: string): Error {
422
- let loc = `stopped at instruction ${this.pos}`
423
- if (innerPos > 0) {
424
- loc += `, inner instruction ${innerPos - 1})`
425
- }
426
- if (withMessagePos && this.messagePos < this.messages.length) {
427
- loc += ` and log message ${this.messagePos}`
428
- }
429
- return new Error(
430
- `Failed to process transaction ${this.tx.signatures[0]}: ${loc}: ${msg}`
431
- )
142
+ function mapTransaction(transactionIndex: number, src: rpc.Transaction): Transaction {
143
+ return {
144
+ transactionIndex,
145
+ version: src.version,
146
+ accountKeys: src.transaction.message.accountKeys,
147
+ addressTableLookups: src.transaction.message.addressTableLookups ?? [],
148
+ numReadonlySignedAccounts: src.transaction.message.header.numReadonlySignedAccounts,
149
+ numReadonlyUnsignedAccounts: src.transaction.message.header.numReadonlyUnsignedAccounts,
150
+ numRequiredSignatures: src.transaction.message.header.numRequiredSignatures,
151
+ recentBlockhash: src.transaction.message.recentBlockhash,
152
+ signatures: src.transaction.signatures,
153
+ err: src.meta.err,
154
+ computeUnitsConsumed: BigInt(src.meta.computeUnitsConsumed ?? 0),
155
+ fee: BigInt(src.meta.fee),
156
+ loadedAddresses: src.meta.loadedAddresses ?? {readonly: [], writable: []},
157
+ hasDroppedLogMessages: false
432
158
  }
433
159
  }
434
160
 
435
161
 
436
- function mapBalances(
437
- getAccount: (idx: number) => Base58Bytes,
438
- transactionIndex: number,
439
- tx: rpc.Transaction,
440
- ): Balance[] {
162
+ function mapBalances(ctx: TransactionContext): Balance[] {
441
163
  let balances: Balance[] = []
442
164
 
443
- let pre = tx.meta.preBalances
444
- let post = tx.meta.postBalances
165
+ let pre = ctx.tx.meta.preBalances
166
+ let post = ctx.tx.meta.postBalances
445
167
 
446
168
  assert(pre.length == post.length)
447
169
 
@@ -450,8 +172,8 @@ function mapBalances(
450
172
  // nothing changed, don't create an entry
451
173
  } else {
452
174
  balances.push({
453
- transactionIndex,
454
- account: getAccount(i),
175
+ transactionIndex: ctx.transactionIndex,
176
+ account: ctx.getAccount(i),
455
177
  pre: BigInt(pre[i]),
456
178
  post: BigInt(post[i])
457
179
  })
@@ -468,26 +190,22 @@ function mapBalances(
468
190
  }
469
191
 
470
192
 
471
- function mapTokenBalances(
472
- getAccount: (idx: number) => Base58Bytes,
473
- transactionIndex: number,
474
- tx: rpc.Transaction
475
- ): TokenBalance[] {
193
+ function mapTokenBalances(ctx: TransactionContext): TokenBalance[] {
476
194
  let balances: TokenBalance[] = []
477
195
 
478
196
  let preBalances = new Map(
479
- tx.meta.preTokenBalances?.map(b => [getAccount(b.accountIndex), b])
197
+ ctx.tx.meta.preTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
480
198
  )
481
199
 
482
200
  let postBalances = new Map(
483
- tx.meta.postTokenBalances?.map(b => [getAccount(b.accountIndex), b])
201
+ ctx.tx.meta.postTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
484
202
  )
485
203
 
486
204
  for (let [account, post] of postBalances.entries()) {
487
205
  let pre = preBalances.get(account)
488
206
  if (pre) {
489
207
  balances.push({
490
- transactionIndex,
208
+ transactionIndex: ctx.transactionIndex,
491
209
  account,
492
210
 
493
211
  preProgramId: pre.programId ?? undefined,
@@ -504,7 +222,7 @@ function mapTokenBalances(
504
222
  })
505
223
  } else {
506
224
  balances.push({
507
- transactionIndex,
225
+ transactionIndex: ctx.transactionIndex,
508
226
  account,
509
227
  postProgramId: post.programId ?? undefined,
510
228
  postMint: post.mint,
@@ -518,7 +236,7 @@ function mapTokenBalances(
518
236
  for (let [account, pre] of preBalances.entries()) {
519
237
  if (postBalances.has(account)) continue
520
238
  balances.push({
521
- transactionIndex,
239
+ transactionIndex: ctx.transactionIndex,
522
240
  account,
523
241
  preProgramId: pre.programId ?? undefined,
524
242
  preMint: pre.mint,
@@ -535,8 +253,4 @@ function mapTokenBalances(
535
253
  })
536
254
 
537
255
  return balances
538
- }
539
-
540
- function isIntructionError(err: unknown): err is {InstructionError: [number, string]} {
541
- return typeof err == 'object' && err != null && 'InstructionError' in err
542
256
  }
@@ -0,0 +1,99 @@
1
+ import * as rpc from '@subsquid/solana-rpc-data'
2
+ import {Base58Bytes} from '@subsquid/solana-rpc-data'
3
+ import assert from 'assert'
4
+
5
+ export interface Journal {
6
+ warn(props: any, msg: string): void
7
+ error(props: any, msg: string): void
8
+ }
9
+
10
+ export class TransactionContext {
11
+ public readonly erroredInstruction: number
12
+ public readonly couldFailBeforeInvokeMessage: boolean
13
+
14
+ private accounts: Base58Bytes[]
15
+
16
+ constructor(
17
+ public readonly transactionIndex: number,
18
+ public readonly tx: rpc.Transaction,
19
+ private journal: Journal
20
+ ) {
21
+ if (tx.version == 'legacy') {
22
+ this.accounts = tx.transaction.message.accountKeys
23
+ } else {
24
+ this.accounts = tx.transaction.message.accountKeys.concat(
25
+ tx.meta.loadedAddresses?.writable ?? [],
26
+ tx.meta.loadedAddresses?.readonly ?? []
27
+ )
28
+ }
29
+
30
+ let err = this.tx.meta.err
31
+ if (isInstructionError(err)) {
32
+ let pos = err.InstructionError[0]
33
+ let type = err.InstructionError[1]
34
+ if (Number.isSafeInteger(pos)) {
35
+ this.erroredInstruction = pos
36
+ } else {
37
+ this.erroredInstruction = tx.transaction.message.instructions.length
38
+ this.warn({transactionError: err}, 'got InstructionError of unrecognized shape')
39
+ }
40
+ switch (type) {
41
+ case 'AccountNotExecutable':
42
+ case 'CallDepth':
43
+ case 'MissingAccount':
44
+ case 'NotEnoughAccountKeys':
45
+ case 'ReentrancyNotAllowed':
46
+ case 'UnsupportedProgramId':
47
+ this.couldFailBeforeInvokeMessage = true
48
+ break
49
+ default:
50
+ this.couldFailBeforeInvokeMessage = false
51
+ }
52
+ } else if (err != null && !this.tx.meta.logMessages?.length && !this.tx.meta.innerInstructions?.length) {
53
+ this.erroredInstruction = -1
54
+ this.couldFailBeforeInvokeMessage = false
55
+ } else {
56
+ this.erroredInstruction = tx.transaction.message.instructions.length
57
+ this.couldFailBeforeInvokeMessage = false
58
+ }
59
+ }
60
+
61
+ get isCommitted(): boolean {
62
+ return this.tx.meta.err == null
63
+ }
64
+
65
+ get transactionHash(): string {
66
+ return this.tx.transaction.signatures[0]
67
+ }
68
+
69
+ getAccount(index: number): Base58Bytes {
70
+ assert(index < this.accounts.length)
71
+ return this.accounts[index]
72
+ }
73
+
74
+ warn(props: any, msg: string): void {
75
+ this.journal.warn(
76
+ {
77
+ transactionHash: this.transactionHash,
78
+ transactionIndex: this.transactionIndex,
79
+ ...props,
80
+ },
81
+ msg
82
+ )
83
+ }
84
+
85
+ error(props: any, msg: string): void {
86
+ this.journal.error(
87
+ {
88
+ transactionHash: this.transactionHash,
89
+ transactionIndex: this.transactionIndex,
90
+ ...props,
91
+ },
92
+ msg
93
+ )
94
+ }
95
+ }
96
+
97
+ function isInstructionError(err: unknown): err is {InstructionError: [number, string]} {
98
+ return typeof err == 'object' && err != null && 'InstructionError' in err
99
+ }