@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,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TransactionContext = void 0;
|
|
7
|
+
const assert_1 = __importDefault(require("assert"));
|
|
8
|
+
class TransactionContext {
|
|
9
|
+
constructor(transactionIndex, tx, journal) {
|
|
10
|
+
this.transactionIndex = transactionIndex;
|
|
11
|
+
this.tx = tx;
|
|
12
|
+
this.journal = journal;
|
|
13
|
+
if (tx.version == 'legacy') {
|
|
14
|
+
this.accounts = tx.transaction.message.accountKeys;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this.accounts = tx.transaction.message.accountKeys.concat(tx.meta.loadedAddresses?.writable ?? [], tx.meta.loadedAddresses?.readonly ?? []);
|
|
18
|
+
}
|
|
19
|
+
let err = this.tx.meta.err;
|
|
20
|
+
if (err && 'InstructionError' in err) {
|
|
21
|
+
let pos = err.InstructionError?.[0];
|
|
22
|
+
let type = err.InstructionError?.[1];
|
|
23
|
+
if (Number.isSafeInteger(pos)) {
|
|
24
|
+
this.erroredInstruction = pos;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
this.erroredInstruction = tx.transaction.message.instructions.length;
|
|
28
|
+
this.warn({ transactionError: err }, 'got InstructionError of unrecognized shape');
|
|
29
|
+
}
|
|
30
|
+
this.exceededCallDepth = type === 'CallDepth';
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
this.erroredInstruction = tx.transaction.message.instructions.length;
|
|
34
|
+
this.exceededCallDepth = false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
get isCommitted() {
|
|
38
|
+
return this.tx.meta.err == null;
|
|
39
|
+
}
|
|
40
|
+
get transactionHash() {
|
|
41
|
+
return this.tx.transaction.signatures[0];
|
|
42
|
+
}
|
|
43
|
+
getAccount(index) {
|
|
44
|
+
(0, assert_1.default)(index < this.accounts.length);
|
|
45
|
+
return this.accounts[index];
|
|
46
|
+
}
|
|
47
|
+
warn(props, msg) {
|
|
48
|
+
this.journal.warn({
|
|
49
|
+
transactionHash: this.transactionHash,
|
|
50
|
+
transactionIndex: this.transactionIndex,
|
|
51
|
+
...props
|
|
52
|
+
}, msg);
|
|
53
|
+
}
|
|
54
|
+
error(props, msg) {
|
|
55
|
+
this.journal.error({
|
|
56
|
+
transactionHash: this.transactionHash,
|
|
57
|
+
transactionIndex: this.transactionIndex,
|
|
58
|
+
...props
|
|
59
|
+
}, msg);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.TransactionContext = TransactionContext;
|
|
63
|
+
//# sourceMappingURL=transaction-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-context.js","sourceRoot":"","sources":["../src/transaction-context.ts"],"names":[],"mappings":";;;;;;AAEA,oDAA2B;AAS3B,MAAa,kBAAkB;IAM3B,YACoB,gBAAwB,EACxB,EAAmB,EAC3B,OAAgB;QAFR,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,OAAE,GAAF,EAAE,CAAiB;QAC3B,YAAO,GAAP,OAAO,CAAS;QAExB,IAAI,EAAE,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,CAAA;QACtD,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CACrD,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,EAAE,EACvC,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,EAAE,CAC1C,CAAA;QACL,CAAC;QAED,IAAI,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAA;QAC1B,IAAI,GAAG,IAAI,kBAAkB,IAAI,GAAG,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAA;YACnC,IAAI,IAAI,GAAG,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAA;YACpC,IAAI,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,kBAAkB,GAAG,GAAG,CAAA;YACjC,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAA;gBACpE,IAAI,CAAC,IAAI,CAAC,EAAC,gBAAgB,EAAE,GAAG,EAAC,EAAE,4CAA4C,CAAC,CAAA;YACpF,CAAC;YACD,IAAI,CAAC,iBAAiB,GAAG,IAAI,KAAK,WAAW,CAAA;QACjD,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAA;YACpE,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAA;QAClC,CAAC;IACL,CAAC;IAED,IAAI,WAAW;QACX,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAA;IACnC,CAAC;IAED,IAAI,eAAe;QACf,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC;IAED,UAAU,CAAC,KAAa;QACpB,IAAA,gBAAM,EAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED,IAAI,CAAC,KAAU,EAAE,GAAW;QACxB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACd,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,GAAG,KAAK;SACX,EAAE,GAAG,CAAC,CAAA;IACX,CAAC;IAED,KAAK,CAAC,KAAU,EAAE,GAAW;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YACf,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,GAAG,KAAK;SACX,EAAE,GAAG,CAAC,CAAA;IACX,CAAC;CACJ;AAjED,gDAiEC"}
|
package/lib/votes.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"votes.d.ts","sourceRoot":"","sources":["../src/votes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,QAAQ,CAAA;AAG5B,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAkB9C"}
|
package/lib/votes.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeVotes = removeVotes;
|
|
4
|
+
function removeVotes(block) {
|
|
5
|
+
let removed = new Set();
|
|
6
|
+
for (let i of block.instructions) {
|
|
7
|
+
if (i.programId == 'Vote111111111111111111111111111111111111111') {
|
|
8
|
+
removed.add(i.transactionIndex);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function kept(item) {
|
|
12
|
+
return !removed.has(item.transactionIndex);
|
|
13
|
+
}
|
|
14
|
+
block.transactions = block.transactions.filter(kept);
|
|
15
|
+
block.instructions = block.instructions.filter(kept);
|
|
16
|
+
block.logs = block.logs.filter(kept);
|
|
17
|
+
block.balances = block.balances.filter(kept);
|
|
18
|
+
block.tokenBalances = block.tokenBalances.filter(kept);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=votes.js.map
|
package/lib/votes.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"votes.js","sourceRoot":"","sources":["../src/votes.ts"],"names":[],"mappings":";;AAGA,kCAkBC;AAlBD,SAAgB,WAAW,CAAC,KAAY;IACpC,IAAI,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IAE/B,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,SAAS,IAAI,6CAA6C,EAAE,CAAC;YAC/D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;QACnC,CAAC;IACL,CAAC;IAED,SAAS,IAAI,CAAC,IAAgC;QAC1C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC5C,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AAC1D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subsquid/solana-normalization",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "1.0.0-portal-api.d887ad",
|
|
4
4
|
"description": "Solana data model",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"repository": "git@github.com:subsquid/squid.git",
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
],
|
|
14
14
|
"main": "lib/index.js",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@subsquid/solana-rpc-data": "
|
|
17
|
-
"@subsquid/util-internal": "
|
|
16
|
+
"@subsquid/solana-rpc-data": "1.0.0-portal-api.d887ad",
|
|
17
|
+
"@subsquid/util-internal": "3.3.0-portal-api.d887ad",
|
|
18
18
|
"@subsquid/util-internal-validation": "^0.7.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
+
"@subsquid/util-internal-json": "^1.2.3",
|
|
22
|
+
"@subsquid/util-internal-json-fix-unsafe-integers": "^0.0.0",
|
|
21
23
|
"@types/node": "^18.18.14",
|
|
22
|
-
"typescript": "
|
|
24
|
+
"typescript": "5.5.4"
|
|
23
25
|
},
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "rm -rf lib && tsc"
|
package/src/archive.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {Base58Bytes} from '@subsquid/solana-rpc-data'
|
|
2
|
+
import * as norm from './data'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Block account index
|
|
7
|
+
*/
|
|
8
|
+
export type AccountIndex = number
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface Block {
|
|
12
|
+
header: BlockHeader
|
|
13
|
+
accounts: Base58Bytes[]
|
|
14
|
+
transactions: Transaction[]
|
|
15
|
+
instructions: Instruction[]
|
|
16
|
+
logs: LogMessage[]
|
|
17
|
+
balances: Balance[]
|
|
18
|
+
tokenBalances: TokenBalance[]
|
|
19
|
+
rewards: Reward[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export interface BlockHeader {
|
|
24
|
+
number: number
|
|
25
|
+
hash: string
|
|
26
|
+
parentNumber: number
|
|
27
|
+
parentHash: string
|
|
28
|
+
height?: number
|
|
29
|
+
timestamp?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export type Transaction = Omit<norm.Transaction, 'accountKeys' | 'addressTableLookups' | 'loadedAddresses'> & {
|
|
34
|
+
accountKeys: AccountIndex[]
|
|
35
|
+
addressTableLookups: AddressTableLookup[]
|
|
36
|
+
loadedAddresses: {
|
|
37
|
+
readonly: AccountIndex[]
|
|
38
|
+
writable: AccountIndex[]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
export interface AddressTableLookup {
|
|
44
|
+
accountKey: AccountIndex
|
|
45
|
+
readonlyIndexes: number[]
|
|
46
|
+
writableIndexes: number[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export type Instruction = Omit<norm.Instruction, 'programId' | 'accounts'> & {
|
|
51
|
+
programId: AccountIndex
|
|
52
|
+
accounts: AccountIndex[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
export type LogMessage = Omit<norm.LogMessage, 'programId'> & {
|
|
57
|
+
programId: AccountIndex
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
export type Balance = Omit<norm.Balance, 'account'> & {
|
|
62
|
+
account: AccountIndex
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export type TokenBalance = PatchAccounts<norm.TokenBalance>
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
type PatchAccounts<T> = {
|
|
70
|
+
[K in keyof T]: T[K] extends string ? AccountIndex : T[K]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export type Reward = Omit<norm.Reward, 'pubkey'> & {
|
|
75
|
+
pubkey: AccountIndex
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export function toArchiveBlock(block: norm.Block): Block {
|
|
80
|
+
let dict = new AccountDict()
|
|
81
|
+
|
|
82
|
+
let {
|
|
83
|
+
header: {number: slot, parentNumber: parentSlot, ...hdr}
|
|
84
|
+
} = block
|
|
85
|
+
|
|
86
|
+
let header = {
|
|
87
|
+
number: slot,
|
|
88
|
+
parentNumber: parentSlot,
|
|
89
|
+
...hdr
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let transactions = block.transactions.map(tx => {
|
|
93
|
+
let {accountKeys, addressTableLookups, loadedAddresses, ...props} = tx
|
|
94
|
+
return {
|
|
95
|
+
...props,
|
|
96
|
+
accountKeys: accountKeys.map(a => dict.get(a)),
|
|
97
|
+
addressTableLookups: addressTableLookups.map(lookup => {
|
|
98
|
+
return {
|
|
99
|
+
accountKey: dict.get(lookup.accountKey),
|
|
100
|
+
readonlyIndexes: lookup.readonlyIndexes,
|
|
101
|
+
writableIndexes: lookup.writableIndexes
|
|
102
|
+
}
|
|
103
|
+
}),
|
|
104
|
+
loadedAddresses: {
|
|
105
|
+
readonly: loadedAddresses.readonly.map(a => dict.get(a)),
|
|
106
|
+
writable: loadedAddresses.writable.map(a => dict.get(a))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
let instructions = block.instructions.map(ins => {
|
|
112
|
+
let {programId, accounts, ...props} = ins
|
|
113
|
+
return {
|
|
114
|
+
...props,
|
|
115
|
+
programId: dict.get(programId),
|
|
116
|
+
accounts: accounts.map(a => dict.get(a))
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
let logs = block.logs.map(rec => {
|
|
121
|
+
let {programId, ...props} = rec
|
|
122
|
+
return {
|
|
123
|
+
...props,
|
|
124
|
+
programId: dict.get(programId)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
let balances = block.balances.map(b => {
|
|
129
|
+
let {account, ...props} = b
|
|
130
|
+
return {
|
|
131
|
+
...props,
|
|
132
|
+
account: dict.get(account)
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
let tokenBalances: TokenBalance[] = block.tokenBalances.map(b => {
|
|
137
|
+
let res: any = {}
|
|
138
|
+
let key: keyof norm.TokenBalance
|
|
139
|
+
for (key in b) {
|
|
140
|
+
let val = b[key]
|
|
141
|
+
if (typeof val == 'string') {
|
|
142
|
+
res[key] = dict.get(val)
|
|
143
|
+
} else {
|
|
144
|
+
res[key] = val
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return res
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
let rewards = block.rewards.map(reward => {
|
|
151
|
+
let {pubkey, ...props} = reward
|
|
152
|
+
return {
|
|
153
|
+
pubkey: dict.get(pubkey),
|
|
154
|
+
...props
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
header,
|
|
160
|
+
accounts: dict.list(),
|
|
161
|
+
transactions,
|
|
162
|
+
instructions,
|
|
163
|
+
logs,
|
|
164
|
+
balances,
|
|
165
|
+
tokenBalances,
|
|
166
|
+
rewards
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class AccountDict {
|
|
172
|
+
private map = new Map<Base58Bytes, AccountIndex>
|
|
173
|
+
|
|
174
|
+
get(account: Base58Bytes): AccountIndex {
|
|
175
|
+
let idx = this.map.get(account)
|
|
176
|
+
if (idx == null) {
|
|
177
|
+
idx = this.map.size
|
|
178
|
+
this.map.set(account, idx)
|
|
179
|
+
}
|
|
180
|
+
return idx
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
list(): Base58Bytes[] {
|
|
184
|
+
return Array.from(this.map.keys())
|
|
185
|
+
}
|
|
186
|
+
}
|
package/src/data.ts
CHANGED
|
@@ -2,10 +2,10 @@ import type {Base58Bytes} from '@subsquid/solana-rpc-data'
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
export interface BlockHeader {
|
|
5
|
+
number: number
|
|
5
6
|
hash: Base58Bytes
|
|
6
7
|
height: number
|
|
7
|
-
|
|
8
|
-
parentSlot: number
|
|
8
|
+
parentNumber: number
|
|
9
9
|
parentHash: Base58Bytes
|
|
10
10
|
timestamp: number
|
|
11
11
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import * as rpc from '@subsquid/solana-rpc-data'
|
|
2
|
+
import {unexpectedCase} from '@subsquid/util-internal'
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import {Instruction, LogMessage} from './data'
|
|
5
|
+
import {Message, parseLogMessage} from './log-parser'
|
|
6
|
+
import {TransactionContext} from './transaction-context'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const PROGRAMS_MISSING_INVOKE_LOG = new Set([
|
|
10
|
+
'AddressLookupTab1e1111111111111111111111111',
|
|
11
|
+
'BPFLoader1111111111111111111111111111111111',
|
|
12
|
+
'BPFLoader2111111111111111111111111111111111',
|
|
13
|
+
'BPFLoaderUpgradeab1e11111111111111111111111',
|
|
14
|
+
'Ed25519SigVerify111111111111111111111111111',
|
|
15
|
+
'KeccakSecp256k11111111111111111111111111111',
|
|
16
|
+
'NativeLoader1111111111111111111111111111111',
|
|
17
|
+
'ZkTokenProof1111111111111111111111111111111'
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export class ParsingError {
|
|
22
|
+
instructionIndex?: number
|
|
23
|
+
innerInstructionIndex?: number
|
|
24
|
+
logMessageIndex?: number
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
public msg: string
|
|
28
|
+
) {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export class MessageStream {
|
|
34
|
+
private messages: Message[]
|
|
35
|
+
private pos = 0
|
|
36
|
+
|
|
37
|
+
constructor(messages: string[]) {
|
|
38
|
+
this.messages = messages.map(parseLogMessage)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get unfinished(): boolean {
|
|
42
|
+
return !this.ended
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get ended(): boolean {
|
|
46
|
+
return this.pos >= this.messages.length || this.messages[this.pos].kind == 'truncate'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get truncated(): boolean {
|
|
50
|
+
return this.pos < this.messages.length && this.messages[this.pos].kind == 'truncate'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get current(): Message {
|
|
54
|
+
assert(this.pos < this.messages.length, 'eof reached')
|
|
55
|
+
return this.messages[this.pos]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get position(): number {
|
|
59
|
+
return this.pos
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
advance(): boolean {
|
|
63
|
+
if (this.truncated) return false
|
|
64
|
+
this.pos = Math.min(this.pos + 1, this.messages.length)
|
|
65
|
+
return this.pos < this.messages.length
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
export class InstructionTreeTraversal {
|
|
71
|
+
private lastAddress: number[]
|
|
72
|
+
private instructions: rpc.Instruction[]
|
|
73
|
+
private pos = 0
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
private tx: TransactionContext,
|
|
77
|
+
private messages: MessageStream,
|
|
78
|
+
private instructionIndex: number,
|
|
79
|
+
instruction: rpc.Instruction,
|
|
80
|
+
inner: rpc.Instruction[],
|
|
81
|
+
private output: Instruction[],
|
|
82
|
+
private log: LogMessage[]
|
|
83
|
+
) {
|
|
84
|
+
this.lastAddress = [instructionIndex - 1]
|
|
85
|
+
this.instructions = [instruction, ...inner]
|
|
86
|
+
if (this.tx.erroredInstruction >= this.instructionIndex) {
|
|
87
|
+
this.call(1)
|
|
88
|
+
this.finishLogLess()
|
|
89
|
+
} else {
|
|
90
|
+
this.assert(
|
|
91
|
+
this.instructions.length === 1,
|
|
92
|
+
'failed instructions should not have inner calls'
|
|
93
|
+
)
|
|
94
|
+
this.push(1)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private call(stackHeight: number): void {
|
|
99
|
+
let ins = this.current
|
|
100
|
+
|
|
101
|
+
this.assert(
|
|
102
|
+
ins.stackHeight == null || ins.stackHeight === stackHeight,
|
|
103
|
+
'stack height mismatch',
|
|
104
|
+
this.pos
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
let programId = this.tx.getAccount(ins.programIdIndex)
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
this.messages.unfinished &&
|
|
111
|
+
this.messages.current.kind === 'invoke' &&
|
|
112
|
+
this.messages.current.programId === programId
|
|
113
|
+
) {
|
|
114
|
+
this.assert(
|
|
115
|
+
this.messages.current.stackHeight === stackHeight,
|
|
116
|
+
'invoke message has unexpected stack height',
|
|
117
|
+
this.pos,
|
|
118
|
+
this.messages.position
|
|
119
|
+
)
|
|
120
|
+
this.messages.advance()
|
|
121
|
+
this.invoke(stackHeight)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.messages.truncated) {
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (PROGRAMS_MISSING_INVOKE_LOG.has(programId)) {
|
|
130
|
+
this.push(stackHeight).hasDroppedLogMessages = true
|
|
131
|
+
this.dropNonInvokeMessages()
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.messages.ended) {
|
|
136
|
+
throw this.error('unexpected end of message log', this.pos)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
throw this.error('missing invoke message', this.pos, this.messages.position)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private invoke(stackHeight: number): void {
|
|
143
|
+
let pos = this.pos
|
|
144
|
+
let ins = this.push(stackHeight)
|
|
145
|
+
|
|
146
|
+
this.takeInstructionMessages(ins, pos)
|
|
147
|
+
|
|
148
|
+
if (this.messages.truncated) {
|
|
149
|
+
ins.hasDroppedLogMessages = true
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this.messages.ended) {
|
|
154
|
+
throw this.error('unexpected end of log', pos)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let result = this.messages.current
|
|
158
|
+
assert(result.kind === 'invoke-result')
|
|
159
|
+
this.assert(
|
|
160
|
+
result.programId === ins.programId,
|
|
161
|
+
'invoke result message and instruction program ids don\'t match'
|
|
162
|
+
)
|
|
163
|
+
ins.error = result.error
|
|
164
|
+
this.messages.advance()
|
|
165
|
+
|
|
166
|
+
// consume invoke-less subcalls,
|
|
167
|
+
// that might have left unvisited due to missing 'invoke' messages
|
|
168
|
+
this.eatInvokeLessSubCalls(ins)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private eatInvokeLessSubCalls(parent: Instruction) {
|
|
172
|
+
while (this.unfinished) {
|
|
173
|
+
let stackHeight: number
|
|
174
|
+
if (this.current.stackHeight == null) {
|
|
175
|
+
if (parent.error) {
|
|
176
|
+
// all remaining calls must belong to the given parent
|
|
177
|
+
stackHeight = parent.instructionAddress.length + 1
|
|
178
|
+
} else {
|
|
179
|
+
stackHeight = 2
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
stackHeight = this.current.stackHeight
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (stackHeight <= parent.instructionAddress.length) return
|
|
186
|
+
|
|
187
|
+
let pos = this.pos
|
|
188
|
+
let ins = this.push(stackHeight)
|
|
189
|
+
|
|
190
|
+
// even if we have some messages emitted,
|
|
191
|
+
// we already assigned them to parent call
|
|
192
|
+
ins.hasDroppedLogMessages = true
|
|
193
|
+
|
|
194
|
+
if (PROGRAMS_MISSING_INVOKE_LOG.has(ins.programId)) {
|
|
195
|
+
// all good, it is expected to not have 'invoke' message
|
|
196
|
+
} else if (
|
|
197
|
+
this.tx.exceededCallDepth &&
|
|
198
|
+
this.tx.erroredInstruction == this.instructionIndex &&
|
|
199
|
+
this.ended
|
|
200
|
+
) {
|
|
201
|
+
// we've reached the max stack depth,
|
|
202
|
+
// there will be no invoke message either
|
|
203
|
+
} else {
|
|
204
|
+
this.tx.error({
|
|
205
|
+
instruction: this.instructionIndex,
|
|
206
|
+
innerInstruction: pos - 1
|
|
207
|
+
}, 'missing invoke message for inner instruction')
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private takeInstructionMessages(ins: Instruction, pos: number): void {
|
|
213
|
+
while (this.messages.unfinished) {
|
|
214
|
+
let msg = this.messages.current
|
|
215
|
+
switch(msg.kind) {
|
|
216
|
+
case 'log':
|
|
217
|
+
case 'data':
|
|
218
|
+
case 'other':
|
|
219
|
+
this.log.push({
|
|
220
|
+
transactionIndex: this.tx.transactionIndex,
|
|
221
|
+
logIndex: this.messages.position,
|
|
222
|
+
instructionAddress: ins.instructionAddress,
|
|
223
|
+
programId: ins.programId,
|
|
224
|
+
kind: msg.kind,
|
|
225
|
+
message: msg.message
|
|
226
|
+
})
|
|
227
|
+
this.messages.advance()
|
|
228
|
+
break
|
|
229
|
+
case 'cu':
|
|
230
|
+
if (ins.programId == msg.programId) {
|
|
231
|
+
ins.computeUnitsConsumed = msg.consumed
|
|
232
|
+
this.messages.advance()
|
|
233
|
+
} else {
|
|
234
|
+
throw this.error(
|
|
235
|
+
'unexpected programId in compute unit message',
|
|
236
|
+
pos,
|
|
237
|
+
this.messages.position
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
break
|
|
241
|
+
case 'invoke':
|
|
242
|
+
this.call(ins.instructionAddress.length + 1)
|
|
243
|
+
break
|
|
244
|
+
case 'invoke-result':
|
|
245
|
+
case 'truncate':
|
|
246
|
+
return
|
|
247
|
+
default:
|
|
248
|
+
throw unexpectedCase()
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private dropNonInvokeMessages(): void {
|
|
254
|
+
while (this.messages.unfinished) {
|
|
255
|
+
let msg = this.messages.current
|
|
256
|
+
switch(msg.kind) {
|
|
257
|
+
case 'log':
|
|
258
|
+
case 'data':
|
|
259
|
+
case 'cu':
|
|
260
|
+
case 'other':
|
|
261
|
+
this.messages.advance()
|
|
262
|
+
break
|
|
263
|
+
case 'invoke':
|
|
264
|
+
case 'invoke-result':
|
|
265
|
+
case 'truncate':
|
|
266
|
+
return
|
|
267
|
+
default:
|
|
268
|
+
throw unexpectedCase()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private finishLogLess(): void {
|
|
274
|
+
while (this.unfinished) {
|
|
275
|
+
this.push(this.current.stackHeight ?? 2).hasDroppedLogMessages = true
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private get current(): rpc.Instruction {
|
|
280
|
+
assert(this.pos < this.instructions.length)
|
|
281
|
+
return this.instructions[this.pos]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private get unfinished(): boolean {
|
|
285
|
+
return this.pos < this.instructions.length
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private get ended(): boolean {
|
|
289
|
+
return !this.unfinished
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private assert(ok: any, msg: string, pos?: number, messagePos?: number): void {
|
|
293
|
+
if (!ok) throw this.error(msg, pos, messagePos)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private error(msg: string, pos?: number, messagePos?: number): ParsingError {
|
|
297
|
+
let err = new ParsingError(msg)
|
|
298
|
+
err.instructionIndex = this.instructionIndex
|
|
299
|
+
if (pos) {
|
|
300
|
+
err.innerInstructionIndex = pos - 1
|
|
301
|
+
}
|
|
302
|
+
if (messagePos != null) {
|
|
303
|
+
err.logMessageIndex = messagePos
|
|
304
|
+
}
|
|
305
|
+
return err
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private push(stackHeight: number): Instruction {
|
|
309
|
+
assert(stackHeight > 0)
|
|
310
|
+
|
|
311
|
+
let ins = this.current
|
|
312
|
+
if (ins.stackHeight != null) {
|
|
313
|
+
assert(stackHeight === ins.stackHeight)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let address = this.lastAddress.slice()
|
|
317
|
+
|
|
318
|
+
while (address.length > stackHeight) {
|
|
319
|
+
address.pop()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (address.length === stackHeight) {
|
|
323
|
+
address[stackHeight - 1] += 1
|
|
324
|
+
} else {
|
|
325
|
+
assert(address.length + 1 == stackHeight)
|
|
326
|
+
address[stackHeight - 1] = 0
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let mapped: Instruction = {
|
|
330
|
+
transactionIndex: this.tx.transactionIndex,
|
|
331
|
+
instructionAddress: address,
|
|
332
|
+
programId: this.tx.getAccount(ins.programIdIndex),
|
|
333
|
+
accounts: ins.accounts.map(a => this.tx.getAccount(a)),
|
|
334
|
+
data: ins.data,
|
|
335
|
+
isCommitted: this.tx.isCommitted,
|
|
336
|
+
hasDroppedLogMessages: false
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
this.output.push(mapped)
|
|
340
|
+
this.lastAddress = address
|
|
341
|
+
this.pos = Math.min(this.pos + 1, this.instructions.length)
|
|
342
|
+
return mapped
|
|
343
|
+
}
|
|
344
|
+
}
|