Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Benchmark Ethereum Pallet #149

Merged
merged 30 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ff61e17
Add skeleton for worst case import_unsigned_header
HCastano Jun 25, 2020
bdeb817
Fix a typo
HCastano Jun 25, 2020
765ed91
Add benchmark test for best case unsigned header import
HCastano Jun 25, 2020
0201c8c
Add finality verification to worst case bench
HCastano Jun 25, 2020
5b87f54
Move `insert_header()` from mock to test_utils
HCastano Jun 25, 2020
6ff214c
Add a rough bench to test a finalizing a "long" chain
HCastano Jun 25, 2020
c4131e3
Try to use complexity parameter for finality bench
HCastano Jun 26, 2020
e2b3461
Improve long finality bench
HCastano Jun 27, 2020
b345e94
Remove stray dot file
HCastano Jun 27, 2020
199c925
Remove old "worst" case bench
HCastano Jun 27, 2020
92a845d
Scribble some ideas down for pruning bench
HCastano Jun 27, 2020
d464d07
Prune headers during benchmarking
HCastano Jun 27, 2020
4475995
Clean up some comments
HCastano Jun 29, 2020
92c1bf0
Make finality bench work for entire range of complexity parameter
HCastano Jun 29, 2020
4174441
Place initialization code into a function
HCastano Jun 29, 2020
fdb126f
Add bench for block finalization with caching
HCastano Jun 29, 2020
a8e6c56
First attempt at bench with receipts
HCastano Jun 30, 2020
c032a96
Try and trigger validator set change
HCastano Jul 1, 2020
044b2e2
Merge branch 'master' into hc-bench-eth-pallet
HCastano Jul 2, 2020
ca5c1f2
Perform a validator set change during benchmarking
HCastano Jul 2, 2020
b21d07a
Move `validators_change_receipt()` to shared location
HCastano Jul 2, 2020
751929e
Extract a test receipt root into a constant
HCastano Jul 2, 2020
d5eab22
Clean up description of pruning bench
HCastano Jul 3, 2020
1c85d7b
Fix cache and pruning tests
HCastano Jul 3, 2020
51245da
Remove unecessary `build_custom_header` usage
HCastano Jul 3, 2020
a6517ae
Get rid of warnings
HCastano Jul 3, 2020
f8ddaef
Remove code duplication comment
HCastano Jul 5, 2020
029d966
Increase the range of the complexity parameter
HCastano Jul 5, 2020
33035d1
Use dynamic number of receipts while benchmarking
HCastano Jul 6, 2020
777500a
Prune a dynamic number of headers
HCastano Jul 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 166 additions & 4 deletions modules/ethereum/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@

use super::*;

use crate::test_utils::{build_custom_header, build_genesis_header, validator_utils::*};
use crate::finality::FinalityAncestor;
use crate::test_utils::{build_custom_header, build_genesis_header, insert_header, validator_utils::*, HeaderBuilder};

use frame_benchmarking::benchmarks;
use frame_system::RawOrigin;
use primitives::U256;
use primitives::{Address, U256};

// We want to try and benchmark scenario which are going to cause a lot for work for our runtime.
// Some of the ones which we should test that are still missing are:
// - Importing a header with transaction receipts
// - An import which causes a chain re-org
benchmarks! {
_ { }

Expand All @@ -33,7 +38,7 @@ benchmarks! {
import_unsigned_header_best_case {
let n in 1..1000;
HCastano marked this conversation as resolved.
Show resolved Hide resolved

// initialize storage with some initial header
// Initialize storage with some initial header
let initial_header = build_genesis_header(&validator(0));
let initial_header_hash = initial_header.compute_hash();
let initial_difficulty = initial_header.difficulty;
Expand All @@ -55,6 +60,163 @@ benchmarks! {

}: import_unsigned_header(RawOrigin::None, header, None)
verify {
assert_eq!(BridgeStorage::<T>::new().best_block().0.number, 1);
let storage = BridgeStorage::<T>::new();
assert_eq!(storage.best_block().0.number, 1);
assert_eq!(storage.finalized_block().number, 0);
}

// Our goal with this bench is to try and see the effect that finalizing difference ranges of
// blocks has on our import time. As such we need to make sure that we keep the number of
// validators fixed while changing the number blocks finalized (the complixity parameter) by
// importing the last header.
import_unsigned_finality {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing I forgot to add to my top-level comment - imo weight(import_signed_headers) is actually weight(import_unsigned_header) * len(headers) + some_constant, where some_constant stands for recovering signature + iterating headers + invoking callbacks (which are noop in our runtime - not sure how to measure that in general case?). So we may only introduce benchmarks for unsigned version and use results for signed version.

// Our complexity parameter, n, will represent the number of blocks imported before
// finalization.
//
// For two validators this is only going to work for even numbers...
let n in 4..10;
HCastano marked this conversation as resolved.
Show resolved Hide resolved

// This should remain fixed for the bench.
let num_validators: u32 = 2;

let mut storage = BridgeStorage::<T>::new();

// Initialize storage with some initial header
let initial_header = build_genesis_header(&validator(0));
let initial_header_hash = initial_header.compute_hash();
let initial_difficulty = initial_header.difficulty;
let initial_validators = validators_addresses(num_validators as usize);

initialize_storage::<T>(
&initial_header,
initial_difficulty,
&initial_validators,
);

let mut headers = Vec::new();
let mut ancestry: Vec<FinalityAncestor<Option<Address>>> = Vec::new(); // Is this needed?
let mut parent = initial_header.clone();

// Q: Is this a sketchy way of "delaying" finality, or should I be manually editing the
HCastano marked this conversation as resolved.
Show resolved Hide resolved
// "step" of the new blocks?
for i in 1..=n {
let header = build_custom_header(
&validator(0),
&parent,
|mut header| {
header
},
);

let id = header.compute_id();
insert_header(&mut storage, header.clone());
ancestry.push(FinalityAncestor {
id: header.compute_id(),
submitter: None,
signers: vec![header.author].into_iter().collect(),
});
headers.push(header.clone());
parent = header;
}

// NOTE: I should look into using `sign_by_set()`
let last_header = headers.last().unwrap().clone();
let last_authority = validator(1);

// Need to make sure that the header we're going to import hasn't been inserted
// into storage already
let header = build_custom_header(
&last_authority,
&last_header,
|mut header| {
header
},
);
}: import_unsigned_header(RawOrigin::None, header, None)
verify {
let storage = BridgeStorage::<T>::new();
assert_eq!(storage.best_block().0.number, (n + 1) as u64);
assert_eq!(storage.finalized_block().number, n as u64);
}

// The default pruning range is 10 blocks behind. We'll start with this for the bench, but we
// should move to a "dynamic" strategy based off the complexity parameter
//
// Look at `headers_are_pruned_during_import()` test from `import.rs`
//
// So it looks like we're constrained by: MAX_BLOCKS_TO_PRUNE_IN_SINGLE_IMPORT= 8
//
// So it doesn't matter how we set the pruning window or how many blocks we build because at the
// end of the day we can only prune that many blocks
import_unsigned_pruning {
// The default pruning strategy is to keep 10 headers, so let's build more than 10
let n in 10..20;

let mut storage = BridgeStorage::<T>::new();

// TODO: Wrap this so we don't repeat so much between benches
// Initialize storage with some initial header
let initial_header = build_genesis_header(&validator(0));
let initial_header_hash = initial_header.compute_hash();
let initial_difficulty = initial_header.difficulty;
let validators = validators(3);

initialize_storage::<T>(
&initial_header,
initial_difficulty,
&validators_addresses(3),
);

// Want to prune eligible blocks between [0, 10)
BlocksToPrune::put(PruningRange {
oldest_unpruned_block: 0,
oldest_block_to_keep: 10,
});

let mut parent = initial_header;
for i in 1..=n {
let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
let id = header.compute_id();
insert_header(&mut storage, header.clone());
parent = header;
}

let header = HeaderBuilder::with_parent(&parent).sign_by_set(&validators);
}: import_unsigned_header(RawOrigin::None, header, None)
verify {
let storage = BridgeStorage::<T>::new();
assert_eq!(storage.best_block().0.number, (n + 1) as u64);

// We're limited to pruning only 8 blocks per import
assert!(HeadersByNumber::get(&0).is_none());
assert!(HeadersByNumber::get(&7).is_none());
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{run_test, TestRuntime};
use frame_support::assert_ok;

#[test]
fn insert_unsigned_header_best_case() {
run_test(2, |_| {
assert_ok!(test_benchmark_import_unsigned_header_best_case::<TestRuntime>());
});
}

#[test]
fn insert_unsigned_header_finality() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_finality::<TestRuntime>());
});
}

#[test]
fn insert_unsigned_header_pruning() {
run_test(1, |_| {
assert_ok!(test_benchmark_import_unsigned_pruning::<TestRuntime>());
});
}
}
8 changes: 7 additions & 1 deletion modules/ethereum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ decl_storage! {
// the initial blocks should be selected so that:
// 1) it doesn't signal validators changes;
// 2) there are no scheduled validators changes from previous blocks;
// 3) (implied) all direct children of initial block are authred by the same validators set.
// 3) (implied) all direct children of initial block are authored by the same validators set.

assert!(
!config.initial_validators.is_empty(),
Expand Down Expand Up @@ -563,6 +563,7 @@ impl<T: Trait> BridgeStorage<T> {
// start pruning blocks
let begin = new_pruning_range.oldest_unpruned_block;
let end = new_pruning_range.oldest_block_to_keep;
frame_support::debug::trace!(target: "runtime", "Pruning blocks in range [{}..{})", begin, end);
for number in begin..end {
// if we can't prune anything => break
if max_blocks_to_prune == 0 {
Expand All @@ -588,6 +589,11 @@ impl<T: Trait> BridgeStorage<T> {

// we have pruned all headers at number
new_pruning_range.oldest_unpruned_block = number + 1;
frame_support::debug::trace!(
target: "runtime",
"Oldest unpruned PoA header is now: {}",
new_pruning_range.oldest_unpruned_block,
);
}

// update pruning range in storage
Expand Down
16 changes: 1 addition & 15 deletions modules/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

pub use crate::test_utils::{validator_utils::*, HeaderBuilder, GAS_LIMIT};
pub use crate::test_utils::{insert_header, validator_utils::*, HeaderBuilder, GAS_LIMIT};
pub use primitives::signatures::secret_to_address;

use crate::finality::FinalityVotes;
Expand Down Expand Up @@ -149,20 +149,6 @@ pub fn run_test_with_genesis<T>(genesis: Header, total_validators: usize, test:
})
}

/// Insert unverified header into storage.
pub fn insert_header<S: Storage>(storage: &mut S, header: Header) {
storage.insert_header(HeaderToImport {
context: storage.import_context(None, &header.parent_hash).unwrap(),
is_best: true,
id: header.compute_id(),
header,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
finality_votes: FinalityVotes::default(),
});
}

/// Pruning strategy that keeps 10 headers behind best block.
pub struct KeepSomeHeadersBehindBest(pub u64);

Expand Down
16 changes: 16 additions & 0 deletions modules/ethereum/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
//!
//! On the other hand, they may be used directly by the bechmarking module.

use crate::finality::FinalityVotes;
use crate::verification::calculate_score;
use crate::{HeaderToImport, Storage};

use primitives::{
rlp_encode,
Expand Down Expand Up @@ -206,6 +208,20 @@ where
custom_header.sign_by(author)
}

/// Insert unverified header into storage.
pub fn insert_header<S: Storage>(storage: &mut S, header: Header) {
storage.insert_header(HeaderToImport {
context: storage.import_context(None, &header.parent_hash).unwrap(),
is_best: true,
id: header.compute_id(),
header,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
finality_votes: FinalityVotes::default(),
});
}

pub mod validator_utils {
use super::*;

Expand Down