Skip to content

Commit

Permalink
Merge pull request #133 from 0xPolygonHermez/feature/vc-improvement
Browse files Browse the repository at this point in the history
Feature/vc improvement
  • Loading branch information
krlosMata authored Feb 1, 2024
2 parents dd488e9 + 3897398 commit 8e09d92
Show file tree
Hide file tree
Showing 8 changed files with 2,121 additions and 18 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ module.exports.MTBridge = require('./src/mt-bridge');
module.exports.mtBridgeUtils = require('./src/mt-bridge-utils');
module.exports.Database = require('./src/database');
module.exports.l1InfoTreeUtils = require('./src/l1-info-tree-utils');
module.exports.VirtualCountersManager = require('./src/virtual-counters-manager');
module.exports.blockUtils = require('./src/block-utils');
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@ethereumjs/block": "^3.6.2",
"@ethereumjs/tx": "^3.4.0",
"@polygon-hermez/common": "2.6.4",
"@polygon-hermez/vm": "5.7.42",
"@polygon-hermez/vm": "6.0.11",
"ethereumjs-util": "^7.1.4",
"ethers": "^5.5.4",
"ffjavascript": "^0.2.55",
Expand Down
9 changes: 5 additions & 4 deletions src/processor-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,14 +402,15 @@ async function computeL2TxHash(tx, returnEncoded = false) {
}
// Add value
hash += `${formatL2TxHashParam(tx.value, 32)}`;
let { data } = tx;
// Compute data length
if (tx.data.startsWith('0x')) {
tx.data = tx.data.slice(2);
if (data.startsWith('0x')) {
data = data.slice(2);
}
const dataLength = Math.ceil(tx.data.length / 2);
const dataLength = Math.ceil(data.length / 2);
hash += `${formatL2TxHashParam(dataLength.toString(16), 3)}`;
if (dataLength > 0) {
hash += `${formatL2TxHashParam(tx.data, dataLength)}`;
hash += `${formatL2TxHashParam(data, dataLength)}`;
}
// Add chainID
if (typeof tx.chainID !== 'undefined') {
Expand Down
85 changes: 77 additions & 8 deletions src/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ const { Block } = require('@ethereumjs/block');
const {
Address, BN, toBuffer, bufferToInt,
} = require('ethereumjs-util');

const { cloneDeep } = require('lodash');
const { Scalar } = require('ffjavascript');
const SMT = require('./smt');
const TmpSmtDB = require('./tmp-smt-db');
const Constants = require('./constants');
const stateUtils = require('./state-utils');
const smtUtils = require('./smt-utils');
const VirtualCountersManager = require('./virtual-counters-manager');

const { getCurrentDB } = require('./smt-utils');
const { calculateAccInputHash, calculateSnarkInput, calculateBatchHashData } = require('./contract-utils');
Expand Down Expand Up @@ -74,6 +75,7 @@ module.exports = class Processor {
vm,
options,
extraData,
smtLevels,
) {
this.db = db;
this.newNumBatch = numBatch;
Expand All @@ -83,7 +85,7 @@ module.exports = class Processor {
this.F = poseidon.F;
this.tmpSmtDB = new TmpSmtDB(db);
this.smt = new SMT(this.tmpSmtDB, poseidon, poseidon.F);

this.smt.setMaxLevel(smtLevels);
this.rawTxs = [];
this.decodedTxs = [];
this.builded = false;
Expand All @@ -107,6 +109,8 @@ module.exports = class Processor {
this.l1InfoTree = {};

this.vm = vm;
this.oldVm = cloneDeep(vm);
this.oldVm.stateManager = vm.stateManager.copy();
this.evmSteps = [];
this.updatedAccounts = {};
this.isLegacyTx = false;
Expand All @@ -117,6 +121,7 @@ module.exports = class Processor {
this.txIndex = 0;
this.logIndex = 0;
this.blockInfoRoot = [this.F.zero, this.F.zero, this.F.zero, this.F.zero];
this.vcm = new VirtualCountersManager(options.vcmConfig);
}

/**
Expand Down Expand Up @@ -157,6 +162,11 @@ module.exports = class Processor {
await this._computeStarkInput();

this.builded = true;

// Check virtual counters
const virtualCounters = this.vcm.getCurrentSpentCounters();

return { virtualCounters };
}

/**
Expand Down Expand Up @@ -343,14 +353,43 @@ module.exports = class Processor {
* B: ENOUGH UPFRONT TX COST
* Process transaction will perform the following operations
* from: increase nonce
* from: substract total tx cost
* from: subtract total tx cost
* from: refund unused gas
* to: increase balance
* update state
* finally pay all the fees to the sequencer address
*/
async _processTx() {
this.vcm.setSMTLevels((2 ** this.smt.maxLevel + 50000).toString(2).length);
// Compute init processing counters
this.vcm.computeFunctionCounters('batchProcessing', { batchL2DataLength: (this.rawTxs.join('').length - this.rawTxs.length * 2) / 2 });
// Compute rlp parsing counters
for (let i = 0; i < this.decodedTxs.length; i++) {
if (this.decodedTxs[i].tx.type === Constants.TX_CHANGE_L2_BLOCK) {
this.vcm.computeFunctionCounters('decodeChangeL2BlockTx');
} else {
const txDataLen = this.decodedTxs[i].tx.data ? (this.decodedTxs[i].tx.data.length - 2) / 2 : 0;
this.vcm.computeFunctionCounters('rlpParsing', {
txRLPLength: (this.rawTxs[i].length - 2) / 2,
txDataLen,
v: Scalar.e(this.decodedTxs[i].tx.v),
r: Scalar.e(this.decodedTxs[i].tx.r),
s: Scalar.e(this.decodedTxs[i].tx.s),
gasPriceLen: (this.decodedTxs[i].tx.gasPrice.length - 2) / 2,
gasLimitLen: (this.decodedTxs[i].tx.gasLimit.length - 2) / 2,
valueLen: (this.decodedTxs[i].tx.value.length - 2) / 2,
chainIdLen: typeof this.decodedTxs[i].tx.chainID === 'undefined' ? 0 : (ethers.utils.hexlify(this.decodedTxs[i].tx.chainID).length - 2) / 2,
nonceLen: (this.decodedTxs[i].tx.nonce.length - 2) / 2,
});
}
}
for (let i = 0; i < this.decodedTxs.length; i++) {
/**
* Set vcm poseidon levels. Maximum (theoretical) levels that can be added in a tx is 50k poseidons.
* We count how much can the smt increase in a tx and compute the virtual poseidons with this worst case scenario. This is a tx full of SSTORE (20000 gas), max gas in a tx is 30M so we can do 1.5k sstore in a tx. Assuming tree already has 0 leafs, increases to 2**11. 11*1.5k = 22.5k * 2 (go up and down in the tree) = 45k poseidon, we round up to 50k for safety reasons. This happens in case the tree levels are greater than 32, in case are lower, we will put the levels to 32
*/
const maxLevelPerTx = (2 ** this.smt.maxLevel + 50000).toString(2).length < 32 ? 32 : (2 ** this.smt.maxLevel + 50000).toString(2).length;
this.vcm.setSMTLevels(maxLevelPerTx);
const currentDecodedTx = this.decodedTxs[i];

// skip verification first tx is a changeL2Block
Expand Down Expand Up @@ -419,8 +458,8 @@ module.exports = class Processor {
}
const v = this.isLegacyTx ? currentTx.v : Number(currentTx.v) - 27 + currentTx.chainID * 2 + 35;

const bytecodeLength = await stateUtils.getContractBytecodeLength(currentTx.from, this.smt, this.currentStateRoot);
if (bytecodeLength > 0) {
const bytecodeLen = await stateUtils.getContractBytecodeLength(currentTx.from, this.smt, this.currentStateRoot);
if (bytecodeLen > 0) {
currentDecodedTx.isInvalid = true;
currentDecodedTx.reason = 'TX INVALID: EIP3607 Do not allow transactions for which tx.sender has any code deployed';
continue;
Expand Down Expand Up @@ -461,8 +500,25 @@ module.exports = class Processor {

const evmBlock = Block.fromBlockData(blockData, { common: evmTx.common });
try {
const txResult = await this.vm.runTx({ tx: evmTx, block: evmBlock, effectivePercentage: currentTx.effectivePercentage });
this.evmSteps.push(txResult.execResult.evmSteps);
// init custom counters snapshot
this.vcm.initCustomSnapshot(i);
const txResult = await this.vm.runTx({
tx: evmTx, block: evmBlock, effectivePercentage: currentTx.effectivePercentage, vcm: this.vcm,
});
// Compute counters
let bytecodeLength;
let isDeploy = false;
if (typeof currentDecodedTx.tx.to === 'undefined' || currentDecodedTx.tx.to.length <= 2) {
bytecodeLength = txResult.execResult.returnValue.length;
isDeploy = true;
} else {
bytecodeLength = await stateUtils.getContractBytecodeLength(currentDecodedTx.tx.to, this.smt, this.currentStateRoot);
}
this.vcm.computeFunctionCounters('processTx', { bytecodeLength, isDeploy });
this.evmSteps.push({
steps: txResult.execResult.evmSteps,
counters: this.vcm.computeCustomSnapshotConsumption(i),
});

currentDecodedTx.receipt = txResult.receipt;
currentDecodedTx.createdAddress = txResult.createdAddress;
Expand Down Expand Up @@ -621,8 +677,16 @@ module.exports = class Processor {
await this.consolidateBlock();
}
}
/**
* We check how much the smt has increased. In case it has increased more than expected, it means we may have a pow attack so we have to recompute counters cost with this new smt levels
* We re run the batch with a new maxLevel value at the smt
*/
if (this.smt.maxLevel > maxLevelPerTx) {
console.log('WARNING: smt levels increased more than expected, re running batch with new smt levels');
this._rollbackBatch();
i = -1;
}
}

await this.consolidateBlock();
}

Expand Down Expand Up @@ -708,6 +772,9 @@ module.exports = class Processor {
}

async _processChangeL2BlockTx(tx) {
// Reduce counters
this.vcm.computeFunctionCounters('processChangeL2Block');

// write old blockhash (oldStateRoot) on storage
// Get old blockNumber
const oldBlockNumber = await stateUtils.getContractStorage(
Expand Down Expand Up @@ -867,6 +934,8 @@ module.exports = class Processor {

_rollbackBatch() {
this.currentStateRoot = this.oldStateRoot;
this.vm = cloneDeep(this.oldVm);
this.vm.stateManager = this.oldVm.stateManager.copy();
this.updatedAccounts = {};
}

Expand Down
24 changes: 19 additions & 5 deletions src/smt.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ class SMT {
this.hash = hash;
this.F = F;
this.empty = [F.zero, F.zero, F.zero, F.zero];
// set init smt level to 0
this.maxLevel = 0;
}

/**
* Set current smt level
* @param {Number} level max smt level
*/
setMaxLevel(level) {
this.maxLevel = level;
}

/**
Expand All @@ -31,12 +41,12 @@ class SMT {
* {Array[Field]} key modified,
* {Array[Array[Field]]} siblings: array of siblings,
* {Array[Field]} insKey: inserted key,
* {Scalar} insValue: insefted value,
* {Scalar} insValue: inserted value,
* {Bool} isOld0: is new insert or delete,
* {Scalar} oldValue: old leaf value,
* {Scalar} newValue: new leaf value,
* {String} mode: action performed by the insertion,
* {Number} proofHashCounter: counter of hashs must be done to proof this operation
* {Number} proofHashCounter: counter of hash must be done to proof this operation
*/
async set(oldRoot, key, value) {
const self = this;
Expand Down Expand Up @@ -101,7 +111,6 @@ class SMT {

level -= 1;
accKey.pop();

// calculate hash to validate oldRoot
proofHashCounter = nodeIsZero(oldRoot, F) ? 0 : (siblings.slice(0, level + 1).length + (((foundVal ?? 0n) === 0n) ? 0 : 2));

Expand Down Expand Up @@ -270,6 +279,11 @@ class SMT {
}
}

// Update max level in case last insertion increased smt size
if (this.maxLevel < siblings.length) {
this.maxLevel = siblings.length;
}

return {
oldRoot,
newRoot,
Expand All @@ -290,7 +304,7 @@ class SMT {
/**
* Get value merkle-tree
* @param {Array[Field]} root - merkle-tree root
* @param {Array[Field]} key - path to retoreve the value
* @param {Array[Field]} key - path to retrieve the value
* @returns {Object} Information about the value to retrieve
* {Array[Field]} root: merkle-tree root,
* {Array[Field]} key: key to look for,
Expand All @@ -299,7 +313,7 @@ class SMT {
* {Bool} isOld0: is new insert or delete,
* {Array[Field]} insKey: key found,
* {Scalar} insValue: value found,
* {Number} proofHashCounter: counter of hashs must be done to proof this operation
* {Number} proofHashCounter: counter of hash must be done to proof this operation
*/
async get(root, key) {
const self = this;
Expand Down
Loading

0 comments on commit 8e09d92

Please sign in to comment.