diff --git a/fortuna/Cargo.lock b/fortuna/Cargo.lock index 81a150f135..d3a6884d1e 100644 --- a/fortuna/Cargo.lock +++ b/fortuna/Cargo.lock @@ -1486,7 +1486,7 @@ dependencies = [ [[package]] name = "fortuna" -version = "3.2.4" +version = "3.3.4" dependencies = [ "anyhow", "axum", diff --git a/fortuna/Cargo.toml b/fortuna/Cargo.toml index c537a03eab..3532e0f93d 100644 --- a/fortuna/Cargo.toml +++ b/fortuna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fortuna" -version = "3.2.4" +version = "3.3.4" edition = "2021" [dependencies] diff --git a/fortuna/src/api.rs b/fortuna/src/api.rs index 6a61da86f5..c3a0b649d4 100644 --- a/fortuna/src/api.rs +++ b/fortuna/src/api.rs @@ -2,6 +2,7 @@ use { crate::{ chain::reader::{ BlockNumber, + BlockStatus, EntropyReader, }, state::HashChainState, @@ -73,14 +74,17 @@ impl ApiState { #[derive(Clone)] pub struct BlockchainState { /// The hash chain(s) required to serve random numbers for this blockchain - pub state: Arc, + pub state: Arc, /// The contract that the server is fulfilling requests for. - pub contract: Arc, + pub contract: Arc, /// The address of the provider that this server is operating for. - pub provider_address: Address, + pub provider_address: Address, /// The server will wait for this many block confirmations of a request before revealing /// the random number. - pub reveal_delay_blocks: BlockNumber, + pub reveal_delay_blocks: BlockNumber, + /// The BlockStatus of the block that is considered to be confirmed on the blockchain. + /// For eg., Finalized, Safe + pub confirmed_block_status: BlockStatus, } pub struct Metrics { @@ -203,7 +207,10 @@ mod test { BlockchainState, GetRandomValueResponse, }, - chain::reader::mock::MockEntropyReader, + chain::reader::{ + mock::MockEntropyReader, + BlockStatus, + }, state::{ HashChainState, PebbleHashChain, @@ -238,19 +245,21 @@ mod test { let eth_read = Arc::new(MockEntropyReader::with_requests(10, &[])); let eth_state = BlockchainState { - state: ETH_CHAIN.clone(), - contract: eth_read.clone(), - provider_address: PROVIDER, - reveal_delay_blocks: 1, + state: ETH_CHAIN.clone(), + contract: eth_read.clone(), + provider_address: PROVIDER, + reveal_delay_blocks: 1, + confirmed_block_status: BlockStatus::Latest, }; let avax_read = Arc::new(MockEntropyReader::with_requests(10, &[])); let avax_state = BlockchainState { - state: AVAX_CHAIN.clone(), - contract: avax_read.clone(), - provider_address: PROVIDER, - reveal_delay_blocks: 2, + state: AVAX_CHAIN.clone(), + contract: avax_read.clone(), + provider_address: PROVIDER, + reveal_delay_blocks: 2, + confirmed_block_status: BlockStatus::Latest, }; let api_state = ApiState::new(&[ diff --git a/fortuna/src/api/revelation.rs b/fortuna/src/api/revelation.rs index ee3e371da6..22946da3c9 100644 --- a/fortuna/src/api/revelation.rs +++ b/fortuna/src/api/revelation.rs @@ -62,7 +62,9 @@ pub async fn revelation( let maybe_request_fut = state.contract.get_request(state.provider_address, sequence); - let current_block_number_fut = state.contract.get_block_number(); + let current_block_number_fut = state + .contract + .get_block_number(state.confirmed_block_status); let (maybe_request, current_block_number) = try_join!(maybe_request_fut, current_block_number_fut).map_err(|e| { diff --git a/fortuna/src/chain/ethereum.rs b/fortuna/src/chain/ethereum.rs index a167ca68d8..e47db3f7dd 100644 --- a/fortuna/src/chain/ethereum.rs +++ b/fortuna/src/chain/ethereum.rs @@ -1,16 +1,16 @@ use { crate::{ - chain::{ - reader, - reader::{ - BlockNumber, - EntropyReader, - }, + chain::reader::{ + self, + BlockNumber, + BlockStatus, + EntropyReader, }, config::EthereumConfig, }, anyhow::{ anyhow, + Error, Result, }, axum::async_trait, @@ -39,7 +39,10 @@ use { LocalWallet, Signer, }, - types::transaction::eip2718::TypedTransaction, + types::{ + transaction::eip2718::TypedTransaction, + BlockNumber as EthersBlockNumber, + }, }, sha3::{ Digest, @@ -209,7 +212,17 @@ impl EntropyReader for PythContract { } } - async fn get_block_number(&self) -> Result { - Ok(self.client().get_block_number().await?.as_u64()) + async fn get_block_number(&self, confirmed_block_status: BlockStatus) -> Result { + let block_number: EthersBlockNumber = confirmed_block_status.into(); + let block = self + .client() + .get_block(block_number) + .await? + .ok_or_else(|| Error::msg("pending block confirmation"))?; + + Ok(block + .number + .ok_or_else(|| Error::msg("pending confirmation"))? + .as_u64()) } } diff --git a/fortuna/src/chain/reader.rs b/fortuna/src/chain/reader.rs index cf3922df3e..606d92d4c8 100644 --- a/fortuna/src/chain/reader.rs +++ b/fortuna/src/chain/reader.rs @@ -1,11 +1,37 @@ use { anyhow::Result, axum::async_trait, - ethers::types::Address, + ethers::types::{ + Address, + BlockNumber as EthersBlockNumber, + }, }; pub type BlockNumber = u64; +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, +)] +pub enum BlockStatus { + /// Latest block + #[default] + Latest, + /// Finalized block accepted as canonical + Finalized, + /// Safe head block + Safe, +} + +impl Into for BlockStatus { + fn into(self) -> EthersBlockNumber { + match self { + BlockStatus::Latest => EthersBlockNumber::Latest, + BlockStatus::Finalized => EthersBlockNumber::Finalized, + BlockStatus::Safe => EthersBlockNumber::Safe, + } + } +} + /// EntropyReader is the read-only interface of the Entropy contract. #[async_trait] pub trait EntropyReader: Send + Sync { @@ -15,7 +41,7 @@ pub trait EntropyReader: Send + Sync { async fn get_request(&self, provider: Address, sequence_number: u64) -> Result>; - async fn get_block_number(&self) -> Result; + async fn get_block_number(&self, confirmed_block_status: BlockStatus) -> Result; } /// An in-flight request stored in the contract. @@ -36,6 +62,7 @@ pub mod mock { use { crate::chain::reader::{ BlockNumber, + BlockStatus, EntropyReader, Request, }, @@ -114,7 +141,10 @@ pub mod mock { .map(|r| (*r).clone())) } - async fn get_block_number(&self) -> Result { + async fn get_block_number( + &self, + confirmed_block_status: BlockStatus, + ) -> Result { Ok(*self.block_number.read().unwrap()) } } diff --git a/fortuna/src/command/run.rs b/fortuna/src/command/run.rs index c95f86b74b..bdc53b2c18 100644 --- a/fortuna/src/command/run.rs +++ b/fortuna/src/command/run.rs @@ -93,6 +93,7 @@ pub async fn run(opts: &RunOptions) -> Result<()> { contract, provider_address: opts.provider, reveal_delay_blocks: chain_config.reveal_delay_blocks, + confirmed_block_status: chain_config.confirmed_block_status, }; chains.insert(chain_id.clone(), state); diff --git a/fortuna/src/config.rs b/fortuna/src/config.rs index cd0005a7a4..98a54779c1 100644 --- a/fortuna/src/config.rs +++ b/fortuna/src/config.rs @@ -1,7 +1,10 @@ use { crate::{ api::ChainId, - chain::reader::BlockNumber, + chain::reader::{ + BlockNumber, + BlockStatus, + }, }, anyhow::{ anyhow, @@ -131,10 +134,18 @@ pub struct EthereumConfig { /// Address of a Pyth Randomness contract to interact with. pub contract_addr: Address, - /// How many blocks to wait before revealing the random number. + /// reveal_delay_blocks - The difference between the block number with the + /// confirmed_block_status(see below) and the block number of a request to + /// Entropy should be greater than `reveal_delay_blocks` for Fortuna to reveal + /// its commitment. pub reveal_delay_blocks: BlockNumber, /// Use the legacy transaction format (for networks without EIP 1559) #[serde(default)] pub legacy_tx: bool, + + /// The BlockStatus of the block that is considered confirmed. + /// For example, Finalized, Safe, Latest + #[serde(default)] + pub confirmed_block_status: BlockStatus, }