diff --git a/crates/core/component/sct/src/component/tree.rs b/crates/core/component/sct/src/component/tree.rs index 897226d619..b0d987a109 100644 --- a/crates/core/component/sct/src/component/tree.rs +++ b/crates/core/component/sct/src/component/tree.rs @@ -72,6 +72,11 @@ pub trait SctManager: StateWrite { epoch_root: Option, ) { let sct_anchor = sct.root(); + let block_timestamp = self + .get_current_block_timestamp() + .await + .map(|t| t.unix_timestamp()) + .unwrap_or(0); // Write the anchor as a key, so we can check claimed anchors... self.put_proto(state_key::tree::anchor_lookup(sct_anchor), height); @@ -79,8 +84,8 @@ pub trait SctManager: StateWrite { // TODO: can we move this out to NV storage? self.put(state_key::tree::anchor_by_height(height), sct_anchor); - self.record_proto(event::anchor(height, sct_anchor)); - self.record_proto(event::block_root(height, block_root)); + self.record_proto(event::anchor(height, sct_anchor, block_timestamp)); + self.record_proto(event::block_root(height, block_root, block_timestamp)); // Only record an epoch root event if we are ending the epoch. if let Some(epoch_root) = epoch_root { let index = self @@ -88,7 +93,7 @@ pub trait SctManager: StateWrite { .await .expect("epoch must be set") .index; - self.record_proto(event::epoch_root(index, epoch_root)); + self.record_proto(event::epoch_root(index, epoch_root, block_timestamp)); } self.write_sct_cache(sct); @@ -238,4 +243,5 @@ pub trait VerificationExt: StateRead { Ok(()) } } + impl VerificationExt for T {} diff --git a/crates/core/component/sct/src/event.rs b/crates/core/component/sct/src/event.rs index 3bde931a2d..65a462b0f8 100644 --- a/crates/core/component/sct/src/event.rs +++ b/crates/core/component/sct/src/event.rs @@ -1,3 +1,4 @@ +use pbjson_types::Timestamp; use penumbra_tct as tct; use tct::builder::{block, epoch}; @@ -5,24 +6,36 @@ use penumbra_proto::core::component::sct::v1 as pb; use crate::CommitmentSource; -pub fn anchor(height: u64, anchor: tct::Root) -> pb::EventAnchor { +pub fn anchor(height: u64, anchor: tct::Root, timestamp: i64) -> pb::EventAnchor { pb::EventAnchor { height, anchor: Some(anchor.into()), + timestamp: Some(Timestamp { + seconds: timestamp, + nanos: 0, + }), } } -pub fn block_root(height: u64, root: block::Root) -> pb::EventBlockRoot { +pub fn block_root(height: u64, root: block::Root, timestamp: i64) -> pb::EventBlockRoot { pb::EventBlockRoot { height, root: Some(root.into()), + timestamp: Some(Timestamp { + seconds: timestamp, + nanos: 0, + }), } } -pub fn epoch_root(index: u64, root: epoch::Root) -> pb::EventEpochRoot { +pub fn epoch_root(index: u64, root: epoch::Root, timestamp: i64) -> pb::EventEpochRoot { pb::EventEpochRoot { index, root: Some(root.into()), + timestamp: Some(Timestamp { + seconds: timestamp, + nanos: 0, + }), } } diff --git a/crates/proto/src/gen/penumbra.core.component.sct.v1.rs b/crates/proto/src/gen/penumbra.core.component.sct.v1.rs index 6f132c5141..24724a4047 100644 --- a/crates/proto/src/gen/penumbra.core.component.sct.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.sct.v1.rs @@ -229,6 +229,8 @@ pub struct EventAnchor { >, #[prost(uint64, tag = "2")] pub height: u64, + #[prost(message, optional, tag = "3")] + pub timestamp: ::core::option::Option<::pbjson_types::Timestamp>, } impl ::prost::Name for EventAnchor { const NAME: &'static str = "EventAnchor"; @@ -247,6 +249,8 @@ pub struct EventEpochRoot { >, #[prost(uint64, tag = "2")] pub index: u64, + #[prost(message, optional, tag = "3")] + pub timestamp: ::core::option::Option<::pbjson_types::Timestamp>, } impl ::prost::Name for EventEpochRoot { const NAME: &'static str = "EventEpochRoot"; @@ -265,6 +269,8 @@ pub struct EventBlockRoot { >, #[prost(uint64, tag = "2")] pub height: u64, + #[prost(message, optional, tag = "3")] + pub timestamp: ::core::option::Option<::pbjson_types::Timestamp>, } impl ::prost::Name for EventBlockRoot { const NAME: &'static str = "EventBlockRoot"; diff --git a/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs index 6618d12701..773dcedfea 100644 --- a/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.sct.v1.serde.rs @@ -1147,6 +1147,9 @@ impl serde::Serialize for EventAnchor { if self.height != 0 { len += 1; } + if self.timestamp.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.EventAnchor", len)?; if let Some(v) = self.anchor.as_ref() { struct_ser.serialize_field("anchor", v)?; @@ -1155,6 +1158,9 @@ impl serde::Serialize for EventAnchor { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; } + if let Some(v) = self.timestamp.as_ref() { + struct_ser.serialize_field("timestamp", v)?; + } struct_ser.end() } } @@ -1167,12 +1173,14 @@ impl<'de> serde::Deserialize<'de> for EventAnchor { const FIELDS: &[&str] = &[ "anchor", "height", + "timestamp", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Anchor, Height, + Timestamp, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -1197,6 +1205,7 @@ impl<'de> serde::Deserialize<'de> for EventAnchor { match value { "anchor" => Ok(GeneratedField::Anchor), "height" => Ok(GeneratedField::Height), + "timestamp" => Ok(GeneratedField::Timestamp), _ => Ok(GeneratedField::__SkipField__), } } @@ -1218,6 +1227,7 @@ impl<'de> serde::Deserialize<'de> for EventAnchor { { let mut anchor__ = None; let mut height__ = None; + let mut timestamp__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Anchor => { @@ -1234,6 +1244,12 @@ impl<'de> serde::Deserialize<'de> for EventAnchor { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -1242,6 +1258,7 @@ impl<'de> serde::Deserialize<'de> for EventAnchor { Ok(EventAnchor { anchor: anchor__, height: height__.unwrap_or_default(), + timestamp: timestamp__, }) } } @@ -1262,6 +1279,9 @@ impl serde::Serialize for EventBlockRoot { if self.height != 0 { len += 1; } + if self.timestamp.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.EventBlockRoot", len)?; if let Some(v) = self.root.as_ref() { struct_ser.serialize_field("root", v)?; @@ -1270,6 +1290,9 @@ impl serde::Serialize for EventBlockRoot { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; } + if let Some(v) = self.timestamp.as_ref() { + struct_ser.serialize_field("timestamp", v)?; + } struct_ser.end() } } @@ -1282,12 +1305,14 @@ impl<'de> serde::Deserialize<'de> for EventBlockRoot { const FIELDS: &[&str] = &[ "root", "height", + "timestamp", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Root, Height, + Timestamp, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -1312,6 +1337,7 @@ impl<'de> serde::Deserialize<'de> for EventBlockRoot { match value { "root" => Ok(GeneratedField::Root), "height" => Ok(GeneratedField::Height), + "timestamp" => Ok(GeneratedField::Timestamp), _ => Ok(GeneratedField::__SkipField__), } } @@ -1333,6 +1359,7 @@ impl<'de> serde::Deserialize<'de> for EventBlockRoot { { let mut root__ = None; let mut height__ = None; + let mut timestamp__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Root => { @@ -1349,6 +1376,12 @@ impl<'de> serde::Deserialize<'de> for EventBlockRoot { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -1357,6 +1390,7 @@ impl<'de> serde::Deserialize<'de> for EventBlockRoot { Ok(EventBlockRoot { root: root__, height: height__.unwrap_or_default(), + timestamp: timestamp__, }) } } @@ -1509,6 +1543,9 @@ impl serde::Serialize for EventEpochRoot { if self.index != 0 { len += 1; } + if self.timestamp.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.sct.v1.EventEpochRoot", len)?; if let Some(v) = self.root.as_ref() { struct_ser.serialize_field("root", v)?; @@ -1517,6 +1554,9 @@ impl serde::Serialize for EventEpochRoot { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("index", ToString::to_string(&self.index).as_str())?; } + if let Some(v) = self.timestamp.as_ref() { + struct_ser.serialize_field("timestamp", v)?; + } struct_ser.end() } } @@ -1529,12 +1569,14 @@ impl<'de> serde::Deserialize<'de> for EventEpochRoot { const FIELDS: &[&str] = &[ "root", "index", + "timestamp", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { Root, Index, + Timestamp, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -1559,6 +1601,7 @@ impl<'de> serde::Deserialize<'de> for EventEpochRoot { match value { "root" => Ok(GeneratedField::Root), "index" => Ok(GeneratedField::Index), + "timestamp" => Ok(GeneratedField::Timestamp), _ => Ok(GeneratedField::__SkipField__), } } @@ -1580,6 +1623,7 @@ impl<'de> serde::Deserialize<'de> for EventEpochRoot { { let mut root__ = None; let mut index__ = None; + let mut timestamp__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Root => { @@ -1596,6 +1640,12 @@ impl<'de> serde::Deserialize<'de> for EventEpochRoot { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::Timestamp => { + if timestamp__.is_some() { + return Err(serde::de::Error::duplicate_field("timestamp")); + } + timestamp__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -1604,6 +1654,7 @@ impl<'de> serde::Deserialize<'de> for EventEpochRoot { Ok(EventEpochRoot { root: root__, index: index__.unwrap_or_default(), + timestamp: timestamp__, }) } } diff --git a/proto/penumbra/penumbra/core/component/sct/v1/sct.proto b/proto/penumbra/penumbra/core/component/sct/v1/sct.proto index f18bcbb06e..742b69216c 100644 --- a/proto/penumbra/penumbra/core/component/sct/v1/sct.proto +++ b/proto/penumbra/penumbra/core/component/sct/v1/sct.proto @@ -89,19 +89,23 @@ message EventCommitment { message EventAnchor { crypto.tct.v1.MerkleRoot anchor = 1; uint64 height = 2; + google.protobuf.Timestamp timestamp = 3; } // Event recording an SCT epoch root. message EventEpochRoot { crypto.tct.v1.MerkleRoot root = 1; uint64 index = 2; + google.protobuf.Timestamp timestamp = 3; } // Event recording an SCT block root. message EventBlockRoot { crypto.tct.v1.MerkleRoot root = 1; uint64 height = 2; + google.protobuf.Timestamp timestamp = 3; } + message EpochByHeightRequest { uint64 height = 1; }