Skip to content

Commit

Permalink
pool server work
Browse files Browse the repository at this point in the history
  • Loading branch information
madMAx43v3r committed Sep 24, 2024
1 parent d136a0b commit 2a72fa7
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 68 deletions.
227 changes: 163 additions & 64 deletions www/pool-server/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const utils = require('./utils.js');
const config = require('./config.js');

var db = null;
var sync_height = null;

async function update_account(address, reward, pool_share, points, count, height, opt)
{
Expand All @@ -16,7 +17,7 @@ async function update_account(address, reward, pool_share, points, count, height
}
account.pool_share = pool_share;
account.points_rate = points / config.share_window;
account.partial_rate = count / 24;
account.partial_rate = count / config.share_window_hours;
account.last_update = height;
await account.save(opt);
}
Expand All @@ -26,86 +27,60 @@ var update_lock = false;

async function update()
{
let sync_height = null;
try {
sync_height = await utils.get_synced_height();
if(!sync_height) {
console.log('Waiting for node sync ...');
return;
}
} catch(e) {
console.log("Failed to query sync height:", e.message);
if(!sync_height) {
return;
}
const now = Date.now();

if(update_lock) {
return;
}
update_lock = true;
try {
const blocks = await dbs.Block.find({
pending: true,
height: {$lte: sync_height - config.account_delay}
});

let total_rewards = 0;
for(const block of blocks) {
const res = await axios.get(config.node_url + '/wapi/header?height=' + block.height);
const valid = (res.data.hash === block.hash);
if(valid) {
total_rewards += block.reward_value;
}
block.valid = valid;
block.pending = false;
}

const conn = await db.startSession();
try {
await conn.startTransaction();
const opt = {session: conn};

if(total_rewards) {
const fee = total_rewards * config.pool_fee;
total_rewards -= fee;

const account = await dbs.Account.findOne({address: config.fee_account});
if(!account) {
account = new dbs.Account({address: config.fee_account});
}
account.balance += fee;
await account.save(opt);
}
const start_height = sync_height - config.share_window;

const result = await dbs.Partial.aggregate([
{$match: {valid: true, height: {$gte: start_height}}},
{$match: {pending: false, valid: true, height: {$gte: start_height}}},
{$group: {_id: "$account", points: {$sum: "$points"}, count: {$sum: 1}}}
]);

const distribution = await dbs.Partial.aggregate([
{$match: {pending: false, valid: true, height: {$gte: start_height}}},
{$group: {_id: "$height", points: {$sum: "$points"}, count: {$sum: 1}}}
]);

const errors = await dbs.Partial.aggregate([
{$match: {valid: false, height: {$gte: start_height}}},
{$match: {pending: false, valid: false, height: {$gte: start_height}}},
{$group: {_id: "$error_code", points: {$sum: "$points"}, count: {$sum: 1}}}
]);

let uptime = 0;
let reward_enable = false;
{
const blocks_per_hour = 3600 * 1000 / config.block_interval;

const map = new Map();
for(const entry of distribution) {
const hour = Math.floor((entry._id - start_height) / blocks_per_hour);
map.set(hour, true);
}
uptime = Math.min(map.size, config.share_window_hours);
reward_enable = (uptime > config.share_window_hours / 2);
}

let total_points = 0;
let total_partials = 0;
for(const entry of result) {
total_points += entry.points;
total_partials += entry.count;
}

let res = [];
for(const entry of result) {
const pool_share = entry.points / total_points;
const reward_share = total_rewards * pool_share;
res.push(update_account(
entry._id, reward_share, pool_share, entry.points, entry.count, sync_height, opt));
}
await Promise.all(res);

for(const block of blocks) {
await block.save(opt);
let total_points_failed = 0;
for(const entry of errors) {
total_points_failed += entry.points;
}

let pool = await dbs.Pool.findOne({id: "this"});
Expand All @@ -114,32 +89,83 @@ async function update()
}
pool.farmers = result.length;
pool.points_rate = total_points / config.share_window;
pool.partial_rate = total_partials / 24;
pool.partial_rate = total_partials / config.share_window_hours;

const partial_errors = {};
for(const entry of errors) {
partial_errors[entry._id] = {
points: entry.points,
count: entry.count,
share: entry.points / total_points
share: entry.points / (total_points + total_points_failed)
};
}
pool.partial_errors = partial_errors;
pool.last_update = sync_height;
await pool.save(opt);

await conn.commitTransaction();
let total_rewards = 0;

for(const block of blocks) {
if(block.valid) {
console.log("Farmed block", block.height, "reward", block.reward_value / config.mmx_divider, "MMX", "account", block.account);
} else {
console.log("Invalid block", block.height);
if(reward_enable) {
// Find all blocks that are pending and have been confirmed enough
const blocks = await dbs.Block.find({
pending: true,
height: {$lte: sync_height - config.account_delay}
});

for(const block of blocks) {
const res = await axios.get(config.node_url + '/wapi/header?height=' + block.height);
const valid = (res.data.hash === block.hash);
if(valid) {
total_rewards += block.reward_value;
}
block.valid = valid;
block.pending = false;
await block.save(opt);

if(valid) {
console.log("Farmed block", block.height, "reward", block.reward_value / config.mmx_divider, "MMX", "account", block.account);
} else {
console.log("Invalid block", block.height);
}
}
}

// Take pool fee first
if(total_rewards) {
const fee = total_rewards * config.pool_fee;
total_rewards -= fee;

const account = await dbs.Account.findOne({address: config.fee_account});
if(!account) {
account = new dbs.Account({address: config.fee_account});
}
account.balance += fee;
await account.save(opt);
}

let res = [];
for(const entry of result) {
const pool_share = entry.points / total_points;
const reward_share = total_rewards * pool_share;
res.push(update_account(
entry._id, reward_share, pool_share, entry.points, entry.count, sync_height, opt));
}
await Promise.all(res);

await conn.commitTransaction();

if(total_rewards) {
console.log(
"Distributed", total_rewards / config.mmx_divider, "MMX to", result.length, "accounts",
"total_points", total_points, "total_partials", total_partials);
}
console.log(
"Distributed", total_rewards / config.mmx_divider, "MMX to", result.length, "accounts",
"total_points", total_points, "total_partials", total_partials);
console.log("Errors:", partial_errors);
"height", sync_height, "points_rate", pool.points_rate, "partial_rate", pool.partial_rate, "farmers", pool.farmers,
"reward_enable", reward_enable, "uptime", uptime, "/", config.share_window_hours, "hours"
);
if(total_points_failed) {
console.log("partial_errors:", partial_errors);
}
} catch(e) {
await conn.abortTransaction();
throw e;
Expand All @@ -153,12 +179,85 @@ async function update()
}
}

var check_lock = false;

async function check()
{
try {
sync_height = await utils.get_synced_height();
if(!sync_height) {
console.log('Waiting for node sync ...');
return;
}
} catch(e) {
console.log("Failed to query sync height:", e.message);
return;
}

if(check_lock) {
return;
}
check_lock = true;
try {
let since = 0;
{
const latest = await dbs.Block.findOne().sort({height: -1});
if(latest) {
since = Math.max(latest.height - config.account_delay, 0);
}
}
const res = await axios.get(config.node_url + '/wapi/address/history?id='
+ config.fee_account + "&since=" + since + "&until=" + (sync_height - 1) + "&limit=-1",
{headers: {'x-api-token': config.api_token}}
);

for(const entry of res.data) {
if(entry.type != 'REWARD') {
continue;
}
if(!entry.is_native) {
continue; // not MMX
}
const block_hash = entry.txid; // txid = block hash in case of type == REWARD

if(await dbs.Block.exists({hash: block_hash})) {
continue;
}
try {
const res = await axios.get(config.node_url + '/wapi/header?hash=' + block_hash);
const header = res.data;
const block = new dbs.Block({
hash: header.hash,
height: header.height,
account: header.reward_account,
contract: header.reward_contract,
reward_addr: header.reward_addr,
farmer_key: header.proof.farmer_key,
reward: entry.amount,
reward_value: entry.value,
time: entry.time,
pending: true,
});
await block.save();
} catch(e) {
console.log("Failed to add block:", e.message);
}
}
} catch(e) {
console.log("check() failed:", e.message);
} finally {
check_lock = false;
}
}

async function main()
{
db = await mongoose.connect(config.mongodb_uri);

update();
await check();
await update();

setInterval(check, config.block_interval / 5);
setInterval(update, config.share_interval * 1000);
}

Expand Down
8 changes: 5 additions & 3 deletions www/pool-server/config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
const config = {};

config.mmx_divider = 1e6;
config.challenge_delay = 6; // [blocks]
config.block_interval = 10 * 1000; // [ms]
config.max_response_time = 55 * 1000; // [ms]
config.partial_expiry = 100; // [blocks]
config.account_delay = 24; // [blocks]
config.pool_fee = 0.01; // [%]
config.share_window = 8640; // [blocks]
config.mmx_divider = 1e6;
config.share_interval = 60; // [sec]
config.share_window_hours = 24; // [hours]
config.share_interval = 5 * 60; // [sec]
config.min_difficulty = 1;
config.default_difficulty = 1;

config.server_port = 8080;
config.default_difficulty = 1;
config.node_url = "http://localhost:11380";
config.fee_account = "mmx1e7yktu9vpeyq7hx39cmagzfp2um3kddwjf4tlt8j3kmktwc7fk6qmyc6ns";
config.pool_target = "mmx1uj2dth7r9tcn3vas42f0hzz74dkz8ygv59mpx44n7px7j7yhvv4sfmkf0d";
Expand Down
2 changes: 1 addition & 1 deletion www/pool-server/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const pool = new mongoose.Schema({
farmers: {type: Number, default: 0},
points_rate: {type: Number, default: 0},
partial_rate: {type: Number, default: 0},
partial_errors: Object,
partial_errors: {type: Object, minimize: false},
last_update: {type: Number, default: 0},
});

Expand Down

0 comments on commit 2a72fa7

Please sign in to comment.