Skip to content

Commit 3aa3de6

Browse files
authored
Merge pull request #1894 from carver/net-sstore-original-value-bugfix
Lock in storage changes before transaction starts
2 parents f25d209 + 3ff3b35 commit 3aa3de6

File tree

7 files changed

+57
-4
lines changed

7 files changed

+57
-4
lines changed

eth/abc.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1573,6 +1573,16 @@ def commit(self, checkpoint: JournalDBCheckpoint) -> None:
15731573
"""
15741574
...
15751575

1576+
@abstractmethod
1577+
def lock_changes(self) -> None:
1578+
"""
1579+
Locks in changes to storage, typically just as a transaction starts.
1580+
1581+
This is used, for example, to look up the storage value from the start
1582+
of the transaction, when calculating gas costs in EIP-2200: net gas metering.
1583+
"""
1584+
...
1585+
15761586
@abstractmethod
15771587
def make_storage_root(self) -> None:
15781588
"""
@@ -2153,6 +2163,16 @@ def commit(self, snapshot: Tuple[Hash32, UUID]) -> None:
21532163
"""
21542164
...
21552165

2166+
@abstractmethod
2167+
def lock_changes(self) -> None:
2168+
"""
2169+
Locks in all changes to state, typically just as a transaction starts.
2170+
2171+
This is used, for example, to look up the storage value from the start
2172+
of the transaction, when calculating gas costs in EIP-2200: net gas metering.
2173+
"""
2174+
...
2175+
21562176
@abstractmethod
21572177
def persist(self) -> None:
21582178
"""

eth/db/account.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ def commit(self, checkpoint: JournalDBCheckpoint) -> None:
370370
for _, store in self._dirty_account_stores():
371371
store.commit(checkpoint)
372372

373+
def lock_changes(self) -> None:
374+
for _, store in self._dirty_account_stores():
375+
store.lock_changes()
376+
373377
def make_state_root(self) -> Hash32:
374378
for _, store in self._dirty_account_stores():
375379
store.make_storage_root()

eth/db/storage.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def __init__(self, db: AtomicDatabaseAPI, storage_root: Hash32, address: Address
157157
158158
.. code::
159159
160-
db -> _storage_lookup -> _storage_cache -> _journal_storage
160+
db -> _storage_lookup -> _storage_cache -> _locked_changes -> _journal_storage
161161
162162
db is the raw database, we can assume it hits disk when written to.
163163
Keys are stored as node hashes and rlp-encoded node values.
@@ -174,6 +174,10 @@ def __init__(self, db: AtomicDatabaseAPI, storage_root: Hash32, address: Address
174174
after a state root change. Otherwise, you will see data since the last
175175
storage root was calculated.
176176
177+
_locked_changes is a batch database that includes only those values that are
178+
un-revertable in the EVM. Currently, that means changes that completed in a
179+
previous transaction.
180+
177181
Journaling batches writes at the _journal_storage layer, until persist is called.
178182
It manages all the checkpointing and rollbacks that happen during EVM execution.
179183
@@ -183,11 +187,12 @@ def __init__(self, db: AtomicDatabaseAPI, storage_root: Hash32, address: Address
183187
self._address = address
184188
self._storage_lookup = StorageLookup(db, storage_root, address)
185189
self._storage_cache = CacheDB(self._storage_lookup)
186-
self._journal_storage = JournalDB(self._storage_cache)
190+
self._locked_changes = BatchDB(self._storage_cache)
191+
self._journal_storage = JournalDB(self._locked_changes)
187192

188193
def get(self, slot: int, from_journal: bool=True) -> int:
189194
key = int_to_big_endian(slot)
190-
lookup_db = self._journal_storage if from_journal else self._storage_cache
195+
lookup_db = self._journal_storage if from_journal else self._locked_changes
191196
try:
192197
encoded_value = lookup_db[key]
193198
except MissingStorageTrieNode:
@@ -237,9 +242,13 @@ def commit(self, checkpoint: JournalDBCheckpoint) -> None:
237242
# then flatten all changes, without persisting
238243
self._journal_storage.flatten()
239244

240-
def make_storage_root(self) -> None:
245+
def lock_changes(self) -> None:
241246
self._journal_storage.persist()
242247

248+
def make_storage_root(self) -> None:
249+
self.lock_changes()
250+
self._locked_changes.commit(apply_deletes=True)
251+
243252
def _validate_flushed(self) -> None:
244253
"""
245254
Will raise an exception if there are some changes made since the last persist.

eth/vm/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ def apply_transaction(self,
148148
transaction: SignedTransactionAPI
149149
) -> Tuple[ReceiptAPI, ComputationAPI]:
150150
self.validate_transaction_against_header(header, transaction)
151+
152+
# Mark current state as un-revertable, since new transaction is starting...
153+
self.state.lock_changes()
154+
151155
computation = self.state.apply_transaction(transaction)
152156
receipt = self.make_receipt(header, transaction, computation, self.state)
153157
self.validate_receipt(receipt)

eth/vm/state.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ def commit(self, snapshot: Tuple[Hash32, UUID]) -> None:
175175
_, account_snapshot = snapshot
176176
self._account_db.commit(account_snapshot)
177177

178+
def lock_changes(self) -> None:
179+
self._account_db.lock_changes()
180+
178181
def persist(self) -> None:
179182
self._account_db.persist()
180183

newsfragments/1893.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix for net gas metering (EIP-2200) in Istanbul. The "original value" used to calculate gas
2+
costs was incorrectly accessing the value at the start of the block, instead of the start of the
3+
transaction.

tests/core/vm/test_vm_state.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,13 @@ def test_revert_selfdestruct(state, read_storage_before_snapshot):
101101
# "starting" storage root hash would always be the empty one, which causes
102102
# it to not be able to recover from a revert
103103
assert state.get_storage(ADDRESS, 1) == 2
104+
105+
106+
def test_lock_state(state):
107+
assert state.get_storage(ADDRESS, 1, from_journal=False) == 0
108+
109+
state.set_storage(ADDRESS, 1, 2)
110+
assert state.get_storage(ADDRESS, 1, from_journal=False) == 0
111+
112+
state.lock_changes()
113+
assert state.get_storage(ADDRESS, 1, from_journal=False) == 2

0 commit comments

Comments
 (0)