|
|
@@ -1,8 +1,12 @@
|
|
|
use {
|
|
|
- crate::api::ChainId,
|
|
|
+ crate::{
|
|
|
+ api::ChainId,
|
|
|
+ keeper::keeper_metrics::{AccountLabel, KeeperMetrics},
|
|
|
+ },
|
|
|
anyhow::{ensure, Result},
|
|
|
ethers::types::Address,
|
|
|
sha3::{Digest, Keccak256},
|
|
|
+ std::sync::Arc,
|
|
|
tokio::task::spawn_blocking,
|
|
|
};
|
|
|
|
|
|
@@ -127,11 +131,22 @@ impl PebbleHashChain {
|
|
|
/// which requires tracking multiple hash chains here.
|
|
|
pub struct HashChainState {
|
|
|
// The sequence number where the hash chain starts. Must be stored in sorted order.
|
|
|
- pub offsets: Vec<usize>,
|
|
|
- pub hash_chains: Vec<PebbleHashChain>,
|
|
|
+ offsets: Vec<usize>,
|
|
|
+ hash_chains: Vec<PebbleHashChain>,
|
|
|
}
|
|
|
|
|
|
impl HashChainState {
|
|
|
+ pub fn new(offsets: Vec<usize>, hash_chains: Vec<PebbleHashChain>) -> Result<HashChainState> {
|
|
|
+ if offsets.len() != hash_chains.len() {
|
|
|
+ return Err(anyhow::anyhow!(
|
|
|
+ "Offsets and hash chains must have the same length."
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ Ok(HashChainState {
|
|
|
+ offsets,
|
|
|
+ hash_chains,
|
|
|
+ })
|
|
|
+ }
|
|
|
pub fn from_chain_at_offset(offset: usize, chain: PebbleHashChain) -> HashChainState {
|
|
|
HashChainState {
|
|
|
offsets: vec![offset],
|
|
|
@@ -152,12 +167,54 @@ impl HashChainState {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+pub struct MonitoredHashChainState {
|
|
|
+ hash_chain_state: Arc<HashChainState>,
|
|
|
+ metrics: Arc<KeeperMetrics>,
|
|
|
+ account_label: AccountLabel,
|
|
|
+}
|
|
|
+impl MonitoredHashChainState {
|
|
|
+ pub fn new(
|
|
|
+ hash_chain_state: Arc<HashChainState>,
|
|
|
+ metrics: Arc<KeeperMetrics>,
|
|
|
+ chain_id: ChainId,
|
|
|
+ provider_address: Address,
|
|
|
+ ) -> Self {
|
|
|
+ Self {
|
|
|
+ hash_chain_state,
|
|
|
+ metrics,
|
|
|
+ account_label: AccountLabel {
|
|
|
+ chain_id,
|
|
|
+ address: provider_address.to_string(),
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn reveal(&self, sequence_number: u64) -> Result<[u8; 32]> {
|
|
|
+ let res = self.hash_chain_state.reveal(sequence_number);
|
|
|
+ if res.is_ok() {
|
|
|
+ let metric = self
|
|
|
+ .metrics
|
|
|
+ .highest_revealed_sequence_number
|
|
|
+ .get_or_create(&self.account_label);
|
|
|
+ if metric.get() < sequence_number as i64 {
|
|
|
+ metric.set(sequence_number as i64);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ res
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#[cfg(test)]
|
|
|
mod test {
|
|
|
use {
|
|
|
- crate::state::{HashChainState, PebbleHashChain},
|
|
|
+ crate::{
|
|
|
+ keeper::keeper_metrics::{AccountLabel, KeeperMetrics},
|
|
|
+ state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
|
|
|
+ },
|
|
|
anyhow::Result,
|
|
|
+ ethers::types::Address,
|
|
|
sha3::{Digest, Keccak256},
|
|
|
+ std::{sync::Arc, vec},
|
|
|
};
|
|
|
|
|
|
fn run_hash_chain_test(secret: [u8; 32], length: usize, sample_interval: usize) {
|
|
|
@@ -294,4 +351,65 @@ mod test {
|
|
|
|
|
|
Ok(())
|
|
|
}
|
|
|
+ #[test]
|
|
|
+ fn test_inconsistent_lengths() -> Result<()> {
|
|
|
+ let chain1 = PebbleHashChain::new([0u8; 32], 10, 1);
|
|
|
+ let chain2 = PebbleHashChain::new([1u8; 32], 10, 1);
|
|
|
+
|
|
|
+ let hash_chain_state = HashChainState::new(vec![5], vec![chain1.clone(), chain2.clone()]);
|
|
|
+ assert!(hash_chain_state.is_err());
|
|
|
+ let hash_chain_state = HashChainState::new(vec![5, 10], vec![chain1.clone()]);
|
|
|
+ assert!(hash_chain_state.is_err());
|
|
|
+ let hash_chain_state =
|
|
|
+ HashChainState::new(vec![5, 10], vec![chain1.clone(), chain2.clone()]);
|
|
|
+ assert!(hash_chain_state.is_ok());
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_highest_revealed_sequence_number() {
|
|
|
+ let chain = PebbleHashChain::new([0u8; 32], 100, 1);
|
|
|
+ let hash_chain_state = HashChainState::new(vec![0], vec![chain]).unwrap();
|
|
|
+ let metrics = Arc::new(KeeperMetrics::default());
|
|
|
+ let provider = Address::random();
|
|
|
+ let monitored = MonitoredHashChainState::new(
|
|
|
+ Arc::new(hash_chain_state),
|
|
|
+ metrics.clone(),
|
|
|
+ "ethereum".to_string(),
|
|
|
+ provider,
|
|
|
+ );
|
|
|
+ let label = AccountLabel {
|
|
|
+ chain_id: "ethereum".to_string(),
|
|
|
+ address: provider.to_string(),
|
|
|
+ };
|
|
|
+
|
|
|
+ assert!(monitored.reveal(5).is_ok());
|
|
|
+ let current = metrics
|
|
|
+ .highest_revealed_sequence_number
|
|
|
+ .get_or_create(&label)
|
|
|
+ .get();
|
|
|
+ assert_eq!(current, 5);
|
|
|
+
|
|
|
+ assert!(monitored.reveal(15).is_ok());
|
|
|
+ let current = metrics
|
|
|
+ .highest_revealed_sequence_number
|
|
|
+ .get_or_create(&label)
|
|
|
+ .get();
|
|
|
+ assert_eq!(current, 15);
|
|
|
+
|
|
|
+ assert!(monitored.reveal(10).is_ok());
|
|
|
+ let current = metrics
|
|
|
+ .highest_revealed_sequence_number
|
|
|
+ .get_or_create(&label)
|
|
|
+ .get();
|
|
|
+ assert_eq!(current, 15);
|
|
|
+
|
|
|
+ assert!(monitored.reveal(1000).is_err());
|
|
|
+ let current = metrics
|
|
|
+ .highest_revealed_sequence_number
|
|
|
+ .get_or_create(&label)
|
|
|
+ .get();
|
|
|
+ assert_eq!(current, 15);
|
|
|
+ }
|
|
|
}
|