// This file is part of Substrate.
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
use sc_keystore::LocalKeystore;
use sc_network_test::Block;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use beefy_primitives::{crypto::Signature, Commitment, MmrRootHash, VoteMessage, KEY_TYPE};
use crate::keystore::{tests::Keyring, BeefyKeystore};
use super::*;
#[test]
fn note_round_works() {
let gv = GossipValidator::::new();
gv.note_round(1u64);
let live = gv.known_votes.read();
assert!(GossipValidator::::is_live(&live, &1u64));
drop(live);
gv.note_round(3u64);
gv.note_round(7u64);
gv.note_round(10u64);
let live = gv.known_votes.read();
assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS);
assert!(!GossipValidator::::is_live(&live, &1u64));
assert!(GossipValidator::::is_live(&live, &3u64));
assert!(GossipValidator::::is_live(&live, &7u64));
assert!(GossipValidator::::is_live(&live, &10u64));
}
#[test]
fn keeps_most_recent_max_rounds() {
let gv = GossipValidator::::new();
gv.note_round(3u64);
gv.note_round(7u64);
gv.note_round(10u64);
gv.note_round(1u64);
let live = gv.known_votes.read();
assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS);
assert!(GossipValidator::::is_live(&live, &3u64));
assert!(!GossipValidator::::is_live(&live, &1u64));
drop(live);
gv.note_round(23u64);
gv.note_round(15u64);
gv.note_round(20u64);
gv.note_round(2u64);
let live = gv.known_votes.read();
assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS);
assert!(GossipValidator::::is_live(&live, &15u64));
assert!(GossipValidator::::is_live(&live, &20u64));
assert!(GossipValidator::::is_live(&live, &23u64));
}
#[test]
fn note_same_round_twice() {
let gv = GossipValidator::::new();
gv.note_round(3u64);
gv.note_round(7u64);
gv.note_round(10u64);
let live = gv.known_votes.read();
assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS);
drop(live);
// note round #7 again -> should not change anything
gv.note_round(7u64);
let live = gv.known_votes.read();
assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS);
assert!(GossipValidator::::is_live(&live, &3u64));
assert!(GossipValidator::::is_live(&live, &7u64));
assert!(GossipValidator::::is_live(&live, &10u64));
}
struct TestContext;
impl ValidatorContext for TestContext {
fn broadcast_topic(&mut self, _topic: B::Hash, _force: bool) {
todo!()
}
fn broadcast_message(&mut self, _topic: B::Hash, _message: Vec, _force: bool) {
todo!()
}
fn send_message(&mut self, _who: &sc_network::PeerId, _message: Vec) {
todo!()
}
fn send_topic(&mut self, _who: &sc_network::PeerId, _topic: B::Hash, _force: bool) {
todo!()
}
}
fn sign_commitment(
who: &Keyring,
commitment: &Commitment,
) -> Signature {
let store: SyncCryptoStorePtr = std::sync::Arc::new(LocalKeystore::in_memory());
SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&who.to_seed())).unwrap();
let beefy_keystore: BeefyKeystore = Some(store).into();
beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap()
}
#[test]
fn should_avoid_verifying_signatures_twice() {
let gv = GossipValidator::::new();
let sender = sc_network::PeerId::random();
let mut context = TestContext;
let commitment =
Commitment { payload: MmrRootHash::default(), block_number: 3_u64, validator_set_id: 0 };
let signature = sign_commitment(&Keyring::Alice, &commitment);
let vote = VoteMessage { commitment, id: Keyring::Alice.public(), signature };
gv.note_round(3u64);
gv.note_round(7u64);
gv.note_round(10u64);
// first time the cache should be populated.
let res = gv.validate(&mut context, &sender, &vote.encode());
assert!(matches!(res, ValidationResult::ProcessAndKeep(_)));
assert_eq!(gv.known_votes.read().get(&vote.commitment.block_number).map(|x| x.len()), Some(1));
// second time we should hit the cache
let res = gv.validate(&mut context, &sender, &vote.encode());
assert!(matches!(res, ValidationResult::ProcessAndKeep(_)));
// next we should quickly reject if the round is not live.
gv.note_round(11_u64);
gv.note_round(12_u64);
assert!(!GossipValidator::::is_live(
&*gv.known_votes.read(),
&vote.commitment.block_number
));
let res = gv.validate(&mut context, &sender, &vote.encode());
assert!(matches!(res, ValidationResult::Discard));
}