Skip to content

Commit 6a73d24

Browse files
authored
feat(fortuna): More metrics around revelation (#2726)
* feat(fortuna): More metrics around revelation
1 parent a55edf8 commit 6a73d24

File tree

9 files changed

+169
-21
lines changed

9 files changed

+169
-21
lines changed

apps/fortuna/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/fortuna/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fortuna"
3-
version = "7.6.2"
3+
version = "7.6.3"
44
edition = "2021"
55

66
[lib]

apps/fortuna/src/api.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use {
22
crate::{
33
chain::reader::{BlockNumber, BlockStatus, EntropyReader},
44
history::History,
5-
state::HashChainState,
5+
state::MonitoredHashChainState,
66
},
77
anyhow::Result,
88
axum::{
@@ -91,7 +91,7 @@ pub struct BlockchainState {
9191
/// Obtained from the response of eth_chainId rpc call
9292
pub network_id: u64,
9393
/// The hash chain(s) required to serve random numbers for this blockchain
94-
pub state: Arc<HashChainState>,
94+
pub state: Arc<MonitoredHashChainState>,
9595
/// The contract that the server is fulfilling requests for.
9696
pub contract: Arc<dyn EntropyReader>,
9797
/// The address of the provider that this server is operating for.
@@ -212,7 +212,7 @@ mod test {
212212
},
213213
chain::reader::{mock::MockEntropyReader, BlockStatus},
214214
history::History,
215-
state::{HashChainState, PebbleHashChain},
215+
state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
216216
},
217217
axum::http::StatusCode,
218218
axum_test::{TestResponse, TestServer},
@@ -241,10 +241,17 @@ mod test {
241241
async fn test_server() -> (TestServer, Arc<MockEntropyReader>, Arc<MockEntropyReader>) {
242242
let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
243243

244+
let eth_state = MonitoredHashChainState::new(
245+
ETH_CHAIN.clone(),
246+
Default::default(),
247+
"ethereum".into(),
248+
PROVIDER,
249+
);
250+
244251
let eth_state = BlockchainState {
245252
id: "ethereum".into(),
246253
network_id: 1,
247-
state: ETH_CHAIN.clone(),
254+
state: Arc::new(eth_state),
248255
contract: eth_read.clone(),
249256
provider_address: PROVIDER,
250257
reveal_delay_blocks: 1,
@@ -255,10 +262,17 @@ mod test {
255262

256263
let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[]));
257264

265+
let avax_state = MonitoredHashChainState::new(
266+
AVAX_CHAIN.clone(),
267+
Default::default(),
268+
"avalanche".into(),
269+
PROVIDER,
270+
);
271+
258272
let avax_state = BlockchainState {
259273
id: "avalanche".into(),
260274
network_id: 43114,
261-
state: AVAX_CHAIN.clone(),
275+
state: Arc::new(avax_state),
262276
contract: avax_read.clone(),
263277
provider_address: PROVIDER,
264278
reveal_delay_blocks: 2,

apps/fortuna/src/chain/ethereum.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ impl<T: JsonRpcClient + 'static> EntropyReader for PythRandom<Provider<T>> {
309309
let mut event = self.requested_with_callback_filter();
310310
event.filter = event
311311
.filter
312+
.address(self.address())
312313
.from_block(from_block)
313314
.to_block(to_block)
314315
.topic1(provider);

apps/fortuna/src/command/generate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub async fn generate(opts: &GenerateOptions) -> Result<()> {
4848
let mut event = contract.revealed_with_callback_filter();
4949
event.filter = event
5050
.filter
51+
.address(contract.address())
5152
.from_block(last_block_number)
5253
.to_block(current_block_number);
5354

apps/fortuna/src/command/run.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use {
77
eth_utils::traced_client::RpcMetrics,
88
history::History,
99
keeper::{self, keeper_metrics::KeeperMetrics},
10-
state::{HashChainState, PebbleHashChain},
10+
state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
1111
},
1212
anyhow::{anyhow, Error, Result},
1313
axum::Router,
@@ -183,6 +183,7 @@ async fn setup_chain_and_run_keeper(
183183
chain_id,
184184
&chain_config,
185185
rpc_metrics.clone(),
186+
keeper_metrics.clone(),
186187
)
187188
.await?;
188189
chains.write().await.insert(
@@ -210,6 +211,7 @@ async fn setup_chain_state(
210211
chain_id: &ChainId,
211212
chain_config: &EthereumConfig,
212213
rpc_metrics: Arc<RpcMetrics>,
214+
keeper_metrics: Arc<KeeperMetrics>,
213215
) -> Result<BlockchainState> {
214216
let contract = Arc::new(InstrumentedPythContract::from_config(
215217
chain_config,
@@ -284,10 +286,7 @@ async fn setup_chain_state(
284286
hash_chains.push(pebble_hash_chain);
285287
}
286288

287-
let chain_state = HashChainState {
288-
offsets,
289-
hash_chains,
290-
};
289+
let chain_state = HashChainState::new(offsets, hash_chains)?;
291290

292291
if chain_state.reveal(provider_info.original_commitment_sequence_number)?
293292
!= provider_info.original_commitment
@@ -297,9 +296,16 @@ async fn setup_chain_state(
297296
tracing::info!("Root of chain id {} matches commitment", &chain_id);
298297
}
299298

299+
let monitored_chain_state = MonitoredHashChainState::new(
300+
Arc::new(chain_state),
301+
keeper_metrics.clone(),
302+
chain_id.clone(),
303+
*provider,
304+
);
305+
300306
let state = BlockchainState {
301307
id: chain_id.clone(),
302-
state: Arc::new(chain_state),
308+
state: Arc::new(monitored_chain_state),
303309
network_id,
304310
contract,
305311
provider_address: *provider,

apps/fortuna/src/command/setup_provider.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,12 @@ async fn setup_chain_provider(
122122
provider_config.chain_sample_interval,
123123
)
124124
.await?;
125-
let chain_state = HashChainState {
126-
offsets: vec![provider_info
125+
let chain_state = HashChainState::new(
126+
vec![provider_info
127127
.original_commitment_sequence_number
128128
.try_into()?],
129-
hash_chains: vec![hash_chain],
130-
};
129+
vec![hash_chain],
130+
)?;
131131

132132
if chain_state.reveal(provider_info.original_commitment_sequence_number)?
133133
!= provider_info.original_commitment

apps/fortuna/src/keeper/keeper_metrics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub struct KeeperMetrics {
4141
pub final_gas_multiplier: Family<AccountLabel, Histogram>,
4242
pub final_fee_multiplier: Family<AccountLabel, Histogram>,
4343
pub gas_price_estimate: Family<AccountLabel, Gauge<f64, AtomicU64>>,
44+
pub highest_revealed_sequence_number: Family<AccountLabel, Gauge>,
4445
pub accrued_pyth_fees: Family<ChainIdLabel, Gauge<f64, AtomicU64>>,
4546
pub block_timestamp_lag: Family<ChainIdLabel, Gauge>,
4647
pub latest_block_timestamp: Family<ChainIdLabel, Gauge>,
@@ -88,6 +89,7 @@ impl Default for KeeperMetrics {
8889
Histogram::new(vec![100.0, 110.0, 120.0, 140.0, 160.0, 180.0, 200.0].into_iter())
8990
}),
9091
gas_price_estimate: Family::default(),
92+
highest_revealed_sequence_number: Family::default(),
9193
accrued_pyth_fees: Family::default(),
9294
block_timestamp_lag: Family::default(),
9395
latest_block_timestamp: Family::default(),
@@ -223,6 +225,12 @@ impl KeeperMetrics {
223225
keeper_metrics.gas_price_estimate.clone(),
224226
);
225227

228+
writable_registry.register(
229+
"highest_revealed_sequence_number",
230+
"The highest sequence number revealed by the keeper either via callbacks or manual reveal",
231+
keeper_metrics.highest_revealed_sequence_number.clone(),
232+
);
233+
226234
writable_registry.register(
227235
"accrued_pyth_fees",
228236
"Accrued Pyth fees on the contract",

apps/fortuna/src/state.rs

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use {
2-
crate::api::ChainId,
2+
crate::{
3+
api::ChainId,
4+
keeper::keeper_metrics::{AccountLabel, KeeperMetrics},
5+
},
36
anyhow::{ensure, Result},
47
ethers::types::Address,
58
sha3::{Digest, Keccak256},
9+
std::sync::Arc,
610
tokio::task::spawn_blocking,
711
};
812

@@ -127,11 +131,22 @@ impl PebbleHashChain {
127131
/// which requires tracking multiple hash chains here.
128132
pub struct HashChainState {
129133
// The sequence number where the hash chain starts. Must be stored in sorted order.
130-
pub offsets: Vec<usize>,
131-
pub hash_chains: Vec<PebbleHashChain>,
134+
offsets: Vec<usize>,
135+
hash_chains: Vec<PebbleHashChain>,
132136
}
133137

134138
impl HashChainState {
139+
pub fn new(offsets: Vec<usize>, hash_chains: Vec<PebbleHashChain>) -> Result<HashChainState> {
140+
if offsets.len() != hash_chains.len() {
141+
return Err(anyhow::anyhow!(
142+
"Offsets and hash chains must have the same length."
143+
));
144+
}
145+
Ok(HashChainState {
146+
offsets,
147+
hash_chains,
148+
})
149+
}
135150
pub fn from_chain_at_offset(offset: usize, chain: PebbleHashChain) -> HashChainState {
136151
HashChainState {
137152
offsets: vec![offset],
@@ -152,12 +167,54 @@ impl HashChainState {
152167
}
153168
}
154169

170+
pub struct MonitoredHashChainState {
171+
hash_chain_state: Arc<HashChainState>,
172+
metrics: Arc<KeeperMetrics>,
173+
account_label: AccountLabel,
174+
}
175+
impl MonitoredHashChainState {
176+
pub fn new(
177+
hash_chain_state: Arc<HashChainState>,
178+
metrics: Arc<KeeperMetrics>,
179+
chain_id: ChainId,
180+
provider_address: Address,
181+
) -> Self {
182+
Self {
183+
hash_chain_state,
184+
metrics,
185+
account_label: AccountLabel {
186+
chain_id,
187+
address: provider_address.to_string(),
188+
},
189+
}
190+
}
191+
192+
pub fn reveal(&self, sequence_number: u64) -> Result<[u8; 32]> {
193+
let res = self.hash_chain_state.reveal(sequence_number);
194+
if res.is_ok() {
195+
let metric = self
196+
.metrics
197+
.highest_revealed_sequence_number
198+
.get_or_create(&self.account_label);
199+
if metric.get() < sequence_number as i64 {
200+
metric.set(sequence_number as i64);
201+
}
202+
}
203+
res
204+
}
205+
}
206+
155207
#[cfg(test)]
156208
mod test {
157209
use {
158-
crate::state::{HashChainState, PebbleHashChain},
210+
crate::{
211+
keeper::keeper_metrics::{AccountLabel, KeeperMetrics},
212+
state::{HashChainState, MonitoredHashChainState, PebbleHashChain},
213+
},
159214
anyhow::Result,
215+
ethers::types::Address,
160216
sha3::{Digest, Keccak256},
217+
std::{sync::Arc, vec},
161218
};
162219

163220
fn run_hash_chain_test(secret: [u8; 32], length: usize, sample_interval: usize) {
@@ -294,4 +351,65 @@ mod test {
294351

295352
Ok(())
296353
}
354+
#[test]
355+
fn test_inconsistent_lengths() -> Result<()> {
356+
let chain1 = PebbleHashChain::new([0u8; 32], 10, 1);
357+
let chain2 = PebbleHashChain::new([1u8; 32], 10, 1);
358+
359+
let hash_chain_state = HashChainState::new(vec![5], vec![chain1.clone(), chain2.clone()]);
360+
assert!(hash_chain_state.is_err());
361+
let hash_chain_state = HashChainState::new(vec![5, 10], vec![chain1.clone()]);
362+
assert!(hash_chain_state.is_err());
363+
let hash_chain_state =
364+
HashChainState::new(vec![5, 10], vec![chain1.clone(), chain2.clone()]);
365+
assert!(hash_chain_state.is_ok());
366+
367+
Ok(())
368+
}
369+
370+
#[test]
371+
fn test_highest_revealed_sequence_number() {
372+
let chain = PebbleHashChain::new([0u8; 32], 100, 1);
373+
let hash_chain_state = HashChainState::new(vec![0], vec![chain]).unwrap();
374+
let metrics = Arc::new(KeeperMetrics::default());
375+
let provider = Address::random();
376+
let monitored = MonitoredHashChainState::new(
377+
Arc::new(hash_chain_state),
378+
metrics.clone(),
379+
"ethereum".to_string(),
380+
provider,
381+
);
382+
let label = AccountLabel {
383+
chain_id: "ethereum".to_string(),
384+
address: provider.to_string(),
385+
};
386+
387+
assert!(monitored.reveal(5).is_ok());
388+
let current = metrics
389+
.highest_revealed_sequence_number
390+
.get_or_create(&label)
391+
.get();
392+
assert_eq!(current, 5);
393+
394+
assert!(monitored.reveal(15).is_ok());
395+
let current = metrics
396+
.highest_revealed_sequence_number
397+
.get_or_create(&label)
398+
.get();
399+
assert_eq!(current, 15);
400+
401+
assert!(monitored.reveal(10).is_ok());
402+
let current = metrics
403+
.highest_revealed_sequence_number
404+
.get_or_create(&label)
405+
.get();
406+
assert_eq!(current, 15);
407+
408+
assert!(monitored.reveal(1000).is_err());
409+
let current = metrics
410+
.highest_revealed_sequence_number
411+
.get_or_create(&label)
412+
.get();
413+
assert_eq!(current, 15);
414+
}
297415
}

0 commit comments

Comments
 (0)