-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
133 lines (112 loc) · 4.44 KB
/
index.js
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
const fs = require('fs')
const path = require('path')
const mkdirp = require('mkdirp')
const chalk = require('chalk')
const ethers = require('ethers')
const filter = require('lodash.filter')
const reduce = require('lodash.reduce')
const uniq = require('lodash.uniq')
const compact = require('lodash.compact')
const { contractVerified, saveVerifiedCache } = require('./polygonscan')
const START_BLOCK = parseInt(process.env.START_BLOCK, 10)
const END_BLOCK = parseInt(process.env.END_BLOCK, 10)
const TOTAL_BLOCKS = END_BLOCK - START_BLOCK
const SPAM_CUTOFF = 8
const OUTPUT_PATH = path.join(__dirname, `./out/${START_BLOCK}-${END_BLOCK}/`)
mkdirp.sync(OUTPUT_PATH)
const OK_LIST = [
'0x6e5Fa679211d7F6b54e14E187D34bA547c5d3fe0' // sunflower minter
]
async function main () {
const provider = new ethers.providers.WebSocketProvider(process.env.POLYGON_RPC_WS)
const spamRates = []
const totalSpamCounts = []
const eoas = {}
const blocks = {}
async function fetchBlocks (blockNumbers) {
for (const blockNumber of blockNumbers) {
if (blocks[blockNumber]) continue
blocks[blockNumber] = await provider.getBlockWithTransactions(blockNumber)
}
}
async function removeBlocks (minBlockNumberToKeep) {
for (const k of Object.keys(blocks)) {
if (k < minBlockNumberToKeep) {
delete blocks[k]
}
}
}
for (let i = START_BLOCK; i < END_BLOCK; i++) {
console.log(chalk.blue.underline(`${i - START_BLOCK + 1}/${TOTAL_BLOCKS} Checking block ${i}`))
let total = 0
let spam = 0
const spammersInBlock = {}
// get block with all transactions
await fetchBlocks([i - 1, i, i + 1])
// find unique "to" addresses
const txTos = compact(blocks[i].transactions.map(tx => tx.to))
const txsForThisBlockPrevBlockAndNextBlock = [
...blocks[i - 1].transactions,
...blocks[i].transactions,
...blocks[i + 1].transactions
]
// for each "to" address, determine whether it is spam or not
for (const address of uniq(txTos)) {
const aboveCutoff = filter(txsForThisBlockPrevBlockAndNextBlock, t => t.to === address).length >= SPAM_CUTOFF && !OK_LIST.includes(address)
if (aboveCutoff) {
const verified = await contractVerified(address)
if (verified) continue
spammersInBlock[address] = true
console.log(chalk.red(`found block spammer ${address}`))
}
}
// for each tx in the block, we can now mark it as spam / not spam
for (const tx of blocks[i].transactions) {
if (!eoas[tx.from]) eoas[tx.from] = { address: tx.from, spamCount: 0, txCount: 0 }
eoas[tx.from].txCount++
total += 1
if (tx.to && spammersInBlock[tx.to]) {
eoas[tx.from].spamCount++
if (!totalSpamCounts[tx.to]) totalSpamCounts[tx.to] = { to: tx.to, count: 0 }
totalSpamCounts[tx.to].count += 1
spam += 1
}
}
// assemble a spamrate and save the block data
const spamRate = spam > 0 ? Math.round(spam / total * 100) : 0
spamRates.push({ block: i, rate: spamRate, spam, total })
console.log(chalk.bold(`Block ${i}, found ${spam} spam txs, ${total} total transactions. Spam rate ${spamRate}%`))
console.log('')
removeBlocks(i - 1)
}
provider._websocket.close()
const sum = reduce(spamRates, function (sum, a) { return sum + a.rate }, 0)
const avg = Math.round(sum / spamRates.length)
console.log('')
console.log(chalk.bold(`Analyzed ${spamRates.length} blocks. Average spam rate ${avg}%`))
console.log('')
console.log('')
// save the blocks data
const blocksCsv = [
'block,rate,spam,total',
...spamRates.map(s => `${s.block},${s.rate},${s.spam},${s.total}`)
].join('\n')
fs.writeFileSync(path.join(OUTPUT_PATH, 'blocks.csv'), blocksCsv, 'utf-8')
// save the spammers data
const sortedSpamCounts = Object.values(totalSpamCounts).sort((a, b) => a.count > b.count ? -1 : 1)
const spammersCsv = [
'contract,numTransactions',
...sortedSpamCounts.map(c => `${c.to},${c.count}`)
].join('\n')
fs.writeFileSync(path.join(OUTPUT_PATH, 'spammers.csv'), spammersCsv, 'utf-8')
// save the EOAs
const sortedEoas = Object.values(eoas).sort((a, b) => a.spamCount > b.spamCount ? -1 : 1)
const eoasCsv = [
'address,spamcount,txcount',
...sortedEoas.map(e => `${e.address},${e.spamCount},${e.txCount}`)
].join('\n')
fs.writeFileSync(path.join(OUTPUT_PATH, 'eoas.csv'), eoasCsv, 'utf-8')
// cache the verified contracts for next time
saveVerifiedCache()
}
main()