diff --git a/src/commands/start.py b/src/commands/start.py index 24982a2d..40e2003e 100644 --- a/src/commands/start.py +++ b/src/commands/start.py @@ -41,10 +41,16 @@ '--max-fee-per-gas-gwei', type=int, envvar='MAX_FEE_PER_GAS_GWEI', - help=f'Maximum fee per gas limit for transactions. ' + help=f'Maximum fee per gas for transactions. ' f'Default is {DEFAULT_MAX_FEE_PER_GAS_GWEI} Gwei.', default=DEFAULT_MAX_FEE_PER_GAS_GWEI, ) +@click.option( + '--max-priority-fee-per-gas-gwei', + type=int, + envvar='MAX_PRIORITY_FEE_PER_GAS_GWEI', + help='Maximum priority fee per gas for transactions.', +) @click.option( '--hot-wallet-password-file', type=click.Path(exists=True, file_okay=True, dir_okay=False), @@ -216,6 +222,7 @@ def start( hot_wallet_file: str | None, hot_wallet_password_file: str | None, max_fee_per_gas_gwei: int, + max_priority_fee_per_gas_gwei: int | None, database_dir: str | None, pool_size: int | None, ) -> None: @@ -245,6 +252,7 @@ def start( hot_wallet_file=hot_wallet_file, hot_wallet_password_file=hot_wallet_password_file, max_fee_per_gas_gwei=max_fee_per_gas_gwei, + max_priority_fee_per_gas_gwei=max_priority_fee_per_gas_gwei, database_dir=database_dir, log_level=log_level, log_format=log_format, diff --git a/src/commands/start_api.py b/src/commands/start_api.py index 7d80c485..6194212e 100644 --- a/src/commands/start_api.py +++ b/src/commands/start_api.py @@ -45,10 +45,16 @@ '--max-fee-per-gas-gwei', type=int, envvar='MAX_FEE_PER_GAS_GWEI', - help=f'Maximum fee per gas limit for transactions. ' + help=f'Maximum fee per gas for transactions. ' f'Default is {DEFAULT_MAX_FEE_PER_GAS_GWEI} Gwei.', default=DEFAULT_MAX_FEE_PER_GAS_GWEI, ) +@click.option( + '--max-priority-fee-per-gas-gwei', + type=int, + envvar='MAX_PRIORITY_FEE_PER_GAS_GWEI', + help='Maximum priority fee per gas for transactions.', +) @click.option( '--hot-wallet-password-file', type=click.Path(exists=True, file_okay=True, dir_okay=False), @@ -187,6 +193,7 @@ def start_api( hot_wallet_file: str | None, hot_wallet_password_file: str | None, max_fee_per_gas_gwei: int, + max_priority_fee_per_gas_gwei: int | None, database_dir: str | None, api_host: str, api_port: int, @@ -213,6 +220,7 @@ def start_api( hot_wallet_file=hot_wallet_file, hot_wallet_password_file=hot_wallet_password_file, max_fee_per_gas_gwei=max_fee_per_gas_gwei, + max_priority_fee_per_gas_gwei=max_priority_fee_per_gas_gwei, database_dir=database_dir, log_level=log_level, log_format=log_format, diff --git a/src/common/execution.py b/src/common/execution.py index a9a7326d..0f20e8e6 100644 --- a/src/common/execution.py +++ b/src/common/execution.py @@ -5,8 +5,9 @@ import click from eth_typing import BlockNumber from web3 import Web3 -from web3.exceptions import BadFunctionCallOutput, MethodUnavailable -from web3.types import BlockIdentifier, Wei +from web3._utils.async_transactions import _max_fee_per_gas +from web3.exceptions import BadFunctionCallOutput +from web3.types import BlockIdentifier, TxParams, Wei from src.common.clients import execution_client, ipfs_fetch_client from src.common.contracts import keeper_contract, multicall_contract, vault_contract @@ -149,8 +150,32 @@ async def get_oracles() -> Oracles: ) +async def get_tx_params() -> TxParams: + tx_params: TxParams = {} + + if settings.max_priority_fee_per_gas_gwei: + max_priority_fee_per_gas = Web3.to_wei(settings.max_priority_fee_per_gas_gwei, 'gwei') + + # Reference: `_max_fee_per_gas` in web3/_utils/async_transactions.py + block = await execution_client.eth.get_block('latest') + max_fee_per_gas = Wei(max_priority_fee_per_gas + (2 * block['baseFeePerGas'])) + + tx_params['maxPriorityFeePerGas'] = max_priority_fee_per_gas + tx_params['maxFeePerGas'] = max_fee_per_gas + logger.debug('tx_params %s', tx_params) + + return tx_params + + async def check_gas_price() -> bool: - max_fee_per_gas = await _get_max_fee_per_gas() + if settings.max_priority_fee_per_gas_gwei: + # custom gas-price logic + tx_params = await get_tx_params() + max_fee_per_gas = Wei(int(tx_params['maxFeePerGas'])) + else: + # fallback to logic from web3 + max_fee_per_gas = await _max_fee_per_gas(execution_client, {}) + if max_fee_per_gas >= Web3.to_wei(settings.max_fee_per_gas_gwei, 'gwei'): logging.warning( 'Current gas price (%s gwei) is too high. ' @@ -163,17 +188,6 @@ async def check_gas_price() -> bool: return True -async def _get_max_fee_per_gas() -> Wei: - try: - priority_fee = await execution_client.eth.max_priority_fee - except MethodUnavailable: - priority_fee = await _calculate_median_priority_fee() - latest_block = await execution_client.eth.get_block('latest') - base_fee = latest_block['baseFeePerGas'] - max_fee_per_gas = priority_fee + base_fee - return Wei(max_fee_per_gas) - - async def _calculate_median_priority_fee(block_id: BlockIdentifier = 'latest') -> Wei: block = await execution_client.eth.get_block(block_id) diff --git a/src/config/settings.py b/src/config/settings.py index d0bc9305..b2d546ca 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -57,6 +57,7 @@ class Settings(metaclass=Singleton): hot_wallet_file: Path hot_wallet_password_file: Path max_fee_per_gas_gwei: int + max_priority_fee_per_gas_gwei: int | None database: Path log_level: str @@ -91,6 +92,7 @@ def set( metrics_port: int = DEFAULT_METRICS_PORT, metrics_host: str = DEFAULT_METRICS_HOST, max_fee_per_gas_gwei: int = DEFAULT_MAX_FEE_PER_GAS_GWEI, + max_priority_fee_per_gas_gwei: int | None = None, deposit_data_file: str | None = None, keystores_dir: str | None = None, keystores_password_file: str | None = None, @@ -121,6 +123,7 @@ def set( self.metrics_host = metrics_host self.metrics_port = metrics_port self.max_fee_per_gas_gwei = max_fee_per_gas_gwei + self.max_priority_fee_per_gas_gwei = max_priority_fee_per_gas_gwei self.deposit_data_file = ( Path(deposit_data_file) if deposit_data_file else vault_dir / 'deposit_data.json' diff --git a/src/harvest/execution.py b/src/harvest/execution.py index 55688e4b..5d84b608 100644 --- a/src/harvest/execution.py +++ b/src/harvest/execution.py @@ -5,6 +5,7 @@ from src.common.clients import execution_client from src.common.contracts import vault_contract +from src.common.execution import get_tx_params from src.common.typings import HarvestParams from src.common.utils import format_error from src.config.networks import ETH_NETWORKS @@ -19,6 +20,7 @@ async def submit_harvest_transaction(harvest_params: HarvestParams) -> HexStr | logger.info('Submitting harvest transaction...') try: + tx_params = await get_tx_params() tx = await vault_contract.functions.updateState( ( harvest_params.rewards_root, @@ -26,7 +28,7 @@ async def submit_harvest_transaction(harvest_params: HarvestParams) -> HexStr | harvest_params.unlocked_mev_reward, harvest_params.proof, ) - ).transact() + ).transact(tx_params) except Exception as e: logger.error('Failed to harvest: %s', format_error(e)) if settings.verbose: diff --git a/src/validators/execution.py b/src/validators/execution.py index aeb9db6f..54de2104 100644 --- a/src/validators/execution.py +++ b/src/validators/execution.py @@ -15,6 +15,7 @@ validators_registry_contract, vault_contract, ) +from src.common.execution import get_tx_params from src.common.ipfs import fetch_harvest_params from src.common.metrics import metrics from src.common.typings import OraclesApproval @@ -249,6 +250,8 @@ async def register_single_validator( multi_proof.proof, ] try: + tx_params = await get_tx_params() + if update_state_call is not None: register_call = vault_contract.encode_abi( fn_name='registerValidator', @@ -256,9 +259,10 @@ async def register_single_validator( ) tx = await vault_contract.functions.multicall( [update_state_call, register_call] - ).transact() + ).transact(tx_params) else: - tx = await vault_contract.functions.registerValidator(*register_call_args).transact() + register_func = vault_contract.functions.registerValidator + tx = await register_func(*register_call_args).transact(tx_params) except Exception as e: logger.error('Failed to register validator: %s', format_error(e)) if settings.verbose: @@ -305,6 +309,8 @@ async def register_multiple_validator( multi_proof.proof, ] try: + tx_params = await get_tx_params() + if update_state_call is not None: register_call = vault_contract.encode_abi( fn_name='registerValidators', @@ -312,9 +318,10 @@ async def register_multiple_validator( ) tx = await vault_contract.functions.multicall( [update_state_call, register_call] - ).transact() + ).transact(tx_params) else: - tx = await vault_contract.functions.registerValidators(*register_call_args).transact() + register_func = vault_contract.functions.registerValidators + tx = await register_func(*register_call_args).transact(tx_params) except Exception as e: logger.error('Failed to register validators: %s', format_error(e)) if settings.verbose: