-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
345 lines (306 loc) · 12 KB
/
app.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
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
const dsteem = require('dsteem')
const fs = require('fs')
const config = JSON.parse(fs.readFileSync('config.json'))
var nodes = config.nodes
const chalk = require('chalk')
const blacklist = config.blacklist
nodes.filter((x) => blacklist.indexOf(x) == -1)
const steemtrxfinder = require('steemtrxfinder')
const sm_pub = 'STM7yk3tav5BFEyppNzHhKaXsMTPw8xYX1B1gWXq6bvtT34uVUKbQ'
var mongoUtil = require('./database')
const sbt_url = 'https://steembottracker.net'
const axios = require('axios')
const utils = dsteem.cryptoUtils
var response
var bidbots
var clients = []
var pendingVotes = []
var vote_sellers = []
var ignore_list = []
var corrupted = []
var postURL = ''
nodes.forEach((node) => {
clients.push(new dsteem.Client('https://' + node))
})
function wait (seconds) {
return new Promise((resolve, reject) => {
setTimeout(() => {resolve()}, seconds * 1000)
})
}
function timeout (seconds, mode, address, vote) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (mode == 'compute') {
return reject(vote)
} else if (mode == 'rpc_node') {
return reject(address)
}
}, seconds * 1000)
})
}
function reflect(promise){
return promise.then(function(v){ return {v:v, status: "fulfilled" }},
function(e){ return {e:e, status: "rejected" }});
}
function loadClients () {
return new Promise(async (resolve, reject) => {
var promises = []
var bad_clients = []
for (let i = 0; i < clients.length; i++) {
let client = clients[i]
console.log(client.address)
promises.push(reflect(Promise.race([client.database.call('get_account_history', ['smartsteem', -1, 50]), timeout(3, 'rpc_node', client.address)])))
}
let result = await Promise.all(promises)
bad_clients = result.filter((x) => x.status == 'rejected').map((x) => x.e)
clients = clients.filter((x) => bad_clients.indexOf(x.address) == -1)
resolve()
})
}
async function start (postURL) {
mongoUtil.connectDB(async (err) => {
if (err) throw err
const db = mongoUtil.getDB()
const dbase = db.db('steemium')
const smartsteem = dbase.collection('smartsteem')
var author = postURL.substring(postURL.lastIndexOf('@') + 1, postURL.lastIndexOf('/'))
var permlink = postURL.substr(postURL.lastIndexOf('/') + 1)
var campaign
try {
campaign = await campaigns.find({'postURL': {$regex : permlink }}).toArray()
let ts = Date.parse(campaign[0].ts)
console.log('campaign found')
postURL = campaign[0].postURL
} catch(e) {
console.log('not a steemium campaign')
}
await loadClients(5)
console.log('Number of rpc node connections: ' + clients.length)
let registry = await smartsteem.find({'get_account_history': {$exists: true}}).toArray()
if (!registry[0]) {
let lastVoteSellers = await fetchLastVoteSellers()
await smartsteem.insertOne(
{'get_account_history': lastVoteSellers, createdDate: new Date(), lastUpdate: new Date()}
)
votesellers = lastVoteSellers
} else {
votesellers = registry[0].get_account_history
}
// refresh get account history every X time
response = await axios.get(sbt_url + '/bid_bots')
bidbots = response.data
bidbots.map((x) => ignore_list.push(x.name))
ignore_list.push(...['tipu', 'ocdb'])
query_ignore = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {ignore: true}]}).toArray()
query_votesellers = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {ignore: false}, {$or: [{ mb: { $exists:false } }, { mb: false } ]}]}).toArray()
query_corrupted = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {mb: true}]}).toArray()
vote_sellers.push(...query_votesellers.map((x) => x.account))
ignore_list.push(...query_ignore.map((x) => x.account))
corrupted.push(...query_corrupted.map((x) => x.account))
console.log('number of registered votesellers => ' + vote_sellers.length)
console.log('number of ignored accounts => ' + ignore_list.length)
console.log('number of corrupted signatures => ' + corrupted.length)
// let random_node = getRandomInt(clients.length)
clients[0].database.call('get_content', [author, permlink])
.then(async(result) => {
console.log('content loaded')
let votes = result.active_votes
if (votes.length == 0) return start(postURL)
//////////////////////////////////////////////////
////////// MAIN LOOP PROCESS //////////////
////////////////////////////////////////////////
await votesLoop(votes, false)
while (pendingVotes.length > 0) {
console.log(pendingVotes)
query_ignore = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {ignore: true}]}).toArray()
query_votesellers = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {ignore: false}, {$or: [{ mb: { $exists:false } }, { mb: false } ]}]}).toArray()
query_corrupted = await smartsteem.find({$and:[{account: {$exists:true}}, {postURL: {$regex : permlink }}, {mb: true}]}).toArray()
vote_sellers.push(...query_votesellers.map((x) => x.account))
ignore_list.push(...query_ignore.map((x) => x.account))
corrupted.push(...query_corrupted.map((x) => x.account))
await votesLoop(pendingVotes, true)
}
await getVoteValue(postURL)
})
function fetchHistoricalPrices(postURL) {
return new Promise(async (resolve, reject) => {
let campaign = await campaigns.find({postURL: postURL}).toArray()
try {
let prices = campaign[0].prices
return resolve(prices)
}catch(e) {
return reject(e)
}
})
}
})
}
async function votesLoop (votes, rerun) {
var promises = []
for (let i = 0; i < votes.length; i++) {
let vote = votes[i]
let voter = vote.voter
// var pending = []
console.log(i + ' / ' + (votes.length - 1) + ' ' + chalk.bold(voter))
if (vote_sellers.indexOf(voter) > -1) {
try {
console.log(voter + ' is already registered as SM voteseller')
// pending.push({ account: vote.voter, vote: vote, ignore: false, postURL: postURL, prices: prices})
// console.log(chalk.green('succesfully added ' + voter))
continue
}catch(e) {
if (e.code == 11000) {
console.log(voter + ' is duplicated')
continue
} else {
console.log(e)
console.log(chalk.red('breaking the loop...'))
break
}
}
}
if (ignore_list.indexOf(voter) > -1) {
console.log(voter + ' is already registered in the ignore list')
continue
}
if (corrupted.indexOf(voter) > -1) {
console.log(voter + ' is already registered in the corrupted list')
continue
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///// The main reason you dont worry about 'Promise.all' triggering at the first rejection -even if there are still pending promises, ////
///// is the fact that you set a defined max tolerance for timeout (rejection) and that make use of a reflection abstraction that //
///// avoids rejections, that way, you have better control over the asynchronous processes //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
promises.push(reflect(Promise.race([compute(clients[promises.length], vote), timeout(7, 'compute', clients[promises.length].address, vote)])))
console.log('using ' + clients[promises.length - 1].address + ' for ' + voter)
if (promises.length == clients.length) {
// await wait(5)
console.log('clients = ' + clients.length)
console.log(chalk.yellow('reached ' + promises.length + ' promises..waiting'))
let results = await Promise.all(promises)
let failed_promises = results.filter((result) => result.status == 'rejected').map((x) => x.e)
// add to pending votes all failed ones
pendingVotes.push(...failed_promises)
// in case we are re-running from pendingVotes, update the list removing the resolved
let successful_promises = results.filter((result) => result.status == 'fulfilled').map((x) => x.v)
if (rerun) {
pendingVotes.filter((x) => {successful_promises.indexOf(x) == -1})
}
console.log('number of pending votes = ' + pendingVotes.length)
console.log('all promises have resolved/rejected, continueing..')
promises = []
}
}
// if (pending.length == 0) return console.log(chalk.bgGreen.bold('voteLoop function finished, no pending votes'))
// try {
// await smartsteem.insertMany(pending, {ordered: false})
// console.log('bulkInsert all good')
// } catch(e) {
// console.log(chalk.red('bulkInsert error at end of loop'))
// console.log(e)
// }
}
async function fetchLastVoteSellers () {
return new Promise((resolve, reject) => {
var votesellers = []
clients[0].database.call('get_account_history', ['smartsteem', -1, 5000])
.then((res) => {
res.forEach((el) => {
let trans = el[1]
let op = trans.op
if (op[0] == 'transfer' && op[1].memo.startsWith("#")) {
votesellers.push(op[1].to)
}
})
let uniq = [...new Set(votesellers)]
return resolve(uniq)
})
})
}
// you could store ignored accounts per permlink
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function findVotePseudoTrx (client, vote) {
return new Promise(async (resolve, reject) => {
let permlink = postURL.substr(postURL.lastIndexOf('/') + 1)
let voter = vote.voter
let history = []
let match = null
var interval = 0
while (!match) {
try {
history = await client.database.call('get_account_history', [voter, -1 - interval, 1500 + interval])
} catch(e){
console.log(e)
console.log(client.address + ' error at get_account_history ' + client.address)
return reject(vote)
}
match = history.find((x) => x[1].op[0] == 'vote' && x[1].op[1].permlink == permlink)
interval += 1500
await wait(0.5)
// if (interval > 5000) return reject()
}
if (interval > 0) console.log(vote.voter + ' findVotePseudoTrx resolved with interval = ' + interval)
return resolve(match)
})
}
function compute (client, vote, prices) {
return new Promise(async(resolve, reject) => {
let voter = vote.voter
let pseudo_trx
try {
pseudo_trx = await findVotePseudoTrx(client, vote)
} catch(e) {
console.log(e)
return reject(e)
}
let trx
try {
trx = await steemtrxfinder.findVoteTrx(client, pseudo_trx)
} catch(e) {
console.log(chalk.red('error at steemtrxfinder'))
console.log(e)
return reject(vote)
}
let digest = utils.transactionDigest(trx)
let signature
let pub = ''
for (let i = 0; i < trx.signatures.length; i++) {
let _signature = trx.signatures[i]
try {
signature = dsteem.Signature.fromString(_signature)
pub = signature.recover(digest).toString()
} catch(e) {
console.log(trx)
console.log(e)
if (voter !== trx.operations[0][1].voter) return console.log(chalk.red('trx op voter from "steemtrxfinder" result does not match current voter'))
console.log(chalk.red(voter + ': cannot extract pubkey from origin trx'))
// console.log(trx)
if (i == (trx.signatures.length - 1)) {
// console.log(chalk.bgRed.bold(client.address))
// console.log(e)
// console.log(trx)
// console.log(trx.operations[0][1])
console.log(vote)
smartsteem.insertOne({ account: vote.voter, vote: vote, ignore: false, postURL: postURL, prices: prices, mb: true })
return reject(new Error('cannot extract pubkey'))
}
}
}
if (pub == sm_pub) {
// find account SP
console.log(chalk.green('BINGO smartmarket voter found! - ' + voter))
smartsteem.insertOne({ account: vote.voter, vote: vote, ignore: false, postURL: postURL, prices: prices })
return resolve(vote)
} else {
console.log(chalk.green(voter + ' added to ignore list'))
smartsteem.insertOne({ account: vote.voter, ignore: true, postURL: postURL })
return resolve(vote)
}
})
}
module.exports = {
start: start
}