Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
8f9808a
skeleton for finality tracker
rphmeier Jan 28, 2019
4e28e99
dispatch events when nothing finalized for a long time
rphmeier Jan 28, 2019
37af080
begin integrating finality tracker into grandpa
rphmeier Jan 29, 2019
1185041
add delay field to pending change
rphmeier Jan 29, 2019
1a48669
add has_api_with function to sr_version for querying APIs
rphmeier Jan 29, 2019
f84de0e
partially integrate new force changes into grandpa
rphmeier Jan 29, 2019
5b53bea
implement forced changes
rphmeier Jan 30, 2019
db20c36
get srml-grandpa compiling
rphmeier Jan 30, 2019
9df9464
Update core/finality-grandpa/src/authorities.rs
bkchr Jan 30, 2019
cc5d70b
Update core/finality-grandpa/src/authorities.rs
bkchr Jan 30, 2019
3e78042
Update core/finality-grandpa/src/authorities.rs
bkchr Jan 30, 2019
68c94bf
remove explicit dependence on CoreApi
rphmeier Jan 30, 2019
1fbb26c
increase node runtime version
rphmeier Jan 30, 2019
ecc489b
integrate grandpa forced changes into node runtime
rphmeier Jan 30, 2019
a75d5fd
add some tests to finality-tracker
rphmeier Jan 30, 2019
b9246b0
integrate finality tracking into node-runtime
rphmeier Jan 30, 2019
36dfdf7
Merge branch 'master' into rh-grandpa-offline-fallback
rphmeier Jan 30, 2019
619c7d4
test forced-change logic
rphmeier Jan 31, 2019
5e0a57a
test forced changes in the authority-set handler
rphmeier Jan 31, 2019
81ad7d7
kill some unneeded bounds in client
rphmeier Jan 31, 2019
a631a8d
test forced-changes in finality-grandpa and fix logic
rphmeier Jan 31, 2019
1ce7d40
Merge branch 'master' into rh-grandpa-offline-fallback: bump node ver…
rphmeier Jan 31, 2019
5ccd254
build wasm and finality-tracker is no-std
rphmeier Jan 31, 2019
b3291d4
Merge branch 'master' into rh-grandpa-offline-fallback
rphmeier Feb 4, 2019
386a5f9
restart voter on forced change
rphmeier Feb 5, 2019
7d1981c
Merge branch 'master' into rh-grandpa-offline-fallback
rphmeier Feb 11, 2019
a2add58
allow returning custom error type from lock_import_and_run
rphmeier Feb 14, 2019
35f444a
extract out most DB logic to aux_schema and use atomic client ops
rphmeier Feb 14, 2019
464f616
unify authority set writing
rphmeier Feb 14, 2019
bc2761f
implement set pausing
rphmeier Feb 14, 2019
08d5275
Merge branch 'master' into rh-grandpa-offline-fallback
rphmeier Feb 14, 2019
9518455
bump runtime version
rphmeier Feb 15, 2019
e7d24df
note on DB when we pause.
rphmeier Feb 15, 2019
8b018f1
Merge branch 'master' into rh-grandpa-offline-fallback
rphmeier Feb 15, 2019
4290f7d
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Feb 21, 2019
1a47254
core: grandpa: integrate forced changes with multiple pending standar…
andresilva Feb 22, 2019
e5b1c56
core: grandpa: fix AuthoritySet tests
andresilva Feb 22, 2019
f7420b7
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Feb 22, 2019
ad7a092
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Feb 25, 2019
0f21b3f
runtime: bump impl_version
andresilva Feb 25, 2019
2bcf2c7
core: clear pending justification requests after forced change import
andresilva Feb 25, 2019
33fe51f
srml: finality-tracker: use FinalizedInherentData
andresilva Feb 25, 2019
eb98d97
core: log requests for clearing justification requests
andresilva Feb 25, 2019
ed6b67e
core, node: update runtimes
andresilva Feb 25, 2019
ff1a8a5
core: grandpa: fix tests
andresilva Feb 25, 2019
30890ec
core: grandpa: remove todos and add comments
andresilva Feb 25, 2019
5bff073
core: grandpa: use has_api_with from ApiExt
andresilva Feb 25, 2019
f20746c
core: fix tests
andresilva Feb 25, 2019
c3d05fc
core: grandpa: remove unnecessary mut modifier
andresilva Feb 25, 2019
e537dd5
core: replace PostImportActions bitflags with struct
andresilva Feb 25, 2019
5d80af7
core: grandpa: restrict genesis on forced authority set change
andresilva Feb 26, 2019
abaccb3
core: grandpa: add more docs
andresilva Feb 26, 2019
94729c8
core: grandpa: prevent safety violations in Environment::finalize_block
andresilva Feb 26, 2019
9c43a28
core: grandpa: register finality tracker inherent data provider
andresilva Feb 26, 2019
3c37916
core: grandpa: fix tests
andresilva Feb 26, 2019
0976224
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Feb 26, 2019
de63d65
node: update runtime blobs
andresilva Feb 26, 2019
b5df296
core: grandpa: remove outdated todo
andresilva Mar 1, 2019
9799356
core: aura: fix typo in log message
andresilva Mar 1, 2019
fbd995b
core: grandpa: check re-finalization is on canonical chain
andresilva Mar 1, 2019
815410d
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Mar 1, 2019
d034c36
srml: finality-tracker: fix initialization
andresilva Mar 5, 2019
a391918
Merge branch 'master' into rh-grandpa-offline-fallback
andresilva Mar 5, 2019
93b98f7
node: update runtime wasm
andresilva Mar 5, 2019
cbb959f
srml: finality-tracker: don't re-initialize config keys
andresilva Mar 5, 2019
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
Prev Previous commit
Next Next commit
test forced-changes in finality-grandpa and fix logic
  • Loading branch information
rphmeier committed Jan 31, 2019
commit a631a8d198a2cb87c1f129c34f46d38e4a1ef5f5
224 changes: 129 additions & 95 deletions core/finality-grandpa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1089,55 +1089,51 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> GrandpaBlockImport<B, E, Block, RA
let digest = header.digest();

let api = self.api.runtime_api();
// check normal scheduled change.

// check for forced change.
{
let maybe_change = api.grandpa_pending_change(
let maybe_change = api.grandpa_forced_change(
&at,
digest,
);

match maybe_change {
Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()),
Err(e) => match api.version(&at) {
Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()),
Ok(version) => if version.has_api_with::<GrandpaApi<Block>, _>(|v| v >= 2) {
// API version is high enough to support forced changes
// but got error, so it is legitimate.
return Err(ConsensusErrorKind::ClientImport(e.to_string()).into())
}
}
Ok(None) => {},
Ok(Some(change)) => return Ok(Some(PendingChange {
next_authorities: change.next_authorities,
delay: change.delay,
canon_height: *header.number(),
canon_hash: hash,
delay_kind: DelayKind::Finalized,
delay_kind: DelayKind::Best,
})),
_ => {}
}
};
}

// check for forced change.
// check normal scheduled change.
{
let maybe_change = api.grandpa_forced_change(
let maybe_change = api.grandpa_pending_change(
&at,
digest,
);

match maybe_change {
Err(e) => match api.version(&at) {
Err(e) => Err(ConsensusErrorKind::ClientImport(e.to_string()).into()),
Ok(version) => if version.has_api_with::<GrandpaApi<Block>, _>(|v| v >= 2) {
// API version is high enough to support forced changes
// but got error, so it is legitimate.
Err(ConsensusErrorKind::ClientImport(e.to_string()).into())
} else {
// this might be ignoring some legitimate client
// errors but odds are those would have popped up
// in the previous API call.
Ok(None)
}
}
Ok(None) => Ok(None),
Err(e) => Err(ConsensusErrorKind::ClientImport(e.to_string()).into()),
Ok(Some(change)) => Ok(Some(PendingChange {
next_authorities: change.next_authorities,
delay: change.delay,
canon_height: *header.number(),
canon_hash: hash,
delay_kind: DelayKind::Best,
delay_kind: DelayKind::Finalized,
})),
Ok(None) => Ok(None),
}
}
}
Expand All @@ -1147,6 +1143,43 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> GrandpaBlockImport<B, E, Block, RA
{
use consensus_common::ForkChoiceStrategy;

// when we update the authorities, we need to hold the lock
// until the block is written to prevent a race if we need to restore
// the old authority set on error or panic.
struct InnerGuard<'a, T: 'a> {
old: Option<T>,
guard: Option<RwLockWriteGuard<'a, T>>,
}

impl<'a, T: 'a> InnerGuard<'a, T> {
fn as_mut(&mut self) -> &mut T {
&mut **self.guard.as_mut().expect("only taken on deconstruction; qed")
}

fn set_old(&mut self, old: T) {
if self.old.is_none() {
// ignore "newer" old changes.
self.old = Some(old);
}
}

fn consume(mut self) -> Option<(T, RwLockWriteGuard<'a, T>)> {
if let Some(old) = self.old.take() {
Some((old, self.guard.take().expect("only taken on deconstruction; qed")))
} else {
None
}
}
}

impl<'a, T: 'a> Drop for InnerGuard<'a, T> {
fn drop(&mut self) {
if let (Some(mut guard), Some(old)) = (self.guard.take(), self.old.take()) {
*guard = old;
}
}
}

let number = block.header.number().clone();
let maybe_change = self.check_new_change(
&block.header,
Expand Down Expand Up @@ -1178,85 +1211,75 @@ impl<B, E, Block: BlockT<Hash=H256>, RA, PRA> GrandpaBlockImport<B, E, Block, RA
)
};

// when we update the authorities, we need to hold the lock
// until the block is written to prevent a race if we need to restore
// the old authority set on error.
let just_in_case = {
let mut authorities = self.authority_set.inner().write();
let forced_change_set = authorities.apply_forced_changes(number, &canon_at_height)
.map_err(|e| ConsensusErrorKind::ClientImport(e.to_string()))
.map_err(ConsensusError::from)?;

if let Some(new_set) = forced_change_set {
// forced changes take priority over other scheduled changes
// because they apply on import and not finality.
let old_set = ::std::mem::replace(&mut *authorities, new_set);
Some((old_set, authorities))
} else if let Some(change) = maybe_change {
let parent_hash = *block.header.parent_hash();

let old_set = authorities.clone();

let is_equal_or_descendent_of = |base: &Block::Hash| -> Result<(), ConsensusError> {
let error = || {
debug!(target: "afg", "rejecting change: {} is in the same chain as {}",
hash, base);
Err(ConsensusErrorKind::ClientImport(
format!("Incorrect base hash")
).into())
};

if *base == hash { return error(); }
if *base == parent_hash { return error(); }

let tree_route = ::client::blockchain::tree_route(
self.inner.backend().blockchain(),
BlockId::Hash(parent_hash),
BlockId::Hash(*base),
);
let mut guard = InnerGuard {
guard: Some(self.authority_set.inner().write()),
old: None,
};

let tree_route = match tree_route {
Err(e) => return Err(
ConsensusErrorKind::ClientImport(e.to_string()).into()
),
Ok(route) => route,
};
// add any pending changes.
if let Some(change) = maybe_change {
let parent_hash = *block.header.parent_hash();

if tree_route.common_block().hash == *base {
return error();
}
let old = guard.as_mut().clone();
guard.set_old(old);

Ok(())
let is_equal_or_descendent_of = |base: &Block::Hash| -> Result<(), ConsensusError> {
let error = || {
debug!(target: "afg", "rejecting change: {} is in the same chain as {}",
hash, base);
Err(ConsensusErrorKind::ClientImport(
format!("Incorrect base hash")
).into())
};

authorities.add_pending_change(
change,
is_equal_or_descendent_of,
)?;
if *base == hash { return error(); }
if *base == parent_hash { return error(); }

Some((old_set, authorities))
} else {
None
}
};
let tree_route = ::client::blockchain::tree_route(
self.inner.backend().blockchain(),
BlockId::Hash(parent_hash),
BlockId::Hash(*base),
);

let needs_justification = {
let read_lock;
let set_ref = match just_in_case {
None => {
read_lock = self.authority_set.inner().read();
&*read_lock
let tree_route = match tree_route {
Err(e) => return Err(
ConsensusErrorKind::ClientImport(e.to_string()).into()
),
Ok(route) => route,
};

if tree_route.common_block().hash == *base {
return error();
}
Some((_, ref authorities)) => &*authorities,

Ok(())
};

// note: if there has been a forced set change then
// pending changes have been wiped at this point.
set_ref.enacts_standard_change(number, &canon_at_height)
guard.as_mut().add_pending_change(
change,
is_equal_or_descendent_of,
)?;
}

let needs_justification = {
let forced_change_set = guard.as_mut().apply_forced_changes(number, &canon_at_height)
.map_err(|e| ConsensusErrorKind::ClientImport(e.to_string()))
.map_err(ConsensusError::from)?
.map_err(ConsensusError::from)?;

if let Some(new_set) = forced_change_set {
let old = ::std::mem::replace(guard.as_mut(), new_set);
guard.set_old(old);

false
} else {
guard.as_mut().enacts_standard_change(number, &canon_at_height)
.map_err(|e| ConsensusErrorKind::ClientImport(e.to_string()))
.map_err(ConsensusError::from)?
}
};

// consume the guard safely.
let just_in_case = guard.consume();
if let Some((_, ref authorities)) = just_in_case {
block.auxiliary.push((AUTHORITY_SET_KEY.to_vec(), Some(authorities.encode())));
}
Expand Down Expand Up @@ -1465,30 +1488,41 @@ fn canonical_at_height<B, E, Block: BlockT<Hash=H256>, RA>(
B: Backend<Block, Blake2Hasher>,
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
{
use runtime_primitives::traits::{One, Zero};
use runtime_primitives::traits::{One, Zero, BlockNumberToHash};

if height > base.1 {
return Ok(None);
}

if height == base.1 && base_is_canonical {
return Ok(Some(base.0));
if height == base.1 {
if base_is_canonical {
return Ok(Some(base.0));
} else {
return Ok(client.block_number_to_hash(height));
}
} else if base_is_canonical {
return Ok(client.block_number_to_hash(height));
}

let mut current = match client.header(&BlockId::Hash(base.0))? {
let one = NumberFor::<Block>::one();

// start by getting _canonical_ block with number at parent position and then iterating
// backwards by hash.
let mut current = match client.header(&BlockId::Number(base.1 - one))? {
Some(header) => header,
_ => return Ok(None),
};

let mut steps = base.1 - height;
// we've already checked that base > height above.
let mut steps = base.1 - height - one;

while steps > NumberFor::<Block>::zero() {
current = match client.header(&BlockId::Hash(*current.parent_hash()))? {
Some(header) => header,
_ => return Ok(None),
};

steps -= NumberFor::<Block>::one();
steps -= one;
}

Ok(Some(current.hash()))
Expand Down
61 changes: 59 additions & 2 deletions core/finality-grandpa/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,15 @@ impl Network<Block> for MessageRouting {
struct TestApi {
genesis_authorities: Vec<(Ed25519AuthorityId, u64)>,
scheduled_changes: Arc<Mutex<HashMap<Hash, ScheduledChange<BlockNumber>>>>,
forced_changes: Arc<Mutex<HashMap<Hash, ScheduledChange<BlockNumber>>>>,
}

impl TestApi {
fn new(genesis_authorities: Vec<(Ed25519AuthorityId, u64)>) -> Self {
TestApi {
genesis_authorities,
scheduled_changes: Arc::new(Mutex::new(HashMap::new())),
forced_changes: Arc::new(Mutex::new(HashMap::new())),
}
}
}
Expand Down Expand Up @@ -322,10 +324,17 @@ impl GrandpaApi<Block> for RuntimeApi {
Ok(self.inner.scheduled_changes.lock().get(&parent_hash).map(|c| c.clone()))
}

fn grandpa_forced_change(&self, _at: &BlockId<Block>, _: &DigestFor<Block>)
fn grandpa_forced_change(&self, at: &BlockId<Block>, _: &DigestFor<Block>)
-> Result<Option<ScheduledChange<NumberFor<Block>>>>
{
Ok(None)
let parent_hash = match at {
&BlockId::Hash(at) => at,
_ => panic!("not requested by block hash!!"),
};

// we take only scheduled changes at given block number where there are no
// extrinsics.
Ok(self.inner.forced_changes.lock().get(&parent_hash).map(|c| c.clone()))
}
}

Expand Down Expand Up @@ -778,3 +787,51 @@ fn doesnt_vote_on_the_tip_of_the_chain() {
// the highest block to be finalized will be 3/4 deep in the unfinalized chain
assert_eq!(highest, 75);
}

#[test]
fn force_change_to_new_set() {
// two of these guys are offline.
let genesis_authorities = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie, Keyring::One, Keyring::Two];
let peers_a = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie];
let api = TestApi::new(make_ids(genesis_authorities));

let voters = make_ids(peers_a);
let normal_transitions = api.scheduled_changes.clone();
let forced_transitions = api.forced_changes.clone();
let net = GrandpaTestNet::new(api, 3);
let net = Arc::new(Mutex::new(net));

net.lock().peer(0).push_blocks(1, false);

{
// add a forced transition at block 12.
let parent_hash = net.lock().peer(0).client().info().unwrap().chain.best_hash;
forced_transitions.lock().insert(parent_hash, ScheduledChange {
next_authorities: voters.clone(),
delay: 10,
});

// add a normal transition too to ensure that forced changes take priority.
normal_transitions.lock().insert(parent_hash, ScheduledChange {
next_authorities: make_ids(genesis_authorities),
delay: 5,
});
}

net.lock().peer(0).push_blocks(25, false);
net.lock().sync();

for (i, peer) in net.lock().peers().iter().enumerate() {
assert_eq!(peer.client().info().unwrap().chain.best_number, 26,
"Peer #{} failed to sync", i);

let set_raw = peer.client().backend().get_aux(::AUTHORITY_SET_KEY).unwrap().unwrap();
let set = AuthoritySet::<Hash, BlockNumber>::decode(&mut &set_raw[..]).unwrap();

assert_eq!(set.current(), (1, voters.as_slice()));
assert_eq!(set.pending_changes().len(), 0);
}

// it will only finalize if the forced transition happens.
run_to_completion(25, net, peers_a);
}