//! Benchmarking tests for DataHaven governance system //! //! Performance and stress tests for governance pallets to ensure //! the system can handle high load and scales appropriately with //! the number of participants, proposals, and votes. #![cfg(feature = "runtime-benchmarks")] use crate::common::*; use datahaven_testnet_runtime::{ configs::governance::council::{TechnicalMaxMembers, TechnicalMaxProposals}, governance::TracksInfo, AccountId, Balance, Balances, ConvictionVoting, Preimage, Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, System, TechnicalCommittee, TreasuryCouncil, DAYS, UNIT, }; use frame_support::traits::schedule::DispatchTime; use frame_support::{ assert_ok, dispatch::GetDispatchInfo, traits::{Get, StorePreimage}, }; use pallet_conviction_voting::{AccountVote, Conviction, Vote}; use sp_std::vec::Vec; /// Benchmark council proposal creation with varying member counts #[test] fn benchmark_council_proposal_scaling() { // Test with different council sizes let member_counts = vec![3, 5, 10, 15, 20]; for member_count in member_counts { ExtBuilder::governance().build().execute_with(|| { // Generate members let members: Vec = (0..member_count) .map(|i| AccountId::from([i as u8; 20])) .collect(); setup_technical_committee(members.clone()); let proposal = make_simple_proposal(); let proposal_len = proposal.encoded_size() as u32; // Measure proposal creation time let start_block = System::block_number(); assert_ok!(TechnicalCommittee::propose( RuntimeOrigin::signed(members[0]), (member_count as u32 + 1) / 2, // Majority threshold Box::new(proposal.clone()), proposal_len, )); let end_block = System::block_number(); // In real benchmarks, you'd measure actual execution time // For this test, we just verify it completed successfully assert_eq!(TechnicalCommittee::proposal_count(), 1); println!( "Council size {}: proposal created in {} blocks", member_count, end_block - start_block ); }); } } /// Benchmark voting performance with many participants #[test] fn benchmark_mass_voting_performance() { ExtBuilder::governance().build().execute_with(|| { let proposal = make_simple_proposal(); // Setup referendum assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), proposal.encode() )); let bounded_proposal = ::bound(proposal).unwrap(); assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(frame_system::RawOrigin::Root.into()), bounded_proposal, DispatchTime::After(100) )); // Wait for prepare period and place decision deposit advance_referendum_time(1 * DAYS + 1); assert_ok!(Referenda::place_decision_deposit( RuntimeOrigin::signed(alice()), 0 )); // Simulate mass voting let voter_counts = vec![10, 50, 100]; for voter_count in voter_counts { let start_block = System::block_number(); // Create voters and have them vote for i in 0..voter_count { let voter = AccountId::from([(i % 255) as u8; 32]); // Ensure voter has balance let _ = Balances::make_free_balance_be(&voter, INITIAL_BALANCE); // Try to vote - may fail if referendum isn't ready let _ = ConvictionVoting::vote( RuntimeOrigin::signed(voter), 0, AccountVote::Standard { vote: Vote { aye: i % 2 == 0, conviction: if i % 3 == 0 { Conviction::Locked3x } else { Conviction::Locked1x }, }, balance: 10 * UNIT, }, ); } let end_block = System::block_number(); println!( "Processed {} votes in {} blocks", voter_count, end_block - start_block ); } }); } /// Benchmark referendum lifecycle with multiple tracks #[test] fn benchmark_multi_track_performance() { ExtBuilder::governance().build().execute_with(|| { let referendum_counts = vec![1, 5, 10, 20]; for referendum_count in referendum_counts { let start_block = System::block_number(); // Create multiple referenda across different tracks for i in 0..referendum_count { let proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![(format!(":test:{}", i).into_bytes(), b"value".to_vec())], }); let proposal_hash = make_proposal_hash(&proposal); assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), proposal.encode() )); // Alternate between different origin types to test different tracks let origin = if i % 2 == 0 { frame_system::RawOrigin::Root.into() } else { frame_system::RawOrigin::Signed(alice()).into() }; assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(origin), DispatchTime::After(100 + i as u32 * 10), Box::new(proposal_hash.into()) )); // Place decision deposits assert_ok!(Referenda::place_decision_deposit( RuntimeOrigin::signed(alice()), i as u32 )); // Add some voting assert_ok!(ConvictionVoting::vote( RuntimeOrigin::signed(bob()), i as u32, AccountVote::Standard { vote: Vote { aye: true, conviction: Conviction::Locked1x }, balance: 50 * UNIT } )); } let end_block = System::block_number(); println!( "Created and initialized {} referenda in {} blocks", referendum_count, end_block - start_block ); // Verify all referenda were created for i in 0..referendum_count { assert!(Referenda::referendum_info(i as u32).is_some()); } } }); } /// Benchmark delegation chains and complex voting patterns #[test] fn benchmark_delegation_performance() { ExtBuilder::governance().build().execute_with(|| { let proposal = make_simple_proposal(); let proposal_hash = make_proposal_hash(&proposal); // Setup referendum assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), proposal.encode() )); assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(frame_system::RawOrigin::Root.into()), DispatchTime::After(100), Box::new(proposal_hash.into()) )); assert_ok!(Referenda::place_decision_deposit( RuntimeOrigin::signed(alice()), 0 )); let delegation_counts = vec![5, 20, 50]; let track_class = 0u16; for delegation_count in delegation_counts { let start_block = System::block_number(); // Create delegation chain for i in 0..delegation_count { let delegator = AccountId::from([(i % 255) as u8; 20]); let target = if i == 0 { alice() } else { AccountId::from([((i - 1) % 255) as u8; 20]) }; // Ensure delegator has balance let _ = Balances::mint_into(&delegator, INITIAL_BALANCE); assert_ok!(ConvictionVoting::delegate( RuntimeOrigin::signed(delegator), track_class, target, Conviction::Locked2x, 50 * UNIT )); } // Alice votes, should cascade through delegation chain assert_ok!(ConvictionVoting::vote( RuntimeOrigin::signed(alice()), 0, AccountVote::Standard { vote: Vote { aye: true, conviction: Conviction::Locked1x }, balance: 100 * UNIT } )); let end_block = System::block_number(); println!( "Processed delegation chain of {} delegators in {} blocks", delegation_count, end_block - start_block ); // Test undelegation performance let undelegate_start = System::block_number(); for i in 0..delegation_count { let delegator = AccountId::from([(i % 255) as u8; 20]); assert_ok!(ConvictionVoting::undelegate( RuntimeOrigin::signed(delegator), track_class )); } let undelegate_end = System::block_number(); println!( "Undelegated {} accounts in {} blocks", delegation_count, undelegate_end - undelegate_start ); } }); } /// Benchmark preimage storage and retrieval with large proposals #[test] fn benchmark_preimage_performance() { ExtBuilder::governance().build().execute_with(|| { let data_sizes = vec![1_000, 10_000, 100_000]; // Different proposal sizes in bytes for data_size in data_sizes { // Create large proposal let large_data = vec![0u8; data_size]; let large_proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![(b":large_data".to_vec(), large_data)], }); let proposal_hash = make_proposal_hash(&large_proposal); let start_block = System::block_number(); // Note large preimage assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), large_proposal.encode() )); let note_end = System::block_number(); // Request preimage assert_ok!(Preimage::request_preimage( RuntimeOrigin::signed(alice()), proposal_hash )); let request_end = System::block_number(); // Use preimage in referendum assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(frame_system::RawOrigin::Root.into()), DispatchTime::After(100), Box::new(proposal_hash.into()) )); let submit_end = System::block_number(); println!( "Preimage size {}: note={} blocks, request={} blocks, submit={} blocks", data_size, note_end - start_block, request_end - note_end, submit_end - request_end ); } }); } /// Benchmark council operations under maximum load #[test] fn benchmark_council_maximum_load() { ExtBuilder::governance().build().execute_with(|| { // Test with maximum allowed members let max_members = TechnicalMaxMembers::get() as usize; let members: Vec = (0..max_members) .map(|i| AccountId::from([(i % 255) as u8; 20])) .collect(); setup_technical_committee(members.clone()); // Test maximum concurrent proposals let max_proposals = TechnicalMaxProposals::get() as usize; let start_block = System::block_number(); for i in 0..max_proposals { let proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![(format!(":max_test:{}", i).into_bytes(), b"value".to_vec())], }); let proposal_len = proposal.encoded_size() as u32; assert_ok!(TechnicalCommittee::propose( RuntimeOrigin::signed(members[i % members.len()]), (members.len() as u32 + 1) / 2, Box::new(proposal.clone()), proposal_len, )); } let proposals_end = System::block_number(); // Vote on all proposals with all members let vote_start = System::block_number(); for proposal_index in 0..max_proposals { let proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![(format!(":max_test:{}", proposal_index).into_bytes(), b"value".to_vec())], }); let proposal_hash = make_proposal_hash(&proposal); // Each member votes for (member_index, member) in members.iter().enumerate() { if member_index < (members.len() + 1) / 2 { // Majority votes yes assert_ok!(TechnicalCommittee::vote( RuntimeOrigin::signed(*member), proposal_hash, proposal_index as u32, true, )); } } } let vote_end = System::block_number(); println!( "Maximum load test: {} members, {} proposals created in {} blocks, {} votes processed in {} blocks", max_members, max_proposals, proposals_end - start_block, max_proposals * ((members.len() + 1) / 2), vote_end - vote_start ); // All proposals should be executed due to majority approval assert_eq!(TechnicalCommittee::proposal_count(), 0); }); } /// Benchmark track configuration and switching #[test] fn benchmark_track_operations() { ExtBuilder::governance().build().execute_with(|| { let tracks = TracksInfo::tracks(); println!("Testing {} governance tracks", tracks.len()); for (track_id, track_info) in tracks.iter() { let start_block = System::block_number(); // Create proposal for this track let proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![( format!(":track:{}:{}", track_id, track_info.name).into_bytes(), b"test".to_vec(), )], }); let proposal_hash = make_proposal_hash(&proposal); assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), proposal.encode() )); // Map track to appropriate origin let origin = if *track_id == 0 { frame_system::RawOrigin::Root.into() } else { frame_system::RawOrigin::Signed(alice()).into() }; assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(origin), DispatchTime::After(track_info.min_enactment_period), Box::new(proposal_hash.into()) )); assert_ok!(Referenda::place_decision_deposit( RuntimeOrigin::signed(bob()), *track_id as u32 )); // Test voting on this track assert_ok!(ConvictionVoting::vote( RuntimeOrigin::signed(charlie()), *track_id as u32, AccountVote::Standard { vote: Vote { aye: true, conviction: Conviction::Locked1x }, balance: 100 * UNIT } )); let end_block = System::block_number(); println!( "Track {} ({}): processed in {} blocks (max_deciding: {}, decision_deposit: {})", track_id, track_info.name, end_block - start_block, track_info.max_deciding, track_info.decision_deposit ); } }); } /// Memory usage estimation test #[test] fn benchmark_memory_usage() { ExtBuilder::governance().build().execute_with(|| { println!("Memory usage estimation for governance components:"); // Estimate storage overhead for different components let member_count = 10; let proposal_count = 5; let referendum_count = 3; let voter_count = 100; // Setup components let members: Vec = (0..member_count) .map(|i| AccountId::from([i as u8; 20])) .collect(); setup_technical_committee(members.clone()); // Create proposals for i in 0..proposal_count { let proposal = RuntimeCall::System(frame_system::Call::set_storage { items: vec![(format!(":memory_test:{}", i).into_bytes(), vec![0u8; 1000])], }); let proposal_len = proposal.encoded_size() as u32; assert_ok!(TechnicalCommittee::propose( RuntimeOrigin::signed(members[0]), (member_count + 1) / 2, Box::new(proposal), proposal_len, )); } // Create referenda for i in 0..referendum_count { let proposal = make_simple_proposal(); let proposal_hash = make_proposal_hash(&proposal); assert_ok!(Preimage::note_preimage( RuntimeOrigin::signed(alice()), proposal.encode() )); assert_ok!(Referenda::submit( RuntimeOrigin::signed(alice()), Box::new(frame_system::RawOrigin::Root.into()), DispatchTime::After(100), Box::new(proposal_hash.into()) )); assert_ok!(Referenda::place_decision_deposit( RuntimeOrigin::signed(alice()), i as u32 )); } // Add voters for i in 0..voter_count { let voter = AccountId::from([(i % 255) as u8; 20]); let _ = Balances::mint_into(&voter, INITIAL_BALANCE); assert_ok!(ConvictionVoting::vote( RuntimeOrigin::signed(voter), 0, // Vote on first referendum AccountVote::Standard { vote: Vote { aye: i % 2 == 0, conviction: Conviction::Locked1x }, balance: 10 * UNIT } )); } println!( "Loaded: {} committee members, {} proposals, {} referenda, {} voters", member_count, proposal_count, referendum_count, voter_count ); // In a real benchmark, you'd measure actual memory usage here // For this test, we just verify everything loaded successfully assert_eq!(TechnicalCommittee::members().len(), member_count); assert_eq!(TechnicalCommittee::proposal_count(), proposal_count as u32); for i in 0..referendum_count { assert!(Referenda::referendum_info(i as u32).is_some()); } }); }