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