use super::*;
use crate::db::EthBlockBloomStore;
const BLOOM_SIZE: usize = 2048;
const BLOOM_SIZE_IN_BYTES: usize = BLOOM_SIZE / 8;
pub(super) const FULL_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0xff; BLOOM_SIZE_IN_BYTES];
pub(super) const EMPTY_BLOOM: [u8; BLOOM_SIZE_IN_BYTES] = [0x0; BLOOM_SIZE_IN_BYTES];
pub(crate) const COMPUTE_BLOOM_ON_MISS_ENV: &str = "FOREST_ETH_RPC_COMPUTE_BLOOM_ON_MISS";
#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema, GetSize)]
pub struct Bloom(
#[schemars(with = "String")]
#[serde(with = "crate::lotus_json::hexify_bytes")]
#[get_size(ignore)]
pub ethereum_types::Bloom,
);
lotus_json_with_self!(Bloom);
impl Bloom {
pub fn accrue(&mut self, input: &[u8]) {
self.0.accrue(ethereum_types::BloomInput::Raw(input));
}
}
pub(super) fn accrue_eth_log(bloom: &mut Bloom, address: &EthAddress, topics: &[EthHash]) {
for topic in topics {
bloom.accrue(topic.0.as_bytes());
}
bloom.accrue(address.0.as_bytes());
}
fn compute_block_logs_bloom(
state_manager: &StateManager,
state_root: &Cid,
executed_messages: &[ExecutedMessage],
) -> anyhow::Result<Bloom> {
let state_tree = state_manager.get_state_tree(state_root)?;
let mut resolved_eth_addrs = HashMap::default();
let mut bloom = Bloom::default();
for executed_message in executed_messages {
let Some(events) = &executed_message.events else {
continue;
};
for event in events {
let emitter = event.emitter();
let address = resolved_eth_addrs.entry(emitter).or_insert_with(|| {
state_tree
.resolve_to_deterministic_address(
state_manager.chain_store().db(),
FilecoinAddress::new_id(emitter),
)
.ok()
.and_then(|addr| EthAddress::from_filecoin_address(&addr).ok())
});
let Some(address) = address else {
continue;
};
let entries: Vec<EventEntry> = event
.entries()
.into_iter()
.map(|entry| {
let (flags, key, codec, value) = entry.into_parts();
EventEntry {
flags,
key,
codec,
value: value.into(),
}
})
.collect();
let Some((_data, topics)) = eth_log_from_event(&entries) else {
continue;
};
accrue_eth_log(&mut bloom, address, &topics);
}
}
Ok(bloom)
}
pub(crate) fn store_block_logs_bloom(
state_manager: &StateManager,
tipset: &Tipset,
state_root: &Cid,
executed_messages: &[ExecutedMessage],
) -> anyhow::Result<()> {
let key = tipset.key().cid()?;
if state_manager.db().read_bloom(&key)?.is_some() {
return Ok(());
}
let bloom = compute_block_logs_bloom(state_manager, state_root, executed_messages)?;
state_manager
.db()
.write_bloom(&key, tipset.epoch(), &bloom.0.0)
}
pub(super) fn block_logs_bloom(
state_manager: &StateManager,
tipset: &Tipset,
state_root: &Cid,
executed_messages: &[ExecutedMessage],
) -> anyhow::Result<Bloom> {
crate::def_is_env_truthy!(compute_bloom_on_miss, COMPUTE_BLOOM_ON_MISS_ENV);
let key = tipset.key().cid()?;
if let Some(bloom) = state_manager.db().read_bloom(&key)? {
return Ok(Bloom(ethereum_types::Bloom(bloom)));
}
if compute_bloom_on_miss() {
let bloom = compute_block_logs_bloom(state_manager, state_root, executed_messages)?;
state_manager
.db()
.write_bloom(&key, tipset.epoch(), &bloom.0.0)?;
return Ok(bloom);
}
Ok(Bloom(ethereum_types::Bloom(FULL_BLOOM)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_accrue_eth_log_and_block_bloom_decomposition() {
let empty = Bloom::default();
let full = Bloom(ethereum_types::Bloom(FULL_BLOOM));
assert_eq!(empty.0.0, EMPTY_BLOOM);
let addr_a = EthAddress(ethereum_types::H160::from_slice(&[0x11; ADDRESS_LENGTH]));
let topic_a = EthHash(ethereum_types::H256::from_slice(&[0x22; EVM_WORD_LENGTH]));
let addr_b = EthAddress(ethereum_types::H160::from_slice(&[0x33; ADDRESS_LENGTH]));
let topic_b = EthHash(ethereum_types::H256::from_slice(&[0x44; EVM_WORD_LENGTH]));
let mut bloom_a = empty.clone();
accrue_eth_log(&mut bloom_a, &addr_a, std::slice::from_ref(&topic_a));
assert_ne!(bloom_a, empty);
assert_ne!(bloom_a, full);
let mut bloom_b = empty.clone();
accrue_eth_log(&mut bloom_b, &addr_b, std::slice::from_ref(&topic_b));
let mut combined = bloom_a.clone();
accrue_eth_log(&mut combined, &addr_b, std::slice::from_ref(&topic_b));
let mut expected = bloom_a.0.0;
for (out, b) in expected.iter_mut().zip(bloom_b.0.0.iter()) {
*out |= *b;
}
assert_eq!(combined.0.0, expected);
let mut twice = bloom_a.clone();
accrue_eth_log(&mut twice, &addr_a, std::slice::from_ref(&topic_a));
assert_eq!(twice, bloom_a);
}
}