UNPKG

@polygon-hermez/vm

Version:
414 lines 16.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateTxReceipt = exports.encodeReceipt = exports.rewardAccount = exports.calculateMinerReward = void 0; const debug_1 = require("debug"); const merkle_patricia_tree_1 = require("merkle-patricia-tree"); const ethereumjs_util_1 = require("ethereumjs-util"); const block_1 = require("@ethereumjs/block"); const common_1 = require("@ethereumjs/common"); const bloom_1 = __importDefault(require("./bloom")); const opcodes_1 = require("./evm/opcodes"); const tx_1 = require("@ethereumjs/tx"); const DAOConfig = __importStar(require("./config/dao_fork_accounts_config.json")); const debug = (0, debug_1.debug)('vm:block'); /* DAO account list */ const DAOAccountList = DAOConfig.DAOAccounts; const DAORefundContract = DAOConfig.DAORefundContract; /** * @ignore */ async function runBlock(opts) { const state = this.stateManager; const { root } = opts; let { block } = opts; const generateFields = !!opts.generate; /** * The `beforeBlock` event. * * @event Event: beforeBlock * @type {Object} * @property {Block} block emits the block that is about to be processed */ await this._emit('beforeBlock', block); if (this._hardforkByBlockNumber || this._hardforkByTD) { this._common.setHardforkByBlockNumber(block.header.number.toNumber(), this._hardforkByTD); } if (this.DEBUG) { debug('-'.repeat(100)); debug(`Running block hash=${block.hash().toString('hex')} number=${block.header.number} hardfork=${this._common.hardfork()}`); } // Set state root if provided if (root) { if (this.DEBUG) { debug(`Set provided state root ${root.toString('hex')}`); } await state.setStateRoot(root); } // check for DAO support and if we should apply the DAO fork if (this._common.hardforkIsActiveOnChain('dao') && block.header.number.eq(this._common.hardforkBlockBN('dao'))) { if (this.DEBUG) { debug(`Apply DAO hardfork`); } await _applyDAOHardfork(state); } // Checkpoint state await state.checkpoint(); if (this.DEBUG) { debug(`block checkpoint`); } let result; try { result = await applyBlock.bind(this)(block, opts); if (this.DEBUG) { debug(`Received block results gasUsed=${result.gasUsed} bloom=${(0, opcodes_1.short)(result.bloom.bitvector)} (${result.bloom.bitvector.length} bytes) receiptRoot=${result.receiptRoot.toString('hex')} receipts=${result.receipts.length} txResults=${result.results.length}`); } } catch (err) { await state.revert(); if (this.DEBUG) { debug(`block checkpoint reverted`); } throw err; } // Persist state await state.commit(); if (this.DEBUG) { debug(`block checkpoint committed`); } const stateRoot = await state.getStateRoot(false); // Given the generate option, either set resulting header // values to the current block, or validate the resulting // header values against the current block. if (generateFields) { const bloom = result.bloom.bitvector; const gasUsed = result.gasUsed; const receiptTrie = result.receiptRoot; const transactionsTrie = await _genTxTrie(block); const generatedFields = { stateRoot, bloom, gasUsed, receiptTrie, transactionsTrie }; const blockData = Object.assign(Object.assign({}, block), { header: Object.assign(Object.assign({}, block.header), generatedFields) }); block = block_1.Block.fromBlockData(blockData, { common: this._common }); } else { if (result.receiptRoot && !result.receiptRoot.equals(block.header.receiptTrie)) { if (this.DEBUG) { debug(`Invalid receiptTrie received=${result.receiptRoot.toString('hex')} expected=${block.header.receiptTrie.toString('hex')}`); } const msg = _errorMsg('invalid receiptTrie', this, block); throw new Error(msg); } if (!result.bloom.bitvector.equals(block.header.logsBloom)) { if (this.DEBUG) { debug(`Invalid bloom received=${result.bloom.bitvector.toString('hex')} expected=${block.header.logsBloom.toString('hex')}`); } const msg = _errorMsg('invalid bloom', this, block); throw new Error(msg); } if (!result.gasUsed.eq(block.header.gasUsed)) { if (this.DEBUG) { debug(`Invalid gasUsed received=${result.gasUsed} expected=${block.header.gasUsed}`); } const msg = _errorMsg('invalid gasUsed', this, block); throw new Error(msg); } if (!stateRoot.equals(block.header.stateRoot)) { if (this.DEBUG) { debug(`Invalid stateRoot received=${stateRoot.toString('hex')} expected=${block.header.stateRoot.toString('hex')}`); } const msg = _errorMsg('invalid block stateRoot', this, block); throw new Error(msg); } } const results = { receipts: result.receipts, results: result.results, stateRoot, gasUsed: result.gasUsed, logsBloom: result.bloom.bitvector, receiptRoot: result.receiptRoot, }; const afterBlockEvent = Object.assign(Object.assign({}, results), { block }); /** * The `afterBlock` event * * @event Event: afterBlock * @type {AfterBlockEvent} * @property {AfterBlockEvent} result emits the results of processing a block */ await this._emit('afterBlock', afterBlockEvent); if (this.DEBUG) { debug(`Running block finished hash=${block.hash().toString('hex')} number=${block.header.number} hardfork=${this._common.hardfork()}`); } return results; } exports.default = runBlock; /** * Validates and applies a block, computing the results of * applying its transactions. This method doesn't modify the * block itself. It computes the block rewards and puts * them on state (but doesn't persist the changes). * @param {Block} block * @param {RunBlockOpts} opts */ async function applyBlock(block, opts) { // Validate block if (!opts.skipBlockValidation) { if (block.header.gasLimit.gte(new ethereumjs_util_1.BN('8000000000000000', 16))) { const msg = _errorMsg('Invalid block with gas limit greater than (2^63 - 1)', this, block); throw new Error(msg); } else { if (this.DEBUG) { debug(`Validate block`); } await block.validate(this.blockchain); } } // Apply transactions if (this.DEBUG) { debug(`Apply transactions`); } const blockResults = await applyTransactions.bind(this)(block, opts); // Pay ommers and miners if (block._common.consensusType() === common_1.ConsensusType.ProofOfWork) { await assignBlockRewards.bind(this)(block); } return blockResults; } /** * Applies the transactions in a block, computing the receipts * as well as gas usage and some relevant data. This method is * side-effect free (it doesn't modify the block nor the state). * @param {Block} block * @param {RunBlockOpts} opts */ async function applyTransactions(block, opts) { const bloom = new bloom_1.default(); // the total amount of gas used processing these transactions let gasUsed = new ethereumjs_util_1.BN(0); const receiptTrie = new merkle_patricia_tree_1.BaseTrie(); const receipts = []; const txResults = []; /* * Process transactions */ for (let txIdx = 0; txIdx < block.transactions.length; txIdx++) { const tx = block.transactions[txIdx]; let maxGasLimit; if (this._common.isActivatedEIP(1559)) { maxGasLimit = block.header.gasLimit.muln(this._common.param('gasConfig', 'elasticityMultiplier')); } else { maxGasLimit = block.header.gasLimit; } const gasLimitIsHigherThanBlock = maxGasLimit.lt(tx.gasLimit.add(gasUsed)); if (gasLimitIsHigherThanBlock) { const msg = _errorMsg('tx has a higher gas limit than the block', this, block); throw new Error(msg); } // Run the tx through the VM const { skipBalance, skipNonce } = opts; const txRes = await this.runTx({ tx, block, skipBalance, skipNonce, blockGasUsed: gasUsed, }); txResults.push(txRes); if (this.DEBUG) { debug('-'.repeat(100)); } // Add to total block gas usage gasUsed = gasUsed.add(txRes.gasUsed); if (this.DEBUG) { debug(`Add tx gas used (${txRes.gasUsed}) to total block gas usage (-> ${gasUsed})`); } // Combine blooms via bitwise OR bloom.or(txRes.bloom); // Add receipt to trie to later calculate receipt root receipts.push(txRes.receipt); const encodedReceipt = encodeReceipt(tx, txRes.receipt); await receiptTrie.put(ethereumjs_util_1.rlp.encode(txIdx), encodedReceipt); } return { bloom, gasUsed, receiptRoot: receiptTrie.root, receipts, results: txResults, }; } /** * Calculates block rewards for miner and ommers and puts * the updated balances of their accounts to state. */ async function assignBlockRewards(block) { if (this.DEBUG) { debug(`Assign block rewards`); } const state = this.stateManager; const minerReward = new ethereumjs_util_1.BN(this._common.param('pow', 'minerReward')); const ommers = block.uncleHeaders; // Reward ommers for (const ommer of ommers) { const reward = calculateOmmerReward(ommer.number, block.header.number, minerReward); const account = await rewardAccount(state, ommer.coinbase, reward); if (this.DEBUG) { debug(`Add uncle reward ${reward} to account ${ommer.coinbase} (-> ${account.balance})`); } } // Reward miner const reward = calculateMinerReward(minerReward, ommers.length); const account = await rewardAccount(state, block.header.coinbase, reward); if (this.DEBUG) { debug(`Add miner reward ${reward} to account ${block.header.coinbase} (-> ${account.balance})`); } } function calculateOmmerReward(ommerBlockNumber, blockNumber, minerReward) { const heightDiff = blockNumber.sub(ommerBlockNumber); let reward = new ethereumjs_util_1.BN(8).sub(heightDiff).mul(minerReward.divn(8)); if (reward.ltn(0)) { reward = new ethereumjs_util_1.BN(0); } return reward; } function calculateMinerReward(minerReward, ommersNum) { // calculate nibling reward const niblingReward = minerReward.divn(32); const totalNiblingReward = niblingReward.muln(ommersNum); const reward = minerReward.add(totalNiblingReward); return reward; } exports.calculateMinerReward = calculateMinerReward; async function rewardAccount(state, address, reward) { const account = await state.getAccount(address); account.balance.iadd(reward); await state.putAccount(address, account); return account; } exports.rewardAccount = rewardAccount; /** * Returns the encoded tx receipt. */ function encodeReceipt(tx, receipt) { var _a; const encoded = ethereumjs_util_1.rlp.encode([ (_a = receipt.stateRoot) !== null && _a !== void 0 ? _a : receipt.status, receipt.gasUsed, receipt.bitvector, receipt.logs, ]); if (!tx.supports(tx_1.Capability.EIP2718TypedTransaction)) { return encoded; } const type = (0, ethereumjs_util_1.intToBuffer)(tx.type); return Buffer.concat([type, encoded]); } exports.encodeReceipt = encodeReceipt; /** * Generates the tx receipt and returns { txReceipt, encodedReceipt, receiptLog } * @deprecated Please use the new `generateTxReceipt` located in runTx. */ async function generateTxReceipt(tx, txRes, blockGasUsed) { var _a; const abstractTxReceipt = { gasUsed: blockGasUsed.toArrayLike(Buffer), bitvector: txRes.bloom.bitvector, logs: (_a = txRes.execResult.logs) !== null && _a !== void 0 ? _a : [], }; let txReceipt; let encodedReceipt; let receiptLog = `Generate tx receipt transactionType=${tx.type} gasUsed=${blockGasUsed} bitvector=${(0, opcodes_1.short)(abstractTxReceipt.bitvector)} (${abstractTxReceipt.bitvector.length} bytes) logs=${abstractTxReceipt.logs.length}`; if (!tx.supports(999)) { // Legacy transaction if (this._common.gteHardfork('byzantium')) { // Post-Byzantium txReceipt = Object.assign({ status: txRes.execResult.exceptionError ? 0 : 1 }, abstractTxReceipt); const statusInfo = txRes.execResult.exceptionError ? 'error' : 'ok'; receiptLog += ` status=${txReceipt.status} (${statusInfo}) (>= Byzantium)`; } else { // Pre-Byzantium const stateRoot = await this.stateManager.getStateRoot(true); txReceipt = Object.assign({ stateRoot: stateRoot }, abstractTxReceipt); receiptLog += ` stateRoot=${txReceipt.stateRoot.toString('hex')} (< Byzantium)`; } encodedReceipt = ethereumjs_util_1.rlp.encode(Object.values(txReceipt)); } else { // EIP2930 Transaction txReceipt = Object.assign({ status: txRes.execResult.exceptionError ? 0 : 1 }, abstractTxReceipt); encodedReceipt = Buffer.concat([(0, ethereumjs_util_1.intToBuffer)(tx.type), ethereumjs_util_1.rlp.encode(Object.values(txReceipt))]); } return { txReceipt, encodedReceipt, receiptLog, }; } exports.generateTxReceipt = generateTxReceipt; // apply the DAO fork changes to the VM async function _applyDAOHardfork(state) { const DAORefundContractAddress = new ethereumjs_util_1.Address(Buffer.from(DAORefundContract, 'hex')); if (!state.accountExists(DAORefundContractAddress)) { await state.putAccount(DAORefundContractAddress, new ethereumjs_util_1.Account()); } const DAORefundAccount = await state.getAccount(DAORefundContractAddress); for (const addr of DAOAccountList) { // retrieve the account and add it to the DAO's Refund accounts' balance. const address = new ethereumjs_util_1.Address(Buffer.from(addr, 'hex')); const account = await state.getAccount(address); DAORefundAccount.balance.iadd(account.balance); // clear the accounts' balance account.balance = new ethereumjs_util_1.BN(0); await state.putAccount(address, account); } // finally, put the Refund Account await state.putAccount(DAORefundContractAddress, DAORefundAccount); } async function _genTxTrie(block) { const trie = new merkle_patricia_tree_1.BaseTrie(); for (const [i, tx] of block.transactions.entries()) { await trie.put(ethereumjs_util_1.rlp.encode(i), tx.serialize()); } return trie.root; } /** * Internal helper function to create an annotated error message * * @param msg Base error message * @hidden */ function _errorMsg(msg, vm, block) { const blockErrorStr = 'errorStr' in block ? block.errorStr() : 'block'; const errorMsg = `${msg} (${vm.errorStr()} -> ${blockErrorStr})`; return errorMsg; } //# sourceMappingURL=runBlock.js.map