scan blocks with leaderboard validation, handle reward
This commit is contained in:
366
index.js
366
index.js
@@ -4,7 +4,7 @@ const port = 9700
|
||||
|
||||
const web3operatorAddress = "http://vps.playpoolstudios.com:2015/"
|
||||
const apiAddress = "https://vps.playpoolstudios.com/metahunt/api/"
|
||||
|
||||
app.use(express.json());
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Validator is validating')
|
||||
})
|
||||
@@ -39,35 +39,174 @@ app.get('/validateSession', async (req, res) => {
|
||||
|
||||
})
|
||||
|
||||
const tournamentTimers = new Map();
|
||||
|
||||
app.post('/updateBlock', async (req, res) => {
|
||||
const jsonData = req.body;
|
||||
|
||||
if (!jsonData) {
|
||||
return res.status(400).send("No JSON data received");
|
||||
}
|
||||
const tournamentBlock = new TournamentBlockData(jsonData);
|
||||
tournamentBlocks.push(tournamentBlock);
|
||||
tournamentBlock.minute = timeIndexToMinute(new Date());
|
||||
if (Object.keys(tournamentBlock.leaderboard).length > 0) {
|
||||
tournamentBlocks.push(tournamentBlock);
|
||||
}else{
|
||||
console.log("Empty leaderboard, probably the initial one. Not pushing");
|
||||
res.send.status(200);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(tournamentBlocks);
|
||||
const tournamentData = GetTournamentById(tournamentBlock.tournamentId);
|
||||
const tournamentEndMinute = timeIndexToMinute(new Date(tournamentData.start_date)) + 10;
|
||||
|
||||
if(tournamentBlock.minute >= tournamentEndMinute){
|
||||
console.log(`this must be the last block ${tournamentBlock.minute}>${tournamentEndMinute}`);
|
||||
// console.log(`${minuteToTimeIndex(tournamentBlock.minute)}:${minuteToTimeIndex(tournamentEndMinute)}. Time:${(new Date()).toString()}`)
|
||||
//Final block from this user for this tournament
|
||||
if (!tournamentTimers.has(tournamentBlock.tournamentId)) {
|
||||
console.log(`Final block received for tournament ${tournamentBlock.tournamentId}. Starting 5-second timer...`);
|
||||
|
||||
// Set a 5-second timer for this tournament
|
||||
const timer = setTimeout(async () => {
|
||||
ScanBlocks();
|
||||
console.log(`Scanned blocks after finishing t:${tournamentBlock.tournamentId}`);
|
||||
RewardTournament(tournamentBlock.tournamentId);
|
||||
}, 5000);
|
||||
|
||||
// Store the timer in the Map for this tournament
|
||||
tournamentTimers.set(tournamentBlock.tournamentId, timer);
|
||||
}else{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
function timeIndexToMinute(time) {
|
||||
// Get the total number of milliseconds since the Unix epoch (January 1st, 1970)
|
||||
const milliseconds = time.getTime();
|
||||
|
||||
// Convert milliseconds to minutes (1 minute = 60,000 milliseconds)
|
||||
const minutes = Math.floor(milliseconds / 60000);
|
||||
|
||||
return minutes;
|
||||
}
|
||||
|
||||
function minuteToTimeIndex(minutes) {
|
||||
// Convert minutes back to milliseconds (1 minute = 60,000 milliseconds)
|
||||
const milliseconds = minutes * 60000;
|
||||
|
||||
// Create a new Date object using the milliseconds since the Unix epoch
|
||||
const date = new Date(milliseconds);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------- DEV GETS --------------------------------------- */
|
||||
|
||||
app.get("/getBlocks", (req,res)=>{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(JSON.stringify(tournamentBlocks));
|
||||
})
|
||||
|
||||
app.get("/getTourneys",(req,res)=>{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(JSON.stringify(startedTournaments));
|
||||
})
|
||||
|
||||
app.get("/getLeaderboard", (req,res)=>{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
const {id} = req.query;
|
||||
if(!id){
|
||||
res.send(JSON.stringify(confirmedLeaderboards));
|
||||
}else{
|
||||
res.send(JSON.stringify(confirmedLeaderboards[id]));
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/getTournamentEndResults", (req,res)=>{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
const {id} = req.query;
|
||||
|
||||
if(id){
|
||||
try{
|
||||
for(const endResult in tournamentEndResults){
|
||||
if(endResult.id === id){
|
||||
res.send(JSON.stringify(endResult));
|
||||
return;
|
||||
}
|
||||
}
|
||||
res.send("0");
|
||||
}catch{
|
||||
res.send("-1");
|
||||
}
|
||||
}else{
|
||||
res.send(JSON.stringify(tournamentEndResults));
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/getTournamentTimers",(req,res)=>{
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(JSON.stringify(tournamentTimers));
|
||||
})
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
let tournamentsList ;
|
||||
let startedTournaments;
|
||||
let tournamentBlocks = [];
|
||||
|
||||
let tournamentEndResults=[];
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
async function RewardTournament(tournamentId){
|
||||
const leaderboard = confirmedLeaderboards[tournamentId];
|
||||
let mostKills =-1;
|
||||
let topPlayer = "";
|
||||
for(const playerId in leaderboard){
|
||||
const playerStat = leaderboard[playerId];
|
||||
|
||||
if(playerStat.kills > mostKills){
|
||||
mostKills =playerStat.kills;
|
||||
topPlayer = playerId;
|
||||
}
|
||||
}
|
||||
|
||||
let winnerWallet = "";
|
||||
for(const block in tournamentBlocks){
|
||||
if(block.tournamentId == tournamentId && topPlayer == block.owner){
|
||||
winnerWallet = block.owner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
try{
|
||||
const tx = await fetch(web3operatorAddress+`rewardTournament?password=SekretWordHere&tournamentId=${tournamentId}&winnerWallet=${winnerWallet}`);
|
||||
const endResult = new TournamentEndResult(tournamentId, winnerWallet, tx);
|
||||
tournamentEndResults.push(endResult);
|
||||
console.log(`*** Rewarded ${winnerWallet} for tourney : ${tournamentId}`);
|
||||
}catch{
|
||||
console.log("*** ERROR REWARDING ***");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CheckForStartedTourneys();
|
||||
setInterval(async () => {
|
||||
CheckForStartedTourneys();
|
||||
}, 60000)
|
||||
|
||||
async function CheckForStartedTourneys() {
|
||||
const tournamentsResponse = await fetch(apiAddress + "get_tournaments_raw.php");
|
||||
const tournaments = await tournamentsResponse.json();
|
||||
|
||||
try{
|
||||
const tournamentsResponse = await fetch(apiAddress + "get_tournaments_raw.php");
|
||||
const tournaments = await tournamentsResponse.json();
|
||||
|
||||
tournamentsList = [];
|
||||
startedTournaments = [];
|
||||
const now = new Date();
|
||||
@@ -92,11 +231,12 @@ async function CheckForStartedTourneys() {
|
||||
if (tournamentDate >= tenMinutesAgo && tournamentDate <= now || true) {
|
||||
console.log(`Tournament "${tournament.name}" started within the last 10 minutes.`);
|
||||
|
||||
const participentsUrl = web3operatorAddress + "getTournamentParticipants?id=" + newTournament.id;
|
||||
const participentsResponse = await fetch(participentsUrl);
|
||||
const participentsJson = await participentsResponse.json();
|
||||
const participents = participentsJson["wallets"].split(',');
|
||||
|
||||
try {
|
||||
const participentsUrl = web3operatorAddress + "getTournamentParticipants?id=" + newTournament.id;
|
||||
const participentsResponse = await fetch(participentsUrl);
|
||||
const participentsJson = await participentsResponse.json();
|
||||
const participents = participentsJson["wallets"].split(',');
|
||||
participents.forEach(participent => {
|
||||
newTournament.participents.push(participent);
|
||||
})
|
||||
@@ -117,77 +257,37 @@ async function CheckForStartedTourneys() {
|
||||
// Push the new tournament instance into tournamentsList
|
||||
tournamentsList.push(newTournament);
|
||||
});
|
||||
}catch{
|
||||
console.log("*** API SERVER IS NOT RESPONDING? ***");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
setInterval(async () => {
|
||||
ScanBlocks();
|
||||
}, 60000)
|
||||
startScanBlocksAt30Seconds();
|
||||
|
||||
function ScanBlocks(){
|
||||
for(let i=0; i < tournamentBlocks.length; i++){
|
||||
let isValid = true;
|
||||
for(let j=0; j < startedTournaments.length; j++){
|
||||
if(startedTournaments[j].id == tournamentBlocks[i].tournamentId){
|
||||
//Found the matching tourney for this block
|
||||
if(!startedTournaments[j].participents.include(tournamentBlocks[i].owner)){
|
||||
// isValid=false;
|
||||
console.log("***Block was sent by non-participent. Allowing this for now.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------METHODS----------------------------------- */
|
||||
|
||||
function GetTournamentById(tournamentId) {
|
||||
const tournament = startedTournaments.find(tournament => tournament.id == tournamentId);
|
||||
return tournament;
|
||||
}
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------CUSTOM CLASSES------------------------------------ */
|
||||
|
||||
|
||||
class TournamentData {
|
||||
constructor(id, name, start_date, game_mode, reward, php_reward, is_test, ticket_count, owner_id) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.start_date = start_date;
|
||||
this.game_mode = game_mode;
|
||||
this.reward = reward;
|
||||
this.php_reward = php_reward;
|
||||
this.is_test = is_test;
|
||||
this.ticket_count = ticket_count;
|
||||
this.owner_id = owner_id;
|
||||
this.participents = [];
|
||||
function startScanBlocksAt30Seconds() {
|
||||
const now = new Date();
|
||||
const seconds = now.getSeconds();
|
||||
|
||||
// Calculate time (in ms) to the next 30th second of the minute
|
||||
let delay;
|
||||
if (seconds < 30) {
|
||||
delay = (30 - seconds) * 1000; // wait till 30th second
|
||||
} else {
|
||||
delay = (60 - seconds + 30) * 1000; // wait till next minute's 30th second
|
||||
}
|
||||
|
||||
// Method to display tournament details
|
||||
displayDetails() {
|
||||
console.log(`Tournament ID: ${this.id}`);
|
||||
console.log(`Name: ${this.name}`);
|
||||
console.log(`Date: ${this.start_date}`);
|
||||
console.log(`Game Mode: ${this.game_mode}`);
|
||||
console.log(`Reward: ${this.reward}`);
|
||||
console.log(`PHP Reward: ${this.php_reward}`);
|
||||
console.log(`Test Tournament: ${this.is_test ? "Yes" : "No"}`);
|
||||
console.log(`Ticket Count: ${this.ticket_count}`);
|
||||
console.log(`Owner ID: ${this.owner_id}`);
|
||||
console.log(`Participents: ${this.participents}`);
|
||||
setTimeout(() => {
|
||||
// First run at the exact 30th second
|
||||
ScanBlocks();
|
||||
|
||||
}
|
||||
// Continue running every 60 seconds after this
|
||||
setInterval(() => {
|
||||
ScanBlocks();
|
||||
}, 60000); // Run every 60 seconds
|
||||
}, delay);
|
||||
}
|
||||
|
||||
|
||||
@@ -239,8 +339,11 @@ function ScanBlocks() {
|
||||
blockValidationCounts[tournamentId] = { count: 0, total: 0 };
|
||||
}
|
||||
blockValidationCounts[tournamentId].count++;
|
||||
|
||||
tournamentBlocks[i].validation=1;
|
||||
} else {
|
||||
console.log(`Block ${i + 1} is invalid.`);
|
||||
tournamentBlocks[i].validation=-1;
|
||||
}
|
||||
|
||||
// Track total blocks per tournament for comparison later
|
||||
@@ -256,13 +359,122 @@ function ScanBlocks() {
|
||||
const { count, total } = blockValidationCounts[tournamentId];
|
||||
if (count > total / 2) {
|
||||
// More than 50% of blocks are valid, store the leaderboard
|
||||
const validBlock = tournamentBlocks.find(block => block.tournamentId === parseInt(tournamentId));
|
||||
let lastValidBlockIndex=-1;
|
||||
for(let i=0; i<tournamentBlocks.length; i++){
|
||||
if(tournamentBlocks[i].tournamentId == tournamentId){
|
||||
if(lastValidBlockIndex>0){
|
||||
if(tournamentBlocks[lastValidBlockIndex].minute < tournamentBlocks[i].minute){
|
||||
lastValidBlockIndex=i;
|
||||
}
|
||||
}else{
|
||||
lastValidBlockIndex=i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validBlock = tournamentBlocks[lastValidBlockIndex];
|
||||
if (validBlock) {
|
||||
confirmedLeaderboards[tournamentId] = validBlock.leaderboard;
|
||||
console.log(`*** Tournament ${tournamentId} confirmed with a valid leaderboard.`);
|
||||
if (Object.keys(validBlock.leaderboard).length > 0) {
|
||||
confirmedLeaderboards[tournamentId] = validBlock.leaderboard;
|
||||
console.log(`*** Tournament ${tournamentId} confirmed with a valid leaderboard.`);
|
||||
}else{
|
||||
console.log(`Block ${validBlock.tournamentId} from ${validBlock.owner} has an empty leaderboard. Ignoring for now`);
|
||||
}
|
||||
}else{
|
||||
console.log(`Could not find a block for t:${tournamentId}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`*** Tournament ${tournamentId} did not meet the 50% valid block requirement.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------METHODS----------------------------------- */
|
||||
|
||||
function GetTournamentById(tournamentId) {
|
||||
const tournament = startedTournaments.find(tournament => tournament.id == tournamentId);
|
||||
return tournament;
|
||||
}
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------CUSTOM CLASSES------------------------------------ */
|
||||
|
||||
|
||||
class TournamentData {
|
||||
constructor(id, name, start_date, game_mode, reward, php_reward, is_test, ticket_count, owner_id) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.start_date = start_date;
|
||||
this.game_mode = game_mode;
|
||||
this.reward = reward;
|
||||
this.php_reward = php_reward;
|
||||
this.is_test = is_test;
|
||||
this.ticket_count = ticket_count;
|
||||
this.owner_id = owner_id;
|
||||
this.participents = [];
|
||||
}
|
||||
|
||||
// Method to display tournament details
|
||||
displayDetails() {
|
||||
console.log(`Tournament ID: ${this.id}`);
|
||||
console.log(`Name: ${this.name}`);
|
||||
console.log(`Date: ${this.start_date}`);
|
||||
console.log(`Game Mode: ${this.game_mode}`);
|
||||
console.log(`Reward: ${this.reward}`);
|
||||
console.log(`PHP Reward: ${this.php_reward}`);
|
||||
console.log(`Test Tournament: ${this.is_test ? "Yes" : "No"}`);
|
||||
console.log(`Ticket Count: ${this.ticket_count}`);
|
||||
console.log(`Owner ID: ${this.owner_id}`);
|
||||
console.log(`Participents: ${this.participents}`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Define the PlayerStat class
|
||||
class PlayerStat {
|
||||
constructor(data) {
|
||||
this.username = data.username || '';
|
||||
this.xp = data.xp || 0;
|
||||
this.kills = data.kills || 0;
|
||||
this.deaths = data.deaths || 0;
|
||||
this.assists = data.assists || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Define the TournamentBlockData class
|
||||
class TournamentBlockData {
|
||||
constructor(data) {
|
||||
console.log(data);
|
||||
this.tournamentId = data.tournamentId || 0;
|
||||
this.owner = data.owner || '';
|
||||
this.owner_username=data.owner_username || '';
|
||||
this.minute=data.minute;
|
||||
this.validation =0;
|
||||
this.leaderboard = {};
|
||||
|
||||
// Populate the leaderboard
|
||||
if (data.leaderboard) {
|
||||
for (const playerId in data.leaderboard) {
|
||||
if (data.leaderboard.hasOwnProperty(playerId)) {
|
||||
this.leaderboard[playerId] = new PlayerStat(data.leaderboard[playerId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TournamentEndResult{
|
||||
|
||||
constructor(id, winner, tx){
|
||||
this.id=id;
|
||||
this.winner = winner;
|
||||
this.tx=tx;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user