Skip to main content

tempo_commonware_node/
validators.rs

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_node::TempoFullNode;
17use tempo_precompiles::{
18    storage::StorageCtx,
19    validator_config_v2::{IValidatorConfigV2, ValidatorConfigV2},
20};
21
22use tracing::{Level, instrument, warn};
23
24use crate::utils::public_key_to_b256;
25
26/// Returns active validator config v2 entries at block `hash`.
27///
28/// This returns both the validators that are `active` as per the contract, and
29/// those that are `known`.
30pub(crate) fn read_active_and_known_peers_at_block_hash(
31    node: &TempoFullNode,
32    known: &ordered::Set<PublicKey>,
33    hash: B256,
34) -> eyre::Result<ordered::Map<PublicKey, commonware_p2p::Address>> {
35    read_validator_config_at_block_hash(node, hash, |config: &ValidatorConfigV2| {
36        let mut all = HashMap::new();
37        for raw in config
38            .get_active_validators()
39            .wrap_err("failed getting active validator set")?
40        {
41            if let Ok(decoded) = DecodedValidatorV2::decode_from_contract(raw)
42                && all
43                    .insert(decoded.public_key.clone(), decoded.to_address())
44                    .is_some()
45            {
46                warn!(
47                    duplicate = %decoded.public_key,
48                    "found duplicate public keys",
49                );
50            }
51        }
52        for member in known {
53            if !all.contains_key(member) {
54                let decoded = config
55                    .validator_by_public_key(public_key_to_b256(member))
56                    .map_err(eyre::Report::new)
57                    .and_then(DecodedValidatorV2::decode_from_contract)
58                    .expect(
59                        "invariant: known peers must have an entry in the \
60                        smart contract and be well formed",
61                    );
62                all.insert(decoded.public_key.clone(), decoded.to_address());
63            }
64        }
65        Ok(ordered::Map::try_from_iter(all).expect("hashmaps don't contain duplicates"))
66    })
67    .map(|(_height, _hash, value)| value)
68}
69
70/// Reads the validator state at the given block hash.
71pub(crate) fn read_validator_config_at_block_hash<C, T>(
72    node: &TempoFullNode,
73    block_hash: B256,
74    read_fn: impl FnOnce(&C) -> eyre::Result<T>,
75) -> eyre::Result<(u64, B256, T)>
76where
77    C: Default,
78{
79    let header = node
80        .provider
81        .header(block_hash)
82        .map_err(eyre::Report::new)
83        .and_then(|maybe| maybe.ok_or_eyre("execution layer returned empty header"))
84        .wrap_err_with(|| format!("failed reading block with hash `{block_hash}`"))?;
85
86    let db = State::builder()
87        .with_database(StateProviderDatabase::new(
88            node.provider
89                .state_by_block_hash(block_hash)
90                .wrap_err_with(|| {
91                    format!("failed to get state from node provider for hash `{block_hash}`")
92                })?,
93        ))
94        .build();
95
96    let mut evm = node
97        .evm_config
98        .evm_for_block(db, &header)
99        .wrap_err("failed instantiating evm for block")?;
100
101    let ctx = evm.ctx_mut();
102    let res = StorageCtx::enter_evm(
103        &mut ctx.journaled_state,
104        &ctx.block,
105        &ctx.cfg,
106        &ctx.tx,
107        || read_fn(&C::default()),
108    )?;
109    Ok((header.number(), block_hash, res))
110}
111
112/// An entry in the validator config v2 contract with all its fields decoded
113/// into Rust types.
114pub(crate) struct DecodedValidatorV2 {
115    public_key: PublicKey,
116    ingress: SocketAddr,
117    egress: IpAddr,
118    added_at_height: u64,
119    deleted_at_height: u64,
120    index: u64,
121    address: Address,
122}
123
124impl DecodedValidatorV2 {
125    #[instrument(ret(Display, level = Level::DEBUG), err(level = Level::WARN))]
126    pub(crate) fn decode_from_contract(
127        IValidatorConfigV2::Validator {
128            publicKey,
129            validatorAddress: address,
130            ingress,
131            egress,
132            index,
133            addedAtHeight: added_at_height,
134            deactivatedAtHeight: deleted_at_height,
135            ..
136        }: IValidatorConfigV2::Validator,
137    ) -> eyre::Result<Self> {
138        let public_key = PublicKey::decode(publicKey.as_ref())
139            .wrap_err("failed decoding publicKey field as ed25519 public key")?;
140        let ingress = ingress.parse().wrap_err("ingress was not valid")?;
141        let egress = egress.parse().wrap_err("egress was not valid")?;
142        Ok(Self {
143            public_key,
144            ingress,
145            egress,
146            added_at_height,
147            deleted_at_height,
148            index,
149            address,
150        })
151    }
152
153    fn to_address(&self) -> commonware_p2p::Address {
154        // NOTE: commonware takes egress as socket address but only uses the IP part.
155        // So setting port to 0 is ok.
156        commonware_p2p::Address::Asymmetric {
157            ingress: Ingress::Socket(self.ingress),
158            egress: SocketAddr::from((self.egress, 0)),
159        }
160    }
161}
162impl std::fmt::Display for DecodedValidatorV2 {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        f.write_fmt(format_args!(
165            "public key = `{}`, ingress = `{}`, egress = `{}`, added_at_height: `{}`, deleted_at_height = `{}`, index = `{}`, address = `{}`",
166            self.public_key,
167            self.ingress,
168            self.egress,
169            self.added_at_height,
170            self.deleted_at_height,
171            self.index,
172            self.address
173        ))
174    }
175}