Skip to content

Commit abbf976

Browse files
committed
grandpa: ensure voting doesn't fail after a re-org
1 parent 15e94d4 commit abbf976

File tree

2 files changed

+118
-2
lines changed

2 files changed

+118
-2
lines changed

substrate/client/consensus/grandpa/src/environment.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1217,14 +1217,20 @@ where
12171217
.header(target_hash)?
12181218
.expect("Header known to exist after `finality_target` call; qed"),
12191219
Err(err) => {
1220-
warn!(
1220+
debug!(
12211221
target: LOG_TARGET,
12221222
"Encountered error finding best chain containing {:?}: couldn't find target block: {}",
12231223
block,
12241224
err,
12251225
);
12261226

1227-
return Ok(None)
1227+
// NOTE: in case the given `SelectChain` doesn't provide any block we fallback to using
1228+
// the given base block provided by the GRANDPA voter.
1229+
//
1230+
// For example, `LongestChain` will error if the given block to use as base isn't part
1231+
// of the best chain (as defined by `LongestChain`), which could happen if there was a
1232+
// re-org.
1233+
base_header.clone()
12281234
},
12291235
};
12301236

substrate/client/consensus/grandpa/src/tests.rs

+110
Original file line numberDiff line numberDiff line change
@@ -1820,6 +1820,116 @@ async fn grandpa_environment_checks_if_best_block_is_descendent_of_finality_targ
18201820
);
18211821
}
18221822

1823+
// This is a regression test for an issue that was triggered by a reorg
1824+
// - https://github.com/paritytech/polkadot-sdk/issues/3487
1825+
// - https://github.com/humanode-network/humanode/issues/1104
1826+
#[tokio::test]
1827+
async fn grandpa_environment_uses_round_base_block_for_voting_if_finality_target_errors() {
1828+
use finality_grandpa::voter::Environment;
1829+
use sp_consensus::SelectChain;
1830+
1831+
let peers = &[Ed25519Keyring::Alice];
1832+
let voters = make_ids(peers);
1833+
1834+
let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0);
1835+
let peer = net.peer(0);
1836+
let network_service = peer.network_service().clone();
1837+
let sync_service = peer.sync_service().clone();
1838+
let notification_service =
1839+
peer.take_notification_service(&grandpa_protocol_name::NAME.into()).unwrap();
1840+
let link = peer.data.lock().take().unwrap();
1841+
let client = peer.client().as_client().clone();
1842+
let select_chain = sc_consensus::LongestChain::new(peer.client().as_backend());
1843+
1844+
// create a chain that is 10 blocks long
1845+
peer.push_blocks(10, false);
1846+
1847+
let env = test_environment_with_select_chain(
1848+
&link,
1849+
None,
1850+
network_service.clone(),
1851+
sync_service,
1852+
notification_service,
1853+
select_chain.clone(),
1854+
VotingRulesBuilder::default().build(),
1855+
);
1856+
1857+
let hashof7 = client.expect_block_hash_from_id(&BlockId::Number(7)).unwrap();
1858+
let hashof8_a = client.expect_block_hash_from_id(&BlockId::Number(8)).unwrap();
1859+
1860+
// finalize the 7th block
1861+
peer.client().finalize_block(hashof7, None, false).unwrap();
1862+
1863+
assert_eq!(peer.client().info().finalized_hash, hashof7);
1864+
1865+
// simulate completed grandpa round
1866+
env.completed(
1867+
1,
1868+
finality_grandpa::round::State {
1869+
prevote_ghost: Some((hashof8_a, 8)),
1870+
finalized: Some((hashof7, 7)),
1871+
estimate: Some((hashof8_a, 8)),
1872+
completable: true,
1873+
},
1874+
Default::default(),
1875+
&finality_grandpa::HistoricalVotes::new(),
1876+
)
1877+
.unwrap();
1878+
1879+
// check simulated last completed round
1880+
assert_eq!(
1881+
env.voter_set_state.read().last_completed_round().state,
1882+
finality_grandpa::round::State {
1883+
prevote_ghost: Some((hashof8_a, 8)),
1884+
finalized: Some((hashof7, 7)),
1885+
estimate: Some((hashof8_a, 8)),
1886+
completable: true
1887+
}
1888+
);
1889+
1890+
// `hashof8_a` should be finalized next, `best_chain_containing` should return `hashof8_a`
1891+
assert_eq!(env.best_chain_containing(hashof8_a).await.unwrap().unwrap().0, hashof8_a);
1892+
1893+
// simulate reorg on block 8 by creating a fork starting at block 7 that is 10 blocks long
1894+
peer.generate_blocks_at(
1895+
BlockId::Number(7),
1896+
10,
1897+
BlockOrigin::File,
1898+
|mut builder| {
1899+
builder.push_deposit_log_digest_item(DigestItem::Other(vec![1])).unwrap();
1900+
builder.build().unwrap().block
1901+
},
1902+
false,
1903+
false,
1904+
true,
1905+
ForkChoiceStrategy::LongestChain,
1906+
);
1907+
1908+
// check that new best chain is on longest chain
1909+
assert_eq!(env.select_chain.best_chain().await.unwrap().number, 17);
1910+
1911+
// verify that last completed round has `prevote_ghost` and `estimate` blocks related to
1912+
// `hashof8_a`
1913+
assert_eq!(
1914+
env.voter_set_state.read().last_completed_round().state,
1915+
finality_grandpa::round::State {
1916+
prevote_ghost: Some((hashof8_a, 8)),
1917+
finalized: Some((hashof7, 7)),
1918+
estimate: Some((hashof8_a, 8)),
1919+
completable: true
1920+
}
1921+
);
1922+
1923+
// `hashof8_a` should be finalized next, `best_chain_containing` should still return `hashof8_a`
1924+
assert_eq!(env.best_chain_containing(hashof8_a).await.unwrap().unwrap().0, hashof8_a);
1925+
1926+
// simulate finalization of the `hashof8_a` block
1927+
peer.client().finalize_block(hashof8_a, None, false).unwrap();
1928+
1929+
// check that best chain is reorged back
1930+
assert_eq!(env.select_chain.best_chain().await.unwrap().number, 10);
1931+
}
1932+
18231933
#[tokio::test]
18241934
async fn grandpa_environment_never_overwrites_round_voter_state() {
18251935
use finality_grandpa::voter::Environment;

0 commit comments

Comments
 (0)