Skip to content

Commit 43e0f86

Browse files
eclark0426jhunsaker
authored andcommitted
Copy ethereum runloop to monad_ethblocks
1 parent c506e97 commit 43e0f86

File tree

3 files changed

+343
-1
lines changed

3 files changed

+343
-1
lines changed

cmd/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ add_executable(
2727
monad/runloop_ethereum.cpp
2828
monad/runloop_ethereum.hpp
2929
monad/runloop_monad.cpp
30-
monad/runloop_monad.hpp)
30+
monad/runloop_monad.hpp
31+
monad/runloop_monad_ethblocks.cpp
32+
monad/runloop_monad_ethblocks.hpp)
3133

3234
monad_compile_options(monad)
3335

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
// Copyright (C) 2025 Category Labs, Inc.
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
#include "runloop_ethereum.hpp"
17+
18+
#include <category/core/assert.h>
19+
#include <category/core/bytes.hpp>
20+
#include <category/core/config.hpp>
21+
#include <category/core/fiber/priority_pool.hpp>
22+
#include <category/core/keccak.hpp>
23+
#include <category/core/procfs/statm.h>
24+
#include <category/execution/ethereum/block_hash_buffer.hpp>
25+
#include <category/execution/ethereum/chain/chain.hpp>
26+
#include <category/execution/ethereum/core/block.hpp>
27+
#include <category/execution/ethereum/core/rlp/block_rlp.hpp>
28+
#include <category/execution/ethereum/db/block_db.hpp>
29+
#include <category/execution/ethereum/db/db.hpp>
30+
#include <category/execution/ethereum/execute_block.hpp>
31+
#include <category/execution/ethereum/execute_transaction.hpp>
32+
#include <category/execution/ethereum/metrics/block_metrics.hpp>
33+
#include <category/execution/ethereum/state2/block_state.hpp>
34+
#include <category/execution/ethereum/trace/call_tracer.hpp>
35+
#include <category/execution/ethereum/validate_block.hpp>
36+
#include <category/execution/ethereum/validate_transaction.hpp>
37+
#include <category/vm/evm/switch_traits.hpp>
38+
#include <category/vm/evm/traits.hpp>
39+
40+
#include <boost/outcome/try.hpp>
41+
#include <quill/Quill.h>
42+
43+
#include <algorithm>
44+
#include <chrono>
45+
#include <memory>
46+
#include <vector>
47+
48+
MONAD_ANONYMOUS_NAMESPACE_BEGIN
49+
50+
#pragma GCC diagnostic push
51+
#pragma GCC diagnostic ignored "-Wunused-variable"
52+
#pragma GCC diagnostic ignored "-Wunused-parameter"
53+
54+
void log_tps(
55+
uint64_t const block_num, uint64_t const nblocks, uint64_t const ntxs,
56+
uint64_t const gas, std::chrono::steady_clock::time_point const begin)
57+
{
58+
auto const now = std::chrono::steady_clock::now();
59+
auto const elapsed = std::max(
60+
static_cast<uint64_t>(
61+
std::chrono::duration_cast<std::chrono::microseconds>(now - begin)
62+
.count()),
63+
1UL); // for the unlikely case that elapsed < 1 mic
64+
uint64_t const tps = (ntxs) * 1'000'000 / elapsed;
65+
uint64_t const gps = gas / elapsed;
66+
67+
LOG_INFO(
68+
"Run {:4d} blocks to {:8d}, number of transactions {:6d}, "
69+
"tps = {:5d}, gps = {:4d} M, rss = {:6d} MB",
70+
nblocks,
71+
block_num,
72+
ntxs,
73+
tps,
74+
gps,
75+
monad_procfs_self_resident() / (1L << 20));
76+
};
77+
78+
#pragma GCC diagnostic pop
79+
80+
// Process a single historical Ethereum block
81+
template <Traits traits>
82+
Result<void> process_ethereum_block(
83+
Chain const &chain, Db &db, vm::VM &vm,
84+
BlockHashBufferFinalized &block_hash_buffer,
85+
fiber::PriorityPool &priority_pool, Block &block, bytes32_t const &block_id,
86+
bytes32_t const &parent_block_id, bool const enable_tracing)
87+
{
88+
[[maybe_unused]] auto const block_start = std::chrono::system_clock::now();
89+
auto const block_begin = std::chrono::steady_clock::now();
90+
91+
// Block input validation
92+
BOOST_OUTCOME_TRY(chain.static_validate_header(block.header));
93+
BOOST_OUTCOME_TRY(static_validate_block<traits>(block));
94+
95+
// Sender and authority recovery
96+
auto const sender_recovery_begin = std::chrono::steady_clock::now();
97+
auto const recovered_senders =
98+
recover_senders(block.transactions, priority_pool);
99+
auto const recovered_authorities =
100+
recover_authorities(block.transactions, priority_pool);
101+
[[maybe_unused]] auto const sender_recovery_time =
102+
std::chrono::duration_cast<std::chrono::microseconds>(
103+
std::chrono::steady_clock::now() - sender_recovery_begin);
104+
std::vector<Address> senders(block.transactions.size());
105+
for (unsigned i = 0; i < recovered_senders.size(); ++i) {
106+
if (recovered_senders[i].has_value()) {
107+
senders[i] = recovered_senders[i].value();
108+
}
109+
else {
110+
return TransactionError::MissingSender;
111+
}
112+
}
113+
114+
// Call tracer initialization
115+
std::vector<std::vector<CallFrame>> call_frames{block.transactions.size()};
116+
std::vector<std::unique_ptr<CallTracerBase>> call_tracers{
117+
block.transactions.size()};
118+
std::vector<std::unique_ptr<trace::StateTracer>> state_tracers{
119+
block.transactions.size()};
120+
for (unsigned i = 0; i < block.transactions.size(); ++i) {
121+
call_tracers[i] =
122+
enable_tracing
123+
? std::unique_ptr<CallTracerBase>{std::make_unique<CallTracer>(
124+
block.transactions[i], call_frames[i])}
125+
: std::unique_ptr<CallTracerBase>{
126+
std::make_unique<NoopCallTracer>()};
127+
state_tracers[i] = std::unique_ptr<trace::StateTracer>{
128+
std::make_unique<trace::StateTracer>(std::monostate{})};
129+
}
130+
131+
// Core execution: transaction-level EVM execution that tracks state
132+
// changes but does not commit them
133+
db.set_block_and_prefix(block.header.number - 1, parent_block_id);
134+
BlockMetrics block_metrics;
135+
BlockState block_state(db, vm);
136+
BOOST_OUTCOME_TRY(
137+
auto const receipts,
138+
execute_block<traits>(
139+
chain,
140+
block,
141+
senders,
142+
recovered_authorities,
143+
block_state,
144+
block_hash_buffer,
145+
priority_pool,
146+
block_metrics,
147+
call_tracers,
148+
state_tracers));
149+
150+
// Database commit of state changes (incl. Merkle root calculations)
151+
block_state.log_debug();
152+
auto const commit_begin = std::chrono::steady_clock::now();
153+
block_state.commit(
154+
bytes32_t{block.header.number},
155+
block.header,
156+
receipts,
157+
call_frames,
158+
senders,
159+
block.transactions,
160+
block.ommers,
161+
block.withdrawals);
162+
[[maybe_unused]] auto const commit_time =
163+
std::chrono::duration_cast<std::chrono::microseconds>(
164+
std::chrono::steady_clock::now() - commit_begin);
165+
if (commit_time > std::chrono::milliseconds(500)) {
166+
LOG_WARNING(
167+
"Slow block commit detected - block {}: {}",
168+
block.header.number,
169+
commit_time);
170+
}
171+
// Post-commit validation of header, with Merkle root fields filled in
172+
auto const output_header = db.read_eth_header();
173+
BOOST_OUTCOME_TRY(
174+
chain.validate_output_header(block.header, output_header));
175+
176+
// Commit prologue: database finalization, computation of the Ethereum
177+
// block hash to append to the circular hash buffer
178+
db.finalize(block.header.number, block_id);
179+
db.update_verified_block(block.header.number);
180+
auto const eth_block_hash =
181+
to_bytes(keccak256(rlp::encode_block_header(output_header)));
182+
block_hash_buffer.set(block.header.number, eth_block_hash);
183+
184+
// Emit the block metrics log line
185+
[[maybe_unused]] auto const block_time =
186+
std::chrono::duration_cast<std::chrono::microseconds>(
187+
std::chrono::steady_clock::now() - block_begin);
188+
LOG_INFO(
189+
"__exec_block,bl={:8},ts={}"
190+
",tx={:5},rt={:4},rtp={:5.2f}%"
191+
",sr={:>7},txe={:>8},cmt={:>8},tot={:>8},tpse={:5},tps={:5}"
192+
",gas={:9},gpse={:4},gps={:3}{}{}{}",
193+
block.header.number,
194+
std::chrono::duration_cast<std::chrono::milliseconds>(
195+
block_start.time_since_epoch())
196+
.count(),
197+
block.transactions.size(),
198+
block_metrics.num_retries(),
199+
100.0 * (double)block_metrics.num_retries() /
200+
std::max(1.0, (double)block.transactions.size()),
201+
sender_recovery_time,
202+
block_metrics.tx_exec_time(),
203+
commit_time,
204+
block_time,
205+
block.transactions.size() * 1'000'000 /
206+
(uint64_t)std::max(1L, block_metrics.tx_exec_time().count()),
207+
block.transactions.size() * 1'000'000 /
208+
(uint64_t)std::max(1L, block_time.count()),
209+
output_header.gas_used,
210+
output_header.gas_used /
211+
(uint64_t)std::max(1L, block_metrics.tx_exec_time().count()),
212+
output_header.gas_used / (uint64_t)std::max(1L, block_time.count()),
213+
db.print_stats(),
214+
vm.print_and_reset_block_counts(),
215+
vm.print_compiler_stats());
216+
217+
return outcome_e::success();
218+
}
219+
220+
MONAD_ANONYMOUS_NAMESPACE_END
221+
222+
MONAD_NAMESPACE_BEGIN
223+
224+
Result<std::pair<uint64_t, uint64_t>> runloop_monad_ethblocks(
225+
Chain const &chain, std::filesystem::path const &ledger_dir, Db &db,
226+
vm::VM &vm, BlockHashBufferFinalized &block_hash_buffer,
227+
fiber::PriorityPool &priority_pool, uint64_t &block_num,
228+
uint64_t const end_block_num, sig_atomic_t const volatile &stop,
229+
bool const enable_tracing)
230+
{
231+
uint64_t const batch_size =
232+
end_block_num == std::numeric_limits<uint64_t>::max() ? 1 : 1000;
233+
uint64_t batch_num_blocks = 0;
234+
uint64_t batch_num_txs = 0;
235+
uint64_t total_gas = 0;
236+
uint64_t batch_gas = 0;
237+
auto batch_begin = std::chrono::steady_clock::now();
238+
uint64_t ntxs = 0;
239+
240+
BlockDb block_db(ledger_dir);
241+
bytes32_t parent_block_id{};
242+
while (block_num <= end_block_num && stop == 0) {
243+
Block block;
244+
MONAD_ASSERT_PRINTF(
245+
block_db.get(block_num, block),
246+
"Could not query %lu from blockdb",
247+
block_num);
248+
249+
bytes32_t const block_id = bytes32_t{block.header.number};
250+
evmc_revision const rev =
251+
chain.get_revision(block.header.number, block.header.timestamp);
252+
253+
BOOST_OUTCOME_TRY([&] {
254+
SWITCH_EVM_TRAITS(
255+
process_ethereum_block,
256+
chain,
257+
db,
258+
vm,
259+
block_hash_buffer,
260+
priority_pool,
261+
block,
262+
block_id,
263+
parent_block_id,
264+
enable_tracing);
265+
MONAD_ABORT_PRINTF("unhandled rev switch case: %d", rev);
266+
}());
267+
268+
ntxs += block.transactions.size();
269+
batch_num_txs += block.transactions.size();
270+
total_gas += block.header.gas_used;
271+
batch_gas += block.header.gas_used;
272+
++batch_num_blocks;
273+
274+
if (block_num % batch_size == 0) {
275+
log_tps(
276+
block_num,
277+
batch_num_blocks,
278+
batch_num_txs,
279+
batch_gas,
280+
batch_begin);
281+
batch_num_blocks = 0;
282+
batch_num_txs = 0;
283+
batch_gas = 0;
284+
batch_begin = std::chrono::steady_clock::now();
285+
}
286+
parent_block_id = block_id;
287+
++block_num;
288+
}
289+
if (batch_num_blocks > 0) {
290+
log_tps(
291+
block_num, batch_num_blocks, batch_num_txs, batch_gas, batch_begin);
292+
}
293+
return {ntxs, total_gas};
294+
}
295+
296+
MONAD_NAMESPACE_END
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (C) 2025 Category Labs, Inc.
2+
//
3+
// This program is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// This program is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
#pragma once
17+
18+
#include <category/core/config.hpp>
19+
#include <category/core/result.hpp>
20+
#include <category/vm/vm.hpp>
21+
22+
#include <cstdint>
23+
#include <filesystem>
24+
#include <utility>
25+
26+
#include <signal.h>
27+
28+
MONAD_NAMESPACE_BEGIN
29+
30+
struct Chain;
31+
struct Db;
32+
class BlockHashBufferFinalized;
33+
34+
namespace fiber
35+
{
36+
class PriorityPool;
37+
}
38+
39+
Result<std::pair<uint64_t, uint64_t>> runloop_monad_ethblocks(
40+
Chain const &, std::filesystem::path const &, Db &, vm::VM &,
41+
BlockHashBufferFinalized &, fiber::PriorityPool &, uint64_t &, uint64_t,
42+
sig_atomic_t const volatile &, bool enable_tracing);
43+
44+
MONAD_NAMESPACE_END

0 commit comments

Comments
 (0)