Skip to content

Tb/fortuna/use requested v2 event #2845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions apps/fortuna/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*private-key*
.envrc
fortuna.db*
.env*

This file was deleted.

This file was deleted.

This file was deleted.

12 changes: 10 additions & 2 deletions apps/fortuna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "8.1.0"
version = "9.0.0"
edition = "2021"

[lib]
Expand Down Expand Up @@ -46,8 +46,16 @@ chrono = { version = "0.4.38", features = [
backoff = { version = "0.4.0", features = ["futures", "tokio"] }
thiserror = "1.0.61"
futures-locks = "0.7.1"
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] }
sqlx = { version = "0.8", features = [
"runtime-tokio",
"tls-rustls",
"sqlite",
"any",
"postgres",
"chrono",
] }
num-traits = "0.2.19"
dotenv = "0.15.0"

[dev-dependencies]
axum-test = "13.1.1"
35 changes: 19 additions & 16 deletions apps/fortuna/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,38 @@ Each blockchain is configured in `config.yaml`.

## Build & Test

We use sqlx query macros to check the SQL queries at compile time. This requires
a database to be available at build time. Create a `.env` file in the root of the project with the following content:
Fortuna uses Cargo for building and dependency management.
Simply run `cargo build` and `cargo test` to build and test the project.
To run Fortuna locally, see the [Local Development](#local-development) section below.

### Connect a database
Fortuna stores request history in a SQL database and serves it from its explorer API.
Any SQLite or Postgres database is supported. The database connection is sourced from the `DATABASE_URL` env var.
Create a `.env` file in the root of the project with a DB connection string.
```
DATABASE_URL="sqlite:fortuna.db?mode=rwc"
```
If not provided, Fortuna will create and use a SQLite file-based database at `./fortuna.db`, as in the example above.

### Database migrations
Fortuna will automatically apply the schema migrations in the `./migrations` directory when connecting to the database.
To manually administer the migrations, use the `sqlx` tool for cargo. The tool automatically uses the
database connection in the `.env` file.

Install sqlx for cargo with:
Install `sqlx`:
```bash
cargo install sqlx
```

Next, you need to create the database and apply the schema migrations. You can do this by running:

To create the database if needed and apply the migrations:
```bash
cargo sqlx migrate run # automatically picks up the .env file
cargo sqlx migrate run
```
This will create a SQLite database file called `fortuna.db` in the root of the project and apply the schema migrations to it.
This will allow `cargo check` to check the queries against the existing database.

Fortuna uses Cargo for building and dependency management.
Simply run `cargo build` and `cargo test` to build and test the project.

If you have changed any queries in the code, you need to update the .sqlx folder with the new queries:

To restore the database to a fresh state (drop, recreate, apply migrations):
```bash
cargo sqlx prepare
cargo sqlx database reset
```
Please add the changed files in the `.sqlx` folder to your git commit.

## Command-Line Interface

Expand Down Expand Up @@ -124,7 +127,7 @@ To start an instance of the webserver for local testing, you first need to perfo
1. Run `cargo run -- setup-provider` to register a randomness provider for this service. This command
will update the on-chain contracts such that the configured provider key is a randomness provider,
and its on-chain configuration matches `config.yaml`.

1. Review the [Connect a database](#connect-a-database) section above. The default configuration will create a file-based DB.
Once you've completed the setup, simply run the following command to start the service:

```bash
Expand Down
1 change: 1 addition & 0 deletions apps/fortuna/migrations/20250707000000_init.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE IF EXISTS request;
36 changes: 36 additions & 0 deletions apps/fortuna/migrations/20250707000000_init.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
CREATE TABLE request(
chain_id VARCHAR(20) NOT NULL,
network_id INTEGER NOT NULL,
provider VARCHAR(40) NOT NULL,
sequence INTEGER NOT NULL,
created_at INTEGER NOT NULL,
last_updated_at INTEGER NOT NULL,
state VARCHAR(10) NOT NULL,
request_block_number INTEGER NOT NULL,
request_tx_hash VARCHAR(64) NOT NULL,
user_random_number VARCHAR(64) NOT NULL,
sender VARCHAR(40) NOT NULL,
reveal_block_number INTEGER,
reveal_tx_hash VARCHAR(64),
provider_random_number VARCHAR(64),
info TEXT,
gas_used VARCHAR(100),
gas_limit VARCHAR(100) NOT NULL,
PRIMARY KEY (network_id, sequence, provider, request_tx_hash)
);

CREATE INDEX request__network_id__state__created_at ON request(network_id, state, created_at);
CREATE INDEX request__network_id__created_at ON request(network_id, created_at);
CREATE INDEX request__sender__network_id__state__created_at ON request(sender, network_id, state, created_at);
CREATE INDEX request__sender__network_id__created_at ON request(sender, network_id, created_at);
CREATE INDEX request__sender__state__created_at ON request(sender, state, created_at);
CREATE INDEX request__sender__created_at ON request(sender, created_at);
CREATE INDEX request__sequence__network_id__state__created_at ON request(sequence, network_id, state, created_at);
CREATE INDEX request__sequence__network_id__created_at ON request(sequence, network_id, created_at);
CREATE INDEX request__sequence__state__created_at ON request(sequence, state, created_at);
CREATE INDEX request__sequence__created_at ON request(sequence, created_at);
CREATE INDEX request__state__created_at ON request(state, created_at);
CREATE INDEX request__created_at ON request(created_at);

CREATE INDEX request__request_tx_hash ON request (request_tx_hash) WHERE request_tx_hash IS NOT NULL;
CREATE INDEX request__reveal_tx_hash ON request (reveal_tx_hash) WHERE reveal_tx_hash IS NOT NULL;
10 changes: 10 additions & 0 deletions apps/fortuna/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ pub enum StateTag {
Failed,
}

impl std::fmt::Display for StateTag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StateTag::Pending => write!(f, "Pending"),
StateTag::Completed => write!(f, "Completed"),
StateTag::Failed => write!(f, "Failed"),
}
}
}

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct RequestLabel {
pub value: String,
Expand Down
2 changes: 1 addition & 1 deletion apps/fortuna/src/api/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub struct ExplorerQueryParams {
#[derive(Debug, serde::Serialize, utoipa::ToSchema)]
pub struct ExplorerResponse {
pub requests: Vec<RequestStatus>,
pub total_results: u64,
pub total_results: i64,
}

/// Returns the logs of all requests captured by the keeper.
Expand Down
31 changes: 10 additions & 21 deletions apps/fortuna/src/chain/ethereum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use {
crate::{
api::ChainId,
chain::reader::{
self, BlockNumber, BlockStatus, EntropyReader, EntropyRequestInfo,
RequestedWithCallbackEvent,
},
chain::reader::{self, BlockNumber, BlockStatus, EntropyReader, RequestedV2Event},
config::EthereumConfig,
eth_utils::{
eth_gas_oracle::EthProviderOracle,
Expand Down Expand Up @@ -280,6 +277,7 @@ impl<T: JsonRpcClient + 'static> EntropyReader for PythRandom<Provider<T>> {
block_number: request.block_number,
use_blockhash: request.use_blockhash,
callback_status: reader::RequestCallbackStatus::try_from(request.callback_status)?,
gas_limit_10k: request.gas_limit_1_0k,
}))
}

Expand All @@ -302,33 +300,24 @@ impl<T: JsonRpcClient + 'static> EntropyReader for PythRandom<Provider<T>> {
from_block: BlockNumber,
to_block: BlockNumber,
provider: Address,
) -> Result<Vec<RequestedWithCallbackEvent>> {
let mut event = self.requested_with_callback_filter();
) -> Result<Vec<RequestedV2Event>> {
let mut event = self.requested_2_filter();
event.filter = event
.filter
.address(self.address())
.from_block(from_block)
.to_block(to_block)
.topic1(provider);

let res: Vec<(RequestedWithCallbackFilter, LogMeta)> = event.query_with_meta().await?;
let res: Vec<(Requested2Filter, LogMeta)> = event.query_with_meta().await?;
Ok(res
.into_iter()
.map(|(r, meta)| RequestedWithCallbackEvent {
.map(|(r, meta)| RequestedV2Event {
sequence_number: r.sequence_number,
user_random_number: r.user_random_number,
provider_address: r.request.provider,
requestor: r.requestor,
request: EntropyRequestInfo {
provider: r.request.provider,
sequence_number: r.request.sequence_number,
num_hashes: r.request.num_hashes,
commitment: r.request.commitment,
block_number: r.request.block_number,
requester: r.request.requester,
use_blockhash: r.request.use_blockhash,
is_request_with_callback: r.request.is_request_with_callback,
},
user_random_number: r.user_contribution,
provider_address: r.provider,
sender: r.caller,
gas_limit: r.gas_limit,
log_meta: meta,
})
.filter(|r| r.provider_address == provider)
Expand Down
14 changes: 9 additions & 5 deletions apps/fortuna/src/chain/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ pub struct EntropyRequestInfo {
}

#[derive(Clone)]
pub struct RequestedWithCallbackEvent {
pub struct RequestedV2Event {
pub sequence_number: u64,
pub user_random_number: [u8; 32],
pub provider_address: Address,
pub requestor: Address,
pub request: EntropyRequestInfo,
pub sender: Address,
pub gas_limit: u32,
pub log_meta: LogMeta,
}

Expand All @@ -73,7 +73,7 @@ pub trait EntropyReader: Send + Sync {
from_block: BlockNumber,
to_block: BlockNumber,
provider: Address,
) -> Result<Vec<RequestedWithCallbackEvent>>;
) -> Result<Vec<RequestedV2Event>>;

/// Estimate the gas required to reveal a random number with a callback.
async fn estimate_reveal_with_callback_gas(
Expand All @@ -97,6 +97,8 @@ pub struct Request {
pub block_number: BlockNumber,
pub use_blockhash: bool,
pub callback_status: RequestCallbackStatus,
/// The gas limit for the request, in 10k gas units. (i.e., 2 = 20k gas).
pub gas_limit_10k: u16,
}

/// Status values for Request.callback_status
Expand Down Expand Up @@ -169,6 +171,7 @@ pub mod mock {
block_number: b,
use_blockhash: u,
callback_status: RequestCallbackStatus::CallbackNotNecessary,
gas_limit_10k: 0,
})
.collect(),
),
Expand All @@ -189,6 +192,7 @@ pub mod mock {
block_number,
use_blockhash,
callback_status: RequestCallbackStatus::CallbackNotNecessary,
gas_limit_10k: 0,
});
self
}
Expand Down Expand Up @@ -227,7 +231,7 @@ pub mod mock {
_from_block: BlockNumber,
_to_block: BlockNumber,
_provider: Address,
) -> Result<Vec<super::RequestedWithCallbackEvent>> {
) -> Result<Vec<super::RequestedV2Event>> {
Ok(vec![])
}

Expand Down
2 changes: 2 additions & 0 deletions apps/fortuna/src/command/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub async fn run_api(
}

pub async fn run(opts: &RunOptions) -> Result<()> {
// Load environment variables from a .env file if present
let _ = dotenv::dotenv()?;
let config = Config::load(&opts.config.config)?;
let secret = config.provider.secret.load()?.ok_or(anyhow!(
"Please specify a provider secret in the config file."
Expand Down
Loading
Loading