mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 21:01:02 +00:00
grandpa: fix early enactment of forced changes (#7321)
* grandpa: fix early enactment of forced authority set changes * grandpa: add test for early enactment of forced changes * grandpa: fix typo in log message * grandpa: only allow one pending forced change per fork * grandpa: fix tests
This commit is contained in:
@@ -32,14 +32,42 @@ use std::ops::Add;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Error type returned on operations on the `AuthoritySet`.
|
/// Error type returned on operations on the `AuthoritySet`.
|
||||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
#[derive(Debug, derive_more::Display)]
|
||||||
pub enum Error<E> {
|
pub enum Error<N, E> {
|
||||||
#[display("Invalid authority set, either empty or with an authority weight set to 0.")]
|
#[display(fmt = "Invalid authority set, either empty or with an authority weight set to 0.")]
|
||||||
InvalidAuthoritySet,
|
InvalidAuthoritySet,
|
||||||
|
#[display(fmt = "Client error during ancestry lookup: {}", _0)]
|
||||||
|
Client(E),
|
||||||
|
#[display(fmt = "Duplicate authority set change.")]
|
||||||
|
DuplicateAuthoritySetChange,
|
||||||
|
#[display(fmt = "Multiple pending forced authority set changes are not allowed.")]
|
||||||
|
MultiplePendingForcedAuthoritySetChanges,
|
||||||
|
#[display(
|
||||||
|
fmt = "A pending forced authority set change could not be applied since it must be applied after \
|
||||||
|
the pending standard change at #{}",
|
||||||
|
_0
|
||||||
|
)]
|
||||||
|
ForcedAuthoritySetChangeDependencyUnsatisfied(N),
|
||||||
#[display(fmt = "Invalid operation in the pending changes tree: {}", _0)]
|
#[display(fmt = "Invalid operation in the pending changes tree: {}", _0)]
|
||||||
ForkTree(fork_tree::Error<E>),
|
ForkTree(fork_tree::Error<E>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<N, E> From<fork_tree::Error<E>> for Error<N, E> {
|
||||||
|
fn from(err: fork_tree::Error<E>) -> Error<N, E> {
|
||||||
|
match err {
|
||||||
|
fork_tree::Error::Client(err) => Error::Client(err),
|
||||||
|
fork_tree::Error::Duplicate => Error::DuplicateAuthoritySetChange,
|
||||||
|
err => Error::ForkTree(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N, E: std::error::Error> From<E> for Error<N, E> {
|
||||||
|
fn from(err: E) -> Error<N, E> {
|
||||||
|
Error::Client(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A shared authority set.
|
/// A shared authority set.
|
||||||
pub struct SharedAuthoritySet<H, N> {
|
pub struct SharedAuthoritySet<H, N> {
|
||||||
inner: Arc<RwLock<AuthoritySet<H, N>>>,
|
inner: Arc<RwLock<AuthoritySet<H, N>>>,
|
||||||
@@ -116,14 +144,20 @@ pub struct AuthoritySet<H, N> {
|
|||||||
/// a given branch
|
/// a given branch
|
||||||
pub(crate) pending_standard_changes: ForkTree<H, N, PendingChange<H, N>>,
|
pub(crate) pending_standard_changes: ForkTree<H, N, PendingChange<H, N>>,
|
||||||
/// Pending forced changes across different forks (at most one per fork).
|
/// Pending forced changes across different forks (at most one per fork).
|
||||||
/// Forced changes are enacted on block depth (not finality), for this reason
|
/// Forced changes are enacted on block depth (not finality), for this
|
||||||
/// only one forced change should exist per fork.
|
/// reason only one forced change should exist per fork. When trying to
|
||||||
|
/// apply forced changes we keep track of any pending standard changes that
|
||||||
|
/// they may depend on, this is done by making sure that any pending change
|
||||||
|
/// that is an ancestor of the forced changed and its effective block number
|
||||||
|
/// is lower than the last finalized block (as signaled in the forced
|
||||||
|
/// change) must be applied beforehand.
|
||||||
pending_forced_changes: Vec<PendingChange<H, N>>,
|
pending_forced_changes: Vec<PendingChange<H, N>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H, N> AuthoritySet<H, N>
|
impl<H, N> AuthoritySet<H, N>
|
||||||
where H: PartialEq,
|
where
|
||||||
N: Ord,
|
H: PartialEq,
|
||||||
|
N: Ord,
|
||||||
{
|
{
|
||||||
// authority sets must be non-empty and all weights must be greater than 0
|
// authority sets must be non-empty and all weights must be greater than 0
|
||||||
fn invalid_authority_list(authorities: &AuthorityList) -> bool {
|
fn invalid_authority_list(authorities: &AuthorityList) -> bool {
|
||||||
@@ -185,7 +219,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
best_hash: &H,
|
best_hash: &H,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
) -> Result<Option<(H, N)>, fork_tree::Error<E>>
|
) -> Result<Option<(H, N)>, Error<N, E>>
|
||||||
where
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
@@ -224,7 +258,8 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
pending: PendingChange<H, N>,
|
pending: PendingChange<H, N>,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
) -> Result<(), Error<E>> where
|
) -> Result<(), Error<N, E>>
|
||||||
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
@@ -255,16 +290,18 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
pending: PendingChange<H, N>,
|
pending: PendingChange<H, N>,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
) -> Result<(), Error<E>> where
|
) -> Result<(), Error<N, E>>
|
||||||
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
for change in self.pending_forced_changes.iter() {
|
for change in &self.pending_forced_changes {
|
||||||
if change.canon_hash == pending.canon_hash ||
|
if change.canon_hash == pending.canon_hash {
|
||||||
is_descendent_of(&change.canon_hash, &pending.canon_hash)
|
return Err(Error::DuplicateAuthoritySetChange);
|
||||||
.map_err(fork_tree::Error::Client)?
|
}
|
||||||
{
|
|
||||||
return Err(fork_tree::Error::UnfinalizedAncestor.into());
|
if is_descendent_of(&change.canon_hash, &pending.canon_hash)? {
|
||||||
|
return Err(Error::MultiplePendingForcedAuthoritySetChanges);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +335,8 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
pending: PendingChange<H, N>,
|
pending: PendingChange<H, N>,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
) -> Result<(), Error<E>> where
|
) -> Result<(), Error<N, E>>
|
||||||
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
@@ -346,52 +384,92 @@ where
|
|||||||
///
|
///
|
||||||
/// These transitions are always forced and do not lead to justifications
|
/// These transitions are always forced and do not lead to justifications
|
||||||
/// which light clients can follow.
|
/// which light clients can follow.
|
||||||
|
///
|
||||||
|
/// Forced changes can only be applied after all pending standard changes
|
||||||
|
/// that it depends on have been applied. If any pending standard change
|
||||||
|
/// exists that is an ancestor of a given forced changed and which effective
|
||||||
|
/// block number is lower than the last finalized block (as defined by the
|
||||||
|
/// forced change), then the forced change cannot be applied. An error will
|
||||||
|
/// be returned in that case which will prevent block import.
|
||||||
pub(crate) fn apply_forced_changes<F, E>(
|
pub(crate) fn apply_forced_changes<F, E>(
|
||||||
&self,
|
&self,
|
||||||
best_hash: H,
|
best_hash: H,
|
||||||
best_number: N,
|
best_number: N,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
initial_sync: bool,
|
initial_sync: bool,
|
||||||
) -> Result<Option<(N, Self)>, E>
|
) -> Result<Option<(N, Self)>, Error<N, E>>
|
||||||
where F: Fn(&H, &H) -> Result<bool, E>,
|
where
|
||||||
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
let mut new_set = None;
|
let mut new_set = None;
|
||||||
|
|
||||||
for change in self.pending_forced_changes.iter()
|
for change in self
|
||||||
|
.pending_forced_changes
|
||||||
|
.iter()
|
||||||
.take_while(|c| c.effective_number() <= best_number) // to prevent iterating too far
|
.take_while(|c| c.effective_number() <= best_number) // to prevent iterating too far
|
||||||
.filter(|c| c.effective_number() == best_number)
|
.filter(|c| c.effective_number() == best_number)
|
||||||
{
|
{
|
||||||
// check if the given best block is in the same branch as
|
// check if the given best block is in the same branch as
|
||||||
// the block that signaled the change.
|
// the block that signaled the change.
|
||||||
if change.canon_hash == best_hash || is_descendent_of(&change.canon_hash, &best_hash)? {
|
if change.canon_hash == best_hash || is_descendent_of(&change.canon_hash, &best_hash)? {
|
||||||
|
let median_last_finalized = match change.delay_kind {
|
||||||
|
DelayKind::Best {
|
||||||
|
ref median_last_finalized,
|
||||||
|
} => median_last_finalized.clone(),
|
||||||
|
_ => unreachable!(
|
||||||
|
"pending_forced_changes only contains forced changes; forced changes have delay kind Best; qed."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// check if there's any pending standard change that we depend on
|
||||||
|
for (_, _, standard_change) in self.pending_standard_changes.roots() {
|
||||||
|
if standard_change.effective_number() <= median_last_finalized
|
||||||
|
&& is_descendent_of(&standard_change.canon_hash, &change.canon_hash)?
|
||||||
|
{
|
||||||
|
log::info!(target: "afg",
|
||||||
|
"Not applying authority set change forced at block #{:?}, due to pending standard change at block #{:?}",
|
||||||
|
change.canon_height,
|
||||||
|
standard_change.effective_number(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Err(
|
||||||
|
Error::ForcedAuthoritySetChangeDependencyUnsatisfied(
|
||||||
|
standard_change.effective_number()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// apply this change: make the set canonical
|
// apply this change: make the set canonical
|
||||||
afg_log!(initial_sync,
|
afg_log!(
|
||||||
|
initial_sync,
|
||||||
"👴 Applying authority set change forced at block #{:?}",
|
"👴 Applying authority set change forced at block #{:?}",
|
||||||
change.canon_height,
|
change.canon_height,
|
||||||
);
|
);
|
||||||
telemetry!(CONSENSUS_INFO; "afg.applying_forced_authority_set_change";
|
|
||||||
|
telemetry!(
|
||||||
|
CONSENSUS_INFO;
|
||||||
|
"afg.applying_forced_authority_set_change";
|
||||||
"block" => ?change.canon_height
|
"block" => ?change.canon_height
|
||||||
);
|
);
|
||||||
|
|
||||||
let median_last_finalized = match change.delay_kind {
|
new_set = Some((
|
||||||
DelayKind::Best { ref median_last_finalized } => median_last_finalized.clone(),
|
median_last_finalized,
|
||||||
_ => unreachable!("pending_forced_changes only contains forced changes; forced changes have delay kind Best; qed."),
|
AuthoritySet {
|
||||||
};
|
current_authorities: change.next_authorities.clone(),
|
||||||
|
set_id: self.set_id + 1,
|
||||||
new_set = Some((median_last_finalized, AuthoritySet {
|
pending_standard_changes: ForkTree::new(), // new set, new changes.
|
||||||
current_authorities: change.next_authorities.clone(),
|
pending_forced_changes: Vec::new(),
|
||||||
set_id: self.set_id + 1,
|
},
|
||||||
pending_standard_changes: ForkTree::new(), // new set, new changes.
|
));
|
||||||
pending_forced_changes: Vec::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't wipe forced changes until another change is
|
|
||||||
// applied
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we don't wipe forced changes until another change is applied, hence
|
||||||
|
// why we return a new set instead of mutating.
|
||||||
Ok(new_set)
|
Ok(new_set)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +489,8 @@ where
|
|||||||
finalized_number: N,
|
finalized_number: N,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
initial_sync: bool,
|
initial_sync: bool,
|
||||||
) -> Result<Status<H, N>, Error<E>> where
|
) -> Result<Status<H, N>, Error<N, E>>
|
||||||
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
@@ -434,12 +513,11 @@ where
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// we will keep all forced change for any later blocks and that are a
|
// we will keep all forced changes for any later blocks and that are a
|
||||||
// descendent of the finalized block (i.e. they are from this fork).
|
// descendent of the finalized block (i.e. they are part of this branch).
|
||||||
for change in pending_forced_changes {
|
for change in pending_forced_changes {
|
||||||
if change.effective_number() > finalized_number &&
|
if change.effective_number() > finalized_number &&
|
||||||
is_descendent_of(&finalized_hash, &change.canon_hash)
|
is_descendent_of(&finalized_hash, &change.canon_hash)?
|
||||||
.map_err(fork_tree::Error::Client)?
|
|
||||||
{
|
{
|
||||||
self.pending_forced_changes.push(change)
|
self.pending_forced_changes.push(change)
|
||||||
}
|
}
|
||||||
@@ -484,7 +562,8 @@ where
|
|||||||
finalized_hash: H,
|
finalized_hash: H,
|
||||||
finalized_number: N,
|
finalized_number: N,
|
||||||
is_descendent_of: &F,
|
is_descendent_of: &F,
|
||||||
) -> Result<Option<bool>, Error<E>> where
|
) -> Result<Option<bool>, Error<N, E>>
|
||||||
|
where
|
||||||
F: Fn(&H, &H) -> Result<bool, E>,
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
E: std::error::Error,
|
E: std::error::Error,
|
||||||
{
|
{
|
||||||
@@ -933,7 +1012,13 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
authorities.add_pending_change(change_a, &static_is_descendent_of(false)).unwrap();
|
authorities.add_pending_change(change_a, &static_is_descendent_of(false)).unwrap();
|
||||||
authorities.add_pending_change(change_b, &static_is_descendent_of(false)).unwrap();
|
authorities.add_pending_change(change_b.clone(), &static_is_descendent_of(false)).unwrap();
|
||||||
|
|
||||||
|
// no duplicates are allowed
|
||||||
|
assert!(matches!(
|
||||||
|
authorities.add_pending_change(change_b, &static_is_descendent_of(false)),
|
||||||
|
Err(Error::DuplicateAuthoritySetChange)
|
||||||
|
));
|
||||||
|
|
||||||
// there's an effective change triggered at block 15 but not a standard one.
|
// there's an effective change triggered at block 15 but not a standard one.
|
||||||
// so this should do nothing.
|
// so this should do nothing.
|
||||||
@@ -942,12 +1027,7 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// throw a standard change into the mix to prove that it's discarded
|
// there can only be one pending forced change per fork
|
||||||
// for being on the same fork.
|
|
||||||
//
|
|
||||||
// NOTE: after https://github.com/paritytech/substrate/issues/1861
|
|
||||||
// this should still be rejected based on the "span" rule -- it overlaps
|
|
||||||
// with another change on the same fork.
|
|
||||||
let change_c = PendingChange {
|
let change_c = PendingChange {
|
||||||
next_authorities: set_b.clone(),
|
next_authorities: set_b.clone(),
|
||||||
delay: 3,
|
delay: 3,
|
||||||
@@ -956,37 +1036,45 @@ mod tests {
|
|||||||
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_descendent_of_a = is_descendent_of(|base: &&str, _| {
|
let is_descendent_of_a = is_descendent_of(|base: &&str, _| base.starts_with("hash_a"));
|
||||||
base.starts_with("hash_a")
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(authorities.add_pending_change(change_c, &is_descendent_of_a).is_err());
|
assert!(matches!(
|
||||||
|
authorities.add_pending_change(change_c, &is_descendent_of_a),
|
||||||
|
Err(Error::MultiplePendingForcedAuthoritySetChanges)
|
||||||
|
));
|
||||||
|
|
||||||
// too early.
|
// let's try and apply the forced changes.
|
||||||
|
// too early and there's no forced changes to apply.
|
||||||
assert!(
|
assert!(
|
||||||
authorities.apply_forced_changes("hash_a10", 10, &static_is_descendent_of(true), false)
|
authorities
|
||||||
|
.apply_forced_changes("hash_a10", 10, &static_is_descendent_of(true), false)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
|
|
||||||
// too late.
|
// too late.
|
||||||
assert!(
|
assert!(
|
||||||
authorities.apply_forced_changes("hash_a16", 16, &static_is_descendent_of(true), false)
|
authorities
|
||||||
|
.apply_forced_changes("hash_a16", 16, &is_descendent_of_a, false)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
|
|
||||||
// on time -- chooses the right change.
|
// on time -- chooses the right change for this fork.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
authorities.apply_forced_changes("hash_a15", 15, &is_descendent_of_a, false)
|
authorities
|
||||||
|
.apply_forced_changes("hash_a15", 15, &is_descendent_of_a, false)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
(42, AuthoritySet {
|
(
|
||||||
current_authorities: set_a,
|
42,
|
||||||
set_id: 1,
|
AuthoritySet {
|
||||||
pending_standard_changes: ForkTree::new(),
|
current_authorities: set_a,
|
||||||
pending_forced_changes: Vec::new(),
|
set_id: 1,
|
||||||
}),
|
pending_standard_changes: ForkTree::new(),
|
||||||
|
pending_forced_changes: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,6 +1115,106 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forced_changes_blocked_by_standard_changes() {
|
||||||
|
let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 1)];
|
||||||
|
|
||||||
|
let mut authorities = AuthoritySet {
|
||||||
|
current_authorities: set_a.clone(),
|
||||||
|
set_id: 0,
|
||||||
|
pending_standard_changes: ForkTree::new(),
|
||||||
|
pending_forced_changes: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// effective at #15
|
||||||
|
let change_a = PendingChange {
|
||||||
|
next_authorities: set_a.clone(),
|
||||||
|
delay: 5,
|
||||||
|
canon_height: 10,
|
||||||
|
canon_hash: "hash_a",
|
||||||
|
delay_kind: DelayKind::Finalized,
|
||||||
|
};
|
||||||
|
|
||||||
|
// effective #20
|
||||||
|
let change_b = PendingChange {
|
||||||
|
next_authorities: set_a.clone(),
|
||||||
|
delay: 0,
|
||||||
|
canon_height: 20,
|
||||||
|
canon_hash: "hash_b",
|
||||||
|
delay_kind: DelayKind::Finalized,
|
||||||
|
};
|
||||||
|
|
||||||
|
// effective at #35
|
||||||
|
let change_c = PendingChange {
|
||||||
|
next_authorities: set_a.clone(),
|
||||||
|
delay: 5,
|
||||||
|
canon_height: 30,
|
||||||
|
canon_hash: "hash_c",
|
||||||
|
delay_kind: DelayKind::Finalized,
|
||||||
|
};
|
||||||
|
|
||||||
|
// add some pending standard changes all on the same fork
|
||||||
|
authorities.add_pending_change(change_a, &static_is_descendent_of(true)).unwrap();
|
||||||
|
authorities.add_pending_change(change_b, &static_is_descendent_of(true)).unwrap();
|
||||||
|
authorities.add_pending_change(change_c, &static_is_descendent_of(true)).unwrap();
|
||||||
|
|
||||||
|
// effective at #45
|
||||||
|
let change_d = PendingChange {
|
||||||
|
next_authorities: set_a.clone(),
|
||||||
|
delay: 5,
|
||||||
|
canon_height: 40,
|
||||||
|
canon_hash: "hash_d",
|
||||||
|
delay_kind: DelayKind::Best {
|
||||||
|
median_last_finalized: 31,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// now add a forced change on the same fork
|
||||||
|
authorities.add_pending_change(change_d, &static_is_descendent_of(true)).unwrap();
|
||||||
|
|
||||||
|
// the forced change cannot be applied since the pending changes it depends on
|
||||||
|
// have not been applied yet.
|
||||||
|
assert!(matches!(
|
||||||
|
authorities.apply_forced_changes("hash_d45", 45, &static_is_descendent_of(true), false),
|
||||||
|
Err(Error::ForcedAuthoritySetChangeDependencyUnsatisfied(15))
|
||||||
|
));
|
||||||
|
|
||||||
|
// we apply the first pending standard change at #15
|
||||||
|
authorities
|
||||||
|
.apply_standard_changes("hash_a15", 15, &static_is_descendent_of(true), false)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// but the forced change still depends on the next standard change
|
||||||
|
assert!(matches!(
|
||||||
|
authorities.apply_forced_changes("hash_d", 45, &static_is_descendent_of(true), false),
|
||||||
|
Err(Error::ForcedAuthoritySetChangeDependencyUnsatisfied(20))
|
||||||
|
));
|
||||||
|
|
||||||
|
// we apply the pending standard change at #20
|
||||||
|
authorities
|
||||||
|
.apply_standard_changes("hash_b", 20, &static_is_descendent_of(true), false)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// afterwards the forced change at #45 can already be applied since it signals
|
||||||
|
// that finality stalled at #31, and the next pending standard change is effective
|
||||||
|
// at #35. subsequent forced changes on the same branch must be kept
|
||||||
|
assert_eq!(
|
||||||
|
authorities
|
||||||
|
.apply_forced_changes("hash_d", 45, &static_is_descendent_of(true), false)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap(),
|
||||||
|
(
|
||||||
|
31,
|
||||||
|
AuthoritySet {
|
||||||
|
current_authorities: set_a.clone(),
|
||||||
|
set_id: 3,
|
||||||
|
pending_standard_changes: ForkTree::new(),
|
||||||
|
pending_forced_changes: Vec::new(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn next_change_works() {
|
fn next_change_works() {
|
||||||
let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)];
|
let current_authorities = vec![(AuthorityId::from_slice(&[1; 32]), 1)];
|
||||||
@@ -1283,26 +1471,11 @@ mod tests {
|
|||||||
add_pending_change(15, "C3", true);
|
add_pending_change(15, "C3", true);
|
||||||
add_pending_change(20, "D", true);
|
add_pending_change(20, "D", true);
|
||||||
|
|
||||||
println!(
|
|
||||||
"pending_changes: {:?}",
|
|
||||||
authorities
|
|
||||||
.pending_changes()
|
|
||||||
.map(|c| c.canon_hash)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
// applying the standard change at A should not prune anything
|
// applying the standard change at A should not prune anything
|
||||||
// other then the change that was applied
|
// other then the change that was applied
|
||||||
authorities
|
authorities
|
||||||
.apply_standard_changes("A", 5, &is_descendent_of, false)
|
.apply_standard_changes("A", 5, &is_descendent_of, false)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!(
|
|
||||||
"pending_changes: {:?}",
|
|
||||||
authorities
|
|
||||||
.pending_changes()
|
|
||||||
.map(|c| c.canon_hash)
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(authorities.pending_changes().count(), 6);
|
assert_eq!(authorities.pending_changes().count(), 6);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user