datahaven/operator/runtime/testnet/tests/governance/benchmarks.rs
undercover-cactus b8cc9eee4b
feat: upgrade to polkadot SDK 2503 (#444)
## Polkadot upgrade 2503

In this PR, we upgrade form Polkadot SDK 2412 to Polkadot SDK 2503. In
order to upgrade the SDK we need to upgrade some dependencies :
StorageHub and frontier simultaneously.


## What changes 

### Trivial changes

* https://github.com/paritytech/polkadot-sdk/pull/7634 -> The new trait
is required in all the pallets using scale encoding.

* https://github.com/paritytech/polkadot-sdk/pull/7043 -> Remove
deprecated `sp-std` and replace with `alloc` or `core`.

* https://github.com/paritytech/polkadot-sdk/pull/6140 -> Accurate
weight reclaim with frame_system::WeightReclaim


### Breaking changes

* https://github.com/paritytech/polkadot-sdk/pull/2072 -> There is a
change in `pallet-referenda`. Now, the tracks are retrieved as a list of
`Track`s. Also, the names of the tracks might have some trailing null
values (`\0`). This means display representation of the tracks' names
must be sanitized.

* https://github.com/paritytech/polkadot-sdk/pull/5723 -> adds the
ability for these pallets to specify their source of the block number.
This is useful when these pallets are migrated from the relay chain to a
parachain and vice versa. (Not entirely sure it is breaking as it is
being marked as backward compatible).

* https://github.com/paritytech/polkadot-sdk/pull/6338 -> Update
Referenda to Support Block Number Provider

### Notable changes

* https://github.com/paritytech/polkadot-sdk/pull/5703 -> Not changes
required in the codebase but impact fastSync mode. Should improve
testing.

* https://github.com/paritytech/polkadot-sdk/pull/5842 -> Removes
`libp2p` types in authority-discovery, and replace them with network
backend agnostic types from `sc-network-types`. The `sc-network`
interface is therefore updated accordingly.

## What changes in Frontier 

* https://github.com/polkadot-evm/frontier/pull/1693 -> Add support for
EIP 7702 which has been enable with Pectra. This EIP add a new field
`AuthorizationList` in Ethereum transaction.

Changes example :

```rust
#[test]
fn validate_transaction_fails_on_filtered_call() {
...
            pallet_evm::Call::<Runtime>::call {
                source: H160::default(),
                target: H160::default(),
                input: Vec::new(),
                value: sp_core::U256::zero(),
                gas_limit: 21000,
                max_fee_per_gas: sp_core::U256::zero(),
                max_priority_fee_per_gas: Some(sp_core::U256::zero()),
                nonce: None,
                access_list: Vec::new(),
                authorization_list: Vec::new(),
            }
            .into(),
```

## ⚠️ Breaking Changes ⚠️

* Upgrade to Stirage hub v0.5.1
* Support for Ethereum latest upgrade (txs now have the
`authoriation_list` for support EIP 7702)

---------

Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 10:04:57 +01:00

605 lines
20 KiB
Rust

// Copyright 2025 DataHaven
// This file is part of DataHaven.
// DataHaven is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// DataHaven is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with DataHaven. If not, see <http://www.gnu.org/licenses/>.
//! 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 alloc::vec::Vec;
use core::str::from_utf8;
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};
/// 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<AccountId> = (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 = <Preimage as StorePreimage>::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<AccountId> = (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: Vec<_> = TracksInfo::tracks().collect();
println!("Testing {} governance tracks", tracks.len());
for track in tracks.iter() {
let track_name = from_utf8(&track.info.name).unwrap();
let track_name = track_name.trim_end_matches('\0');
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_name).into_bytes(),
b"test".to_vec(),
)],
});
assert_ok!(Preimage::note_preimage(
RuntimeOrigin::signed(alice()),
proposal.encode()
));
let bounded_proposal = <Preimage as StorePreimage>::bound(proposal).unwrap();
// 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),
bounded_proposal,
DispatchTime::After(track.info.min_enactment_period),
));
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_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<AccountId> = (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());
}
});
}