feat: persist contract analysis in db (#1640)

This commit is contained in:
Bjerg
2023-03-08 16:25:14 +01:00
committed by GitHub
parent b449ac06dd
commit 161de9aadf
19 changed files with 165 additions and 63 deletions

8
Cargo.lock generated
View File

@@ -5161,7 +5161,7 @@ dependencies = [
[[package]]
name = "revm"
version = "3.0.0"
source = "git+https://github.com/bluealloy/revm#7bb73da23d0278829f9669f175a6eede247c0538"
source = "git+https://github.com/bluealloy/revm#f2656b7eac6fab1cb872268bb7d0dda4b5d3aa85"
dependencies = [
"auto_impl",
"revm-interpreter",
@@ -5171,7 +5171,7 @@ dependencies = [
[[package]]
name = "revm-interpreter"
version = "1.0.0"
source = "git+https://github.com/bluealloy/revm#7bb73da23d0278829f9669f175a6eede247c0538"
source = "git+https://github.com/bluealloy/revm#f2656b7eac6fab1cb872268bb7d0dda4b5d3aa85"
dependencies = [
"bitvec 1.0.1",
"derive_more",
@@ -5183,7 +5183,7 @@ dependencies = [
[[package]]
name = "revm-precompile"
version = "2.0.0"
source = "git+https://github.com/bluealloy/revm#7bb73da23d0278829f9669f175a6eede247c0538"
source = "git+https://github.com/bluealloy/revm#f2656b7eac6fab1cb872268bb7d0dda4b5d3aa85"
dependencies = [
"k256",
"num",
@@ -5199,7 +5199,7 @@ dependencies = [
[[package]]
name = "revm-primitives"
version = "1.0.0"
source = "git+https://github.com/bluealloy/revm#7bb73da23d0278829f9669f175a6eede247c0538"
source = "git+https://github.com/bluealloy/revm#f2656b7eac6fab1cb872268bb7d0dda4b5d3aa85"
dependencies = [
"arbitrary",
"auto_impl",

View File

@@ -10,8 +10,8 @@ use reth_db::{
Error as DbError,
};
use reth_primitives::{
keccak256, Account as RethAccount, Address, ChainSpec, ForkCondition, Hardfork, JsonU256,
SealedBlock, SealedHeader, StorageEntry, H256, U256,
keccak256, Account as RethAccount, Address, Bytecode, ChainSpec, ForkCondition, Hardfork,
JsonU256, SealedBlock, SealedHeader, StorageEntry, H256, U256,
};
use reth_provider::Transaction;
use reth_rlp::Decodable;
@@ -162,7 +162,7 @@ pub async fn run_test(path: PathBuf) -> eyre::Result<TestOutcome> {
},
)?;
if let Some(code_hash) = code_hash {
tx.put::<tables::Bytecodes>(code_hash, account.code.to_vec())?;
tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.0))?;
}
account.storage.iter().try_for_each(|(k, v)| {
trace!(target: "reth::cli", ?address, key = ?k.0, value = ?v.0, "Update storage");

View File

@@ -533,8 +533,8 @@ pub fn verify_receipt<'a>(
mod tests {
use super::*;
use reth_primitives::{
hex_literal::hex, keccak256, Account, Address, Bytes, ChainSpecBuilder, ForkCondition,
StorageKey, H256, MAINNET, U256,
hex_literal::hex, keccak256, Account, Address, Bytecode, Bytes, ChainSpecBuilder,
ForkCondition, StorageKey, H256, MAINNET, U256,
};
use reth_provider::{AccountProvider, BlockHashProvider, StateProvider};
use reth_revm::database::State;
@@ -544,7 +544,7 @@ mod tests {
#[derive(Debug, Default, Clone, Eq, PartialEq)]
struct StateProviderTest {
accounts: HashMap<Address, (HashMap<StorageKey, U256>, Account)>,
contracts: HashMap<H256, Bytes>,
contracts: HashMap<H256, Bytecode>,
block_hash: HashMap<U256, H256>,
}
@@ -560,7 +560,7 @@ mod tests {
if let Some(bytecode) = bytecode {
let hash = keccak256(&bytecode);
account.bytecode_hash = Some(hash);
self.contracts.insert(hash, bytecode);
self.contracts.insert(hash, Bytecode::new_raw(bytecode.into()));
}
self.accounts.insert(address, (storage, account));
}
@@ -591,7 +591,7 @@ mod tests {
.and_then(|(storage, _)| storage.get(&storage_key).cloned()))
}
fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytes>> {
fn bytecode_by_hash(&self, code_hash: H256) -> reth_interfaces::Result<Option<Bytecode>> {
Ok(self.contracts.get(&code_hash).cloned())
}
}

View File

@@ -1,5 +1,10 @@
use crate::{H256, KECCAK_EMPTY, U256};
use bytes::{Buf, BufMut, Bytes};
use fixed_hash::byteorder::{BigEndian, ReadBytesExt};
use reth_codecs::{main_codec, Compact};
use revm_primitives::{Bytecode as RevmBytecode, BytecodeState, JumpMap};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
/// An Ethereum account.
#[main_codec]
@@ -31,10 +36,97 @@ impl Account {
}
}
/// Bytecode for an account.
///
/// A wrapper around [`revm::primitives::Bytecode`][RevmBytecode] with encoding/decoding support.
///
/// Note: Upon decoding bytecode from the database, you *should* set the code hash using
/// [`Self::with_code_hash`].
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Bytecode(pub RevmBytecode);
impl Bytecode {
/// Create new bytecode from raw bytes.
///
/// No analysis will be performed.
pub fn new_raw(bytes: Bytes) -> Self {
Self(RevmBytecode::new_raw(bytes))
}
/// Set the hash of the inner bytecode.
pub fn with_code_hash(mut self, code_hash: H256) -> Self {
self.0.hash = code_hash;
self
}
}
impl Deref for Bytecode {
type Target = RevmBytecode;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Compact for Bytecode {
fn to_compact(self, buf: &mut impl BufMut) -> usize {
buf.put_u32(self.0.bytecode.len() as u32);
buf.put_slice(self.0.bytecode.as_ref());
let len = match self.0.state() {
BytecodeState::Raw => {
buf.put_u8(0);
1
}
BytecodeState::Checked { len } => {
buf.put_u8(1);
buf.put_u64(*len as u64);
9
}
BytecodeState::Analysed { len, jump_map } => {
buf.put_u8(2);
buf.put_u64(*len as u64);
let map = jump_map.as_slice();
buf.put_slice(map);
9 + map.len()
}
};
len + self.0.bytecode.len() + 4
}
fn from_compact(mut buf: &[u8], _: usize) -> (Self, &[u8])
where
Self: Sized,
{
let len = buf.read_u32::<BigEndian>().expect("could not read bytecode length");
let bytes = buf.copy_to_bytes(len as usize);
let variant = buf.read_u8().expect("could not read bytecode variant");
let decoded = match variant {
0 => Bytecode(RevmBytecode::new_raw(bytes)),
1 => Bytecode(unsafe {
RevmBytecode::new_checked(
bytes,
buf.read_u64::<BigEndian>().unwrap() as usize,
None,
)
}),
2 => Bytecode(RevmBytecode {
bytecode: bytes,
hash: KECCAK_EMPTY,
state: BytecodeState::Analysed {
len: buf.read_u64::<BigEndian>().unwrap() as usize,
jump_map: JumpMap::from_slice(buf),
},
}),
_ => unreachable!(),
};
(decoded, &[])
}
}
#[cfg(test)]
mod tests {
use crate::{Account, U256};
use reth_codecs::Compact;
use super::*;
use hex_literal::hex;
#[test]
fn test_account() {
@@ -51,4 +143,26 @@ mod tests {
let len = acc.to_compact(&mut buf);
assert_eq!(len, 4);
}
#[test]
fn test_bytecode() {
let mut buf = vec![];
let mut bytecode = Bytecode(RevmBytecode::new_raw(Bytes::default()));
let len = bytecode.clone().to_compact(&mut buf);
assert_eq!(len, 5);
let mut buf = vec![];
bytecode.0.bytecode = Bytes::from(hex!("ffff").as_ref());
let len = bytecode.clone().to_compact(&mut buf);
assert_eq!(len, 7);
let mut buf = vec![];
bytecode.0.state = BytecodeState::Analysed { len: 2, jump_map: JumpMap::from_slice(&[0]) };
let len = bytecode.clone().to_compact(&mut buf);
assert_eq!(len, 16);
let (decoded, remainder) = Bytecode::from_compact(&buf, len);
assert_eq!(decoded, bytecode);
assert!(remainder.is_empty());
}
}

View File

@@ -35,7 +35,7 @@ mod withdrawal;
/// Helper function for calculating Merkle proofs and hashes
pub mod proofs;
pub use account::Account;
pub use account::{Account, Bytecode};
pub use bits::H512;
pub use block::{Block, BlockHashOrNumber, BlockId, BlockNumberOrTag, SealedBlock};
pub use bloom::Bloom;
@@ -56,6 +56,7 @@ pub use log::Log;
pub use net::NodeRecord;
pub use peer::{PeerId, WithPeerId};
pub use receipt::Receipt;
pub use revm_primitives::JumpMap;
pub use serde_helper::JsonU256;
pub use storage::{StorageEntry, StorageTrieEntry};
pub use transaction::{

View File

@@ -39,7 +39,7 @@ pub fn fill_cfg_env(
cfg_env.chain_id = U256::from(chain_spec.chain().id());
cfg_env.spec_id = spec_id;
cfg_env.perf_all_precompiles_have_balance = false;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Raw;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
}
/// Fill block environment from Block.
pub fn fill_block_env(block_env: &mut BlockEnv, header: &Header, after_merge: bool) {

View File

@@ -6,7 +6,6 @@
))]
//! revm utils and implementations specific to reth.
pub mod config;
/// Helpers for configuring revm [Env](revm::primitives::Env)

View File

@@ -49,19 +49,9 @@ impl<DB: StateProvider> DatabaseRef for State<DB> {
fn code_by_hash(&self, code_hash: H256) -> Result<Bytecode, Self::Error> {
let bytecode = self.0.bytecode_by_hash(code_hash)?;
// SAFETY: We are requesting the code by its hash, so it is almost tautological why this
// would be safe. If the bytecode is not found, we return an empty bytecode with the
// appropriate hash.
//
// In an ideal world we would return analysed bytecode here, but analysed bytecode in revm
// depends on the current active hard fork, since it calculates gas blocks...
if let Some(bytecode) = bytecode {
Ok(unsafe { Bytecode::new_raw_with_hash(bytecode.0, code_hash) })
Ok(bytecode.with_code_hash(code_hash).0)
} else {
// NOTE(onbjerg): This corresponds to an empty analysed bytecode with a hash of
// `KECCAK_EMPTY`. In the case where the bytecode is not found, we would
// return empty bytes anyway: this simply skips the hashing and analysis steps, which
// would otherwise be present if we simply did an `.unwrap_or_default()` above.
Ok(Bytecode::new())
}
}

View File

@@ -15,7 +15,7 @@ where
let state =
self.state_at_block_id_or_latest(block_id)?.ok_or(EthApiError::UnknownBlockNumber)?;
let code = state.account_code(address)?.unwrap_or_default();
Ok(code)
Ok(code.original_bytes().into())
}
pub(crate) fn balance(&self, address: Address, block_id: Option<BlockId>) -> EthResult<U256> {

View File

@@ -293,8 +293,8 @@ mod tests {
};
use reth_executor::Factory;
use reth_primitives::{
hex_literal::hex, keccak256, Account, ChainSpecBuilder, SealedBlock, StorageEntry, H160,
H256, U256,
hex_literal::hex, keccak256, Account, Bytecode, ChainSpecBuilder, SealedBlock,
StorageEntry, H160, H256, U256,
};
use reth_provider::insert_canonical_block;
use reth_rlp::Decodable;
@@ -347,7 +347,7 @@ mod tests {
Account { nonce: 0, balance, bytecode_hash: None },
)
.unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap();
tx.commit().unwrap();
let mut execution_stage = stage();
@@ -430,7 +430,7 @@ mod tests {
db_tx.put::<tables::PlainAccountState>(acc1, acc1_info).unwrap();
db_tx.put::<tables::PlainAccountState>(acc2, acc2_info).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap();
tx.commit().unwrap();
// execute
@@ -502,7 +502,7 @@ mod tests {
// set account
db_tx.put::<tables::PlainAccountState>(caller_address, caller_info).unwrap();
db_tx.put::<tables::PlainAccountState>(destroyed_address, destroyed_info).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, code.to_vec()).unwrap();
db_tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap();
// set storage to check when account gets destroyed.
db_tx
.put::<tables::PlainStorageState>(

View File

@@ -43,7 +43,8 @@ impl_compression_for_compact!(
StorageTrieEntry,
StoredBlockBody,
StoredBlockOmmers,
StoredBlockWithdrawals
StoredBlockWithdrawals,
Bytecode
);
impl_compression_for_compact!(AccountBeforeTx, TransactionSigned);
impl_compression_for_compact!(CompactU256);

View File

@@ -18,7 +18,7 @@ use crate::{
},
};
use reth_primitives::{
Account, Address, BlockHash, BlockNumber, Header, IntegerList, Receipt, StorageEntry,
Account, Address, BlockHash, BlockNumber, Bytecode, Header, IntegerList, Receipt, StorageEntry,
StorageTrieEntry, TransactionSigned, TransitionId, TxHash, TxNumber, H256,
};
@@ -304,6 +304,3 @@ pub type StageId = Vec<u8>;
//
// TODO: Temporary types, until they're properly defined alongside with the Encode and Decode Trait
//
/// Temporary placeholder type for DB.
pub type Bytecode = Vec<u8>;

View File

@@ -10,7 +10,7 @@ use reth_db::{
};
use reth_interfaces::Result;
use reth_primitives::{
Account, Address, Bytes, StorageKey, StorageValue, TransitionId, H256, U256,
Account, Address, Bytecode, StorageKey, StorageValue, TransitionId, H256, U256,
};
use std::marker::PhantomData;
@@ -116,8 +116,8 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for HistoricalStateProviderRef<'a, 'b,
}
/// Get account code by its hash
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
self.tx.get::<tables::Bytecodes>(code_hash).map_err(Into::into).map(|r| r.map(Bytes::from))
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
self.tx.get::<tables::Bytecodes>(code_hash).map_err(Into::into)
}
}

View File

@@ -4,7 +4,7 @@ use crate::{
};
use reth_db::{cursor::DbDupCursorRO, tables, transaction::DbTx};
use reth_interfaces::Result;
use reth_primitives::{Account, Address, Bytes, StorageKey, StorageValue, H256, U256};
use reth_primitives::{Account, Address, Bytecode, StorageKey, StorageValue, H256, U256};
use std::marker::PhantomData;
/// State provider over latest state that takes tx reference.
@@ -49,8 +49,8 @@ impl<'a, 'b, TX: DbTx<'a>> StateProvider for LatestStateProviderRef<'a, 'b, TX>
}
/// Get account code by its hash
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
self.db.get::<tables::Bytecodes>(code_hash).map_err(Into::into).map(|r| r.map(Bytes::from))
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
self.db.get::<tables::Bytecodes>(code_hash).map_err(Into::into)
}
}

View File

@@ -38,7 +38,7 @@ macro_rules! delegate_provider_impls {
}
StateProvider $(where [$($generics)*])?{
fn storage(&self, account: reth_primitives::Address, storage_key: reth_primitives::StorageKey) -> reth_interfaces::Result<Option<reth_primitives::StorageValue>>;
fn bytecode_by_hash(&self, code_hash: reth_primitives::H256) -> reth_interfaces::Result<Option<reth_primitives::Bytes>>;
fn bytecode_by_hash(&self, code_hash: reth_primitives::H256) -> reth_interfaces::Result<Option<reth_primitives::Bytecode>>;
}
);
}

View File

@@ -5,8 +5,9 @@ use crate::{
use parking_lot::Mutex;
use reth_interfaces::Result;
use reth_primitives::{
keccak256, Account, Address, Block, BlockHash, BlockId, BlockNumber, BlockNumberOrTag, Bytes,
ChainInfo, Header, StorageKey, StorageValue, TransactionSigned, TxHash, H256, U256,
keccak256, Account, Address, Block, BlockHash, BlockId, BlockNumber, BlockNumberOrTag,
Bytecode, Bytes, ChainInfo, Header, StorageKey, StorageValue, TransactionSigned, TxHash, H256,
U256,
};
use revm_primitives::{BlockEnv, CfgEnv};
use std::{collections::HashMap, ops::RangeBounds, sync::Arc};
@@ -26,7 +27,7 @@ pub struct MockEthProvider {
#[derive(Debug, Clone)]
pub struct ExtendedAccount {
account: Account,
bytecode: Option<Bytes>,
bytecode: Option<Bytecode>,
storage: HashMap<StorageKey, StorageValue>,
}
@@ -44,7 +45,7 @@ impl ExtendedAccount {
pub fn with_bytecode(mut self, bytecode: Bytes) -> Self {
let hash = keccak256(&bytecode);
self.account.bytecode_hash = Some(hash);
self.bytecode = Some(bytecode);
self.bytecode = Some(Bytecode::new_raw(bytecode.into()));
self
}
}
@@ -226,7 +227,7 @@ impl StateProvider for MockEthProvider {
Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).cloned())
}
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>> {
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>> {
let lock = self.accounts.lock();
Ok(lock.values().find_map(|account| {
match (account.account.bytecode_hash.as_ref(), account.bytecode.as_ref()) {

View File

@@ -4,8 +4,8 @@ use crate::{
};
use reth_interfaces::Result;
use reth_primitives::{
Account, Address, Block, BlockHash, BlockId, BlockNumber, Bytes, ChainInfo, Header, StorageKey,
StorageValue, TransactionSigned, TxHash, TxNumber, H256, U256,
Account, Address, Block, BlockHash, BlockId, BlockNumber, Bytecode, ChainInfo, Header,
StorageKey, StorageValue, TransactionSigned, TxHash, TxNumber, H256, U256,
};
use revm_primitives::{BlockEnv, CfgEnv};
use std::ops::RangeBounds;
@@ -96,7 +96,7 @@ impl StateProvider for NoopProvider {
Ok(None)
}
fn bytecode_by_hash(&self, _code_hash: H256) -> Result<Option<Bytes>> {
fn bytecode_by_hash(&self, _code_hash: H256) -> Result<Option<Bytecode>> {
Ok(None)
}
}

View File

@@ -3,7 +3,7 @@ use crate::BlockHashProvider;
use auto_impl::auto_impl;
use reth_interfaces::Result;
use reth_primitives::{
Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, H256, KECCAK_EMPTY, U256,
Address, BlockHash, BlockNumber, Bytecode, StorageKey, StorageValue, H256, KECCAK_EMPTY, U256,
};
/// An abstraction for a type that provides state data.
@@ -13,12 +13,12 @@ pub trait StateProvider: BlockHashProvider + AccountProvider + Send + Sync {
fn storage(&self, account: Address, storage_key: StorageKey) -> Result<Option<StorageValue>>;
/// Get account code by its hash
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytes>>;
fn bytecode_by_hash(&self, code_hash: H256) -> Result<Option<Bytecode>>;
/// Get account code by its address.
///
/// Returns `None` if the account doesn't exist or account is not a contract
fn account_code(&self, addr: Address) -> Result<Option<Bytes>> {
fn account_code(&self, addr: Address) -> Result<Option<Bytecode>> {
// Get basic account information
// Returns None if acc doesn't exist
let acc = match self.basic_account(addr)? {

View File

@@ -14,8 +14,8 @@ use reth_db::{
};
use reth_interfaces::{db::Error as DbError, provider::ProviderError};
use reth_primitives::{
keccak256, Account, Address, BlockHash, BlockNumber, ChainSpec, Hardfork, Header, SealedBlock,
StorageEntry, TransitionId, TxNumber, H256, U256,
keccak256, Account, Address, BlockHash, BlockNumber, Bytecode, ChainSpec, Hardfork, Header,
SealedBlock, StorageEntry, TransitionId, TxNumber, H256, U256,
};
use reth_tracing::tracing::{info, trace};
use std::{
@@ -710,10 +710,9 @@ where
for (hash, bytecode) in result.new_bytecodes.into_iter() {
// make different types of bytecode. Checked and maybe even analyzed (needs to
// be packed). Currently save only raw bytes.
let bytecode = bytecode.bytes();
trace!(target: "sync::stages::execution", ?hash, ?bytecode, len = bytecode.len(), "Inserting bytecode");
self.put::<tables::Bytecodes>(hash, bytecode[..bytecode.len()].to_vec())?;
let bytes = bytecode.bytes();
trace!(target: "sync::stages::execution", ?hash, ?bytes, len = bytes.len(), "Inserting bytecode");
self.put::<tables::Bytecodes>(hash, Bytecode(bytecode))?;
// NOTE: bytecode bytes are not inserted in change set and can be found in
// separate table
}