Files
pezkuwi-subxt/bridges/modules/ethereum/src/finality.rs
T
Svyatoslav Nikolsky c6c46462ab Sync ethereum headers using unsigned (substrate) transactions (#45)
* reward submitters on finalization

* PoA -> Substrate: unsigned_import_header API

* fix grumble

* make submitter part of ImportContext

* verify using next validators set + tests

* fix nostd compilation

* add sub-tx-mode argument

* support sub-tx-mode

* impl ValidateUnsigned for Runtime

* do not submit too much transactions to the pool

* cargo fmt

* fix bad merge

* revert license fix

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* Update modules/ethereum/src/verification.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* updated comment

* validate receipts before accepting unsigned tx to pool

* cargo fmt

* fix comment

* fix grumbles

* Update modules/ethereum/src/verification.rs

Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com>

* cargo fmt --all

* struct ChangeToEnact

* updated doc

* fix doc

* add docs to the code method

* simplify fn ancestry

* finish incomplete docs

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* Update modules/ethereum/src/lib.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* return err from unsigned_import_header

* get header once

* Update relays/ethereum/src/ethereum_sync.rs

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* fix

* UnsignedTooFarInTheFuture -> Custom(err.code())

* updated ImportContext::last_signal_block

* cargo fmt --all

* rename runtime calls

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
2024-04-10 10:28:37 +02:00

320 lines
9.2 KiB
Rust

// Copyright 2019-2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.
use crate::error::Error;
use crate::Storage;
use primitives::{public_to_address, Address, Header, SealedEmptyStep, H256};
use sp_io::crypto::secp256k1_ecdsa_recover;
use sp_std::prelude::*;
use sp_std::{
collections::{
btree_map::{BTreeMap, Entry},
btree_set::BTreeSet,
vec_deque::VecDeque,
},
iter::from_fn,
};
/// Tries to finalize blocks when given block is imported.
///
/// Returns numbers and hashes of finalized blocks in ascending order.
pub fn finalize_blocks<S: Storage>(
storage: &S,
best_finalized_hash: &H256,
header_validators: (&H256, &[Address]),
hash: &H256,
submitter: Option<&S::Submitter>,
header: &Header,
two_thirds_majority_transition: u64,
) -> Result<Vec<(u64, H256, Option<S::Submitter>)>, Error> {
// compute count of voters for every unfinalized block in ancestry
let validators = header_validators.1.iter().collect();
let (mut votes, mut headers) = prepare_votes(
storage,
best_finalized_hash,
&header_validators.0,
&validators,
hash,
header,
submitter,
two_thirds_majority_transition,
)?;
// now let's iterate in reverse order && find just finalized blocks
let mut newly_finalized = Vec::new();
while let Some((oldest_hash, oldest_number, submitter, signers)) = headers.pop_front() {
if !is_finalized(&validators, &votes, oldest_number >= two_thirds_majority_transition) {
break;
}
remove_signers_votes(&signers, &mut votes);
newly_finalized.push((oldest_number, oldest_hash, submitter));
}
Ok(newly_finalized)
}
/// Returns true if there are enough votes to treat this header as finalized.
fn is_finalized(
validators: &BTreeSet<&Address>,
votes: &BTreeMap<Address, u64>,
requires_two_thirds_majority: bool,
) -> bool {
(!requires_two_thirds_majority && votes.len() * 2 > validators.len())
|| (requires_two_thirds_majority && votes.len() * 3 > validators.len() * 2)
}
/// Prepare 'votes' of header and its ancestors' signers.
fn prepare_votes<S: Storage>(
storage: &S,
best_finalized_hash: &H256,
validators_begin: &H256,
validators: &BTreeSet<&Address>,
hash: &H256,
header: &Header,
submitter: Option<&S::Submitter>,
two_thirds_majority_transition: u64,
) -> Result<
(
BTreeMap<Address, u64>,
VecDeque<(H256, u64, Option<S::Submitter>, BTreeSet<Address>)>,
),
Error,
> {
// this fn can only work with single validators set
if !validators.contains(&header.author) {
return Err(Error::NotValidator);
}
// prepare iterator of signers of all ancestors of the header
// we only take ancestors that are not yet pruned and those signed by
// the same set of validators
let mut parent_empty_step_signers = empty_steps_signers(header);
let ancestry = ancestry(storage, header)
.map(|(hash, header, submitter)| {
let mut signers = BTreeSet::new();
sp_std::mem::swap(&mut signers, &mut parent_empty_step_signers);
signers.insert(header.author);
let empty_step_signers = empty_steps_signers(&header);
let res = (hash, header.number, submitter, signers);
parent_empty_step_signers = empty_step_signers;
res
})
.take_while(|&(hash, _, _, _)| hash != *validators_begin && hash != *best_finalized_hash);
// now let's iterate built iterator and compute number of validators
// 'voted' for each header
// we stop when finalized block is met (because we only interested in
// just finalized blocks)
let mut votes = BTreeMap::new();
let mut headers = VecDeque::new();
for (hash, number, submitter, signers) in ancestry {
add_signers_votes(validators, &signers, &mut votes)?;
if is_finalized(validators, &votes, number >= two_thirds_majority_transition) {
remove_signers_votes(&signers, &mut votes);
break;
}
headers.push_front((hash, number, submitter, signers));
}
// update votes with last header vote
let mut header_signers = BTreeSet::new();
header_signers.insert(header.author);
*votes.entry(header.author).or_insert(0) += 1;
headers.push_back((*hash, header.number, submitter.cloned(), header_signers));
Ok((votes, headers))
}
/// Increase count of 'votes' for every passed signer.
/// Fails if at least one of signers is not in the `validators` set.
fn add_signers_votes(
validators: &BTreeSet<&Address>,
signers_to_add: &BTreeSet<Address>,
votes: &mut BTreeMap<Address, u64>,
) -> Result<(), Error> {
for signer in signers_to_add {
if !validators.contains(signer) {
return Err(Error::NotValidator);
}
*votes.entry(*signer).or_insert(0) += 1;
}
Ok(())
}
/// Decrease 'votes' count for every passed signer.
fn remove_signers_votes(signers_to_remove: &BTreeSet<Address>, votes: &mut BTreeMap<Address, u64>) {
for signer in signers_to_remove {
match votes.entry(*signer) {
Entry::Occupied(mut entry) => {
if *entry.get() <= 1 {
entry.remove();
} else {
*entry.get_mut() -= 1;
}
}
Entry::Vacant(_) => unreachable!("we only remove signers that have been added; qed"),
}
}
}
/// Returns unique set of empty steps signers.
fn empty_steps_signers(header: &Header) -> BTreeSet<Address> {
header
.empty_steps()
.into_iter()
.flat_map(|steps| steps)
.filter_map(|step| empty_step_signer(&step, &header.parent_hash))
.collect::<BTreeSet<_>>()
}
/// Returns author of empty step signature.
fn empty_step_signer(empty_step: &SealedEmptyStep, parent_hash: &H256) -> Option<Address> {
let message = empty_step.message(parent_hash);
secp256k1_ecdsa_recover(empty_step.signature.as_fixed_bytes(), message.as_fixed_bytes())
.ok()
.map(|public| public_to_address(&public))
}
/// Return iterator of given header ancestors.
pub(crate) fn ancestry<'a, S: Storage>(
storage: &'a S,
header: &Header,
) -> impl Iterator<Item = (H256, Header, Option<S::Submitter>)> + 'a {
let mut parent_hash = header.parent_hash.clone();
from_fn(move || {
let (header, submitter) = storage.header(&parent_hash)?;
if header.number == 0 {
return None;
}
let hash = parent_hash.clone();
parent_hash = header.parent_hash.clone();
Some((hash, header, submitter))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{genesis, validator, validators_addresses, InMemoryStorage};
use crate::HeaderToImport;
#[test]
fn verifies_header_author() {
assert_eq!(
finalize_blocks(
&InMemoryStorage::new(genesis(), validators_addresses(5)),
&Default::default(),
(&Default::default(), &[]),
&Default::default(),
None,
&Header::default(),
0,
),
Err(Error::NotValidator),
);
}
#[test]
fn prepares_votes() {
// let's say we have 5 validators (we need 'votes' from 3 validators to achieve
// finality)
let mut storage = InMemoryStorage::new(genesis(), validators_addresses(5));
// when header#1 is inserted, nothing is finalized (1 vote)
let header1 = Header {
author: validator(0).address().as_fixed_bytes().into(),
parent_hash: genesis().hash(),
number: 1,
..Default::default()
};
let hash1 = header1.hash();
let mut header_to_import = HeaderToImport {
context: storage.import_context(None, &genesis().hash()).unwrap(),
is_best: true,
hash: hash1,
header: header1,
total_difficulty: 0.into(),
enacted_change: None,
scheduled_change: None,
};
assert_eq!(
finalize_blocks(
&storage,
&Default::default(),
(&Default::default(), &validators_addresses(5)),
&hash1,
None,
&header_to_import.header,
u64::max_value(),
),
Ok(Vec::new()),
);
storage.insert_header(header_to_import.clone());
// when header#2 is inserted, nothing is finalized (2 votes)
header_to_import.header = Header {
author: validator(1).address().as_fixed_bytes().into(),
parent_hash: hash1,
number: 2,
..Default::default()
};
header_to_import.hash = header_to_import.header.hash();
let hash2 = header_to_import.header.hash();
assert_eq!(
finalize_blocks(
&storage,
&Default::default(),
(&Default::default(), &validators_addresses(5)),
&hash2,
None,
&header_to_import.header,
u64::max_value(),
),
Ok(Vec::new()),
);
storage.insert_header(header_to_import.clone());
// when header#3 is inserted, header#1 is finalized (3 votes)
header_to_import.header = Header {
author: validator(2).address().as_fixed_bytes().into(),
parent_hash: hash2,
number: 3,
..Default::default()
};
header_to_import.hash = header_to_import.header.hash();
let hash3 = header_to_import.header.hash();
assert_eq!(
finalize_blocks(
&storage,
&Default::default(),
(&Default::default(), &validators_addresses(5)),
&hash3,
None,
&header_to_import.header,
u64::max_value(),
),
Ok(vec![(1, hash1, None)]),
);
storage.insert_header(header_to_import);
}
}