250 lines
9.5 KiB
JavaScript
250 lines
9.5 KiB
JavaScript
const {
|
|
getNetworkName,
|
|
getWeb3,
|
|
contracts,
|
|
BN,
|
|
config,
|
|
} = require('./test-helper.js');
|
|
let web3 = getWeb3();
|
|
const {SqlDao} = require('../common/dao.js');
|
|
const sqlDao = new SqlDao(getNetworkName());
|
|
const KeysManagerContract = new web3.eth.Contract(contracts.KeysManagerAbi, contracts.KeysManagerAddress);
|
|
let blockToCheck;
|
|
let masterOfCeremony;
|
|
|
|
checkRewardByBlock();
|
|
|
|
/**
|
|
Checks reward for new blocks (from the last checked)
|
|
*/
|
|
async function checkRewardByBlock() {
|
|
await sqlDao.createRewardByBlockTable();
|
|
let lastBlock = await web3.eth.getBlock('latest');
|
|
let latestRewardByBlockRecord = await sqlDao.getLatestRewardByBlockRecord();
|
|
let lastCheckedBlock;
|
|
if (latestRewardByBlockRecord.length === 0) {
|
|
console.log("No records yet");
|
|
lastCheckedBlock = await web3.eth.getBlock(lastBlock.number - 300);
|
|
}
|
|
else {
|
|
lastCheckedBlock = await web3.eth.getBlock(latestRewardByBlockRecord[0].block);
|
|
if (!lastCheckedBlock) {
|
|
console.warn("Blocks are pruned");
|
|
lastCheckedBlock = await web3.eth.getBlock(lastBlock.number - 300);
|
|
}
|
|
}
|
|
console.log("lastBlock: " + lastBlock.number + ", lastCheckedBlock: " + lastCheckedBlock.number);
|
|
|
|
masterOfCeremony = await KeysManagerContract.methods.masterOfCeremony().call();
|
|
blockToCheck = lastCheckedBlock;
|
|
|
|
while (blockToCheck.number < lastBlock.number) {
|
|
blockToCheck = await web3.eth.getBlock(blockToCheck.number + 1);
|
|
let validator = blockToCheck.miner;
|
|
let result = {
|
|
passed: true,
|
|
block: blockToCheck.number,
|
|
validator: validator,
|
|
payoutKey: "",
|
|
error: [],
|
|
};
|
|
console.log("-- blockToCheck: " + blockToCheck.number + ", validator: " + validator);
|
|
|
|
let emissionResult = await checkEmissionFunds();
|
|
let blockRewardResult = await checkPayoutKeyBalance(validator);
|
|
let txsRewardResult = await checkMiningKeyBalance(validator);
|
|
|
|
if (emissionResult.isPruned || blockRewardResult.isPruned || txsRewardResult.isPruned) {
|
|
console.warn("Skip pruned block");
|
|
continue;
|
|
}
|
|
|
|
result.passed = !emissionResult.error && !blockRewardResult.error && !txsRewardResult.error;
|
|
result.payoutKey = blockRewardResult.payoutKey;
|
|
|
|
if (!result.passed) {
|
|
if (emissionResult.error) {
|
|
result.error.push(emissionResult.error);
|
|
}
|
|
if (blockRewardResult.error) {
|
|
result.error.push(blockRewardResult.error);
|
|
}
|
|
if (txsRewardResult.error) {
|
|
result.error.push(txsRewardResult.error);
|
|
}
|
|
}
|
|
await sqlDao.addToRewardByBlockTable([new Date(Date.now()).toISOString(), (result.passed) ? 1 : 0,
|
|
result.block, result.validator, result.payoutKey, JSON.stringify(result.error)]);
|
|
}
|
|
sqlDao.closeDb();
|
|
}
|
|
|
|
|
|
/**
|
|
Checks block reward sent to the EmissionFunds contract
|
|
*/
|
|
async function checkEmissionFunds() {
|
|
let result = {
|
|
error: null,
|
|
isPruned: false
|
|
};
|
|
let emissionFundsContractAddress = contracts.EmissionFundsAddress;
|
|
console.log('checkEmissionFunds(), contract address: ' + emissionFundsContractAddress);
|
|
let expectedEmission = new BN(config.emissionFundsAmount);
|
|
let actualEmission;
|
|
try {
|
|
actualEmission = new BN(await web3.eth.getBalance(emissionFundsContractAddress, blockToCheck.number))
|
|
.sub(new BN(await web3.eth.getBalance(emissionFundsContractAddress, blockToCheck.number - 1)));
|
|
console.log("actualEmission: " + actualEmission.toString() + ", expectedEmission: " + expectedEmission.toString());
|
|
} catch (e) {
|
|
console.error("Error ", e);
|
|
result.error = {
|
|
description: "Error in emission funds test: " + e.message,
|
|
expected: "",
|
|
actual: ""
|
|
};
|
|
if (isPruningError(e)) {
|
|
result.isPruned = true;
|
|
}
|
|
return result;
|
|
}
|
|
let isEmissionRight = actualEmission.eq(expectedEmission);
|
|
if (!isEmissionRight) {
|
|
result.error = {
|
|
description: "Wrong emission funds amount",
|
|
expected: expectedEmission.toString(),
|
|
actual: actualEmission.toString()
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
Checks block reward sent to validator's payout key
|
|
*/
|
|
async function checkPayoutKeyBalance(validator) {
|
|
console.log('checkPayoutKeyBalance(), validator: ' + validator);
|
|
let result = {
|
|
error: null,
|
|
payoutKey: "",
|
|
isPruned: false
|
|
};
|
|
// Master of Ceremony doesn't have payout key
|
|
if (validator === masterOfCeremony) {
|
|
console.log("masterOfCeremony ");
|
|
return result;
|
|
}
|
|
let expectedBlockReward = new BN(config.miningReward);
|
|
let actualBlockReward;
|
|
let payoutKey;
|
|
try {
|
|
payoutKey = await KeysManagerContract.methods.getPayoutByMining(validator).call();
|
|
result.payoutKey = payoutKey;
|
|
let balanceAtCheckedBlock = await web3.eth.getBalance(payoutKey, blockToCheck.number);
|
|
let balanceAtPreviousBlock = await web3.eth.getBalance(payoutKey, blockToCheck.number - 1);
|
|
console.log("balanceAtCheckedBlock: " + balanceAtCheckedBlock.toString() + ", balanceAtPreviousBlock: " + balanceAtPreviousBlock.toString());
|
|
actualBlockReward = new BN(balanceAtCheckedBlock).sub(new BN(balanceAtPreviousBlock));
|
|
} catch (e) {
|
|
console.error("Error ", e);
|
|
result.error = {
|
|
description: "Error in payout key balance check: " + e.message,
|
|
expected: "",
|
|
actual: ""
|
|
};
|
|
if (isPruningError(e)) {
|
|
result.isPruned = true;
|
|
}
|
|
return result;
|
|
}
|
|
for (let j = 0; j < blockToCheck.transactions.length; j++) {
|
|
let tx = await web3.eth.getTransaction(blockToCheck.transactions[j]);
|
|
let gasPrice = new BN(tx.gasPrice);
|
|
let receipt = await web3.eth.getTransactionReceipt(blockToCheck.transactions[j]);
|
|
let transactionPrice = new BN(receipt.gasUsed).mul(gasPrice);
|
|
console.log("tx.from: " + tx.from + ", tx.to: " + tx.to + ", tx.value: " + tx.value + ", transactionPrice: " + transactionPrice);
|
|
if (tx.from === payoutKey) {
|
|
expectedBlockReward = (expectedBlockReward.sub(new BN(tx.value)).sub(new BN(transactionPrice)));
|
|
}
|
|
else if (tx.to === payoutKey) {
|
|
expectedBlockReward = expectedBlockReward.add(new BN(tx.value));
|
|
}
|
|
}
|
|
console.log("actualBlockReward: " + actualBlockReward.toString() + ", expectedBlockReward: " + expectedBlockReward.toString() + ', payoutKey: ' + payoutKey);
|
|
let isRewardRight = actualBlockReward.eq(expectedBlockReward);
|
|
if (!isRewardRight) {
|
|
result.error = {
|
|
description: "Wrong block reward (payout key)",
|
|
expected: expectedBlockReward.toString(),
|
|
actual: actualBlockReward.toString()
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Checks reward for block transactions sent to validator's mining key
|
|
*/
|
|
async function checkMiningKeyBalance(validator) {
|
|
console.log('checkMiningKeyBalance(), validator: ' + validator);
|
|
let result = {
|
|
error: null,
|
|
isPruned: false
|
|
};
|
|
let actualTxsReward;
|
|
try {
|
|
actualTxsReward = new BN(await web3.eth.getBalance(validator, blockToCheck.number))
|
|
.sub(new BN(await web3.eth.getBalance(validator, blockToCheck.number - 1)));
|
|
console.log("actualTxsReward: " + actualTxsReward);
|
|
} catch (e) {
|
|
console.error("checkMiningKeyBalance Error " + e.message);
|
|
result.error = {
|
|
description: "Error in mining key balance check: " + e.message,
|
|
expected: "",
|
|
actual: ""
|
|
};
|
|
if (isPruningError(e)) {
|
|
result.isPruned = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
let expectedTxsReward = new BN(0);
|
|
for (let j = 0; j < blockToCheck.transactions.length; j++) {
|
|
let tx = await web3.eth.getTransaction(blockToCheck.transactions[j]);
|
|
let gasPrice = new BN(tx.gasPrice);
|
|
let receipt = await web3.eth.getTransactionReceipt(blockToCheck.transactions[j]);
|
|
let transactionPrice = new BN(receipt.gasUsed).mul(gasPrice);
|
|
console.log("tx.from: " + tx.from + ", tx.to: " + tx.to);
|
|
if (!(tx.from === blockToCheck.miner)) {
|
|
expectedTxsReward = expectedTxsReward.add(transactionPrice);
|
|
}
|
|
else if (tx.from === blockToCheck.miner) {
|
|
expectedTxsReward = expectedTxsReward.sub(new BN(tx.value));
|
|
}
|
|
else if (tx.to === blockToCheck.miner) {
|
|
expectedTxsReward = expectedTxsReward.add(new BN(tx.value));
|
|
}
|
|
}
|
|
|
|
// Master of Ceremony receives block reward on the Mining key
|
|
if (validator === masterOfCeremony) {
|
|
console.log("masterOfCeremony, reward for txs: " + expectedTxsReward.toString());
|
|
expectedTxsReward = expectedTxsReward.add(new BN(config.miningReward));
|
|
}
|
|
console.log("expectedTxsReward: " + expectedTxsReward.toString() + ", actualTxsReward: " + actualTxsReward.toString());
|
|
|
|
let isRewardRight = actualTxsReward.eq(expectedTxsReward);
|
|
if (!isRewardRight) {
|
|
result.error = {
|
|
description: "Wrong txs reward (mining key)",
|
|
expected: expectedTxsReward.toString(),
|
|
actual: actualTxsReward.toString()
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function isPruningError(error) {
|
|
return error.message.includes("This request is not supported because your node is running with state pruning");
|
|
} |