Skip to content

Commit bff934e

Browse files
Add MEV commission getter/setter for runtime changes
- Add getMevCommission and setMevCommission RPC methods - Share TipManager between TPU and Admin RPC for runtime updates
1 parent cfb70b0 commit bff934e

File tree

7 files changed

+162
-12
lines changed

7 files changed

+162
-12
lines changed

core/src/admin_rpc_post_init.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
cluster_slots_service::cluster_slots::ClusterSlots,
44
proxy::{block_engine_stage::BlockEngineConfig, relayer_stage::RelayerConfig},
55
repair::{outstanding_requests::OutstandingRequests, serve_repair::ShredRepairType},
6+
tip_manager::TipManager,
67
},
78
arc_swap::ArcSwap,
89
solana_gossip::{cluster_info::ClusterInfo, node::NodeMultihoming},
@@ -85,4 +86,5 @@ pub struct AdminRpcRequestMetadataPostInit {
8586
pub relayer_config: Arc<Mutex<RelayerConfig>>,
8687
pub shred_receiver_address: Arc<ArcSwap<Option<SocketAddr>>>,
8788
pub shred_retransmit_receiver_address: Arc<ArcSwap<Option<SocketAddr>>>,
89+
pub tip_manager: Arc<Mutex<TipManager>>,
8890
}

core/src/tip_manager.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,16 @@ impl TipManager {
607607
}))
608608
}
609609
}
610+
611+
/// Updates the MEV commission for this tip manager
612+
pub fn set_commission_bps(&mut self, new_commission_bps: u16) {
613+
self.tip_distribution_account_config.commission_bps = new_commission_bps;
614+
}
615+
616+
/// Gets the current MEV commission
617+
pub fn get_commission_bps(&self) -> u16 {
618+
self.tip_distribution_account_config.commission_bps
619+
}
610620
}
611621

612622
pub fn derive_tip_distribution_account_address(

core/src/tpu.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use {
2626
sigverify::TransactionSigVerifier,
2727
sigverify_stage::SigVerifyStage,
2828
staked_nodes_updater_service::StakedNodesUpdaterService,
29-
tip_manager::{TipManager, TipManagerConfig},
29+
tip_manager::TipManager,
3030
tpu_entry_notifier::TpuEntryNotifier,
3131
validator::{BlockProductionMethod, GeneratorConfig, TransactionStructure},
3232
vortexor_receiver_adapter::VortexorReceiverAdapter,
@@ -186,7 +186,7 @@ impl Tpu {
186186
key_notifiers: Arc<RwLock<KeyUpdaters>>,
187187
block_engine_config: Arc<Mutex<BlockEngineConfig>>,
188188
relayer_config: Arc<Mutex<RelayerConfig>>,
189-
tip_manager_config: TipManagerConfig,
189+
tip_manager: Arc<Mutex<TipManager>>,
190190
shred_receiver_address: Arc<ArcSwap<Option<SocketAddr>>>,
191191
preallocated_bundle_cost: u64,
192192
) -> Self {
@@ -401,8 +401,6 @@ impl Tpu {
401401
duplicate_confirmed_slot_sender,
402402
);
403403

404-
let tip_manager = TipManager::new(tip_manager_config);
405-
406404
let bundle_account_locker = BundleAccountLocker::default();
407405

408406
// The tip program can't be used in BankingStage to avoid someone from stealing tips mid-slot.
@@ -416,7 +414,7 @@ impl Tpu {
416414
.saturating_div(10);
417415

418416
let mut blacklisted_accounts = HashSet::new();
419-
blacklisted_accounts.insert(tip_manager.tip_payment_program_id());
417+
blacklisted_accounts.insert(tip_manager.lock().unwrap().tip_payment_program_id());
420418

421419
let banking_stage = BankingStage::new_num_threads(
422420
block_production_method,
@@ -464,7 +462,7 @@ impl Tpu {
464462
replay_vote_sender,
465463
log_messages_bytes_limit,
466464
exit.clone(),
467-
tip_manager,
465+
tip_manager.lock().unwrap().clone(),
468466
bundle_account_locker,
469467
&block_builder_fee_info,
470468
prioritization_fee_cache,

core/src/validator.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use {
2727
system_monitor_service::{
2828
verify_net_stats_access, SystemMonitorService, SystemMonitorStatsReportConfig,
2929
},
30-
tip_manager::TipManagerConfig,
30+
tip_manager::{TipManager, TipManagerConfig},
3131
tpu::{ForwardingClientOption, Tpu, TpuSockets, DEFAULT_TPU_COALESCE},
3232
tvu::{Tvu, TvuConfig, TvuSockets},
3333
},
@@ -1632,6 +1632,9 @@ impl Validator {
16321632
cancel_tpu_client_next,
16331633
))
16341634
};
1635+
// Create shared TipManager for both TPU and AdminRPC
1636+
let shared_tip_manager = Arc::new(Mutex::new(TipManager::new(config.tip_manager_config.clone())));
1637+
16351638
let tpu = Tpu::new_with_client(
16361639
&cluster_info,
16371640
&poh_recorder,
@@ -1687,7 +1690,7 @@ impl Validator {
16871690
key_notifiers.clone(),
16881691
config.block_engine_config.clone(),
16891692
config.relayer_config.clone(),
1690-
config.tip_manager_config.clone(),
1693+
shared_tip_manager.clone(),
16911694
config.shred_receiver_address.clone(),
16921695
config.preallocated_bundle_cost,
16931696
);
@@ -1728,6 +1731,7 @@ impl Validator {
17281731
relayer_config: config.relayer_config.clone(),
17291732
shred_receiver_address: config.shred_receiver_address.clone(),
17301733
shred_retransmit_receiver_address: config.shred_retransmit_receiver_address.clone(),
1734+
tip_manager: shared_tip_manager.clone(),
17311735
});
17321736

17331737
Ok(Self {

validator/src/admin_rpc_service.rs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use {
1717
relayer_stage::{RelayerConfig, RelayerStage},
1818
},
1919
repair::repair_service,
20+
tip_manager::TipManager,
2021
validator::ValidatorStartProgress,
2122
},
2223
solana_geyser_plugin_manager::GeyserPluginManagerRequest,
@@ -36,7 +37,7 @@ use {
3637
str::FromStr,
3738
sync::{
3839
atomic::{AtomicBool, Ordering},
39-
Arc, RwLock,
40+
Arc, RwLock, Mutex,
4041
},
4142
thread::{self, Builder},
4243
time::{Duration, SystemTime},
@@ -56,6 +57,7 @@ pub struct AdminRpcRequestMetadata {
5657
pub staked_nodes_overrides: Arc<RwLock<HashMap<Pubkey, u64>>>,
5758
pub post_init: Arc<RwLock<Option<AdminRpcRequestMetadataPostInit>>>,
5859
pub rpc_to_plugin_manager_sender: Option<Sender<GeyserPluginManagerRequest>>,
60+
pub tip_manager: Arc<Mutex<TipManager>>,
5961
}
6062

6163
impl Metadata for AdminRpcRequestMetadata {}
@@ -292,6 +294,12 @@ pub trait AdminRpc {
292294
meta: Self::Metadata,
293295
addr: String,
294296
) -> Result<()>;
297+
298+
#[rpc(meta, name = "getMevCommission")]
299+
fn get_mev_commission(&self, meta: Self::Metadata) -> Result<u16>;
300+
301+
#[rpc(meta, name = "setMevCommission")]
302+
fn set_mev_commission(&self, meta: Self::Metadata, commission_bps: u16) -> Result<()>;
295303
}
296304

297305
pub struct AdminRpcImpl;
@@ -871,6 +879,26 @@ impl AdminRpc for AdminRpcImpl {
871879
Ok(())
872880
})
873881
}
882+
883+
fn get_mev_commission(&self, meta: Self::Metadata) -> Result<u16> {
884+
debug!("get_mev_commission request received");
885+
let tip_manager = meta.tip_manager.lock().unwrap();
886+
Ok(tip_manager.get_commission_bps())
887+
}
888+
889+
fn set_mev_commission(&self, meta: Self::Metadata, commission_bps: u16) -> Result<()> {
890+
debug!("set_mev_commission request received");
891+
892+
if commission_bps > 10000 {
893+
return Err(jsonrpc_core::error::Error::invalid_params(
894+
"Commission bps cannot be larger than 100%",
895+
))
896+
}
897+
898+
let mut tip_manager = meta.tip_manager.lock().unwrap();
899+
tip_manager.set_commission_bps(commission_bps);
900+
Ok(())
901+
}
874902
}
875903

876904
impl AdminRpcImpl {
@@ -1070,6 +1098,7 @@ mod tests {
10701098
solana_core::{
10711099
admin_rpc_post_init::{KeyUpdaterType, KeyUpdaters},
10721100
consensus::tower_storage::NullTowerStorage,
1101+
tip_manager::TipManagerConfig,
10731102
validator::{Validator, ValidatorConfig, ValidatorTpuConfig},
10741103
},
10751104
solana_gossip::{cluster_info::ClusterInfo, node::Node},
@@ -1144,6 +1173,7 @@ mod tests {
11441173
let relayer_config = Arc::new(Mutex::new(RelayerConfig::default()));
11451174
let shred_receiver_address = Arc::new(ArcSwap::default());
11461175
let shred_retransmit_receiver_address = Arc::new(ArcSwap::default());
1176+
let shared_tip_manager = Arc::new(Mutex::new(TipManager::new(TipManagerConfig::default())));
11471177
let meta = AdminRpcRequestMetadata {
11481178
rpc_addr: None,
11491179
start_time: SystemTime::now(),
@@ -1170,9 +1200,11 @@ mod tests {
11701200
relayer_config,
11711201
shred_receiver_address,
11721202
shred_retransmit_receiver_address,
1203+
tip_manager: shared_tip_manager.clone(),
11731204
}))),
11741205
staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
11751206
rpc_to_plugin_manager_sender: None,
1207+
tip_manager: shared_tip_manager,
11761208
};
11771209
let mut io = MetaIoHandler::default();
11781210
io.extend_with(AdminRpcImpl.to_delegate());
@@ -1593,6 +1625,7 @@ mod tests {
15931625
post_init: post_init.clone(),
15941626
staked_nodes_overrides: Arc::new(RwLock::new(HashMap::new())),
15951627
rpc_to_plugin_manager_sender: None,
1628+
tip_manager: Arc::new(Mutex::new(TipManager::new(TipManagerConfig::default()))),
15961629
};
15971630

15981631
let _validator = Validator::new(
@@ -1699,4 +1732,105 @@ mod tests {
16991732
.expect("actual response deserialization");
17001733
assert_eq!(actual_parsed_response, expected_parsed_response);
17011734
}
1735+
1736+
#[test]
1737+
fn test_get_mev_commission() {
1738+
let test_validator = TestValidatorWithAdminRpc::new();
1739+
1740+
// Test getting initial commission (should be 0 by default)
1741+
let request = r#"{"jsonrpc":"2.0","id":1,"method":"getMevCommission","params":[]}"#;
1742+
let response = test_validator.handle_request(&request);
1743+
let parsed_response: Value = serde_json::from_str(&response.expect("response")).expect("parse");
1744+
1745+
// Should return 0 (default commission)
1746+
assert_eq!(parsed_response["result"], 0);
1747+
assert_eq!(parsed_response["id"], 1);
1748+
}
1749+
1750+
#[test]
1751+
fn test_set_mev_commission_valid() {
1752+
let test_validator = TestValidatorWithAdminRpc::new();
1753+
1754+
// Test setting valid commission (50% = 5000 bps)
1755+
let request = r#"{"jsonrpc":"2.0","id":1,"method":"setMevCommission","params":[5000]}"#;
1756+
let response = test_validator.handle_request(&request);
1757+
let parsed_response: Value = serde_json::from_str(&response.expect("response")).expect("parse");
1758+
1759+
// Should succeed
1760+
assert_eq!(parsed_response["result"], Value::Null);
1761+
assert_eq!(parsed_response["id"], 1);
1762+
1763+
// Verify the commission was actually set by getting it
1764+
let get_request = r#"{"jsonrpc":"2.0","id":2,"method":"getMevCommission","params":[]}"#;
1765+
let get_response = test_validator.handle_request(&get_request);
1766+
let get_parsed: Value = serde_json::from_str(&get_response.expect("response")).expect("parse");
1767+
1768+
assert_eq!(get_parsed["result"], 5000);
1769+
}
1770+
1771+
#[test]
1772+
fn test_set_mev_commission_max_valid() {
1773+
let test_validator = TestValidatorWithAdminRpc::new();
1774+
1775+
// Test setting maximum valid commission (100% = 10000 bps)
1776+
let request = r#"{"jsonrpc":"2.0","id":1,"method":"setMevCommission","params":[10000]}"#;
1777+
let response = test_validator.handle_request(&request);
1778+
let parsed_response: Value = serde_json::from_str(&response.expect("response")).expect("parse");
1779+
1780+
// Should succeed
1781+
assert_eq!(parsed_response["result"], Value::Null);
1782+
1783+
// Verify it was set
1784+
let get_request = r#"{"jsonrpc":"2.0","id":2,"method":"getMevCommission","params":[]}"#;
1785+
let get_response = test_validator.handle_request(&get_request);
1786+
let get_parsed: Value = serde_json::from_str(&get_response.expect("response")).expect("parse");
1787+
1788+
assert_eq!(get_parsed["result"], 10000);
1789+
}
1790+
1791+
#[test]
1792+
fn test_set_mev_commission_invalid_too_high() {
1793+
let test_validator = TestValidatorWithAdminRpc::new();
1794+
1795+
// Test setting invalid commission (>100% = >10000 bps)
1796+
let request = r#"{"jsonrpc":"2.0","id":1,"method":"setMevCommission","params":[10001]}"#;
1797+
let response = test_validator.handle_request(&request);
1798+
let parsed_response: Value = serde_json::from_str(&response.expect("response")).expect("parse");
1799+
1800+
// Should return error
1801+
assert!(parsed_response["error"].is_object());
1802+
assert_eq!(parsed_response["error"]["code"], -32602); // Invalid params error code
1803+
assert!(parsed_response["error"]["message"].as_str().unwrap().contains("Commission bps cannot be larger than 100%"));
1804+
1805+
// Verify commission wasn't changed (should still be 0)
1806+
let get_request = r#"{"jsonrpc":"2.0","id":2,"method":"getMevCommission","params":[]}"#;
1807+
let get_response = test_validator.handle_request(&get_request);
1808+
let get_parsed: Value = serde_json::from_str(&get_response.expect("response")).expect("parse");
1809+
1810+
assert_eq!(get_parsed["result"], 0);
1811+
}
1812+
1813+
#[test]
1814+
fn test_set_mev_commission_zero() {
1815+
let test_validator = TestValidatorWithAdminRpc::new();
1816+
1817+
// First set to non-zero
1818+
let set_request = r#"{"jsonrpc":"2.0","id":1,"method":"setMevCommission","params":[2500]}"#;
1819+
test_validator.handle_request(&set_request);
1820+
1821+
// Then set back to zero
1822+
let zero_request = r#"{"jsonrpc":"2.0","id":2,"method":"setMevCommission","params":[0]}"#;
1823+
let response = test_validator.handle_request(&zero_request);
1824+
let parsed_response: Value = serde_json::from_str(&response.expect("response")).expect("parse");
1825+
1826+
// Should succeed
1827+
assert_eq!(parsed_response["result"], Value::Null);
1828+
1829+
// Verify it was set to zero
1830+
let get_request = r#"{"jsonrpc":"2.0","id":3,"method":"getMevCommission","params":[]}"#;
1831+
let get_response = test_validator.handle_request(&get_request);
1832+
let get_parsed: Value = serde_json::from_str(&get_response.expect("response")).expect("parse");
1833+
1834+
assert_eq!(get_parsed["result"], 0);
1835+
}
17021836
}

validator/src/bin/solana-test-validator.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use {
3838
net::{IpAddr, Ipv4Addr, SocketAddr},
3939
path::{Path, PathBuf},
4040
process::exit,
41-
sync::{Arc, RwLock},
41+
sync::{Arc, Mutex, RwLock},
4242
time::{Duration, SystemTime, UNIX_EPOCH},
4343
},
4444
};
@@ -431,6 +431,7 @@ fn main() {
431431
post_init: admin_service_post_init,
432432
tower_storage: tower_storage.clone(),
433433
rpc_to_plugin_manager_sender,
434+
tip_manager: Arc::new(Mutex::new(solana_core::tip_manager::TipManager::new(solana_core::tip_manager::TipManagerConfig::default()))),
434435
},
435436
);
436437
let dashboard = if output == Output::Dashboard {

validator/src/commands/run/execute.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use {
3232
repair::repair_handler::RepairHandlerType,
3333
snapshot_packager_service::SnapshotPackagerService,
3434
system_monitor_service::SystemMonitorService,
35-
tip_manager::{TipDistributionAccountConfig, TipManagerConfig},
35+
tip_manager::{TipDistributionAccountConfig, TipManager, TipManagerConfig},
3636
validator::{
3737
is_snapshot_config_valid, BlockProductionMethod, BlockVerificationMethod,
3838
TransactionStructure, Validator, ValidatorConfig, ValidatorError,
@@ -748,7 +748,7 @@ pub fn execute(
748748
block_engine_config,
749749
shred_receiver_address,
750750
shred_retransmit_receiver_address,
751-
tip_manager_config,
751+
tip_manager_config: tip_manager_config.clone(),
752752
preallocated_bundle_cost: value_of(matches, "preallocated_bundle_cost")
753753
.expect("preallocated_bundle_cost set as default"),
754754
};
@@ -844,6 +844,7 @@ pub fn execute(
844844
tower_storage: validator_config.tower_storage.clone(),
845845
staked_nodes_overrides,
846846
rpc_to_plugin_manager_sender,
847+
tip_manager: Arc::new(Mutex::new(TipManager::new(tip_manager_config.clone()))),
847848
},
848849
);
849850

0 commit comments

Comments
 (0)