Skip to content

feat(fortuna): configurable escalation policy for transactions. #2244

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

Merged
merged 7 commits into from
Jan 13, 2025
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
2 changes: 1 addition & 1 deletion apps/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 apps/fortuna/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fortuna"
version = "6.8.1"
version = "7.0.0"
edition = "2021"

[dependencies]
Expand Down
27 changes: 20 additions & 7 deletions apps/fortuna/config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@ chains:
# Keeper configuration for the chain
reveal_delay_blocks: 0
gas_limit: 500000
# Increase the transaction gas limit by 10% each time the callback fails
# defaults to 100 (i.e., don't change the gas limit) if not specified.
backoff_gas_multiplier_pct: 110

# Multiplier for the priority fee estimate, as a percentage (i.e., 100 = no change).
# Defaults to 100 if the field is omitted.
priority_fee_multiplier_pct: 100

escalation_policy:
# Pad the first callback transaction's gas estimate by 25%,
# then multiply each successive callback transaction's gas estimate by 10% until the cap is reached.
# All numbers are expressed as percentages where 100 = no change.
initial_gas_multiplier_pct: 125
gas_multiplier_pct: 110
gas_multiplier_cap_pct: 600

# Multiply successive callback transaction's fees by 10% until the cap is reached.
# All numbers are expressed as percentages where 100 = no change.
# (See also priority_fee_multiplier_pct above to generically adjust the priority fee estimates for the chain --
# adjusting that parameter will influence the fee of the first transaction, in addition to other things)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note there isn't an initial value here. Instead you adjust the priority_fee_multiplier_pct above. The reason for that is because the other parameter affects all gas estimates for the chain, including the one that is used to determine the keeper's fee

fee_multiplier_pct: 110
fee_multiplier_cap_pct: 200

min_keeper_balance: 100000000000000000

# Provider configuration
# How much to charge in fees
fee: 1500000000000000

# Multiplier for the priority fee estimate, as a percentage (i.e., 100 = no change).
# Defaults to 100 if the field is omitted.
priority_fee_multiplier_pct: 100

# Configuration for dynamic fees under high gas prices. The keeper will set
# on-chain fees to make between [min_profit_pct, max_profit_pct] of the max callback
# cost in profit per transaction.
Expand Down
112 changes: 100 additions & 12 deletions apps/fortuna/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,13 @@ pub struct EthereumConfig {
/// The gas limit to use for entropy callback transactions.
pub gas_limit: u64,

/// The percentage multiplier to apply to the gas limit for each backoff.
#[serde(default = "default_backoff_gas_multiplier_pct")]
pub backoff_gas_multiplier_pct: u64,
/// The percentage multiplier to apply to priority fee estimates (100 = no change, e.g. 150 = 150% of base fee)
#[serde(default = "default_priority_fee_multiplier_pct")]
pub priority_fee_multiplier_pct: u64,

/// The escalation policy governs how the gas limit and fee are increased during backoff retries.
#[serde(default)]
pub escalation_policy: EscalationPolicyConfig,

/// The minimum percentage profit to earn as a function of the callback cost.
/// For example, 20 means a profit of 20% over the cost of the callback.
Expand Down Expand Up @@ -170,16 +174,104 @@ pub struct EthereumConfig {
/// Maximum number of hashes to record in a request.
/// This should be set according to the maximum gas limit the provider supports for callbacks.
pub max_num_hashes: Option<u32>,

/// The percentage multiplier to apply to the priority fee (100 = no change, e.g. 150 = 150% of base fee)
#[serde(default = "default_priority_fee_multiplier_pct")]
pub priority_fee_multiplier_pct: u64,
}

fn default_backoff_gas_multiplier_pct() -> u64 {
fn default_priority_fee_multiplier_pct() -> u64 {
100
}

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct EscalationPolicyConfig {
/// The initial gas multiplier to apply to the gas limit.
#[serde(default = "default_initial_gas_multiplier_pct")]
pub initial_gas_multiplier_pct: u64,

/// The gas multiplier to apply to the gas limit during backoff retries.
/// The gas on each successive retry is multiplied by this value, with the maximum multiplier capped at `gas_multiplier_cap_pct`.
#[serde(default = "default_gas_multiplier_pct")]
pub gas_multiplier_pct: u64,
/// The maximum gas multiplier to apply to the gas limit during backoff retries.
#[serde(default = "default_gas_multiplier_cap_pct")]
pub gas_multiplier_cap_pct: u64,

/// The fee multiplier to apply to the fee during backoff retries.
/// The initial fee is 100% of the estimate (which itself may be padded based on our chain configuration)
/// The fee on each successive retry is multiplied by this value, with the maximum multiplier capped at `fee_multiplier_cap_pct`.
#[serde(default = "default_fee_multiplier_pct")]
pub fee_multiplier_pct: u64,
#[serde(default = "default_fee_multiplier_cap_pct")]
pub fee_multiplier_cap_pct: u64,
}

fn default_initial_gas_multiplier_pct() -> u64 {
125
}

fn default_gas_multiplier_pct() -> u64 {
110
}

fn default_gas_multiplier_cap_pct() -> u64 {
600
}

fn default_fee_multiplier_pct() -> u64 {
110
}

fn default_fee_multiplier_cap_pct() -> u64 {
200
}

impl Default for EscalationPolicyConfig {
fn default() -> Self {
Self {
initial_gas_multiplier_pct: default_initial_gas_multiplier_pct(),
gas_multiplier_pct: default_gas_multiplier_pct(),
gas_multiplier_cap_pct: default_gas_multiplier_cap_pct(),
fee_multiplier_pct: default_fee_multiplier_pct(),
fee_multiplier_cap_pct: default_fee_multiplier_cap_pct(),
}
}
}

impl EscalationPolicyConfig {
pub fn get_gas_multiplier_pct(&self, num_retries: u64) -> u64 {
self.apply_escalation_policy(
num_retries,
self.initial_gas_multiplier_pct,
self.gas_multiplier_pct,
self.gas_multiplier_cap_pct,
)
}

pub fn get_fee_multiplier_pct(&self, num_retries: u64) -> u64 {
self.apply_escalation_policy(
num_retries,
100,
self.fee_multiplier_pct,
self.fee_multiplier_cap_pct,
)
}

fn apply_escalation_policy(
&self,
num_retries: u64,
initial: u64,
multiplier: u64,
cap: u64,
) -> u64 {
let mut current = initial;
let mut i = 0;
while i < num_retries && current < cap {
current = current.saturating_mul(multiplier) / 100;
i += 1;
}

current.min(cap)
}
}

/// A commitment that the provider used to generate random numbers at some point in the past.
/// These historical commitments need to be stored in the configuration to support transition points where
/// the commitment changes. In theory, this information is stored on the blockchain, but unfortunately it
Expand Down Expand Up @@ -227,10 +319,6 @@ fn default_chain_sample_interval() -> u64 {
1
}

fn default_priority_fee_multiplier_pct() -> u64 {
100
}

/// Configuration values for the keeper service that are shared across chains.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct KeeperConfig {
Expand Down
Loading
Loading