|
| 1 | +# Nimbus |
| 2 | +# Copyright (c) 2025 Status Research & Development GmbH |
| 3 | +# Licensed under either of |
| 4 | +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or |
| 5 | +# http://www.apache.org/licenses/LICENSE-2.0) |
| 6 | +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or |
| 7 | +# http://opensource.org/licenses/MIT) |
| 8 | +# at your option. This file may not be copied, modified, or |
| 9 | +# distributed except according to those terms. |
| 10 | + |
| 11 | +{.used.} |
| 12 | + |
| 13 | +import |
| 14 | + stew/byteutils, |
| 15 | + unittest2, |
| 16 | + ../execution_chain/db/ledger, |
| 17 | + ../execution_chain/common/common |
| 18 | + |
| 19 | +suite "Stateroot Mismatch Checks": |
| 20 | + |
| 21 | + # The purpose of this test is to reproduce an issue where a state root mismatch |
| 22 | + # was encountered when using multiple snapshots before committing to the database. |
| 23 | + # See here for the full context of the problem: https://github.com/status-im/nimbus-eth1/issues/3381#issuecomment-3131162957 |
| 24 | + |
| 25 | + # Problem was related to having stale account and storage slot caches in the main |
| 26 | + # database because the required values were not being updated in the call to persist. |
| 27 | + # This was because when we persist changes to the database we stop iterating over the txFrames |
| 28 | + # once we hit a snapshot but the account and storage caches were not copying over any needed |
| 29 | + # cache values when moving a snapshot. |
| 30 | + # See the fix here: https://github.com/status-im/nimbus-eth1/pull/3527 |
| 31 | + |
| 32 | + # A number of steps are required to reproduce the problem which is what these |
| 33 | + # tests cover. These steps are as follows: |
| 34 | + # 1. Create a txFrame, create the first checkpoint and persist some state to the database. |
| 35 | + # 2. Create a new txFrame and then read a value that doesn't exist and then set the value |
| 36 | + # and then create the second checkpoint (the state read and write here triggers the issue |
| 37 | + # because the write will be lost but the read causes a nil value to be set in the db |
| 38 | + # cache imediately). |
| 39 | + # 3. Create a new txFrame and then set some more state then create the third checkpoint |
| 40 | + # and then persist the changes to the database (we just do this step so that |
| 41 | + # we have multiple checkpoints created before the call to persist which is required |
| 42 | + # to reproduce the issue). |
| 43 | + # 4. Create a new txFrame and check that the state (account/slot) which was read |
| 44 | + # in step 2 is as expected before and after persisting to the database. |
| 45 | + |
| 46 | + setup: |
| 47 | + let |
| 48 | + memDB = DefaultDbMemory.newCoreDbRef() |
| 49 | + addr1 = address"0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" |
| 50 | + addr2 = address"0x1f572e5295c57f15886f9b263e2f6d2d6c7b5ec7" |
| 51 | + addr3 = address"0x2f572e5295c57f15886f9b263e2f6d2d6c7b5ec8" |
| 52 | + code = hexToSeqByte("0x010203") |
| 53 | + slot1 = 1.u256 |
| 54 | + slot2 = 2.u256 |
| 55 | + slot3 = 3.u256 |
| 56 | + |
| 57 | + test "Stateroot check - Accounts": |
| 58 | + |
| 59 | + # Persist first update to database - changes from a single checkpoint |
| 60 | + let txFrame0 = memDB.baseTxFrame().txFrameBegin() |
| 61 | + block: |
| 62 | + let ac0 = LedgerRef.init(txFrame0, false) |
| 63 | + ac0.setBalance(addr3, 300.u256) |
| 64 | + ac0.persist(clearCache = true) |
| 65 | + txFrame0.checkpoint(1.BlockNumber, skipSnapshot = false) |
| 66 | + memDB.persist(txFrame0, Opt.none(Hash32)) |
| 67 | + |
| 68 | + |
| 69 | + # Persist second update to database - changes from two checkpoints |
| 70 | + let txFrame1 = memDB.baseTxFrame().txFrameBegin() |
| 71 | + block: |
| 72 | + let |
| 73 | + ac1 = LedgerRef.init(txFrame1, false) |
| 74 | + preStateRoot = ac1.getStateRoot() |
| 75 | + |
| 76 | + ac1.addBalance(addr1, 50.u256) |
| 77 | + ac1.persist(clearCache = true) |
| 78 | + txFrame1.checkpoint(2.BlockNumber, skipSnapshot = false) |
| 79 | + |
| 80 | + let postStateRoot = ac1.getStateRoot() |
| 81 | + check preStateRoot != postStateRoot |
| 82 | + |
| 83 | + let txFrame2 = txFrame1.txFrameBegin() |
| 84 | + block: |
| 85 | + let |
| 86 | + ac2 = LedgerRef.init(txFrame2, false) |
| 87 | + preStateRoot = ac2.getStateRoot() |
| 88 | + |
| 89 | + ac2.addBalance(addr2, 200.u256) |
| 90 | + ac2.persist(clearCache = true) |
| 91 | + txFrame2.checkpoint(3.BlockNumber, skipSnapshot = false) |
| 92 | + memDB.persist(txFrame2, Opt.none(Hash32)) |
| 93 | + |
| 94 | + let postStateRoot = ac2.getStateRoot() |
| 95 | + check preStateRoot != postStateRoot |
| 96 | + |
| 97 | + |
| 98 | + # Persist third update to database - changes from a single checkpoint |
| 99 | + let txFrame3 = memDB.baseTxFrame().txFrameBegin() |
| 100 | + block: |
| 101 | + let |
| 102 | + ac3 = LedgerRef.init(txFrame3, false) |
| 103 | + preStateRoot = ac3.getStateRoot() |
| 104 | + |
| 105 | + ac3.addBalance(addr1, 50.u256) |
| 106 | + ac3.persist(clearCache = true) |
| 107 | + |
| 108 | + check: |
| 109 | + # Check balances |
| 110 | + ac3.getBalance(addr1) == 100.u256 |
| 111 | + ac3.getBalance(addr2) == 200.u256 |
| 112 | + ac3.getBalance(addr3) == 300.u256 |
| 113 | + |
| 114 | + txFrame3.checkpoint(4.BlockNumber, skipSnapshot = false) |
| 115 | + memDB.persist(txFrame3, Opt.none(Hash32)) |
| 116 | + |
| 117 | + let postStateRoot = ac3.getStateRoot() |
| 118 | + check: |
| 119 | + preStateRoot != postStateRoot |
| 120 | + |
| 121 | + # Check balances |
| 122 | + ac3.getBalance(addr1) == 100.u256 |
| 123 | + ac3.getBalance(addr2) == 200.u256 |
| 124 | + ac3.getBalance(addr3) == 300.u256 |
| 125 | + |
| 126 | + test "Stateroot check - Storage Slots": |
| 127 | + |
| 128 | + # Persist first update to database - changes from a single checkpoint |
| 129 | + let txFrame0 = memDB.baseTxFrame().txFrameBegin() |
| 130 | + block: |
| 131 | + let ac0 = LedgerRef.init(txFrame0, false) |
| 132 | + ac0.setBalance(addr1, 0.u256) |
| 133 | + ac0.setCode(addr1, code) |
| 134 | + ac0.setStorage(addr1, slot3, 300.u256) |
| 135 | + ac0.persist(clearCache = true) |
| 136 | + txFrame0.checkpoint(1.BlockNumber, skipSnapshot = false) |
| 137 | + memDB.persist(txFrame0, Opt.none(Hash32)) |
| 138 | + |
| 139 | + |
| 140 | + # Persist second update to database - changes from two checkpoints |
| 141 | + let txFrame1 = memDB.baseTxFrame().txFrameBegin() |
| 142 | + block: |
| 143 | + let |
| 144 | + ac1 = LedgerRef.init(txFrame1, false) |
| 145 | + preStateRoot = ac1.getStateRoot() |
| 146 | + |
| 147 | + discard ac1.getStorage(addr1, slot1) |
| 148 | + ac1.setStorage(addr1, slot1, 50.u256) |
| 149 | + ac1.persist(clearCache = true) |
| 150 | + txFrame1.checkpoint(2.BlockNumber, skipSnapshot = false) |
| 151 | + |
| 152 | + let postStateRoot = ac1.getStateRoot() |
| 153 | + check preStateRoot != postStateRoot |
| 154 | + |
| 155 | + let txFrame2 = txFrame1.txFrameBegin() |
| 156 | + block: |
| 157 | + let |
| 158 | + ac2 = LedgerRef.init(txFrame2, false) |
| 159 | + preStateRoot = ac2.getStateRoot() |
| 160 | + |
| 161 | + ac2.setStorage(addr1, slot2, 200.u256) |
| 162 | + ac2.persist(clearCache = true) |
| 163 | + txFrame2.checkpoint(3.BlockNumber, skipSnapshot = false) |
| 164 | + memDB.persist(txFrame2, Opt.none(Hash32)) |
| 165 | + |
| 166 | + let postStateRoot = ac2.getStateRoot() |
| 167 | + check preStateRoot != postStateRoot |
| 168 | + |
| 169 | + |
| 170 | + # Persist third update to database - changes from a single checkpoint |
| 171 | + let txFrame3 = memDB.baseTxFrame().txFrameBegin() |
| 172 | + block: |
| 173 | + let |
| 174 | + ac3 = LedgerRef.init(txFrame3, false) |
| 175 | + preStateRoot = ac3.getStateRoot() |
| 176 | + |
| 177 | + let slotValue = ac3.getStorage(addr1, slot1) |
| 178 | + ac3.setStorage(addr1, slot1, slotValue + 50.u256) |
| 179 | + ac3.persist(clearCache = true) |
| 180 | + |
| 181 | + check: |
| 182 | + # Check slots |
| 183 | + ac3.getStorage(addr1, slot1) == 100.u256 |
| 184 | + ac3.getStorage(addr1, slot2) == 200.u256 |
| 185 | + ac3.getStorage(addr1, slot3) == 300.u256 |
| 186 | + |
| 187 | + txFrame3.checkpoint(4.BlockNumber, skipSnapshot = false) |
| 188 | + memDB.persist(txFrame3, Opt.none(Hash32)) |
| 189 | + |
| 190 | + let postStateRoot = ac3.getStateRoot() |
| 191 | + check: |
| 192 | + preStateRoot != postStateRoot |
| 193 | + # Check slots |
| 194 | + ac3.getStorage(addr1, slot1) == 100.u256 |
| 195 | + ac3.getStorage(addr1, slot2) == 200.u256 |
| 196 | + ac3.getStorage(addr1, slot3) == 300.u256 |
0 commit comments