Skip to content

zkevm_estimateCounters is always returning the same amount of Steps for different executions #1677

Closed
@tclemos

Description

Steps to reproduce:

  • deploy the following SC:
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract ZkCounters {
    uint256 public count = 0;

    // set number and gas limit
    function steps100() public {
        count = 0;
        for (uint i = 0; i < 100; i++) {
            assembly {
                mstore(0x0, 1234)
            }
        }
    }

    function steps1000() public {
        count = 0;
        for (uint i = 0; i < 1000; i++) {
            assembly {
                mstore(0x0, 1234)
            }
        }
    }

    function steps10000() public {
        count = 0;
        for (uint i = 0; i < 10000; i++) {
            assembly {
                mstore(0x0, 1234)
            }
        }
    }
}
  • estimate counters for each method
{"jsonrpc":"2.0","id":1,"method":"zkevm_estimateCounters","params":[{"From":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","Gas":"0x1c9c380","GasPrice":"0x989680","Input":"0x018b0aa6","To":"0x0c3dCa7865203A9bbdC83942a3f1B1567D331Aa6","Value":"0x0"}]}
{"jsonrpc":"2.0","id":1,"method":"zkevm_estimateCounters","params":[{"From":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","Gas":"0x1c9c380","GasPrice":"0x989680","Input":"0x173fcd38","To":"0x0c3dCa7865203A9bbdC83942a3f1B1567D331Aa6","Value":"0x0"}]}
{"jsonrpc":"2.0","id":1,"method":"zkevm_estimateCounters","params":[{"From":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","Gas":"0x1c9c380","GasPrice":"0x989680","Input":"0x44446453","To":"0x0c3dCa7865203A9bbdC83942a3f1B1567D331Aa6","Value":"0x0"}]}

Actual result:
The response has the countersUsed.steps equal for all methods execution.

Expected result:
The response should have the countersUsed.steps different for each method, given the number of operations.

Activity

praetoriansentry

praetoriansentry commented on Jan 29, 2025

@praetoriansentry

I took a look at this one. Adding some addition details.

SMT Depth

The method getSmtDepth has a bug which will cause it to always return 0 instead of the value returned by hermezDb.GetClosestSmtDepth(blockNum). In the code below, the assignments on lines 313 and 317 don't actually impact the value returned in the end because smtDepth is declared twice and shadowed.

func getSmtDepth(hermezDb *hermez_db.HermezDbReader, blockNum uint64, config *tracers.TraceConfig_ZkEvm) (int, error) {
var smtDepth int
if config != nil && config.SmtDepth != nil {
smtDepth = *config.SmtDepth
} else {
depthBlockNum, smtDepth, err := hermezDb.GetClosestSmtDepth(blockNum)
if err != nil {
return 0, err
}
if depthBlockNum < blockNum {
smtDepth += smtDepth / 10
}
if smtDepth == 0 || smtDepth > 256 {
smtDepth = 256
}
}
return smtDepth, nil
}

Example Case

On the Bali network, the transaction 0x6d0f4b598c1ac7f0c294e1b79534359ed4324e3065cd7e0ff0377d7b1077498e takes up an entire batch. The batch the batch has only a single block. If I run debug_traceTransactionCounters these are the counter values I get:

{
  "A": 627,
  "B": 65216,
  "M": 2,
  "K": 3,
  "D": 6,
  "P": 1025802,
  "SHA": 0,
  "S": 1468504
}

These values make sense to me because the test is meant to push the poseidon counters and this is very close to the limit of 1028275.

The issue is, if we use zkevm_getBatchCountersByNumber to check the counters for this batch, we get very different numbers:

{
  "smtDepth": 0,
  "batchNumber": 86693,
  "blockFrom": 9637277,
  "blockTo": 9637277,
  "countersUsed": {
    "gas": 1386010,
    "keccakHashes": 9,
    "poseidonhashes": 368,
    "poseidonPaddings": 6,
    "memAligns": 0,
    "arithmetics": 622,
    "binaries": 826,
    "steps": 12491,
    "SHA256hashes": 0
  },
  "countersLimits": {
    "gas": 0,
    "keccakHashes": 9029,
    "poseidonhashes": 1028275,
    "poseidonPaddings": 569223,
    "memAligns": 996141,
    "arithmetics": 996141,
    "binaries": 1992283,
    "steps": 31876521,
    "SHA256hashes": 7083
  }
}

The amount of gas used looks right and aligns with the receipt: 1386010. But the other counters do not look right. If there were so few counters used, I assume the batch would not have been closed with one block in it.

As far as I can tell, both zkevm_getBatchCountersByNumber and zkevm_estimateCounters make use of the method BatchCounterCollector.CombineCollectors defined here:

// CombineCollectors takes the batch level data from all transactions and combines these counters with each transactions'
// rlp level counters and execution level counters
func (bcc *BatchCounterCollector) CombineCollectors(verifyMerkleProof bool) (Counters, error) {
// combine all the counters we have so far
// if we have external coutners use them, otherwise create new
// this is used when sequencer starts mid batch and we need the already comulated counters
combined := bcc.NewCounters()
if bcc.addonCounters != nil {
for k, v := range *bcc.addonCounters {
combined[k].used = v.used
combined[k].remaining -= v.used
}
}
if err := bcc.processBatchLevelData(); err != nil {
return nil, err
}
// these counter collectors can be re-used for each new block in the batch as they don't rely on inputs
// from the block or transactions themselves
changeL2BlockCounter := NewCounterCollector(bcc.smtLevelsForTransaction, bcc.forkId)
changeL2BlockCounter.processChangeL2Block(verifyMerkleProof)
changeBlockCounters := NewCounterCollector(bcc.smtLevelsForTransaction, bcc.forkId)
changeBlockCounters.decodeChangeL2BlockTx()
// handling changeL2Block counters for each block in the batch - simulating a call to decodeChangeL2BlockTx from the js
for i := 0; i < bcc.blockCount; i++ {
for k, v := range changeBlockCounters.counters {
combined[k].used += v.used
combined[k].remaining -= v.used
}
for k, v := range changeL2BlockCounter.counters {
combined[k].used += v.used
combined[k].remaining -= v.used
}
}
if bcc.l2DataCollector != nil {
for k, v := range bcc.l2DataCollector.Counters() {
combined[k].used += v.used
combined[k].remaining -= v.used
}
}
for k := range combined {
val := bcc.rlpCombinedCounters[k].used + bcc.executionCombinedCounters[k].used + bcc.processingCombinedCounters[k].used
combined[k].used += val
combined[k].remaining -= val
}
return combined, nil
}

It seems like this method collects counters for blocks, l2 data, rlp, and stuff like that, but I'm not seeing where it actually accounts for the counters used by transactions. There is a similar function in the package called CombineCollectorsNoChanges which seems to add transaction counters:

txCounters := NewCounters()
for _, tx := range bcc.transactions {
_ = tx.CombineCountersInto(&txCounters)
for k, v := range txCounters {
combined[k].used += v.used
combined[k].remaining -= v.used
}
txCounters.NullateUsed()
}

If I switch to using this method, I can see the counter value increases a lot but seems to be about 3x higher than expected.

tclemos

tclemos commented on Jan 29, 2025

@tclemos
Author

Complementary to this, I can have a TX mined using Bali, but when I estimate its counters, I get an out of gas issue.

Steps to reproduce:

  • Deploys this SC (it's already deployed here on Bali: 0x09be87b1E6D1d3B9f11Bf0983320F9f05CD7bFE1)
// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract ZkCounters {
    uint256 public count;

    function maxKeccakHashes() public {
        count = 0;
        assembly {
            let _bytes := 10000000
            let test := keccak256(0, _bytes)
            test := keccak256(0, _bytes)
            test := keccak256(0, _bytes)
            test := keccak256(0, _bytes)
            test := keccak256(0, _bytes)
        }
    }
}
  • Send a TX to this SC with GasLimit=30000000 (this is the TX on Bali 0xfb47aafffe6a281aa0707b3a3f2da292e660bb97158463d7c06abb56439364cb)
  • Estimate counters for the same TX using zkevm_estimateCounters

Here are the logs showing the response I'm getting from the zkevm_estimateCounters for this test:

This shows the counters:

    log.go:59: *** RPC call to https://rpc-debug.internal.zkevm-rpc.com
    log.go:40: *** ├─ request: {"jsonrpc":"2.0","id":1,"method":"zkevm_estimateCounters","params":[{"From":"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","Gas":"0x1c9c380","GasPrice":"0x989680","Input":"0xe050f2bd","To":"0x09be87b1E6D1d3B9f11Bf0983320F9f05CD7bFE1","Value":"0x0"}]}
    log.go:42: *** └─ response: {"jsonrpc":"2.0","id":1,"result":{"countersUsed":{"gas":30000000,"keccakHashes":5,"poseidonhashes":69,"poseidonPaddings":5,"memAligns":0,"arithmetics":568,"binaries":647,"steps":9158,"SHA256hashes":0},"countersLimits":{"gas":30000000,"keccakHashes":9029,"poseidonhashes":1028275,"poseidonPaddings":569223,"memAligns":996141,"arithmetics":996141,"binaries":1992283,"steps":31876521,"SHA256hashes":7083},"revertInfo":{"message":"out of gas","data":null},"oocError":""}}
    log.go:55: *** | ------------------------------ - ------------------------------ - ------------------------------ |
    log.go:55: *** |                                            zkCounters                                            |
    log.go:55: *** | ------------------------------ - ------------------------------ - ------------------------------ |
    log.go:55: *** |                   counter name |                           used |                         limits |
    log.go:55: *** | ------------------------------ | ------------------------------ | ------------------------------ |
    log.go:55: *** |                            gas |                          3e+07 |                          3e+07 | 
    log.go:55: *** |                      memAligns |                              0 |                         996141 | 
    log.go:55: *** |                          steps |                           9158 |                  3.1876521e+07 | 
    log.go:55: *** |                   SHA256hashes |                              0 |                           7083 | 
    log.go:55: *** | ------------------------------ | ------------------------------ | ------------------------------ |
    log.go:55: *** |                   keccakHashes |                              5 |                           9029 |  <== target counter
    log.go:55: *** | ------------------------------ | ------------------------------ | ------------------------------ |
    log.go:55: *** |                 poseidonhashes |                             69 |                   1.028275e+06 | 
    log.go:55: *** |               poseidonPaddings |                              5 |                         569223 | 
    log.go:55: *** |                    arithmetics |                            568 |                         996141 | 
    log.go:55: *** |                       binaries |                            647 |                   1.992283e+06 | 
    log.go:55: *** | ------------------------------ | ------------------------------ | ------------------------------ |

This shows the TX was already mined, as we have blockHash and blockNumber in the response for eth_getTransactionByHash

    log.go:59: *** RPC call to https://rpc-debug.internal.zkevm-rpc.com
    log.go:40: *** ├─ request: {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionByHash","params":["0xfb47aafffe6a281aa0707b3a3f2da292e660bb97158463d7c06abb56439364cb"]}
    log.go:42: *** └─ response: {"jsonrpc":"2.0","id":1,"result":{"blockHash":"0x361cf257d2263aa328cae4297c8ccff9dec6555420eaa4fa71fd0213b83d4b10","blockNumber":"0x937a4f","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","gas":"0x1c9c380","gasPrice":"0x989680","hash":"0xfb47aafffe6a281aa0707b3a3f2da292e660bb97158463d7c06abb56439364cb","input":"0xe050f2bd","nonce":"0x172f","to":"0x09be87b1e6d1d3b9f11bf0983320f9f05cd7bfe1","transactionIndex":"0x0","value":"0x0","type":"0x0","chainId":"0x988","v":"0x1334","r":"0xf029c1b5e8d92de4a248677fbfa11e15138b7d556b8ddf528a4cc535bd0168c8","s":"0x65460b4d899923bbd6ce0aa7a340a5dd55b6d92168f1bb4c160f4b9af902fea0"}}
added
bugSomething isn't working
and removed
bugSomething isn't working
on Jan 29, 2025
self-assigned this
on Jan 29, 2025
revitteth

revitteth commented on Jan 29, 2025

@revitteth
Collaborator

Thanks for all the detail! Working on it now :)

tclemos

tclemos commented on Feb 12, 2025

@tclemos
Author

Any news about it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    zkevm_estimateCounters is always returning the same amount of Steps for different executions · Issue #1677 · 0xPolygonHermez/cdk-erigon