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
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.7.2"
version = "6.8.0"
edition = "2021"

[dependencies]
Expand Down
3 changes: 3 additions & 0 deletions apps/fortuna/config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ 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
min_keeper_balance: 100000000000000000

# Provider configuration
Expand Down
8 changes: 8 additions & 0 deletions apps/fortuna/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ 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 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.
/// The fee will be raised if the profit is less than this number.
Expand Down Expand Up @@ -172,6 +176,10 @@ pub struct EthereumConfig {
pub priority_fee_multiplier_pct: u64,
}

fn default_backoff_gas_multiplier_pct() -> u64 {
100
Copy link
Contributor Author

Choose a reason for hiding this comment

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

we may want to set this to some reasonable % in the future, but let's see how the abstract thing goes first.

}

/// 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
53 changes: 48 additions & 5 deletions apps/fortuna/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const UPDATE_COMMITMENTS_INTERVAL: Duration = Duration::from_secs(30);
const UPDATE_COMMITMENTS_THRESHOLD_FACTOR: f64 = 0.95;
/// Rety last N blocks
const RETRY_PREVIOUS_BLOCKS: u64 = 100;
/// By default, we scale the gas estimate by 25% when submitting the tx.
const DEFAULT_GAS_ESTIMATE_MULTIPLIER_PCT: u64 = 125;

#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct AccountLabel {
Expand Down Expand Up @@ -254,6 +256,7 @@ pub async fn run_keeper_threads(
},
contract.clone(),
gas_limit,
chain_eth_config.backoff_gas_multiplier_pct,
chain_state.clone(),
metrics.clone(),
fulfilled_requests_cache.clone(),
Expand All @@ -279,6 +282,7 @@ pub async fn run_keeper_threads(
rx,
Arc::clone(&contract),
gas_limit,
chain_eth_config.backoff_gas_multiplier_pct,
metrics.clone(),
fulfilled_requests_cache.clone(),
)
Expand All @@ -303,7 +307,9 @@ pub async fn run_keeper_threads(
chain_state.provider_address,
ADJUST_FEE_INTERVAL,
chain_eth_config.legacy_tx,
chain_eth_config.gas_limit,
// NOTE: we adjust fees based on the maximum gas that the keeper will submit a callback with.
// This number is *larger* than the configured gas limit, as we pad gas on transaction submission for reliability.
(chain_eth_config.gas_limit * DEFAULT_GAS_ESTIMATE_MULTIPLIER_PCT) / 100,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We were previously multiplying by 5/4 in the function that sends the TX, but we weren't accounting for that when setting the tx fee.

chain_eth_config.min_profit_pct,
chain_eth_config.target_profit_pct,
chain_eth_config.max_profit_pct,
Expand Down Expand Up @@ -372,6 +378,7 @@ pub async fn process_event_with_backoff(
chain_state: BlockchainState,
contract: Arc<InstrumentedSignablePythContract>,
gas_limit: U256,
backoff_gas_multiplier_pct: u64,
metrics: Arc<KeeperMetrics>,
) {
let start_time = std::time::Instant::now();
Expand All @@ -388,13 +395,35 @@ pub async fn process_event_with_backoff(
max_elapsed_time: Some(Duration::from_secs(300)), // retry for 5 minutes
..Default::default()
};

let current_multiplier = Arc::new(AtomicU64::new(DEFAULT_GAS_ESTIMATE_MULTIPLIER_PCT));

match backoff::future::retry_notify(
backoff,
|| async {
process_event(&event, &chain_state, &contract, gas_limit, metrics.clone()).await
let multiplier = current_multiplier.load(std::sync::atomic::Ordering::Relaxed);
process_event(
&event,
&chain_state,
&contract,
gas_limit,
multiplier,
metrics.clone(),
)
.await
},
|e, dur| {
tracing::error!("Error happened at {:?}: {}", dur, e);
let multiplier = current_multiplier.load(std::sync::atomic::Ordering::Relaxed);
tracing::error!(
"Error at duration {:?} with gas multiplier {}: {}",
dur,
multiplier,
e
);
current_multiplier.store(
multiplier.saturating_mul(backoff_gas_multiplier_pct) / 100,
std::sync::atomic::Ordering::Relaxed,
);
},
)
.await
Expand Down Expand Up @@ -436,6 +465,8 @@ pub async fn process_event(
chain_config: &BlockchainState,
contract: &InstrumentedSignablePythContract,
gas_limit: U256,
// A value of 100 submits the tx with the same gas as the estimate.
gas_estimate_multiplier_pct: u64,
metrics: Arc<KeeperMetrics>,
) -> Result<(), backoff::Error<anyhow::Error>> {
// ignore requests that are not for the configured provider
Expand Down Expand Up @@ -466,6 +497,8 @@ pub async fn process_event(
backoff::Error::transient(anyhow!("Error estimating gas for reveal: {:?}", e))
})?;

// The gas limit on the simulated transaction is the configured gas limit on the chain,
// but we are willing to pad the gas a bit to ensure reliable submission.
if gas_estimate > gas_limit {
return Err(backoff::Error::permanent(anyhow!(
"Gas estimate for reveal with callback is higher than the gas limit {} > {}",
Expand All @@ -474,8 +507,10 @@ pub async fn process_event(
)));
}

// Pad the gas estimate by 25% after checking it against the gas limit
let gas_estimate = gas_estimate.saturating_mul(5.into()) / 4;
// Pad the gas estimate after checking it against the simulation gas limit, ensuring that
// the padded gas estimate doesn't exceed the maximum amount of gas we are willing to use.
let gas_estimate = gas_estimate.saturating_mul(gas_estimate_multiplier_pct.into()) / 100;
let gas_estimate = gas_estimate.min((gas_limit * DEFAULT_GAS_ESTIMATE_MULTIPLIER_PCT) / 100);

let contract_call = contract
.reveal_with_callback(
Expand Down Expand Up @@ -589,6 +624,7 @@ pub async fn process_block_range(
block_range: BlockRange,
contract: Arc<InstrumentedSignablePythContract>,
gas_limit: U256,
backoff_gas_multiplier_pct: u64,
chain_state: api::BlockchainState,
metrics: Arc<KeeperMetrics>,
fulfilled_requests_cache: Arc<RwLock<HashSet<u64>>>,
Expand All @@ -612,6 +648,7 @@ pub async fn process_block_range(
},
contract.clone(),
gas_limit,
backoff_gas_multiplier_pct,
chain_state.clone(),
metrics.clone(),
fulfilled_requests_cache.clone(),
Expand All @@ -634,6 +671,7 @@ pub async fn process_single_block_batch(
block_range: BlockRange,
contract: Arc<InstrumentedSignablePythContract>,
gas_limit: U256,
backoff_gas_multiplier_pct: u64,
chain_state: api::BlockchainState,
metrics: Arc<KeeperMetrics>,
fulfilled_requests_cache: Arc<RwLock<HashSet<u64>>>,
Expand All @@ -660,6 +698,7 @@ pub async fn process_single_block_batch(
chain_state.clone(),
contract.clone(),
gas_limit,
backoff_gas_multiplier_pct,
metrics.clone(),
)
.in_current_span(),
Expand Down Expand Up @@ -806,6 +845,7 @@ pub async fn process_new_blocks(
mut rx: mpsc::Receiver<BlockRange>,
contract: Arc<InstrumentedSignablePythContract>,
gas_limit: U256,
backoff_gas_multiplier_pct: u64,
metrics: Arc<KeeperMetrics>,
fulfilled_requests_cache: Arc<RwLock<HashSet<u64>>>,
) {
Expand All @@ -816,6 +856,7 @@ pub async fn process_new_blocks(
block_range,
Arc::clone(&contract),
gas_limit,
backoff_gas_multiplier_pct,
chain_state.clone(),
metrics.clone(),
fulfilled_requests_cache.clone(),
Expand All @@ -832,6 +873,7 @@ pub async fn process_backlog(
backlog_range: BlockRange,
contract: Arc<InstrumentedSignablePythContract>,
gas_limit: U256,
backoff_gas_multiplier_pct: u64,
chain_state: BlockchainState,
metrics: Arc<KeeperMetrics>,
fulfilled_requests_cache: Arc<RwLock<HashSet<u64>>>,
Expand All @@ -841,6 +883,7 @@ pub async fn process_backlog(
backlog_range,
contract,
gas_limit,
backoff_gas_multiplier_pct,
chain_state,
metrics,
fulfilled_requests_cache,
Expand Down
Loading