Skip to content

Commit f628e8c

Browse files
kianenigmagithub-actions[bot]seadanda
authored andcommitted
fix epmb solution duplicate issue + add remote mining apparatus to epm (#8585)
Backports a part of #8422 to master so it can be included in ongoing releases sooner. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dónal Murray <[email protected]>
1 parent 4a95dc6 commit f628e8c

14 files changed

Lines changed: 475 additions & 35 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/sdk/src/guides/your_first_node.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ mod tests {
267267
}
268268

269269
#[test]
270+
#[ignore = "is flaky"]
270271
fn works_with_different_block_times() {
271272
test_runtime_preset(PARA_RUNTIME, 100, Some(DEV_RUNTIME_PRESET.into()));
272273
test_runtime_preset(PARA_RUNTIME, 3000, Some(DEV_RUNTIME_PRESET.into()));

prdoc/pr_8585.prdoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
title: fix epmb solution duplicate issue + add remote mining apparatus to epm
2+
doc:
3+
- audience: Runtime Dev
4+
description: |
5+
Prevents the NPoS election process from accepting duplicate voters and targets.
6+
crates:
7+
- name: pallet-election-provider-multi-block
8+
bump: patch
9+
- name: pallet-election-provider-multi-phase
10+
bump: minor
11+
- name: frame-election-provider-solution-type
12+
bump: patch
13+
- name: frame-election-provider-support
14+
bump: patch
15+
- name: sp-npos-elections
16+
bump: major

substrate/frame/election-provider-multi-block/src/unsigned/miner.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,7 +1036,7 @@ mod trimming {
10361036
(40, Support { total: 40, voters: vec![(40, 40)] })
10371037
],
10381038
vec![
1039-
(30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }),
1039+
(30, Support { total: 11, voters: vec![(5, 2), (6, 2), (7, 7)] }),
10401040
(40, Support { total: 7, voters: vec![(5, 3), (6, 4)] })
10411041
],
10421042
vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })]
@@ -1080,7 +1080,7 @@ mod trimming {
10801080
// page only.
10811081
vec![(40, Support { total: 40, voters: vec![(40, 40)] })],
10821082
vec![
1083-
(30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }),
1083+
(30, Support { total: 11, voters: vec![(5, 2), (6, 2), (7, 7)] }),
10841084
(40, Support { total: 7, voters: vec![(5, 3), (6, 4)] })
10851085
],
10861086
vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })]
@@ -1122,7 +1122,7 @@ mod trimming {
11221122
vec![
11231123
vec![],
11241124
vec![
1125-
(30, Support { total: 11, voters: vec![(7, 7), (5, 2), (6, 2)] }),
1125+
(30, Support { total: 11, voters: vec![(5, 2), (6, 2), (7, 7)] }),
11261126
(40, Support { total: 7, voters: vec![(5, 3), (6, 4)] })
11271127
],
11281128
vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })]
@@ -1160,11 +1160,11 @@ mod trimming {
11601160
assert!(VerifierPallet::queued_score().is_some());
11611161

11621162
assert_eq!(
1163-
dbg!(supports),
1163+
supports,
11641164
vec![
11651165
vec![],
11661166
vec![
1167-
(30, Support { total: 9, voters: vec![(7, 7), (6, 2)] }),
1167+
(30, Support { total: 9, voters: vec![(6, 2), (7, 7)] }),
11681168
(40, Support { total: 4, voters: vec![(6, 4)] })
11691169
],
11701170
vec![(40, Support { total: 9, voters: vec![(2, 2), (3, 3), (4, 4)] })]
@@ -1203,7 +1203,7 @@ mod trimming {
12031203
(40, Support { total: 40, voters: vec![(40, 40)] })
12041204
],
12051205
vec![
1206-
(30, Support { total: 9, voters: vec![(7, 7), (6, 2)] }),
1206+
(30, Support { total: 9, voters: vec![(6, 2), (7, 7)] }),
12071207
(40, Support { total: 9, voters: vec![(5, 5), (6, 4)] }) /* notice how
12081208
* 5's stake is
12091209
* re-distributed
@@ -1287,7 +1287,7 @@ mod trimming {
12871287
(40, Support { total: 40, voters: vec![(40, 40)] })
12881288
],
12891289
vec![
1290-
(30, Support { total: 14, voters: vec![(5, 5), (7, 7), (6, 2)] }),
1290+
(30, Support { total: 14, voters: vec![(5, 5), (6, 2), (7, 7)] }),
12911291
(40, Support { total: 4, voters: vec![(6, 4)] })
12921292
],
12931293
vec![(40, Support { total: 7, voters: vec![(3, 3), (4, 4)] })]
@@ -1412,12 +1412,12 @@ mod base_miner {
14121412
assert_eq!(
14131413
supports,
14141414
vec![vec![
1415-
(10, Support { total: 30, voters: vec![(1, 10), (8, 10), (4, 5), (5, 5)] }),
1415+
(10, Support { total: 30, voters: vec![(1, 10), (4, 5), (5, 5), (8, 10)] }),
14161416
(
14171417
40,
14181418
Support {
14191419
total: 40,
1420-
voters: vec![(2, 10), (3, 10), (6, 10), (4, 5), (5, 5)]
1420+
voters: vec![(2, 10), (3, 10), (4, 5), (5, 5), (6, 10)]
14211421
}
14221422
)
14231423
]]
@@ -1472,7 +1472,7 @@ mod base_miner {
14721472
// voter 6 (index 1) is backing 40 (index 3).
14731473
// voter 8 (index 3) is backing 10 (index 0)
14741474
votes1: vec![(1, 3), (3, 0)],
1475-
// voter 5 (index 0) is backing 40 (index 10) and 10 (index 0)
1475+
// voter 5 (index 0) is backing 40 (index 3) and 10 (index 0)
14761476
votes2: vec![(0, [(0, PerU16::from_parts(32768))], 3)],
14771477
..Default::default()
14781478
},
@@ -1500,8 +1500,8 @@ mod base_miner {
15001500
vec![
15011501
// page0, supports from voters 5, 6, 7, 8
15021502
vec![
1503-
(10, Support { total: 15, voters: vec![(8, 10), (5, 5)] }),
1504-
(40, Support { total: 15, voters: vec![(6, 10), (5, 5)] })
1503+
(10, Support { total: 15, voters: vec![(5, 5), (8, 10)] }),
1504+
(40, Support { total: 15, voters: vec![(5, 5), (6, 10)] })
15051505
],
15061506
// page1 supports from voters 1, 2, 3, 4
15071507
vec![
@@ -1591,13 +1591,13 @@ mod base_miner {
15911591
],
15921592
// page 1: 5, 6, 7, 8
15931593
vec![
1594-
(30, Support { total: 20, voters: vec![(7, 10), (5, 5), (6, 5)] }),
1594+
(30, Support { total: 20, voters: vec![(5, 5), (6, 5), (7, 10)] }),
15951595
(40, Support { total: 10, voters: vec![(5, 5), (6, 5)] })
15961596
],
15971597
// page 2: 1, 2, 3, 4
15981598
vec![
15991599
(30, Support { total: 5, voters: vec![(2, 5)] }),
1600-
(40, Support { total: 25, voters: vec![(3, 10), (4, 10), (2, 5)] })
1600+
(40, Support { total: 25, voters: vec![(2, 5), (3, 10), (4, 10)] })
16011601
]
16021602
]
16031603
.try_from_unbounded_paged()
@@ -1761,8 +1761,8 @@ mod base_miner {
17611761
vec![],
17621762
// supports from voters 5, 6, 7, 8
17631763
vec![
1764-
(10, Support { total: 15, voters: vec![(8, 10), (5, 5)] }),
1765-
(40, Support { total: 15, voters: vec![(6, 10), (5, 5)] })
1764+
(10, Support { total: 15, voters: vec![(5, 5), (8, 10)] }),
1765+
(40, Support { total: 15, voters: vec![(5, 5), (6, 10)] })
17661766
],
17671767
// supports from voters 1, 2, 3, 4
17681768
vec![

substrate/frame/election-provider-multi-block/src/verifier/impls.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,8 @@ pub fn feasibility_check_page_inner_with_snapshot<T: MinerConfig>(
898898
let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
899899

900900
// Then convert solution -> assignment. This will fail if any of the indices are
901-
// gibberish.
901+
// gibberish. It will also ensure each assignemnt (voter) is unique, and all targets within it
902+
// are unique.
902903
let assignments = partial_solution
903904
.into_assignment(voter_at, target_at)
904905
.map_err::<FeasibilityError, _>(Into::into)?;

substrate/frame/election-provider-multi-block/src/verifier/tests.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use frame_election_provider_support::Support;
2828
use frame_support::{assert_noop, assert_ok};
2929
use sp_core::bounded_vec;
3030
use sp_npos_elections::ElectionScore;
31-
use sp_runtime::{traits::Bounded, Perbill};
31+
use sp_runtime::{traits::Bounded, PerU16, Perbill};
3232

3333
mod feasibility_check {
3434
use super::*;
@@ -189,6 +189,52 @@ mod feasibility_check {
189189
})
190190
}
191191

192+
#[test]
193+
fn prevents_duplicate_voter_index() {
194+
ExtBuilder::verifier().pages(1).build_and_execute(|| {
195+
roll_to_snapshot_created();
196+
197+
// let's build a manual, bogus solution with duplicate voters, on top of page 0 of
198+
// snapshot (see `mock/staking.rs`).
199+
let faulty_page = TestNposSolution {
200+
// voter index 0 is giving 100% of stake to target index 0
201+
votes1: vec![(0, 0)],
202+
// and again 50% to target index 0 and target index 1. Both votes are "valid",
203+
// as in they are in the snapshot.
204+
votes2: vec![(0, [(0, PerU16::from_percent(50))], 1)],
205+
..Default::default()
206+
};
207+
208+
assert_noop!(
209+
VerifierPallet::feasibility_check_page_inner(faulty_page, 0),
210+
FeasibilityError::NposElection(
211+
frame_election_provider_support::Error::DuplicateVoter
212+
),
213+
);
214+
});
215+
}
216+
217+
#[test]
218+
fn prevents_duplicate_target_index() {
219+
ExtBuilder::verifier().pages(1).build_and_execute(|| {
220+
roll_to_snapshot_created();
221+
222+
// A bad solution with duplicate targets for a single voter in votes2.
223+
let faulty_page = TestNposSolution {
224+
// 50% to 0, and then the rest to 0 again, not valid.
225+
votes2: vec![(0, [(0, PerU16::from_percent(50))], 0)],
226+
..Default::default()
227+
};
228+
229+
assert_noop!(
230+
VerifierPallet::feasibility_check_page_inner(faulty_page, 0),
231+
FeasibilityError::NposElection(
232+
frame_election_provider_support::Error::DuplicateTarget
233+
),
234+
);
235+
});
236+
}
237+
192238
#[test]
193239
fn heuristic_max_backers_per_winner_per_page() {
194240
ExtBuilder::verifier().max_backers_per_winner(2).build_and_execute(|| {

substrate/frame/election-provider-multi-phase/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ frame-benchmarking = { optional = true, workspace = true }
3434
rand = { features = ["alloc", "small_rng"], optional = true, workspace = true }
3535
strum = { features = ["derive"], optional = true, workspace = true }
3636

37+
# optional for remote testing
38+
hex = { workspace = true, default-features = true, optional = true }
39+
remote-externalities = { workspace = true, default-features = true, optional = true }
40+
tokio = { features = ["macros"], workspace = true, default-features = true, optional = true }
41+
3742
[dev-dependencies]
3843
frame-benchmarking = { workspace = true, default-features = true }
3944
pallet-balances = { workspace = true, default-features = true }
@@ -42,7 +47,9 @@ rand = { workspace = true, default-features = true }
4247
sp-npos-elections = { workspace = true }
4348
sp-tracing = { workspace = true, default-features = true }
4449

50+
4551
[features]
52+
remote-mining = ["hex", "remote-externalities", "tokio"]
4653
default = ["std"]
4754
std = [
4855
"codec/std",

substrate/frame/election-provider-multi-phase/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ use sp_runtime::TryRuntimeError;
279279
mod benchmarking;
280280
#[cfg(test)]
281281
mod mock;
282+
#[cfg(all(test, feature = "remote-mining"))]
283+
mod remote_mining;
282284
#[macro_use]
283285
pub mod helpers;
284286

@@ -489,9 +491,9 @@ where
489491
/// These are stored together because they are often accessed together.
490492
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
491493
#[scale_info(skip_type_params(T))]
492-
pub struct RoundSnapshot<AccountId, DataProvider> {
494+
pub struct RoundSnapshot<AccountId, VoterType> {
493495
/// All of the voters.
494-
pub voters: Vec<DataProvider>,
496+
pub voters: Vec<VoterType>,
495497
/// All of the targets.
496498
pub targets: Vec<AccountId>,
497499
}

0 commit comments

Comments
 (0)