Skip to content

Commit

Permalink
feat(Fortuna): add finality checks (#1322)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dev Kalra authored Feb 27, 2024
1 parent 281ef68 commit eaaa74a
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 30 deletions.
2 changes: 1 addition & 1 deletion fortuna/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion fortuna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "3.2.4"
version = "3.3.4"
edition = "2021"

[dependencies]
Expand Down
35 changes: 22 additions & 13 deletions fortuna/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use {
crate::{
chain::reader::{
BlockNumber,
BlockStatus,
EntropyReader,
},
state::HashChainState,
Expand Down Expand Up @@ -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<HashChainState>,
pub state: Arc<HashChainState>,
/// The contract that the server is fulfilling requests for.
pub contract: Arc<dyn EntropyReader>,
pub contract: Arc<dyn EntropyReader>,
/// 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 {
Expand Down Expand Up @@ -203,7 +207,10 @@ mod test {
BlockchainState,
GetRandomValueResponse,
},
chain::reader::mock::MockEntropyReader,
chain::reader::{
mock::MockEntropyReader,
BlockStatus,
},
state::{
HashChainState,
PebbleHashChain,
Expand Down Expand Up @@ -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(&[
Expand Down
4 changes: 3 additions & 1 deletion fortuna/src/api/revelation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
31 changes: 22 additions & 9 deletions fortuna/src/chain/ethereum.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -39,7 +39,10 @@ use {
LocalWallet,
Signer,
},
types::transaction::eip2718::TypedTransaction,
types::{
transaction::eip2718::TypedTransaction,
BlockNumber as EthersBlockNumber,
},
},
sha3::{
Digest,
Expand Down Expand Up @@ -209,7 +212,17 @@ impl EntropyReader for PythContract {
}
}

async fn get_block_number(&self) -> Result<BlockNumber> {
Ok(self.client().get_block_number().await?.as_u64())
async fn get_block_number(&self, confirmed_block_status: BlockStatus) -> Result<BlockNumber> {
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())
}
}
36 changes: 33 additions & 3 deletions fortuna/src/chain/reader.rs
Original file line number Diff line number Diff line change
@@ -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<EthersBlockNumber> 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 {
Expand All @@ -15,7 +41,7 @@ pub trait EntropyReader: Send + Sync {
async fn get_request(&self, provider: Address, sequence_number: u64)
-> Result<Option<Request>>;

async fn get_block_number(&self) -> Result<BlockNumber>;
async fn get_block_number(&self, confirmed_block_status: BlockStatus) -> Result<BlockNumber>;
}

/// An in-flight request stored in the contract.
Expand All @@ -36,6 +62,7 @@ pub mod mock {
use {
crate::chain::reader::{
BlockNumber,
BlockStatus,
EntropyReader,
Request,
},
Expand Down Expand Up @@ -114,7 +141,10 @@ pub mod mock {
.map(|r| (*r).clone()))
}

async fn get_block_number(&self) -> Result<BlockNumber> {
async fn get_block_number(
&self,
confirmed_block_status: BlockStatus,
) -> Result<BlockNumber> {
Ok(*self.block_number.read().unwrap())
}
}
Expand Down
1 change: 1 addition & 0 deletions fortuna/src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 13 additions & 2 deletions fortuna/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use {
crate::{
api::ChainId,
chain::reader::BlockNumber,
chain::reader::{
BlockNumber,
BlockStatus,
},
},
anyhow::{
anyhow,
Expand Down Expand Up @@ -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,
}

0 comments on commit eaaa74a

Please sign in to comment.