1use std::{
2 collections::HashMap,
3 net::{IpAddr, SocketAddr},
4};
5
6use alloy_consensus::BlockHeader;
7use alloy_primitives::{Address, B256};
8use commonware_codec::DecodeExt as _;
9use commonware_cryptography::ed25519::PublicKey;
10use commonware_p2p::Ingress;
11use commonware_utils::{TryFromIterator, ordered};
12use eyre::{OptionExt as _, WrapErr as _};
13use reth_ethereum::evm::revm::{State, database::StateProviderDatabase};
14use reth_node_builder::ConfigureEvm as _;
15use reth_provider::{HeaderProvider as _, StateProviderFactory as _};
16use tempo_chainspec::hardfork::TempoHardforks as _;
17use tempo_node::TempoFullNode;
18use tempo_precompiles::{
19 storage::StorageCtx,
20 validator_config::{IValidatorConfig, ValidatorConfig},
21 validator_config_v2::{IValidatorConfigV2, ValidatorConfigV2},
22};
23
24use tracing::{Level, instrument, warn};
25
26use crate::utils::public_key_to_b256;
27
28pub(crate) fn read_active_and_known_peers_at_block_hash(
44 node: &TempoFullNode,
45 known: &ordered::Set<PublicKey>,
46 hash: B256,
47) -> eyre::Result<ordered::Map<PublicKey, commonware_p2p::Address>> {
48 if can_use_v2_at_block_hash(node, hash, None)
49 .wrap_err("failed to determine validator config v2 status")?
50 {
51 read_active_and_known_peers_at_block_hash_v2(node, known, hash)
52 .wrap_err("failed reading peers from validator config v2")
53 } else {
54 read_active_and_known_peers_at_block_hash_v1(node, known, hash)
55 .wrap_err("failed reading peers from validator config v1")
56 }
57}
58
59pub(crate) fn read_active_and_known_peers_at_block_hash_v1(
65 node: &TempoFullNode,
66 known: &ordered::Set<PublicKey>,
67 hash: B256,
68) -> eyre::Result<ordered::Map<PublicKey, commonware_p2p::Address>> {
69 read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfig| {
70 let mut all = HashMap::new();
71 for raw in config
72 .get_validators()
73 .wrap_err("failed to query contract for validator config")?
74 {
75 if let Ok(decoded) = DecodedValidatorV1::decode_from_contract(raw)
76 && let Some(dupe) = all.insert(decoded.public_key.clone(), decoded)
77 {
78 warn!(
79 duplicate = %dupe.public_key,
80 "found duplicate public keys",
81 );
82 }
83 }
84 all.retain(|k, v| v.active || known.position(k).is_some());
85 Ok(
86 ordered::Map::try_from_iter(all.into_iter().map(|(k, v)| (k, v.to_address())))
87 .expect("hashmaps don't contain duplicates"),
88 )
89 })
90 .map(|(_height, _hash, value)| value)
91}
92
93pub(crate) fn read_active_and_known_peers_at_block_hash_v2(
98 node: &TempoFullNode,
99 known: &ordered::Set<PublicKey>,
100 hash: B256,
101) -> eyre::Result<ordered::Map<PublicKey, commonware_p2p::Address>> {
102 read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfigV2| {
103 let mut all = HashMap::new();
104 for raw in config
105 .get_active_validators()
106 .wrap_err("failed getting active validator set")?
107 {
108 if let Ok(decoded) = DecodedValidatorV2::decode_from_contract(raw)
109 && all
110 .insert(decoded.public_key.clone(), decoded.to_address())
111 .is_some()
112 {
113 warn!(
114 duplicate = %decoded.public_key,
115 "found duplicate public keys",
116 );
117 }
118 }
119 for member in known {
120 if !all.contains_key(member) {
121 let decoded = config
122 .validator_by_public_key(public_key_to_b256(member))
123 .map_err(eyre::Report::new)
124 .and_then(DecodedValidatorV2::decode_from_contract)
125 .expect(
126 "invariant: known peers must have an entry in the \
127 smart contract and be well formed",
128 );
129 all.insert(decoded.public_key.clone(), decoded.to_address());
130 }
131 }
132 Ok(ordered::Map::try_from_iter(all).expect("hashmaps don't contain duplicates"))
133 })
134 .map(|(_height, _hash, value)| value)
135}
136
137fn v2_initialization_height_at_block_hash(node: &TempoFullNode, hash: B256) -> eyre::Result<u64> {
138 read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfigV2| {
139 config
140 .get_initialized_at_height()
141 .map_err(eyre::Report::new)
142 })
143 .map(|(_, _, activation_height)| activation_height)
144}
145
146fn is_v2_initialized_at_block_hash(node: &TempoFullNode, hash: B256) -> eyre::Result<bool> {
147 read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfigV2| {
148 config.is_initialized().map_err(eyre::Report::new)
149 })
150 .map(|(_, _, activated)| activated)
151}
152
153pub(crate) fn read_validator_config_at_block_hash<C, T>(
155 node: &TempoFullNode,
156 block_hash: B256,
157 read_fn: impl FnOnce(&C) -> eyre::Result<T>,
158) -> eyre::Result<(u64, B256, T)>
159where
160 C: Default,
161{
162 let header = node
163 .provider
164 .header(block_hash)
165 .map_err(eyre::Report::new)
166 .and_then(|maybe| maybe.ok_or_eyre("execution layer returned empty header"))
167 .wrap_err_with(|| format!("failed reading block with hash `{block_hash}`"))?;
168
169 let db = State::builder()
170 .with_database(StateProviderDatabase::new(
171 node.provider
172 .state_by_block_hash(block_hash)
173 .wrap_err_with(|| {
174 format!("failed to get state from node provider for hash `{block_hash}`")
175 })?,
176 ))
177 .build();
178
179 let mut evm = node
180 .evm_config
181 .evm_for_block(db, &header)
182 .wrap_err("failed instantiating evm for block")?;
183
184 let ctx = evm.ctx_mut();
185 let res = StorageCtx::enter_evm(
186 &mut ctx.journaled_state,
187 &ctx.block,
188 &ctx.cfg,
189 &ctx.tx,
190 || read_fn(&C::default()),
191 )?;
192 Ok((header.number(), block_hash, res))
193}
194
195pub(crate) fn can_use_v2_at_block_hash(
219 node: &TempoFullNode,
220 hash: B256,
221 latest: Option<B256>,
222) -> eyre::Result<bool> {
223 let header = node
224 .provider
225 .header(hash)
226 .map_err(eyre::Report::new)
227 .and_then(|maybe| maybe.ok_or_eyre("hash not known"))
228 .wrap_err_with(|| {
229 format!("failed reading header for block hash `{hash}` from execution layer")
230 })?;
231 let state_hash = latest.unwrap_or(hash);
232 Ok(node
233 .chain_spec()
234 .is_t2_active_at_timestamp(header.timestamp())
235 && is_v2_initialized_at_block_hash(node, state_hash)
236 .wrap_err("failed reading validator config v2 initialization flag")?
237 && v2_initialization_height_at_block_hash(node, state_hash)
238 .wrap_err("failed reading validator config v2 initialization height")?
239 <= header.number())
240}
241
242#[derive(Clone, Debug, PartialEq, Eq)]
249pub(crate) struct DecodedValidatorV1 {
250 pub(crate) active: bool,
251 pub(crate) public_key: PublicKey,
256 pub(crate) inbound: SocketAddr,
260 pub(crate) outbound: SocketAddr,
264 pub(crate) index: u64,
268 pub(crate) address: Address,
272}
273
274impl DecodedValidatorV1 {
275 #[instrument(ret(Display, level = Level::DEBUG), err(level = Level::WARN))]
281 fn decode_from_contract(
282 IValidatorConfig::Validator {
283 active,
284 publicKey,
285 index,
286 validatorAddress,
287 inboundAddress,
288 outboundAddress,
289 }: IValidatorConfig::Validator,
290 ) -> eyre::Result<Self> {
291 let public_key = PublicKey::decode(publicKey.as_ref())
292 .wrap_err("failed decoding publicKey field as ed25519 public key")?;
293 let inbound = inboundAddress
294 .parse()
295 .wrap_err("inboundAddress was not valid")?;
296 let outbound = outboundAddress
297 .parse()
298 .wrap_err("outboundAddress was not valid")?;
299 Ok(Self {
300 active,
301 public_key,
302 inbound,
303 outbound,
304 index,
305 address: validatorAddress,
306 })
307 }
308
309 fn to_address(&self) -> commonware_p2p::Address {
310 commonware_p2p::Address::Asymmetric {
313 ingress: Ingress::Socket(self.inbound),
314 egress: self.outbound,
315 }
316 }
317}
318
319impl std::fmt::Display for DecodedValidatorV1 {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 f.write_fmt(format_args!(
322 "public key = `{}`, inbound = `{}`, outbound = `{}`, index = `{}`, address = `{}`",
323 self.public_key, self.inbound, self.outbound, self.index, self.address
324 ))
325 }
326}
327
328pub(crate) struct DecodedValidatorV2 {
331 public_key: PublicKey,
332 ingress: SocketAddr,
333 egress: IpAddr,
334 added_at_height: u64,
335 deleted_at_height: u64,
336 index: u64,
337 address: Address,
338}
339
340impl DecodedValidatorV2 {
341 #[instrument(ret(Display, level = Level::DEBUG), err(level = Level::WARN))]
342 pub(crate) fn decode_from_contract(
343 IValidatorConfigV2::Validator {
344 publicKey,
345 validatorAddress: address,
346 ingress,
347 egress,
348 index,
349 addedAtHeight: added_at_height,
350 deactivatedAtHeight: deleted_at_height,
351 ..
352 }: IValidatorConfigV2::Validator,
353 ) -> eyre::Result<Self> {
354 let public_key = PublicKey::decode(publicKey.as_ref())
355 .wrap_err("failed decoding publicKey field as ed25519 public key")?;
356 let ingress = ingress.parse().wrap_err("ingress was not valid")?;
357 let egress = egress.parse().wrap_err("egress was not valid")?;
358 Ok(Self {
359 public_key,
360 ingress,
361 egress,
362 added_at_height,
363 deleted_at_height,
364 index,
365 address,
366 })
367 }
368
369 fn to_address(&self) -> commonware_p2p::Address {
370 commonware_p2p::Address::Asymmetric {
373 ingress: Ingress::Socket(self.ingress),
374 egress: SocketAddr::from((self.egress, 0)),
375 }
376 }
377}
378impl std::fmt::Display for DecodedValidatorV2 {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 f.write_fmt(format_args!(
381 "public key = `{}`, ingress = `{}`, egress = `{}`, added_at_height: `{}`, deleted_at_height = `{}`, index = `{}`, address = `{}`",
382 self.public_key,
383 self.ingress,
384 self.egress,
385 self.added_at_height,
386 self.deleted_at_height,
387 self.index,
388 self.address
389 ))
390 }
391}