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"); }