diff --git a/.gitignore b/.gitignore index 44b25244..a7152e17 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ testvectors/commit.bin testvectors/constants.bin tmp pil-config.json +parallel-tests \ No newline at end of file diff --git a/package.json b/package.json index 66e64055..a88bc7e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@0xpolygonhermez/zkevm-proverjs", - "version": "0.0.7", + "version": "0.4.0", "description": "JS version of the zkProver.", "main": "index.js", "scripts": { @@ -14,8 +14,8 @@ "exec": ". ./pre.sh && INPUT=${npm_config_input:=tools/build-genesis/input_executor.json} && echo \"input: $INPUT\" && node $NODE src/main_executor $INPUT $PIL -r $BDIR/rom.json -o $BDIR/zkevm.commit && cp $INPUT $BDIR/input_executor.json", "pilverify": ". ./pre.sh && $PILCOM/main_pilverifier.js $BDIR/zkevm.commit -c $BDIR/zkevm.const $PIL", "buildstarkinfo": ". ./pre.sh && $PILSTARK/main_genstarkinfo.js $PIL -s $BDIR/zkevm.starkstruct.json -i $BDIR/zkevm.starkinfo.json", - "buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js $PIL -s $BDIR/zkevm.starkstruct.json -c $BDIR/zkevm.chelpers.cpp", - "buildconstanttree": ". ./pre.sh && $PILSTARK/main_buildconsttree.js -c $BDIR/zkevm.const $PIL -s $BDIR/zkevm.starkstruct.json -t $BDIR/zkevm.consttree -v $BDIR/zkevm.verkey.json", + "buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js -m $PIL -s $BDIR/zkevm.starkstruct.json -c $BDIR/zkevm.chelpers/zkevm.chelpers.cpp", + "buildconstanttree": ". ./pre.sh && $BCTREE -c $BDIR/zkevm.const $PIL -s $BDIR/zkevm.starkstruct.json -t $BDIR/zkevm.consttree -v $BDIR/zkevm.verkey.json", "prove": ". ./pre.sh && $PILSTARK/main_prover.js -m $BDIR/zkevm.commit -c $BDIR/zkevm.const -t $BDIR/zkevm.consttree $PIL -s $BDIR/zkevm.starkstruct.json -o $BDIR/zkevm.proof.json -z $BDIR/zkevm.zkin.proof.json -b $BDIR/zkevm.public.json", "verify": ". ./pre.sh && $PILSTARK/main_verifier.js $PIL -s $BDIR/zkevm.starkstruct.json -o $BDIR/zkevm.proof.json -b $BDIR/zkevm.public.json -v $BDIR/zkevm.verkey.json", "gencircom": ". ./pre.sh && $PILSTARK/main_pil2circom.js $PIL -s $BDIR/zkevm.starkstruct.json -v $BDIR/zkevm.verkey.json -o $BDIR/zkevm.verifier.circom", @@ -23,21 +23,21 @@ "c12a_setup": ". ./pre.sh && $PILSTARK/compressor12/main_compressor12_setup.js -r $BDIR/zkevm.verifier.r1cs -p $BDIR/zkevm.c12a.pil -c $BDIR/zkevm.c12a.const -e $BDIR/zkevm.c12a.exec", "c12a_buildstarkinfo": ". ./pre.sh && $PILSTARK/main_genstarkinfo.js -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -i $BDIR/zkevm.c12a.starkinfo.json", - "c12a_buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -c $BDIR/zkevm.c12a.chelpers.cpp", + "c12a_buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js -m -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -c $BDIR/zkevm.c12a.chelpers/zkevm.c12a.chelpers.cpp", "c12a_exec": ". ./pre.sh && $PILSTARK/compressor12/main_compressor12_exec.js -i $BDIR/zkevm.zkin.proof.json -w $BDIR/zkevm.verifier_js/zkevm.verifier.wasm -p $BDIR/zkevm.c12a.pil -e $BDIR/zkevm.c12a.exec -m $BDIR/zkevm.c12a.commit", "c12a_pilverify": ". ./pre.sh && $PILCOM/main_pilverifier.js $BDIR/zkevm.c12a.commit -c $BDIR/zkevm.c12a.const -p $BDIR/zkevm.c12a.pil", - "c12a_buildconstanttree": ". ./pre.sh && $PILSTARK/main_buildconsttree.js -c $BDIR/zkevm.c12a.const -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -t $BDIR/zkevm.c12a.consttree -v $BDIR/zkevm.c12a.verkey.json", - "c12a_prove": ". ./pre.sh && $PILSTARK/main_prover.js -m $BDIR/zkevm.c12a.commit -c $BDIR/zkevm.c12a.const -t $BDIR/zkevm.c12a.consttree -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -o $BDIR/zkevm.c12a.proof.json -z $BDIR/zkevm.c12a.zkin.proof.json -b $BDIR/zkevm.c12a.public.json --proverAddr=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "c12a_buildconstanttree": ". ./pre.sh && $BCTREE -c $BDIR/zkevm.c12a.const -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -t $BDIR/zkevm.c12a.consttree -v $BDIR/zkevm.c12a.verkey.json", + "c12a_prove": ". ./pre.sh && $PILSTARK/main_prover.js -m $BDIR/zkevm.c12a.commit -c $BDIR/zkevm.c12a.const -t $BDIR/zkevm.c12a.consttree -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -o $BDIR/zkevm.c12a.proof.json -z $BDIR/zkevm.c12a.zkin.proof.json -b $BDIR/zkevm.c12a.public.json", "c12a_verify": ". ./pre.sh && $PILSTARK/main_verifier.js -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -o $BDIR/zkevm.c12a.proof.json -b $BDIR/zkevm.c12a.public.json -v $BDIR/zkevm.c12a.verkey.json", "c12a_gencircom": ". ./pre.sh && $PILSTARK/main_pil2circom.js -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json -v $BDIR/zkevm.c12a.verkey.json -o $BDIR/zkevm.c12a.verifier.circom", "c12a_compilecircom": ". ./pre.sh && circom --r1cs --sym --wasm --c --verbose --O1 --prime goldilocks $BDIR/zkevm.c12a.verifier.circom -o $BDIR -l node_modules/pil-stark/circuits.gl", "c12b_setup": ". ./pre.sh && $PILSTARK/compressor12/main_compressor12_setup.js -r $BDIR/zkevm.c12a.verifier.r1cs -p $BDIR/zkevm.c12b.pil -c $BDIR/zkevm.c12b.const -e $BDIR/zkevm.c12b.exec", "c12b_buildstarkinfo": ". ./pre.sh && $PILSTARK/main_genstarkinfo.js -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -i $BDIR/zkevm.c12b.starkinfo.json", - "c12b_buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -c $BDIR/zkevm.c12b.chelpers.cpp", + "c12b_buildchelpers": ". ./pre.sh && $PILSTARK/main_buildchelpers.js -m -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -c $BDIR/zkevm.c12b.chelpers/zkevm.c12b.chelpers.cpp", "c12b_exec": ". ./pre.sh && $PILSTARK/compressor12/main_compressor12_exec.js -i $BDIR/zkevm.c12a.zkin.proof.json -w $BDIR/zkevm.c12a.verifier_js/zkevm.c12a.verifier.wasm -p $BDIR/zkevm.c12b.pil -e $BDIR/zkevm.c12b.exec -m $BDIR/zkevm.c12b.commit", "c12b_pilverify": ". ./pre.sh && $PILCOM/main_pilverifier.js $BDIR/zkevm.c12b.commit -c $BDIR/zkevm.c12b.const -p $BDIR/zkevm.c12b.pil", - "c12b_buildconstanttree": ". ./pre.sh && $PILSTARK/main_buildconsttree.js -c $BDIR/zkevm.c12b.const -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -t $BDIR/zkevm.c12b.consttree -v $BDIR/zkevm.c12b.verkey.json", + "c12b_buildconstanttree": ". ./pre.sh && $BCTREE -c $BDIR/zkevm.c12b.const -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -t $BDIR/zkevm.c12b.consttree -v $BDIR/zkevm.c12b.verkey.json", "c12b_prove": ". ./pre.sh && $PILSTARK/main_prover.js -m $BDIR/zkevm.c12b.commit -c $BDIR/zkevm.c12b.const -t $BDIR/zkevm.c12b.consttree -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -o $BDIR/zkevm.c12b.proof.json -z $BDIR/zkevm.c12b.zkin.proof.json -b $BDIR/zkevm.c12b.public.json --proverAddr=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "c12b_verify": ". ./pre.sh && $PILSTARK/main_verifier.js -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -o $BDIR/zkevm.c12b.proof.json -b $BDIR/zkevm.c12b.public.json -v $BDIR/zkevm.c12b.verkey.json", "c12b_gencircom": ". ./pre.sh && $PILSTARK/main_pil2circom.js -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json -v $BDIR/zkevm.c12b.verkey.json -o $BDIR/zkevm.c12b.verifier.circom", @@ -55,7 +55,13 @@ "prebuildstarkinfo": ". ./pre.sh && if [ \"$npm_config_starkstruct\" = \"debug\" ]; then node tools/gen_debug_starkstruct.js -t GL $PIL -s $BDIR/zkevm.starkstruct.json; else cp src/zkevm.starkstruct.json $BDIR; fi", "prec12a_buildstarkinfo": ". ./pre.sh && if [ \"$npm_config_starkstruct\" = \"debug\" ]; then node tools/gen_debug_starkstruct.js -t GL -p $BDIR/zkevm.c12a.pil -s $BDIR/zkevm.c12a.starkstruct.json; else cp src/zkevm.c12a.starkstruct.json $BDIR; fi", "prec12b_buildstarkinfo": ". ./pre.sh && if [ \"$npm_config_starkstruct\" = \"debug\" ]; then node tools/gen_debug_starkstruct.js -t BN128 -p $BDIR/zkevm.c12b.pil -s $BDIR/zkevm.c12b.starkstruct.json; else cp src/zkevm.c12b.starkstruct.json $BDIR; fi", + + "buildsetup:basic": "npm run buildsetup --pil=pil/basic_main.pil --build=build/basic_proof --starkstruct=debug", + "buildall:basic": "npm run buildall --pil=pil/basic_main.pil --build=build/basic_proof --starkstruct=debug", + "buildproof:basic": "npm run buildproof --pil=pil/basic_main.pil --build=build/basic_proof --starkstruct=debug", + "test": "mocha", + "test:storage": "mocha test/sm_storage_test.js", "test:mem_align": "mocha test/sm_mem_align_test.js", "test:counters:arith": "mocha test/counters/arith.js", @@ -65,14 +71,16 @@ "test:counters:padding_pg": "mocha test/counters/padding_pg.js", "test:counters:poseidon": "mocha test/counters/poseidon.js", "test:counters:storage": "mocha test/counters/storage.js", - "test:all-inputs": "mocha --timeout 0 --max-old-space-size=8000 tools/run-test/run-inputs-mocha.test.js" + "test:all-inputs": "mocha --timeout 0 --max-old-space-size=8000 tools/run-test/run-inputs-mocha.test.js", + "test:gen-parallel": "node tools/run-test/gen-parallel-tests.js", + "test:run-parallel": "mocha --jobs 2 --timeout 0 --max-old-space-size=8000 --parallel tools/run-test/parallel-tests/*.test.js" }, "author": "Jordi Baylina", "license": "UNLICENSED", "dependencies": { - "@0xpolygonhermez/zkasmcom": "https://github.com/0xPolygonHermez/zkasmcom.git#mandarin", - "@0xpolygonhermez/zkevm-commonjs": "https://github.com/0xpolygonhermez/zkevm-commonjs.git", - "@0xpolygonhermez/zkevm-rom": "https://github.com/0xpolygonhermez/zkevm-rom.git", + "@0xpolygonhermez/zkasmcom": "https://github.com/0xPolygonHermez/zkasmcom.git#v0.4.0.0", + "@0xpolygonhermez/zkevm-commonjs": "https://github.com/0xpolygonhermez/zkevm-commonjs.git#v0.4.0.1", + "@0xpolygonhermez/zkevm-rom": "https://github.com/0xpolygonhermez/zkevm-rom.git#v0.4.0.0", "chalk": "^3.0.0", "circomlib": "^2.0.3", "circomlibjs": "0.1.1", @@ -80,15 +88,15 @@ "ethers": "^5.4.7", "ffjavascript": "^0.2.55", "fs": "^0.0.1-security", - "pil-stark": "^0.0.25", - "pilcom": "^0.0.17", - "snarkjs": "^0.4.24", + "pil-stark": "0.0.25", + "pilcom": "0.0.18", + "snarkjs": "0.4.27", "yargs": "^17.4.0" }, "devDependencies": { "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "circom_tester": "^0.0.9", "mocha": "^9.1.3" } } - diff --git a/pre.sh b/pre.sh index aaf9039c..2087639f 100644 --- a/pre.sh +++ b/pre.sh @@ -10,4 +10,5 @@ PIL="-p ${npm_config_pil:=pil/main.pil}`[ ! -z $npm_config_pilconfig ] && echo \ PILSTARK="node $NODE node_modules/pil-stark/src" PILCOM="node $NODE node_modules/pilcom/src" SNARKJS="node $NODE node_modules/snarkjs/cli.js" +BCTREE="${npm_config_bctree:=$PILSTARK/main_buildconsttree.js}" true \ No newline at end of file diff --git a/src/main_executor.js b/src/main_executor.js index 950a6d45..83db90a2 100644 --- a/src/main_executor.js +++ b/src/main_executor.js @@ -28,7 +28,7 @@ const fileCachePil = path.join(__dirname, "../cache-main-pil.json"); const argv = require("yargs") .version(version) - .usage("main_executor -r -o -t -l -s -d [-p ] [-P ] -u -e -v") + .usage("main_executor -r -o -t -l -s -d [-p ] [-P ] -u -e -v -T") .alias("o", "output") .alias("r", "rom") .alias("t", "test") @@ -39,8 +39,8 @@ const argv = require("yargs") .alias("P", "pilconfig") .alias("u", "unsigned") .alias("e", "execute") - .alias("P", "pilconfig") .alias("v", "verbose") + .alias("T", "tracer") .argv; async function run() { @@ -106,7 +106,8 @@ async function run() { inputName: path.basename(inputFile, ".json") }, unsigned: (argv.unsigned === true), - execute: (argv.execute === true) + execute: (argv.execute === true), + tracer: (argv.tracer === true) } const N = cmPols.Main.PC.length; diff --git a/src/sm/sm_main/debug/full-tracer.js b/src/sm/sm_main/debug/full-tracer.js index a48e6cfb..7e8c690f 100644 --- a/src/sm/sm_main/debug/full-tracer.js +++ b/src/sm/sm_main/debug/full-tracer.js @@ -84,11 +84,7 @@ class FullTracer { return; } this.info[this.info.length - 1].error = errorName; - // Dont decrease depth if the error is from processing a RETURN opcode - const lastOpcode = this.info[this.info.length - 1] - if (!opDecContext.includes(lastOpcode.opcode)) { - this.depth-- - } + // Revert logs this.logs[ctx.CTX] = null } @@ -223,7 +219,12 @@ class FullTracer { }; //Set consumed tx gas - response.gas_used = String(Number(response.gas_left) - Number(ctx.GAS)); + if(Number(ctx.GAS) > Number(response.gas_left)) { + response.gas_used = String(Number(response.gas_left)); + } else { + response.gas_used = String(Number(response.gas_left) - Number(ctx.GAS)); + } + response.call_trace.context.gas_used = response.gas_used; this.accBatchGas += Number(response.gas_used); @@ -368,13 +369,14 @@ class FullTracer { const finalMemory = []; const lengthMemOffset = findOffsetLabel(ctx.rom.program, "memLength"); const lenMemValue = ctx.mem[offsetCtx + lengthMemOffset]; - const lenMemValueFinal = typeof lenMemValue === "undefined" ? 0 : Number(fea2scalar(ctx.Fr, lenMemValue)); - + const lenMemValueFinal = typeof lenMemValue === "undefined" ? 0 : Math.ceil(Number(fea2scalar(ctx.Fr, lenMemValue))/32); for (let i = 0; i < lenMemValueFinal; i++) { const memValue = ctx.mem[addrMem + i]; - if (typeof memValue === "undefined") + if (typeof memValue === "undefined") { + finalMemory.push("0".padStart(64, "0")) continue; + } let memScalar = fea2scalar(ctx.Fr, memValue); let hexString = memScalar.toString(16); hexString = hexString.length % 2 ? `0${hexString}` : hexString; @@ -399,6 +401,7 @@ class FullTracer { } // add info opcodes + this.depth = Number(getVarFromCtx(ctx, true, "depth")); singleInfo.depth = this.depth; singleInfo.pc = Number(ctx.PC); singleInfo.remaining_gas = ctx.GAS.toString(); @@ -438,6 +441,7 @@ class FullTracer { singleInfo.contract.data = getCalldataFromStack(ctx); singleInfo.contract.gas = this.txGAS[this.depth]; singleInfo.storage = JSON.parse(JSON.stringify(this.deltaStorage[this.depth])); + // Round up to next multiple of 32 singleInfo.memory_size = String(Math.ceil(Number(getVarFromCtx(ctx, false, "memLength")) / 32) * 32); singleInfo.counters = { @@ -481,13 +485,8 @@ class FullTracer { } } - //Check opcodes that alter depth - if (opDecContext.includes(singleInfo.opcode)) { - this.depth--; - } if (opIncContext.includes(singleInfo.opcode)) { - this.depth++; - this.deltaStorage[this.depth] = {}; + this.deltaStorage[this.depth + 1] = {}; } } diff --git a/src/sm/sm_main/debug/tracer.js b/src/sm/sm_main/debug/tracer.js deleted file mode 100644 index c99a1341..00000000 --- a/src/sm/sm_main/debug/tracer.js +++ /dev/null @@ -1,128 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const { smtUtils } = require("@0xpolygonhermez/zkevm-commonjs"); -const codes = require("./opcodes"); - -class Tracer { - - constructor (logFileName){ - this.info = []; - this.fullStack = []; - this.trace = []; - this.fileName = "process-tx.zkasm"; - this.folderLogs = path.join(__dirname, "../logs-trace"); - this.pathLogFile = path.join(this.folderLogs, `${logFileName}__trace.json`); - this.labels = {}; - } - - async getTrace(ctx, romStep , print = false){ - if (romStep.offsetLabel === "mapping_opcodes" && ctx.fileName.includes(this.fileName)){ - let singleTrace = {}; - const singleInfo = {}; - - const offsetCtx = Number(ctx.CTX) * 0x40000; - - const codeId = ctx.RR; - const opcode = codes[codeId]; - - // store memory - let addrMem = 0; - addrMem += offsetCtx; - addrMem += 0x30000; - - const finalMemory = []; - const lengthMemOffset = this._findOffsetLabel(ctx.rom.program ,"memLength"); - const lenMemValue = ctx.mem[offsetCtx + lengthMemOffset]; - const lenMemValueFinal = typeof lenMemValue === "undefined" ? 0 : Number(smtUtils.fea2scalar(ctx.Fr, lenMemValue)); - - - for (let i = 0; i < lenMemValueFinal; i++) { - const memValue = ctx.mem[addrMem + i]; - if (typeof memValue === "undefined") - continue; - let memScalar = smtUtils.fea2scalar(ctx.Fr, memValue); - let hexString = memScalar.toString(16); - hexString = hexString.length % 2 ? `0${hexString}` : hexString; - finalMemory.push(`0x${hexString}`); - } - - // store stack - let addr = 0; - addr += offsetCtx; - addr += 0x20000; - - const finalStack = []; - - for (let i = 0; i < ctx.SP; i++) { - const stack = ctx.mem[addr + i]; - if (typeof stack === "undefined") - continue; - let stackScalar = smtUtils.fea2scalar(ctx.Fr, stack); - let hexString = stackScalar.toString(16); - hexString = hexString.length % 2 ? `0${hexString}` : hexString; - finalStack.push(`0x${hexString}`); - } - - // add info opcodes - singleInfo.pc = Number(ctx.PC) - 1; - singleInfo.opcode = opcode.toString(); - singleInfo.gasLeft = Number(ctx.GAS.toString()); - - // add ctx vars - const varsToAdd = ["gasRefund"]; - - for (let i = 0; i < varsToAdd.length; i++){ - const varLabel = varsToAdd[i]; - const offsetRelative = this._findOffsetLabel(ctx.rom.program ,varLabel); - const addressMem = offsetCtx + offsetRelative; - const value = ctx.mem[addressMem]; - const finaValue = typeof value === "undefined" ? 0 : Number(smtUtils.fea2scalar(ctx.Fr, value)); - singleInfo[varLabel] = finaValue; - } - - // add debug info - singleInfo.step = ctx.step; - - this.info.push(singleInfo); - this.fullStack.push(finalStack); - - // build trace - const index = this.fullStack.length; - - if (index > 1){ - singleTrace = this.info[index - 2]; - singleTrace.stack = finalStack; - singleTrace.memory = finalMemory; - this.trace.push(singleTrace); - - if (print){ - console.log(JSON.stringify(singleTrace, null, 2)); - } - } - } - } - - exportTrace(){ - if (!fs.existsSync(this.folderLogs)){ - fs.mkdirSync(this.folderLogs) - } - fs.writeFileSync(this.pathLogFile, JSON.stringify(this.trace, null, 2)); - } - - _findOffsetLabel(program, label){ - if (typeof this.labels[label] !== "undefined"){ - return this.labels[label]; - } - - for (let i = 0; i < program.length; i++){ - if (program[i].offsetLabel === label){ - this.labels[label] = program[i].offset; - return program[i].offset; - } - } - - return null; - } -} - -module.exports = Tracer; \ No newline at end of file diff --git a/src/sm/sm_main/sm_main_exec.js b/src/sm/sm_main/sm_main_exec.js index d1d8b698..108fb804 100644 --- a/src/sm/sm_main/sm_main_exec.js +++ b/src/sm/sm_main/sm_main_exec.js @@ -12,7 +12,6 @@ const { byteArray2HexString } = require("@0xpolygonhermez/zkevm-commonjs").utils const testTools = require("./test_tools"); -const Tracer = require("./debug/tracer"); const FullTracer = require("./debug/full-tracer"); const Prints = require("./debug/prints"); @@ -22,7 +21,6 @@ const twoTo256 = Scalar.shl(Scalar.one, 256); const Mask256 = Scalar.sub(Scalar.shl(Scalar.e(1), 256), 1); const byteMaskOn256 = Scalar.bor(Scalar.shl(Mask256, 256), Scalar.shr(Mask256, 8n)); -let iTracer; let fullTracer; module.exports = async function execute(pols, input, rom, config = {}) { @@ -44,7 +42,9 @@ module.exports = async function execute(pols, input, rom, config = {}) { } const debug = config && config.debug; + const flagTracer = config && config.tracer; const N = pols.zkPC.length; + const stepsN = (debug && config.stepsN) ? config.stepsN : N; if (config && config.unsigned){ if (typeof input.from === 'undefined'){ @@ -78,22 +78,23 @@ module.exports = async function execute(pols, input, rom, config = {}) { sto: input.keys, rom: rom, outLogs: {}, - N: N + N, + stepsN } preprocessTxs(ctx); - if (debug) { - iTracer = new Tracer(config.debugInfo.inputName); + if (debug && flagTracer) { fullTracer = new FullTracer(config.debugInfo.inputName) - } else { - iTracer = null } + const iPrint = new Prints(ctx, smt); + let fastDebugExit = false; - for (i=0; i "+l.fileName + ':' + l.line); } - if (iTracer) - await iTracer.getTrace(ctx, l, false); - if (l.cmdBefore) { for (let j=0; j< l.cmdBefore.length; j++) { evalCommand(ctx, l.cmdBefore[j]); @@ -572,7 +571,7 @@ module.exports = async function execute(pols, input, rom, config = {}) { ctx.lastSWrite.newRoot = res.newRoot; ctx.lastSWrite.res = res; - ctx.lastSWrite.step=i; + ctx.lastSWrite.step = step; fi = sr4to8(ctx.Fr, ctx.lastSWrite.newRoot); nHits++; @@ -585,9 +584,9 @@ module.exports = async function execute(pols, input, rom, config = {}) { if ((size<0) || (size>32)) throw new Error(`Invalid size for hash: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); if (pos+size > ctx.hashK[addr].data.length) throw new Error(`Accessing hashK out of bounds ${addr}, ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); let s = Scalar.zero; - for (let i=0; i32)) throw new Error(`Invalid size for hash: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); if (pos+size > ctx.hashP[addr].data.length) throw new Error(`Accessing hashP out of bounds ${addr}, ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); let s = Scalar.zero; - for (let i=0; i32)) throw new Error(`Invalid size for hashK: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); const a = fea2scalar(Fr, [op0, op1, op2, op3, op4, op5, op6, op7]); const maskByte = Scalar.e("0xFF"); - for (let i=0; i32)) throw new Error(`Invalid size for hash: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`); const a = fea2scalar(Fr, [op0, op1, op2, op3, op4, op5, op6, op7]); const maskByte = Scalar.e("0xFF"); - for (let i=0; i { + const addressExists = mapTarget.get(addressString) + if (!addressExists) { + mapTarget.set(addressString, new Set()) + } + const storageSet = mapTarget.get(addressString) + slotSet.forEach((value) => { + storageSet.add(value) + }) + }) + } + } + return scalar2fea(ctx.Fr, Scalar.e(0)); +} + +/** + * Revert accessedStorage to last checkpoint + * @param {Object} ctx current rom context object + */ +function eval_revert(ctx) { + ctx.input.accessedStorage.pop() + return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; +} +/** + * Checks if the address is warm or cold. In case of cold, the address is added as warm + * @param {Object} ctx current rom context object + * @param {Object} tag tag inputs in rom function + * @returns {FEA} returns 0 if address is warm, 1 if cold + */ +function eval_isWarmedAddress(ctx, tag) { + if (tag.params.length != 1) throw new Error(`Invalid number of parameters function ${tag.funcName}: ${ctx.ln}`) + const address = evalCommand(ctx, tag.params[0]); + const addr = address.toString(16) // if address is precompiled smart contract considered warm access - if (Scalar.gt(addr, 0) && Scalar.lt(addr, 10)){ + if (Scalar.gt(address, 0) && Scalar.lt(address, 10)) { return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } - // if address in touchedAddress return 0 - - if(ctx.input.touchedAddress && ctx.input.touchedAddress.filter(x => x == addr).length > 0) { - return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; - } else { - //if address not in touchedAddress, return 1 - if(ctx.input.touchedAddress) { - ctx.input.touchedAddress.push(addr); - } else { - ctx.input.touchedAddress = [addr]; + // if address is warm return 0 + for (let i = ctx.input.accessedStorage.length - 1; i >= 0; i--) { + const currentMap = ctx.input.accessedStorage[i] + if (currentMap.has(addr)) { + return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } - return [ctx.Fr.e(1), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } + //if address is not warm, return 1 and add it as warm. We add an emtpy set because is a warmed address (not warmed slot) + const storageSet = ctx.input.accessedStorage[ctx.input.accessedStorage.length - 1].get(addr) + if (!storageSet) { + const emptyStorage = new Set() + ctx.input.accessedStorage[ctx.input.accessedStorage.length - 1].set(addr, emptyStorage) + } + return [ctx.Fr.e(1), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } -function eval_resetTouchedAddress(ctx, tag) { - if (tag.params.length != 0) throw new Error(`Invalid number of parameters function ${tag.funcName}: ${ctx.ln}`) - ctx.input.touchedAddress = []; - return scalar2fea(ctx.Fr, Scalar.e(0)); -} - -function eval_touchedStorageSlots(ctx, tag) { +/** + * Checks if the storage slot of the account is warm or cold. In case of cold, the slot is added as warm + * @param {Object} ctx current rom context object + * @param {Object} tag tag inputs in rom function + * @returns {FEA} returns 0 if storage solt is warm, 1 if cold + */ +function eval_isWarmedStorage(ctx, tag) { if (tag.params.length != 2) throw new Error(`Invalid number of parameters function ${tag.funcName}: ${ctx.ln}`) - let addr = evalCommand(ctx,tag.params[0]); - let key = evalCommand(ctx,tag.params[1]) - - // if address in touchedStorageSlots return 0 - if(ctx.input.touchedStorageSlots && ctx.input.touchedStorageSlots.filter(x => (x.addr == addr && x.key == key)).length > 0) { - return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; - } else { - //if addres not in touchedStorageSlots, return 1 - if(ctx.input.touchedStorageSlots) { - ctx.input.touchedStorageSlots.push({addr, key}); - } else { - ctx.input.touchedStorageSlots = [{addr, key}]; + let addr = evalCommand(ctx, tag.params[0]).toString(16); + let key = evalCommand(ctx, tag.params[1]) + // if address in acessStorage return 0 + for (let i = ctx.input.accessedStorage.length - 1; i >= 0; i--) { + const currentMap = ctx.input.accessedStorage[i] + if (currentMap.has(addr) && currentMap.get(addr).has(key)) { + return [ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } - return [ctx.Fr.e(1), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } + // if address in acessStorage return 1 and add it as warm + let storageSet = ctx.input.accessedStorage[ctx.input.accessedStorage.length - 1].get(addr) + if (!storageSet) { + storageSet = new Set() + ctx.input.accessedStorage[ctx.input.accessedStorage.length - 1].set(addr, storageSet) + } + storageSet.add(key) + return [ctx.Fr.e(1), ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero, ctx.Fr.zero]; } -function eval_resetStorageSlots(ctx, tag) { - if (tag.params.length != 0) throw new Error(`Invalid number of parameters function ${tag.funcName}: ${ctx.ln}`) - ctx.input.touchedStorageSlots = []; - return scalar2fea(ctx.Fr, Scalar.e(0)); +/** + * Clears wamred storage array, ready to process a new tx + */ +function eval_clearWarmedStorage(ctx) { + ctx.input.accessedStorage = [new Map()] + return scalar2fea(ctx.Fr, Scalar.e(0)); } - function eval_exp(ctx, tag) { if (tag.params.length != 2) throw new Error(`Invalid number of parameters function ${tag.funcName}: ${ctx.ln} at ${ctx.fileName}:${ctx.line}`) - const a = evalCommand(ctx,tag.params[0]); - const b = evalCommand(ctx,tag.params[1]) - return scalar2fea(ctx.Fr, Scalar.exp(a,b));; + const a = evalCommand(ctx, tag.params[0]); + const b = evalCommand(ctx, tag.params[1]) + return scalar2fea(ctx.Fr, Scalar.exp(a, b));; } -function eval_bitwise(ctx, tag){ +function eval_bitwise(ctx, tag) { const func = tag.funcName.split('_')[1]; - const a = evalCommand(ctx,tag.params[0]); + const a = evalCommand(ctx, tag.params[0]); let b; - switch (func){ + switch (func) { case 'and': checkParams(ctx, tag, 2); - b = evalCommand(ctx,tag.params[1]); + b = evalCommand(ctx, tag.params[1]); return Scalar.band(a, b); case 'or': checkParams(ctx, tag, 2); - b = evalCommand(ctx,tag.params[1]); + b = evalCommand(ctx, tag.params[1]); return Scalar.bor(a, b); case 'xor': checkParams(ctx, tag, 2); - b = evalCommand(ctx,tag.params[1]); + b = evalCommand(ctx, tag.params[1]); return Scalar.bxor(a, b); case 'not': checkParams(ctx, tag, 1); @@ -2388,7 +2448,7 @@ function eval_bitwise(ctx, tag){ } function eval_beforeLast(ctx) { - if (ctx.step >= ctx.N-2) { + if (ctx.step >= ctx.stepsN-2) { return [0n, 0n, 0n, 0n, 0n, 0n, 0n, 0n]; } else { return [ctx.Fr.negone, 0n, 0n, 0n, 0n, 0n, 0n, 0n]; @@ -2631,6 +2691,7 @@ function preprocessTxs(ctx) { newStateRoot, globalExitRoot, timestamp, + chainID } = ctx.input; ctx.input.batchHashData = calculateBatchHashData( @@ -2646,11 +2707,11 @@ function preprocessTxs(ctx) { newLocalExitRoot, ctx.input.batchHashData, numBatch, - timestamp + timestamp, + chainID ); - ctx.input.touchedAddress = []; - ctx.input.touchedStorageSlots = []; + ctx.input.accessedStorage = [new Map()] } function printRegs(Fr, ctx) { diff --git a/src/sm/sm_storage/sm_storage.js b/src/sm/sm_storage/sm_storage.js index 4ba69daf..fe3d5cce 100644 --- a/src/sm/sm_storage/sm_storage.js +++ b/src/sm/sm_storage/sm_storage.js @@ -163,15 +163,6 @@ module.exports.execute = async function (pols, action) { logger("StorageExecutor isInsertNotFound returns " + fea42String(fr, op)); } } - else if (rom.line[l].funcName == "isSetReplacingZero") - { - if (!actionListEmpty && action[a].bIsSet && - action[a].setResult.mode == "insertNotFound") - { - op[0] = fr.one; - logger("StorageExecutor isSetReplacingZero returns " + fea42String(fr, op)); - } - } else if (rom.line[l].funcName == "isSetDeleteLast") { if (!actionListEmpty && action[a].bIsSet && diff --git a/test/counters/storage.js b/test/counters/storage.js index 11150788..8e880bf3 100644 --- a/test/counters/storage.js +++ b/test/counters/storage.js @@ -10,7 +10,16 @@ describe("Test Storage Counters", async function () { it("Verify Storage Zkasm Test", async () => { await verifyZkasm("../zkasm/counters/storage.zkasm", true, - { defines: {N: 2 ** 21}, + { defines: {N: 2 ** 16}, + namespaces: ['Global', 'Main', 'Storage'], + verbose: true, + color: true, + disableUnusedError: true}); + }); + + it("Verify Storage Zkasm Test (some delete edge cases)", async () => { + await verifyZkasm("../zkasm/counters/storage2.zkasm", true, + { defines: {N: 2 ** 16}, namespaces: ['Global', 'Main', 'Rom', 'Byte4', 'Storage', 'PoseidonG'], verbose: true, color: true, diff --git a/test/sm/sm_storage/sm_storage_test.js b/test/sm/sm_storage/sm_storage_test.js index 07ab6b6c..f64f8a4e 100644 --- a/test/sm/sm_storage/sm_storage_test.js +++ b/test/sm/sm_storage/sm_storage_test.js @@ -63,6 +63,16 @@ describe("Test storage operations", async function () { cmPols.Main.op5[index] = 0n; cmPols.Main.op6[index] = 0n; cmPols.Main.op7[index] = 0n; + cmPols.Main.incCounter[index] = 0n; + cmPols.Main.sWR[index] = 0n; + cmPols.Main.D0[index] = 0n; + cmPols.Main.D1[index] = 0n; + cmPols.Main.D2[index] = 0n; + cmPols.Main.D3[index] = 0n; + cmPols.Main.D4[index] = 0n; + cmPols.Main.D5[index] = 0n; + cmPols.Main.D6[index] = 0n; + cmPols.Main.D7[index] = 0n; } db = new MemDB(fr); @@ -71,6 +81,59 @@ describe("Test storage operations", async function () { async function smtSet (oldRoot, key, value) { const r = await smt.set(oldRoot, key, value); + const index = plookUpIndex++; + cmPols.Main.sRD[index] = fr.zero; + cmPols.Main.sWR[index] = fr.e(1n); + cmPols.Main.SR0[index] = fr.e(oldRoot[0] & 0xFFFFFFFFn); + cmPols.Main.SR1[index] = fr.e(oldRoot[0] >> 32n); + cmPols.Main.SR2[index] = fr.e(oldRoot[1] & 0xFFFFFFFFn); + cmPols.Main.SR3[index] = fr.e(oldRoot[1] >> 32n); + cmPols.Main.SR4[index] = fr.e(oldRoot[2] & 0xFFFFFFFFn); + cmPols.Main.SR5[index] = fr.e(oldRoot[2] >> 32n); + cmPols.Main.SR6[index] = fr.e(oldRoot[3] & 0xFFFFFFFFn); + cmPols.Main.SR7[index] = fr.e(oldRoot[3] >> 32n); + cmPols.Main.D0[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D1[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D2[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D3[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D4[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D5[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D6[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.D7[index] = value & 0xFFFFFFFFn; + value = value >> 32n; + cmPols.Main.sKey[0][index] = key[0]; + cmPols.Main.sKey[1][index] = key[1]; + cmPols.Main.sKey[2][index] = key[2]; + cmPols.Main.sKey[3][index] = key[3]; + cmPols.Main.op0[index] = fr.e(r.newRoot[0] & 0xFFFFFFFFn); + cmPols.Main.op1[index] = fr.e(r.newRoot[0] >> 32n); + cmPols.Main.op2[index] = fr.e(r.newRoot[1] & 0xFFFFFFFFn); + cmPols.Main.op3[index] = fr.e(r.newRoot[1] >> 32n); + cmPols.Main.op4[index] = fr.e(r.newRoot[2] & 0xFFFFFFFFn); + cmPols.Main.op5[index] = fr.e(r.newRoot[2] >> 32n); + cmPols.Main.op6[index] = fr.e(r.newRoot[3] & 0xFFFFFFFFn); + cmPols.Main.op7[index] = fr.e(r.newRoot[3] >> 32n); + cmPols.Main.incCounter = r.proofHashCounter; +/* console.log(index+' '+[ + fr.e(cmPols.Main.SR0[index] + cmPols.Main.SR1[index] * 2n**32n), + fr.e(cmPols.Main.SR2[index] + cmPols.Main.SR3[index] * 2n**32n), + fr.e(cmPols.Main.SR4[index] + cmPols.Main.SR5[index] * 2n**32n), + fr.e(cmPols.Main.SR6[index] + cmPols.Main.SR7[index] * 2n**32n), + cmPols.Main.sKey[0][index], cmPols.Main.sKey[1][index], cmPols.Main.sKey[2][index], cmPols.Main.sKey[3][index], + cmPols.Main.D0[index], cmPols.Main.D1[index], cmPols.Main.D2[index], cmPols.Main.D3[index], + cmPols.Main.D4[index], cmPols.Main.D5[index], cmPols.Main.D6[index], cmPols.Main.D7[index], + fr.e(cmPols.Main.op0[index] + cmPols.Main.op1[index] * 2n**32n), + fr.e(cmPols.Main.op2[index] + cmPols.Main.op3[index] * 2n**32n), + fr.e(cmPols.Main.op4[index] + cmPols.Main.op5[index] * 2n**32n), + fr.e(cmPols.Main.op6[index] + cmPols.Main.op7[index] * 2n**32n), + cmPols.Main.incCounter].join(','));*/ required.Storage.push({bIsSet: true, setResult: { oldRoot: [...r.oldRoot], @@ -90,9 +153,9 @@ describe("Test storage operations", async function () { async function smtGet (root, key) { const r = await smt.get(root, key); - console.log(['r', r, root, key]); const index = plookUpIndex++; cmPols.Main.sRD[index] = fr.e(1n); + cmPols.Main.sWR[index] = fr.zero; cmPols.Main.SR0[index] = fr.e(r.root[0] & 0xFFFFFFFFn); cmPols.Main.SR1[index] = fr.e(r.root[0] >> 32n); cmPols.Main.SR2[index] = fr.e(r.root[1] & 0xFFFFFFFFn); @@ -101,6 +164,14 @@ describe("Test storage operations", async function () { cmPols.Main.SR5[index] = fr.e(r.root[2] >> 32n); cmPols.Main.SR6[index] = fr.e(r.root[3] & 0xFFFFFFFFn); cmPols.Main.SR7[index] = fr.e(r.root[3] >> 32n); + cmPols.Main.D0[index] = fr.zero; + cmPols.Main.D1[index] = fr.zero; + cmPols.Main.D2[index] = fr.zero; + cmPols.Main.D3[index] = fr.zero; + cmPols.Main.D4[index] = fr.zero; + cmPols.Main.D5[index] = fr.zero; + cmPols.Main.D6[index] = fr.zero; + cmPols.Main.D7[index] = fr.zero; cmPols.Main.sKey[0][index] = key[0]; cmPols.Main.sKey[1][index] = key[1]; cmPols.Main.sKey[2][index] = key[2]; @@ -122,6 +193,7 @@ describe("Test storage operations", async function () { rvalue = rvalue >> 32n; cmPols.Main.op7[index] = rvalue & 0xFFFFFFFFn; rvalue = rvalue >> 32n; + cmPols.Main.incCounter = r.proofHashCounter; required.Storage.push({bIsSet: false, getResult: { root: [...r.root], diff --git a/test/sm/sm_storage/storage_main.pil b/test/sm/sm_storage/storage_main.pil index 9cd89e45..ec1dae15 100644 --- a/test/sm/sm_storage/storage_main.pil +++ b/test/sm/sm_storage/storage_main.pil @@ -4,20 +4,41 @@ include "../../../pil/storage.pil"; namespace Main(%N); -pol commit sRD; +pol commit sRD, sWR; pol commit SR0, SR1, SR2, SR3, SR4, SR5, SR6, SR7; +pol commit D0, D1, D2, D3, D4, D5, D6, D7; pol commit sKey[4]; pol commit op0, op1, op2, op3, op4, op5, op6, op7; +pol commit incCounter; sRD { SR0 + 2**32*SR1, SR2 + 2**32*SR3, SR4 + 2**32*SR5, SR6 + 2**32*SR7, sKey[0], sKey[1], sKey[2], sKey[3], op0, op1, op2, op3, - op4, op5, op6, op7 + op4, op5, op6, op7, + incCounter } in Storage.iLatchGet { Storage.oldRoot0, Storage.oldRoot1, Storage.oldRoot2, Storage.oldRoot3, Storage.rkey0, Storage.rkey1, Storage.rkey2, Storage.rkey3, Storage.valueLow0, Storage.valueLow1, Storage.valueLow2, Storage.valueLow3, - Storage.valueHigh0, Storage.valueHigh1, Storage.valueHigh2, Storage.valueHigh3 + Storage.valueHigh0, Storage.valueHigh1, Storage.valueHigh2, Storage.valueHigh3, + incCounter +}; + +sWR { + SR0 + 2**32*SR1, SR2 + 2**32*SR3, SR4 + 2**32*SR5, SR6 + 2**32*SR7, + sKey[0], sKey[1], sKey[2], sKey[3], + D0, D1, D2, D3, + D4, D5, D6, D7, + op0 + 2**32*op1, op2 + 2**32*op3, op4 + 2**32*op5, op6 + 2**32*op7, + incCounter +} in +Storage.iLatchSet { + Storage.oldRoot0, Storage.oldRoot1, Storage.oldRoot2, Storage.oldRoot3, + Storage.rkey0, Storage.rkey1, Storage.rkey2, Storage.rkey3, + Storage.valueLow0, Storage.valueLow1, Storage.valueLow2, Storage.valueLow3, + Storage.valueHigh0, Storage.valueHigh1, Storage.valueHigh2, Storage.valueHigh3, + Storage.newRoot0, Storage.newRoot1, Storage.newRoot2, Storage.newRoot3, + Storage.incCounter }; diff --git a/test/tools/stepsN.js b/test/tools/stepsN.js new file mode 100644 index 00000000..90a66544 --- /dev/null +++ b/test/tools/stepsN.js @@ -0,0 +1,40 @@ +const chai = require("chai"); +const assert = chai.assert; +const expect = chai.expect; +chai.use(require('chai-as-promised')); + +const fs = require("fs"); +const path = require("path"); + +const {verifyZkasm} = require("../verify_zkasm"); + +describe("Test Arith Counter", async function () { + this.timeout(10000000); + + it("Verify Arith Zkasm fail Test", async () => { + await expect( + verifyZkasm("../zkasm/tools/stepsN.zkasm", false, + { defines: {N: 2 ** 16}, + namespaces: ['Global', 'Main'], + verbose: true, + color: true, + disableUnusedError: true}, + { + stepsN: 2**16, + debug: true + })).to.be.rejectedWith(Error); + }); + + it("Verify Arith Zkasm Test", async () => { + await verifyZkasm("../zkasm/tools/stepsN.zkasm", false, + { defines: {N: 2 ** 16}, + namespaces: ['Global', 'Main'], + verbose: true, + color: true, + disableUnusedError: true}, + { + stepsN: 2**17, + debug: true + }); + }); +}); diff --git a/test/verify_zkasm.js b/test/verify_zkasm.js index 0a33da6b..397a656e 100644 --- a/test/verify_zkasm.js +++ b/test/verify_zkasm.js @@ -25,8 +25,9 @@ const smPoseidonG = require("../src/sm/sm_poseidong.js"); const smRom = require("../src/sm/sm_rom.js"); const smStorage = require("../src/sm/sm_storage/sm_storage.js"); const { index } = require("../src/sm/sm_main/test_tools.js"); +const { config } = require("yargs"); -module.exports.verifyZkasm = async function (zkasmFile, verifyPilFlag = true, pilConfig = {}) { +module.exports.verifyZkasm = async function (zkasmFile, verifyPilFlag = true, pilConfig = {}, mainConfig = {}) { const Fr = new F1Field("0xFFFFFFFF00000001"); const brief = false; @@ -115,7 +116,7 @@ module.exports.verifyZkasm = async function (zkasmFile, verifyPilFlag = true, pi await smBinary.buildConstants(constPols.Binary); } - const requiredMain = await smMain.execute(cmPols.Main, input, rom); + const requiredMain = await smMain.execute(cmPols.Main, input, rom, mainConfig); if (cmPols.Byte4) { console.log("Exec Byte4..."); diff --git a/test/zkasm/counters/storage2.zkasm b/test/zkasm/counters/storage2.zkasm new file mode 100644 index 00000000..18cad895 --- /dev/null +++ b/test/zkasm/counters/storage2.zkasm @@ -0,0 +1,66 @@ +; key bits with A,B = 0 C = # +CONST %_0000101 = 8 ; 0000101....1010 #8 +CONST %_0000111 = 4 ; 0000111....0001 #4 +CONST %_0010001 = 13 ; 0010001....0111 #13 +CONST %_0011010 = 1 ; 0011010....1110 #1 +CONST %_0011101 = 5 ; 0011101....0001 #5 +CONST %_0100101 = 7 ; 0100101....0110 #7 +CONST %_0101011 = 10 ; 0101011....1001 #10 +CONST %_0110101 = 3 ; 0110101....0010 #3 +CONST %_0110110 = 9 ; 0110110....0110 #9 +CONST %_0111001 = 6 ; 0111001....0000 #6 +CONST %_1010110 = 15 ; 1010110....0101 #15 +CONST %_1010111 = 14 ; 1010111....1101 #14 +CONST %_1100011 = 12 ; 1100011....1010 #12 +CONST %_1101000 = 0 ; 1101000....0010 #0 +CONST %_1101010 = 2 ; 1101010....1001 #2 +CONST %_1111001 = 11 ; 1111001....0110 #11 + +start: + + STEP => A + 0 :ASSERT + + ; to verify that there are no correlations between counters + 0 => A + CNT_ARITH :ASSERT + CNT_BINARY :ASSERT + CNT_KECCAK_F: ASSERT + CNT_MEM_ALIGN :ASSERT + CNT_POSEIDON_G :ASSERT + CNT_PADDING_PG :ASSERT + + 0 => SR + + %_1111001 => A + 0x10n => D + $ => SR :SSTORE + + %_1100011 => A + 0x20n => D + $ => SR :SSTORE + + %_1101000 => A + 0x40n => D + $ => SR :SSTORE + + %_1111001 => A + 0n => D + $ => SR :SSTORE + + %_1101000 => A + 0n => D + $ => SR :SSTORE + + %_1100011 => A + 0n => D + $ => SR :SSTORE + +end: + 0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR + +finalWait: + ${beforeLast()} : JMPN(finalWait) + + : JMP(start) +opINVALID: \ No newline at end of file diff --git a/test/zkasm/tools/stepsN.zkasm b/test/zkasm/tools/stepsN.zkasm new file mode 100644 index 00000000..613b4412 --- /dev/null +++ b/test/zkasm/tools/stepsN.zkasm @@ -0,0 +1,23 @@ +start: + +CONST %UPTO = 40000 + + STEP => A + 0 :ASSERT + + 0 => A +counterLoop: + A + 1 => A + A - %UPTO :JMPN(counterLoop) + +counterEnds: + %UPTO :ASSERT + +end: + 0 => A,B,C,D,E,CTX, SP, PC, GAS, MAXMEM, SR + +finalWait: + ${beforeLast()} : JMPN(finalWait) + + : JMP(start) +opINVALID: \ No newline at end of file diff --git a/testvectors/storage_sm_rom.json b/testvectors/storage_sm_rom.json index d6e707cf..67d3b230 100644 --- a/testvectors/storage_sm_rom.json +++ b/testvectors/storage_sm_rom.json @@ -2722,7 +2722,7 @@ { "freeInTag": { "op": "functionCall", - "funcName": "GetRKey", + "funcName": "GetRkey", "params": [] }, "inFREE": "1", diff --git a/tools/build-genesis/build_genesis.js b/tools/build-genesis/build_genesis.js index d0e595ac..61877ebf 100644 --- a/tools/build-genesis/build_genesis.js +++ b/tools/build-genesis/build_genesis.js @@ -6,8 +6,8 @@ const { } = require('@0xpolygonhermez/zkevm-commonjs'); // paths files -const pathInput = path.join(__dirname, "../testvectors/input_gen.json"); -const pathOutput = path.join(__dirname, "../tools/build-genesis/input_executor.json"); +const pathInput = path.join(__dirname, "./input_gen.json"); +const pathOutput = path.join(__dirname, "./input_executor.json"); async function main(){ // build poseidon @@ -52,6 +52,9 @@ async function main(){ [F.zero, F.zero, F.zero, F.zero], // empty smt smtUtils.stringToH4(generateData.oldLocalExitRoot), generateData.genesis, + null, + null, + generateData.chainID ); // start batch diff --git a/tools/build-genesis/input_executor.json b/tools/build-genesis/input_executor.json index 0350d1a3..84d00038 100644 --- a/tools/build-genesis/input_executor.json +++ b/tools/build-genesis/input_executor.json @@ -1,5 +1,6 @@ { "oldStateRoot": "0x2dc4db4293af236cb329700be43f08ace740a05088f8c7654736871709687e90", + "chainId": 1001, "db": { "0x2dc4db4293af236cb329700be43f08ace740a05088f8c7654736871709687e90": [ "0000000000000000", @@ -76,5 +77,6 @@ "globalExitRoot": "0x090bcaf734c4f06c93954a827b45a6e8c67b8e0fd1e0a35a1c5982d6961828f9", "numBatch": 1, "timestamp": 1944498031, + "chainID": 1000, "contractsBytecode": {} } \ No newline at end of file diff --git a/tools/build-genesis/input_gen.json b/tools/build-genesis/input_gen.json index e8bf8a43..ecd31629 100644 --- a/tools/build-genesis/input_gen.json +++ b/tools/build-genesis/input_gen.json @@ -28,6 +28,7 @@ "globalExitRoot": "0x090bcaf734c4f06c93954a827b45a6e8c67b8e0fd1e0a35a1c5982d6961828f9", "numBatch": 1, "timestamp": 1944498031, + "chainID": 1000, "contractsBytecode": {}, "expectedLeafs": { "0x0000000000000000000000000000000000000000": { diff --git a/tools/build_all.sh b/tools/build_all.sh index f61c73bd..83b8b43e 100755 --- a/tools/build_all.sh +++ b/tools/build_all.sh @@ -4,6 +4,7 @@ START_OPTIONS=`env|grep -wE '(npm_config_pil|npm_config_pil|npm_config_continue| echo "####### START $(date +'%Y-%m-%d %H:%M:%S') $START_OPTIONS ########" >> $BDIR/steps.log LAST_STEP_FILE=$BDIR/last_step.txt [ ! -z $npm_config_from ] && SKIP=1 +[ ! -z $npm_config_step ] && npm_config_from=$npm_config_step && npm_config_to=$npm_config_step && SKIP=1 [ $npm_config_continue ] && [ -f $LAST_STEP_FILE ] && LAST_STEP=`cat $LAST_STEP_FILE` && echo "last step done: $LAST_STEP" && SKIP=1 START_TIME=$(date +%s) while [ $# -gt 0 ]; do @@ -11,7 +12,6 @@ while [ $# -gt 0 ]; do STEP=$1 shift [ "$npm_config_from" = "$STEP" ] && SKIP=0 - [ "$npm_config_to" = "$STEP" ] && break [ ! -z "$PREV_STEP" ] && [ "$PREV_STEP" = "$LAST_STEP" ] && SKIP=0 [ $SKIP -eq 1 ] && continue mkdir -p $BDIR/steps @@ -33,6 +33,7 @@ while [ $# -gt 0 ]; do echo "$STEP OK $ELAPSED / $TOT_ELAPSED" >> $BDIR/steps.log echo "$STEP ...[\e[32;1mOK\e[0m] $ELAPSED / $TOT_ELAPSED\n" echo $STEP > $LAST_STEP_FILE + [ "$npm_config_to" = "$STEP" ] && break sleep 1 done echo "####### END $(date +'%Y-%m-%d %H:%M:%S') ########" >> $BDIR/steps.log diff --git a/tools/gen_store_keys.js b/tools/gen_store_keys.js new file mode 100644 index 00000000..45c12fd0 --- /dev/null +++ b/tools/gen_store_keys.js @@ -0,0 +1,64 @@ +const fs = require("fs") +const { Scalar } = require('ffjavascript'); +const buildPoseidon = require("@0xpolygonhermez/zkevm-commonjs").getPoseidon; + +const argv = require("yargs") + .usage("node gen_store_keys.js [-c ] [-o ]") + .help('h') + .alias("o", "output") + .alias("c", "count") + .argv; + +async function main(){ + const output = typeof(argv.output) === "string" ? argv.output.trim() : "-"; + const count = typeof(argv.count) === "number" ? argv.count : 100; + + const poseidon = await buildPoseidon(); + const F = poseidon.F; + + // set A, B = 0 + const A012345B01 = [F.zero, F.zero, F.zero, F.zero, F.zero, F.zero, F.zero, F.zero]; + let hashes = []; + for (let i = 0; i < count; ++i) { + const C = [i, F.zero, F.zero, F.zero, F.zero, F.zero, F.zero, F.zero]; + + const keyI = poseidon(A012345B01); // Kin0 + const key = poseidon(C, keyI); // Kin1 + hashes.push(splitKey(key).join('') + ' #'+i); + } + hashes.sort(); + let maxDepth = 0; + for (let i = 1; i < count; ++i) { + const depth = [...hashes[i]].findIndex((bit, index) => bit !== hashes[i-1][index]); + maxDepth = depth > maxDepth ? depth : maxDepth; + } + const content = hashes.join("\n") + "\n"; + if (output == '-') { + console.log(content); + } else { + fs.writeFileSync(output, content , "utf8"); + } + console.log('max depth:' + maxDepth); + + + function splitKey(k) { + const res = []; + const auxk = [F.toObject(k[0]), F.toObject(k[1]), F.toObject(k[2]), F.toObject(k[3])]; + for (let i = 0; i < 64; i++) { + for (let j = 0; j < 4; j++) { + res.push(Scalar.toNumber(Scalar.band(auxk[j], Scalar.e(1)))); + auxk[j] = Scalar.shr(auxk[j], 1); + } + } + + return res; + } +} + +main().then(()=> { + process.exit(0); +}, (err) => { + console.log(err.message); + console.log(err.stack); + process.exit(1); +}); \ No newline at end of file diff --git a/tools/run-test/checker.sh b/tools/run-test/checker.sh new file mode 100644 index 00000000..3837619f --- /dev/null +++ b/tools/run-test/checker.sh @@ -0,0 +1,6 @@ +#!/bin/sh +FILE=$PWD/tools/run-test/parallel-tests/checker.txt +if [ -f "$FILE" ]; then + cat "$FILE" + exit 1 +fi diff --git a/tools/run-test/gen-parallel-tests.js b/tools/run-test/gen-parallel-tests.js new file mode 100644 index 00000000..2478e784 --- /dev/null +++ b/tools/run-test/gen-parallel-tests.js @@ -0,0 +1,61 @@ + +const chai = require("chai"); +const expect = chai.expect; +const { compile } = require("pilcom"); +const folderPaths = ["../../../zkevm-testvectors/inputs-executor", "../../../zkevm-testvectors/tools/ethereum-tests/GeneralStateTests"]; +const buildPoseidon = require("@0xpolygonhermez/zkevm-commonjs").getPoseidon; +const fs = require("fs"); +const path = require("path"); +const fileCachePil = path.join(__dirname, "../../cache-main-pil.json"); +const inputs = []; +const testsFolder = path.join(__dirname, "parallel-tests"); +const sampleDir = path.join(__dirname, "sample.test.js"); + +async function main() { + + const poseidon = await buildPoseidon(); + const F = poseidon.F; + for (const folder of folderPaths) { + let inputsPath = path.join(__dirname, folder); + fs.readdirSync(inputsPath).forEach(file => { + const filePath = path.join(inputsPath, file); + if (file.endsWith(".json")) { + inputs.push(filePath); + } else { + if (fs.statSync(filePath).isDirectory()) { + fs.readdirSync(filePath).forEach(subFile => { + const subFilePath = path.join(filePath, subFile); + if (subFile.endsWith(".json")) + inputs.push(subFilePath); + }); + } + } + }); + } + + const pilConfig = { + defines: { N: 4096 }, + namespaces: ['Main', 'Global'] + }; + + const pil = await compile(F, "pil/main.pil", null, pilConfig); + fs.writeFileSync(fileCachePil, JSON.stringify(pil, null, 1) + "\n", "utf8"); + genTestsFiles(); + +} + +async function genTestsFiles() { + + if (!fs.existsSync(testsFolder)){ + fs.mkdirSync(testsFolder); + } + for (const inputPath of inputs) { + const name = inputPath.split("/").slice(-1)[0].replace('json','test.js'); + const sample = fs.readFileSync(sampleDir, "utf-8"); + const test = sample.replace("\"%%INPUT_PATH%%\"", `"${inputPath}"`); + fs.writeFileSync(`${testsFolder}/${name}`, test); + } + expect(true).to.be.true +} + +main(); \ No newline at end of file diff --git a/tools/run-test/run-inputs-mocha.test.js b/tools/run-test/run-inputs-mocha.test.js index d3408136..db0851ab 100644 --- a/tools/run-test/run-inputs-mocha.test.js +++ b/tools/run-test/run-inputs-mocha.test.js @@ -14,6 +14,7 @@ const chalk = require("chalk"); describe("Run executor inputs from config file", () => { let cmPols; const inputs = []; + let pilConfig; before(async () => { const poseidon = await buildPoseidon(); @@ -35,9 +36,9 @@ describe("Run executor inputs from config file", () => { } }); } - - const pilConfig = { - defines: { N: 2 ** 21 }, + + pilConfig = { + defines: { N: 4096 }, namespaces: ['Main', 'Global'] }; @@ -45,7 +46,11 @@ describe("Run executor inputs from config file", () => { fs.writeFileSync(fileCachePil, JSON.stringify(pil, null, 1) + "\n", "utf8"); cmPols = newCommitPolsArray(pil); + }); + it("Print info", async () => { + console.log("CONFIG PIL:"); + console.log(pilConfig); }); it('Should run all the inputs', async () => { @@ -61,8 +66,10 @@ describe("Run executor inputs from config file", () => { debug: true, debugInfo: { inputName: path.basename(inputPath) - } + }, + stepsN: 8388608 } + console.log("Running test: ", inputPath); await smMain.execute(cmPols.Main, input, rom, config); const stopTime = performance.now(); info += `${chalk.green(`Finish executor JS ==> ${(stopTime - startTime) / 1000} s\n`)}`; diff --git a/tools/run-test/run-inputs.js b/tools/run-test/run-inputs.js index f82ce455..d864de6a 100644 --- a/tools/run-test/run-inputs.js +++ b/tools/run-test/run-inputs.js @@ -19,7 +19,7 @@ const argv = require("yargs") .alias("e", "exit") .argv; -// example: node run-inputs.js -f ../../../test-vectors/inputs-executor/calldata -r ../../../zkrom/build/rom.json +// example: node run-inputs.js -f ../../../zkevm-testvectors/tools/ethereum-tests/GeneralStateTests/stMemoryTest -r ../../../zkevm-rom/build/rom.json async function main(){ console.time("Init time"); @@ -68,14 +68,14 @@ async function main(){ let romFile; helpers.checkParam(argv.rom, "Rom file"); romFile = argv.rom.trim(); - const rom = JSON.parse(await fs.promises.readFile(romFile, "utf8")); + const rom = JSON.parse(fs.readFileSync(path.join(__dirname, romFile), "utf8")); let pil; if (fs.existsSync(fileCachePil)) { pil = JSON.parse(await fs.promises.readFile(fileCachePil, "utf8")); } else { const pilConfig = { - defines: {N: 2 ** 21}, + defines: { N: 4096 }, namespaces: ['Main', 'Global'] }; @@ -103,7 +103,9 @@ async function main(){ debug: true, debugInfo: { inputName: path.basename(fileName) - } + }, + stepsN: 8388608, + tracer: true } await smMain.execute(cmPols.Main, input, rom, config); const stopTime = performance.now(); diff --git a/tools/run-test/sample.test.js b/tools/run-test/sample.test.js new file mode 100644 index 00000000..f2dbe443 --- /dev/null +++ b/tools/run-test/sample.test.js @@ -0,0 +1,54 @@ +const chai = require("chai"); +const expect = chai.expect; +const rom = require("../../../../zkevm-rom/build/rom.json") +const fs = require("fs"); +const path = require("path"); +const { performance } = require("perf_hooks"); +const smMain = require("../../../src/sm/sm_main/sm_main"); +const chalk = require("chalk"); +const fileCachePil = path.join(__dirname, "../../../cache-main-pil.json"); +const { newCommitPolsArray } = require("pilcom"); +const checkerDir = path.join(__dirname, "checker.txt") + +it('Should run one input', async () => { + if (fs.existsSync(checkerDir)){ + process.exit(1) + } + const initTime = Date.now(); + try { + const pil = JSON.parse(fs.readFileSync(fileCachePil)) + const cmPols = newCommitPolsArray(pil); + const inputPath = "%%INPUT_PATH%%"; + const input = JSON.parse(fs.readFileSync(inputPath, "utf8")); + let info = ""; + const startTime = performance.now(); + try { + const config = { + debug: true, + debugInfo: { + inputName: path.basename(inputPath) + }, + stepsN: 8388608 + } + console.log("Running test: ", inputPath); + await smMain.execute(cmPols.Main, input, rom, config); + const stopTime = performance.now(); + info += `${chalk.green(`Finish executor JS ==> ${(stopTime - startTime) / 1000} s\n`)}`; + } catch (err) { + fs.writeFileSync(checkerDir, `Failed test ${inputPath}`); + info += `${chalk.red('Error')}\n` + info += `${chalk.yellow(`${err}\n`)}`; + console.log(info); + throw err; + } + console.log(info); + const used = process.memoryUsage().heapUsed / 1024 / 1024; + console.log(`Current memory heap: ${Math.round(used * 100) / 100} MB out of ${process.memoryUsage().heapTotal}`); + expect(true).to.be.true + } catch (e) { + console.log(e); + throw e; + } + console.log(`Test finished in ${((Date.now() - initTime) / 1000 / 60).toFixed(2)} minutes`) + +}); \ No newline at end of file