Skip to content
Merged
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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.0.9] - 2025-01-22

### Added

- Initial release of Blocknative Gas Agent
- Real-time gas price estimation for the Gas Network
- EIP-1559 transaction handling and gas estimation
Expand All @@ -18,18 +19,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- RESTful API endpoints for gas price queries

### Changed

- Improved error handling by replacing anyhow with concrete ModelError types
- Enhanced pending floor settlement changed to Fast settlement
- Optimized network requests by reusing single Reqwest client
- Updated model functions to return FromBlock values

### Fixed

- Fixed clippy warnings and code quality issues
- Resolved EIP-1559 handling edge cases
- Improved model prediction error handling

### Technical Details

- Built with Rust and async/await patterns using Tokio
- Supports multiple blockchain networks and gas estimation models
- Comprehensive testing suite with unit and integration tests
- Production-ready with optimized release profile settings
- Production-ready with optimized release profile settings
6 changes: 5 additions & 1 deletion Cargo.lock

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

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gas-agent"
version = "0.0.9"
version = "0.1.0"
edition = "2021"
description = "Blocknative Gas Agent - Generate real-time gas price estimates for the Gas Network"
homepage = "https://gas.network"
Expand All @@ -21,6 +21,7 @@ strip = true
[dependencies]
alloy = { version = "~0.12.0", default-features = false, features = [
"signer-local",
"serde",
] }

tokio = { version = "~1.44.0", features = ["full"] }
Expand All @@ -43,6 +44,7 @@ dotenv = "~0.15.0"
opentelemetry-prometheus = "~0.17.0"
bytes = "~1.10.1"
hex = "~0.4.3"
sha2 = "~0.10.8"

opentelemetry_sdk = { version = "~0.24.1", default-features = false, features = [
"metrics",
Expand Down
37 changes: 18 additions & 19 deletions EVALUATION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Gas Agent Evaluation

Gas Agents submit gas price predictions for EVM networks to the [Gas Network](https://gas.network/) for evaluation. The *Evaluation Function* scores each agent to determine which is providing the best prediction relative to onchain truth. Since the comparison uses the actual onchain non-zero minimum gas price, the evalution is a lagging measurement in order to wait for the comparison block to arrive.
Gas Agents submit gas price predictions for EVM networks to the [Gas Network](https://gas.network/) for evaluation. The _Evaluation Function_ scores each agent to determine which is providing the best prediction relative to onchain truth. Since the comparison uses the actual onchain non-zero minimum gas price, the evalution is a lagging measurement in order to wait for the comparison block to arrive.

## Evaluation Criteria

Expand All @@ -14,27 +14,27 @@ This results in the following evaluation criteria:
4. **Stability Around Overpayment**: The standard deviation of the overpayment rates of the agent's predictions in the evaluation window.
5. **Liveliness**: The consistency of the agent in delivery predictions in the evaluation window.

# Score Function
## Score Function

The score function is a weighted sum of utility functions, with each feature represented by its own utility function and corresponding weight.

$$
\text{TotalScore}= w_{0}*u_{0}(x_{0})+w_{1}*u_{1}(x_{1})+w_{2}*u_{2}(x_{2})+w_{3}*u_{3}(x_{3})+w_{4}*u_{4}(x_{4})
$$

Each utility function output is bounded from [0,1]. The sum of all weights equal to 1. Thus the TotalScore is bounded from [0,1] A perfect score is 1. Higher is better.
Each utility function output is bounded from [0,1]. The sum of all weights equal to 1. Thus the TotalScore is bounded from [0,1] A perfect score is 1. Higher is better.

| | weight | utility function | raw input (with example) |
| --- | --- | --- | --- |
| inclusion mean | $w_0=0.5$ | $u_0()$ | $x_0=0.9$ |
| stability for inclusion | $w_1 = 0.15$ | $u_1() = e^{-\beta_1*x}$ where $\beta_1=3.2$ | $x_1 = 2.5$ |
| overpayment mean | $w_2=0.15$ | $u_2()=e^{-\beta_2*x}$ where $\beta_2=3.2$ | $x_2 = 1.2$ |
| stability for overpayment | $w_3=0.10$ | $u_3()=e^{-\beta_3*x}$ where $\beta_3=3.2$ | $x_3=3.2$ |
| liveliness | $w_4=0.10$ | $u_4()$ | $x_4=0.9$ |
| | weight | utility function | raw input (with example) |
| ------------------------- | ------------ | -------------------------------------------- | ------------------------ |
| inclusion mean | $w_0=0.5$ | $u_0()$ | $x_0=0.9$ |
| stability for inclusion | $w_1 = 0.15$ | $u_1() = e^{-\beta_1*x}$ where $\beta_1=3.2$ | $x_1 = 2.5$ |
| overpayment mean | $w_2=0.15$ | $u_2()=e^{-\beta_2*x}$ where $\beta_2=3.2$ | $x_2 = 1.2$ |
| stability for overpayment | $w_3=0.10$ | $u_3()=e^{-\beta_3*x}$ where $\beta_3=3.2$ | $x_3=3.2$ |
| liveliness | $w_4=0.10$ | $u_4()$ | $x_4=0.9$ |

Note that the utility functions $u_0()$ and $u_4()$ are redundant, as the inclusion mean is already constrained to the interval [0, 1].

# Score Calculation Breakdown
## Score Calculation Breakdown

A block window is a range of blocks for which an estimate is guaranteed to land on chain. As an example, the block window for GasAgents Ethereum is 1 block. (next block prediction)

Expand All @@ -44,9 +44,9 @@ After a block window is closed the following metrics are calculated and inserted
- overpayment error (estimate - on chain block window minimum)
- timestamp of estimate (used for liveliness)

Inclusion & Overpayment
### Inclusion & Overpayment

Once the array is filled, the performance metrics are calculated. For each subsequent block window, the oldest value in the memory array is replaced, and a new performance metric is calculated after the update.
Once the array is filled, the performance metrics are calculated. For each subsequent block window, the oldest value in the memory array is replaced, and a new performance metric is calculated after the update.

- inclusion rate (number of included estimates / array size (default 10))
- average overpayment error (sum of errors/array size)
Expand All @@ -58,7 +58,7 @@ These performance metrics are inserted into a AgentHistory array. The score metr
- average overpayment averages
- standard deviation of overpayment averages

Liveliness
### Liveliness

For each block window in the fixed-size memory array, a 1 or 0 is assigned depending on whether a prediction was submitted. The liveliness metric is then calculated by summing these values and dividing by the size of the memory array.

Expand All @@ -74,7 +74,7 @@ The utility function transforms these values bounds to [0,1]

$u = exp(- \beta * x)$ where $\beta=3.2$

We selected $\beta$ based on the following constraints.
We selected $\beta$ based on the following constraints.

$$
\begin{cases}u=1 \hspace{0.2em}\text{when}\hspace{0.2em}x=0\hspace{3.6em}(1)\\
Expand All @@ -85,19 +85,18 @@ $u$ represents the utility produced by the given metric.

Constraint (1) ✅

$u = 1$ is the highest possible utility and occurs when
$u = 1$ is the highest possible utility and occurs when

- **The average overpayment is 0**, which means that the estimated gas price exactly matched the true on-chain minimum. There was neither overestimation nor underestimation overall.
- **The coefficient of variation (CV) is 0**, which indicates that the **standard deviation is 0**. There is **no variation** in the measured values.

For example, if the **standard deviation of inclusion** is 0, this means that all inclusion metrics (e.g., inclusion times or probabilities) are **identical** across the dataset.

For example, if the **standard deviation of inclusion** is 0, this means that all inclusion metrics (e.g., inclusion times or probabilities) are **identical** across the dataset.

→ satisfied as $u = exp(0)=1$

Constraint (2) ✅

$u = 0.2$ indicates a low utility and occurs when
$u = 0.2$ indicates a low utility and occurs when

- **The average overpayment is 50% (0.5),** which means that the estimate gas price was 50% more expensive then the on-chain minimum.
- **The coefficient of variation (CV) is 0.5,** which indicates that the spread is half as large as the mean. While a CV of 0.5 typically indicates moderate variation, in our context we aim to reward agents with minimal or no variability in their metric performance.
60 changes: 37 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ To register your agent and get your signing addresses whitelisted:
2. **Save Your Keys**: Securely store the private key for your agent configuration and note the corresponding public address

3. **Submit Whitelist Request**: Contact the Blocknative team with your public address(es):

- **Email**: [support@blocknative.com](mailto:support@blocknative.com)
- **Discord**: Join our community at [https://discord.com/invite/KZaBVME](https://discord.com/invite/KZaBVME)

Expand Down Expand Up @@ -185,11 +184,13 @@ cargo run --release -- start --chains 'YOUR-CONFIG-JSON'
### Development Workflow

1. **Create a feature branch**:

```bash
git checkout -b feature/your-feature-name
```

2. **Make your changes** and ensure code quality:

```bash
# Check for compilation errors
cargo check
Expand All @@ -205,6 +206,7 @@ cargo run --release -- start --chains 'YOUR-CONFIG-JSON'
```

3. **Commit your changes**:

```bash
git add .
git commit -m "Add your feature description"
Expand Down Expand Up @@ -261,19 +263,15 @@ The chain configuration is specified as a JSON array where each object represent
#### ChainConfig Fields

- **`system`** (required): The blockchain system to connect to

- Available options: `"ethereum"`, `"base"`, `"polygon"`

- **`network`** (required): The network within the system

- Available options: `"mainnet"`

- **`json_rpc_url`** (required): The JSON-RPC endpoint URL to poll for new blocks

- Example: `"https://ethereum-rpc.publicnode.com"`

- **`pending_block_data_source`** (optional): Configuration for fetching pending-block (mempool) data

- See [Pending Block Data Source](#pending-block-data-source) section below

- **`agents`** (required): Array of agent configurations to run on this chain
Expand Down Expand Up @@ -359,7 +357,6 @@ Each agent in the `agents` array supports the following configuration:
**Fields:**

- **`kind`** (required): The type of agent to run

- `"node"`: Publishes the standard estimate from the node
- `"target"`: Publishes the actual minimum price for new blocks
- Model-based agents:
Expand Down Expand Up @@ -481,10 +478,14 @@ Your Custom Model Description
Explain what your model does and how it works.
*/

use crate::models::{FromBlock, ModelError, Prediction};
use crate::types::Settlement;
use crate::{distribution::BlockDistribution, utils::round_to_9_places};

pub fn get_prediction_your_custom_model(block_distributions: &[BlockDistribution], pending_block_distribution: &Option<BlockDistribution>) -> (f64, Settlement) {
pub fn get_prediction_your_custom_model(
block_distributions: &[BlockDistribution],
latest_block: u64,
) -> Result<(Prediction, Settlement, FromBlock), ModelError> {
// Your model logic here
//
// block_distributions is a Vec<BlockDistribution> where:
Expand All @@ -494,26 +495,33 @@ pub fn get_prediction_your_custom_model(block_distributions: &[BlockDistribution
// Each BlockDistribution represents gas price buckets from a block
// sorted from oldest to newest blocks

// Example: Get the most recent block
let latest_block = block_distributions.last().unwrap();
let Some(latest_block_distribution) = block_distributions.last() else {
return Err(ModelError::insufficient_data(
"YourCustomModel requires at least one block distribution",
));
};

// Example: Calculate some prediction logic
let mut total_gas_price = 0.0;
let mut total_transactions = 0u32;

for bucket in latest_block {
for bucket in latest_block_distribution {
total_gas_price += bucket.gwei * bucket.count as f64;
total_transactions += bucket.count;
}

let predicted_price = if total_transactions > 0 {
total_gas_price / total_transactions as f64
} else {
1.0 // fallback price
};
if total_transactions == 0 {
return Err(ModelError::insufficient_data(
"YourCustomModel requires blocks with transactions",
));
}

// Return the prediction and settlement time
(round_to_9_places(predicted_price), Settlement::Fast)
let predicted_price = total_gas_price / total_transactions as f64;

Ok((
round_to_9_places(predicted_price),
Settlement::Fast,
latest_block + 1,
))
}
```

Expand All @@ -531,12 +539,15 @@ pub async fn apply_model(
model: &ModelKind,
block_distributions: &[BlockDistribution],
pending_block_distribution: Option<BlockDistribution>,
) -> Result<(f64, Settlement)> {
latest_block: u64,
) -> Result<(Prediction, Settlement, FromBlock), ModelError> {
// ... existing code ...

match model {
// ... existing cases ...
ModelKind::YourCustomModel => Ok(get_prediction_your_custom_model(block_distributions, pending_block_distribution)),
ModelKind::YourCustomModel => {
get_prediction_your_custom_model(block_distributions, latest_block)
}
}
}
```
Expand Down Expand Up @@ -564,10 +575,9 @@ gas-agent start --chains '[{

1. **Understand the Data Structure**: Each `BlockDistribution` contains buckets of gas prices with transaction counts, representing the gas price distribution for that block.

2. **Handle Edge Cases**: Always check for empty distributions and provide fallback values.
2. **Handle Edge Cases**: Validate inputs and return descriptive `ModelError`s when data is insufficient.

3. **Consider Settlement Times**: Choose appropriate `Settlement` values:

- `Immediate`: Next block
- `Fast`: ~15 seconds
- `Medium`: ~15 minutes
Expand Down Expand Up @@ -644,7 +654,7 @@ When you submit a gas price prediction, the Gas Network evaluates its accuracy w
- **Inclusion Rate**: Did your prediction price get onchain within the block window
- **Cost Efficiency**: Percentage overpayment

For details on the *Evaluation Function* used to score your predictions, see the [Evaluation Function](EVALUATION.md).
For details on the _Evaluation Function_ used to score your predictions, see the [Evaluation Function](EVALUATION.md).

## Building for Production

Expand Down Expand Up @@ -679,17 +689,21 @@ Update `CHANGELOG.md` following [Keep a Changelog](https://keepachangelog.com/)
## [0.1.0] - 2025-01-22

### Added

- New feature descriptions
- New model implementations

### Changed

- Breaking changes or significant modifications
- Performance improvements

### Fixed

- Bug fixes and error handling improvements

### Removed

- Deprecated features that were removed
```

Expand Down
Loading