|
| 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 |
0 commit comments