Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/hot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ rayon.workspace = true
thiserror.workspace = true

[dev-dependencies]
signet-hot = { path = ".", features = ["test-utils"] }
serde_json = "1.0"

[features]
default = []
Expand Down
78 changes: 75 additions & 3 deletions crates/hot/src/db/consistent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ use crate::{
tables,
};
use ahash::AHashSet;
use alloy::primitives::{Address, BlockNumber, U256, address};
use signet_storage_types::{BlockNumberList, SealedHeader};
use trevm::revm::database::BundleState;
use alloy::{
consensus::Sealable,
genesis::{Genesis, GenesisAccount},
primitives::{Address, B256, BlockNumber, U256, address},
};
use signet_storage_types::{Account, BlockNumberList, EthereumHardfork, SealedHeader};
use trevm::revm::{database::BundleState, state::Bytecode};

/// Maximum address value (all bits set to 1).
const ADDRESS_MAX: Address = address!("0xffffffffffffffffffffffffffffffffffffffff");
Expand Down Expand Up @@ -222,6 +226,74 @@ pub trait HistoryWrite: UnsafeDbWrite + UnsafeHistoryWrite {

Ok(())
}

/// Load genesis data into the database.
///
/// This operation is only valid on an empty database.
fn load_genesis(
&self,
genesis: &Genesis,
genesis_hardforks: &EthereumHardfork,
) -> Result<(), HistoryError<Self::Error>> {
// Check that the database is empty
if self.get_chain_tip().map_err(HistoryError::Db)?.is_some() {
return Err(HistoryError::DbNotEmpty);
}

// Seal the genesis header, record its number, and create a blocknumber
// list.
let header = signet_storage_types::genesis_header(genesis, genesis_hardforks).seal_slow();
let genesis_number = header.number;
let genesis_history = BlockNumberList::new_pre_sorted([genesis_number]);

// Append the header, with empty state
self.append_blocks(&[(header, BundleState::default())])?;

// Keep track of written bytecode hashes to avoid duplicates.
let mut written_bytecode_hashes: AHashSet<B256> = AHashSet::new();

// For each account in the genesis allocation, append account.
// The accounts are pre-sorted by the BTreeMap in Genesis.
genesis.alloc.iter().try_for_each(|(address, account)| {
let GenesisAccount { nonce, balance, code, storage, .. } = account;

// Insert bytecode if present. Check against the set to avoid
// duplicate writes. We still have to compute the hash though.
let bytecode_hash = code
.as_ref()
.map(|code_bytes| -> Result<_, HistoryError<Self::Error>> {
let hash = alloy::primitives::keccak256(code_bytes);
// Short-circuit if already written
if !written_bytecode_hashes.insert(hash) {
return Ok(hash);
}
self.put_bytecode(&hash, &Bytecode::new_raw(code_bytes.clone()))?;
Ok(hash)
})
.transpose()?;

// Append the account.
self.append_account(
address,
&Account { nonce: nonce.unwrap_or_default(), balance: *balance, bytecode_hash },
)?;

// Record account history at genesis
self.write_account_history(address, u64::MAX, &genesis_history)?;

// Insert storage entries and history
storage.iter().flatten().try_for_each(|(slot, value)| {
let slot = U256::from_be_bytes(**slot);
// We can append directly since the slots are sorted and the
// db is empty.
self.append_storage(address, &slot, &U256::from_be_bytes(**value))?;
// Record storage history at genesis
self.write_storage_history(address, slot, u64::MAX, &genesis_history)?;
Ok::<(), HistoryError<Self::Error>>(())
})?;
Ok(())
})
}
}

impl<T> HistoryWrite for T where T: UnsafeDbWrite + UnsafeHistoryWrite {}
9 changes: 7 additions & 2 deletions crates/hot/src/db/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,20 @@ pub enum HistoryError<E: std::error::Error> {
got: B256,
},

/// Empty header range provided to a method that requires at least one header.
/// Invoked `load_genesis` on a non-empty database.
#[error("cannot initialize genesis on a non-empty database")]
DbNotEmpty,

/// Empty header range provided to a method that requires at least one
/// header.
#[error("empty header range provided")]
EmptyRange,

/// Database error.
#[error("{0}")]
Db(#[from] E),

/// Integer List
/// Integer List error.
#[error(transparent)]
IntList(IntegerListError),
}
Expand Down
17 changes: 17 additions & 0 deletions crates/hot/src/db/inconsistent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,28 @@ pub trait UnsafeDbWrite: HotKvWrite + super::sealed::Sealed {
self.queue_put::<tables::PlainAccountState>(address, account)
}

/// Append an account by its address. This should generally only be used
/// when initializing the database (e.g., from genesis).
fn append_account(&self, address: &Address, account: &Account) -> Result<(), Self::Error> {
self.queue_append::<tables::PlainAccountState>(address, account)
}

/// Write a storage entry by its address and key.
fn put_storage(&self, address: &Address, key: &U256, entry: &U256) -> Result<(), Self::Error> {
self.queue_put_dual::<tables::PlainStorageState>(address, key, entry)
}

/// Append a storage entry by its address and key. This should generally
/// only be used when initializing the database (e.g., from genesis).
fn append_storage(
&self,
address: &Address,
key: &U256,
entry: &U256,
) -> Result<(), Self::Error> {
self.queue_append_dual::<tables::PlainStorageState>(address, key, entry)
}

/// Write a sealed block header (header + number).
fn put_header(&self, header: &SealedHeader) -> Result<(), Self::Error> {
self.put_header_inconsistent(header.as_ref())
Expand Down
187 changes: 187 additions & 0 deletions crates/hot/tests/artifacts/local.genesis.json

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions crates/hot/tests/artifacts/local.host.genesis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"chainId": 1,
"homesteadBlock": 1150000,
"daoForkBlock": 1920000,
"daoForkSupport": true,
"eip150Block": 2463000,
"eip155Block": 2675000,
"eip158Block": 2675000,
"byzantiumBlock": 4370000,
"constantinopleBlock": 7280000,
"petersburgBlock": 7280000,
"istanbulBlock": 9069000,
"muirGlacierBlock": 9200000,
"berlinBlock": 12244000,
"londonBlock": 12965000,
"arrowGlacierBlock": 13773000,
"grayGlacierBlock": 15050000,
"shanghaiTime": 1681338455,
"cancunTime": 1710338135,
"pragueTime": 1746612311,
"osakaTime": 1764798551,
"bpo1Time": 1765290071,
"bpo2Time": 1767747671,
"terminalTotalDifficulty": 58750000000000000000000,
"terminalTotalDifficultyPassed": true,
"depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa",
"ethash": {},
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
},
"prague": {
"target": 6,
"max": 9,
"baseFeeUpdateFraction": 5007716
},
"osaka": {
"target": 6,
"max": 9,
"baseFeeUpdateFraction": 5007716
},
"bpo1": {
"target": 10,
"max": 15,
"baseFeeUpdateFraction": 8346193
},
"bpo2": {
"target": 14,
"max": 21,
"baseFeeUpdateFraction": 11684671
}
}
}
144 changes: 144 additions & 0 deletions crates/hot/tests/artifacts/mainnet.genesis.json

Large diffs are not rendered by default.

Loading