Compare commits

..

131 Commits

Author SHA1 Message Date
Gary Rong
0dd173a727 params: release Geth v1.14.5 2024-06-06 21:17:53 +08:00
Felix Lange
85459e1439 p2p/discover: unwrap 4-in-6 UDP source addresses (#29944)
Fixes an issue where discovery responses were not recognized.
2024-06-06 16:15:22 +03:00
Hteev Oli
0750cb0c8f p2p/netutil: fix comments (#29942) 2024-06-06 10:56:41 +03:00
Marquis Shanahan
cbbfa3eac0 rlp: no need to repeat called len method (#29936)
rlp: no need to repeat calling len
2024-06-06 10:55:38 +03:00
Marius Kjærstad
6c518fe606 build: upgrade -dlgo version to Go 1.22.4 (#29938) 2024-06-06 10:52:57 +03:00
Felix Lange
bc6569462d p2p: use netip.Addr where possible (#29891)
enode.Node was recently changed to store a cache of endpoint information. The IP address in the cache is a netip.Addr. I chose that type over net.IP because it is just better. netip.Addr is meant to be used as a value type. Copying it does not allocate, it can be compared with ==, and can be used as a map key.

This PR changes most uses of Node.IP() into Node.IPAddr(), which returns the cached value directly without allocating.
While there are still some public APIs left where net.IP is used, I have converted all code used internally by p2p/discover to the new types. So this does change some public Go API, but hopefully not APIs any external code actually uses.

There weren't supposed to be any semantic differences resulting from this refactoring, however it does introduce one: In package p2p/netutil we treated the 0.0.0.0/8 network (addresses 0.x.y.z) as LAN, but netip.Addr.IsPrivate() doesn't. The treatment of this particular IP address range is controversial, with some software supporting it and others not. IANA lists it as special-purpose and invalid as a destination for a long time, so I don't know why I put it into the LAN list. It has now been marked as special in p2p/netutil as well.
2024-06-05 19:31:04 +02:00
Marquis Shanahan
d09ddac399 core/rawdb: remove unused deriveLogFields (#29913)
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
Co-authored-by: Martin HS <martin@swende.se>
2024-06-05 21:05:00 +08:00
Hteev Oli
e85e21c932 core/state, eth/tracers: fix typos (#29932) 2024-06-05 11:07:37 +02:00
Péter Szilágyi
fc40d68e5b params: begin v1.14.5 release cycle 2024-06-05 11:20:35 +03:00
Péter Szilágyi
5550d8399f params: release Geth v1.14.4 2024-06-05 11:06:37 +03:00
rjl493456442
125fb1ff58 core/state: avoid data race (#29924) 2024-06-04 15:51:34 +03:00
SangIlMo
682ae838b2 internal/ethapi: recap higher args.Gas with block GasLimit in DoEstimateGas (#29738)
* internal/ethapi: recap higher args.Gas with block GasLimit in DoEstimateGas

* internal/ethapi: fix gas estimator capping code

* internal/ethapi: fix test

* fix goimports lint (remove space)

---------

Co-authored-by: Péter Szilágyi <peterke@gmail.com>
2024-06-04 10:59:41 +03:00
Roy Crihfield
68c0ec0815 trie: iterate values pre-order and fix seek behavior (#27838)
This pull request fixes the pre-order trie traversal by defining 
a more accurate iterator order and path comparison rule.

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2024-06-04 11:17:26 +08:00
Steven Wang
adbbd8cd7b core/state: prefetch account trie while starting a prefetcher (#29919)
Always prefetch the account trie while starting the prefetcher.

Co-authored-by: steven <steven@stevendeMacBook-Pro.local>
Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
2024-06-04 11:12:24 +08:00
tianyeyouyou
a6751d6fc8 core/rawdb,eth/protocols,p2p: prealloc slice size (#29893)
chore: prealloc slice size
2024-06-03 15:51:04 +03:00
miles
7270cba25c log: fix a typo (#29883)
logger
2024-06-03 15:50:24 +03:00
maskpp
b36c73813c beacon/engine: prealloc capacity for map and slice (#29903)
* prealloc capacity for map and slice

* revert unnecessary change
2024-06-03 15:38:08 +03:00
HAOYUatHZ
50405e29b7 cmd/evm/internal/t8ntool: fix a typo (#29887)
* i8ntool: fix a typo

* cmd/evm/internal/t8ntool: fix typo typo

---------

Co-authored-by: Péter Szilágyi <peterke@gmail.com>
2024-06-03 15:31:23 +03:00
rjl493456442
d38b88a5a1 core/state: introduce stateupdate structure (#29530)
* core/state: introduce stateUpate structure

* core/state: remove outdated function description

* core/state: address comments
2024-06-03 14:17:12 +03:00
Chris Ziogas
c9e0b3105b Supply delta live tracer (#29347)
Introduces the first built-in live tracer. The supply tracer tracks ETH supply changes across blocks
and writes the output to disk. This will need to be enabled through CLI using the `--vmtrace supply` flag.

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-06-03 12:30:27 +02:00
Sina M
d4b81f0e08 CODEOWNERS: @s1na owns core/tracing (#29899)
Update CODEOWNERS
2024-05-31 18:40:09 +02:00
Péter Szilágyi
2613523cb5 miner: lower default min miner tip from 1 gwei to 0.001 gwei (#29895) 2024-05-31 10:39:40 +03:00
tianyeyouyou
bdc62f9beb common/math: rename variable name int to n (#29890)
* chore: rename variable name `int` to `in`

* chore: rename variable name `int` to `n`
2024-05-31 10:25:49 +03:00
yujinpark
5d7d48fc3e eth/gasprice: add comment to constant (#29892)
Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-30 18:22:23 +02:00
SuiYuan
2262bf3415 crypto/secp256k1: change receiver variable name to lowercase (#29889) 2024-05-30 16:24:16 +02:00
SuiYuan
e015c1116f ethdb: remove unnecessary function wrapper (#29888) 2024-05-30 21:23:04 +08:00
Felix Lange
6bb13e8e2b eth/catalyst: ensure TxPool is synced in Fork (#29876)
This should fix an occasional test failure in ethclient/simulated.TestForkResendTx.
Inspection of logs revealed the cause of the failure to be that the txpool was not done
reorganizing by the time Fork is called.
2024-05-29 15:56:52 +02:00
hattizai
2f06c1e854 cmd/devp2p: fix node.TCP -> node.UDP (#29879) 2024-05-29 15:55:51 +02:00
Felix Lange
3fef53447f build: upgrade to golangci-lint v1.59.0 (#29875) 2024-05-29 16:31:27 +03:00
Felix Lange
94a8b296e4 p2p/discover: refactor node and endpoint representation (#29844)
Here we clean up internal uses of type discover.node, converting most code to use
enode.Node instead. The discover.node type used to be the canonical representation of
network hosts before ENR was introduced. Most code worked with *node to avoid conversions
when interacting with Table methods. Since *node also contains internal state of Table and
is a mutable type, using *node outside of Table code is prone to data races. It's also
cleaner not having to wrap/unwrap *enode.Node all the time.

discover.node has been renamed to tableNode to clarify its purpose.

While here, we also change most uses of net.UDPAddr into netip.AddrPort. While this is
technically a separate refactoring from the *node -> *enode.Node change, it is more
convenient because *enode.Node handles IP addresses as netip.Addr. The switch to package
netip in discovery would've happened very soon anyway.

The change to netip.AddrPort stops at certain interface points. For example, since package
p2p/netutil has not been converted to use netip.Addr yet, we still have to convert to
net.IP/net.UDPAddr in a few places.
2024-05-29 15:02:26 +02:00
牛晓婕
e26fa9e40e core/state: fix typo in comment (#29639) 2024-05-29 14:44:14 +02:00
trillo
2f0e63e5ac eth/downloader, eth/tracer: fix typos in comments (#29707) 2024-05-29 14:43:07 +02:00
PolyMa
06263b1b35 all: fix typos in comments (#29873)
fix using `a` & `the` simutaneously
2024-05-29 12:24:10 +02:00
Steven Wang
b8cf1636d4 accounts: fix TestUpdateKeyfileContents (#29867)
Create the directory before NewKeyStore. This ensures the watcher successfully starts on
the first attempt, and waitWatcherStart functions as intended.
2024-05-29 12:12:57 +02:00
lilasxie
153f8da887 p2p/nodestate: remove unused package (#29872) 2024-05-29 12:11:18 +02:00
bugmaker9371
daf4f72077 p2p/simulations: remove stale information about docker adapter (#29874) 2024-05-29 12:09:58 +02:00
Martin HS
5534c849b6 go.mod: update a number of dependencies (#29763)
* deps: update go-winio

* deps: update fastcache

* deps: update golang-set

* update fatih/color

* update natefinch/lumberjack.v2
2024-05-29 11:30:25 +03:00
lightclient
cc22e0cdf0 p2p/discover: fix update logic in handleAddNode (#29836)
It seems the semantic differences between addFoundNode and addInboundNode were lost in
#29572. My understanding is addFoundNode is for a node you have not contacted directly
(and are unsure if is available) whereas addInboundNode is for adding nodes that have
contacted the local node and we can verify they are active.

handleAddNode seems to be the consolidation of those two methods, yet it bumps the node in
the bucket (updating it's IP addr) even if the node was not an inbound. This PR fixes
this. It wasn't originally caught in tests like TestTable_addSeenNode because the
manipulation of the node object actually modified the node value used by the test.

New logic is added to reject non-inbound updates unless the sequence number of the
(signed) ENR increases. Inbound updates, which are published by the updated node itself,
are always accepted. If an inbound update changes the endpoint, the node will be
revalidated on an expedited schedule.

Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-28 21:30:17 +02:00
jwasinger
171430c3f5 core/state: remove unused error from prefetcher trie method (#29768)
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-28 19:54:55 +02:00
jwasinger
e517183719 eth, eth/downloader: remove references to LightChain, LightSync (#29711)
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2024-05-28 19:52:08 +02:00
Felix Lange
af0a3274be p2p/discover: fix crash when revalidated node is removed (#29864)
In #29572, I assumed the revalidation list that the node is contained in could only ever
be changed by the outcome of a revalidation request. But turns out that's not true: if the
node gets removed due to FINDNODE failure, it will also be removed from the list it is in.
This causes a crash.

The invariant is: while node is in table, it is always in exactly one of the two lists. So
it seems best to store a pointer to the current list within the node itself.
2024-05-28 18:13:03 +02:00
rjl493456442
b88051ec83 core/rawdb, triedb/pathdb: fix freezer read-only option (#29823) 2024-05-28 14:41:11 +02:00
Martin HS
61932e4710 cmd/geth: update testdata (vulncheck) (#29714) 2024-05-28 14:16:45 +02:00
gitglorythegreat
871e55d93e core/state: fix typos in comments (#29767) 2024-05-28 14:10:27 +02:00
Martin HS
42471d7a3e core/vm/runtime: set random to enable merge-opcodes (#29799) 2024-05-28 13:45:16 +02:00
trillo
caafa93598 all: improve some error strings (#29842) 2024-05-28 13:44:40 +02:00
Sina M
ea6c16007c eth/tracers: clear error for non-reverting pre-homestead fail (#29824) 2024-05-28 13:12:46 +02:00
rjl493456442
513276864b eth/downloader: fix flaky test (#29852)
This pull request fixes the flay test TestSkeletonSyncRetrievals. In this test, we first
trigger a sync cycle and wait for it to meet certain expectations. We then inject a new
head and potentially also a new peer, then perform another final sync. The test now
performs the newPeer addition before launching the final sync, and waits a bit for that
peer to get registered. This fixes the logic race that made the test fail sometimes.

Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
2024-05-27 16:26:55 +02:00
Steven Wang
1a4e4a4fe1 miner: fix TestBuildPayload sporadic failure (#29853)
miner: fix TestBuildPayload sporadic failure

Co-authored-by: steven <steven@stevendeMacBook-Pro.local>
2024-05-27 19:42:07 +08:00
Mobin Mohanan
7224576fba core, eth/protocols/snap, internal/ethapi: remove redundant types (#29841) 2024-05-27 14:39:39 +08:00
Wukingbow
7f5cc02a99 metrics: fix function comment (#29843) 2024-05-27 14:34:53 +08:00
winterjihwan
d1d9f34e51 core/types: clarify set inclusion in comments (#29839) 2024-05-26 11:54:37 +02:00
levisyin
b6474e9f90 metrics: add test for SampleSnapshot.Sum (#29831) 2024-05-24 11:34:30 +02:00
gitglorythegreat
64b1cd8aaf p2p: fix typos (#29828) 2024-05-24 11:33:19 +02:00
Halimao
08fe6a8614 metrics: fix flaky testTestExpDecaySampleNanosecondRegression (#29832) 2024-05-24 15:20:05 +08:00
Aaron Chen
61b3d93bb0 p2p/enode: fix TCPEndpoint (#29827) 2024-05-23 23:17:51 +02:00
Felix Lange
cc9e2bd9dd p2p/enode: fix endpoint determination for IPv6 (#29801)
enode.Node has separate accessor functions for getting the IP, UDP port and TCP port.
These methods performed separate checks for attributes set in the ENR.

With this PR, the accessor methods will now return cached information, and the endpoint is
determined when the node is created. The logic to determine the preferred endpoint is now
more correct, and considers how 'global' each address is when both IPv4 and IPv6 addresses
are present in the ENR.
2024-05-23 14:27:03 +02:00
Felix Lange
6a9158bb1b p2p/discover: improved node revalidation (#29572)
Node discovery periodically revalidates the nodes in its table by sending PING, checking
if they are still alive. I recently noticed some issues with the implementation of this
process, which can cause strange results such as nodes dropping unexpectedly, certain
nodes not getting revalidated often enough, and bad results being returned to incoming
FINDNODE queries.

In this change, the revalidation process is improved with the following logic:

- We maintain two 'revalidation lists' containing the table nodes, named 'fast' and 'slow'.
- The process chooses random nodes from each list on a randomized interval, the interval being
  faster for the 'fast' list, and performs revalidation for the chosen node.
- Whenever a node is newly inserted into the table, it goes into the 'fast' list.
  Once validation passes, it transfers to the 'slow' list. If a request fails, or the
  node changes endpoint, it transfers back into 'fast'.
- livenessChecks is incremented by one for successful checks. Unlike the old implementation,
  we will not drop the node on the first failing check. We instead quickly decay the
  livenessChecks give it another chance.
- Order of nodes in bucket doesn't matter anymore.

I am also adding a debug API endpoint to dump the node table content.

Co-authored-by: Martin HS <martin@swende.se>
2024-05-23 14:26:09 +02:00
Halimao
70bee977d6 metrics: fix out of range error message (#29821) 2024-05-23 12:34:34 +02:00
Mobin Mohanan
b779e469da Makefile: add fmt, update help (#29777) 2024-05-23 11:56:32 +02:00
Sina M
fa581766f5 eth/tracers: fix json logger for evm blocktest (#29795) 2024-05-23 10:55:54 +02:00
Karl Bartel
0d4cdb3dbe internal/ethapi: fix typos (#29784)
Fix typos in api.go
2024-05-23 10:41:51 +02:00
Martin HS
7fd7c1f7dd eth/tracers: fix basefee context for traceBlock (#29811)
This fixes an issue for `debug_traceBlock*` methods where the BASEFEE opcode was returning always 0. This caused the method return invalid results.

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-05-21 18:27:36 +02:00
cocoyeal
be5df74ed5 trie: update the valid function comments (#29809) 2024-05-21 19:53:34 +08:00
rjl493456442
473ee8fc07 trie, eth/protocols/snap: sanitize the committed node data (#29485) 2024-05-16 17:58:35 +08:00
zhiqiangxu
7ed52c949e core: move balanceCheck addition in buyGas (#29762)
It's a bit confusing to add msg.value into the balanceCheck within the conditional.
No impact on block validation since GasFeeCap is always set when processing transactions.
2024-05-15 14:23:24 +02:00
cario-dev
d2f00cb54e .github: upgrade to action versions with node20 (#29776)
* github: upgrade checkout action to version with node20

* Update go.yml

---------

Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-14 15:46:11 +02:00
0xbeny
8919c5c0fc core: deploy EIP-4788 contract in dev mode genesis (#29655)
Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-14 15:04:32 +02:00
rjl493456442
be3284373f core/state: remove useless operation (#29769) 2024-05-14 14:54:49 +02:00
rjl493456442
5b3e3cd2be tests: update tests (#29730) 2024-05-13 21:34:29 +08:00
Péter Szilágyi
2ac83e197b core/state: blocking prefetcher on term signal, parallel updates (#29519)
* core/state: trie prefetcher change: calling trie() doesn't stop the associated subfetcher

Co-authored-by: Martin HS <martin@swende.se>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>

* core/state: improve prefetcher

* core/state: restore async prefetcher stask scheduling

* core/state: finish prefetching async and process storage updates async

* core/state: don't use the prefetcher for missing snapshot items

* core/state: remove update concurrency for Verkle tries

* core/state: add some termination checks to prefetcher async shutdowns

* core/state: differentiate db tries and prefetched tries

* core/state: teh teh teh

---------

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
Co-authored-by: Martin HS <martin@swende.se>
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2024-05-13 15:47:45 +03:00
Guillaume Ballet
44a50c9f96 cmd, core, params, trie: add verkle access witness gas charging (#29338)
Implements some of the changes required to charge and do gas accounting in verkle testnet.
2024-05-10 20:13:11 +02:00
Hteev Oli
47af69c2bc core, beacon, ethdb: fix typos (#29748)
* core, beacon, ethdb: fix typos

* revert file that can't be changed
2024-05-10 19:48:14 +02:00
cocoyeal
603fd898d4 event: fix typo (#29749)
typo: of -> or
2024-05-10 19:44:07 +02:00
rjl493456442
e5f5eaebc4 core/state: remove slot dirtyness if it's set back to origin value (#29731)
* core/state: remove slot dirtiness if it's set back to origin value

* core/state: suggestion from martin
2024-05-10 10:57:38 +03:00
Felix Lange
74edc93864 params: gofmt 2024-05-09 16:07:32 +02:00
Felix Lange
0e456d9eeb .travis.yml: disable normal unit tests in cron job (#29746) 2024-05-09 16:05:42 +02:00
Felix Lange
6d51c1f5f4 params: begin v1.14.4 release cycle 2024-05-09 12:40:37 +02:00
Felix Lange
ab48ba42f4 params: release go-ethereum v1.14.3 stable 2024-05-09 12:34:54 +02:00
Felix Lange
804afb8faa .travis.yml: restore PPA condition and bump timeouts (#29742) 2024-05-08 20:46:54 +02:00
Felix Lange
faff03c403 .travis.yml: enable PPA upload on push and fix apt-get command (#29741) 2024-05-08 20:28:05 +02:00
Felix Lange
1a79f8fe58 params: begin v1.14.3 release cycle 2024-05-08 16:31:14 +02:00
Felix Lange
35b2d07f4b params: release go-ethereum v1.14.2 stable 2024-05-08 16:26:01 +02:00
Felix Lange
eeb22089fd .travis.yml: fix package install on PPA builder 2024-05-08 14:34:58 +02:00
Felix Lange
14f4228472 params: begin v1.14.2 release cycle 2024-05-08 14:30:18 +02:00
Felix Lange
dd09f7e3fa params: release go-ethereum v1.14.1 stable 2024-05-08 14:28:40 +02:00
Felix Lange
6154f87c33 .travis.yml: fix apt-get options (#29734) 2024-05-08 11:33:07 +02:00
Felix Lange
dd4afb9fec .travis.yml: fix install of gcc-multilib (#29733) 2024-05-08 11:08:55 +02:00
rjl493456442
9ec50080eb core: use in-memory freezer for tests (#29720)
* core: simplify chain tests

* core, eth, cmd: use in-memory freezer for tests

* core: restore tests
2024-05-08 09:43:33 +03:00
Felix Lange
e96de6489c build: upgrade to go 1.22.3 (#29725) 2024-05-07 22:08:29 +02:00
Martin HS
71aa15c98f travis: use ubuntu noble (24.04) instead of bionic (18.04) (#29723) 2024-05-07 21:24:58 +02:00
nand2
d6e91e2e05 eth/gasestimator: include blobs in virtual balance computation (#29703)
Fixes #29702

Co-authored-by: Felix Lange <fjl@twurst.com>
2024-05-07 14:27:14 +02:00
Nathan
e4b8058d5a eth/gasprice: add query limit for FeeHistory to defend DDOS attack (#29644)
* eth/gasprice: add query limit for FeeHistory to defend DDOS attack

* fix return values after cherry-pick

---------

Co-authored-by: Eric <45141191+zlacfzy@users.noreply.github.com>
2024-05-07 10:25:15 +03:00
Maciej Kulawik
3e896c875a ethdb/pebble: fix pebble metrics registration (#29699)
ethdb/pebble: use GetOrRegister instead of NewRegistered when creating metrics
2024-05-06 14:42:22 +03:00
Guillaume Ballet
43cbcd78ea core, core/state: move TriesInMemory to state package (#29701) 2024-05-06 13:28:53 +02:00
Matthieu Vachon
a09a610384 core/tracing: add system call callback when performing ProcessBeaconBlockRoot (#29355)
Added a start/end system where tracer can be notified that processing of some Ethereum system calls is starting processing and also notifies it when the processing has completed.

Doing a start/end for system call will enable tracers to "route" incoming next tracing events to go to a separate bucket than other EVM calls. Those not interested by this fact can simply avoid registering the hooks.

The EVM call is going to be traced normally afterward between the signals provided by those 2 new hooks but outside of a transaction context OnTxStart/End. That something implementors of live tracers will need to be aware of (since only "trx tracers" are not concerned by ProcessBeaconRoot).

---------

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-05-06 13:21:55 +02:00
Kiarash Hajian
905e325cd8 p2p/discover/v5wire: add tests for invalid handshake and auth data size (#29708) 2024-05-06 13:17:19 +02:00
rjl493456442
86a1f0c394 core/rawdb: fix ancient root folder (#29697) 2024-05-02 13:26:07 +03:00
maskpp
2c67fab0d7 trie/pathdb: preallocate map capacity (#29690)
* preallocated capacity for map's certain usege of memory

* preallocated capacity for map's certain usege of memory
2024-05-02 12:35:45 +03:00
Nathan
fbf6238ae9 params: fix misleading comments (#29684) 2024-05-02 11:21:11 +03:00
Aaron Chen
bc609e852a core/vm: remove redundant error checks (#29692) 2024-05-02 11:18:59 +03:00
Péter Szilágyi
682ee820fa core/state: parallelise parts of state commit (#29681)
* core/state, internal/workerpool: parallelize parts of state commit

* core, internal: move workerpool into syncx

* core/state: use errgroups, commit accounts concurrently

* core: resurrect detailed commit timers to almost-accuracy
2024-05-02 11:18:27 +03:00
rjl493456442
9f96e07c1c core/rawdb, trie: improve db APIs for accessing trie nodes (#29362)
* core/rawdb, trie: improve db APIs for accessing trie nodes

* triedb/pathdb: fix
2024-04-30 16:25:35 +02:00
Bin
f8820f170c accounts, cmd/geth, core: close opened files (#29598)
* fix: open file used up but not closed

* feat: more same case

* feat: accept conversation
2024-04-30 15:47:21 +02:00
jwasinger
45baf21111 eth/downloader: purge pre-merge sync code (#29281)
This PR removes pre-merge sync logic from the downloader. Now-irrelevant tests are removed and others have been updated.
2024-04-30 15:46:53 +02:00
lightclient
2e8e35f2ad all: refactor so NewBlock, WithBody take types.Body (#29482)
* all: refactor so NewBlock(..) and WithBody(..) take a types.Body

* core: fixup comments, remove txs != receipts panic

* core/types: add empty withdrawls to body if len == 0
2024-04-30 14:55:08 +02:00
Martin HS
5e07054589 internal/ethapi: listen to ctx cancellation in access list (#29686) 2024-04-30 14:48:54 +02:00
Marius van der Wijden
bd6bc37eec core/vm: add subgroup checks for mul/mulexp for G1/G2 (#29637) 2024-04-30 14:35:48 +02:00
Dragan Milic
7c7e3a77fc eth/tracers/native: fix flatCallTracer Stop() bug (#29623)
Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
2024-04-30 14:33:22 +02:00
Aaron Chen
ea89f9adf0 core/vm: remove a redundant zero check in opAddmod (#29672) 2024-04-30 14:08:13 +02:00
Martin HS
242b24af9f trie/trienode: minor speedup in nodeset merging (#29683) 2024-04-30 19:51:04 +08:00
rjl493456442
f46c878441 core/rawdb: implement in-memory freezer (#29135) 2024-04-30 11:33:22 +02:00
felipe
c04b8e6d74 cmd/utils: require TTD and difficulty to be zero at genesis for dev mode (#29579) 2024-04-30 11:22:57 +02:00
Nathan
69f815f6f5 params: print time value instead of pointer in ConfigCompatError (#29514) 2024-04-30 11:22:02 +02:00
maskpp
fecc8a0f4a cmd/evm/internal/t8ntool, core: prealloc map sizes where possible (#29620)
set cap for map in a certain scenario
2024-04-30 11:19:59 +02:00
Aaron Chen
8c3fc56d7f p2p/simulations/adapters: use maps.Clone (#29626) 2024-04-29 19:44:41 +02:00
Roy Crihfield
4bdbaab471 params: clarify consensus engine config Strings (#29643)
Define these on a value receiever so that nil is shown differently.
2024-04-28 13:03:03 +02:00
Péter Szilágyi
4253030ef6 core/state: move metrics out of state objects (#29665) 2024-04-26 18:35:52 +03:00
Péter Szilágyi
8d42e115b1 core/state: revert pending storage updates if they revert to original (#29661) 2024-04-26 15:24:40 +03:00
Péter Szilágyi
ad4fb2c729 build: drop trusty from PPA builds, EOL and incompatible (#29651)
* build: drop trusty from PPA builds, EOL and incompatible

* build: add Ubuntu Noble PPA build target
2024-04-25 14:07:39 +03:00
Péter Szilágyi
634d037937 travis: revert the PPA fix hot-build, it works (#29649) 2024-04-25 12:27:36 +03:00
Péter Szilágyi
a0282fc94f travis: temporarilly enable PPA builds for testing (#29648) 2024-04-25 12:00:59 +03:00
Péter Szilágyi
1f628d842c build: build all the builders to build all the builders (#29647)
* build: build all the builders to build all the builders

* build: tweak the indexes a bit to make them consistent
2024-04-25 11:50:25 +03:00
Martin HS
243cde0f54 core/state: better randomized testing (postcheck) on journalling (#29627)
This PR fixes some flaws with the existing tests.

The randomized testing (TestSnapshotRandom) executes a series of steps which modify the state and create journal-events. Later on, we compare the forward-going-states against the backwards-unrolling-journal-states, and check that they are identical.

The "identical" check is performed using various accessors. It turned out that we failed to check some things: 
- the accesslist contents
- the transient storage contents
- the 'newContract' flag
- the dirty storage map

This change adds these new checks
2024-04-25 09:56:25 +02:00
Undefinedor
a13b92524d eth/protocols/eth,p2p/discover: remove unnecessary checks (#29590)
fix useless condition
2024-04-25 08:40:29 +02:00
yujinpark
2f6ff492ae internal/ethapi: typo (#29636) 2024-04-25 13:47:29 +08:00
Péter Szilágyi
4f4f9d88d3 core/state: storage journal entry should revert dirtyness too (#29641)
Currently our state journal tracks each storage update to a contract, having the ability to revert those changes to the previously set value.

For the very first modification however, it behaves a bit wonky. Reverting the update doesn't actually remove the dirty-ness of the slot, rather leaves it as "change this slot to it's original value". This can cause issues down the line with for example write witnesses needing to gather an unneeded proof.

This PR modifies the storageChange journal entry to not only track the previous value of a slot, but also whether there was any previous value at all set in the current execution context. In essence, the PR changes the semantic of storageChange so it does not simply track storage changes, rather it tracks dirty storage changes, an important distinction for being able to cleanly revert the journal item.
2024-04-24 17:45:24 +02:00
Aaron Chen
7362691479 trie, consensus/clique: use maps.Clone (#29616) 2024-04-24 14:27:58 +02:00
qcrao
ac21f9bfb5 trie: preallocate capacity for fields slice (#29614)
trie: Preallocate capacity for fields slice
2024-04-24 14:04:20 +02:00
Martin HS
0d4c38865e core/state: remove account reset operation v2 (#29520)
* core/state, tests: remove account reset operation

* core/state, core/vm: implement createcontract journal event

* core/state: make createcontract not emit dirtied account, unskip tests

* core/state: add createcontract to journal fuzzing

* core/state: fix journal

* core/state: address comments

* core/state: remove useless code

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2024-04-24 12:59:06 +03:00
Péter Szilágyi
938734be3c params: begin 1.14.1 release cycle 2024-04-24 11:05:10 +03:00
240 changed files with 8524 additions and 6669 deletions

1
.github/CODEOWNERS vendored
View File

@@ -10,6 +10,7 @@ core/ @karalabe @holiman @rjl493456442
eth/ @karalabe @holiman @rjl493456442
eth/catalyst/ @gballet
eth/tracers/ @s1na
core/tracing/ @s1na
graphql/ @s1na
les/ @zsfelfoldi @rjl493456442
light/ @zsfelfoldi @rjl493456442

View File

@@ -11,9 +11,9 @@ jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.21.4
- name: Run tests

View File

@@ -6,8 +6,6 @@ run:
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
skip-files:
- core/genesis_alloc.go
linters:
disable-all: true
@@ -26,6 +24,8 @@ linters:
- exportloopref
- whitespace
### linters we tried and will not be using:
###
# - structcheck # lots of false positives
# - errcheck #lot of false positives
# - contextcheck
@@ -40,6 +40,8 @@ linters-settings:
simplify: true
issues:
exclude-files:
- core/genesis_alloc.go
exclude-rules:
- path: crypto/bn256/cloudflare/optate.go
linters:

View File

@@ -15,7 +15,7 @@ jobs:
if: type = push
os: linux
arch: amd64
dist: bionic
dist: noble
go: 1.22.x
env:
- docker
@@ -32,7 +32,7 @@ jobs:
if: type = push
os: linux
arch: arm64
dist: bionic
dist: noble
go: 1.22.x
env:
- docker
@@ -49,21 +49,20 @@ jobs:
- stage: build
if: type = push
os: linux
dist: bionic
dist: noble
sudo: required
go: 1.22.x
env:
- azure-linux
git:
submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
- gcc-multilib
script:
# Build for the primary platforms that Trusty can manage
# build amd64
- go run build/ci.go install -dlgo
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
# build 386
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install gcc-multilib
- go run build/ci.go install -dlgo -arch 386
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds
@@ -98,48 +97,34 @@ jobs:
# These builders run the tests
- stage: build
if: type = push
os: linux
arch: amd64
dist: bionic
dist: noble
go: 1.22.x
script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
- stage: build
if: type = pull_request
if: type = push
os: linux
arch: arm64
dist: bionic
dist: noble
go: 1.21.x
script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- stage: build
os: linux
dist: bionic
go: 1.21.x
script:
- travis_wait 30 go run build/ci.go test $TEST_PACKAGES
- travis_wait 45 go run build/ci.go test $TEST_PACKAGES
# This builder does the Ubuntu PPA nightly uploads
- stage: build
if: type = cron || (type = push && tag ~= /^v[0-9]/)
os: linux
dist: bionic
dist: noble
go: 1.22.x
env:
- ubuntu-ppa
git:
submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
- devscripts
- debhelper
- dput
- fakeroot
- python-bzrlib
- python-paramiko
before_install:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends install devscripts debhelper dput fakeroot
script:
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
@@ -148,7 +133,7 @@ jobs:
- stage: build
if: type = cron
os: linux
dist: bionic
dist: noble
go: 1.22.x
env:
- azure-purge
@@ -161,8 +146,9 @@ jobs:
- stage: build
if: type = cron
os: linux
dist: bionic
dist: noble
go: 1.22.x
env:
- racetests
script:
- travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES
- travis_wait 60 go run build/ci.go test -race $TEST_PACKAGES

View File

@@ -2,31 +2,35 @@
# with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make.
.PHONY: geth all test lint clean devtools help
.PHONY: geth all test lint fmt clean devtools help
GOBIN = ./build/bin
GO ?= latest
GORUN = go run
#? geth: Build geth
#? geth: Build geth.
geth:
$(GORUN) build/ci.go install ./cmd/geth
@echo "Done building."
@echo "Run \"$(GOBIN)/geth\" to launch geth."
#? all: Build all packages and executables
#? all: Build all packages and executables.
all:
$(GORUN) build/ci.go install
#? test: Run the tests
#? test: Run the tests.
test: all
$(GORUN) build/ci.go test
#? lint: Run certain pre-selected linters
#? lint: Run certain pre-selected linters.
lint: ## Run linters.
$(GORUN) build/ci.go lint
#? clean: Clean go cache, built executables, and the auto generated folder
#? fmt: Ensure consistent code formatting.
fmt:
gofmt -s -w $(shell find . -name "*.go")
#? clean: Clean go cache, built executables, and the auto generated folder.
clean:
go clean -cache
rm -fr build/_workspace/pkg/ $(GOBIN)/*
@@ -34,7 +38,7 @@ clean:
# The devtools target installs tools required for 'go generate'.
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
#? devtools: Install recommended developer tools
#? devtools: Install recommended developer tools.
devtools:
env GOBIN= go install golang.org/x/tools/cmd/stringer@latest
env GOBIN= go install github.com/fjl/gencodec@latest
@@ -45,5 +49,9 @@ devtools:
#? help: Get more info on make commands.
help: Makefile
@echo " Choose a command run in go-ethereum:"
@echo ''
@echo 'Usage:'
@echo ' make [target]'
@echo ''
@echo 'Targets:'
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'

View File

@@ -326,6 +326,11 @@ func TestUpdatedKeyfileContents(t *testing.T) {
// Create a temporary keystore to test with
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-updatedkeyfilecontents-test-%d-%d", os.Getpid(), rand.Int()))
// Create the directory
os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir)
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
list := ks.Accounts()
@@ -335,9 +340,7 @@ func TestUpdatedKeyfileContents(t *testing.T) {
if !waitWatcherStart(ks) {
t.Fatal("keystore watcher didn't start in time")
}
// Create the directory and copy a key file into it.
os.MkdirAll(dir, 0700)
defer os.RemoveAll(dir)
// Copy a key file into it
file := filepath.Join(dir, "aaa")
// Place one of our testfiles in there

View File

@@ -312,11 +312,10 @@ func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
// Lock removes the private key with the given address from memory.
func (ks *KeyStore) Lock(addr common.Address) error {
ks.mu.Lock()
if unl, found := ks.unlocked[addr]; found {
ks.mu.Unlock()
unl, found := ks.unlocked[addr]
ks.mu.Unlock()
if found {
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
} else {
ks.mu.Unlock()
}
return nil
}

View File

@@ -95,6 +95,7 @@ func (hub *Hub) readPairings() error {
}
return err
}
defer pairingFile.Close()
pairingData, err := io.ReadAll(pairingFile)
if err != nil {

View File

@@ -209,7 +209,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) {
return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas)
}
var blobHashes []common.Hash
var blobHashes = make([]common.Hash, 0, len(txs))
for _, tx := range txs {
blobHashes = append(blobHashes, tx.BlobHashes()...)
}
@@ -250,7 +250,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash,
BlobGasUsed: params.BlobGasUsed,
ParentBeaconRoot: beaconRoot,
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals)
block := types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: params.Withdrawals})
if block.Hash() != params.BlockHash {
return nil, fmt.Errorf("blockhash mismatch, want %x, got %x", params.BlockHash, block.Hash())
}

View File

@@ -91,7 +91,7 @@ func TestValidatedHead(t *testing.T) {
ts.ServerEvent(EvNewOptimisticUpdate, testServer3, testOptUpdate4)
// finality should be requested from both servers
ts.Run(4, testServer1, ReqFinality{}, testServer3, ReqFinality{})
// future period annonced heads should be queued
// future period announced heads should be queued
ht.ExpValidated(t, 4, nil)
chain.SetNextSyncPeriod(2)

View File

@@ -63,11 +63,9 @@ func convertPayload[T payloadType](payload T, parentRoot *zrntcommon.Root) (*typ
panic("unsupported block type")
}
block := types.NewBlockWithHeader(&header)
block = block.WithBody(transactions, nil)
block = block.WithWithdrawals(withdrawals)
block := types.NewBlockWithHeader(&header).WithBody(types.Body{Transactions: transactions, Withdrawals: withdrawals})
if hash := block.Hash(); hash != expectedHash {
return nil, fmt.Errorf("Sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
return nil, fmt.Errorf("sanity check failed, payload hash does not match (expected %x, got %x)", expectedHash, hash)
}
return block, nil
}

View File

@@ -5,61 +5,97 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v2.1.0/
ca89c76851b0900bfcc3cbb9a26cbece1f3d7c64a3bed38723e914713290df6c fixtures_develop.tar.gz
# version:golang 1.22.2
# version:golang 1.22.4
# https://go.dev/dl/
374ea82b289ec738e968267cac59c7d5ff180f9492250254784b2044e90df5a9 go1.22.2.src.tar.gz
33e7f63077b1c5bce4f1ecadd4d990cf229667c40bfb00686990c950911b7ab7 go1.22.2.darwin-amd64.tar.gz
660298be38648723e783ba0398e90431de1cb288c637880cdb124f39bd977f0d go1.22.2.darwin-arm64.tar.gz
efc7162b0cad2f918ac566a923d4701feb29dc9c0ab625157d49b1cbcbba39da go1.22.2.freebsd-386.tar.gz
d753428296e6709527e291fd204700a587ffef2c0a472b21aebea11618245929 go1.22.2.freebsd-amd64.tar.gz
586d9eb7fe0489ab297ad80dd06414997df487c5cf536c490ffeaa8d8f1807a7 go1.22.2.linux-386.tar.gz
5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17 go1.22.2.linux-amd64.tar.gz
36e720b2d564980c162a48c7e97da2e407dfcc4239e1e58d98082dfa2486a0c1 go1.22.2.linux-arm64.tar.gz
9243dfafde06e1efe24d59df6701818e6786b4adfdf1191098050d6d023c5369 go1.22.2.linux-armv6l.tar.gz
251a8886c5113be6490bdbb955ddee98763b49c9b1bf4c8364c02d3b482dab00 go1.22.2.linux-ppc64le.tar.gz
2b39019481c28c560d65e9811a478ae10e3ef765e0f59af362031d386a71bfef go1.22.2.linux-s390x.tar.gz
651753c06df037020ef4d162c5b273452e9ba976ed17ae39e66ef7ee89d8147e go1.22.2.windows-386.zip
8e581cf330f49d3266e936521a2d8263679ef7e2fc2cbbceb85659122d883596 go1.22.2.windows-amd64.zip
ddfca5beb9a0c62254266c3090c2555d899bf3e7aa26243e7de3621108f06875 go1.22.2.windows-arm64.zip
fed720678e728a7ca30ba8d1ded1caafe27d16028fab0232b8ba8e22008fb784 go1.22.4.src.tar.gz
b9647fa9fc83a0cc5d4f092a19eaeaecf45f063a5aa7d4962fde65aeb7ae6ce1 go1.22.4.aix-ppc64.tar.gz
7788f40f3a46f201df1dc46ca640403eb535d5513fc33449164a90dbd229b761 go1.22.4.darwin-amd64.pkg
c95967f50aa4ace34af0c236cbdb49a9a3e80ee2ad09d85775cb4462a5c19ed3 go1.22.4.darwin-amd64.tar.gz
4036c88faf57a6b096916f1827edcdbf5290a47cc5f59956e88cdd9b1b71088c go1.22.4.darwin-arm64.pkg
242b78dc4c8f3d5435d28a0d2cec9b4c1aa999b601fb8aa59fb4e5a1364bf827 go1.22.4.darwin-arm64.tar.gz
f2fbb51af4719d3616efb482d6ed2b96579b474156f85a7ddc6f126764feec4b go1.22.4.dragonfly-amd64.tar.gz
7c54884bb9f274884651d41e61d1bc12738863ad1497e97ea19ad0e9aa6bf7b5 go1.22.4.freebsd-386.tar.gz
88d44500e1701dd35797619774d6dd51bf60f45a8338b0a82ddc018e4e63fb78 go1.22.4.freebsd-amd64.tar.gz
3d9efe47db142a22679aba46b1772e3900b0d87ae13bd2b3bc80dbf2ac0b2cd6 go1.22.4.freebsd-arm.tar.gz
726dc093cf020277be45debf03c3b02b43c2efb3e2a5d4fba8f52579d65327dc go1.22.4.freebsd-arm64.tar.gz
5f6b67e5e32f1d6ccb2d4dcb44934a5e2e870a877ba7443d86ec43cfc28afa71 go1.22.4.freebsd-riscv64.tar.gz
d56ecc2f85b6418a21ef83879594d0c42ab4f65391a676bb12254870e6690d63 go1.22.4.illumos-amd64.tar.gz
47a2a8d249a91eb8605c33bceec63aedda0441a43eac47b4721e3975ff916cec go1.22.4.linux-386.tar.gz
ba79d4526102575196273416239cca418a651e049c2b099f3159db85e7bade7d go1.22.4.linux-amd64.tar.gz
a8e177c354d2e4a1b61020aca3562e27ea3e8f8247eca3170e3fa1e0c2f9e771 go1.22.4.linux-arm64.tar.gz
e2b143fbacbc9cbd448e9ef41ac3981f0488ce849af1cf37e2341d09670661de go1.22.4.linux-armv6l.tar.gz
e2ff9436e4b34bf6926b06d97916e26d67a909a2effec17967245900f0816f1d go1.22.4.linux-loong64.tar.gz
73f0dcc60458c4770593b05a7bc01cc0d31fc98f948c0c2334812c7a1f2fc3f1 go1.22.4.linux-mips.tar.gz
417af97fc2630a647052375768be4c38adcc5af946352ea5b28613ea81ca5d45 go1.22.4.linux-mips64.tar.gz
7486e2d7dd8c98eb44df815ace35a7fe7f30b7c02326e3741bd934077508139b go1.22.4.linux-mips64le.tar.gz
69479c8aad301e459a8365b40cad1074a0dbba5defb9291669f94809c4c4be6e go1.22.4.linux-mipsle.tar.gz
dd238847e65bc3e2745caca475a5db6522a2fcf85cf6c38fc36a06642b19efd7 go1.22.4.linux-ppc64.tar.gz
a3e5834657ef92523f570f798fed42f1f87bc18222a16815ec76b84169649ec4 go1.22.4.linux-ppc64le.tar.gz
56a827ff7dc6245bcd7a1e9288dffaa1d8b0fd7468562264c1523daf3b4f1b4a go1.22.4.linux-riscv64.tar.gz
7590c3e278e2dc6040aae0a39da3ca1eb2e3921673a7304cc34d588c45889eec go1.22.4.linux-s390x.tar.gz
ddd2eebe34471a2502de6c5dad04ab27c9fc80cbde7a9ad5b3c66ecec4504e1d go1.22.4.netbsd-386.tar.gz
33af79f6f935f6fbacc5d23876450b3567b79348fc065beef8e64081127dd234 go1.22.4.netbsd-amd64.tar.gz
fa3550ebd5375a70b3bcd342b5a71f4bd271dcbbfaf4eabefa2144ab5d8924b6 go1.22.4.netbsd-arm.tar.gz
c9a2971dec9f6d320c6f2b049b2353c6d0a2d35e87b8a4b2d78a2f0d62545f8e go1.22.4.netbsd-arm64.tar.gz
d21af022331bfdc2b5b161d616c3a1a4573d33cf7a30416ee509a8f3641deb47 go1.22.4.openbsd-386.tar.gz
72c0094c43f7e5722ec49c2a3e9dfa7a1123ac43a5f3a63eecf3e3795d3ff0ae go1.22.4.openbsd-amd64.tar.gz
1096831ea3c5ea3ca57d14251d9eda3786889531eb40d7d6775dcaa324d4b065 go1.22.4.openbsd-arm.tar.gz
a7ab8d4e0b02bf06ed144ba42c61c0e93ee00f2b433415dfd4ad4b6e79f31650 go1.22.4.openbsd-arm64.tar.gz
9716327c8a628358798898dc5148c49dbbeb5196bf2cbf088e550721a6e4f60b go1.22.4.openbsd-ppc64.tar.gz
a8dd4503c95c32a502a616ab78870a19889c9325fe9bd31eb16dd69346e4bfa8 go1.22.4.plan9-386.tar.gz
5423a25808d76fe5aca8607a2e5ac5673abf45446b168cb5e9d8519ee9fe39a1 go1.22.4.plan9-amd64.tar.gz
6af939ad583f5c85c09c53728ab7d38c3cc2b39167562d6c18a07c5c6608b370 go1.22.4.plan9-arm.tar.gz
e8cabe69c03085725afdb32a6f9998191a3e55a747b270d835fd05000d56abba go1.22.4.solaris-amd64.tar.gz
5c6446e2ea80bc6a971d2b34446f16e6517e638b0ff8d3ea229228d1931790b0 go1.22.4.windows-386.msi
aca4e2c37278a10f1c70dd0df142f7d66b50334fcee48978d409202d308d6d25 go1.22.4.windows-386.zip
3c21105d7b584759b6e266383b777caf6e87142d304a10b539dbc66ab482bb5f go1.22.4.windows-amd64.msi
26321c4d945a0035d8a5bc4a1965b0df401ff8ceac66ce2daadabf9030419a98 go1.22.4.windows-amd64.zip
c4303f02b864304eb83dd1db0b4ebf9d2ec9d216e7ef44a7657b166a52889c7f go1.22.4.windows-arm.msi
5fcd0671a49cecf39b41021621ee1b6e7aa1370f37122b72e80d4fd4185833b6 go1.22.4.windows-arm.zip
553cc6c460f4e3eb4fad5b897c0bb22cd8bbeb20929f0e3eeb939420320292ce go1.22.4.windows-arm64.msi
8a2daa9ea28cbdafddc6171aefed384f4e5b6e714fb52116fe9ed25a132f37ed go1.22.4.windows-arm64.zip
# version:golangci 1.55.2
# version:golangci 1.59.0
# https://github.com/golangci/golangci-lint/releases/
# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/
632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz
234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz
2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz
e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz
5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz
7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz
33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz
57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz
ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz
8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz
3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz
c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz
758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz
2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz
024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz
6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz
0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz
30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz
5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz
95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz
94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz
ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz
45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip
f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip
fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip
1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip
a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-windows-armv7.zip
# https://github.com/golangci/golangci-lint/releases/download/v1.59.0/
418acf7e255ddc0783e97129c9b03d9311b77826a5311d425a01c708a86417e7 golangci-lint-1.59.0-darwin-amd64.tar.gz
5f6a1d95a6dd69f6e328eb56dd311a38e04cfab79a1305fbf4957f4e203f47b6 golangci-lint-1.59.0-darwin-arm64.tar.gz
8899bf589185d49f747f3e5db9f0bde8a47245a100c64a3dd4d65e8e92cfc4f2 golangci-lint-1.59.0-freebsd-386.tar.gz
658212f138d9df2ac89427e22115af34bf387c0871d70f2a25101718946a014f golangci-lint-1.59.0-freebsd-amd64.tar.gz
4c6395ea40f314d3b6fa17d8997baab93464d5d1deeaab513155e625473bd03a golangci-lint-1.59.0-freebsd-armv6.tar.gz
ff37da4fbaacdb6bbae70fdbdbb1ba932a859956f788c82822fa06bef5b7c6b3 golangci-lint-1.59.0-freebsd-armv7.tar.gz
439739469ed2bda182b1ec276d40c40e02f195537f78e3672996741ad223d6b6 golangci-lint-1.59.0-illumos-amd64.tar.gz
940801d46790e40d0a097d8fee34e2606f0ef148cd039654029b0b8750a15ed6 golangci-lint-1.59.0-linux-386.tar.gz
3b14a439f33c4fff83dbe0349950d984042b9a1feb6c62f82787b598fc3ab5f4 golangci-lint-1.59.0-linux-amd64.tar.gz
c57e6c0b0fa03089a2611dceddd5bc5d206716cccdff8b149da8baac598719a1 golangci-lint-1.59.0-linux-arm64.tar.gz
93149e2d3b25ac754df9a23172403d8aa6d021a7e0d9c090a12f51897f68c9a0 golangci-lint-1.59.0-linux-armv6.tar.gz
d10ac38239d9efee3ee87b55c96cdf3fa09e1a525babe3ffdaaf65ccc48cf3dc golangci-lint-1.59.0-linux-armv7.tar.gz
047338114b4f0d5f08f0fb9a397b03cc171916ed0960be7dfb355c2320cd5e9c golangci-lint-1.59.0-linux-loong64.tar.gz
5632df0f7f8fc03a80a266130faef0b5902d280cf60621f1b2bdc1aef6d97ee9 golangci-lint-1.59.0-linux-mips64.tar.gz
71dd638c82fa4439171e7126d2c7a32b5d103bfdef282cea40c83632cb3d1f4b golangci-lint-1.59.0-linux-mips64le.tar.gz
6cf9ea0d34e91669948483f9ae7f07da319a879344373a1981099fbd890cde00 golangci-lint-1.59.0-linux-ppc64le.tar.gz
af0205fa6fbab197cee613c359947711231739095d21b5c837086233b36ad971 golangci-lint-1.59.0-linux-riscv64.tar.gz
a9d2fb93f3c688ebccef94f5dc96c0b07c4d20bf6556cddebd8442159b0c80f6 golangci-lint-1.59.0-linux-s390x.tar.gz
68ab4c57a847b8ace9679887f2f8b2b6760e57ee29dcde8c3f40dd8bb2654fa2 golangci-lint-1.59.0-netbsd-386.tar.gz
d277b8b435c19406d00de4d509eadf5a024a5782878332e9a1b7c02bb76e87a7 golangci-lint-1.59.0-netbsd-amd64.tar.gz
83211656be8dcfa1545af4f92894409f412d1f37566798cb9460a526593ad62c golangci-lint-1.59.0-netbsd-arm64.tar.gz
6c6866d28bf79fa9817a0f7d2b050890ed109cae80bdb4dfa39536a7226da237 golangci-lint-1.59.0-netbsd-armv6.tar.gz
11587566363bd03ca586b7df9776ccaed569fcd1f3489930ac02f9375b307503 golangci-lint-1.59.0-netbsd-armv7.tar.gz
466181a8967bafa495e41494f93a0bec829c2cf715de874583b0460b3b8ae2b8 golangci-lint-1.59.0-windows-386.zip
3317d8a87a99a49a0a1321d295c010790e6dbf43ee96b318f4b8bb23eae7a565 golangci-lint-1.59.0-windows-amd64.zip
b3af955c7fceac8220a36fc799e1b3f19d3b247d32f422caac5f9845df8f7316 golangci-lint-1.59.0-windows-arm64.zip
6f083c7d0c764e5a0e5bde46ee3e91ae357d80c194190fe1d9754392e9064c7e golangci-lint-1.59.0-windows-armv6.zip
3709b4dd425deadab27748778d08e03c0f804d7748f7dd5b6bb488d98aa031c7 golangci-lint-1.59.0-windows-armv7.zip
# This is the builder on PPA that will build Go itself (inception-y), don't modify!
#
# This version is fine to be old and full of security holes, we just use it
# to build the latest Go. Don't change it. If it ever becomes insufficient,
# we need to switch over to a recursive builder to jump across supported
# versions.
# to build the latest Go. Don't change it.
#
# version:ppa-builder 1.19.6
# version:ppa-builder-1 1.19.6
# https://go.dev/dl/
d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz
# version:ppa-builder-2 1.21.9
# https://go.dev/dl/
58f0c5ced45a0012bce2ff7a9df03e128abcc8818ebabe5027bb92bafe20e421 go1.21.9.src.tar.gz

View File

@@ -117,23 +117,15 @@ var (
debEthereum,
}
// Distros for which packages are created.
// Note: vivid is unsupported because there is no golang-1.6 package for it.
// Note: the following Ubuntu releases have been officially deprecated on Launchpad:
// wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish,
// kinetic, lunar
debDistroGoBoots = map[string]string{
"trusty": "golang-1.11", // 14.04, EOL: 04/2024
"xenial": "golang-go", // 16.04, EOL: 04/2026
"bionic": "golang-go", // 18.04, EOL: 04/2028
"focal": "golang-go", // 20.04, EOL: 04/2030
"jammy": "golang-go", // 22.04, EOL: 04/2032
"mantic": "golang-go", // 23.10, EOL: 07/2024
}
// Distros for which packages are created
debDistros = []string{
"xenial", // 16.04, EOL: 04/2026
"bionic", // 18.04, EOL: 04/2028
"focal", // 20.04, EOL: 04/2030
"jammy", // 22.04, EOL: 04/2032
"noble", // 24.04, EOL: 04/2034
debGoBootPaths = map[string]string{
"golang-1.11": "/usr/lib/go-1.11",
"golang-go": "/usr/lib/go",
"mantic", // 23.10, EOL: 07/2024
}
// This is where the tests should be unpacked.
@@ -694,8 +686,8 @@ func doDebianSource(cmdline []string) {
}
// Download and verify the Go source packages.
var (
gobootbundle = downloadGoBootstrapSources(*cachedir)
gobundle = downloadGoSources(*cachedir)
gobootbundles = downloadGoBootstrapSources(*cachedir)
gobundle = downloadGoSources(*cachedir)
)
// Download all the dependencies needed to build the sources and run the ci script
srcdepfetch := tc.Go("mod", "download")
@@ -708,17 +700,19 @@ func doDebianSource(cmdline []string) {
// Create Debian packages and upload them.
for _, pkg := range debPackages {
for distro, goboot := range debDistroGoBoots {
for _, distro := range debDistros {
// Prepare the debian package with the go-ethereum sources.
meta := newDebMetadata(distro, goboot, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
pkgdir := stageDebianSource(*workdir, meta)
// Add bootstrapper Go source code
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
}
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, ".goboot")); err != nil {
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err)
for i, gobootbundle := range gobootbundles {
if err := build.ExtractArchive(gobootbundle, pkgdir); err != nil {
log.Fatalf("Failed to extract bootstrapper Go sources: %v", err)
}
if err := os.Rename(filepath.Join(pkgdir, "go"), filepath.Join(pkgdir, fmt.Sprintf(".goboot-%d", i+1))); err != nil {
log.Fatalf("Failed to rename bootstrapper Go source folder: %v", err)
}
}
// Add builder Go source code
if err := build.ExtractArchive(gobundle, pkgdir); err != nil {
@@ -754,21 +748,26 @@ func doDebianSource(cmdline []string) {
}
}
// downloadGoBootstrapSources downloads the Go source tarball that will be used
// downloadGoBootstrapSources downloads the Go source tarball(s) that will be used
// to bootstrap the builder Go.
func downloadGoBootstrapSources(cachedir string) string {
func downloadGoBootstrapSources(cachedir string) []string {
csdb := build.MustLoadChecksums("build/checksums.txt")
gobootVersion, err := build.Version(csdb, "ppa-builder")
if err != nil {
log.Fatal(err)
var bundles []string
for _, booter := range []string{"ppa-builder-1", "ppa-builder-2"} {
gobootVersion, err := build.Version(csdb, booter)
if err != nil {
log.Fatal(err)
}
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil {
log.Fatal(err)
}
bundles = append(bundles, dst)
}
file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion)
url := "https://dl.google.com/go/" + file
dst := filepath.Join(cachedir, file)
if err := csdb.DownloadFile(url, dst); err != nil {
log.Fatal(err)
}
return dst
return bundles
}
// downloadGoSources downloads the Go source tarball.
@@ -846,10 +845,7 @@ type debPackage struct {
}
type debMetadata struct {
Env build.Environment
GoBootPackage string
GoBootPath string
Env build.Environment
PackageName string
// go-ethereum version being built. Note that this
@@ -877,21 +873,19 @@ func (d debExecutable) Package() string {
return d.BinaryName
}
func newDebMetadata(distro, goboot, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
if author == "" {
// No signing key, use default author.
author = "Ethereum Builds <fjl@ethereum.org>"
}
return debMetadata{
GoBootPackage: goboot,
GoBootPath: debGoBootPaths[goboot],
PackageName: name,
Env: env,
Author: author,
Distro: distro,
Version: version,
Time: t.Format(time.RFC1123Z),
Executables: exes,
PackageName: name,
Env: env,
Author: author,
Distro: distro,
Version: version,
Time: t.Format(time.RFC1123Z),
Executables: exes,
}
}

View File

@@ -2,7 +2,7 @@ Source: {{.Name}}
Section: science
Priority: extra
Maintainer: {{.Author}}
Build-Depends: debhelper (>= 8.0.0), {{.GoBootPackage}}
Build-Depends: debhelper (>= 8.0.0), golang-go
Standards-Version: 3.9.5
Homepage: https://ethereum.org
Vcs-Git: https://github.com/ethereum/go-ethereum.git

View File

@@ -7,7 +7,7 @@
# Launchpad rejects Go's access to $HOME, use custom folders
export GOCACHE=/tmp/go-build
export GOPATH=/tmp/gopath
export GOROOT_BOOTSTRAP={{.GoBootPath}}
export GOROOT_BOOTSTRAP=/usr/lib/go
override_dh_auto_clean:
# Don't try to be smart Launchpad, we know our build rules better than you
@@ -19,8 +19,9 @@ override_dh_auto_build:
#
# We're also shipping the bootstrapper as of Go 1.20 as it had minimum version
# requirements opposed to older versions of Go.
(mv .goboot ../ && cd ../.goboot/src && ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot ./make.bash)
(mv .goboot-1 ../ && cd ../.goboot-1/src && ./make.bash)
(mv .goboot-2 ../ && cd ../.goboot-2/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-1 ./make.bash)
(mv .go ../ && cd ../.go/src && GOROOT_BOOTSTRAP=`pwd`/../../.goboot-2 ./make.bash)
# We can't download external go modules within Launchpad, so we're shipping the
# entire dependency source cache with go-ethereum.

View File

@@ -552,7 +552,7 @@ func listWallets(c *cli.Context) error {
// accountImport imports a raw hexadecimal private key via CLI.
func accountImport(c *cli.Context) error {
if c.Args().Len() != 1 {
return errors.New("<keyfile> must be given as first argument.")
return errors.New("<keyfile> must be given as first argument")
}
internalApi, ui, err := initInternalApi(c)
if err != nil {

View File

@@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
@@ -28,9 +29,11 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/urfave/cli/v2"
)
@@ -45,6 +48,7 @@ var (
discv4ResolveJSONCommand,
discv4CrawlCommand,
discv4TestCommand,
discv4ListenCommand,
},
}
discv4PingCommand = &cli.Command{
@@ -75,6 +79,14 @@ var (
Flags: discoveryNodeFlags,
ArgsUsage: "<nodes.json file>",
}
discv4ListenCommand = &cli.Command{
Name: "listen",
Usage: "Runs a discovery node",
Action: discv4Listen,
Flags: flags.Merge(discoveryNodeFlags, []cli.Flag{
httpAddrFlag,
}),
}
discv4CrawlCommand = &cli.Command{
Name: "crawl",
Usage: "Updates a nodes.json file with random nodes found in the DHT",
@@ -131,6 +143,10 @@ var (
Usage: "Enode of the remote node under test",
EnvVars: []string{"REMOTE_ENODE"},
}
httpAddrFlag = &cli.StringFlag{
Name: "rpc",
Usage: "HTTP server listening address",
}
)
var discoveryNodeFlags = []cli.Flag{
@@ -154,6 +170,27 @@ func discv4Ping(ctx *cli.Context) error {
return nil
}
func discv4Listen(ctx *cli.Context) error {
disc, _ := startV4(ctx)
defer disc.Close()
fmt.Println(disc.Self())
httpAddr := ctx.String(httpAddrFlag.Name)
if httpAddr == "" {
// Non-HTTP mode.
select {}
}
api := &discv4API{disc}
log.Info("Starting RPC API server", "addr", httpAddr)
srv := rpc.NewServer()
srv.RegisterName("discv4", api)
http.DefaultServeMux.Handle("/", srv)
httpsrv := http.Server{Addr: httpAddr, Handler: http.DefaultServeMux}
return httpsrv.ListenAndServe()
}
func discv4RequestRecord(ctx *cli.Context) error {
n := getNodeArg(ctx)
disc, _ := startV4(ctx)
@@ -362,3 +399,23 @@ func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
}
return nodes, nil
}
type discv4API struct {
host *discover.UDPv4
}
func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) {
it := api.host.RandomNodes()
for len(ns) < n && it.Next() {
ns = append(ns, it.Node())
}
return ns
}
func (api *discv4API) Buckets() [][]discover.BucketNode {
return api.host.TableBuckets()
}
func (api *discv4API) Self() *enode.Node {
return api.host.Self()
}

View File

@@ -53,7 +53,8 @@ func (s *Suite) dial() (*Conn, error) {
// dialAs attempts to dial a given node and perform a handshake using the given
// private key.
func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) {
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP()))
tcpEndpoint, _ := s.Dest.TCPEndpoint()
fd, err := net.Dial("tcp", tcpEndpoint.String())
if err != nil {
return nil, err
}

View File

@@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/internal/utesting"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"golang.org/x/crypto/sha3"
)
func (c *Conn) snapRequest(code uint64, msg any) (any, error) {
@@ -905,7 +904,7 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error {
// that the serving node is missing
var (
bytecodes = res.Codes
hasher = sha3.NewLegacyKeccak256().(crypto.KeccakState)
hasher = crypto.NewKeccakState()
hash = make([]byte, 32)
codes = make([][]byte, len(req.Hashes))
)
@@ -964,7 +963,7 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error {
// Cross reference the requested trienodes with the response to find gaps
// that the serving node is missing
hasher := sha3.NewLegacyKeccak256().(crypto.KeccakState)
hasher := crypto.NewKeccakState()
hash := make([]byte, 32)
trienodes := res.Nodes
if got, want := len(trienodes), len(tc.expHashes); got != want {

View File

@@ -53,16 +53,18 @@ func newTestEnv(remote string, listen1, listen2 string) *testenv {
if err != nil {
panic(err)
}
if node.IP() == nil || node.UDP() == 0 {
if !node.IPAddr().IsValid() || node.UDP() == 0 {
var ip net.IP
var tcpPort, udpPort int
if ip = node.IP(); ip == nil {
if node.IPAddr().IsValid() {
ip = node.IPAddr().AsSlice()
} else {
ip = net.ParseIP("127.0.0.1")
}
if tcpPort = node.TCP(); tcpPort == 0 {
tcpPort = 30303
}
if udpPort = node.TCP(); udpPort == 0 {
if udpPort = node.UDP(); udpPort == 0 {
udpPort = 30303
}
node = enode.NewV4(node.Pubkey(), ip, tcpPort, udpPort)
@@ -110,7 +112,7 @@ func (te *testenv) localEndpoint(c net.PacketConn) v4wire.Endpoint {
}
func (te *testenv) remoteEndpoint() v4wire.Endpoint {
return v4wire.NewEndpoint(te.remoteAddr, 0)
return v4wire.NewEndpoint(te.remoteAddr.AddrPort(), 0)
}
func contains(ns []v4wire.Node, key v4wire.Pubkey) bool {

View File

@@ -19,7 +19,7 @@ package main
import (
"errors"
"fmt"
"net"
"net/netip"
"sort"
"strconv"
"strings"
@@ -205,11 +205,11 @@ func trueFilter(args []string) (nodeFilter, error) {
}
func ipFilter(args []string) (nodeFilter, error) {
_, cidr, err := net.ParseCIDR(args[0])
prefix, err := netip.ParsePrefix(args[0])
if err != nil {
return nil, err
}
f := func(n nodeJSON) bool { return cidr.Contains(n.N.IP()) }
f := func(n nodeJSON) bool { return prefix.Contains(n.N.IPAddr()) }
return f, nil
}

View File

@@ -77,7 +77,11 @@ var (
func rlpxPing(ctx *cli.Context) error {
n := getNodeArg(ctx)
fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", n.IP(), n.TCP()))
tcpEndpoint, ok := n.TCPEndpoint()
if !ok {
return fmt.Errorf("node has no TCP endpoint")
}
fd, err := net.Dial("tcp", tcpEndpoint.String())
if err != nil {
return err
}

View File

@@ -160,7 +160,7 @@ func (i *bbInput) ToBlock() *types.Block {
if i.Header.Difficulty != nil {
header.Difficulty = i.Header.Difficulty
}
return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals)
return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: i.Txs, Uncles: i.Ommers, Withdrawals: i.Withdrawals})
}
// SealBlock seals the given block using the configured engine.

View File

@@ -217,7 +217,7 @@ func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
return nil
}
if env.ParentBaseFee == nil || env.Number == 0 {
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'parentBaseFee' in env section"))
}
env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{
Number: new(big.Int).SetUint64(env.Number - 1),
@@ -296,7 +296,7 @@ func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) {
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 0)
var storage map[common.Hash]common.Hash
if dumpAccount.Storage != nil {
storage = make(map[common.Hash]common.Hash)
storage = make(map[common.Hash]common.Hash, len(dumpAccount.Storage))
for k, v := range dumpAccount.Storage {
storage[k] = common.HexToHash(v)
}

View File

@@ -234,7 +234,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition
base: "./testdata/24",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "",
"alloc.json", "txs.json", "env.json", "Paris", "",
},
output: t8nOutput{alloc: true, result: true},
expOut: "exp.json",
@@ -242,7 +242,7 @@ func TestT8n(t *testing.T) {
{ // Test post-merge transition where input is missing random
base: "./testdata/24",
input: t8nInput{
"alloc.json", "txs.json", "env-missingrandom.json", "Merge", "",
"alloc.json", "txs.json", "env-missingrandom.json", "Paris", "",
},
output: t8nOutput{alloc: false, result: false},
expExitCode: 3,
@@ -250,7 +250,7 @@ func TestT8n(t *testing.T) {
{ // Test base fee calculation
base: "./testdata/25",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "",
"alloc.json", "txs.json", "env.json", "Paris", "",
},
output: t8nOutput{alloc: true, result: true},
expOut: "exp.json",
@@ -378,7 +378,7 @@ func TestT8nTracing(t *testing.T) {
{
base: "./testdata/32",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Merge", "",
"alloc.json", "txs.json", "env.json", "Paris", "",
},
extraArgs: []string{"--trace", "--trace.callframes"},
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},

View File

@@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) {
"--http", "--http.port", httpPort,
"--ws", "--ws.port", wsPort)
t.Run("ipc", func(t *testing.T) {
waitForEndpoint(t, ipc, 3*time.Second)
waitForEndpoint(t, ipc, 4*time.Second)
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
})
t.Run("http", func(t *testing.T) {
endpoint := "http://127.0.0.1:" + httpPort
waitForEndpoint(t, endpoint, 3*time.Second)
waitForEndpoint(t, endpoint, 4*time.Second)
testAttachWelcome(t, geth, endpoint, httpAPIs)
})
t.Run("ws", func(t *testing.T) {
endpoint := "ws://127.0.0.1:" + wsPort
waitForEndpoint(t, endpoint, 3*time.Second)
waitForEndpoint(t, endpoint, 4*time.Second)
testAttachWelcome(t, geth, endpoint, httpAPIs)
})
geth.Kill()

View File

@@ -246,11 +246,17 @@ func removeDB(ctx *cli.Context) error {
ancientDir = config.Node.ResolvePath(ancientDir)
}
// Delete state data
statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)}
statePaths := []string{
rootDir,
filepath.Join(ancientDir, rawdb.StateFreezerName),
}
confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name)
// Delete ancient chain
chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)}
chainPaths := []string{filepath.Join(
ancientDir,
rawdb.ChainFreezerName,
)}
confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name)
return nil
}

View File

@@ -73,6 +73,7 @@ func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) {
if err != nil {
t.Fatal(err)
}
defer readFile.Close()
wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB))
for i, want := range wantLines {
@@ -109,6 +110,7 @@ func TestJsonLogging(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer readFile.Close()
wantLines := split(readFile)
haveLines := split(bytes.NewBuffer(haveB))
for i, wantLine := range wantLines {

View File

@@ -91,7 +91,7 @@ data, and verifies that all snapshot storage data has a corresponding account.
},
{
Name: "inspect-account",
Usage: "Check all snapshot layers for the a specific account",
Usage: "Check all snapshot layers for the specific account",
ArgsUsage: "<address | hash>",
Action: checkAccount,
Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags),

View File

@@ -166,5 +166,37 @@
"severity": "Low",
"CVE": "CVE-2022-29177",
"check": "(Geth\\/v1\\.10\\.(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2023-01",
"summary": "A vulnerable node can be made to consume unbounded amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "The p2p handler spawned a new goroutine to respond to ping requests. By flooding a node with ping requests, an unbounded number of goroutines can be created, leading to resource exhaustion and potentially crash due to OOM.",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-ppjg-v974-84cm",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.12.1",
"published": "2023-09-06",
"severity": "High",
"CVE": "CVE-2023-40591",
"check": "(Geth\\/v1\\.(10|11)\\..*)|(Geth\\/v1\\.12\\.0-.*)$"
},
{
"name": "DoS via malicious p2p message",
"uid": "GETH-2024-01",
"summary": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node.",
"description": "A vulnerable node can be made to consume very large amounts of memory when handling specially crafted p2p messages sent from an attacker node. Full details will be available at the Github security [advisory](https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652)",
"links": [
"https://github.com/ethereum/go-ethereum/security/advisories/GHSA-4xc9-8hmq-j652",
"https://geth.ethereum.org/docs/vulnerabilities/vulnerabilities"
],
"introduced": "v1.10.0",
"fixed": "v1.13.15",
"published": "2024-05-06",
"severity": "High",
"CVE": "CVE-2024-32972",
"check": "(Geth\\/v1\\.(10|11|12)\\..*)|(Geth\\/v1\\.13\\.\\d-.*)|(Geth\\/v1\\.13\\.1(0|1|2|3|4)-.*)$"
}
]

View File

@@ -28,7 +28,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
"github.com/gballet/go-verkle"
"github.com/ethereum/go-verkle"
"github.com/urfave/cli/v2"
)

View File

@@ -1872,13 +1872,15 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
Fatalf("Could not read genesis from database: %v", err)
}
if !genesis.Config.TerminalTotalDifficultyPassed {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true in developer mode")
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true")
}
if genesis.Config.TerminalTotalDifficulty == nil {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified.")
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified")
} else if genesis.Config.TerminalTotalDifficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be 0")
}
if genesis.Difficulty.Cmp(genesis.Config.TerminalTotalDifficulty) != 1 {
Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty")
if genesis.Difficulty.Cmp(big.NewInt(0)) != 0 {
Fatalf("Bad developer-mode genesis configuration: difficulty must be 0")
}
}
chaindb.Close()

View File

@@ -162,8 +162,7 @@ func TestHistoryImportAndExport(t *testing.T) {
}
// Now import Era.
freezer := t.TempDir()
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), freezer, "", false)
db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
panic(err)
}

View File

@@ -180,9 +180,9 @@ func BenchmarkByteAtOld(b *testing.B) {
func TestReadBits(t *testing.T) {
check := func(input string) {
want, _ := hex.DecodeString(input)
int, _ := new(big.Int).SetString(input, 16)
n, _ := new(big.Int).SetString(input, 16)
buf := make([]byte, len(want))
ReadBits(int, buf)
ReadBits(n, buf)
if !bytes.Equal(buf, want) {
t.Errorf("have: %x\nwant: %x", buf, want)
}

View File

@@ -54,11 +54,11 @@ func (i *HexOrDecimal64) UnmarshalJSON(input []byte) error {
// UnmarshalText implements encoding.TextUnmarshaler.
func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
int, ok := ParseUint64(string(input))
n, ok := ParseUint64(string(input))
if !ok {
return fmt.Errorf("invalid hex or decimal integer %q", input)
}
*i = HexOrDecimal64(int)
*i = HexOrDecimal64(n)
return nil
}

View File

@@ -388,7 +388,7 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(true)
// Assemble and return the final block.
return types.NewBlockWithWithdrawals(header, body.Transactions, body.Uncles, receipts, body.Withdrawals, trie.NewStackTrie(nil)), nil
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil
}
// Seal generates a new sealing request for the given input block and pushes

View File

@@ -597,7 +597,7 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Assemble and return the final block for sealing.
return types.NewBlock(header, body.Transactions, nil, receipts, trie.NewStackTrie(nil)), nil
return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil
}
// Authorize injects a private key into the consensus engine to mint new blocks

View File

@@ -19,6 +19,7 @@ package clique
import (
"bytes"
"encoding/json"
"maps"
"slices"
"time"
@@ -108,28 +109,16 @@ func (s *Snapshot) store(db ethdb.Database) error {
// copy creates a deep copy of the snapshot, though not the individual votes.
func (s *Snapshot) copy() *Snapshot {
cpy := &Snapshot{
return &Snapshot{
config: s.config,
sigcache: s.sigcache,
Number: s.Number,
Hash: s.Hash,
Signers: make(map[common.Address]struct{}),
Recents: make(map[uint64]common.Address),
Votes: make([]*Vote, len(s.Votes)),
Tally: make(map[common.Address]Tally),
Signers: maps.Clone(s.Signers),
Recents: maps.Clone(s.Recents),
Votes: slices.Clone(s.Votes),
Tally: maps.Clone(s.Tally),
}
for signer := range s.Signers {
cpy.Signers[signer] = struct{}{}
}
for block, signer := range s.Recents {
cpy.Recents[block] = signer
}
for address, tally := range s.Tally {
cpy.Tally[address] = tally
}
copy(cpy.Votes, s.Votes)
return cpy
}
// validVote returns whether it makes sense to cast the specified vote in the

View File

@@ -520,7 +520,7 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return
return types.NewBlock(header, body.Transactions, body.Uncles, receipts, trie.NewStackTrie(nil)), nil
return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil
}
// SealHash returns the hash of a block prior to it being sealed.

View File

@@ -154,12 +154,10 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
preHeaders := make([]*types.Header, len(preBlocks))
for i, block := range preBlocks {
preHeaders[i] = block.Header()
t.Logf("Pre-merge header: %d", block.NumberU64())
}
postHeaders := make([]*types.Header, len(postBlocks))
for i, block := range postBlocks {
postHeaders[i] = block.Header()
t.Logf("Post-merge header: %d", block.NumberU64())
}
// Run the header checker for blocks one-by-one, checking for both valid and invalid nonces
chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vm.Config{}, nil, nil)

View File

@@ -68,7 +68,6 @@ var (
accountCommitTimer = metrics.NewRegisteredResettingTimer("chain/account/commits", nil)
storageReadTimer = metrics.NewRegisteredResettingTimer("chain/storage/reads", nil)
storageHashTimer = metrics.NewRegisteredResettingTimer("chain/storage/hashes", nil)
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
@@ -101,7 +100,6 @@ const (
blockCacheLimit = 256
receiptsCacheLimit = 32
txLookupCacheLimit = 1024
TriesInMemory = 128
// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
//
@@ -1129,7 +1127,7 @@ func (bc *BlockChain) Stop() {
if !bc.cacheConfig.TrieDirtyDisabled {
triedb := bc.triedb
for _, offset := range []uint64{0, 1, TriesInMemory - 1} {
for _, offset := range []uint64{0, 1, state.TriesInMemory - 1} {
if number := bc.CurrentBlock().Number.Uint64(); number > offset {
recent := bc.GetBlockByNumber(number - offset)
@@ -1310,7 +1308,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
// Delete block data from the main database.
var (
batch = bc.db.NewBatch()
canonHashes = make(map[common.Hash]struct{})
canonHashes = make(map[common.Hash]struct{}, len(blockChain))
)
for _, block := range blockChain {
canonHashes[block.Hash()] = struct{}{}
@@ -1453,7 +1451,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
// writeBlockWithState writes block, metadata and corresponding state data to the
// database.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) error {
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, statedb *state.StateDB) error {
// Calculate the total difficulty of the block
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
if ptd == nil {
@@ -1470,12 +1468,12 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
rawdb.WriteBlock(blockBatch, block)
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
rawdb.WritePreimages(blockBatch, state.Preimages())
rawdb.WritePreimages(blockBatch, statedb.Preimages())
if err := blockBatch.Write(); err != nil {
log.Crit("Failed to write block into disk", "err", err)
}
// Commit all cached state changes into underlying memory database.
root, err := state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
if err != nil {
return err
}
@@ -1494,7 +1492,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
// Flush limits are not considered for the first TriesInMemory blocks.
current := block.NumberU64()
if current <= TriesInMemory {
if current <= state.TriesInMemory {
return nil
}
// If we exceeded our memory allowance, flush matured singleton nodes to disk
@@ -1506,7 +1504,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
bc.triedb.Cap(limit - ethdb.IdealBatchSize)
}
// Find the next state trie we need to commit
chosen := current - TriesInMemory
chosen := current - state.TriesInMemory
flushInterval := time.Duration(bc.flushInterval.Load())
// If we exceeded time allowance, flush an entire trie to disk
if bc.gcproc > flushInterval {
@@ -1518,8 +1516,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
} else {
// If we're exceeding limits but haven't reached a large enough memory gap,
// warn the user that the system is becoming unstable.
if chosen < bc.lastWrite+TriesInMemory && bc.gcproc >= 2*flushInterval {
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/TriesInMemory)
if chosen < bc.lastWrite+state.TriesInMemory && bc.gcproc >= 2*flushInterval {
log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", flushInterval, "optimum", float64(chosen-bc.lastWrite)/state.TriesInMemory)
}
// Flush an entire trie and restart the counters
bc.triedb.Commit(header.Root, true)
@@ -1807,8 +1805,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
}
statedb.SetLogger(bc.logger)
// Enable prefetching to pull in trie node paths while processing transactions
statedb.StartPrefetcher("chain")
// If we are past Byzantium, enable prefetching to pull in trie node paths
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
statedb.StartPrefetcher("chain")
}
activeState = statedb
// If we have a followup block, run that against the current state to pre-cache
@@ -1937,8 +1939,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete(in validation)
storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation)
accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation)
storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation)
triehash := statedb.AccountHashes + statedb.StorageHashes // The time spent on tries hashing
triehash := statedb.AccountHashes // The time spent on tries hashing
trieUpdate := statedb.AccountUpdates + statedb.StorageUpdates // The time spent on tries update
trieRead := statedb.SnapshotAccountReads + statedb.AccountReads // The time spent on account read
trieRead += statedb.SnapshotStorageReads + statedb.StorageReads // The time spent on storage read
@@ -1965,7 +1966,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them
triedbCommitTimer.Update(statedb.TrieDBCommits) // Trie database commits are complete, we can mark them
blockWriteTimer.Update(time.Since(wstart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockWriteTimer.Update(time.Since(wstart) - max(statedb.AccountCommits, statedb.StorageCommits) /* concurrent */ - statedb.SnapshotCommits - statedb.TrieDBCommits)
blockInsertTimer.UpdateSince(start)
return &blockProcessingResult{usedGas: usedGas, procTime: proctime, status: status}, nil

View File

@@ -785,7 +785,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
// Freezer style fast import the chain.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -875,12 +875,12 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee),
}
)
height := uint64(1024)
height := uint64(64)
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// makeDb creates a db instance for testing.
makeDb := func() ethdb.Database {
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1712,7 +1712,7 @@ func TestTrieForkGC(t *testing.T) {
Config: params.TestChainConfig,
BaseFee: big.NewInt(params.InitialBaseFee),
}
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
// Generate a bunch of fork blocks, each side forking from the canonical chain
forks := make([]*types.Block, len(blocks))
@@ -1740,7 +1740,7 @@ func TestTrieForkGC(t *testing.T) {
}
}
// Dereference all the recent tries and ensure no past trie is left in
for i := 0; i < TriesInMemory; i++ {
for i := 0; i < state.TriesInMemory; i++ {
chain.TrieDB().Dereference(blocks[len(blocks)-1-i].Root())
chain.TrieDB().Dereference(forks[len(blocks)-1-i].Root())
}
@@ -1764,11 +1764,11 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee),
}
genDb, shared, _ := GenerateChainWithGenesis(genesis, engine, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) })
original, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) })
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
original, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) })
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
// Import the shared chain and the original canonical one
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -1804,7 +1804,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
}
// In path-based trie database implementation, it will keep 128 diff + 1 disk
// layers, totally 129 latest states available. In hash-based it's 128.
states := TriesInMemory
states := state.TriesInMemory
if scheme == rawdb.PathScheme {
states = states + 1
}
@@ -1833,7 +1833,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
funds = big.NewInt(1000000000)
gspec = &Genesis{Config: params.TestChainConfig, Alloc: types.GenesisAlloc{address: {Balance: funds}}}
)
height := uint64(1024)
height := uint64(64)
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// Import the chain as a ancient-first node and ensure all pointers are updated
@@ -1908,7 +1908,7 @@ func testInsertReceiptChainRollback(t *testing.T, scheme string) {
}
// Set up a BlockChain that uses the ancient store.
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1972,13 +1972,13 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
}
// We must use a pretty long chain to ensure that the fork doesn't overtake us
// until after at least 128 blocks post tip
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 6*TriesInMemory, func(i int, b *BlockGen) {
genDb, blocks, _ := GenerateChainWithGenesis(genesis, engine, 6*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{1})
b.OffsetTime(-9)
})
// Import the canonical chain
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
@@ -1992,7 +1992,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
}
// Generate fork chain, starting from an early block
parent := blocks[10]
fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 8*TriesInMemory, func(i int, b *BlockGen) {
fork, _ := GenerateChain(genesis.Config, parent, engine, genDb, 8*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2})
})
@@ -2055,7 +2055,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// Set the terminal total difficulty in the config
gspec.Config.TerminalTotalDifficulty = big.NewInt(0)
}
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) {
genDb, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2*state.TriesInMemory, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("deadbeef"), big.NewInt(100), 21000, big.NewInt(int64(i+1)*params.GWei), nil), signer, key)
if err != nil {
t.Fatalf("failed to create tx: %v", err)
@@ -2070,9 +2070,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
t.Fatalf("block %d: failed to insert into chain: %v", n, err)
}
lastPrunedIndex := len(blocks) - TriesInMemory - 1
lastPrunedIndex := len(blocks) - state.TriesInMemory - 1
lastPrunedBlock := blocks[lastPrunedIndex]
firstNonPrunedBlock := blocks[len(blocks)-TriesInMemory]
firstNonPrunedBlock := blocks[len(blocks)-state.TriesInMemory]
// Verify pruning of lastPrunedBlock
if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) {
@@ -2099,7 +2099,7 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// Generate fork chain, make it longer than canon
parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock
parent := blocks[parentIndex]
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*TriesInMemory, func(i int, b *BlockGen) {
fork, _ := GenerateChain(gspec.Config, parent, engine, genDb, 2*state.TriesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2})
if int(b.header.Number.Uint64()) >= mergeBlock {
b.SetPoS()
@@ -2190,7 +2190,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
b.OffsetTime(-9) // A higher difficulty
})
// Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2361,7 +2361,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
}
})
// Import the shared chain and the original canonical one
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2742,7 +2742,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
BaseFee: big.NewInt(params.InitialBaseFee),
}
// Generate and import the canonical chain
_, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*TriesInMemory, nil)
_, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*state.TriesInMemory, nil)
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil, nil)
if err != nil {
@@ -2755,9 +2755,9 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
}
// In path-based trie database implementation, it will keep 128 diff + 1 disk
// layers, totally 129 latest states available. In hash-based it's 128.
states := TriesInMemory
states := state.TriesInMemory
if scheme == rawdb.PathScheme {
states = TriesInMemory + 1
states = state.TriesInMemory + 1
}
lastPrunedIndex := len(blocks) - states - 1
lastPrunedBlock := blocks[lastPrunedIndex]
@@ -3634,18 +3634,19 @@ func testSetCanonical(t *testing.T, scheme string) {
Alloc: types.GenesisAlloc{address: {Balance: funds}},
BaseFee: big.NewInt(params.InitialBaseFee),
}
signer = types.LatestSigner(gspec.Config)
engine = ethash.NewFaker()
signer = types.LatestSigner(gspec.Config)
engine = ethash.NewFaker()
chainLength = 10
)
// Generate and import the canonical chain
_, canon, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) {
_, canon, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, gen.header.BaseFee, nil), signer, key)
if err != nil {
panic(err)
}
gen.AddTx(tx)
})
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil)
@@ -3659,7 +3660,7 @@ func testSetCanonical(t *testing.T, scheme string) {
}
// Generate the side chain and import them
_, side, _ := GenerateChainWithGenesis(gspec, engine, 2*TriesInMemory, func(i int, gen *BlockGen) {
_, side, _ := GenerateChainWithGenesis(gspec, engine, chainLength, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(gen.TxNonce(address), common.Address{0x00}, big.NewInt(1), params.TxGas, gen.header.BaseFee, nil), signer, key)
if err != nil {
panic(err)
@@ -3698,8 +3699,8 @@ func testSetCanonical(t *testing.T, scheme string) {
verify(side[len(side)-1])
// Reset the chain head to original chain
chain.SetCanonical(canon[TriesInMemory-1])
verify(canon[TriesInMemory-1])
chain.SetCanonical(canon[chainLength-1])
verify(canon[chainLength-1])
}
// TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted

View File

@@ -23,7 +23,7 @@ import (
// request represents a bloom retrieval task to prioritize and pull from the local
// database or remotely from the network.
type request struct {
section uint64 // Section index to retrieve the a bit-vector from
section uint64 // Section index to retrieve the bit-vector from
bit uint // Bit index within the section to retrieve the vector of
}

View File

@@ -228,7 +228,7 @@ func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Heade
b.t.Error("Unexpected call to Process")
// Can't use Fatal since this is not the test's goroutine.
// Returning error stops the chainIndexer's updateLoop
return errors.New("Unexpected call to Process")
return errors.New("unexpected call to Process")
case b.processCh <- header.Number.Uint64():
}
return nil

View File

@@ -32,7 +32,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/triedb"
"github.com/gballet/go-verkle"
"github.com/ethereum/go-verkle"
"github.com/holiman/uint256"
)

View File

@@ -43,12 +43,11 @@ func TestGeneratePOSChain(t *testing.T) {
bb = common.Address{0xbb}
funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether))
config = *params.AllEthashProtocolChanges
asm4788 = common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500")
gspec = &Genesis{
Config: &config,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
params.BeaconRootsAddress: {Balance: common.Big0, Code: asm4788},
params.BeaconRootsAddress: {Code: params.BeaconRootsCode},
},
BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: common.Big1,

View File

@@ -64,6 +64,11 @@ var (
// than init code size limit.
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
// funds to cover the transfer, but not enough to pay for witness access/modification
// costs for the transaction
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
// ErrInsufficientFunds is returned if the total cost of executing a transaction
// is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")

View File

@@ -476,7 +476,7 @@ func (g *Genesis) ToBlock() *types.Block {
}
}
}
return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals)
return types.NewBlock(head, &types.Body{Withdrawals: withdrawals}, nil, trie.NewStackTrie(nil))
}
// Commit writes the block and state of a genesis specification to the database.
@@ -593,6 +593,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b
// Pre-deploy EIP-4788 system contract
params.BeaconRootsAddress: {Nonce: 1, Code: params.BeaconRootsCode},
},
}
if faucet != nil {

View File

@@ -304,7 +304,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
},
}
expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
expected := common.FromHex("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b")
got := genesis.ToBlock().Root().Bytes()
if !bytes.Equal(got, expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
@@ -314,7 +314,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, PathDB: pathdb.Defaults})
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got)
t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root())
}
// Test that the trie is verkle
@@ -322,7 +322,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
t.Fatalf("expected trie to be verkle")
}
if !rawdb.ExistsAccountTrieNode(db, nil) {
if !rawdb.HasAccountTrieNode(db, nil) {
t.Fatal("could not find node")
}
}

View File

@@ -101,6 +101,7 @@ func main() {
if err != nil {
panic(err)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(g); err != nil {
panic(err)
}

View File

@@ -19,7 +19,6 @@ package rawdb
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math/big"
"slices"
@@ -695,27 +694,6 @@ func (r *receiptLogs) DecodeRLP(s *rlp.Stream) error {
return nil
}
// deriveLogFields fills the logs in receiptLogs with information such as block number, txhash, etc.
func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, txs types.Transactions) error {
logIndex := uint(0)
if len(txs) != len(receipts) {
return errors.New("transaction and receipt count mismatch")
}
for i := 0; i < len(receipts); i++ {
txHash := txs[i].Hash()
// The derived log fields can simply be set from the block and transaction
for j := 0; j < len(receipts[i].Logs); j++ {
receipts[i].Logs[j].BlockNumber = number
receipts[i].Logs[j].BlockHash = hash
receipts[i].Logs[j].TxHash = txHash
receipts[i].Logs[j].TxIndex = uint(i)
receipts[i].Logs[j].Index = logIndex
logIndex++
}
}
return nil
}
// ReadLogs retrieves the logs for all transactions in a block. In case
// receipts is not found, a nil is returned.
// Note: ReadLogs does not derive unstored log fields.
@@ -753,7 +731,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block {
if body == nil {
return nil
}
return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithWithdrawals(body.Withdrawals)
return types.NewBlockWithHeader(header).WithBody(*body)
}
// WriteBlock serializes a block into the database, header and body separately.
@@ -843,7 +821,11 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
}
for _, bad := range badBlocks {
if bad.Header.Hash() == hash {
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals)
block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
return block
}
}
return nil
@@ -862,7 +844,11 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
}
var blocks []*types.Block
for _, bad := range badBlocks {
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles).WithWithdrawals(bad.Body.Withdrawals))
block := types.NewBlockWithHeader(bad.Header)
if bad.Body != nil {
block = block.WithBody(*bad.Body)
}
blocks = append(blocks, block)
}
return blocks
}

View File

@@ -640,7 +640,7 @@ func makeTestBlocks(nblock int, txsPerBlock int) []*types.Block {
Number: big.NewInt(int64(i)),
Extra: []byte("test block"),
}
blocks[i] = types.NewBlockWithHeader(header).WithBody(txs, nil)
blocks[i] = types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs})
blocks[i].Hash() // pre-cache the block hash
}
return blocks
@@ -794,7 +794,7 @@ func TestDeriveLogFields(t *testing.T) {
}),
}
// Create the corresponding receipts
receipts := []*receiptLogs{
receipts := []*types.Receipt{
{
Logs: []*types.Log{
{Address: common.BytesToAddress([]byte{0x11})},
@@ -818,9 +818,7 @@ func TestDeriveLogFields(t *testing.T) {
// Derive log metadata fields
number := big.NewInt(1)
hash := common.BytesToHash([]byte{0x03, 0x14})
if err := deriveLogFields(receipts, hash, number.Uint64(), txs); err != nil {
t.Fatal(err)
}
types.Receipts(receipts).DeriveFields(params.TestChainConfig, hash, number.Uint64(), 0, big.NewInt(0), big.NewInt(0), txs)
// Iterate over all the computed fields and check that they're correct
logIndex := uint(0)

View File

@@ -76,7 +76,7 @@ func TestLookupStorage(t *testing.T) {
tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33})
txs := []*types.Transaction{tx1, tx2, tx3}
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher())
block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, &types.Body{Transactions: txs}, nil, newTestHasher())
// Check that no transactions entries are in a pristine database
for i, tx := range txs {

View File

@@ -24,7 +24,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"golang.org/x/crypto/sha3"
)
// HashScheme is the legacy hash-based state scheme with which trie nodes are
@@ -50,7 +49,7 @@ const PathScheme = "path"
type hasher struct{ sha crypto.KeccakState }
var hasherPool = sync.Pool{
New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} },
New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
}
func newHasher() *hasher {
@@ -65,33 +64,15 @@ func (h *hasher) release() {
hasherPool.Put(h)
}
// ReadAccountTrieNode retrieves the account trie node and the associated node
// hash with the specified node path.
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.Hash) {
data, err := db.Get(accountTrieNodeKey(path))
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
// ReadAccountTrieNode retrieves the account trie node with the specified node path.
func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) []byte {
data, _ := db.Get(accountTrieNodeKey(path))
return data
}
// HasAccountTrieNode checks the account trie node presence with the specified
// node path and the associated node hash.
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) bool {
data, err := db.Get(accountTrieNodeKey(path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsAccountTrieNode checks the presence of the account trie node with the
// HasAccountTrieNode checks the presence of the account trie node with the
// specified node path, regardless of the node hash.
func ExistsAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool {
func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool {
has, err := db.Has(accountTrieNodeKey(path))
if err != nil {
return false
@@ -113,33 +94,15 @@ func DeleteAccountTrieNode(db ethdb.KeyValueWriter, path []byte) {
}
}
// ReadStorageTrieNode retrieves the storage trie node and the associated node
// hash with the specified node path.
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) ([]byte, common.Hash) {
data, err := db.Get(storageTrieNodeKey(accountHash, path))
if err != nil {
return nil, common.Hash{}
}
h := newHasher()
defer h.release()
return data, h.hash(data)
// ReadStorageTrieNode retrieves the storage trie node with the specified node path.
func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) []byte {
data, _ := db.Get(storageTrieNodeKey(accountHash, path))
return data
}
// HasStorageTrieNode checks the storage trie node presence with the provided
// node path and the associated node hash.
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte, hash common.Hash) bool {
data, err := db.Get(storageTrieNodeKey(accountHash, path))
if err != nil {
return false
}
h := newHasher()
defer h.release()
return h.hash(data) == hash
}
// ExistsStorageTrieNode checks the presence of the storage trie node with the
// HasStorageTrieNode checks the presence of the storage trie node with the
// specified account hash and node path, regardless of the node hash.
func ExistsStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool {
func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool {
has, err := db.Has(storageTrieNodeKey(accountHash, path))
if err != nil {
return false
@@ -198,10 +161,18 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
case HashScheme:
return HasLegacyTrieNode(db, hash)
case PathScheme:
var blob []byte
if owner == (common.Hash{}) {
return HasAccountTrieNode(db, path, hash)
blob = ReadAccountTrieNode(db, path)
} else {
blob = ReadStorageTrieNode(db, owner, path)
}
return HasStorageTrieNode(db, owner, path, hash)
if len(blob) == 0 {
return false
}
h := newHasher()
defer h.release()
return h.hash(blob) == hash // exists but not match
default:
panic(fmt.Sprintf("Unknown scheme %v", scheme))
}
@@ -209,43 +180,35 @@ func HasTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash c
// ReadTrieNode retrieves the trie node from database with the provided node info
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
//
// pathScheme-based lookup requires the following:
// - owner
// - path
func ReadTrieNode(db ethdb.KeyValueReader, owner common.Hash, path []byte, hash common.Hash, scheme string) []byte {
switch scheme {
case HashScheme:
return ReadLegacyTrieNode(db, hash)
case PathScheme:
var (
blob []byte
nHash common.Hash
)
var blob []byte
if owner == (common.Hash{}) {
blob, nHash = ReadAccountTrieNode(db, path)
blob = ReadAccountTrieNode(db, path)
} else {
blob, nHash = ReadStorageTrieNode(db, owner, path)
blob = ReadStorageTrieNode(db, owner, path)
}
if nHash != hash {
if len(blob) == 0 {
return nil
}
h := newHasher()
defer h.release()
if h.hash(blob) != hash {
return nil // exists but not match
}
return blob
default:
panic(fmt.Sprintf("Unknown scheme %v", scheme))
}
}
// WriteTrieNode writes the trie node into database with the provided node info
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// WriteTrieNode writes the trie node into database with the provided node info.
//
// pathScheme-based lookup requires the following:
// - owner
// - path
// hash-scheme requires the node hash as the identifier.
// path-scheme requires the node owner and path as the identifier.
func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, node []byte, scheme string) {
switch scheme {
case HashScheme:
@@ -261,14 +224,10 @@ func WriteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash
}
}
// DeleteTrieNode deletes the trie node from database with the provided node info
// and associated node hash.
// hashScheme-based lookup requires the following:
// - hash
// DeleteTrieNode deletes the trie node from database with the provided node info.
//
// pathScheme-based lookup requires the following:
// - owner
// - path
// hash-scheme requires the node hash as the identifier.
// path-scheme requires the node owner and path as the identifier.
func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, hash common.Hash, scheme string) {
switch scheme {
case HashScheme:
@@ -287,9 +246,8 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has
// ReadStateScheme reads the state scheme of persistent state, or none
// if the state is not present in database.
func ReadStateScheme(db ethdb.Reader) string {
// Check if state in path-based scheme is present
blob, _ := ReadAccountTrieNode(db, nil)
if len(blob) != 0 {
// Check if state in path-based scheme is present.
if HasAccountTrieNode(db, nil) {
return PathScheme
}
// The root node might be deleted during the initial snap sync, check
@@ -304,8 +262,7 @@ func ReadStateScheme(db ethdb.Reader) string {
if header == nil {
return "" // empty datadir
}
blob = ReadLegacyTrieNode(db, header.Root)
if len(blob) == 0 {
if !HasLegacyTrieNode(db, header.Root) {
return "" // no state in disk
}
return HashScheme

View File

@@ -16,7 +16,11 @@
package rawdb
import "path/filepath"
import (
"path/filepath"
"github.com/ethereum/go-ethereum/ethdb"
)
// The list of table names of chain freezer.
const (
@@ -75,7 +79,15 @@ var (
// freezers the collections of all builtin freezers.
var freezers = []string{ChainFreezerName, StateFreezerName}
// NewStateFreezer initializes the freezer for state history.
func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) {
return NewResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
// NewStateFreezer initializes the ancient store for state history.
//
// - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func NewStateFreezer(ancientDir string, readOnly bool) (ethdb.ResettableAncientStore, error) {
if ancientDir == "" {
return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil
}
return newResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
}

View File

@@ -89,20 +89,17 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
infos = append(infos, info)
case StateFreezerName:
if ReadStateScheme(db) != PathScheme {
continue
}
datadir, err := db.AncientDatadir()
if err != nil {
return nil, err
}
f, err := NewStateFreezer(datadir, true)
if err != nil {
return nil, err
continue // might be possible the state freezer is not existent
}
defer f.Close()
info, err := inspect(StateFreezerName, stateFreezerNoSnappy, f)
info, err := inspect(freezer, stateFreezerNoSnappy, f)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,325 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package ancienttest
import (
"bytes"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/testrand"
)
// TestAncientSuite runs a suite of tests against an ancient database
// implementation.
func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test basic read methods
t.Run("BasicRead", func(t *testing.T) { basicRead(t, newFn) })
// Test batch read method
t.Run("BatchRead", func(t *testing.T) { batchRead(t, newFn) })
// Test basic write methods
t.Run("BasicWrite", func(t *testing.T) { basicWrite(t, newFn) })
// Test if data mutation is allowed after db write
t.Run("nonMutable", func(t *testing.T) { nonMutable(t, newFn) })
}
func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < len(data); i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test basic tail and head retrievals
tail, err := db.Tail()
if err != nil || tail != 10 {
t.Fatal("Failed to retrieve tail")
}
ancient, err := db.Ancients()
if err != nil || ancient != 90 {
t.Fatal("Failed to retrieve ancient")
}
// Test the deleted items shouldn't be reachable
var cases = []struct {
start int
limit int
}{
{0, 10},
{90, 100},
}
for _, c := range cases {
for i := c.start; i < c.limit; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatalf("Item %d is already truncated", uint64(i))
}
_, err = db.Ancient("a", uint64(i))
if err == nil {
t.Fatal("Error is expected for non-existent item")
}
}
}
// Test the items in range should be reachable
for i := 10; i < 90; i++ {
exist, err := db.HasAncient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if !exist {
t.Fatalf("Item %d is missing", uint64(i))
}
blob, err := db.Ancient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to retrieve item, %v", err)
}
if !bytes.Equal(blob, data[i]) {
t.Fatalf("Unexpected item content, want: %v, got: %v", data[i], blob)
}
}
// Test the items in unknown table shouldn't be reachable
exist, err := db.HasAncient("b", uint64(0))
if err != nil {
t.Fatalf("Failed to check presence, %v", err)
}
if exist {
t.Fatal("Item in unknown table shouldn't be found")
}
_, err = db.Ancient("b", uint64(0))
if err == nil {
t.Fatal("Error is expected for unknown table")
}
}
func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Test the items in range should be reachable
var cases = []struct {
start uint64
count uint64
maxSize uint64
expStart int
expLimit int
}{
// Items in range [10, 90) with no size limitation
{
10, 80, 0, 10, 90,
},
// Items in range [10, 90) with 32 size cap, single item is expected
{
10, 80, 32, 10, 11,
},
// Items in range [10, 90) with 31 size cap, single item is expected
{
10, 80, 31, 10, 11,
},
// Items in range [10, 90) with 32*80 size cap, all items are expected
{
10, 80, 32 * 80, 10, 90,
},
// Extra items above the last item are not returned
{
10, 90, 0, 10, 90,
},
}
for i, c := range cases {
batch, err := db.AncientRange("a", c.start, c.count, c.maxSize)
if err != nil {
t.Fatalf("Failed to retrieve item in range, %v", err)
}
if !reflect.DeepEqual(batch, data[c.expStart:c.expLimit]) {
t.Fatalf("Case %d, Batch content is not matched", i)
}
}
// Test out-of-range / zero-size retrieval should be rejected
_, err := db.AncientRange("a", 0, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 90, 1, 0)
if err == nil {
t.Fatal("Out-of-range retrieval should be rejected")
}
_, err = db.AncientRange("a", 10, 0, 0)
if err == nil {
t.Fatal("Zero-size retrieval should be rejected")
}
// Test item in unknown table shouldn't be reachable
_, err = db.AncientRange("b", 10, 1, 0)
if err == nil {
t.Fatal("Item in unknown table shouldn't be found")
}
}
func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
var (
db = newFn([]string{"a", "b"})
dataA = makeDataset(100, 32)
dataB = makeDataset(100, 32)
)
defer db.Close()
// The ancient write to tables should be aligned
_, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
}
return nil
})
if err == nil {
t.Fatal("Unaligned ancient write should be rejected")
}
// Test normal ancient write
size, err := db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
wantSize := int64(6400)
if size != wantSize {
t.Fatalf("Ancient write size is not expected, want: %d, got: %d", wantSize, size)
}
// Write should work after head truncating
db.TruncateHead(90)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 90; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
// Write should work after truncating everything
db.TruncateTail(0)
_, err = db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), dataA[i])
op.AppendRaw("b", uint64(i), dataB[i])
}
return nil
})
if err != nil {
t.Fatalf("Failed to write ancient data %v", err)
}
}
func nonMutable(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
db := newFn([]string{"a"})
defer db.Close()
// We write 100 zero-bytes to the freezer and immediately mutate the slice
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
data := make([]byte, 100)
op.AppendRaw("a", uint64(0), data)
for i := range data {
data[i] = 0xff
}
return nil
})
// Now read it.
data, err := db.Ancient("a", uint64(0))
if err != nil {
t.Fatal(err)
}
for k, v := range data {
if v != 0 {
t.Fatalf("byte %d != 0: %x", k, v)
}
}
}
// TestResettableAncientSuite runs a suite of tests against a resettable ancient
// database implementation.
func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.ResettableAncientStore) {
t.Run("Reset", func(t *testing.T) {
var (
db = newFn([]string{"a"})
data = makeDataset(100, 32)
)
defer db.Close()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
db.TruncateTail(10)
db.TruncateHead(90)
// Ancient write should work after resetting
db.Reset()
db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for i := 0; i < 100; i++ {
op.AppendRaw("a", uint64(i), data[i])
}
return nil
})
})
}
func makeDataset(size, value int) [][]byte {
var vals [][]byte
for i := 0; i < size; i += 1 {
vals = append(vals, testrand.Bytes(value))
}
return vals
}

View File

@@ -39,26 +39,40 @@ const (
freezerBatchLimit = 30000
)
// chainFreezer is a wrapper of freezer with additional chain freezing feature.
// The background thread will keep moving ancient chain segments from key-value
// database to flat files for saving space on live database.
// chainFreezer is a wrapper of chain ancient store with additional chain freezing
// feature. The background thread will keep moving ancient chain segments from
// key-value database to flat files for saving space on live database.
type chainFreezer struct {
*Freezer
ethdb.AncientStore // Ancient store for storing cold chain segment
quit chan struct{}
wg sync.WaitGroup
trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
}
// newChainFreezer initializes the freezer for ancient chain data.
// newChainFreezer initializes the freezer for ancient chain segment.
//
// - if the empty directory is given, initializes the pure in-memory
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
freezer, err := NewChainFreezer(datadir, namespace, readonly)
var (
err error
freezer ethdb.AncientStore
)
if datadir == "" {
freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy)
} else {
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
if err != nil {
return nil, err
}
return &chainFreezer{
Freezer: freezer,
quit: make(chan struct{}),
trigger: make(chan chan struct{}),
AncientStore: freezer,
quit: make(chan struct{}),
trigger: make(chan chan struct{}),
}, nil
}
@@ -70,7 +84,7 @@ func (f *chainFreezer) Close() error {
close(f.quit)
}
f.wg.Wait()
return f.Freezer.Close()
return f.AncientStore.Close()
}
// readHeadNumber returns the number of chain head block. 0 is returned if the
@@ -167,7 +181,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
log.Debug("Current full block not old enough to freeze", "err", err)
continue
}
frozen := f.frozen.Load()
frozen, _ := f.Ancients() // no error will occur, safe to ignore
// Short circuit if the blocks below threshold are already frozen.
if frozen != 0 && frozen-1 >= threshold {
@@ -190,7 +204,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
backoff = true
continue
}
// Batch of blocks have been frozen, flush them before wiping from leveldb
// Batch of blocks have been frozen, flush them before wiping from key-value store
if err := f.Sync(); err != nil {
log.Crit("Failed to flush frozen tables", "err", err)
}
@@ -210,7 +224,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) {
// Wipe out side chains also and track dangling side chains
var dangling []common.Hash
frozen = f.frozen.Load() // Needs reload after during freezeRange
frozen, _ = f.Ancients() // Needs reload after during freezeRange
for number := first; number < frozen; number++ {
// Always keep the genesis block in active database
if number != 0 {

View File

@@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) {
var block *types.Block
var txs []*types.Transaction
to := common.BytesToAddress([]byte{0x11})
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher()) // Empty genesis block
WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
for i := uint64(1); i <= 10; i++ {
@@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) {
})
}
txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher())
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
}
@@ -111,7 +111,7 @@ func TestIndexTransactions(t *testing.T) {
to := common.BytesToAddress([]byte{0x11})
// Write empty genesis block
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher())
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, newTestHasher())
WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
@@ -138,7 +138,7 @@ func TestIndexTransactions(t *testing.T) {
})
}
txs = append(txs, tx)
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher())
block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, &types.Body{Transactions: types.Transactions{tx}}, nil, newTestHasher())
WriteBlock(chainDb, block)
WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64())
}

View File

@@ -34,11 +34,13 @@ import (
"github.com/olekukonko/tablewriter"
)
// freezerdb is a database wrapper that enables freezer data retrievals.
// freezerdb is a database wrapper that enables ancient chain segment freezing.
type freezerdb struct {
ancientRoot string
ethdb.KeyValueStore
ethdb.AncientStore
*chainFreezer
readOnly bool
ancientRoot string
}
// AncientDatadir returns the path of root ancient directory.
@@ -50,7 +52,7 @@ func (frdb *freezerdb) AncientDatadir() (string, error) {
// the slow ancient tables.
func (frdb *freezerdb) Close() error {
var errs []error
if err := frdb.AncientStore.Close(); err != nil {
if err := frdb.chainFreezer.Close(); err != nil {
errs = append(errs, err)
}
if err := frdb.KeyValueStore.Close(); err != nil {
@@ -66,12 +68,12 @@ func (frdb *freezerdb) Close() error {
// a freeze cycle completes, without having to sleep for a minute to trigger the
// automatic background run.
func (frdb *freezerdb) Freeze() error {
if frdb.AncientStore.(*chainFreezer).readonly {
if frdb.readOnly {
return errReadOnly
}
// Trigger a freeze cycle and block until it's done
trigger := make(chan struct{}, 1)
frdb.AncientStore.(*chainFreezer).trigger <- trigger
frdb.chainFreezer.trigger <- trigger
<-trigger
return nil
}
@@ -192,8 +194,14 @@ func resolveChainFreezerDir(ancient string) string {
// storage. The passed ancient indicates the path of root ancient directory
// where the chain freezer can be opened.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
// Create the idle freezer instance
frdb, err := newChainFreezer(resolveChainFreezerDir(ancient), namespace, readonly)
// Create the idle freezer instance. If the given ancient directory is empty,
// in-memory chain freezer is used (e.g. dev mode); otherwise the regular
// file-based freezer is created.
chainFreezerDir := ancient
if chainFreezerDir != "" {
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
}
frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly)
if err != nil {
printChainMetadata(db)
return nil, err
@@ -277,7 +285,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
}
}
// Freezer is consistent with the key-value database, permit combining the two
if !frdb.readonly {
if !readonly {
frdb.wg.Add(1)
go func() {
frdb.freeze(db)
@@ -287,7 +295,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
return &freezerdb{
ancientRoot: ancient,
KeyValueStore: db,
AncientStore: frdb,
chainFreezer: frdb,
}, nil
}

View File

@@ -62,7 +62,7 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
// reserving it for go-ethereum. This would also reduce the memory requirements
// of Geth, and thus also GC overhead.
type Freezer struct {
frozen atomic.Uint64 // Number of blocks already frozen
frozen atomic.Uint64 // Number of items already frozen
tail atomic.Uint64 // Number of the first stored item in the freezer
// This lock synchronizes writers and the truncate operation, as well as
@@ -76,12 +76,6 @@ type Freezer struct {
closeOnce sync.Once
}
// NewChainFreezer is a small utility method around NewFreezer that sets the
// default parameters for the chain storage.
func NewChainFreezer(datadir string, namespace string, readonly bool) (*Freezer, error) {
return NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
}
// NewFreezer creates a freezer instance for maintaining immutable ordered
// data according to the given parameters.
//

View File

@@ -0,0 +1,428 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rawdb
import (
"errors"
"fmt"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)
// memoryTable is used to store a list of sequential items in memory.
type memoryTable struct {
name string // Table name
items uint64 // Number of stored items in the table, including the deleted ones
offset uint64 // Number of deleted items from the table
data [][]byte // List of rlp-encoded items, sort in order
size uint64 // Total memory size occupied by the table
lock sync.RWMutex
}
// newMemoryTable initializes the memory table.
func newMemoryTable(name string) *memoryTable {
return &memoryTable{name: name}
}
// has returns an indicator whether the specified data exists.
func (t *memoryTable) has(number uint64) bool {
t.lock.RLock()
defer t.lock.RUnlock()
return number >= t.offset && number < t.items
}
// retrieve retrieves multiple items in sequence, starting from the index 'start'.
// It will return:
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (t *memoryTable) retrieve(start uint64, count, maxBytes uint64) ([][]byte, error) {
t.lock.RLock()
defer t.lock.RUnlock()
var (
size uint64
batch [][]byte
)
// Ensure the start is written, not deleted from the tail, and that the
// caller actually wants something.
if t.items <= start || t.offset > start || count == 0 {
return nil, errOutOfBounds
}
// Cap the item count if the retrieval is out of bound.
if start+count > t.items {
count = t.items - start
}
for n := start; n < start+count; n++ {
index := n - t.offset
if len(batch) != 0 && maxBytes != 0 && size+uint64(len(t.data[index])) > maxBytes {
return batch, nil
}
batch = append(batch, t.data[index])
size += uint64(len(t.data[index]))
}
return batch, nil
}
// truncateHead discards any recent data above the provided threshold number.
func (t *memoryTable) truncateHead(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.items <= items {
return nil
}
if items < t.offset {
return errors.New("truncation below tail")
}
t.data = t.data[:items-t.offset]
t.items = items
return nil
}
// truncateTail discards any recent data before the provided threshold number.
func (t *memoryTable) truncateTail(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Short circuit if nothing to delete.
if t.offset >= items {
return nil
}
if t.items < items {
return errors.New("truncation above head")
}
t.data = t.data[items-t.offset:]
t.offset = items
return nil
}
// commit merges the given item batch into table. It's presumed that the
// batch is ordered and continuous with table.
func (t *memoryTable) commit(batch [][]byte) error {
t.lock.Lock()
defer t.lock.Unlock()
for _, item := range batch {
t.size += uint64(len(item))
}
t.data = append(t.data, batch...)
t.items += uint64(len(batch))
return nil
}
// memoryBatch is the singleton batch used for ancient write.
type memoryBatch struct {
data map[string][][]byte
next map[string]uint64
size map[string]int64
}
func newMemoryBatch() *memoryBatch {
return &memoryBatch{
data: make(map[string][][]byte),
next: make(map[string]uint64),
size: make(map[string]int64),
}
}
func (b *memoryBatch) reset(freezer *MemoryFreezer) {
b.data = make(map[string][][]byte)
b.next = make(map[string]uint64)
b.size = make(map[string]int64)
for name, table := range freezer.tables {
b.next[name] = table.items
}
}
// Append adds an RLP-encoded item.
func (b *memoryBatch) Append(kind string, number uint64, item interface{}) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
blob, err := rlp.EncodeToBytes(item)
if err != nil {
return err
}
b.data[kind] = append(b.data[kind], blob)
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// AppendRaw adds an item without RLP-encoding it.
func (b *memoryBatch) AppendRaw(kind string, number uint64, blob []byte) error {
if b.next[kind] != number {
return errOutOrderInsertion
}
b.data[kind] = append(b.data[kind], common.CopyBytes(blob))
b.next[kind]++
b.size[kind] += int64(len(blob))
return nil
}
// commit is called at the end of a write operation and writes all remaining
// data to tables.
func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize int64, err error) {
// Check that count agrees on all batches.
items = math.MaxUint64
for name, next := range b.next {
if items < math.MaxUint64 && next != items {
return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, next, items)
}
items = next
}
// Commit all table batches.
for name, batch := range b.data {
table := freezer.tables[name]
if err := table.commit(batch); err != nil {
return 0, 0, err
}
writeSize += b.size[name]
}
return items, writeSize, nil
}
// MemoryFreezer is an ephemeral ancient store. It implements the ethdb.AncientStore
// interface and can be used along with ephemeral key-value store.
type MemoryFreezer struct {
items uint64 // Number of items stored
tail uint64 // Number of the first stored item in the freezer
readonly bool // Flag if the freezer is only for reading
lock sync.RWMutex // Lock to protect fields
tables map[string]*memoryTable // Tables for storing everything
writeBatch *memoryBatch // Pre-allocated write batch
}
// NewMemoryFreezer initializes an in-memory freezer instance.
func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
tables := make(map[string]*memoryTable)
for name := range tableName {
tables[name] = newMemoryTable(name)
}
return &MemoryFreezer{
writeBatch: newMemoryBatch(),
readonly: readonly,
tables: tables,
}
}
// HasAncient returns an indicator whether the specified data exists.
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.has(number), nil
}
return false, nil
}
// Ancient retrieves an ancient binary blob from the in-memory freezer.
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
data, err := t.retrieve(number, 1, 0)
if err != nil {
return nil, err
}
return data[0], nil
}
// AncientRange retrieves multiple items in sequence, starting from the index 'start'.
// It will return
// - at most 'count' items,
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present
func (f *MemoryFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
t := f.tables[kind]
if t == nil {
return nil, errUnknownTable
}
return t.retrieve(start, count, maxBytes)
}
// Ancients returns the ancient item numbers in the freezer.
func (f *MemoryFreezer) Ancients() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.items, nil
}
// Tail returns the number of first stored item in the freezer.
// This number can also be interpreted as the total deleted item numbers.
func (f *MemoryFreezer) Tail() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
return f.tail, nil
}
// AncientSize returns the ancient size of the specified category.
func (f *MemoryFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
if table := f.tables[kind]; table != nil {
return table.size, nil
}
return 0, errUnknownTable
}
// ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer.
func (f *MemoryFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock()
defer f.lock.RUnlock()
return fn(f)
}
// ModifyAncients runs the given write operation.
func (f *MemoryFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
// Roll back all tables to the starting position in case of error.
defer func(old uint64) {
if err == nil {
return
}
// The write operation has failed. Go back to the previous item position.
for name, table := range f.tables {
err := table.truncateHead(old)
if err != nil {
log.Error("Freezer table roll-back failed", "table", name, "index", old, "err", err)
}
}
}(f.items)
// Modify the ancients in batch.
f.writeBatch.reset(f)
if err := fn(f.writeBatch); err != nil {
return 0, err
}
item, writeSize, err := f.writeBatch.commit(f)
if err != nil {
return 0, err
}
f.items = item
return writeSize, nil
}
// TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number.
func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.items
if old <= items {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateHead(items); err != nil {
return 0, err
}
}
f.items = items
return old, nil
}
// TruncateTail discards any recent data below the provided threshold number.
func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.Lock()
defer f.lock.Unlock()
if f.readonly {
return 0, errReadOnly
}
old := f.tail
if old >= tail {
return old, nil
}
for _, table := range f.tables {
if err := table.truncateTail(tail); err != nil {
return 0, err
}
}
f.tail = tail
return old, nil
}
// Sync flushes all data tables to disk.
func (f *MemoryFreezer) Sync() error {
return nil
}
// MigrateTable processes and migrates entries of a given table to a new format.
// The second argument is a function that takes a raw entry and returns it
// in the newest format.
func (f *MemoryFreezer) MigrateTable(string, func([]byte) ([]byte, error)) error {
return errors.New("not implemented")
}
// Close releases all the sources held by the memory freezer. It will panic if
// any following invocation is made to a closed freezer.
func (f *MemoryFreezer) Close() error {
f.lock.Lock()
defer f.lock.Unlock()
f.tables = nil
f.writeBatch = nil
return nil
}
// Reset drops all the data cached in the memory freezer and reset itself
// back to default state.
func (f *MemoryFreezer) Reset() error {
f.lock.Lock()
defer f.lock.Unlock()
tables := make(map[string]*memoryTable)
for name := range f.tables {
tables[name] = newMemoryTable(name)
}
f.tables = tables
f.items, f.tail = 0, 0
return nil
}

View File

@@ -0,0 +1,41 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rawdb
import (
"testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb"
)
func TestMemoryFreezer(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
return NewMemoryFreezer(false, tables)
})
}

View File

@@ -30,16 +30,17 @@ const tmpSuffix = ".tmp"
// freezerOpenFunc is the function used to open/create a freezer.
type freezerOpenFunc = func() (*Freezer, error)
// ResettableFreezer is a wrapper of the freezer which makes the
// resettableFreezer is a wrapper of the freezer which makes the
// freezer resettable.
type ResettableFreezer struct {
freezer *Freezer
opener freezerOpenFunc
datadir string
lock sync.RWMutex
type resettableFreezer struct {
readOnly bool
freezer *Freezer
opener freezerOpenFunc
datadir string
lock sync.RWMutex
}
// NewResettableFreezer creates a resettable freezer, note freezer is
// newResettableFreezer creates a resettable freezer, note freezer is
// only resettable if the passed file directory is exclusively occupied
// by the freezer. And also the user-configurable ancient root directory
// is **not** supported for reset since it might be a mount and rename
@@ -48,7 +49,7 @@ type ResettableFreezer struct {
//
// The reset function will delete directory atomically and re-create the
// freezer from scratch.
func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*ResettableFreezer, error) {
func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*resettableFreezer, error) {
if err := cleanup(datadir); err != nil {
return nil, err
}
@@ -59,10 +60,11 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
if err != nil {
return nil, err
}
return &ResettableFreezer{
freezer: freezer,
opener: opener,
datadir: datadir,
return &resettableFreezer{
readOnly: readonly,
freezer: freezer,
opener: opener,
datadir: datadir,
}, nil
}
@@ -70,10 +72,13 @@ func NewResettableFreezer(datadir string, namespace string, readonly bool, maxTa
// recreate the freezer from scratch. The atomicity of directory deletion
// is guaranteed by the rename operation, the leftover directory will be
// cleaned up in next startup in case crash happens after rename.
func (f *ResettableFreezer) Reset() error {
func (f *resettableFreezer) Reset() error {
f.lock.Lock()
defer f.lock.Unlock()
if f.readOnly {
return errReadOnly
}
if err := f.freezer.Close(); err != nil {
return err
}
@@ -93,7 +98,7 @@ func (f *ResettableFreezer) Reset() error {
}
// Close terminates the chain freezer, unmapping all the data files.
func (f *ResettableFreezer) Close() error {
func (f *resettableFreezer) Close() error {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -102,7 +107,7 @@ func (f *ResettableFreezer) Close() error {
// HasAncient returns an indicator whether the specified ancient data exists
// in the freezer
func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -110,7 +115,7 @@ func (f *ResettableFreezer) HasAncient(kind string, number uint64) (bool, error)
}
// Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -123,7 +128,7 @@ func (f *ResettableFreezer) Ancient(kind string, number uint64) ([]byte, error)
// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize),
// but will otherwise return as many items as fit into maxByteSize.
// - if maxBytes is not specified, 'count' items will be returned if they are present.
func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -131,7 +136,7 @@ func (f *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uin
}
// Ancients returns the length of the frozen items.
func (f *ResettableFreezer) Ancients() (uint64, error) {
func (f *resettableFreezer) Ancients() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -139,7 +144,7 @@ func (f *ResettableFreezer) Ancients() (uint64, error) {
}
// Tail returns the number of first stored item in the freezer.
func (f *ResettableFreezer) Tail() (uint64, error) {
func (f *resettableFreezer) Tail() (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -147,7 +152,7 @@ func (f *ResettableFreezer) Tail() (uint64, error) {
}
// AncientSize returns the ancient size of the specified category.
func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) {
func (f *resettableFreezer) AncientSize(kind string) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -156,7 +161,7 @@ func (f *ResettableFreezer) AncientSize(kind string) (uint64, error) {
// ReadAncients runs the given read operation while ensuring that no writes take place
// on the underlying freezer.
func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
func (f *resettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -164,7 +169,7 @@ func (f *ResettableFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (
}
// ModifyAncients runs the given write operation.
func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
func (f *resettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -173,7 +178,7 @@ func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error)
// TruncateHead discards any recent data above the provided threshold number.
// It returns the previous head number.
func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) {
func (f *resettableFreezer) TruncateHead(items uint64) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -182,7 +187,7 @@ func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) {
// TruncateTail discards any recent data below the provided threshold number.
// It returns the previous value
func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -190,7 +195,7 @@ func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) {
}
// Sync flushes all data tables to disk.
func (f *ResettableFreezer) Sync() error {
func (f *resettableFreezer) Sync() error {
f.lock.RLock()
defer f.lock.RUnlock()
@@ -199,7 +204,7 @@ func (f *ResettableFreezer) Sync() error {
// MigrateTable processes the entries in a given table in sequence
// converting them to a new format if they're of an old format.
func (f *ResettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error {
func (f *resettableFreezer) MigrateTable(kind string, convert convertLegacyFn) error {
f.lock.RLock()
defer f.lock.RUnlock()

View File

@@ -33,7 +33,7 @@ func TestResetFreezer(t *testing.T) {
{1, bytes.Repeat([]byte{1}, 2048)},
{2, bytes.Repeat([]byte{2}, 2048)},
}
f, _ := NewResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, freezerTestTableDef)
defer f.Close()
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
@@ -87,7 +87,7 @@ func TestFreezerCleanup(t *testing.T) {
{2, bytes.Repeat([]byte{2}, 2048)},
}
datadir := t.TempDir()
f, _ := NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f, _ := newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
for _, item := range items {
op.AppendRaw("test", item.id, item.blob)
@@ -98,7 +98,7 @@ func TestFreezerCleanup(t *testing.T) {
os.Rename(datadir, tmpName(datadir))
// Open the freezer again, trigger cleanup operation
f, _ = NewResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f, _ = newResettableFreezer(datadir, "", false, 2048, freezerTestTableDef)
f.Close()
if _, err := os.Lstat(tmpName(datadir)); !os.IsNotExist(err) {

View File

@@ -27,6 +27,7 @@ import (
"sync"
"testing"
"github.com/ethereum/go-ethereum/core/rawdb/ancienttest"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
@@ -480,3 +481,22 @@ func TestFreezerCloseSync(t *testing.T) {
t.Fatalf("want %v, have %v", have, want)
}
}
func TestFreezerSuite(t *testing.T) {
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newFreezerForTesting(t, tables)
return f
})
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
tables := make(map[string]bool)
for _, kind := range kinds {
tables[kind] = true
}
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables)
return f
})
}

320
core/state/access_events.go Normal file
View File

@@ -0,0 +1,320 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"maps"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
)
// mode specifies how a tree location has been accessed
// for the byte value:
// * the first bit is set if the branch has been edited
// * the second bit is set if the branch has been read
type mode byte
const (
AccessWitnessReadFlag = mode(1)
AccessWitnessWriteFlag = mode(2)
)
var zeroTreeIndex uint256.Int
// AccessEvents lists the locations of the state that are being accessed
// during the production of a block.
type AccessEvents struct {
branches map[branchAccessKey]mode
chunks map[chunkAccessKey]mode
pointCache *utils.PointCache
}
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
return &AccessEvents{
branches: make(map[branchAccessKey]mode),
chunks: make(map[chunkAccessKey]mode),
pointCache: pointCache,
}
}
// Merge is used to merge the access events that were generated during the
// execution of a tx, with the accumulation of all access events that were
// generated during the execution of all txs preceding this one in a block.
func (ae *AccessEvents) Merge(other *AccessEvents) {
for k := range other.branches {
ae.branches[k] |= other.branches[k]
}
for k, chunk := range other.chunks {
ae.chunks[k] |= chunk
}
}
// Keys returns, predictably, the list of keys that were touched during the
// buildup of the access witness.
func (ae *AccessEvents) Keys() [][]byte {
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
keys := make([][]byte, 0, len(ae.chunks))
for chunk := range ae.chunks {
basePoint := ae.pointCache.Get(chunk.addr[:])
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
keys = append(keys, key)
}
return keys
}
func (ae *AccessEvents) Copy() *AccessEvents {
cpy := &AccessEvents{
branches: maps.Clone(ae.branches),
chunks: maps.Clone(ae.chunks),
pointCache: ae.pointCache,
}
return cpy
}
// AddAccount returns the gas to be charged for each of the currently cold
// member fields of an account.
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
return gas
}
// MessageCallGas returns the gas to be charged for each of the currently
// cold member fields of an account, that need to be touched when making a message
// call to that account.
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false)
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
return gas
}
// ValueTransferGas returns the gas to be charged for each of the currently
// cold balance member fields of the caller and the callee accounts.
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
return gas
}
// ContractCreateInitGas returns the access gas costs for the initialization of
// a contract creation.
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
var gas uint64
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
if createSendsValue {
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
}
return gas
}
// AddTxOrigin adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
}
// AddTxDestination adds the member fields of the sender account to the access event list,
// so that cold accesses are not charged, since they are covered by the 21000 gas.
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
}
// SlotGas returns the amount of gas to be charged for a cold storage access.
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 {
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
}
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold
// access cost to be charged, if need be.
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
var gas uint64
if stemRead {
gas += params.WitnessBranchReadCost
}
if selectorRead {
gas += params.WitnessChunkReadCost
}
if stemWrite {
gas += params.WitnessBranchWriteCost
}
if selectorWrite {
gas += params.WitnessChunkWriteCost
}
if selectorFill {
gas += params.WitnessChunkFillCost
}
return gas
}
// touchAddress adds any missing access event to the access event list.
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
branchKey := newBranchAccessKey(addr, treeIndex)
chunkKey := newChunkAccessKey(branchKey, subIndex)
// Read access.
var branchRead, chunkRead bool
if _, hasStem := ae.branches[branchKey]; !hasStem {
branchRead = true
ae.branches[branchKey] = AccessWitnessReadFlag
}
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
chunkRead = true
ae.chunks[chunkKey] = AccessWitnessReadFlag
}
// Write access.
var branchWrite, chunkWrite, chunkFill bool
if isWrite {
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
branchWrite = true
ae.branches[branchKey] |= AccessWitnessWriteFlag
}
chunkValue := ae.chunks[chunkKey]
if (chunkValue & AccessWitnessWriteFlag) == 0 {
chunkWrite = true
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
}
// TODO: charge chunk filling costs if the leaf was previously empty in the state
}
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
}
type branchAccessKey struct {
addr common.Address
treeIndex uint256.Int
}
func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey {
var sk branchAccessKey
sk.addr = addr
sk.treeIndex = treeIndex
return sk
}
type chunkAccessKey struct {
branchAccessKey
leafKey byte
}
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
var lk chunkAccessKey
lk.branchAccessKey = branchKey
lk.leafKey = leafKey
return lk
}
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
// note that in the case where the copied code is outside the range of the
// contract code but touches the last leaf with contract code in it,
// we don't include the last leaf of code in the AccessWitness. The
// reason that we do not need the last leaf is the account's code size
// is already in the AccessWitness so a stateless verifier can see that
// the code from the last leaf is not needed.
if (codeLen == 0 && size == 0) || startPC > codeLen {
return 0
}
endPC := startPC + size
if endPC > codeLen {
endPC = codeLen
}
if endPC > 0 {
endPC -= 1 // endPC is the last bytecode that will be touched.
}
var statelessGasCharged uint64
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
subIndex := byte((chunkNumber + 128) % 256)
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
if overflow {
panic("overflow when adding gas")
}
}
return statelessGasCharged
}
// VersionGas adds the account's version to the accessed data, and returns the
// amount of gas that it costs.
// Note that an access in write mode implies an access in read mode, whereas an
// access in read mode does not imply an access in write mode.
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
}
// BalanceGas adds the account's balance to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
}
// NonceGas adds the account's nonce to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
}
// CodeSizeGas adds the account's code size to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
}
// CodeHashGas adds the account's code hash to the accessed data, and returns the
// amount of gas that it costs.
// in write mode. If false, the charged gas corresponds to an access in read mode.
// Note that an access in write mode implies an access in read mode, whereas an access in
// read mode does not imply an access in write mode.
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
}

View File

@@ -0,0 +1,153 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
)
var (
testAddr [20]byte
testAddr2 [20]byte
)
func init() {
for i := byte(0); i < 20; i++ {
testAddr[i] = i
testAddr[2] = 2 * i
}
}
func TestAccountHeaderGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost
gas := ae.VersionGas(testAddr, false)
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost)
}
// Check warm read cost
gas = ae.VersionGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
// Check cold read costs in the same group no longer incur the branch read cost
gas = ae.BalanceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.NonceGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeSizeGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
gas = ae.CodeHashGas(testAddr, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
// Check cold write cost
gas = ae.VersionGas(testAddr, true)
if gas != params.WitnessBranchWriteCost+params.WitnessChunkWriteCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost)
}
// Check warm write cost
gas = ae.VersionGas(testAddr, true)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
// Check a write without a read charges both read and write costs
gas = ae.BalanceGas(testAddr2, true)
if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost)
}
// Check that a write followed by a read charges nothing
gas = ae.BalanceGas(testAddr2, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
// Check that reading a slot from the account header only charges the
// chunk read cost.
gas = ae.SlotGas(testAddr, common.Hash{}, false)
if gas != params.WitnessChunkReadCost {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
}
}
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
// calculated.
func TestContractCreateInitGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
var testAddr [20]byte
for i := byte(0); i < 20; i++ {
testAddr[i] = i
}
// Check cold read cost, without a value
gas := ae.ContractCreateInitGas(testAddr, false)
if gas != params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*2+params.WitnessChunkReadCost*2 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*3)
}
// Check warm read cost
gas = ae.ContractCreateInitGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
}
// TestMessageCallGas checks that the gas cost of message calls is correctly
// calculated.
func TestMessageCallGas(t *testing.T) {
ae := NewAccessEvents(utils.NewPointCache(1024))
// Check cold read cost, without a value
gas := ae.MessageCallGas(testAddr)
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost*2 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost*2)
}
// Check that reading the version and code size of the same account does not incur the branch read cost
gas = ae.VersionGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
gas = ae.CodeSizeGas(testAddr, false)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
// Check warm read cost
gas = ae.MessageCallGas(testAddr)
if gas != 0 {
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
}
}

View File

@@ -17,7 +17,10 @@
package state
import (
"fmt"
"maps"
"slices"
"strings"
"github.com/ethereum/go-ethereum/common"
)
@@ -130,3 +133,35 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
func (al *accessList) DeleteAddress(address common.Address) {
delete(al.addresses, address)
}
// Equal returns true if the two access lists are identical
func (al *accessList) Equal(other *accessList) bool {
if !maps.Equal(al.addresses, other.addresses) {
return false
}
return slices.EqualFunc(al.slots, other.slots,
func(m map[common.Hash]struct{}, m2 map[common.Hash]struct{}) bool {
return maps.Equal(m, m2)
})
}
// PrettyPrint prints the contents of the access list in a human-readable form
func (al *accessList) PrettyPrint() string {
out := new(strings.Builder)
var sortedAddrs []common.Address
for addr := range al.addresses {
sortedAddrs = append(sortedAddrs, addr)
}
slices.SortFunc(sortedAddrs, common.Address.Cmp)
for _, addr := range sortedAddrs {
idx := al.addresses[addr]
fmt.Fprintf(out, "%#x : (idx %d)\n", addr, idx)
if idx >= 0 {
slotmap := al.slots[idx]
for h := range slotmap {
fmt.Fprintf(out, " %#x\n", h)
}
}
}
return out.String()
}

View File

@@ -20,7 +20,6 @@ import (
"errors"
"fmt"
"github.com/crate-crypto/go-ipa/banderwagon"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -40,11 +39,8 @@ const (
// Cache size granted for caching clean code.
codeCacheSize = 64 * 1024 * 1024
// commitmentSize is the size of commitment stored in cache.
commitmentSize = banderwagon.UncompressedSize
// Cache item granted for caching commitment results.
commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength)
// Number of address->curve point associations to keep.
pointCacheSize = 4096
)
// Database wraps access to tries and contract code.
@@ -67,6 +63,9 @@ type Database interface {
// DiskDB returns the underlying key-value disk database.
DiskDB() ethdb.KeyValueStore
// PointCache returns the cache holding points used in verkle tree key computation
PointCache() *utils.PointCache
// TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database
}
@@ -139,6 +138,9 @@ type Trie interface {
// nodes of the longest existing prefix of the key (at least the root), ending
// with the node that proves the absence of the key.
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
// IsVerkle returns true if the trie is verkle-tree based
IsVerkle() bool
}
// NewDatabase creates a backing store for state. The returned database is safe for
@@ -157,6 +159,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database {
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb.NewDatabase(db, config),
pointCache: utils.NewPointCache(pointCacheSize),
}
}
@@ -167,6 +170,7 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
triedb: triedb,
pointCache: utils.NewPointCache(pointCacheSize),
}
}
@@ -175,12 +179,13 @@ type cachingDB struct {
codeSizeCache *lru.Cache[common.Hash, int]
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
triedb *triedb.Database
pointCache *utils.PointCache
}
// OpenTrie opens the main account trie at a specific root hash.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.triedb.IsVerkle() {
return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems))
return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
@@ -266,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
func (db *cachingDB) TrieDB() *triedb.Database {
return db.triedb
}
// PointCache returns the cache of evaluated curve points.
func (db *cachingDB) PointCache() *utils.PointCache {
return db.pointCache
}

View File

@@ -17,6 +17,8 @@
package state
import (
"maps"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)
@@ -29,6 +31,9 @@ type journalEntry interface {
// dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address
// copy returns a deep-copied journal entry.
copy() journalEntry
}
// journal contains the list of state modifications applied since the last state
@@ -83,22 +88,31 @@ func (j *journal) length() int {
return len(j.entries)
}
// copy returns a deep-copied journal.
func (j *journal) copy() *journal {
entries := make([]journalEntry, 0, j.length())
for i := 0; i < j.length(); i++ {
entries = append(entries, j.entries[i].copy())
}
return &journal{
entries: entries,
dirties: maps.Clone(j.dirties),
}
}
type (
// Changes to the account trie.
createObjectChange struct {
account *common.Address
}
resetObjectChange struct {
account *common.Address
prev *stateObject
prevdestruct bool
prevAccount []byte
prevStorage map[common.Hash][]byte
prevAccountOriginExist bool
prevAccountOrigin []byte
prevStorageOrigin map[common.Hash][]byte
// createContractChange represents an account becoming a contract-account.
// This event happens prior to executing initcode. The journal-event simply
// manages the created-flag, in order to allow same-tx destruction.
createContractChange struct {
account common.Address
}
selfDestructChange struct {
account *common.Address
prev bool // whether account had already self-destructed
@@ -115,8 +129,10 @@ type (
prev uint64
}
storageChange struct {
account *common.Address
key, prevalue common.Hash
account *common.Address
key common.Hash
prevvalue common.Hash
origvalue common.Hash
}
codeChange struct {
account *common.Address
@@ -136,6 +152,7 @@ type (
touchChange struct {
account *common.Address
}
// Changes to the access list
accessListAddAccountChange struct {
address *common.Address
@@ -145,6 +162,7 @@ type (
slot *common.Hash
}
// Changes to transient storage
transientStorageChange struct {
account *common.Address
key, prevalue common.Hash
@@ -153,34 +171,30 @@ type (
func (ch createObjectChange) revert(s *StateDB) {
delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
}
func (ch createObjectChange) dirtied() *common.Address {
return ch.account
}
func (ch resetObjectChange) revert(s *StateDB) {
s.setStateObject(ch.prev)
if !ch.prevdestruct {
delete(s.stateObjectsDestruct, ch.prev.address)
}
if ch.prevAccount != nil {
s.accounts[ch.prev.addrHash] = ch.prevAccount
}
if ch.prevStorage != nil {
s.storages[ch.prev.addrHash] = ch.prevStorage
}
if ch.prevAccountOriginExist {
s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin
}
if ch.prevStorageOrigin != nil {
s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin
func (ch createObjectChange) copy() journalEntry {
return createObjectChange{
account: ch.account,
}
}
func (ch resetObjectChange) dirtied() *common.Address {
return ch.account
func (ch createContractChange) revert(s *StateDB) {
s.getStateObject(ch.account).newContract = false
}
func (ch createContractChange) dirtied() *common.Address {
return nil
}
func (ch createContractChange) copy() journalEntry {
return createContractChange{
account: ch.account,
}
}
func (ch selfDestructChange) revert(s *StateDB) {
@@ -195,6 +209,14 @@ func (ch selfDestructChange) dirtied() *common.Address {
return ch.account
}
func (ch selfDestructChange) copy() journalEntry {
return selfDestructChange{
account: ch.account,
prev: ch.prev,
prevbalance: new(uint256.Int).Set(ch.prevbalance),
}
}
var ripemd = common.HexToAddress("0000000000000000000000000000000000000003")
func (ch touchChange) revert(s *StateDB) {
@@ -204,6 +226,12 @@ func (ch touchChange) dirtied() *common.Address {
return ch.account
}
func (ch touchChange) copy() journalEntry {
return touchChange{
account: ch.account,
}
}
func (ch balanceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev)
}
@@ -212,6 +240,13 @@ func (ch balanceChange) dirtied() *common.Address {
return ch.account
}
func (ch balanceChange) copy() journalEntry {
return balanceChange{
account: ch.account,
prev: new(uint256.Int).Set(ch.prev),
}
}
func (ch nonceChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev)
}
@@ -220,6 +255,13 @@ func (ch nonceChange) dirtied() *common.Address {
return ch.account
}
func (ch nonceChange) copy() journalEntry {
return nonceChange{
account: ch.account,
prev: ch.prev,
}
}
func (ch codeChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
}
@@ -228,14 +270,30 @@ func (ch codeChange) dirtied() *common.Address {
return ch.account
}
func (ch codeChange) copy() journalEntry {
return codeChange{
account: ch.account,
prevhash: common.CopyBytes(ch.prevhash),
prevcode: common.CopyBytes(ch.prevcode),
}
}
func (ch storageChange) revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
s.getStateObject(*ch.account).setState(ch.key, ch.prevvalue, ch.origvalue)
}
func (ch storageChange) dirtied() *common.Address {
return ch.account
}
func (ch storageChange) copy() journalEntry {
return storageChange{
account: ch.account,
key: ch.key,
prevvalue: ch.prevvalue,
}
}
func (ch transientStorageChange) revert(s *StateDB) {
s.setTransientState(*ch.account, ch.key, ch.prevalue)
}
@@ -244,6 +302,14 @@ func (ch transientStorageChange) dirtied() *common.Address {
return nil
}
func (ch transientStorageChange) copy() journalEntry {
return transientStorageChange{
account: ch.account,
key: ch.key,
prevalue: ch.prevalue,
}
}
func (ch refundChange) revert(s *StateDB) {
s.refund = ch.prev
}
@@ -252,6 +318,12 @@ func (ch refundChange) dirtied() *common.Address {
return nil
}
func (ch refundChange) copy() journalEntry {
return refundChange{
prev: ch.prev,
}
}
func (ch addLogChange) revert(s *StateDB) {
logs := s.logs[ch.txhash]
if len(logs) == 1 {
@@ -266,6 +338,12 @@ func (ch addLogChange) dirtied() *common.Address {
return nil
}
func (ch addLogChange) copy() journalEntry {
return addLogChange{
txhash: ch.txhash,
}
}
func (ch addPreimageChange) revert(s *StateDB) {
delete(s.preimages, ch.hash)
}
@@ -274,6 +352,12 @@ func (ch addPreimageChange) dirtied() *common.Address {
return nil
}
func (ch addPreimageChange) copy() journalEntry {
return addPreimageChange{
hash: ch.hash,
}
}
func (ch accessListAddAccountChange) revert(s *StateDB) {
/*
One important invariant here, is that whenever a (addr, slot) is added, if the
@@ -291,6 +375,12 @@ func (ch accessListAddAccountChange) dirtied() *common.Address {
return nil
}
func (ch accessListAddAccountChange) copy() journalEntry {
return accessListAddAccountChange{
address: ch.address,
}
}
func (ch accessListAddSlotChange) revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot)
}
@@ -298,3 +388,10 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
}
func (ch accessListAddSlotChange) copy() journalEntry {
return accessListAddSlotChange{
address: ch.address,
slot: ch.slot,
}
}

View File

@@ -19,7 +19,6 @@ package state
import (
"bytes"
"fmt"
"io"
"maps"
"time"
@@ -27,26 +26,14 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256"
)
type Code []byte
func (c Code) String() string {
return string(c) //strings.Join(Disassemble(c), " ")
}
type Storage map[common.Hash]common.Hash
func (s Storage) String() (str string) {
for key, value := range s {
str += fmt.Sprintf("%X : %X\n", key, value)
}
return
}
func (s Storage) Copy() Storage {
return maps.Clone(s)
}
@@ -65,27 +52,37 @@ type stateObject struct {
data types.StateAccount // Account data with all mutations applied in the scope of block
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction
originStorage Storage // Storage entries that have been accessed within the current block
dirtyStorage Storage // Storage entries that have been modified within the current transaction
pendingStorage Storage // Storage entries that have been modified within the current block
// uncommittedStorage tracks a set of storage entries that have been modified
// but not yet committed since the "last commit operation", along with their
// original values before mutation.
//
// Specifically, the commit will be performed after each transaction before
// the byzantium fork, therefore the map is already reset at the transaction
// boundary; however post the byzantium fork, the commit will only be performed
// at the end of block, this set essentially tracks all the modifications
// made within the block.
uncommittedStorage Storage
// Cache flags.
dirtyCode bool // true if the code was updated
// Flag whether the account was marked as self-destructed. The self-destructed account
// is still accessible in the scope of same transaction.
// Flag whether the account was marked as self-destructed. The self-destructed
// account is still accessible in the scope of same transaction.
selfDestructed bool
// Flag whether the account was marked as deleted. A self-destructed account
// or an account that is considered as empty will be marked as deleted at
// the end of transaction and no longer accessible anymore.
deleted bool
// Flag whether the object was created in the current transaction
created bool
// This is an EIP-6780 flag indicating whether the object is eligible for
// self-destruct according to EIP-6780. The flag could be set either when
// the contract is just created within the current transaction, or when the
// object was previously existent and is being deployed as a contract within
// the current transaction.
newContract bool
}
// empty returns whether the account is considered empty.
@@ -95,31 +92,23 @@ func (s *stateObject) empty() bool {
// newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
var (
origin = acct
created = acct == nil // true if the account was not existent
)
origin := acct
if acct == nil {
acct = types.NewEmptyStateAccount()
}
return &stateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
origin: origin,
data: *acct,
originStorage: make(Storage),
pendingStorage: make(Storage),
dirtyStorage: make(Storage),
created: created,
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
origin: origin,
data: *acct,
originStorage: make(Storage),
dirtyStorage: make(Storage),
pendingStorage: make(Storage),
uncommittedStorage: make(Storage),
}
}
// EncodeRLP implements rlp.Encoder.
func (s *stateObject) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &s.data)
}
func (s *stateObject) markSelfdestructed() {
s.selfDestructed = true
}
@@ -135,39 +124,58 @@ func (s *stateObject) touch() {
}
}
// getTrie returns the associated storage trie. The trie will be opened
// if it's not loaded previously. An error will be returned if trie can't
// be loaded.
// getTrie returns the associated storage trie. The trie will be opened if it's
// not loaded previously. An error will be returned if trie can't be loaded.
//
// If a new trie is opened, it will be cached within the state object to allow
// subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) {
if s.trie == nil {
// Try fetching from prefetcher first
if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil {
// When the miner is creating the pending state, there is no prefetcher
s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root)
}
if s.trie == nil {
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if err != nil {
return nil, err
}
s.trie = tr
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if err != nil {
return nil, err
}
s.trie = tr
}
return s.trie, nil
}
// GetState retrieves a value from the account storage trie.
func (s *stateObject) GetState(key common.Hash) common.Hash {
// If we have a dirty value for this state entry, return it
value, dirty := s.dirtyStorage[key]
if dirty {
return value
// getPrefetchedTrie returns the associated trie, as populated by the prefetcher
// if it's available.
//
// Note, opposed to getTrie, this method will *NOT* blindly cache the resulting
// trie in the state object. The caller might want to do that, but it's cleaner
// to break the hidden interdependency between retrieving tries from the db or
// from the prefetcher.
func (s *stateObject) getPrefetchedTrie() Trie {
// If there's nothing to meaningfully return, let the user figure it out by
// pulling the trie from disk.
if s.data.Root == types.EmptyRootHash || s.db.prefetcher == nil {
return nil
}
// Otherwise return the entry's original value
return s.GetCommittedState(key)
// Attempt to retrieve the trie from the prefetcher
return s.db.prefetcher.trie(s.addrHash, s.data.Root)
}
// GetCommittedState retrieves a value from the committed account storage trie.
// GetState retrieves a value associated with the given storage key.
func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
return value
}
// getState retrieves a value associated with the given storage key, along with
// its original value.
func (s *stateObject) getState(key common.Hash) (common.Hash, common.Hash) {
origin := s.GetCommittedState(key)
value, dirty := s.dirtyStorage[key]
if dirty {
return value, origin
}
return origin, origin
}
// GetCommittedState retrieves the value associated with the specific key
// without any mutations caused in the current execution.
func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// If we have a pending write or clean cached, return that
if value, pending := s.pendingStorage[key]; pending {
@@ -183,6 +191,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// have been handles via pendingStorage above.
// 2) we don't have new values, and can deliver empty response back
if _, destructed := s.db.stateObjectsDestruct[s.address]; destructed {
s.originStorage[key] = common.Hash{} // track the empty slot as origin value
return common.Hash{}
}
// If no live objects are available, attempt to use snapshots
@@ -227,43 +236,74 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
// SetState updates a value in account storage.
func (s *stateObject) SetState(key, value common.Hash) {
// If the new value is the same as old, don't set
prev := s.GetState(key)
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
prev, origin := s.getState(key)
if prev == value {
return
}
// New value is different, update and journal the change
s.db.journal.append(storageChange{
account: &s.address,
key: key,
prevalue: prev,
account: &s.address,
key: key,
prevvalue: prev,
origvalue: origin,
})
if s.db.logger != nil && s.db.logger.OnStorageChange != nil {
s.db.logger.OnStorageChange(s.address, key, prev, value)
}
s.setState(key, value)
s.setState(key, value, origin)
}
func (s *stateObject) setState(key, value common.Hash) {
// setState updates a value in account dirty storage. The dirtiness will be
// removed if the value being set equals to the original value.
func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
// Storage slot is set back to its original value, undo the dirty marker
if value == origin {
delete(s.dirtyStorage, key)
return
}
s.dirtyStorage[key] = value
}
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise(prefetch bool) {
func (s *stateObject) finalise() {
slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage {
s.pendingStorage[key] = value
if value != s.originStorage[key] {
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
// The slot is reverted to its original value, delete the entry
// to avoid thrashing the data structures.
delete(s.uncommittedStorage, key)
} else if exist {
// The slot is modified to another value and the slot has been
// tracked for commit, do nothing here.
} else {
// The slot is different from its original value and hasn't been
// tracked for commit yet.
s.uncommittedStorage[key] = s.GetCommittedState(key)
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
}
// Aggregate the dirty storage slots into the pending area. It might
// be possible that the value of tracked slot here is same with the
// one in originStorage (e.g. the slot was modified in tx_a and then
// modified back in tx_b). We can't blindly remove it from pending
// map as the dirty slot might have been committed already (before the
// byzantium fork) and entry is necessary to modify the value back.
s.pendingStorage[key] = value
}
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch)
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
}
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
// Revoke the flag at the end of the transaction. It finalizes the status
// of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false
}
// updateTrie is responsible for persisting cached storage changes into the
@@ -272,32 +312,29 @@ func (s *stateObject) finalise(prefetch bool) {
// loading or updating of the trie, an error will be returned. Furthermore,
// this function will return the mutated storage trie, or nil if there is no
// storage change at all.
//
// It assumes all the dirty storage slots have been finalized before.
func (s *stateObject) updateTrie() (Trie, error) {
// Make sure all dirty slots are finalized into the pending storage area
s.finalise(false)
// Short circuit if nothing changed, don't bother with hashing anything
if len(s.pendingStorage) == 0 {
if len(s.uncommittedStorage) == 0 {
return s.trie, nil
}
// Track the amount of time wasted on updating the storage trie
defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now())
// The snapshot storage map for the object
var (
storage map[common.Hash][]byte
origin map[common.Hash][]byte
)
tr, err := s.getTrie()
if err != nil {
s.db.setError(err)
return nil, err
// Retrieve a pretecher populated trie, or fall back to the database
tr := s.getPrefetchedTrie()
if tr != nil {
// Prefetcher returned a live trie, swap it out for the current one
s.trie = tr
} else {
// Fetcher not running or empty trie, fallback to the database trie
var err error
tr, err = s.getTrie()
if err != nil {
s.db.setError(err)
return nil, err
}
}
// Insert all the pending storage updates into the trie
usedStorage := make([][]byte, 0, len(s.pendingStorage))
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
// Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
// During the execution of a block:
@@ -306,69 +343,44 @@ func (s *stateObject) updateTrie() (Trie, error) {
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var deletions []common.Hash
for key, value := range s.pendingStorage {
var (
deletions []common.Hash
used = make([][]byte, 0, len(s.uncommittedStorage))
)
for key, origin := range s.uncommittedStorage {
// Skip noop changes, persist actual changes
if value == s.originStorage[key] {
value, exist := s.pendingStorage[key]
if value == origin {
log.Error("Storage update was noop", "address", s.address, "slot", key)
continue
}
if !exist {
log.Error("Storage slot is not found in pending area", s.address, "slot", key)
continue
}
prev := s.originStorage[key]
s.originStorage[key] = value
var encoded []byte // rlp-encoded value to be used by the snapshot
if (value != common.Hash{}) {
// Encoding []byte cannot fail, ok to ignore the error.
trimmed := common.TrimLeftZeroes(value[:])
encoded, _ = rlp.EncodeToBytes(trimmed)
if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil {
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
s.db.setError(err)
return nil, err
}
s.db.StorageUpdated += 1
s.db.StorageUpdated.Add(1)
} else {
deletions = append(deletions, key)
}
// Cache the mutated storage slots until commit
if storage == nil {
if storage = s.db.storages[s.addrHash]; storage == nil {
storage = make(map[common.Hash][]byte)
s.db.storages[s.addrHash] = storage
}
}
khash := crypto.HashData(s.db.hasher, key[:])
storage[khash] = encoded // encoded will be nil if it's deleted
// Cache the original value of mutated storage slots
if origin == nil {
if origin = s.db.storagesOrigin[s.address]; origin == nil {
origin = make(map[common.Hash][]byte)
s.db.storagesOrigin[s.address] = origin
}
}
// Track the original value of slot only if it's mutated first time
if _, ok := origin[khash]; !ok {
if prev == (common.Hash{}) {
origin[khash] = nil // nil if it was not present previously
} else {
// Encoding []byte cannot fail, ok to ignore the error.
b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:]))
origin[khash] = b
}
}
// Cache the items for preloading
usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure
used = append(used, common.CopyBytes(key[:])) // Copy needed for closure
}
for _, key := range deletions {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
s.db.setError(err)
return nil, err
}
s.db.StorageDeleted += 1
s.db.StorageDeleted.Add(1)
}
if s.db.prefetcher != nil {
s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage)
s.db.prefetcher.used(s.addrHash, s.data.Root, used)
}
s.pendingStorage = make(Storage) // reset pending map
s.uncommittedStorage = make(Storage) // empties the commit markers
return tr, nil
}
@@ -381,36 +393,82 @@ func (s *stateObject) updateRoot() {
if err != nil || tr == nil {
return
}
// Track the amount of time wasted on hashing the storage trie
defer func(start time.Time) { s.db.StorageHashes += time.Since(start) }(time.Now())
s.data.Root = tr.Hash()
}
// commit obtains a set of dirty storage trie nodes and updates the account data.
// The returned set can be nil if nothing to commit. This function assumes all
// storage mutations have already been flushed into trie by updateRoot.
func (s *stateObject) commit() (*trienode.NodeSet, error) {
// Short circuit if trie is not even loaded, don't bother with committing anything
if s.trie == nil {
s.origin = s.data.Copy()
return nil, nil
}
// Track the amount of time wasted on committing the storage trie
defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now())
// commitStorage overwrites the clean storage with the storage changes and
// fulfills the storage diffs into the given accountUpdate struct.
func (s *stateObject) commitStorage(op *accountUpdate) {
var (
buf = crypto.NewKeccakState()
encode = func(val common.Hash) []byte {
if val == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
return blob
}
)
for key, val := range s.pendingStorage {
// Skip the noop storage changes, it might be possible the value
// of tracked slot is same in originStorage and pendingStorage
// map, e.g. the storage slot is modified in tx_a and then reset
// back in tx_b.
if val == s.originStorage[key] {
continue
}
hash := crypto.HashData(buf, key[:])
if op.storages == nil {
op.storages = make(map[common.Hash][]byte)
}
op.storages[hash] = encode(val)
if op.storagesOrigin == nil {
op.storagesOrigin = make(map[common.Hash][]byte)
}
op.storagesOrigin[hash] = encode(s.originStorage[key])
// The trie is currently in an open state and could potentially contain
// cached mutations. Call commit to acquire a set of nodes that have been
// modified, the set can be nil if nothing to commit.
// Overwrite the clean value of storage slots
s.originStorage[key] = val
}
s.pendingStorage = make(Storage)
}
// commit obtains the account changes (metadata, storage slots, code) caused by
// state execution along with the dirty storage trie nodes.
//
// Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb.
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
// commit the account metadata changes
op := &accountUpdate{
address: s.address,
data: types.SlimAccountRLP(s.data),
}
if s.origin != nil {
op.origin = types.SlimAccountRLP(*s.origin)
}
// commit the contract code if it's modified
if s.dirtyCode {
op.code = &contractCode{
hash: common.BytesToHash(s.CodeHash()),
blob: s.code,
}
s.dirtyCode = false // reset the dirty flag
}
// Commit storage changes and the associated storage trie
s.commitStorage(op)
if len(op.storages) == 0 {
// nothing changed, don't bother to commit the trie
s.origin = s.data.Copy()
return op, nil, nil
}
root, nodes, err := s.trie.Commit(false)
if err != nil {
return nil, err
return nil, nil, err
}
s.data.Root = root
// Update original account data after commit
s.origin = s.data.Copy()
return nodes, nil
return op, nodes, nil
}
// AddBalance adds amount to s's balance.
@@ -453,22 +511,23 @@ func (s *stateObject) setBalance(amount *uint256.Int) {
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{
db: db,
address: s.address,
addrHash: s.addrHash,
origin: s.origin,
data: s.data,
db: db,
address: s.address,
addrHash: s.addrHash,
origin: s.origin,
data: s.data,
code: s.code,
originStorage: s.originStorage.Copy(),
pendingStorage: s.pendingStorage.Copy(),
dirtyStorage: s.dirtyStorage.Copy(),
uncommittedStorage: s.uncommittedStorage.Copy(),
dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed,
newContract: s.newContract,
}
if s.trie != nil {
obj.trie = db.db.CopyTrie(s.trie)
}
obj.code = s.code
obj.dirtyStorage = s.dirtyStorage.Copy()
obj.originStorage = s.originStorage.Copy()
obj.pendingStorage = s.pendingStorage.Copy()
obj.selfDestructed = s.selfDestructed
obj.dirtyCode = s.dirtyCode
obj.deleted = s.deleted
return obj
}
@@ -483,7 +542,7 @@ func (s *stateObject) Address() common.Address {
// Code returns the contract code associated with this object, if any.
func (s *stateObject) Code() []byte {
if s.code != nil {
if len(s.code) != 0 {
return s.code
}
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
@@ -501,7 +560,7 @@ func (s *stateObject) Code() []byte {
// or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently.
func (s *stateObject) CodeSize() int {
if s.code != nil {
if len(s.code) != 0 {
return len(s.code)
}
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {

View File

@@ -194,106 +194,20 @@ func TestSnapshotEmpty(t *testing.T) {
s.state.RevertToSnapshot(s.state.Snapshot())
}
func TestSnapshot2(t *testing.T) {
func TestCreateObjectRevert(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
addr := common.BytesToAddress([]byte("so0"))
snap := state.Snapshot()
stateobjaddr0 := common.BytesToAddress([]byte("so0"))
stateobjaddr1 := common.BytesToAddress([]byte("so1"))
var storageaddr common.Hash
data0 := common.BytesToHash([]byte{17})
data1 := common.BytesToHash([]byte{18})
state.SetState(stateobjaddr0, storageaddr, data0)
state.SetState(stateobjaddr1, storageaddr, data1)
// db, trie are already non-empty values
so0 := state.getStateObject(stateobjaddr0)
state.CreateAccount(addr)
so0 := state.getStateObject(addr)
so0.SetBalance(uint256.NewInt(42), tracing.BalanceChangeUnspecified)
so0.SetNonce(43)
so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'})
so0.selfDestructed = false
so0.deleted = false
state.setStateObject(so0)
root, _ := state.Commit(0, false)
state, _ = New(root, state.db, state.snaps)
// and one with deleted == true
so1 := state.getStateObject(stateobjaddr1)
so1.SetBalance(uint256.NewInt(52), tracing.BalanceChangeUnspecified)
so1.SetNonce(53)
so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'})
so1.selfDestructed = true
so1.deleted = true
state.setStateObject(so1)
so1 = state.getStateObject(stateobjaddr1)
if so1 != nil {
t.Fatalf("deleted object not nil when getting")
}
snapshot := state.Snapshot()
state.RevertToSnapshot(snapshot)
so0Restored := state.getStateObject(stateobjaddr0)
// Update lazily-loaded values before comparing.
so0Restored.GetState(storageaddr)
so0Restored.Code()
// non-deleted is equal (restored)
compareStateObjects(so0Restored, so0, t)
// deleted should be nil, both before and after restore of state copy
so1Restored := state.getStateObject(stateobjaddr1)
if so1Restored != nil {
t.Fatalf("deleted object not nil after restoring snapshot: %+v", so1Restored)
}
}
func compareStateObjects(so0, so1 *stateObject, t *testing.T) {
if so0.Address() != so1.Address() {
t.Fatalf("Address mismatch: have %v, want %v", so0.address, so1.address)
}
if so0.Balance().Cmp(so1.Balance()) != 0 {
t.Fatalf("Balance mismatch: have %v, want %v", so0.Balance(), so1.Balance())
}
if so0.Nonce() != so1.Nonce() {
t.Fatalf("Nonce mismatch: have %v, want %v", so0.Nonce(), so1.Nonce())
}
if so0.data.Root != so1.data.Root {
t.Errorf("Root mismatch: have %x, want %x", so0.data.Root[:], so1.data.Root[:])
}
if !bytes.Equal(so0.CodeHash(), so1.CodeHash()) {
t.Fatalf("CodeHash mismatch: have %v, want %v", so0.CodeHash(), so1.CodeHash())
}
if !bytes.Equal(so0.code, so1.code) {
t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code)
}
if len(so1.dirtyStorage) != len(so0.dirtyStorage) {
t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage))
}
for k, v := range so1.dirtyStorage {
if so0.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v)
}
}
for k, v := range so0.dirtyStorage {
if so1.dirtyStorage[k] != v {
t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v)
}
}
if len(so1.originStorage) != len(so0.originStorage) {
t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage))
}
for k, v := range so1.originStorage {
if so0.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v)
}
}
for k, v := range so0.originStorage {
if so1.originStorage[k] != v {
t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v)
}
state.RevertToSnapshot(snap)
if state.Exist(addr) {
t.Error("Unexpected account after revert")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
"github.com/holiman/uint256"
@@ -96,7 +95,9 @@ func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction
{
name: "CreateAccount",
fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr)
if !s.Exist(addr) {
s.CreateAccount(addr)
}
},
},
{
@@ -178,9 +179,21 @@ func (test *stateTest) run() bool {
roots []common.Hash
accountList []map[common.Address][]byte
storageList []map[common.Address]map[common.Hash][]byte
onCommit = func(states *triestate.Set) {
accountList = append(accountList, copySet(states.Accounts))
storageList = append(storageList, copy2DSet(states.Storages))
copyUpdate = func(update *stateUpdate) {
accounts := make(map[common.Address][]byte, len(update.accountsOrigin))
for key, val := range update.accountsOrigin {
accounts[key] = common.CopyBytes(val)
}
accountList = append(accountList, accounts)
storages := make(map[common.Address]map[common.Hash][]byte, len(update.storagesOrigin))
for addr, subset := range update.storagesOrigin {
storages[addr] = make(map[common.Hash][]byte, len(subset))
for key, val := range subset {
storages[addr][key] = common.CopyBytes(val)
}
}
storageList = append(storageList, storages)
}
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
@@ -208,8 +221,6 @@ func (test *stateTest) run() bool {
if err != nil {
panic(err)
}
state.onCommit = onCommit
for i, action := range actions {
if i%test.chunk == 0 && i != 0 {
if byzantium {
@@ -225,14 +236,15 @@ func (test *stateTest) run() bool {
} else {
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
}
nroot, err := state.Commit(0, true) // call commit at the block boundary
ret, err := state.commitAndFlush(0, true) // call commit at the block boundary
if err != nil {
panic(err)
}
if nroot == root {
return true // filter out non-change state transition
if ret.empty() {
return true
}
roots = append(roots, nroot)
copyUpdate(ret)
roots = append(roots, ret.root)
}
for i := 0; i < len(test.actions); i++ {
root := types.EmptyRootHash

View File

@@ -21,9 +21,11 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
"math/rand"
"reflect"
"slices"
"strings"
"sync"
"testing"
@@ -225,6 +227,78 @@ func TestCopy(t *testing.T) {
}
}
// TestCopyWithDirtyJournal tests if Copy can correct create a equal copied
// stateDB with dirty journal present.
func TestCopyWithDirtyJournal(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
root, _ := orig.Commit(0, true)
orig, _ = New(root, db, nil)
// modify all in memory without finalizing
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.SubBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
orig.updateStateObject(obj)
}
cpy := orig.Copy()
orig.Finalise(true)
for i := byte(0); i < 255; i++ {
root := orig.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
cpy.Finalise(true)
for i := byte(0); i < 255; i++ {
root := cpy.GetStorageRoot(common.BytesToAddress([]byte{i}))
if root != (common.Hash{}) {
t.Errorf("Unexpected storage root %x", root)
}
}
if cpy.IntermediateRoot(true) != orig.IntermediateRoot(true) {
t.Error("State is not equal after copy")
}
}
// TestCopyObjectState creates an original state, S1, and makes a copy S2.
// It then proceeds to make changes to S1. Those changes are _not_ supposed
// to affect S2. This test checks that the copy properly deep-copies the objectstate
func TestCopyObjectState(t *testing.T) {
db := NewDatabase(rawdb.NewMemoryDatabase())
orig, _ := New(types.EmptyRootHash, db, nil)
// Fill up the initial states
for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)), tracing.BalanceChangeUnspecified)
obj.data.Root = common.HexToHash("0xdeadbeef")
orig.updateStateObject(obj)
}
orig.Finalise(true)
cpy := orig.Copy()
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want)
}
}
orig.Commit(0, true)
for _, op := range cpy.mutations {
if have, want := op.applied, false; have != want {
t.Fatalf("Error: original state affected copy, have %v want %v", have, want)
}
}
}
func TestSnapshotRandom(t *testing.T) {
config := &quick.Config{MaxCount: 1000}
err := quick.Check((*snapshotTest).run, config)
@@ -308,7 +382,30 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
{
name: "CreateAccount",
fn: func(a testAction, s *StateDB) {
s.CreateAccount(addr)
if !s.Exist(addr) {
s.CreateAccount(addr)
}
},
},
{
name: "CreateContract",
fn: func(a testAction, s *StateDB) {
if !s.Exist(addr) {
s.CreateAccount(addr)
}
contractHash := s.GetCodeHash(addr)
emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash
storageRoot := s.GetStorageRoot(addr)
emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash
if s.GetNonce(addr) == 0 && emptyCode && emptyStorage {
s.CreateContract(addr)
// We also set some code here, to prevent the
// CreateContract action from being performed twice in a row,
// which would cause a difference in state when unrolling
// the journal. (CreateContact assumes created was false prior to
// invocation, and the journal rollback sets it to false).
s.SetCode(addr, []byte{1})
}
},
},
{
@@ -462,10 +559,14 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H
if err != nil {
return err
}
it := trie.NewIterator(trieIt)
var (
it = trie.NewIterator(trieIt)
visited = make(map[common.Hash]bool)
)
for it.Next() {
key := common.BytesToHash(s.trie.GetKey(it.Key))
visited[key] = true
if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) {
return nil
@@ -505,6 +606,10 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr))
checkeq("GetCodeHash", state.GetCodeHash(addr), checkstate.GetCodeHash(addr))
checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr))
// Check newContract-flag
if obj := state.getStateObject(addr); obj != nil {
checkeq("IsNewContract", obj.newContract, checkstate.getStateObject(addr).newContract)
}
// Check storage.
if obj := state.getStateObject(addr); obj != nil {
forEachStorage(state, addr, func(key, value common.Hash) bool {
@@ -513,12 +618,49 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
forEachStorage(checkstate, addr, func(key, value common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
})
other := checkstate.getStateObject(addr)
// Check dirty storage which is not in trie
if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) {
print := func(dirty map[common.Hash]common.Hash) string {
var keys []common.Hash
out := new(strings.Builder)
for key := range dirty {
keys = append(keys, key)
}
slices.SortFunc(keys, common.Hash.Cmp)
for i, key := range keys {
fmt.Fprintf(out, " %d. %v %v\n", i, key, dirty[key])
}
return out.String()
}
return fmt.Errorf("dirty storage err, have\n%v\nwant\n%v",
print(obj.dirtyStorage),
print(other.dirtyStorage))
}
}
// Check transient storage.
{
have := state.transientStorage
want := checkstate.transientStorage
eq := maps.EqualFunc(have, want,
func(a Storage, b Storage) bool {
return maps.Equal(a, b)
})
if !eq {
return fmt.Errorf("transient storage differs ,have\n%v\nwant\n%v",
have.PrettyPrint(),
want.PrettyPrint())
}
}
if err != nil {
return err
}
}
if !checkstate.accessList.Equal(state.accessList) { // Check access lists
return fmt.Errorf("AccessLists are wrong, have \n%v\nwant\n%v",
checkstate.accessList.PrettyPrint(),
state.accessList.PrettyPrint())
}
if state.GetRefund() != checkstate.GetRefund() {
return fmt.Errorf("got GetRefund() == %d, want GetRefund() == %d",
state.GetRefund(), checkstate.GetRefund())
@@ -527,6 +669,23 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
return fmt.Errorf("got GetLogs(common.Hash{}) == %v, want GetLogs(common.Hash{}) == %v",
state.GetLogs(common.Hash{}, 0, common.Hash{}), checkstate.GetLogs(common.Hash{}, 0, common.Hash{}))
}
if !maps.Equal(state.journal.dirties, checkstate.journal.dirties) {
getKeys := func(dirty map[common.Address]int) string {
var keys []common.Address
out := new(strings.Builder)
for key := range dirty {
keys = append(keys, key)
}
slices.SortFunc(keys, common.Address.Cmp)
for i, key := range keys {
fmt.Fprintf(out, " %d. %v\n", i, key)
}
return out.String()
}
have := getKeys(state.journal.dirties)
want := getKeys(checkstate.journal.dirties)
return fmt.Errorf("dirty-journal set mismatch.\nhave:\n%v\nwant:\n%v\n", have, want)
}
return nil
}
@@ -709,18 +868,19 @@ func TestCopyCopyCommitCopy(t *testing.T) {
}
}
// TestCommitCopy tests the copy from a committed state is not functional.
// TestCommitCopy tests the copy from a committed state is not fully functional.
func TestCommitCopy(t *testing.T) {
state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil)
db := NewDatabase(rawdb.NewMemoryDatabase())
state, _ := New(types.EmptyRootHash, db, nil)
// Create an account and check if the retrieved balance is correct
addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe")
skey := common.HexToHash("aaa")
sval := common.HexToHash("bbb")
skey1, skey2 := common.HexToHash("a1"), common.HexToHash("a2")
sval1, sval2 := common.HexToHash("b1"), common.HexToHash("b2")
state.SetBalance(addr, uint256.NewInt(42), tracing.BalanceChangeUnspecified) // Change the account trie
state.SetCode(addr, []byte("hello")) // Change an external metadata
state.SetState(addr, skey, sval) // Change the storage trie
state.SetState(addr, skey1, sval1) // Change the storage trie
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42)
@@ -728,25 +888,38 @@ func TestCommitCopy(t *testing.T) {
if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello"))
}
if val := state.GetState(addr, skey); val != sval {
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval)
if val := state.GetState(addr, skey1); val != sval1 {
t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval1)
}
if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) {
if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
}
// Copy the committed state database, the copied one is not functional.
state.Commit(0, true)
root, _ := state.Commit(0, true)
state, _ = New(root, db, nil)
state.SetState(addr, skey2, sval2)
state.Commit(1, true)
// Copy the committed state database, the copied one is not fully functional.
copied := state.Copy()
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(0)) != 0 {
if balance := copied.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("unexpected balance: have %v", balance)
}
if code := copied.GetCode(addr); code != nil {
if code := copied.GetCode(addr); !bytes.Equal(code, []byte("hello")) {
t.Fatalf("unexpected code: have %x", code)
}
if val := copied.GetState(addr, skey); val != (common.Hash{}) {
// Miss slots because of non-functional trie after commit
if val := copied.GetState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey1); val != (common.Hash{}) {
t.Fatalf("unexpected storage slot: have %x", val)
}
if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) {
// Slots cached in the stateDB, available after commit
if val := copied.GetState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", sval1)
}
if val := copied.GetCommittedState(addr, skey2); val != sval2 {
t.Fatalf("unexpected storage slot: have %x", val)
}
if !errors.Is(copied.Error(), trie.ErrCommitted) {
@@ -1103,40 +1276,6 @@ func TestStateDBTransientStorage(t *testing.T) {
}
}
func TestResetObject(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
state, _ = New(types.EmptyRootHash, db, snaps)
addr = common.HexToAddress("0x1")
slotA = common.HexToHash("0x1")
slotB = common.HexToHash("0x2")
)
// Initialize account with balance and storage in first transaction.
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotA, common.BytesToHash([]byte{0x1}))
state.IntermediateRoot(true)
// Reset account and mutate balance and storages
state.CreateAccount(addr)
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
state.SetState(addr, slotB, common.BytesToHash([]byte{0x2}))
root, _ := state.Commit(0, true)
// Ensure the original account is wiped properly
snap := snaps.Snapshot(root)
slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes()))
if len(slot) != 0 {
t.Fatalf("Unexpected storage slot")
}
slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes()))
if !bytes.Equal(slot, []byte{0x2}) {
t.Fatalf("Unexpected storage slot value %v", slot)
}
}
func TestDeleteStorage(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
@@ -1190,3 +1329,47 @@ func TestDeleteStorage(t *testing.T) {
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
}
}
func TestStorageDirtiness(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
db = NewDatabaseWithNodeDB(disk, tdb)
state, _ = New(types.EmptyRootHash, db, nil)
addr = common.HexToAddress("0x1")
checkDirty = func(key common.Hash, value common.Hash, dirty bool) {
obj := state.getStateObject(addr)
v, exist := obj.dirtyStorage[key]
if exist != dirty {
t.Fatalf("Unexpected dirty marker, want: %t, got: %t", dirty, exist)
}
if v != value {
t.Fatalf("Unexpected storage slot, want: %t, got: %t", value, v)
}
}
)
state.CreateAccount(addr)
// the storage change is noop, no dirty marker
state.SetState(addr, common.Hash{0x1}, common.Hash{})
checkDirty(common.Hash{0x1}, common.Hash{}, false)
// the storage change is valid, dirty marker is expected
snap := state.Snapshot()
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
// the storage change is reverted, dirtiness should be revoked
state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{}, false)
// the storage is reset back to its original value, dirtiness should be revoked
state.SetState(addr, common.Hash{0x1}, common.Hash{0x1})
snap = state.Snapshot()
state.SetState(addr, common.Hash{0x1}, common.Hash{})
checkDirty(common.Hash{0x1}, common.Hash{}, false)
// the storage change is reverted, dirty value should be set back
state.RevertToSnapshot(snap)
checkDirty(common.Hash{0x1}, common.Hash{0x1}, true)
}

133
core/state/stateupdate.go Normal file
View File

@@ -0,0 +1,133 @@
// Copyright 2024 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package state
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie/trienode"
)
// contractCode represents a contract code with associated metadata.
type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the contract code.
blob []byte // blob is the binary representation of the contract code.
}
// accountDelete represents an operation for deleting an Ethereum account.
type accountDelete struct {
address common.Address // address is the unique account identifier
origin []byte // origin is the original value of account data in slim-RLP encoding.
storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
}
// accountUpdate represents an operation for updating an Ethereum account.
type accountUpdate struct {
address common.Address // address is the unique account identifier
data []byte // data is the slim-RLP encoded account data.
origin []byte // origin is the original value of account data in slim-RLP encoding.
code *contractCode // code represents mutated contract code; nil means it's not modified.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
storagesOrigin map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in prefix-zero-trimmed RLP format.
}
// stateUpdate represents the difference between two states resulting from state
// execution. It contains information about mutated contract codes, accounts,
// and storage slots, along with their original values.
type stateUpdate struct {
originRoot common.Hash // hash of the state before applying mutation
root common.Hash // hash of the state after applying mutation
destructs map[common.Hash]struct{} // destructs contains the list of destructed accounts
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
storages map[common.Hash]map[common.Hash][]byte // storages stores mutated slots in 'prefix-zero-trimmed' RLP format
storagesOrigin map[common.Address]map[common.Hash][]byte // storagesOrigin stores the original values of mutated slots in 'prefix-zero-trimmed' RLP format
codes map[common.Address]contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
}
// empty returns a flag indicating the state transition is empty or not.
func (sc *stateUpdate) empty() bool {
return sc.originRoot == sc.root
}
// newStateUpdate constructs a state update object, representing the differences
// between two states by performing state execution. It aggregates the given
// account deletions and account updates to form a comprehensive state update.
func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *stateUpdate {
var (
destructs = make(map[common.Hash]struct{})
accounts = make(map[common.Hash][]byte)
accountsOrigin = make(map[common.Address][]byte)
storages = make(map[common.Hash]map[common.Hash][]byte)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
codes = make(map[common.Address]contractCode)
)
// Due to the fact that some accounts could be destructed and resurrected
// within the same block, the deletions must be aggregated first.
for addrHash, op := range deletes {
addr := op.address
destructs[addrHash] = struct{}{}
accountsOrigin[addr] = op.origin
if len(op.storagesOrigin) > 0 {
storagesOrigin[addr] = op.storagesOrigin
}
}
// Aggregate account updates then.
for addrHash, op := range updates {
// Aggregate dirty contract codes if they are available.
addr := op.address
if op.code != nil {
codes[addr] = *op.code
}
// Aggregate the account changes. The original account value will only
// be tracked if it's not present yet.
accounts[addrHash] = op.data
if _, found := accountsOrigin[addr]; !found {
accountsOrigin[addr] = op.origin
}
// Aggregate the storage changes. The original storage slot value will
// only be tracked if it's not present yet.
if len(op.storages) > 0 {
storages[addrHash] = op.storages
}
if len(op.storagesOrigin) > 0 {
origin := storagesOrigin[addr]
if origin == nil {
storagesOrigin[addr] = op.storagesOrigin
continue
}
for key, slot := range op.storagesOrigin {
if _, found := origin[key]; !found {
origin[key] = slot
}
}
storagesOrigin[addr] = origin
}
}
return &stateUpdate{
originRoot: types.TrieRootHash(originRoot),
root: types.TrieRootHash(root),
destructs: destructs,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,
storagesOrigin: storagesOrigin,
codes: codes,
nodes: nodes,
}
}

View File

@@ -17,6 +17,10 @@
package state
import (
"fmt"
"slices"
"strings"
"github.com/ethereum/go-ethereum/common"
)
@@ -30,10 +34,19 @@ func newTransientStorage() transientStorage {
// Set sets the transient-storage `value` for `key` at the given `addr`.
func (t transientStorage) Set(addr common.Address, key, value common.Hash) {
if _, ok := t[addr]; !ok {
t[addr] = make(Storage)
if value == (common.Hash{}) { // this is a 'delete'
if _, ok := t[addr]; ok {
delete(t[addr], key)
if len(t[addr]) == 0 {
delete(t, addr)
}
}
} else {
if _, ok := t[addr]; !ok {
t[addr] = make(Storage)
}
t[addr][key] = value
}
t[addr][key] = value
}
// Get gets the transient storage for `key` at the given `addr`.
@@ -53,3 +66,27 @@ func (t transientStorage) Copy() transientStorage {
}
return storage
}
// PrettyPrint prints the contents of the access list in a human-readable form
func (t transientStorage) PrettyPrint() string {
out := new(strings.Builder)
var sortedAddrs []common.Address
for addr := range t {
sortedAddrs = append(sortedAddrs, addr)
slices.SortFunc(sortedAddrs, common.Address.Cmp)
}
for _, addr := range sortedAddrs {
fmt.Fprintf(out, "%#x:", addr)
var sortedKeys []common.Hash
storage := t[addr]
for key := range storage {
sortedKeys = append(sortedKeys, key)
}
slices.SortFunc(sortedKeys, common.Hash.Cmp)
for _, key := range sortedKeys {
fmt.Fprintf(out, " %X : %X\n", key, storage[key])
}
}
return out.String()
}

View File

@@ -17,6 +17,7 @@
package state
import (
"errors"
"sync"
"github.com/ethereum/go-ethereum/common"
@@ -27,6 +28,10 @@ import (
var (
// triePrefetchMetricsPrefix is the prefix under which to publish the metrics.
triePrefetchMetricsPrefix = "trie/prefetch/"
// errTerminated is returned if a fetcher is attempted to be operated after it
// has already terminated.
errTerminated = errors.New("fetcher is already terminated")
)
// triePrefetcher is an active prefetcher, which receives accounts or storage
@@ -37,160 +42,126 @@ var (
type triePrefetcher struct {
db Database // Database to fetch trie nodes through
root common.Hash // Root hash of the account trie for metrics
fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies.
fetchers map[string]*subfetcher // Subfetchers for each trie
term chan struct{} // Channel to signal interruption
deliveryMissMeter metrics.Meter
accountLoadMeter metrics.Meter
accountDupMeter metrics.Meter
accountSkipMeter metrics.Meter
accountWasteMeter metrics.Meter
storageLoadMeter metrics.Meter
storageDupMeter metrics.Meter
storageSkipMeter metrics.Meter
storageWasteMeter metrics.Meter
}
func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher {
prefix := triePrefetchMetricsPrefix + namespace
p := &triePrefetcher{
return &triePrefetcher{
db: db,
root: root,
fetchers: make(map[string]*subfetcher), // Active prefetchers use the fetchers map
term: make(chan struct{}),
deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil),
accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil),
accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil),
accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil),
accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil),
storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil),
storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil),
storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil),
storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil),
}
return p
}
// close iterates over all the subfetchers, aborts any that were left spinning
// and reports the stats to the metrics subsystem.
func (p *triePrefetcher) close() {
// terminate iterates over all the subfetchers and issues a termination request
// to all of them. Depending on the async parameter, the method will either block
// until all subfetchers spin down, or return immediately.
func (p *triePrefetcher) terminate(async bool) {
// Short circuit if the fetcher is already closed
select {
case <-p.term:
return
default:
}
// Terminate all sub-fetchers, sync or async, depending on the request
for _, fetcher := range p.fetchers {
fetcher.abort() // safe to do multiple times
if metrics.Enabled {
if fetcher.root == p.root {
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
p.accountDupMeter.Mark(int64(fetcher.dups))
p.accountSkipMeter.Mark(int64(len(fetcher.tasks)))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
}
p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
} else {
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
p.storageDupMeter.Mark(int64(fetcher.dups))
p.storageSkipMeter.Mark(int64(len(fetcher.tasks)))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
}
p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
}
}
fetcher.terminate(async)
}
// Clear out all fetchers (will crash on a second call, deliberate)
p.fetchers = nil
close(p.term)
}
// copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data
// already loaded will be copied over, but no goroutines will be started. This
// is mostly used in the miner which creates a copy of it's actively mutated
// state to be sealed while it may further mutate the state.
func (p *triePrefetcher) copy() *triePrefetcher {
copy := &triePrefetcher{
db: p.db,
root: p.root,
fetches: make(map[string]Trie), // Active prefetchers use the fetches map
deliveryMissMeter: p.deliveryMissMeter,
accountLoadMeter: p.accountLoadMeter,
accountDupMeter: p.accountDupMeter,
accountSkipMeter: p.accountSkipMeter,
accountWasteMeter: p.accountWasteMeter,
storageLoadMeter: p.storageLoadMeter,
storageDupMeter: p.storageDupMeter,
storageSkipMeter: p.storageSkipMeter,
storageWasteMeter: p.storageWasteMeter,
}
// If the prefetcher is already a copy, duplicate the data
if p.fetches != nil {
for root, fetch := range p.fetches {
if fetch == nil {
continue
}
copy.fetches[root] = p.db.CopyTrie(fetch)
}
return copy
}
// Otherwise we're copying an active fetcher, retrieve the current states
for id, fetcher := range p.fetchers {
copy.fetches[id] = fetcher.peek()
}
return copy
}
// prefetch schedules a batch of trie items to prefetch.
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) {
// If the prefetcher is an inactive one, bail out
if p.fetches != nil {
// report aggregates the pre-fetching and usage metrics and reports them.
func (p *triePrefetcher) report() {
if !metrics.Enabled {
return
}
// Active fetcher, schedule the retrievals
for _, fetcher := range p.fetchers {
fetcher.wait() // ensure the fetcher's idle before poking in its internals
if fetcher.root == p.root {
p.accountLoadMeter.Mark(int64(len(fetcher.seen)))
p.accountDupMeter.Mark(int64(fetcher.dups))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
}
p.accountWasteMeter.Mark(int64(len(fetcher.seen)))
} else {
p.storageLoadMeter.Mark(int64(len(fetcher.seen)))
p.storageDupMeter.Mark(int64(fetcher.dups))
for _, key := range fetcher.used {
delete(fetcher.seen, string(key))
}
p.storageWasteMeter.Mark(int64(len(fetcher.seen)))
}
}
}
// prefetch schedules a batch of trie items to prefetch. After the prefetcher is
// closed, all the following tasks scheduled will not be executed and an error
// will be returned.
//
// prefetch is called from two locations:
//
// 1. Finalize of the state-objects storage roots. This happens at the end
// of every transaction, meaning that if several transactions touches
// upon the same contract, the parameters invoking this method may be
// repeated.
// 2. Finalize of the main account trie. This happens only once per block.
func (p *triePrefetcher) prefetch(owner common.Hash, root common.Hash, addr common.Address, keys [][]byte) error {
// Ensure the subfetcher is still alive
select {
case <-p.term:
return errTerminated
default:
}
id := p.trieID(owner, root)
fetcher := p.fetchers[id]
if fetcher == nil {
fetcher = newSubfetcher(p.db, p.root, owner, root, addr)
p.fetchers[id] = fetcher
}
fetcher.schedule(keys)
return fetcher.schedule(keys)
}
// trie returns the trie matching the root hash, or nil if the prefetcher doesn't
// have it.
// trie returns the trie matching the root hash, blocking until the fetcher of
// the given trie terminates. If no fetcher exists for the request, nil will be
// returned.
func (p *triePrefetcher) trie(owner common.Hash, root common.Hash) Trie {
// If the prefetcher is inactive, return from existing deep copies
id := p.trieID(owner, root)
if p.fetches != nil {
trie := p.fetches[id]
if trie == nil {
p.deliveryMissMeter.Mark(1)
return nil
}
return p.db.CopyTrie(trie)
}
// Otherwise the prefetcher is active, bail if no trie was prefetched for this root
fetcher := p.fetchers[id]
// Bail if no trie was prefetched for this root
fetcher := p.fetchers[p.trieID(owner, root)]
if fetcher == nil {
log.Error("Prefetcher missed to load trie", "owner", owner, "root", root)
p.deliveryMissMeter.Mark(1)
return nil
}
// Interrupt the prefetcher if it's by any chance still running and return
// a copy of any pre-loaded trie.
fetcher.abort() // safe to do multiple times
trie := fetcher.peek()
if trie == nil {
p.deliveryMissMeter.Mark(1)
return nil
}
return trie
// Subfetcher exists, retrieve its trie
return fetcher.peek()
}
// used marks a batch of state items used to allow creating statistics as to
// how useful or wasteful the prefetcher is.
// how useful or wasteful the fetcher is.
func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte) {
if fetcher := p.fetchers[p.trieID(owner, root)]; fetcher != nil {
fetcher.wait() // ensure the fetcher's idle before poking in its internals
fetcher.used = used
}
}
@@ -218,10 +189,9 @@ type subfetcher struct {
tasks [][]byte // Items queued up for retrieval
lock sync.Mutex // Lock protecting the task queue
wake chan struct{} // Wake channel if a new task is scheduled
stop chan struct{} // Channel to interrupt processing
term chan struct{} // Channel to signal interruption
copy chan chan Trie // Channel to request a copy of the current trie
wake chan struct{} // Wake channel if a new task is scheduled
stop chan struct{} // Channel to interrupt processing
term chan struct{} // Channel to signal interruption
seen map[string]struct{} // Tracks the entries already loaded
dups int // Number of duplicate preload tasks
@@ -240,7 +210,6 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
wake: make(chan struct{}, 1),
stop: make(chan struct{}),
term: make(chan struct{}),
copy: make(chan chan Trie),
seen: make(map[string]struct{}),
}
go sf.loop()
@@ -248,50 +217,61 @@ func newSubfetcher(db Database, state common.Hash, owner common.Hash, root commo
}
// schedule adds a batch of trie keys to the queue to prefetch.
func (sf *subfetcher) schedule(keys [][]byte) {
func (sf *subfetcher) schedule(keys [][]byte) error {
// Ensure the subfetcher is still alive
select {
case <-sf.term:
return errTerminated
default:
}
// Append the tasks to the current queue
sf.lock.Lock()
sf.tasks = append(sf.tasks, keys...)
sf.lock.Unlock()
// Notify the prefetcher, it's fine if it's already terminated
// Notify the background thread to execute scheduled tasks
select {
case sf.wake <- struct{}{}:
// Wake signal sent
default:
// Wake signal not sent as a previous one is already queued
}
return nil
}
// peek tries to retrieve a deep copy of the fetcher's trie in whatever form it
// is currently.
// wait blocks until the subfetcher terminates. This method is used to block on
// an async termination before accessing internal fields from the fetcher.
func (sf *subfetcher) wait() {
<-sf.term
}
// peek retrieves the fetcher's trie, populated with any pre-fetched data. The
// returned trie will be a shallow copy, so modifying it will break subsequent
// peeks for the original data. The method will block until all the scheduled
// data has been loaded and the fethcer terminated.
func (sf *subfetcher) peek() Trie {
ch := make(chan Trie)
select {
case sf.copy <- ch:
// Subfetcher still alive, return copy from it
return <-ch
case <-sf.term:
// Subfetcher already terminated, return a copy directly
if sf.trie == nil {
return nil
}
return sf.db.CopyTrie(sf.trie)
}
// Block until the fetcher terminates, then retrieve the trie
sf.wait()
return sf.trie
}
// abort interrupts the subfetcher immediately. It is safe to call abort multiple
// times but it is not thread safe.
func (sf *subfetcher) abort() {
// terminate requests the subfetcher to stop accepting new tasks and spin down
// as soon as everything is loaded. Depending on the async parameter, the method
// will either block until all disk loads finish or return immediately.
func (sf *subfetcher) terminate(async bool) {
select {
case <-sf.stop:
default:
close(sf.stop)
}
if async {
return
}
<-sf.term
}
// loop waits for new tasks to be scheduled and keeps loading them until it runs
// out of tasks or its underlying trie is retrieved for committing.
// loop loads newly-scheduled trie tasks as they are received and loads them, stopping
// when requested.
func (sf *subfetcher) loop() {
// No matter how the loop stops, signal anyone waiting that it's terminated
defer close(sf.term)
@@ -305,8 +285,6 @@ func (sf *subfetcher) loop() {
}
sf.trie = trie
} else {
// The trie argument can be nil as verkle doesn't support prefetching
// yet. TODO FIX IT(rjl493456442), otherwise code will panic here.
trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil)
if err != nil {
log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err)
@@ -318,48 +296,38 @@ func (sf *subfetcher) loop() {
for {
select {
case <-sf.wake:
// Subfetcher was woken up, retrieve any tasks to avoid spinning the lock
// Execute all remaining tasks in a single run
sf.lock.Lock()
tasks := sf.tasks
sf.tasks = nil
sf.lock.Unlock()
// Prefetch any tasks until the loop is interrupted
for i, task := range tasks {
select {
case <-sf.stop:
// If termination is requested, add any leftover back and return
sf.lock.Lock()
sf.tasks = append(sf.tasks, tasks[i:]...)
sf.lock.Unlock()
return
case ch := <-sf.copy:
// Somebody wants a copy of the current trie, grant them
ch <- sf.db.CopyTrie(sf.trie)
default:
// No termination request yet, prefetch the next entry
if _, ok := sf.seen[string(task)]; ok {
sf.dups++
} else {
if len(task) == common.AddressLength {
sf.trie.GetAccount(common.BytesToAddress(task))
} else {
sf.trie.GetStorage(sf.addr, task)
}
sf.seen[string(task)] = struct{}{}
}
for _, task := range tasks {
if _, ok := sf.seen[string(task)]; ok {
sf.dups++
continue
}
if len(task) == common.AddressLength {
sf.trie.GetAccount(common.BytesToAddress(task))
} else {
sf.trie.GetStorage(sf.addr, task)
}
sf.seen[string(task)] = struct{}{}
}
case ch := <-sf.copy:
// Somebody wants a copy of the current trie, grant them
ch <- sf.db.CopyTrie(sf.trie)
case <-sf.stop:
// Termination is requested, abort and leave remaining tasks
return
// Termination is requested, abort if no more tasks are pending. If
// there are some, exhaust them first.
sf.lock.Lock()
done := sf.tasks == nil
sf.lock.Unlock()
if done {
return
}
// Some tasks are pending, loop and pick them up (that wake branch
// will be selected eventually, whilst stop remains closed to this
// branch will also run afterwards).
}
}
}

View File

@@ -19,7 +19,6 @@ package state
import (
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
@@ -46,68 +45,20 @@ func filledStateDB() *StateDB {
return state
}
func TestCopyAndClose(t *testing.T) {
func TestUseAfterTerminate(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
time.Sleep(1 * time.Second)
a := prefetcher.trie(common.Hash{}, db.originalRoot)
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
b := prefetcher.trie(common.Hash{}, db.originalRoot)
cpy := prefetcher.copy()
cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
cpy.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
c := cpy.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
cpy2 := cpy.copy()
cpy2.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
d := cpy2.trie(common.Hash{}, db.originalRoot)
cpy.close()
cpy2.close()
if a.Hash() != b.Hash() || a.Hash() != c.Hash() || a.Hash() != d.Hash() {
t.Fatalf("Invalid trie, hashes should be equal: %v %v %v %v", a.Hash(), b.Hash(), c.Hash(), d.Hash())
}
}
func TestUseAfterClose(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
a := prefetcher.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
b := prefetcher.trie(common.Hash{}, db.originalRoot)
if a == nil {
t.Fatal("Prefetching before close should not return nil")
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err != nil {
t.Errorf("Prefetch failed before terminate: %v", err)
}
if b != nil {
t.Fatal("Trie after close should return nil")
}
}
prefetcher.terminate(false)
func TestCopyClose(t *testing.T) {
db := filledStateDB()
prefetcher := newTriePrefetcher(db.db, db.originalRoot, "")
skey := common.HexToHash("aaa")
prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()})
cpy := prefetcher.copy()
a := prefetcher.trie(common.Hash{}, db.originalRoot)
b := cpy.trie(common.Hash{}, db.originalRoot)
prefetcher.close()
c := prefetcher.trie(common.Hash{}, db.originalRoot)
d := cpy.trie(common.Hash{}, db.originalRoot)
if a == nil {
t.Fatal("Prefetching before close should not return nil")
if err := prefetcher.prefetch(common.Hash{}, db.originalRoot, common.Address{}, [][]byte{skey.Bytes()}); err == nil {
t.Errorf("Prefetch succeeded after terminate: %v", err)
}
if b == nil {
t.Fatal("Copy trie should return nil")
}
if c != nil {
t.Fatal("Trie after close should return nil")
}
if d == nil {
t.Fatal("Copy trie should not return nil")
if tr := prefetcher.trie(common.Hash{}, db.originalRoot); tr == nil {
t.Errorf("Prefetcher returned nil trie after terminate")
}
}

View File

@@ -186,6 +186,13 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
// contract. This method is exported to be used in tests.
func ProcessBeaconBlockRoot(beaconRoot common.Hash, vmenv *vm.EVM, statedb *state.StateDB) {
if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallStart != nil {
vmenv.Config.Tracer.OnSystemCallStart()
}
if vmenv.Config.Tracer != nil && vmenv.Config.Tracer.OnSystemCallEnd != nil {
defer vmenv.Config.Tracer.OnSystemCallEnd()
}
// If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with
// the new root
msg := &Message{

View File

@@ -417,10 +417,11 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.ParentBeaconRoot = &beaconRoot
}
// Assemble and return the final block for sealing
body := &types.Body{Transactions: txs}
if config.IsShanghai(header.Number, header.Time) {
return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil))
body.Withdrawals = []*types.Withdrawal{}
}
return types.NewBlock(header, txs, nil, receipts, trie.NewStackTrie(nil))
return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil))
}
var (
@@ -481,7 +482,7 @@ func TestProcessVerkle(t *testing.T) {
txCost1 := params.TxGas
txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */)
blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,

View File

@@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
}
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
// Set the starting gas for the raw transaction
var gas uint64
if isContractCreation && isHomestead {
@@ -240,8 +240,9 @@ func (st *StateTransition) buyGas() error {
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
balanceCheck.Add(balanceCheck, st.msg.Value)
}
balanceCheck.Add(balanceCheck, st.msg.Value)
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
// Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap
@@ -405,6 +406,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
st.gasRemaining -= gas
if rules.IsEIP4762 {
st.evm.AccessEvents.AddTxOrigin(msg.From)
if targetAddr := msg.To; targetAddr != nil {
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
}
}
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
@@ -458,6 +467,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
}
}
return &ExecutionResult{

View File

@@ -4,6 +4,15 @@ All notable changes to the tracing interface will be documented in this file.
## [Unreleased]
There have been minor backwards-compatible changes to the tracing interface to explicitly mark the execution of **system** contracts. As of now the only system call updates the parent beacon block root as per [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788). Other system calls are being considered for the future hardfork.
### New methods
- `OnSystemCallStart()`: This hook is called when EVM starts processing a system call. Note system calls happen outside the scope of a transaction. This event will be followed by normal EVM execution events.
- `OnSystemCallEnd()`: This hook is called when EVM finishes processing a system call.
## [v1.14.0]
There has been a major breaking change in the tracing interface for custom native tracers. JS and built-in tracers are not affected by this change and tracing API methods may be used as before. This overhaul has been done as part of the new live tracing feature ([#29189](https://github.com/ethereum/go-ethereum/pull/29189)). To learn more about live tracing please refer to the [docs](https://geth.ethereum.org/docs/developers/evm-tracing/live-tracing).
**The `EVMLogger` interface which the tracers implemented has been removed.** It has been replaced by a new struct `tracing.Hooks`. `Hooks` keeps pointers to event listening functions. Internally the EVM will use these function pointers to emit events and can skip an event if the tracer has opted not to implement it. In fact this is the main reason for this change of approach. Another benefit is the ease of adding new hooks in future, and dynamically assigning event receivers.
@@ -66,4 +75,5 @@ The hooks `CaptureStart` and `CaptureEnd` have been removed. These hooks signale
- `CaptureState` -> `OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)`. `op` is of type `byte` which can be cast to `vm.OpCode` when necessary. A `*vm.ScopeContext` is not passed anymore. It is replaced by `tracing.OpContext` which offers access to the memory, stack and current contract.
- `CaptureFault` -> `OnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)`. Similar to above.
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.13.14...master
[unreleased]: https://github.com/ethereum/go-ethereum/compare/v1.14.0...master
[v1.14.0]: https://github.com/ethereum/go-ethereum/releases/tag/v1.14.0

View File

@@ -0,0 +1,37 @@
// Code generated by "stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go"; DO NOT EDIT.
package tracing
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[BalanceChangeUnspecified-0]
_ = x[BalanceIncreaseRewardMineUncle-1]
_ = x[BalanceIncreaseRewardMineBlock-2]
_ = x[BalanceIncreaseWithdrawal-3]
_ = x[BalanceIncreaseGenesisBalance-4]
_ = x[BalanceIncreaseRewardTransactionFee-5]
_ = x[BalanceDecreaseGasBuy-6]
_ = x[BalanceIncreaseGasReturn-7]
_ = x[BalanceIncreaseDaoContract-8]
_ = x[BalanceDecreaseDaoAccount-9]
_ = x[BalanceChangeTransfer-10]
_ = x[BalanceChangeTouchAccount-11]
_ = x[BalanceIncreaseSelfdestruct-12]
_ = x[BalanceDecreaseSelfdestruct-13]
_ = x[BalanceDecreaseSelfdestructBurn-14]
}
const _BalanceChangeReason_name = "BalanceChangeUnspecifiedBalanceIncreaseRewardMineUncleBalanceIncreaseRewardMineBlockBalanceIncreaseWithdrawalBalanceIncreaseGenesisBalanceBalanceIncreaseRewardTransactionFeeBalanceDecreaseGasBuyBalanceIncreaseGasReturnBalanceIncreaseDaoContractBalanceDecreaseDaoAccountBalanceChangeTransferBalanceChangeTouchAccountBalanceIncreaseSelfdestructBalanceDecreaseSelfdestructBalanceDecreaseSelfdestructBurn"
var _BalanceChangeReason_index = [...]uint16{0, 24, 54, 84, 109, 138, 173, 194, 218, 244, 269, 290, 315, 342, 369, 400}
func (i BalanceChangeReason) String() string {
if i >= BalanceChangeReason(len(_BalanceChangeReason_index)-1) {
return "BalanceChangeReason(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _BalanceChangeReason_name[_BalanceChangeReason_index[i]:_BalanceChangeReason_index[i+1]]
}

View File

@@ -81,6 +81,10 @@ type (
TxEndHook = func(receipt *types.Receipt, err error)
// EnterHook is invoked when the processing of a message starts.
//
// Take note that EnterHook, when in the context of a live tracer, can be invoked
// outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls,
// see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information.
EnterHook = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
// ExitHook is invoked when the processing of a message ends.
@@ -89,6 +93,10 @@ type (
// ran out of gas when attempting to persist the code to database did not
// count as a call failure and did not cause a revert of the call. This will
// be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`.
//
// Take note that ExitHook, when in the context of a live tracer, can be invoked
// outside of the `OnTxStart` and `OnTxEnd` hooks when dealing with system calls,
// see [OnSystemCallStartHook] and [OnSystemCallEndHook] for more information.
ExitHook = func(depth int, output []byte, gasUsed uint64, err error, reverted bool)
// OpcodeHook is invoked just prior to the execution of an opcode.
@@ -125,6 +133,22 @@ type (
// GenesisBlockHook is called when the genesis block is being processed.
GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc)
// OnSystemCallStartHook is called when a system call is about to be executed. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
//
// After this hook, the EVM call tracing will happened as usual so you will receive a `OnEnter/OnExit`
// as well as state hooks between this hook and the `OnSystemCallEndHook`.
//
// Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks
// will not be invoked.
OnSystemCallStartHook = func()
// OnSystemCallEndHook is called when a system call has finished executing. Today,
// this hook is invoked when the EIP-4788 system call is about to be executed to set the
// beacon block root.
OnSystemCallEndHook = func()
/*
- State events -
*/
@@ -155,12 +179,14 @@ type Hooks struct {
OnFault FaultHook
OnGasChange GasChangeHook
// Chain events
OnBlockchainInit BlockchainInitHook
OnClose CloseHook
OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook
OnBlockchainInit BlockchainInitHook
OnClose CloseHook
OnBlockStart BlockStartHook
OnBlockEnd BlockEndHook
OnSkippedBlock SkippedBlockHook
OnGenesisBlock GenesisBlockHook
OnSystemCallStart OnSystemCallStartHook
OnSystemCallEnd OnSystemCallEndHook
// State events
OnBalanceChange BalanceChangeHook
OnNonceChange NonceChangeHook
@@ -173,6 +199,8 @@ type Hooks struct {
// for tracing and reporting.
type BalanceChangeReason byte
//go:generate stringer -type=BalanceChangeReason -output gen_balance_change_reason_stringer.go
const (
BalanceChangeUnspecified BalanceChangeReason = 0
@@ -272,6 +300,12 @@ const (
GasChangeCallStorageColdAccess GasChangeReason = 13
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
GasChangeCallFailedExecution GasChangeReason = 14
// GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step
GasChangeWitnessContractInit GasChangeReason = 15
// GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step
GasChangeWitnessContractCreation GasChangeReason = 16
// GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks
GasChangeWitnessCodeChunk GasChangeReason = 17
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event.

View File

@@ -18,7 +18,6 @@ package core
import (
"math/big"
"os"
"testing"
"github.com/ethereum/go-ethereum/common"
@@ -211,8 +210,7 @@ func TestTxIndexer(t *testing.T) {
},
}
for _, c := range cases {
frdir := t.TempDir()
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), frdir, "", false)
db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), append([]types.Receipts{{}}, receipts...), big.NewInt(0))
// Index the initial blocks from ancient store
@@ -238,6 +236,5 @@ func TestTxIndexer(t *testing.T) {
verify(db, 0, indexer)
db.Close()
os.RemoveAll(frdir)
}
}

View File

@@ -87,7 +87,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header {
}
func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil, trie.NewStackTrie(nil))
return types.NewBlock(bc.CurrentBlock(), nil, nil, trie.NewStackTrie(nil))
}
func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {

View File

@@ -23,6 +23,7 @@ import (
"io"
"math/big"
"reflect"
"slices"
"sync/atomic"
"time"
@@ -217,13 +218,19 @@ type extblock struct {
// NewBlock creates a new block. The input data is copied, changes to header and to the
// field values will not affect the block.
//
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
// are ignored and set to values derived from the given txs, uncles
// and receipts.
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
b := &Block{header: CopyHeader(header)}
// The body elements and the receipts are used to recompute and overwrite the
// relevant portions of the header.
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block {
if body == nil {
body = &Body{}
}
var (
b = NewBlockWithHeader(header)
txs = body.Transactions
uncles = body.Uncles
withdrawals = body.Withdrawals
)
// TODO: panic if len(txs) != len(receipts)
if len(txs) == 0 {
b.header.TxHash = EmptyTxsHash
} else {
@@ -249,27 +256,18 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*
}
}
return b
}
// NewBlockWithWithdrawals creates a new block with withdrawals. The input data is copied,
// changes to header and to the field values will not affect the block.
//
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header are ignored and set to
// values derived from the given txs, uncles and receipts.
func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block {
b := NewBlock(header, txs, uncles, receipts, hasher)
if withdrawals == nil {
b.header.WithdrawalsHash = nil
} else if len(withdrawals) == 0 {
b.header.WithdrawalsHash = &EmptyWithdrawalsHash
b.withdrawals = Withdrawals{}
} else {
h := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &h
hash := DeriveSha(Withdrawals(withdrawals), hasher)
b.header.WithdrawalsHash = &hash
b.withdrawals = slices.Clone(withdrawals)
}
return b.WithWithdrawals(withdrawals)
return b
}
// CopyHeader creates a deep copy of a block header.
@@ -453,31 +451,17 @@ func (b *Block) WithSeal(header *Header) *Block {
}
}
// WithBody returns a copy of the block with the given transaction and uncle contents.
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
// WithBody returns a new block with the original header and a deep copy of the
// provided body.
func (b *Block) WithBody(body Body) *Block {
block := &Block{
header: b.header,
transactions: make([]*Transaction, len(transactions)),
uncles: make([]*Header, len(uncles)),
withdrawals: b.withdrawals,
transactions: slices.Clone(body.Transactions),
uncles: make([]*Header, len(body.Uncles)),
withdrawals: slices.Clone(body.Withdrawals),
}
copy(block.transactions, transactions)
for i := range uncles {
block.uncles[i] = CopyHeader(uncles[i])
}
return block
}
// WithWithdrawals returns a copy of the block containing the given withdrawals.
func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block {
block := &Block{
header: b.header,
transactions: b.transactions,
uncles: b.uncles,
}
if withdrawals != nil {
block.withdrawals = make([]*Withdrawal, len(withdrawals))
copy(block.withdrawals, withdrawals)
for i := range body.Uncles {
block.uncles[i] = CopyHeader(body.Uncles[i])
}
return block
}

View File

@@ -254,7 +254,7 @@ func makeBenchBlock() *Block {
Extra: []byte("benchmark uncle"),
}
}
return NewBlock(header, txs, uncles, receipts, blocktest.NewHasher())
return NewBlock(header, &Body{Transactions: txs, Uncles: uncles}, receipts, blocktest.NewHasher())
}
func TestRlpDecodeParentHash(t *testing.T) {

View File

@@ -556,7 +556,7 @@ func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
}
}
// TxDifference returns a new set which is the difference between a and b.
// TxDifference returns a new set of transactions that are present in a but not in b.
func TxDifference(a, b Transactions) Transactions {
keep := make(Transactions, 0, len(a))
@@ -574,7 +574,7 @@ func TxDifference(a, b Transactions) Transactions {
return keep
}
// HashDifference returns a new set which is the difference between a and b.
// HashDifference returns a new set of hashes that are present in a but not in b.
func HashDifference(a, b []common.Hash) []common.Hash {
keep := make([]common.Hash, 0, len(a))

View File

@@ -379,7 +379,7 @@ func assertEqual(orig *Transaction, cpy *Transaction) error {
}
if orig.AccessList() != nil {
if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) {
return errors.New("access list wrong!")
return errors.New("access list wrong")
}
}
return nil

View File

@@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
return common.RightPadBytes(data[start:end], int(size))
}
func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
length := uint64(len(data))
if start > length {
start = length
}
end := start + size
if end > length {
end = length
}
return common.RightPadBytes(data[start:end], int(size)), start, end - start
}
// toWordSize returns the ceiled word size required for memory expansion.
func toWordSize(size uint64) uint64 {
if size > math.MaxUint64-31 {

View File

@@ -57,6 +57,9 @@ type Contract struct {
CodeAddr *common.Address
Input []byte
// is the execution frame represented by this object a contract deployment
IsDeployment bool
Gas uint64
value *uint256.Int
}

View File

@@ -137,6 +137,8 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
var PrecompiledContractsBLS = PrecompiledContractsPrague
var PrecompiledContractsVerkle = PrecompiledContractsPrague
var (
PrecompiledAddressesPrague []common.Address
PrecompiledAddressesCancun []common.Address
@@ -705,6 +707,8 @@ func (c *bls12381G1Add) Run(input []byte) ([]byte, error) {
return nil, err
}
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1
p0.Add(p0, p1)
@@ -734,6 +738,11 @@ func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG1(input[:128]); err != nil {
return nil, err
}
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
// Decode scalar value
e := new(big.Int).SetBytes(input[128:])
@@ -787,6 +796,11 @@ func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) {
if err != nil {
return nil, err
}
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G1PointSubgroup
}
points[i] = *p
// Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@@ -827,6 +841,8 @@ func (c *bls12381G2Add) Run(input []byte) ([]byte, error) {
return nil, err
}
// No need to check the subgroup here, as specified by EIP-2537
// Compute r = p_0 + p_1
r := new(bls12381.G2Affine)
r.Add(p0, p1)
@@ -857,6 +873,11 @@ func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) {
if p0, err = decodePointG2(input[:256]); err != nil {
return nil, err
}
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p0.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
// Decode scalar value
e := new(big.Int).SetBytes(input[256:])
@@ -910,6 +931,11 @@ func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) {
if err != nil {
return nil, err
}
// 'point is on curve' check already done,
// Here we need to apply subgroup checks.
if !p.IsInSubGroup() {
return nil, errBLS12381G2PointSubgroup
}
points[i] = *p
// Decode scalar value
scalars[i] = *new(fr.Element).SetBytes(input[t1:t2])
@@ -1099,9 +1125,6 @@ func (c *bls12381MapG1) Run(input []byte) ([]byte, error) {
// Compute mapping
r := bls12381.MapToG1(fe)
if err != nil {
return nil, err
}
// Encode the G1 point to 128 bytes
return encodePointG1(&r), nil
@@ -1135,9 +1158,6 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) {
// Compute mapping
r := bls12381.MapToG2(bls12381.E2{A0: c0, A1: c1})
if err != nil {
return nil, err
}
// Encode the G2 point to 256 bytes
return encodePointG2(&r), nil

View File

@@ -18,9 +18,11 @@ package vm
import (
"fmt"
"math"
"sort"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
@@ -37,6 +39,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884,
1344: enable1344,
1153: enable1153,
4762: enable4762,
}
// EnableEIP enables the given EIP on the config.
@@ -319,3 +322,214 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0),
}
}
func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
stack = scope.Stack
a = stack.pop()
memOffset = stack.pop()
codeOffset = stack.pop()
length = stack.pop()
)
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
if overflow {
uint64CodeOffset = math.MaxUint64
}
addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr)
contract := &Contract{
Code: code,
self: AccountRef(addr),
}
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
return nil, nil
}
// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which
// need not worry about the adjusted bound logic when adding the PUSHDATA to
// the list of access events.
func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int)
)
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
if !scope.Contract.IsDeployment && *pc%31 == 0 {
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary.
contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
} else {
scope.Stack.push(integer.Clear())
}
return nil, nil
}
func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
codeLen = len(scope.Contract.Code)
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)
scope.Stack.push(new(uint256.Int).SetBytes(
common.RightPadBytes(
scope.Contract.Code[start:end],
pushByteSize,
)),
)
if !scope.Contract.IsDeployment {
contractAddr := scope.Contract.Address()
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false)
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
*pc += size
return nil, nil
}
}
func enable4762(jt *JumpTable) {
jt[SSTORE] = &operation{
dynamicGas: gasSStore4762,
execute: opSstore,
minStack: minStack(2, 0),
maxStack: maxStack(2, 0),
}
jt[SLOAD] = &operation{
dynamicGas: gasSLoad4762,
execute: opSload,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[BALANCE] = &operation{
execute: opBalance,
dynamicGas: gasBalance4762,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[EXTCODESIZE] = &operation{
execute: opExtCodeSize,
dynamicGas: gasExtCodeSize4762,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[EXTCODEHASH] = &operation{
execute: opExtCodeHash,
dynamicGas: gasExtCodeHash4762,
minStack: minStack(1, 1),
maxStack: maxStack(1, 1),
}
jt[EXTCODECOPY] = &operation{
execute: opExtCodeCopyEIP4762,
dynamicGas: gasExtCodeCopyEIP4762,
minStack: minStack(4, 0),
maxStack: maxStack(4, 0),
memorySize: memoryExtCodeCopy,
}
jt[CODECOPY] = &operation{
execute: opCodeCopy,
constantGas: GasFastestStep,
dynamicGas: gasCodeCopyEip4762,
minStack: minStack(3, 0),
maxStack: maxStack(3, 0),
memorySize: memoryCodeCopy,
}
jt[SELFDESTRUCT] = &operation{
execute: opSelfdestruct6780,
dynamicGas: gasSelfdestructEIP4762,
constantGas: params.SelfdestructGasEIP150,
minStack: minStack(1, 0),
maxStack: maxStack(1, 0),
}
jt[CREATE] = &operation{
execute: opCreate,
constantGas: params.CreateNGasEip4762,
dynamicGas: gasCreateEip3860,
minStack: minStack(3, 1),
maxStack: maxStack(3, 1),
memorySize: memoryCreate,
}
jt[CREATE2] = &operation{
execute: opCreate2,
constantGas: params.CreateNGasEip4762,
dynamicGas: gasCreate2Eip3860,
minStack: minStack(4, 1),
maxStack: maxStack(4, 1),
memorySize: memoryCreate2,
}
jt[CALL] = &operation{
execute: opCall,
dynamicGas: gasCallEIP4762,
minStack: minStack(7, 1),
maxStack: maxStack(7, 1),
memorySize: memoryCall,
}
jt[CALLCODE] = &operation{
execute: opCallCode,
dynamicGas: gasCallCodeEIP4762,
minStack: minStack(7, 1),
maxStack: maxStack(7, 1),
memorySize: memoryCall,
}
jt[STATICCALL] = &operation{
execute: opStaticCall,
dynamicGas: gasStaticCallEIP4762,
minStack: minStack(6, 1),
maxStack: maxStack(6, 1),
memorySize: memoryStaticCall,
}
jt[DELEGATECALL] = &operation{
execute: opDelegateCall,
dynamicGas: gasDelegateCallEIP4762,
minStack: minStack(6, 1),
maxStack: maxStack(6, 1),
memorySize: memoryDelegateCall,
}
jt[PUSH1] = &operation{
execute: opPush1EIP4762,
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
for i := 1; i < 32; i++ {
jt[PUSH1+OpCode(i)] = &operation{
execute: makePushEIP4762(uint64(i+1), i+1),
constantGas: GasFastestStep,
minStack: minStack(0, 1),
maxStack: maxStack(0, 1),
}
}
}

View File

@@ -22,6 +22,7 @@ import (
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -42,6 +43,8 @@ type (
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsVerkle:
precompiles = PrecompiledContractsVerkle
case evm.chainRules.IsPrague:
precompiles = PrecompiledContractsPrague
case evm.chainRules.IsCancun:
@@ -85,10 +88,11 @@ type BlockContext struct {
// All fields can change between transactions.
type TxContext struct {
// Message information
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
BlobHashes []common.Hash // Provides information for BLOBHASH
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
}
// EVM is the Ethereum Virtual Machine base object and provides
@@ -156,6 +160,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
// Reset resets the EVM with a new transaction context.Reset
// This is not threadsafe and should only be done very cautiously.
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
if evm.chainRules.IsEIP4762 {
txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache())
}
evm.TxContext = txCtx
evm.StateDB = statedb
}
@@ -200,6 +207,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
p, isPrecompile := evm.precompile(addr)
if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP4762 {
// add proof of absence to witness
wgas := evm.AccessEvents.AddAccount(addr, false)
if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas
}
gas -= wgas
}
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non-existing account, don't do anything.
return nil, gas, nil
@@ -436,14 +453,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
return nil, common.Address{}, gas, ErrNonceUintOverflow
}
evm.StateDB.SetNonce(caller.Address(), nonce+1)
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
// the access-list change should not be rolled back
if evm.chainRules.IsBerlin {
// We add this to the access list _before_ taking a snapshot. Even if the
// creation fails, the access-list change should not be rolled back.
if evm.chainRules.IsEIP2929 {
evm.StateDB.AddAddressToAccessList(address)
}
// Ensure there's no existing contract already at the designated address.
// Account is regarded as existent if any of these three conditions is met:
// - the nonce is nonzero
// - the nonce is non-zero
// - the code is non-empty
// - the storage is non-empty
contractHash := evm.StateDB.GetCodeHash(address)
@@ -456,9 +474,19 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
}
return nil, common.Address{}, 0, ErrContractAddressCollision
}
// Create a new account on the state
// Create a new account on the state only if the object was not present.
// It might be possible the contract code is deployed to a pre-existent
// account with non-zero balance.
snapshot := evm.StateDB.Snapshot()
evm.StateDB.CreateAccount(address)
if !evm.StateDB.Exist(address) {
evm.StateDB.CreateAccount(address)
}
// CreateContract means that regardless of whether the account previously existed
// in the state trie or not, it _now_ becomes created as a _contract_ account.
// This is performed _prior_ to executing the initcode, since the initcode
// acts inside that account.
evm.StateDB.CreateContract(address)
if evm.chainRules.IsEIP158 {
evm.StateDB.SetNonce(address, 1)
}
@@ -468,8 +496,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash)
contract.IsDeployment = true
ret, err = evm.interpreter.Run(contract, nil, false)
// Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 {
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
err = ErrOutOfGas
}
}
if err == nil {
ret, err = evm.interpreter.Run(contract, nil, false)
}
// Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
@@ -486,11 +524,24 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// be stored due to not enough gas set an error and let it be handled
// by the error checking condition below.
if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
evm.StateDB.SetCode(address, ret)
if !evm.chainRules.IsEIP4762 {
createDataGas := uint64(len(ret)) * params.CreateDataGas
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
err = ErrCodeStoreOutOfGas
}
} else {
err = ErrCodeStoreOutOfGas
// Contract creation completed, touch the missing fields in the contract
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
err = ErrCodeStoreOutOfGas
}
if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
err = ErrCodeStoreOutOfGas
}
}
if err == nil {
evm.StateDB.SetCode(address, ret)
}
}

View File

@@ -383,7 +383,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
} else if !evm.StateDB.Exist(address) {
gas += params.CallNewAccountGas
}
if transfersValue {
if transfersValue && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
memoryGas, err := memoryGasCost(mem, memorySize)
@@ -394,7 +394,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
if evm.chainRules.IsEIP4762 {
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err
@@ -402,6 +409,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
@@ -414,12 +422,22 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
gas uint64
overflow bool
)
if stack.Back(2).Sign() != 0 {
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return 0, ErrGasUintOverflow
}
if evm.chainRules.IsEIP4762 {
address := common.Address(stack.Back(1).Bytes20())
transfersValue := !stack.Back(2).IsZero()
if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
if overflow {
return 0, ErrGasUintOverflow
}
}
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
if err != nil {
return 0, err

View File

@@ -173,11 +173,7 @@ func opByte(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
func opAddmod(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y, z := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.peek()
if z.IsZero() {
z.Clear()
} else {
z.AddMod(&x, &y, z)
}
z.AddMod(&x, &y, z)
return nil, nil
}
@@ -363,9 +359,9 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
if overflow {
uint64CodeOffset = math.MaxUint64
}
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
return nil, nil
}
@@ -438,6 +434,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
num.Clear()
return nil, nil
}
var upper, lower uint64
upper = interpreter.evm.Context.BlockNumber.Uint64()
if upper < 257 {
@@ -587,6 +584,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
// reuse size int for stackvalue
stackvalue := size
@@ -627,6 +625,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
@@ -641,7 +640,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
stackvalue.SetBytes(addr.Bytes())
}
scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if suberr == ErrExecutionReverted {
@@ -900,6 +898,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
pushByteSize,
)),
)
*pc += size
return nil, nil
}

View File

@@ -643,7 +643,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
}
}
func TestCreate2Addreses(t *testing.T) {
func TestCreate2Addresses(t *testing.T) {
type testcase struct {
origin string
salt string

Some files were not shown because too many files have changed in this diff Show More