pod_types/consensus/
attestation.rs

1use alloy_primitives::{Address, Signature};
2use alloy_sol_types::SolValue;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    Merkleizable, Receipt, Signed, Timestamp,
7    cryptography::{Hash, hash::Hashable, merkle_tree::MerkleBuilder},
8};
9
10pub type ReceiptAttestation = Attestation<Receipt>;
11
12#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
13#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
14pub struct Indexed<T> {
15    #[serde(rename = "timestamp")]
16    pub index: Timestamp, // TODO: consider sequential numbers
17    pub value: T,
18}
19
20impl<T> Indexed<T> {
21    pub fn new(index: Timestamp, value: T) -> Self {
22        Indexed { index, value }
23    }
24}
25
26impl<T> From<Indexed<Attestation<T>>> for Indexed<HeadlessAttestation> {
27    fn from(value: Indexed<Attestation<T>>) -> Self {
28        Indexed {
29            index: value.index,
30            value: value.value.into(),
31        }
32    }
33}
34
35impl<T: Hashable> Hashable for Indexed<T> {
36    fn hash_custom(&self) -> Hash {
37        alloy_primitives::keccak256(
38            [
39                self.index.as_micros().abi_encode(),
40                self.value.hash_custom().to_vec(),
41            ]
42            .concat(),
43        )
44    }
45}
46
47// An Attestation<T> is T signed by a validator using ECDSA
48#[derive(Copy, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
49pub struct Attestation<T> {
50    pub public_key: Address,
51    pub signature: Signature,
52    pub attested: T,
53}
54
55#[cfg(feature = "arbitrary")]
56impl<'a, T: arbitrary::Arbitrary<'a> + Hashable> arbitrary::Arbitrary<'a> for Attestation<T> {
57    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
58        use alloy_signer::SignerSync;
59        let signer = alloy_signer_local::PrivateKeySigner::random();
60        let attested = T::arbitrary(u)?;
61
62        Ok(Attestation {
63            public_key: signer.address(),
64            signature: signer.sign_hash_sync(&attested.hash_custom()).unwrap(),
65            attested,
66        })
67    }
68}
69
70impl<T: Hashable> Hashable for Attestation<T> {
71    fn hash_custom(&self) -> Hash {
72        let mut hasher = alloy_primitives::Keccak256::default();
73        hasher.update(self.public_key.as_slice());
74        hasher.update(self.signature.as_bytes());
75        hasher.update(self.attested.hash_custom().as_slice());
76        hasher.finalize()
77    }
78}
79
80impl<T> From<Signed<T>> for Attestation<T> {
81    fn from(signed: Signed<T>) -> Self {
82        Attestation {
83            public_key: signed.signer,
84            signature: signed.signature,
85            attested: signed.signed,
86        }
87    }
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize)]
91#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
92pub struct TimestampedHeadlessAttestation {
93    pub timestamp: Timestamp,
94    pub public_key: Address,
95    pub signature: Signature,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
100pub struct HeadlessAttestation {
101    pub public_key: Address,
102    pub signature: Signature,
103}
104
105impl<T> From<Attestation<T>> for HeadlessAttestation {
106    fn from(attestation: Attestation<T>) -> Self {
107        HeadlessAttestation {
108            public_key: attestation.public_key,
109            signature: attestation.signature,
110        }
111    }
112}
113
114impl From<Indexed<HeadlessAttestation>> for TimestampedHeadlessAttestation {
115    fn from(indexed: Indexed<HeadlessAttestation>) -> Self {
116        TimestampedHeadlessAttestation {
117            timestamp: indexed.index,
118            public_key: indexed.value.public_key,
119            signature: indexed.value.signature,
120        }
121    }
122}
123
124impl<T> From<Indexed<Attestation<T>>> for TimestampedHeadlessAttestation {
125    fn from(indexed: Indexed<Attestation<T>>) -> Self {
126        Self {
127            timestamp: indexed.index,
128            public_key: indexed.value.public_key,
129            signature: indexed.value.signature,
130        }
131    }
132}
133
134#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
135#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
136pub struct AttestedTx {
137    pub hash: Hash,
138    pub committee_epoch: u64,
139}
140
141impl AttestedTx {
142    pub fn new(hash: Hash, committee_epoch: u64) -> Self {
143        AttestedTx {
144            hash,
145            committee_epoch,
146        }
147    }
148}
149
150impl Merkleizable for AttestedTx {
151    fn append_leaves(&self, builder: &mut MerkleBuilder) {
152        builder.add_field("hash", self.hash);
153        builder.add_field(
154            "committee_epoch",
155            self.committee_epoch.abi_encode().hash_custom(),
156        );
157    }
158}