@subsquid/solana-normalization 0.0.3 → 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/log-parser.d.ts.map +1 -1
- package/lib/log-parser.js +2 -2
- package/lib/log-parser.js.map +1 -1
- package/lib/mapping.d.ts +3 -1
- package/lib/mapping.d.ts.map +1 -1
- package/lib/mapping.js +92 -307
- 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 -395
- 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,393 +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 {
|
|
354
|
-
throw this.error(false, pos, 'invoke message is missing')
|
|
355
|
-
}
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
110
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
pos: number,
|
|
363
|
-
cb?: (ins: Instruction, pos: number) => void
|
|
364
|
-
): number {
|
|
365
|
-
while (pos < instructions.length) {
|
|
366
|
-
let instruction = instructions[pos]
|
|
367
|
-
let stackHeight = instruction.stackHeight ?? 2
|
|
368
|
-
if (stackHeight > parentStackHeight) {
|
|
369
|
-
let ins = this.push(stackHeight, instruction)
|
|
370
|
-
cb?.(ins, pos)
|
|
371
|
-
pos += 1
|
|
372
|
-
} else {
|
|
373
|
-
return pos
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
return pos
|
|
111
|
+
this.transactions.push(mapped)
|
|
112
|
+
this.balances.push(...mapBalances(ctx))
|
|
113
|
+
this.tokenBalances.push(...mapTokenBalances(ctx))
|
|
377
114
|
}
|
|
378
115
|
|
|
379
|
-
private
|
|
380
|
-
|
|
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]
|
|
381
119
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
let address = this.lastAddress.slice()
|
|
387
|
-
|
|
388
|
-
while (address.length > stackHeight) {
|
|
389
|
-
address.pop()
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (address.length === stackHeight) {
|
|
393
|
-
address[stackHeight - 1] += 1
|
|
394
|
-
} else {
|
|
395
|
-
assert(address.length + 1 == stackHeight)
|
|
396
|
-
address[stackHeight - 1] = 0
|
|
397
|
-
}
|
|
120
|
+
let inner = ctx.tx.meta.innerInstructions?.flatMap(pack => {
|
|
121
|
+
return pack.index === i ? pack.instructions : []
|
|
122
|
+
})
|
|
398
123
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
124
|
+
new InstructionTreeTraversal(
|
|
125
|
+
ctx,
|
|
126
|
+
messages,
|
|
127
|
+
i,
|
|
128
|
+
ins,
|
|
129
|
+
inner ?? [],
|
|
130
|
+
this.instructions,
|
|
131
|
+
this.logs
|
|
132
|
+
)
|
|
407
133
|
}
|
|
408
|
-
|
|
409
|
-
this.instructions.push(i)
|
|
410
|
-
this.lastAddress = address
|
|
411
|
-
return i
|
|
412
134
|
}
|
|
135
|
+
}
|
|
413
136
|
|
|
414
|
-
private assert(ok: unknown, withMessagePos: boolean, innerPos: number, msg: string): asserts ok {
|
|
415
|
-
if (!ok) throw this.error(withMessagePos, innerPos, msg)
|
|
416
|
-
}
|
|
417
137
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|
|
429
154
|
}
|
|
430
155
|
}
|
|
431
156
|
|
|
432
157
|
|
|
433
|
-
function mapBalances(
|
|
434
|
-
getAccount: (idx: number) => Base58Bytes,
|
|
435
|
-
transactionIndex: number,
|
|
436
|
-
tx: rpc.Transaction,
|
|
437
|
-
): Balance[] {
|
|
158
|
+
function mapBalances(ctx: TransactionContext): Balance[] {
|
|
438
159
|
let balances: Balance[] = []
|
|
439
160
|
|
|
440
|
-
let pre = tx.meta.preBalances
|
|
441
|
-
let post = tx.meta.postBalances
|
|
161
|
+
let pre = ctx.tx.meta.preBalances
|
|
162
|
+
let post = ctx.tx.meta.postBalances
|
|
442
163
|
|
|
443
164
|
assert(pre.length == post.length)
|
|
444
165
|
|
|
@@ -447,8 +168,8 @@ function mapBalances(
|
|
|
447
168
|
// nothing changed, don't create an entry
|
|
448
169
|
} else {
|
|
449
170
|
balances.push({
|
|
450
|
-
transactionIndex,
|
|
451
|
-
account: getAccount(i),
|
|
171
|
+
transactionIndex: ctx.transactionIndex,
|
|
172
|
+
account: ctx.getAccount(i),
|
|
452
173
|
pre: BigInt(pre[i]),
|
|
453
174
|
post: BigInt(post[i])
|
|
454
175
|
})
|
|
@@ -465,26 +186,22 @@ function mapBalances(
|
|
|
465
186
|
}
|
|
466
187
|
|
|
467
188
|
|
|
468
|
-
function mapTokenBalances(
|
|
469
|
-
getAccount: (idx: number) => Base58Bytes,
|
|
470
|
-
transactionIndex: number,
|
|
471
|
-
tx: rpc.Transaction
|
|
472
|
-
): TokenBalance[] {
|
|
189
|
+
function mapTokenBalances(ctx: TransactionContext): TokenBalance[] {
|
|
473
190
|
let balances: TokenBalance[] = []
|
|
474
191
|
|
|
475
192
|
let preBalances = new Map(
|
|
476
|
-
tx.meta.preTokenBalances?.map(b => [getAccount(b.accountIndex), b])
|
|
193
|
+
ctx.tx.meta.preTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
|
|
477
194
|
)
|
|
478
195
|
|
|
479
196
|
let postBalances = new Map(
|
|
480
|
-
tx.meta.postTokenBalances?.map(b => [getAccount(b.accountIndex), b])
|
|
197
|
+
ctx.tx.meta.postTokenBalances?.map(b => [ctx.getAccount(b.accountIndex), b])
|
|
481
198
|
)
|
|
482
199
|
|
|
483
200
|
for (let [account, post] of postBalances.entries()) {
|
|
484
201
|
let pre = preBalances.get(account)
|
|
485
202
|
if (pre) {
|
|
486
203
|
balances.push({
|
|
487
|
-
transactionIndex,
|
|
204
|
+
transactionIndex: ctx.transactionIndex,
|
|
488
205
|
account,
|
|
489
206
|
|
|
490
207
|
preProgramId: pre.programId ?? undefined,
|
|
@@ -501,7 +218,7 @@ function mapTokenBalances(
|
|
|
501
218
|
})
|
|
502
219
|
} else {
|
|
503
220
|
balances.push({
|
|
504
|
-
transactionIndex,
|
|
221
|
+
transactionIndex: ctx.transactionIndex,
|
|
505
222
|
account,
|
|
506
223
|
postProgramId: post.programId ?? undefined,
|
|
507
224
|
postMint: post.mint,
|
|
@@ -515,7 +232,7 @@ function mapTokenBalances(
|
|
|
515
232
|
for (let [account, pre] of preBalances.entries()) {
|
|
516
233
|
if (postBalances.has(account)) continue
|
|
517
234
|
balances.push({
|
|
518
|
-
transactionIndex,
|
|
235
|
+
transactionIndex: ctx.transactionIndex,
|
|
519
236
|
account,
|
|
520
237
|
preProgramId: pre.programId ?? undefined,
|
|
521
238
|
preMint: pre.mint,
|