forked from shardeum/shardeum
-
Notifications
You must be signed in to change notification settings - Fork 0
/
failedTxCheck.ts
224 lines (192 loc) · 7.99 KB
/
failedTxCheck.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/**
* This script filters failed transactions from the specified database based on account types and cycle numbers.
*
* Usage:
* npx ts-node scripts/failedTxCheck.ts [dbPath] [options]
*
* Arguments:
* dbPath: Optional. Path to the database directory. Default is 'instances/archiver-db-4000'.
*
* Options:
* --excludeAccount, -e: Exclude specific account types from the check.
* Can specify multiple types: -e 12 10 or -e 12 -e 10
* --range, -r: Specify cycle range for filtering.
* Format: -r startCycle [endCycle]
* If only startCycle is provided, it filters from that cycle onwards.
* --color: Enable ANSI color output.
*
* Examples:
* npx ts-node scripts/failedTxCheck.ts
* npx ts-node scripts/failedTxCheck.ts path/to/custom/db
* npx ts-node scripts/failedTxCheck.ts -e 12 10 -r 1000 2000
* npx ts-node scripts/failedTxCheck.ts -r 5000 // All cycles from 5000 onwards
* npx ts-node scripts/failedTxCheck.ts --color
*
* Output:
* The script generates a report file named 'failed_transactions_report_[timestamp].txt' in the root directory.
*/
import sqlite3 from 'sqlite3'
import path from 'path'
import fs from 'fs'
const defaultDbPath = 'instances/archiver-db-4000'
let dbPath = defaultDbPath
let excludedAccountTypes: number[] = []
let useAnsiColors = false
let cycleRange: number[] = []
// Account Type mapping
const AccountType = {
0: "Account",
1: "ContractStorage",
2: "ContractCode",
3: "Receipt",
4: "Debug",
5: "NetworkAccount",
6: "NodeAccount",
7: "NodeRewardReceipt",
8: "DevAccount",
9: "NodeAccount2",
10: "StakeReceipt",
11: "UnstakeReceipt",
12: "InternalTxReceipt",
}
// Parse command line arguments
for (let i = 2; i < process.argv.length; i++) {
if (process.argv[i] === '--excludeAccount' || process.argv[i] === '-e') {
i++
while (i < process.argv.length && !process.argv[i].startsWith('-')) {
const accountTypes = process.argv[i].split(' ').map(num => parseInt(num, 10))
excludedAccountTypes.push(...accountTypes)
i++
}
i--
} else if (process.argv[i] === '--range' || process.argv[i] === '-r') {
i++
cycleRange = process.argv[i].split(' ').map(num => parseInt(num, 10))
if (cycleRange.length > 2 || cycleRange.some(isNaN)) {
console.error('Invalid range format. Use: --range startCycle [endCycle]')
process.exit(1)
}
} else if (process.argv[i] === '--color') {
useAnsiColors = true
} else if (!process.argv[i].startsWith('-')) {
dbPath = process.argv[i]
}
}
// Remove any NaN values that might have been introduced
excludedAccountTypes = excludedAccountTypes.filter(num => !isNaN(num))
const excludedAccountTypeNames = excludedAccountTypes.map(type => `${type} (${AccountType[type] || 'Unknown'})`)
const dbFilePath = path.join(dbPath, 'archiverdb-4000.sqlite3')
console.log(`Attempting to open database at: ${dbFilePath}`)
console.log(`Excluding AccountTypes: ${excludedAccountTypeNames.join(', ')}`)
console.log(`Cycle range: ${cycleRange.length === 1 ? `${cycleRange[0]} onwards` : cycleRange.length === 2 ? `${cycleRange[0]} to ${cycleRange[1]}` : 'All cycles'}`)
if (!fs.existsSync(dbFilePath)) {
console.error(`Database file does not exist at path: ${dbFilePath}`)
process.exit(1)
}
// ANSI color codes
const colors = {
reset: (): string => useAnsiColors ? "\x1b[0m" : "",
bright: (): string => useAnsiColors ? "\x1b[1m" : "",
red: (): string => useAnsiColors ? "\x1b[31m" : "",
green: (): string => useAnsiColors ? "\x1b[32m" : "",
yellow: (): string => useAnsiColors ? "\x1b[33m" : "",
blue: (): string => useAnsiColors ? "\x1b[34m" : "",
magenta: (): string => useAnsiColors ? "\x1b[35m" : "",
cyan: (): string => useAnsiColors ? "\x1b[36m" : "",
}
function log(message: string, fileStream: fs.WriteStream): void {
console.log(message)
fileStream.write(message + '\n')
}
function getDB(dbPath: string): sqlite3.Database {
return new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.error('Error opening database: ', err.message)
console.error('Full error object:', err)
process.exit(1)
} else {
console.log('Successfully opened the database.')
}
})
}
function runQuery(db: sqlite3.Database, query: string, params: any[] = []): Promise<any[]> {
return new Promise((resolve, reject) => {
db.all(query, params, (err, rows) => {
if (err) reject(err)
else resolve(rows)
})
})
}
async function filterFailedTransactions(): Promise<void> {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const outputPath = path.join(process.cwd(), `failed_transactions_report_${timestamp}.txt`)
const fileStream = fs.createWriteStream(outputPath, { flags: 'w' })
const db = getDB(dbFilePath)
let queryString = `
SELECT txId, data, timestamp, cycleNumber
FROM transactions
WHERE JSON_EXTRACT(data, '$.readableReceipt.status') = 0
`
if (cycleRange.length > 0) {
if (cycleRange.length === 1) {
queryString += ` AND cycleNumber >= ${cycleRange[0]}`
} else {
queryString += ` AND cycleNumber >= ${cycleRange[0]} AND cycleNumber <= ${cycleRange[1]}`
}
}
queryString += ' ORDER BY cycleNumber, timestamp'
const transactions = await runQuery(db, queryString)
const failedTxsByAccount = new Map<string, any[]>()
for (const tx of transactions) {
const txData = JSON.parse(tx.data)
// Skip excluded AccountTypes
if (excludedAccountTypes.includes(txData.accountType)) {
continue
}
const fromAddress = txData.txFrom || txData.readableReceipt?.from
const toAddress = txData.txTo || txData.readableReceipt?.to
const nonce = parseInt(txData.readableReceipt?.nonce || '0', 16)
const accountType = txData.accountType
const reason = txData.readableReceipt?.reason || 'Unknown reason'
if (fromAddress) {
if (!failedTxsByAccount.has(fromAddress)) {
failedTxsByAccount.set(fromAddress, [])
}
failedTxsByAccount.get(fromAddress)!.push({
txId: tx.txId,
toAddress,
nonce,
accountType,
reason,
cycleNumber: tx.cycleNumber,
timestamp: tx.timestamp
})
}
}
let totalFailedTransactions = 0
log('Failed Transactions by Account:', fileStream)
for (const [fromAddress, txs] of failedTxsByAccount.entries()) {
log(`\n${colors.bright()}${colors.cyan()}From Address: ${fromAddress}${colors.reset()}`, fileStream)
log(`${colors.bright()}Number of failed transactions: ${txs.length}${colors.reset()}`, fileStream)
txs.forEach(({ txId, toAddress, nonce, accountType, reason, cycleNumber, timestamp }) => {
const accountTypeStr = AccountType[accountType] || 'Unknown'
const date = new Date(timestamp)
const formattedDate = `${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
const isoDate = date.toISOString()
log(` ${colors.bright()}TxID: ${txId}${colors.reset()}`, fileStream)
log(` To: ${toAddress}`, fileStream)
log(` Nonce: ${nonce}`, fileStream)
log(` Account Type: ${colors.yellow()}${accountTypeStr}${colors.reset()}`, fileStream)
log(` Cycle Number: ${colors.magenta()}${cycleNumber}${colors.reset()}`, fileStream)
log(` Timestamp: ${colors.blue()}${formattedDate} (${isoDate})${colors.reset()}`, fileStream)
log(` ${colors.red()}Failure Reason: ${reason}${colors.reset()}`, fileStream)
})
totalFailedTransactions += txs.length
}
log(`\n${colors.bright()}${colors.blue()}Total failed transactions: ${totalFailedTransactions}${colors.reset()}`, fileStream)
log(`${colors.bright()}${colors.blue()}Total accounts with failed transactions: ${failedTxsByAccount.size}${colors.reset()}`, fileStream)
db.close()
fileStream.end()
console.log(`\nReport saved to: ${outputPath}`)
}
filterFailedTransactions().catch(console.error)