vaporyjs-vm
Version:
an vapory VM implementation
210 lines (179 loc) • 5.94 kB
JavaScript
const async = require('async')
const vapUtil = require('vaporyjs-util')
const Bloom = require('./bloom.js')
const common = require('vapory-common')
const rlp = vapUtil.rlp
const Trie = require('@vaporyjs/merkle-patricia-tree')
const BN = vapUtil.BN
const minerReward = new BN(common.minerReward.v)
const niblingReward = new BN(common.niblingReward.v)
const ommerReward = new BN(common.ommerReward.v)
/**
* process the transaction in a block and pays the miners
* @param opts
* @param opts.block {Block} the block we are processing
* @param opts.generate {Boolean} [gen=false] whether to generate the stateRoot
* @param cb {Function} the callback which is given an error string
*/
module.exports = function (opts, cb) {
const self = this
// parse options
const block = opts.block
const generateStateRoot = !!opts.generate
const validateStateRoot = !generateStateRoot
const bloom = new Bloom()
const receiptTrie = new Trie()
// the total amount of gas used processing this block
var gasUsed = new BN(0)
// miner account
var minerAccount
var receipts = []
var txResults = []
var result
if (opts.root) {
self.stateManager.trie.root = opts.root
}
this.trie.checkpoint()
// run everything
async.series([
beforeBlock,
populateCache,
processTransactions
], parseBlockResults)
function beforeBlock (cb) {
self.emit('beforeBlock', opts.block, cb)
}
function afterBlock (cb) {
self.emit('afterBlock', result, cb)
}
// populates the cache with accounts that we know we will need
function populateCache (cb) {
var accounts = new Set()
accounts.add(block.header.coinbase.toString('hex'))
block.transactions.forEach(function (tx) {
accounts.add(tx.getSenderAddress().toString('hex'))
accounts.add(tx.to.toString('hex'))
})
block.uncleHeaders.forEach(function (uh) {
accounts.add(uh.coinbase.toString('hex'))
})
self.populateCache(accounts, cb)
}
/**
* Processes all of the transaction in the block
* @method processTransaction
* @param {Function} cb the callback is given error if there are any
*/
function processTransactions (cb) {
var validReceiptCount = 0
async.eachSeries(block.transactions, processTx, cb)
function processTx (tx, cb) {
var gasLimitIsHigherThanBlock = new BN(block.header.gasLimit).cmp(new BN(tx.gasLimit).add(gasUsed)) === -1
if (gasLimitIsHigherThanBlock) {
cb('tx has a higher gas limit than the block')
return
}
// run the tx through the VM
self.runTx({
tx: tx,
block: block,
populateCache: false
}, parseTxResult)
function parseTxResult (err, result) {
txResults.push(result)
// abort if error
if (err) {
receipts.push(null)
cb(err)
return
}
gasUsed = gasUsed.add(result.gasUsed)
// combine blooms via bitwise OR
bloom.or(result.bloom)
if (generateStateRoot) {
block.header.bloom = bloom.bitvector
}
var txLogs = result.vm.logs || []
var rawTxReceipt = [
self.trie.root,
Buffer.from(gasUsed.toArray()),
result.bloom.bitvector,
txLogs
]
var txReceipt = {
stateRoot: rawTxReceipt[0],
gasUsed: rawTxReceipt[1],
bitvector: rawTxReceipt[2],
logs: rawTxReceipt[3]
}
receipts.push(txReceipt)
receiptTrie.put(rlp.encode(validReceiptCount), rlp.encode(rawTxReceipt))
validReceiptCount++
cb()
}
}
}
// handle results or error from block run
function parseBlockResults (err) {
if (err) {
self.trie.revert()
cb(err)
return
}
// credit all block rewards
payOmmersAndMiner()
// credit all block rewards
if (generateStateRoot) {
block.header.stateRoot = self.trie.root
}
self.trie.commit(function (err) {
self.stateManager.cache.flush(function () {
if (validateStateRoot) {
if (receiptTrie.root && receiptTrie.root.toString('hex') !== block.header.receiptTrie.toString('hex')) {
err = (err || '') + 'invalid receiptTrie '
}
if (bloom.bitvector.toString('hex') !== block.header.bloom.toString('hex')) {
err = (err || '') + 'invalid bloom '
}
if (vapUtil.bufferToInt(block.header.gasUsed) !== Number(gasUsed)) {
err = (err || '') + 'invalid gasUsed '
}
if (self.trie.root.toString('hex') !== block.header.stateRoot.toString('hex')) {
err = (err || '') + 'invalid block stateRoot '
}
}
self.stateManager.cache.clear()
result = {
receipts: receipts,
results: txResults,
error: err
}
afterBlock(cb.bind(this, err, result))
})
})
}
// credit all block rewards
function payOmmersAndMiner () {
var ommers = block.uncleHeaders
// pay each ommer
ommers.forEach(rewardOmmer)
// calculate nibling reward
var totalNiblingReward = niblingReward.mul(new BN(ommers.length))
minerAccount = self.stateManager.cache.get(block.header.coinbase)
// give miner the block reward
minerAccount.balance = new BN(minerAccount.balance)
.add(minerReward)
.add(totalNiblingReward)
self.stateManager.cache.put(block.header.coinbase, minerAccount)
}
// credit ommer
function rewardOmmer (ommer) {
// calculate reward
var heightDiff = new BN(block.header.number).sub(new BN(ommer.number))
var reward = minerReward.sub(ommerReward.mul(heightDiff))
// credit miners account
var ommerAccount = self.stateManager.cache.get(ommer.coinbase)
ommerAccount.balance = reward.add(new BN(ommerAccount.balance))
self.stateManager.cache.put(ommer.coinbase, ommerAccount)
}
}