@subsquid/solana-normalization 0.0.8 → 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,395 +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
- ])
140
-
141
-
142
- class InstructionParser {
143
- private pos = 0
144
- private messages: Message[]
145
- private messagePos = 0
146
- private messagesTruncated = false
147
- private errorPos?: number
148
- private lastAddress: number[] = []
53
+ class ItemMapping {
54
+ transactions: Transaction[] = []
55
+ instructions: Instruction[] = []
56
+ logs: LogMessage[] = []
57
+ balances: Balance[] = []
58
+ tokenBalances: TokenBalance[] = []
149
59
 
150
60
  constructor(
151
- private getAccount: (index: number) => Base58Bytes,
152
- private tx: Transaction,
153
- private src: rpc.Transaction,
154
- messages: Message[] | undefined,
155
- private instructions: Instruction[],
156
- private logs: LogMessage[]
61
+ private journal: Journal,
62
+ block: rpc.GetBlock
157
63
  ) {
158
- if (messages == null) {
159
- this.messages = []
160
- this.messagesTruncated = true
161
- } else {
162
- this.messages = messages
163
- }
164
- let err: any = this.src.meta.err
165
- if (isIntructionError(err)) {
166
- let pos = err.InstructionError[0]
167
- assert(typeof pos == 'number')
168
- assert(0 <= pos && pos < this.src.transaction.message.instructions.length)
169
- this.errorPos = pos
170
- }
171
- }
172
-
173
- parse(): void {
174
- while (this.pos < this.src.transaction.message.instructions.length) {
175
- let instruction = this.src.transaction.message.instructions[this.pos]
176
-
177
- let inner = this.src.meta.innerInstructions
178
- ?.filter(i => i.index === this.pos)
179
- .flatMap(i => i.instructions) ?? []
180
-
181
- if (this.errorPos == null || this.errorPos >= this.pos) {
182
- let instructions = [instruction].concat(inner)
183
- let end = this.traverse(instructions, 0, 1)
184
- assert(end == instructions.length)
185
- } else {
186
- this.assert(inner.length == 0, false, 0, 'seemingly non-executed instruction has inner instructions')
187
- 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
+ })
188
74
  }
189
-
190
- this.pos += 1
191
75
  }
192
- this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || this.messagesTruncated
193
76
  }
194
77
 
195
- private traverse(
196
- instructions: rpc.Instruction[],
197
- pos: number,
198
- stackHeight: number
199
- ): number {
200
- this.assert(pos < instructions.length, true, 0, 'unexpected and of inner instructions')
201
-
202
- let instruction = instructions[pos]
78
+ private processTransaction(transactionIndex: number, src: rpc.Transaction): void {
79
+ let mapped = mapTransaction(transactionIndex, src)
203
80
 
204
- this.assert(
205
- instruction.stackHeight == null || instruction.stackHeight == stackHeight,
206
- false, pos, 'instruction has unexpected stack height'
207
- )
208
-
209
- let msg: Message | undefined
210
- if (this.messagePos < this.messages.length) {
211
- msg = this.messages[this.messagePos]
212
- }
213
-
214
- if (msg?.kind == 'truncate') {
215
- this.messagePos += 1
216
- this.messagesTruncated = true
217
- }
218
-
219
- if (this.messagesTruncated) {
220
- this.push(stackHeight, instruction)
221
- return this.logLessTraversal(stackHeight, instructions, pos + 1)
222
- }
81
+ let ctx = new TransactionContext(transactionIndex, src, this.journal)
223
82
 
224
- let programId = this.getAccount(instruction.programIdIndex)
83
+ let messages = new MessageStream(src.meta.logMessages ?? [])
225
84
 
226
- if (msg?.kind === 'invoke' && msg.programId === programId) {
227
- this.assert(msg.stackHeight == stackHeight, true, pos, 'invoke message has unexpected stack height')
228
- this.messagePos += 1
229
- return this.invokeInstruction(stackHeight, instructions, pos)
230
- }
231
-
232
- if (PROGRAMS_MISSING_INVOKE_LOG.has(programId)) {
233
- let dropped = this.dropInvokeLessInstructionMessages(pos)
234
- let ins = this.push(stackHeight, instruction)
235
- ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || dropped
236
- this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || dropped
237
- return this.invokeLessTraversal(dropped, stackHeight, instructions, pos + 1)
238
- }
239
-
240
- // FIXME: add an option to ignore this
241
- throw this.error(true, pos, 'missing invoke message')
242
- }
243
-
244
- private dropInvokeLessInstructionMessages(pos: number): boolean {
245
- let initialPos = this.messagePos
246
- while (this.messagePos < this.messages.length && !this.messagesTruncated) {
247
- let msg = this.messages[this.messagePos]
248
- switch(msg.kind) {
249
- case 'log':
250
- case 'data':
251
- case 'cu':
252
- case 'other':
253
- this.messagePos += 1
254
- break
255
- case 'truncate':
256
- this.messagePos += 1
257
- this.messagesTruncated = true
258
- return true
259
- case 'invoke':
260
- return this.messagePos - initialPos > 0
261
- case 'invoke-result':
262
- throw this.error(true, pos, `invoke result message does not match any invoke`)
263
- default:
264
- throw unexpectedCase()
265
- }
266
- }
267
- return false
268
- }
269
-
270
- private invokeInstruction(
271
- stackHeight: number,
272
- instructions: rpc.Instruction[],
273
- instructionPos: number
274
- ): number {
275
- let ins = this.push(stackHeight, instructions[instructionPos])
276
- let pos = instructionPos + 1
277
- while (true) {
278
- let token = this.takeInstructionMessages(ins, instructionPos)
279
- switch(token.kind) {
280
- case 'invoke':
281
- pos = this.traverse(instructions, pos, stackHeight + 1)
282
- break
283
- case 'invoke-result':
284
- if (token.programId != ins.programId) {
285
- throw this.error(true, instructionPos,
286
- `invoke result message and instruction program ids don't match`
287
- )
288
- }
289
- if (token.error) {
290
- ins.error = token.error
291
- }
292
- pos = this.invokeLessTraversal(true, stackHeight, instructions, pos)
293
- this.messagePos += 1
294
- return pos
295
- case 'truncate':
296
- ins.hasDroppedLogMessages = true
297
- return this.logLessTraversal(stackHeight, instructions, pos)
298
- default:
299
- throw unexpectedCase()
300
- }
301
- }
302
- }
303
-
304
- private takeInstructionMessages(
305
- ins: Instruction,
306
- pos: number
307
- ): InvokeMessage | InvokeResultMessage | LogTruncatedMessage {
308
- if (this.messagesTruncated) return new LogTruncatedMessage()
309
- while (this.messagePos < this.messages.length) {
310
- let msg = this.messages[this.messagePos]
311
- switch(msg.kind) {
312
- case 'log':
313
- case 'data':
314
- case 'other':
315
- this.logs.push({
316
- transactionIndex: ins.transactionIndex,
317
- logIndex: this.messagePos,
318
- instructionAddress: ins.instructionAddress,
319
- programId: ins.programId,
320
- kind: msg.kind,
321
- message: msg.message
322
- })
323
- break
324
- case 'cu':
325
- if (ins.programId != msg.programId) {
326
- throw this.error(true, pos, 'unexpected programId in compute unit message')
327
- }
328
- ins.computeUnitsConsumed = msg.consumed
329
- break
330
- case 'invoke':
331
- case 'invoke-result':
332
- return msg
333
- case 'truncate':
334
- this.messagesTruncated = true
335
- this.messagePos += 1
336
- return msg
337
- default:
338
- 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
339
94
  }
340
- this.messagePos += 1
341
- }
342
- throw this.error(false, pos, 'unexpected end of log messages')
343
- }
344
-
345
- private invokeLessTraversal(
346
- messagesDropped: boolean,
347
- parentStackHeight: number,
348
- instructions: rpc.Instruction[],
349
- pos: number
350
- ): number {
351
- return this.logLessTraversal(parentStackHeight, instructions, pos, (ins, pos) => {
352
- ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || messagesDropped
353
- if (PROGRAMS_MISSING_INVOKE_LOG.has(ins.programId)) {
354
- } 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
+ )
355
110
  } else {
356
- throw this.error(false, pos, 'invoke message is missing')
357
- }
358
- })
359
- }
360
-
361
- private logLessTraversal(
362
- parentStackHeight: number,
363
- instructions: rpc.Instruction[],
364
- pos: number,
365
- cb?: (ins: Instruction, pos: number) => void
366
- ): number {
367
- while (pos < instructions.length) {
368
- let instruction = instructions[pos]
369
- let stackHeight = instruction.stackHeight ?? 2
370
- if (stackHeight > parentStackHeight) {
371
- let ins = this.push(stackHeight, instruction)
372
- cb?.(ins, pos)
373
- pos += 1
374
- } else {
375
- return pos
111
+ throw err
376
112
  }
377
113
  }
378
- return pos
379
- }
380
-
381
- private push(stackHeight: number, src: rpc.Instruction): Instruction {
382
- assert(stackHeight > 0)
383
114
 
384
- if (src.stackHeight != null) {
385
- assert(stackHeight === src.stackHeight)
386
- }
115
+ this.transactions.push(mapped)
116
+ this.balances.push(...mapBalances(ctx))
117
+ this.tokenBalances.push(...mapTokenBalances(ctx))
118
+ }
387
119
 
388
- 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]
389
123
 
390
- while (address.length > stackHeight) {
391
- address.pop()
392
- }
393
-
394
- if (address.length === stackHeight) {
395
- address[stackHeight - 1] += 1
396
- } else {
397
- assert(address.length + 1 == stackHeight)
398
- address[stackHeight - 1] = 0
399
- }
124
+ let inner = ctx.tx.meta.innerInstructions?.flatMap(pack => {
125
+ return pack.index === i ? pack.instructions : []
126
+ })
400
127
 
401
- let i: Instruction = {
402
- transactionIndex: this.tx.transactionIndex,
403
- instructionAddress: address,
404
- programId: this.getAccount(src.programIdIndex),
405
- accounts: src.accounts.map(a => this.getAccount(a)),
406
- data: src.data,
407
- isCommitted: !this.tx.err,
408
- hasDroppedLogMessages: this.messagesTruncated
128
+ new InstructionTreeTraversal(
129
+ ctx,
130
+ messages,
131
+ i,
132
+ ins,
133
+ inner ?? [],
134
+ this.instructions,
135
+ this.logs
136
+ )
409
137
  }
410
-
411
- this.instructions.push(i)
412
- this.lastAddress = address
413
- return i
414
138
  }
139
+ }
415
140
 
416
- private assert(ok: unknown, withMessagePos: boolean, innerPos: number, msg: string): asserts ok {
417
- if (!ok) throw this.error(withMessagePos, innerPos, msg)
418
- }
419
141
 
420
- private error(withMessagePos: boolean, innerPos: number, msg: string): Error {
421
- let loc = `stopped at instruction ${this.pos}`
422
- if (innerPos > 0) {
423
- loc += `, inner instruction ${innerPos - 1})`
424
- }
425
- if (withMessagePos && this.messagePos < this.messages.length) {
426
- loc += ` and log message ${this.messagePos}`
427
- }
428
- return new Error(
429
- `Failed to process transaction ${this.tx.signatures[0]}: ${loc}: ${msg}`
430
- )
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
431
158
  }
432
159
  }
433
160
 
434
161
 
435
- function mapBalances(
436
- getAccount: (idx: number) => Base58Bytes,
437
- transactionIndex: number,
438
- tx: rpc.Transaction,
439
- ): Balance[] {
162
+ function mapBalances(ctx: TransactionContext): Balance[] {
440
163
  let balances: Balance[] = []
441
164
 
442
- let pre = tx.meta.preBalances
443
- let post = tx.meta.postBalances
165
+ let pre = ctx.tx.meta.preBalances
166
+ let post = ctx.tx.meta.postBalances
444
167
 
445
168
  assert(pre.length == post.length)
446
169
 
@@ -449,8 +172,8 @@ function mapBalances(
449
172
  // nothing changed, don't create an entry
450
173
  } else {
451
174
  balances.push({
452
- transactionIndex,
453
- account: getAccount(i),
175
+ transactionIndex: ctx.transactionIndex,
176
+ account: ctx.getAccount(i),
454
177
  pre: BigInt(pre[i]),
455
178
  post: BigInt(post[i])
456
179
  })
@@ -467,26 +190,22 @@ function mapBalances(
467
190
  }
468
191
 
469
192
 
470
- function mapTokenBalances(
471
- getAccount: (idx: number) => Base58Bytes,
472
- transactionIndex: number,
473
- tx: rpc.Transaction
474
- ): TokenBalance[] {
193
+ function mapTokenBalances(ctx: TransactionContext): TokenBalance[] {
475
194
  let balances: TokenBalance[] = []
476
195
 
477
196
  let preBalances = new Map(
478
- tx.meta.preTokenBalances?.map(b => [getAccount(b.accountIndex), b])
197
+ ctx.tx.meta.preTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
479
198
  )
480
199
 
481
200
  let postBalances = new Map(
482
- tx.meta.postTokenBalances?.map(b => [getAccount(b.accountIndex), b])
201
+ ctx.tx.meta.postTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
483
202
  )
484
203
 
485
204
  for (let [account, post] of postBalances.entries()) {
486
205
  let pre = preBalances.get(account)
487
206
  if (pre) {
488
207
  balances.push({
489
- transactionIndex,
208
+ transactionIndex: ctx.transactionIndex,
490
209
  account,
491
210
 
492
211
  preProgramId: pre.programId ?? undefined,
@@ -503,7 +222,7 @@ function mapTokenBalances(
503
222
  })
504
223
  } else {
505
224
  balances.push({
506
- transactionIndex,
225
+ transactionIndex: ctx.transactionIndex,
507
226
  account,
508
227
  postProgramId: post.programId ?? undefined,
509
228
  postMint: post.mint,
@@ -517,7 +236,7 @@ function mapTokenBalances(
517
236
  for (let [account, pre] of preBalances.entries()) {
518
237
  if (postBalances.has(account)) continue
519
238
  balances.push({
520
- transactionIndex,
239
+ transactionIndex: ctx.transactionIndex,
521
240
  account,
522
241
  preProgramId: pre.programId ?? undefined,
523
242
  preMint: pre.mint,
@@ -534,8 +253,4 @@ function mapTokenBalances(
534
253
  })
535
254
 
536
255
  return balances
537
- }
538
-
539
- function isIntructionError(err: unknown): err is {InstructionError: [number, string]} {
540
- return typeof err == 'object' && err != null && 'InstructionError' in err
541
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
+ }