Skip to content

Commit 68fd3f8

Browse files
fix: address PR feedback for fee manager/keeper separation
Co-Authored-By: Tejas Badadare <[email protected]>
1 parent dbbda0b commit 68fd3f8

File tree

4 files changed

+78
-41
lines changed

4 files changed

+78
-41
lines changed

apps/fortuna/src/command/run.rs

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use {
33
api::{self, ApiBlockChainState, BlockchainState, ChainId},
44
chain::ethereum::InstrumentedPythContract,
55
command::register_provider::CommitmentMetadata,
6-
config::{Commitment, Config, EthereumConfig, ProviderConfig, RunOptions},
6+
config::{Commitment, Config, EthereumConfig, KeeperConfig, ProviderConfig, RunOptions},
77
eth_utils::traced_client::RpcMetrics,
88
history::History,
99
keeper::{self, keeper_metrics::KeeperMetrics},
@@ -100,9 +100,6 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
100100
tracing::info!("Not starting keeper service: no keeper private key specified. Please add one to the config if you would like to run the keeper service.")
101101
}
102102

103-
let keeper_replica_config = config.keeper.replica_config.clone();
104-
let keeper_run_config = config.keeper.run_config.clone();
105-
106103
let chains: Arc<RwLock<HashMap<ChainId, ApiBlockChainState>>> = Arc::new(RwLock::new(
107104
config
108105
.chains
@@ -115,28 +112,16 @@ pub async fn run(opts: &RunOptions) -> Result<()> {
115112
keeper_metrics.add_chain(chain_id.clone(), config.provider.address);
116113
let keeper_metrics = keeper_metrics.clone();
117114
let keeper_private_key_option = keeper_private_key_option.clone();
118-
let keeper_replica_config = keeper_replica_config.clone();
119-
let keeper_run_config = keeper_run_config.clone();
120115
let chains = chains.clone();
121116
let secret_copy = secret.clone();
122117
let rpc_metrics = rpc_metrics.clone();
123118
let provider_config = config.provider.clone();
124119
let history = history.clone();
125-
let fee_manager_private_key = config.keeper.fee_manager_private_key.clone();
126-
let known_keeper_addresses = config.keeper.known_keeper_addresses.clone();
120+
let keeper_config_base = config.keeper.clone();
127121
spawn(async move {
128122
loop {
129123
let keeper_config = if keeper_private_key_option.is_some() {
130-
Some(crate::config::KeeperConfig {
131-
private_key: crate::config::SecretString {
132-
value: keeper_private_key_option.clone(),
133-
file: None,
134-
},
135-
fee_manager_private_key: fee_manager_private_key.clone(),
136-
known_keeper_addresses: known_keeper_addresses.clone(),
137-
replica_config: keeper_replica_config.clone(),
138-
run_config: keeper_run_config.clone(),
139-
})
124+
Some(keeper_config_base.clone())
140125
} else {
141126
None
142127
};
@@ -195,7 +180,7 @@ async fn setup_chain_and_run_keeper(
195180
chain_id: &ChainId,
196181
chain_config: EthereumConfig,
197182
keeper_metrics: Arc<KeeperMetrics>,
198-
keeper_config: Option<crate::config::KeeperConfig>,
183+
keeper_config: Option<KeeperConfig>,
199184
chains: Arc<RwLock<HashMap<ChainId, ApiBlockChainState>>>,
200185
secret_copy: &str,
201186
history: Arc<History>,

apps/fortuna/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ pub struct KeeperConfig {
385385

386386
/// The fee manager's private key for fee manager operations.
387387
/// This key is used to withdraw fees from the contract as the fee manager.
388-
/// Multiple replicas can share the same fee manager private key.
388+
/// Multiple replicas can share the same fee manager private key but different keeper keys (`private_key`).
389389
#[serde(default)]
390390
pub fee_manager_private_key: Option<SecretString>,
391391

apps/fortuna/src/keeper.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,17 @@ pub async fn run_keeper_threads(
126126
let fee_manager_private_key = keeper_config
127127
.fee_manager_private_key
128128
.as_ref()
129-
.and_then(|key| key.load().ok())
130-
.flatten();
129+
.ok_or_else(|| {
130+
anyhow::anyhow!(
131+
"Fee manager private key is required when fee withdrawal is enabled"
132+
)
133+
})?
134+
.load()?
135+
.ok_or_else(|| {
136+
anyhow::anyhow!(
137+
"Fee manager private key value is required when fee withdrawal is enabled"
138+
)
139+
})?;
131140
spawn(
132141
withdraw_fees_wrapper(
133142
contract.clone(),

apps/fortuna/src/keeper/fee.rs

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ use {
88
anyhow::{anyhow, Result},
99
ethers::{
1010
middleware::Middleware,
11-
signers::Signer,
12-
types::{Address, U256},
11+
signers::{LocalWallet, Signer},
12+
types::{Address, TransactionRequest, U256},
1313
},
14-
std::sync::Arc,
14+
std::{str::FromStr, sync::Arc},
1515
tokio::time::{self, Duration},
1616
tracing::{self, Instrument},
1717
};
@@ -57,7 +57,7 @@ pub async fn withdraw_fees_wrapper(
5757
provider_address: Address,
5858
poll_interval: Duration,
5959
min_balance: U256,
60-
fee_manager_private_key: Option<String>,
60+
fee_manager_private_key: String,
6161
known_keeper_addresses: Vec<Address>,
6262
) {
6363
loop {
@@ -82,17 +82,12 @@ pub async fn withdraw_fees_if_necessary(
8282
contract: Arc<InstrumentedSignablePythContract>,
8383
provider_address: Address,
8484
min_balance: U256,
85-
fee_manager_private_key: Option<String>,
85+
fee_manager_private_key: String,
8686
known_keeper_addresses: Vec<Address>,
8787
) -> Result<()> {
8888
let provider = contract.provider();
8989
let wallet = contract.wallet();
9090

91-
let keeper_balance = provider
92-
.get_balance(wallet.address(), None)
93-
.await
94-
.map_err(|e| anyhow!("Error while getting balance. error: {:?}", e))?;
95-
9691
if !should_withdraw_fees(
9792
Arc::new(provider.clone()),
9893
wallet.address(),
@@ -103,6 +98,11 @@ pub async fn withdraw_fees_if_necessary(
10398
return Ok(());
10499
}
105100

101+
let keeper_balance = provider
102+
.get_balance(wallet.address(), None)
103+
.await
104+
.map_err(|e| anyhow!("Error while getting balance. error: {:?}", e))?;
105+
106106
let provider_info = contract
107107
.get_provider_info(provider_address)
108108
.call()
@@ -114,16 +114,59 @@ pub async fn withdraw_fees_if_necessary(
114114
if keeper_balance < min_balance && U256::from(fees) > min_balance {
115115
tracing::info!("Claiming accrued fees...");
116116

117-
if let Some(_fee_manager_key) = fee_manager_private_key {
118-
let contract_call = contract.withdraw_as_fee_manager(provider_address, fees);
119-
send_and_confirm(contract_call).await?;
120-
} else {
121-
if provider_info.fee_manager != wallet.address() {
122-
return Err(anyhow!("Fee manager for provider {:?} is not the keeper wallet and no fee manager private key is configured. Fee manager: {:?} Keeper: {:?}", provider_address, provider_info.fee_manager, wallet.address()));
123-
}
117+
let contract_call = contract.withdraw_as_fee_manager(provider_address, fees);
118+
send_and_confirm(contract_call).await?;
119+
120+
let fee_manager_wallet = LocalWallet::from_str(&fee_manager_private_key)
121+
.map_err(|e| anyhow!("Invalid fee manager private key: {:?}", e))?;
122+
let fee_manager_address = fee_manager_wallet.address();
123+
let keeper_address = wallet.address();
124+
125+
if fee_manager_address != keeper_address {
126+
tracing::info!(
127+
"Transferring withdrawn fees from fee manager {:?} to keeper {:?}",
128+
fee_manager_address,
129+
keeper_address
130+
);
131+
132+
let transfer_amount = U256::from(fees);
133+
let chain_id = provider
134+
.get_chainid()
135+
.await
136+
.map_err(|e| anyhow!("Failed to get chain ID: {:?}", e))?;
137+
138+
let gas_price = provider
139+
.get_gas_price()
140+
.await
141+
.map_err(|e| anyhow!("Failed to get gas price: {:?}", e))?;
142+
143+
let nonce = provider
144+
.get_transaction_count(fee_manager_address, None)
145+
.await
146+
.map_err(|e| anyhow!("Failed to get nonce: {:?}", e))?;
147+
148+
let tx = TransactionRequest::new()
149+
.to(keeper_address)
150+
.value(transfer_amount)
151+
.from(fee_manager_address)
152+
.gas(21000) // Standard ETH transfer gas limit
153+
.gas_price(gas_price)
154+
.nonce(nonce)
155+
.chain_id(chain_id.as_u64());
156+
157+
let typed_tx = tx.into();
158+
let signature = fee_manager_wallet
159+
.sign_transaction(&typed_tx)
160+
.await
161+
.map_err(|e| anyhow!("Failed to sign transfer transaction: {:?}", e))?;
162+
163+
let signed_tx = typed_tx.rlp_signed(&signature);
164+
let tx_hash = provider
165+
.send_raw_transaction(signed_tx)
166+
.await
167+
.map_err(|e| anyhow!("Failed to send transfer transaction: {:?}", e))?;
124168

125-
let contract_call = contract.withdraw_as_fee_manager(provider_address, fees);
126-
send_and_confirm(contract_call).await?;
169+
tracing::info!("Transfer transaction sent: {:?}", tx_hash);
127170
}
128171
} else if keeper_balance < min_balance {
129172
// NOTE: This log message triggers a grafana alert. If you want to change the text, please change the alert also.

0 commit comments

Comments
 (0)