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 _, StateProviderBox, StateProviderFactory as _};
16use tempo_node::{TempoFullNode, evm::evm::TempoEvm};
17use tempo_precompiles::{
18 storage::StorageCtx,
19 validator_config_v2::{IValidatorConfigV2, ValidatorConfigV2},
20};
21use tempo_primitives::TempoHeader;
22
23use tracing::{Level, debug, instrument, warn};
24
25use crate::utils::public_key_to_b256;
26
27pub(crate) trait ExecutionNode {
34 fn header(&self, block_hash: B256) -> eyre::Result<TempoHeader>;
35
36 fn state_by_block_hash(&self, block_hash: B256) -> eyre::Result<StateProviderBox>;
37
38 fn evm_for_block(
39 &self,
40 db: State<StateProviderDatabase<StateProviderBox>>,
41 header: &TempoHeader,
42 ) -> eyre::Result<TempoEvm<State<StateProviderDatabase<StateProviderBox>>>>;
43}
44
45impl ExecutionNode for TempoFullNode {
46 fn header(&self, block_hash: B256) -> eyre::Result<TempoHeader> {
47 self.provider
48 .header(block_hash)
49 .map_err(eyre::Report::new)
50 .and_then(|maybe| maybe.ok_or_eyre("execution layer returned empty header"))
51 }
52
53 fn state_by_block_hash(&self, block_hash: B256) -> eyre::Result<StateProviderBox> {
54 self.provider
55 .state_by_block_hash(block_hash)
56 .map_err(eyre::Report::new)
57 }
58
59 fn evm_for_block(
60 &self,
61 db: State<StateProviderDatabase<StateProviderBox>>,
62 header: &TempoHeader,
63 ) -> eyre::Result<TempoEvm<State<StateProviderDatabase<StateProviderBox>>>> {
64 self.evm_config
65 .evm_for_block(db, header)
66 .map_err(eyre::Report::new)
67 }
68}
69
70impl<N> ExecutionNode for &N
71where
72 N: ExecutionNode,
73{
74 fn header(&self, block_hash: B256) -> eyre::Result<TempoHeader> {
75 (*self).header(block_hash)
76 }
77
78 fn state_by_block_hash(&self, block_hash: B256) -> eyre::Result<StateProviderBox> {
79 (*self).state_by_block_hash(block_hash)
80 }
81
82 fn evm_for_block(
83 &self,
84 db: State<StateProviderDatabase<StateProviderBox>>,
85 header: &TempoHeader,
86 ) -> eyre::Result<TempoEvm<State<StateProviderDatabase<StateProviderBox>>>> {
87 (*self).evm_for_block(db, header)
88 }
89}
90
91pub(crate) fn read_active_and_known_peers_at_block_hash(
96 node: impl ExecutionNode,
97 known: &ordered::Set<PublicKey>,
98 hash: B256,
99) -> eyre::Result<ordered::Map<PublicKey, commonware_p2p::Address>> {
100 read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfigV2| {
101 let mut all = HashMap::new();
102 for raw in config
103 .get_active_validators()
104 .wrap_err("failed getting active validator set")?
105 {
106 if let Ok(decoded) = DecodedValidatorV2::decode_from_contract(raw)
107 && all
108 .insert(decoded.public_key.clone(), decoded.to_p2p_address())
109 .is_some()
110 {
111 warn!(
112 duplicate = %decoded.public_key,
113 "found duplicate public keys",
114 );
115 }
116 }
117 debug!(
118 active_validators = ?all,
119 "read active validators from contract, now extending with historic \
120 peers that are still in the peer set but no longer marked active",
121 );
122 for member in known {
123 if !all.contains_key(member) {
124 let decoded = config
125 .validator_by_public_key(public_key_to_b256(member))
126 .map_err(eyre::Report::new)
127 .and_then(DecodedValidatorV2::decode_from_contract)
128 .wrap_err_with(|| format!("failed to read peer `{member}` from contract"))
129 .expect(
130 "invariant: known peers must have an entry in the \
131 smart contract and be well formed",
132 );
133 all.insert(decoded.public_key.clone(), decoded.to_p2p_address());
134 }
135 }
136 Ok(ordered::Map::try_from_iter(all).expect("hashmaps don't contain duplicates"))
137 })
138 .map(|(_height, _hash, value)| value)
139}
140
141#[instrument(skip_all, fields(%block_hash), err(Display))]
143pub(crate) fn read_validator_config_at_block_hash<C, T>(
144 node: impl ExecutionNode,
145 block_hash: B256,
146 read_fn: impl FnOnce(&C) -> eyre::Result<T>,
147) -> eyre::Result<(u64, B256, T)>
148where
149 C: Default,
150{
151 let header = node
152 .header(block_hash)
153 .wrap_err_with(|| format!("failed reading block with hash `{block_hash}`"))?;
154
155 debug!(height = header.number(), "header found");
156
157 let db = State::builder()
158 .with_database(StateProviderDatabase::new(
159 node.state_by_block_hash(block_hash).wrap_err_with(|| {
160 format!("failed to get state from node provider for hash `{block_hash}`")
161 })?,
162 ))
163 .build();
164
165 let mut evm = node
166 .evm_for_block(db, &header)
167 .wrap_err("failed instantiating evm for block")?;
168
169 let ctx = evm.ctx_mut();
170 let res = StorageCtx::enter_evm(
171 &mut ctx.journaled_state,
172 &ctx.block,
173 &ctx.cfg,
174 &ctx.tx,
175 || read_fn(&C::default()),
176 )?;
177 Ok((header.number(), block_hash, res))
178}
179
180pub(crate) struct DecodedValidatorV2 {
183 public_key: PublicKey,
184 ingress: SocketAddr,
185 egress: IpAddr,
186 added_at_height: u64,
187 deleted_at_height: u64,
188 index: u64,
189 address: Address,
190}
191
192impl DecodedValidatorV2 {
193 #[instrument(ret(Display, level = Level::DEBUG), err(level = Level::WARN))]
194 pub(crate) fn decode_from_contract(
195 IValidatorConfigV2::Validator {
196 publicKey,
197 validatorAddress: address,
198 ingress,
199 egress,
200 index,
201 addedAtHeight: added_at_height,
202 deactivatedAtHeight: deleted_at_height,
203 ..
204 }: IValidatorConfigV2::Validator,
205 ) -> eyre::Result<Self> {
206 let public_key = PublicKey::decode(publicKey.as_ref())
207 .wrap_err("failed decoding publicKey field as ed25519 public key")?;
208 let ingress = ingress.parse().wrap_err("ingress was not valid")?;
209 let egress = egress.parse().wrap_err("egress was not valid")?;
210 Ok(Self {
211 public_key,
212 ingress,
213 egress,
214 added_at_height,
215 deleted_at_height,
216 index,
217 address,
218 })
219 }
220
221 pub(crate) fn public_key(&self) -> &PublicKey {
222 &self.public_key
223 }
224
225 pub(crate) fn to_p2p_address(&self) -> commonware_p2p::Address {
226 commonware_p2p::Address::Asymmetric {
229 ingress: Ingress::Socket(self.ingress),
230 egress: SocketAddr::from((self.egress, 0)),
231 }
232 }
233}
234impl std::fmt::Display for DecodedValidatorV2 {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.write_fmt(format_args!(
237 "public key = `{}`, ingress = `{}`, egress = `{}`, added_at_height: `{}`, deleted_at_height = `{}`, index = `{}`, address = `{}`",
238 self.public_key,
239 self.ingress,
240 self.egress,
241 self.added_at_height,
242 self.deleted_at_height,
243 self.index,
244 self.address
245 ))
246 }
247}